godaddy-tofu

godaddy-tofu forgejo

Notes

Review 4
  • Verdict: READY

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- Standalone, discovered during PR #37 review
    • [x] Repo -- ldraney/godaddy-tofu
    • [x] User Story -- present, well-formed
    • [x] Context -- clear explanation of concurrent pipeline failures (#5 and #6)
    • [x] File Targets -- .woodpecker.yml identified
    • [x] Feature Flag -- None (appropriate for CI config)
    • [x] Acceptance Criteria -- 3 criteria, testable
    • [x] Test Expectations -- 2 manual verification steps
    • [x] Constraints -- golang:1.26 image, secrets, Woodpecker when conditions
    • [x] Checklist -- present
    • [x] Related -- present

    Traceability

    • [x] story:dns-iac label -- DNS as IaC
    • [x] story note verified -- story-godaddy-tofu-dns-iac exists in project-godaddy-tofu user-stories section
    • [x] arch:godaddy-tofu label -- provider component
    • [ ] arch note MISSING -- no arch-godaddy-tofu note found in pal-e-docs. However, architecture docs exist in-repo at docs/architecture.md. Acceptable for this repo's maturity level -- the arch label correctly references the component even without a pal-e-docs note.
    • [x] Forgejo issue -- https://forgejo.tail5b443a.ts.net/ldraney/godaddy-tofu/issues/38, open

    File Targets

    • [x] .woodpecker.yml -- verified: exists, currently has a single pipeline with both build and test steps running on all push+pull_request events. Structure matches what the issue describes: build (go build, go vet) and test (go test ./pkg/godaddy/) in one undifferentiated pipeline with a global when: event: [push, pull_request] trigger.

    Repo Placement

    Correct. The CI config lives in this repo and the fix is scoped entirely to this repo.

    Dependencies

    No blockers. All other board items are in the done column. This is standalone work with no upstream or downstream dependencies on the board.

    Acceptance Criteria

    All 3 criteria are verifiable:

    • "PRs trigger build + vet only" -- verifiable by opening a test PR and checking Woodpecker pipeline steps
    • "Pushes to main trigger build + vet + integration tests" -- verifiable by merging and observing pipeline
    • "No concurrent integration test runs possible" -- inherent from Woodpecker's pipeline serialization when only main-push triggers integration tests

    Test Expectations are manual verification steps (open PR, merge to main) -- appropriate for CI config changes where automated test verification would be circular.

    Blast Radius

    Low. This change only affects .woodpecker.yml. No downstream consumers are affected. Note: root-level acceptance tests (resource_dns_record_test.go, data_source_dns_records_test.go) use resource.Test() which requires TF_ACC=1. These are not currently run by CI and the ticket does not address them -- this is a separate concern and does not block this ticket.

    Integration tests in pkg/godaddy/ self-skip via testClient(t) when env vars are absent, except for two pure unit tests (TestNewClient, TestNewClientWithBaseURL) which always run. The split is clean: build-only PRs will still pass the unit tests if go test is run without credentials.

    Decomposition Assessment

    No decomposition needed. Single file target, 3 acceptance criteria, single repo. Estimated agent work well under 5 minutes. This is a straightforward Woodpecker YAML restructuring.

    Recommendation

    No action needed.

  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- present (but contains wrong dependency reference)
    • [x] Repo -- ldraney/godaddy-tofu
    • [x] User Story -- present
    • [x] Context -- present, good background
    • [x] File Targets -- present, 3 files listed
    • [x] Feature Flag -- present (None)
    • [x] Acceptance Criteria -- present, 5 items
    • [x] Test Expectations -- present, 3 items
    • [x] Constraints -- present, 4 items
    • [x] Checklist -- present
    • [x] Related -- present

    Traceability

    • [x] story:dns-iac label -- present on board item
    • [x] story note reference -- dns-iac entry exists in project-godaddy-tofu user-stories table (Key: dns-iac, links to story-godaddy-tofu-dns-iac)
    • [ ] story note MISSING -- story-godaddy-tofu-dns-iac note does not exist in pal-e-docs (search returned 0 results). The project page links to it but the note was never created. [SCOPE] Create story note story-godaddy-tofu-dns-iac.
    • [x] arch:godaddy-tofu label -- present on board item
    • [ ] arch note MISSING -- No arch-godaddy-tofu note found in pal-e-docs. [SCOPE] Create architecture note arch-godaddy-tofu.
    • [x] Forgejo issue -- ldraney/godaddy-tofu#4, state: open

    File Targets

    • [x] pkg/godaddy/client_test.go -- does not yet exist (to be created). Client code exists in HEAD at pkg/godaddy/client.go. Verified: NewClient(), WithBaseURL(), WithHTTPClient() options pattern is available for test setup.
    • [x] pkg/godaddy/dns_test.go -- does not yet exist (to be created). DNS methods verified in HEAD:
      • GetRecords(ctx, domain, recordType, name) -- matches ticket's TestGetRecords
      • AddRecords(ctx, domain, records []DNSRecord) -- ticket says TestAddAndDeleteRecord (singular) but method is AddRecords (plural, takes slice)
      • DeleteRecords(ctx, domain, recordType, name) -- available for cleanup
      • ReplaceRecords(ctx, domain, recordType, name, records) -- matches TestReplaceRecords
      • ReplaceAllRecords(ctx, domain, records) -- NOT covered in test plan
      • ReplaceRecordsByType(ctx, domain, recordType, records) -- NOT covered in test plan
    • [x] pkg/godaddy/domains_test.go -- does not yet exist (to be created). Domain methods verified in HEAD:
      • ListDomains(ctx) -- matches TestListDomains
      • GetDomain(ctx, domain) -- matches TestGetDomain

    Repo Placement

    OK -- issue is filed on ldraney/godaddy-tofu, all file targets are in the same repo.

    Dependencies

    • [BODY] Wrong dependency reference -- Issue says "Depends on #2" but #2 is the docs foundation PR. The Go scaffold that created pkg/godaddy/ is issue #3 / PR #5. Should say "Depends on #3".
    • #3 (Go scaffold) is in done column on the board -- dependency is satisfied.
    • Issue #6 relationship -- Issue #6 (QA nits: DomainID float64->int64, io.ReadAll size limit, url variable naming) is on the board in backlog (item #1454, 1 point). These nits affect the code that #4's tests will exercise:
      • DomainID float64 -- domain tests in #4 will deserialize this field. If #6 changes it to int64, tests written against float64 will break. Recommendation: land #6 first, or acknowledge in #4 that Domain struct may change.
      • io.ReadAll unbounded -- not directly tested by #4 but could affect error handling tests.
      • url vs reqURL in ListDomains -- confirmed: line 37 of domains.go uses url local var, shadows net/url import. Not a test-breaking issue but is a code smell the tests will exercise.

    Acceptance Criteria

    • [x] "go test ./pkg/godaddy/ -v passes" -- verifiable by running command
    • [x] "DNS tests create, read, and delete a test record without leaving state" -- verifiable by test teardown logic
    • [x] "Domain tests read existing domain data" -- verifiable
    • [x] "Tests skip gracefully when credentials are not set" -- verifiable by running without env vars
    • [x] "Tests handle 429 rate limiting with backoff" -- verifiable but the client has no retry/backoff logic. Tests cannot handle 429 if the client doesn't. Either the test must implement its own retry wrapper, or this AC implies adding retry logic to the client (scope creep beyond "integration tests").

    Blast Radius

    Low. These are new test files -- no existing code is modified. The only risk is if tests accidentally mutate production DNS records on palinks.app. The constraint to use TXT records mitigates this. No downstream consumers are affected.

    Decomposition Assessment

    3 file targets in 1 repo, 5 acceptance criteria, estimated agent time ~3-4 minutes. No decomposition needed.

    Recommendations

    • [BODY] Fix dependency reference: "Depends on #2" should be "Depends on #3" (the Go scaffold issue).
    • [BODY] Fix test name TestAddAndDeleteRecord to reflect that the actual method is AddRecords (plural, takes a slice) -- suggest TestAddAndDeleteRecords.
    • [BODY] Address #6 relationship: add note that #6 (QA nits) should land first to avoid DomainID type churn in domain tests. Or explicitly state tests should be written against the post-#6 struct.
    • [BODY] Clarify AC for 429 handling: the client has no retry logic. Either (a) descope the 429 AC from this ticket and create a separate ticket for client retry logic, or (b) expand scope to include retry in the client. Current scope is ambiguous.
    • [BODY] Consider adding test coverage for ReplaceAllRecords and ReplaceRecordsByType methods (2 of 6 DNS methods are untested by this plan).
    • [SCOPE] Create story note story-godaddy-tofu-dns-iac in pal-e-docs (referenced from project page but does not exist).
    • [SCOPE] Create architecture note arch-godaddy-tofu in pal-e-docs (board item has arch:godaddy-tofu label but no backing note).
  • Verdict: READY

    Re-review of board item #1424. Previous review review-1424-2026-06-13 returned NEEDS_REFINEMENT with two [SCOPE] items. Both have been resolved.

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- depends on #1 (closed/merged)
    • [x] Repo -- ldraney/godaddy-tofu
    • [x] User Story -- present and well-formed
    • [x] Context -- sufficient background on Go module, GoDaddy API, provider skeleton
    • [x] File Targets -- 6 file entries with method signatures
    • [x] Feature Flag -- None (appropriate for scaffold)
    • [x] Acceptance Criteria -- 6 items, all verifiable
    • [x] Test Expectations -- 2 items (go build, go vet)
    • [x] Constraints -- 4 items covering Go conventions, net/http, encoding/json, stubs only
    • [x] Checklist -- 6 items
    • [x] Related -- project and doc references

    Traceability

    • [x] story:dns-iac label -- present on board item
    • [x] story entry verified -- dns-iac row found in project-godaddy-tofu user-stories section (links to story-godaddy-tofu-dns-iac)
    • [x] story note verified -- story-godaddy-tofu-dns-iac exists in pal-e-docs (note_type: user-story, status: active). Well-formed with Role, Want, So That, AC, Success Metric, Related Architecture sections.
    • [x] arch:godaddy-tofu label -- present on board item
    • [x] arch notes verified -- all three architecture notes exist in pal-e-docs:
      • arch-domain-godaddy-tofu -- domain model with mermaid ER diagram (PROVIDER, DNS_RECORD, DOMAIN entities)
      • arch-dataflow-godaddy-tofu -- data flow with mermaid sequence diagram (plan/apply/destroy flows)
      • arch-deployment-godaddy-tofu -- deployment with mermaid graph (dev/CI/consumption pipeline)
    • [x] Forgejo issue -- ldraney/godaddy-tofu#3, state: open

    File Targets

    • [x] go.mod / go.sum -- to create (greenfield repo, correct)
    • [x] main.go -- to create (provider entry point)
    • [x] provider.go -- to create (provider skeleton)
    • [x] pkg/godaddy/client.go -- to create (Client struct, NewClient, sso-key auth). Auth format consistent with docs/auth.md at HEAD.
    • [x] pkg/godaddy/dns.go -- to create (6 DNS methods). Methods consistent with docs/dns-endpoints.md at HEAD.
    • [x] pkg/godaddy/domains.go -- to create (ListDomains, GetDomain). Read-only domain methods.

    All file targets are new files in a greenfield repo. No conflicts with existing files. Reference docs (docs/dns-endpoints.md, docs/auth.md, docs/swagger_domains.json) exist at HEAD on main and are consistent with file target specifications.

    Repo Placement

    OK. Issue filed on ldraney/godaddy-tofu, all work is in ldraney/godaddy-tofu. Single repo scope.

    Dependencies

    Issue #1 (docs foundation) is closed and merged (commit c2119c7). Board item #1423 is in done column. Issue #4 (board item #1425, 3 pts) is a follow-up in backlog that depends on this ticket -- not a blocker. No undocumented dependencies.

    Acceptance Criteria

    6 criteria, all agent-verifiable:

    • go build . -- direct command
    • go vet ./... -- direct command
    • NewClient instantiation -- verifiable via go build or simple test
    • DNS method signatures -- verifiable via go vet and compilation
    • Domain method signatures -- verifiable via go vet and compilation
    • Provider skeleton with terraform-plugin-framework -- verifiable via go build and tofu init

    All criteria are clear and testable. No missing criteria identified.

    Blast Radius

    Greenfield repo with no existing consumers. No sibling services affected. No downstream dependencies. Clean scope.

    Decomposition Assessment

    6 file targets in 1 repo, 6 AC. The work is a single coherent Go scaffold -- all files are tightly coupled (provider depends on client, client has dns/domains). Estimated agent time: ~5 minutes. No decomposition needed -- this is a single cohesive unit that would be harder to split than to do in one pass.

    Previous Review Resolution

    • [x] [SCOPE] story note story-godaddy-tofu-dns-iac -- RESOLVED. Note created with note_type: user-story, status: active. Contains well-formed Role/Want/So-That/AC/Success-Metric.
    • [x] [SCOPE] arch note arch-godaddy-tofu -- RESOLVED. Three architecture notes created (domain, dataflow, deployment), all with mermaid diagrams and key-decisions sections.

    Recommendation

    No action needed. All scope items from the previous review have been resolved. Ticket is ready to move to next_up.

  • Verdict: NEEDS_REFINEMENT

    Template Completeness

    • [x] Type -- Feature
    • [x] Lineage -- depends on #1 (closed)
    • [x] Repo -- ldraney/godaddy-tofu
    • [x] User Story -- present and well-formed
    • [x] Context -- sufficient background on Go module, GoDaddy API, provider skeleton
    • [x] File Targets -- 8 files listed with method signatures
    • [x] Feature Flag -- None (appropriate for scaffold)
    • [x] Acceptance Criteria -- 6 items, all verifiable
    • [x] Test Expectations -- 2 items (go build, go vet)
    • [x] Constraints -- 4 items covering Go conventions, net/http, encoding/json, stubs only
    • [x] Checklist -- 6 items
    • [x] Related -- project and doc references

    Traceability

    • [x] story:dns-iac label -- present on board item
    • [x] story entry verified -- dns-iac row found in project-godaddy-tofu user-stories section
    • [ ] story note MISSING -- story-godaddy-tofu-dns-iac does not exist in pal-e-docs. [SCOPE] Create story note story-godaddy-tofu-dns-iac.
    • [x] arch:godaddy-tofu label -- present on board item
    • [ ] arch note MISSING -- no arch-godaddy-tofu note found in pal-e-docs. [SCOPE] Create architecture note arch-godaddy-tofu.
    • [x] Forgejo issue -- ldraney/godaddy-tofu#3, open

    File Targets

    • [x] go.mod / go.sum -- to create (greenfield repo, correct)
    • [x] main.go -- to create (provider entry point)
    • [x] provider.go -- to create (provider skeleton)
    • [x] pkg/godaddy/client.go -- to create (Client struct, NewClient, sso-key auth). Auth format matches docs/auth.md.
    • [x] pkg/godaddy/dns.go -- to create (6 DNS methods). Methods match exactly with docs/dns-endpoints.md endpoint map.
    • [x] pkg/godaddy/domains.go -- to create (ListDomains, GetDomain). Read-only domain methods.

    All file targets are new files in a greenfield repo. No conflicts with existing files. Reference docs (docs/dns-endpoints.md, docs/auth.md, docs/swagger_domains.json) exist and are consistent with the file target specifications.

    Repo Placement

    OK. Issue filed on ldraney/godaddy-tofu, all work is in ldraney/godaddy-tofu. Single repo scope.

    Dependencies

    Issue #1 (docs foundation) is closed and merged -- dependency satisfied. Board item #1423 is in done column. Issue #4 (integration tests) is a follow-up that depends on this ticket, not a blocker. No undocumented dependencies.

    Acceptance Criteria

    6 criteria, all agent-verifiable:

    • go build . -- direct command
    • go vet ./... -- direct command
    • NewClient instantiation -- verifiable via go build or simple test
    • DNS method signatures -- verifiable via go vet and compilation
    • Domain method signatures -- verifiable via go vet and compilation
    • Provider skeleton with terraform-plugin-framework -- verifiable via go build and tofu init

    All criteria are clear and testable. No missing criteria identified.

    Blast Radius

    Greenfield repo with no existing consumers. No sibling services affected. No downstream dependencies. Clean scope.

    Decomposition Assessment

    8 file targets in 1 repo, 6 AC. The AC count is at the threshold (>5) but the work is a single coherent Go scaffold -- all files are tightly coupled (provider depends on client, client has dns/domains). Estimated agent time: ~5 minutes. No decomposition needed -- this is a single cohesive unit that would be harder to split than to do in one pass.

    Recommendation

    • [SCOPE] Create story note story-godaddy-tofu-dns-iac in pal-e-docs. The user-stories table on project-godaddy-tofu references it but the note does not exist.
    • [SCOPE] Create architecture note arch-godaddy-tofu in pal-e-docs. The board item carries the arch:godaddy-tofu label but no backing architecture note exists.
Project Page 1
  • Project: GoDaddy Tofu project-godaddy-tofu

    Vision

    Custom OpenTofu provider for the GoDaddy API. Manages DNS records and domains as IaC, consumed by pal-e-platform/terraform/. Replaces abandoned community providers (veksh/godaddy-dns, n3integration/godaddy — one deletes unmanaged records). Built in Go with the Terraform Plugin Framework. First dogfood target: palinks.app DNS records.

    User Stories

    Key Story Note Role Success Metric
    dns-iac story-godaddy-tofu-dns-iac Platform operator palinks.app A/CNAME records managed via tofu apply
    domain-iac story-godaddy-tofu-domain-iac Platform operator Domain details readable via tofu plan

    Architecture

    Go provider built on Terraform Plugin Framework. Internal Go client package (pkg/godaddy/) wraps the GoDaddy REST API directly — no Python SDK dependency.

    • Resources: godaddy_dns_record, godaddy_domain
    • Data sources: godaddy_domains, godaddy_dns_records
    • Auth: sso-key {key}:{secret} header from provider config
    1. Domain Model — provider resources, data sources, and their GoDaddy API mapping
    2. Data Flow — how tofu plan/apply translates to GoDaddy API calls
    3. Deployment — provider registry, pal-e-platform consumption
    4. DNS as Foundational Dependency — why DNS is the technical heart of every project that serves users

    Key decisions:

    • Go client package inside provider repo (not separate SDK) — single language, single test suite
    • Python SDK dropped — Tofu providers must be Go, no cross-language benefit
    • DNS records as individual resources (not bulk) — safer than replace-all semantics

    Board

    board-godaddy-tofu

    Integration tests merged (PR #8, closes #4): full coverage of all 8 client methods against live GoDaddy API. Docs restructured (PR #9): README is now lightweight intro + TOC, detailed architecture/deployment docs with mermaid diagrams live in docs/. QA nits from PR #8 tracked in #10. Next: DNS resource implementation, DNS data source, provider acceptance tests.

    QA nits fixed (PR #7, closes #6): DomainID changed to int64, io.ReadAll capped at 1MB, url variable naming consistent. Go scaffold and DNS client solid. Next: integration tests (#4).

    • 2026-06-13 — Repo initialized, docs foundation merged (#1/#2)
    • 2026-06-14 — Go scaffold + DNS client merged (#3/PR #5). Provider compiles, all client methods implemented.
    • 2026-06-14 — QA nits fixed (PR #7, closes #6): DomainID int64, io.ReadAll limit, url variable naming.
    • 2026-06-14 — Integration tests merged (PR #8, closes #4): all 8 client methods tested against live API.
    • 2026-06-14 — Docs restructured (PR #9): README as intro+TOC, docs/architecture.md and docs/deployment.md with mermaid diagrams.

    • 2026-06-13 — Repo initialized, docs foundation merged (#1/#2)
    • 2026-06-14 — Go scaffold + DNS client merged (#3/PR #5). Provider compiles, all client methods implemented.

    Repo Platform Role Status
    godaddy-tofu Forgejo OpenTofu provider Docs complete, awaiting Go scaffold
Doc 1
  • DNS as Foundational Dependency dns-foundational-dependency

    Thesis

    DNS is the foundational dependency of nearly every project that serves users. It's the first thing that must work and the last thing you notice — until it doesn't. Every layer above it (TLS, reverse proxy, app routing, SSO redirects) assumes DNS is already correct. When DNS is broken, nothing above it can be debugged meaningfully.

    The Dependency Chain

    For any custom domain to go live, three waves must complete in order:

    1. DNS records — A/CNAME records pointing the domain to the right IP. Without this, the domain doesn't resolve at all. This is pure IaC: godaddy_dns_record resources managed by OpenTofu.
    2. Reverse proxy — Caddy/nginx config that accepts traffic for the domain, terminates TLS (via ACME/Let's Encrypt), and routes to the backend. TLS cert provisioning itself depends on DNS being correct — ACME HTTP-01 challenges need the domain to resolve to the proxy.
    3. Application config — Rails config.hosts, Keycloak realm settings, CORS origins, OAuth redirect URIs. The app must know its own domain. This is the last mile, and it's meaningless without the first two.

    Why DNS Is Special

    • Invisible when working. DNS is infrastructure you forget exists — until propagation delays, stale records, or missing entries make everything downstream look broken for no apparent reason.
    • Slow feedback loop. TTLs mean mistakes take minutes to hours to manifest and equally long to fix. You can't iterate on DNS the way you iterate on app code.
    • Blast radius. A wrong A record doesn't just break one endpoint — it breaks every service, cert renewal, and auth flow on that domain.
    • Hard to test locally. Unlike app code, you can't spin up DNS in a dev environment. You're always testing against the real thing (or maintaining /etc/hosts hacks that hide real issues).
    • Silent failures. A missing DNS record doesn't throw an error — the domain just doesn't resolve. Curl gets "could not resolve host." Users get a browser error page. There's no stack trace pointing at the root cause.

    Implications for Platform Work

    • DNS goes first. When onboarding a new domain, create and verify DNS records before touching proxy or app config. Don't parallelize wave 1 with wave 2 — wave 2 literally can't succeed without wave 1.
    • DNS must be IaC. Manual DNS changes via a registrar UI are invisible to the rest of the platform. The godaddy-tofu provider exists specifically to keep DNS in the same tofu state as everything else.
    • Verify at the DNS layer first. When a domain isn't working, start with dig / nslookup before checking proxy logs or app config. 90% of "the domain is broken" issues are DNS issues.
    • Treat DNS changes as high-risk. TTL propagation means rollback isn't instant. A bad DNS change is a slow-motion incident.

    pal-e Platform Context

    On pal-e, the chain is:

    GoDaddy DNS (godaddy-tofu) → Hetzner edge VPS (178.156.129.142) → Caddy reverse proxy → Tailscale → k3s services

    The godaddy_dns_record resources in pal-e-platform/terraform/dns.tf point domains at module.hetzner_edge.server_ipv4, creating an explicit Terraform dependency: DNS records can't be planned without the Hetzner module refreshing first. This is correct — the IP must exist before DNS can reference it.

Architecture 4
  • Architecture: GoDaddy Tofu arch-godaddy-tofu

    Architecture: GoDaddy Tofu

    Diagram

    graph TB
        subgraph "Developer Machine"
            TF[pal-e-platform/terraform/]
            PROV[godaddy-tofu binary]
        end
    
        subgraph "GoDaddy"
            API[api.godaddy.com]
            DNS_PA[palinks.app DNS]
            DNS_LA[landscaping-assistant.app DNS]
        end
    
        subgraph "Hetzner Edge"
            EDGE[178.156.129.142 Caddy + Tailscale cpx11 Ashburn]
        end
    
        subgraph "Home Lab"
            K3S[k3s cluster Arch box]
        end
    
        TF --> PROV
        PROV -->|sso-key auth| API
        API --> DNS_PA
        API --> DNS_LA
        DNS_PA -->|A record| EDGE
        DNS_LA -->|A record| EDGE
        EDGE -->|Tailscale mesh| K3S
    

    Components

    Component Purpose Notes
    godaddy-tofu binary OpenTofu provider plugin Go binary, terraform-plugin-framework, registers as registry.terraform.io/ldraney/godaddy
    pkg/godaddy Client HTTP client for GoDaddy REST API sso-key auth, net/http, 6 DNS + 2 domain methods
    godaddy_dns_record resource CRUD for individual DNS records P0 - maps to PUT/GET/DELETE /v1/domains/{d}/records/{t}/{n}
    godaddy_domain resource Domain settings management P1 - read-heavy, maps to PATCH /v1/domains/{d}
    godaddy_dns_records data source Read existing DNS records P0 - maps to GET /v1/domains/{d}/records
    godaddy_domains data source List/read domain details P1 - maps to GET /v1/domains
    Hetzner edge node Reverse proxy for custom domains 178.156.129.142, Caddy auto-TLS, Tailscale mesh to k3s
    pal-e-platform/terraform/ Consumer of the provider HCL config defining which DNS records exist

    Key Decisions

    • Go client inside provider repo (not separate SDK) — single language, single test suite, no cross-language overhead
    • DNS records as individual resources (not bulk replace-all) — safer than providers that delete unmanaged records
    • net/http only — no external HTTP dependencies, stdlib is sufficient for REST calls
    • sso-key auth — matches GoDaddy production API, credentials from provider config or env vars
    • Hetzner edge as reverse proxy target — Caddy handles public TLS, Tailscale handles private mesh back to k3s
    • Provider consumed by pal-e-platform — DNS config lives where all other infra config lives, not in godaddy-tofu repo
    • project-godaddy-tofu — project page
    • story-godaddy-tofu-dns-iac — user story this architecture serves
    • project-palinks — first consumer (palinks.app DNS)
    • pal-e-platform PR #420/#424 — Hetzner edge module
  • Deployment: godaddy-tofu arch-deployment-godaddy-tofu

    Deployment: godaddy-tofu

    Diagram

    graph TB
        subgraph "Development"
            Dev[Developer workstation] --> |"go install ."|Local[~/.terraform.d/plugins/]
        end
        subgraph "CI (Woodpecker)"
            Repo[godaddy-tofu repo] --> |"push"|CI[Woodpecker pipeline]
            CI --> |"go test"|Tests[Client + acceptance tests]
            CI --> |"goreleaser"|Release[GitHub/Forgejo release]
        end
        subgraph "Consumption"
            Release --> |"provider source"|Platform[pal-e-platform/terraform/]
            Platform --> |"tofu init"|Init[Download provider binary]
            Init --> |"tofu apply"|Apply[Manage DNS records]
            Apply --> |"sso-key auth"|GoDaddy[api.godaddy.com]
        end
    

    Components

    Component Purpose Notes
    Developer workstation Local development and testing go install places binary in local plugin cache
    Woodpecker pipeline CI — runs tests, builds releases Needs GODADDY_API_KEY/SECRET for acceptance tests
    Forgejo release Binary distribution goreleaser produces multi-arch binaries
    pal-e-platform Consumer — IaC repo that uses the provider References provider in required_providers block
    api.godaddy.com GoDaddy API Production endpoint for DNS/domain management

    Key Decisions

    • Local plugin cache for development (~/.terraform.d/plugins/) — no registry needed during development
    • goreleaser for releases — standard Terraform provider distribution pattern
    • Woodpecker CI needs real API credentials for acceptance tests — no mocking the API
    • pal-e-platform is the first and primary consumer — provider design driven by platform needs
    • arch-domain-godaddy-tofu — entity definitions
    • arch-dataflow-godaddy-tofu — runtime API flow
    • project-godaddy-tofu — project page
  • Data Flow: godaddy-tofu arch-dataflow-godaddy-tofu

    Data Flow: godaddy-tofu

    Diagram

    sequenceDiagram
        participant User as tofu CLI
        participant Provider as godaddy provider
        participant Client as pkg/godaddy/client
        participant API as api.godaddy.com
    
        Note over User,API: tofu plan (Read)
        User->>Provider: Plan — read current state
        Provider->>Client: GetRecords(domain, type, name)
        Client->>API: GET /v1/domains/{domain}/records/{type}/{name}
        Note right of Client: Authorization: sso-key {key}:{secret}
        API-->>Client: []DNSRecord
        Client-->>Provider: []DNSRecord
        Provider-->>User: Plan diff
    
        Note over User,API: tofu apply (Create/Update)
        User->>Provider: Apply — desired state
        Provider->>Client: ReplaceRecords(domain, type, name, records)
        Client->>API: PUT /v1/domains/{domain}/records/{type}/{name}
        API-->>Client: 200 OK
        Client-->>Provider: nil error
        Provider-->>User: Apply complete
    
        Note over User,API: tofu destroy (Delete)
        User->>Provider: Destroy — remove resource
        Provider->>Client: DeleteRecords(domain, type, name)
        Client->>API: DELETE /v1/domains/{domain}/records/{type}/{name}
        API-->>Client: 204 No Content
        Client-->>Provider: nil error
        Provider-->>User: Destroy complete
    

    Components

    Component Purpose Notes
    tofu CLI User interface for plan/apply/destroy OpenTofu or Terraform CLI
    godaddy provider Terraform Plugin Framework provider Translates Tofu resource lifecycle to API calls
    pkg/godaddy/client Go HTTP client for GoDaddy API Handles auth, JSON marshaling, error handling, rate limit backoff
    api.godaddy.com GoDaddy REST API Returns 429 with retryAfterSec on rate limit

    Key Decisions

    • Read uses GET by type+name (most specific) — avoids fetching all records and filtering client-side
    • Create/Update uses PUT (replace by type+name) — idempotent, matches Tofu's desired-state model
    • Delete uses DELETE by type+name — only removes the managed record, not siblings
    • Rate limit handling in the client layer (pkg/godaddy/) — provider doesn't need to know about retries
    • arch-domain-godaddy-tofu — entity definitions
    • arch-deployment-godaddy-tofu — where the provider runs
    • project-godaddy-tofu — project page
  • Domain Model: godaddy-tofu arch-domain-godaddy-tofu

    Domain Model: godaddy-tofu

    Diagram

    erDiagram
        PROVIDER ||--o{ DNS_RECORD : "manages"
        PROVIDER ||--o{ DOMAIN : "manages"
        PROVIDER {
            string api_key
            string api_secret
            string base_url
        }
        DNS_RECORD {
            string domain PK
            string type PK
            string name PK
            string data
            int ttl
            int priority
        }
        DOMAIN {
            string domain PK
            string status
            datetime expires
            bool renewable
            bool privacy
        }
        DNS_RECORD }o--|| DOMAIN : "belongs to"
    

    Components

    Component Purpose Notes
    PROVIDER Provider config with GoDaddy API credentials Maps to provider "godaddy" {} block
    DNS_RECORD Individual DNS record (A, CNAME, TXT, MX, etc.) Resource: godaddy_dns_record. Composite key: domain+type+name
    DOMAIN Registered domain with metadata Resource: godaddy_domain (read-heavy, limited write). Data source: godaddy_domains

    Key Decisions

    • DNS records as individual resources (not bulk replace) — avoids the dangerous "delete unmanaged records" pattern from community providers
    • Composite key (domain+type+name) identifies a record — matches GoDaddy API path structure
    • Domain resource is read-heavy — purchase/transfer/delete are out of scope for initial implementation
    • arch-dataflow-godaddy-tofu — runtime API call flow
    • arch-deployment-godaddy-tofu — provider distribution
    • project-godaddy-tofu — project page
User Story 1
  • DNS as IaC story-godaddy-tofu-dns-iac

    story: DNS as IaC

    Role

    Platform operator (Lucas)

    Key

    dns-iac

    Want

    As a platform operator, I want to manage DNS records as OpenTofu resources

    So That

    So that DNS changes are version-controlled, reviewable, and reproducible — no manual GoDaddy dashboard clicks

    Acceptance Criteria

    • [ ] tofu plan shows current DNS records as data sources
    • [ ] tofu apply creates/updates A, CNAME, and TXT records
    • [ ] tofu destroy removes managed records without touching unmanaged ones
    • [ ] palinks.app A record managed via tofu apply in pal-e-platform

    Success Metric

    palinks.app A/CNAME records fully managed via tofu apply with no manual GoDaddy interaction

    • arch-domain-godaddy-tofu — provider resources and data sources
    • arch-dataflow-godaddy-tofu — tofu plan/apply to GoDaddy API flow
    • arch-deployment-godaddy-tofu — provider distribution and pal-e-platform consumption
    • project-godaddy-tofu — parent project page
    • board-godaddy-tofu — project board (tickets reference this story)
Board 1