Add Go scaffold with provider skeleton and pkg/godaddy DNS client #5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feature/go-scaffold-dns-client"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Initializes the Go module, creates the terraform-plugin-framework provider skeleton with api_key/api_secret configuration, and implements the full pkg/godaddy HTTP client with all 6 DNS record methods and 2 domain read methods.
Changes
go.mod/go.sum-- Go modulegithub.com/ldraney/godaddy-tofuwithterraform-plugin-framework v1.19.0main.go-- Provider entry point usingproviderserver.Serveprovider.go-- Provider struct withapi_keyandapi_secretschema attributes (config or env var),Configureinstantiatesgodaddy.Client, empty resource/data source slices as stubspkg/godaddy/client.go--Clientstruct withNewClient(apiKey, apiSecret, ...Option),sso-keyauth header,WithBaseURLandWithHTTPClientoptions,APIErrortype for non-2xx responsespkg/godaddy/dns.go--DNSRecordstruct (Type, Name, Data, TTL, Priority, Weight, Port, Protocol, Service) and all 6 DNS methods:GetRecords,ReplaceRecords,DeleteRecords,ReplaceAllRecords,ReplaceRecordsByType,AddRecordspkg/godaddy/domains.go--Domainstruct matching DomainSummary swagger schema,ListDomainsandGetDomainmethodsTest Plan
go build ./...compiles cleango vet ./...passes with no warningsReview Checklist
go build ./...passesgo vet ./...passesnet/httponly)Related Notes
None -- first code in repo, no prior notes to reference.
Related
Closes #3
PR #5 Review
DOMAIN REVIEW
Tech stack: Go 1.26, terraform-plugin-framework v1.19.0, stdlib
net/httpclient.Go conventions -- Generally solid. Functional options pattern for
Client, propercontext.Contextthreading in all API methods, wrapped errors with%w, godoc comments on all exports. The code reads clean and idiomatic.Terraform provider patterns -- Correct use of
provider.Providerinterface, compile-time check viavar _ provider.Provider = (*godaddyProvider)(nil), schema withSensitive: trueon credentials, env var fallback with config precedence, client passed viaresp.DataSourceData/resp.ResourceData. All matches current framework conventions.API client correctness -- All 6 DNS methods match the swagger spec endpoints (verified against
docs/dns-endpoints.mdandswagger_domains.json). Domain struct covers 23 of 23 DomainSummary properties from swagger (the 4 Contact fields andsubAccountIdare correctly omitted -- contacts are complex objects not needed for a summary data source, andsubAccountIdis an optional edge case).Specific findings:
DomainIDtyped asfloat64(domains.go:12) -- The swagger spec definesdomainIdas"type": "number", "format": "double", sofloat64is technically correct against the spec. However, domain IDs are integer identifiers in practice. Usingint64with a custom JSON unmarshaler, or simplyjson.Number, would be safer against floating-point precision issues for large IDs. This is a nit since the spec saysnumberand JSON numbers decode intofloat64by default in Go.do()response body handling on 204 No Content (client.go:65-71) -- When the API returns a success status with an empty body (e.g., 204 from DELETE),do()returns the response anddoJSON()attemptsjson.Decodeon an empty body, which returnsio.EOF. Thev == nilguard indoJSON(line 95-97) protects the write methods that passnil, so this is fine forDeleteRecords,ReplaceRecords,AddRecords, etc. However, if a future caller mistakenly passes a non-nil target on a 204-returning endpoint, it will get an opaque EOF error. Consider draining/checkingresp.Bodylength orresp.StatusCode == 204as a guard. Nit -- current callers are all correct.No URL path escaping (dns.go, domains.go) -- Domain names and record names are interpolated directly into URL paths via
fmt.Sprintf. While DNS names are typically safe ASCII, a domain with special characters would produce a malformed URL. Usingurl.PathEscape()for the path segments would be defensive. Nit -- DNS names are constrained to safe chars in practice.io.ReadAllerror discarded (client.go:67) --body, _ := io.ReadAll(resp.Body)silently discards the read error. If the body read fails,Messagewill be empty, making theAPIErrorless useful for debugging. Minor, but logging or including a fallback message would improve debuggability. Nit.http.DefaultClientshared globally (client.go:42) -- Usinghttp.DefaultClientmeans timeout behavior depends on whatever the process-wide default is (no timeout by default). For a Terraform provider that makes blocking HTTP calls during plan/apply, a client with a reasonable timeout (e.g., 30s) would be safer. TheWithHTTPClientoption allows override, but the default should be more conservative. Nit -- acceptable for scaffold, should be addressed before production use.Provider
New()function signature (main.go:11) --providerserver.Servetakes afunc() provider.Providerfactory. TheNewfunction signature matches (func New() provider.Provider), so this is correct. Good.provider.goConfigure stores client on struct (line 95) --p.client = godaddy.NewClient(...)stores the client on the provider struct, AND passes it viaresp.DataSourceData/resp.ResourceData. The struct field is unused -- resources/data sources should retrieve the client fromreq.ProviderDatain their own Configure methods. Theclientfield ongodaddyProvideris dead code. Nit -- remove the field to avoid confusion, or document that it is intentionally kept for provider-level use.BLOCKERS
No blockers found.
The PR body explicitly states "Next issue will add integration tests against live API" and issue #4 exists for that purpose. For a scaffold PR that introduces types, method signatures, and wiring -- with no runtime behavior exposed to end users yet -- the absence of tests is acceptable. The code cannot be exercised by
tofu plan/applyuntil resources/data sources are registered (the slices are empty stubs). Tests belong with the next PR (#4).No secrets, no hardcoded credentials, no unvalidated user input, no DRY violations.
NITS
DomainID float64-- Considerint64orjson.Numberfor domain IDs to avoid floating-point precision edge cases. (domains.go:12)doJSONagainst empty response bodies (204 No Content) to prevent opaque EOF errors from future callers. (client.go:88-99)url.PathEscape()on domain/type/name path segments for defense-in-depth. (dns.go, domains.go)io.ReadAllerror -- Log or provide a fallback message when body read fails in error path. (client.go:67)http.Clientinstead of relying onhttp.DefaultClient. (client.go:42)clientfield on provider struct -- Remove or document theclientfield ongodaddyProvider; resources usereq.ProviderData. (provider.go:20)SOP COMPLIANCE
go.sumcommitted (required for reproducible builds)PROCESS OBSERVATIONS
pkg/godaddy/) and provider wiring (root package).CLAUDE.md.docs/cross-references (swagger specs, endpoint docs, auth guide) provide good verification ground truth for the client implementation.VERDICT: APPROVED
PR #5 Review
DOMAIN REVIEW
Tech stack: Go 1.26, terraform-plugin-framework v1.19.0, stdlib
net/httpHTTP client.Provider skeleton (
provider.go,main.go):The provider follows the standard terraform-plugin-framework pattern correctly:
providerserver.Serveentry point with correct address formatSensitive: truefor bothapi_keyandapi_secretresp.DataSourceDataandresp.ResourceDatavar _ provider.Provider = (*godaddyProvider)(nil)Resources()andDataSources()slices as stubs -- appropriate for scaffoldOne note: the
New()function signature isfunc New() provider.Provider, butproviderserver.Serveexpectsfunc() provider.Provider. Inmain.goline 11,Newis passed directly as a function value -- this works because Go matches the signature. Correct usage.HTTP client (
pkg/godaddy/client.go):WithBaseURL,WithHTTPClient) is clean and idiomaticsso-keyauth format matches GoDaddy API docs:Authorization: sso-key {key}:{secret}do()reads and closes the body on error, returns structuredAPIErrordoJSON()correctly handlesniltarget (for write-only endpoints like PUT/DELETE that return no body)APIErrorimplements theerrorinterfaceDNS methods (
pkg/godaddy/dns.go):Cross-referenced all 6 methods against
docs/dns-endpoints.mdandswagger_domains.json:GetRecords/v1/domains/{domain}/records/{type}/{name}ReplaceRecords/v1/domains/{domain}/records/{type}/{name}DeleteRecords/v1/domains/{domain}/records/{type}/{name}ReplaceAllRecords/v1/domains/{domain}/recordsReplaceRecordsByType/v1/domains/{domain}/records/{type}AddRecords/v1/domains/{domain}/recordsAll 6 DNS endpoints from the swagger spec are covered. HTTP methods and URL paths match the spec.
DNSRecordstruct fields verified against swaggerdefinitions.DNSRecord:type,name,data,ttl,priority,port,protocol,service,weight-- all present with correct Go types. JSON tags match swagger property names exactly.Domain methods (
pkg/godaddy/domains.go):Domainstruct verified against swaggerdefinitions.DomainSummary(line 8466 ofswagger_domains.json):domainIdtyped asfloat64-- matches swagger"type": "number", "format": "double". Correct.contactRegistrant,contactAdmin, etc.) are omitted from the struct -- these are$refto nestedContactobjects. Acceptable for a summary-level struct; full contact support is P1/future work.ListDomainsandGetDomainmap toGET /v1/domainsandGET /v1/domains/{domain}correctly.BLOCKERS
1. No test coverage (BLOCKER)
Zero
_test.gofiles in this PR. The PR body acknowledges this ("Next issue will add integration tests against live API") and issue #4 exists to track it, but the BLOCKER criteria states: "New functionality with zero test coverage."This is 566 lines of new code across an HTTP client, 6 DNS methods, 2 domain methods, and a provider skeleton -- all untested. The
pkg/godaddy/client in particular has complex behavior (auth header injection, error parsing, JSON marshaling) that should have at least unit tests with a mock HTTP server before merge.Mitigating factor: Issue #4 is already created for integration tests, and the PR body explicitly calls this out. If the team considers this an acceptable scaffold-then-test workflow for a brand-new provider, this blocker can be waived at reviewer discretion. But the policy as written requires test coverage for new functionality.
2. URL path parameters not encoded (BLOCKER -- input validation)
In
dns.goanddomains.go, user-supplieddomain,recordType, andnamevalues are interpolated directly into URL paths viafmt.Sprintf:If any parameter contains
/,?,#, or other URL-special characters, this would construct a malformed or unintended URL. While DNS record types and names are constrained by the GoDaddy API, the client library should not trust its callers to sanitize inputs. Usenet/url.PathEscape()orurl.URLconstruction.This falls under "Unvalidated user input" in the BLOCKER criteria. The risk is path traversal or request to unintended endpoints.
NITS
doJSONdiscards response body on nil target (client.goline 95-97): Whenv == nil, the response body is still deferred-closed but never drained. For HTTP/1.1 connection reuse, the body should be fully read before close. Considerio.Copy(io.Discard, resp.Body)before returning. Not a correctness bug sincehttp.DefaultClienthandles this, but a robustness improvement.io.ReadAllerror ignored (client.goline 67):body, _ := io.ReadAll(resp.Body)-- if the read fails,bodywill be empty and theAPIError.Messagewill be blank. Consider at least logging or noting the read failure in the error message.domainIdasfloat64(domains.goline 12): While this matches the swagger spec ("type": "number", "format": "double"), domain IDs are integers in practice. Usingint64withjson:"domainId"would avoid floating-point precision issues for very large IDs. The swagger spec's choice ofdoubleis arguably a spec bug, but following it is defensible.Version string hardcoded (
provider.goline 32):version: "dev"is fine for scaffold but should be injected via-ldflagsat build time for releases. Not blocking for scaffold.http.DefaultClientas default (client.goline 42): The default client has no timeout. For a provider that will be called duringtofu plan/apply, a hung API call could block indefinitely. Consider setting a default timeout (e.g., 30s). Not blocking for scaffold.Context propagation: All methods correctly use
http.NewRequestWithContext(ctx, ...)-- good. Context cancellation will propagate to the HTTP client.SOP COMPLIANCE
Closes #3links PR to parent issuePROCESS OBSERVATIONS
VERDICT: NOT APPROVED
Two blockers:
PR #5 Review (Re-review)
DOMAIN REVIEW
Tech stack: Go / Terraform Plugin Framework / stdlib
net/httpGo / Terraform Provider specifics:
provider.Providerinterface with compile-time check (var _ provider.Provider = (*godaddyProvider)(nil))api_keyandapi_secretare properly markedSensitive: true-- credentials will be redacted in plan outputcontext.Contextpropagated through all client methods viahttp.NewRequestWithContext-- correct for cancellation and timeout supportWithBaseURL,WithHTTPClient) is clean and testableAPIErrortype withError()method enables callers to type-assert for status code inspection%wthroughout is correct for Go 1.13+ error chainsdoJSONhandles thev == nilcase for write-only endpoints (PUT/PATCH/DELETE that return no body) -- gooddo()correctly closes response body on error paths (defer resp.Body.Close()inside the>= 400block) and leaves it open for callers on successDNS client coverage:
All 6 DNS endpoints from the swagger spec are implemented with correct HTTP methods:
GET /v1/domains/{domain}/records/{type}/{name}-- GetRecordsPUT /v1/domains/{domain}/records/{type}/{name}-- ReplaceRecordsDELETE /v1/domains/{domain}/records/{type}/{name}-- DeleteRecordsPUT /v1/domains/{domain}/records-- ReplaceAllRecordsPUT /v1/domains/{domain}/records/{type}-- ReplaceRecordsByTypePATCH /v1/domains/{domain}/records-- AddRecordsDomain client coverage:
GET /v1/domains-- ListDomainsGET /v1/domains/{domain}-- GetDomainDomainstruct fields align with swaggerDomainSummaryschemaPREVIOUS BLOCKER STATUS
1. URL path parameters not encoded -- FIXED
Verified:
url.PathEscape()is applied to every user-supplied path parameter across all methods indns.go(8 instances across 6 methods) anddomains.go(1 instance inGetDomain). Thenet/urlpackage is imported in both files.ListDomainshas no path parameters so no escaping is needed. Fix is correct and complete.2. Zero test coverage -- WAIVED
Issue #4 ("Integration tests for pkg/godaddy/ client") exists as an explicit follow-up. The project strategy (documented in CLAUDE.md under "Testing Strategy") is scaffold-then-test: prove the client against the live API before wiring provider acceptance tests. This is a deliberate architectural decision, not an oversight. Waived for this scaffold PR.
BLOCKERS
None remaining.
NITS
Domain.DomainIDisfloat64(domains.go line 13) -- The swagger schema definesdomainIdas an integer. Go'sencoding/jsonwill decode JSON integers intofloat64without loss for values under 2^53, so this works in practice, butint64would be more semantically correct and avoids potential floating-point display artifacts. Low priority since this is a read-only field.io.ReadAllin error path without size limit (client.go line 71) --body, _ := io.ReadAll(resp.Body)reads the entire error response body into memory. A malicious or misconfigured upstream could return a very large body. Considerio.LimitReader(resp.Body, 1<<20)(1 MB cap). Low risk since GoDaddy API error responses are small, but defensive coding is worth considering.ListDomainsshadowsurlimport (domains.go line 41) -- The local variableurl := fmt.Sprintf(...)shadows the importednet/urlpackage. Harmless sinceurl.PathEscapeis not called in that function, but renaming toreqURL(matching the pattern used inGetDomainand alldns.gomethods) would be more consistent.SOP COMPLIANCE
Sensitive: truein provider schemago.sumcommitted (required for reproducible builds)PROCESS OBSERVATIONS
VERDICT: APPROVED