Skip to content

Commit c92ce37

Browse files
committed
chore: agentic development readiness
Signed-off-by: Dmitriy Vovk <dmitriy.vovk@namecheap.com>
1 parent 1421147 commit c92ce37

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed

.claude/settings.json

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
{
2+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
3+
"permissions": {
4+
"allow": [
5+
"Bash(go build *)",
6+
"Bash(go test *)",
7+
"Bash(go vet *)",
8+
"Bash(go mod tidy)",
9+
"Bash(go mod verify)",
10+
"Bash(make build)",
11+
"Bash(make format)",
12+
"Bash(make check)",
13+
"Bash(make test)",
14+
"Bash(make lint)",
15+
"Bash(make docs)",
16+
"Bash(make docs-validate)",
17+
"Bash(make vendor)",
18+
"Bash(golangci-lint *)",
19+
"Bash(gh pr list *)",
20+
"Bash(gh pr diff *)",
21+
"Bash(gh pr view *)",
22+
"Bash(gh pr checks *)",
23+
"Bash(git diff *)",
24+
"Bash(git log *)",
25+
"Bash(git status)",
26+
"Bash(git show *)",
27+
"WebFetch(domain:developer.hashicorp.com)",
28+
"WebFetch(domain:pkg.go.dev)",
29+
"WebFetch(domain:registry.terraform.io)",
30+
"WebFetch(domain:github.com)",
31+
"WebFetch(domain:raw.githubusercontent.com)"
32+
],
33+
"deny": [
34+
"Read(.env)",
35+
"Read(*.env)",
36+
"Read(**/.env)",
37+
"Read(.claude/settings.local.json)",
38+
"Read(*.tfstate)",
39+
"Read(*.tfstate.backup)",
40+
"Read(**/*.tfstate)",
41+
"Read(*.tfvars)",
42+
"Read(**/*.tfvars)",
43+
"Read(*.tfvars.json)",
44+
"Read(**/*.tfvars.json)",
45+
"Read(~/.terraform.d/credentials.tfrc.json)",
46+
"Read(~/.terraformrc)",
47+
"Read(~/.ssh/*)",
48+
"Read(~/.gnupg/*)",
49+
"Read(~/.netrc)",
50+
"Read(~/.aws/credentials)",
51+
"Read(~/.aws/config)"
52+
]
53+
},
54+
"enabledPlugins": {
55+
"gopls-lsp@claude-plugins-official": true,
56+
"code-simplifier@claude-plugins-official": true,
57+
"code-review@claude-plugins-official": true
58+
},
59+
"hooks": {
60+
"PostToolUse": [
61+
{
62+
"matcher": "Edit|Write",
63+
"hooks": [
64+
{
65+
"type": "command",
66+
"command": "go vet ./... 2>&1 | head -30"
67+
}
68+
]
69+
}
70+
]
71+
}
72+
}

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,8 @@ website/vendor
3333
# Test exclusions
3434
!command/test-fixtures/**/*.tfstate
3535
!command/test-fixtures/**/.terraform/
36+
37+
terraform-provider-namecheap
38+
39+
# Agents settings
40+
.claude/settings.local.json

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
CLAUDE.md

CLAUDE.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Build & Development Commands
6+
7+
```bash
8+
make build # Build binary: terraform-provider-namecheap
9+
make format # go fmt ./...
10+
make check # go vet ./...
11+
make lint # golangci-lint run (requires local install)
12+
make test # Unit tests: go test -v ./namecheap/... -count=1 -cover
13+
make testacc # Acceptance tests (requires env vars, see below)
14+
make docs # Generate docs with tfplugindocs
15+
make vendor # go mod vendor
16+
```
17+
18+
Run a single test: `go test -v ./namecheap/... -run TestFunctionName -count=1`
19+
20+
### Acceptance Tests
21+
22+
Require environment variables: `NAMECHEAP_USER_NAME`, `NAMECHEAP_API_USER`, `NAMECHEAP_API_KEY`, `NAMECHEAP_TEST_DOMAIN`. Set `NAMECHEAP_USE_SANDBOX=true` for sandbox testing.
23+
24+
## Architecture
25+
26+
This is a Terraform provider built with **terraform-plugin-sdk/v2** that manages Namecheap domain DNS configuration through the **go-namecheap-sdk/v2**.
27+
28+
### Single Resource Provider
29+
30+
The provider exposes one resource: `namecheap_domain_records`. All provider logic lives in the `namecheap/` package (package name: `namecheap_provider`).
31+
32+
**Key files:**
33+
- `main.go` — Plugin entry point, serves the provider
34+
- `namecheap/provider.go` — Provider schema, config, and API client setup
35+
- `namecheap/namecheap_domain_record.go` — Resource CRUD schema and dispatch
36+
- `namecheap/namecheap_domain_record_functions.go` — Core business logic (~670 lines)
37+
- `namecheap/internal/mutexkv/` — Domain-level mutex for concurrent access
38+
39+
### MERGE vs OVERWRITE Mode
40+
41+
The central design pattern. Every CRUD operation has paired implementations:
42+
43+
- **MERGE mode** (default): Multiple Terraform configs can manage different records on the same domain. Uses `ncMutexKV` (defined in provider.go) for domain-level locking to prevent race conditions.
44+
- **OVERWRITE mode**: Single Terraform config owns all records for a domain. No locking needed.
45+
46+
Functions follow the naming convention: `{operation}Records{Mode}()` and `{operation}Nameservers{Mode}()` (e.g., `createRecordsMerge`, `readRecordsOverwrite`).
47+
48+
### Address Normalization
49+
50+
DNS records require address fixup before API calls:
51+
- `getFixedAddressOfRecord()` — Routes to type-specific fixers
52+
- `fixAddressEndWithDot()` — Appends trailing dot for CNAME, ALIAS, NS, MX records
53+
- `fixCAAAddressValue()` — Formats CAA record values with flags, tag, and quoted value
54+
- `filterDefaultParkingRecords()` — Strips Namecheap default parking records
55+
56+
### Error Handling
57+
58+
Uses `diag.Diagnostics` throughout for Terraform-native error reporting. API errors are wrapped with `diag.FromErr()`.
59+
60+
## CI Pipeline
61+
62+
Runs on push (`.github/workflows/ci.yml`):
63+
1. `go vet` + golangci-lint v1.54 + unit tests (ubuntu-latest)
64+
2. Acceptance tests on self-hosted EC2 runner (AL2023) against Namecheap sandbox
65+
66+
## go-namecheap-sdk/v2 (Core Dependency)
67+
68+
The provider is entirely built on `github.com/namecheap/go-namecheap-sdk/v2`. Understanding SDK patterns is critical.
69+
70+
### SDK Client Structure
71+
72+
The `*namecheap.Client` (stored as `meta interface{}` in provider) exposes three services:
73+
- `client.Domains``GetInfo()`, `GetList()`
74+
- `client.DomainsDNS``GetHosts()`, `SetHosts()`, `GetList()`, `SetCustom()`, `SetDefault()`
75+
- `client.DomainsNS``Create()`, `Delete()`, `GetInfo()`, `Update()`
76+
77+
DNS methods accept full domain strings and parse internally. NS methods take pre-split `sld`/`tld` parameters.
78+
79+
### Pointer-Heavy Design
80+
81+
All SDK struct fields are pointers (`*string`, `*int`, `*bool`). Use the SDK helper constructors: `namecheap.String()`, `namecheap.Int()`, `namecheap.Bool()`, `namecheap.UInt8()`. Nil fields mean absent/unset values, not zero values.
82+
83+
### MXPref Type Mismatch
84+
85+
`GetHosts` returns `MXPref` as `*int`, but `SetHosts` expects `*uint8`. The provider bridges this with `namecheap.UInt8(uint8(*remoteRecord.MXPref))`.
86+
87+
### Retry Logic
88+
89+
The SDK retries on HTTP 405 (Namecheap's rate-limit response) with progressive delays: 1s, 5s, 15s, 30s, 50s. Retries are mutex-serialized. Total max wait: 101 seconds.
90+
91+
### SetHosts Validation
92+
93+
The SDK validates client-side before API calls:
94+
- Record type must be in `AllowedRecordTypeValues` (A, AAAA, ALIAS, CAA, CNAME, MX, MXE, NS, TXT, URL, URL301, FRAME)
95+
- TTL must be 60–60000
96+
- MX records require `MXPref` and `EmailType == "MX"`; MXE requires `EmailType == "MXE"` and exactly 1 record
97+
- URL/URL301/FRAME records require protocol prefix; CAA iodef requires `http://` or `mailto:`
98+
- Email type must be in: NONE, MXE, MX, FWD, OX, GMAIL
99+
100+
### SDK Gotchas
101+
102+
- `SetCustom()` requires minimum 2 nameservers — the provider enforces this in merge logic too
103+
- `DomainsDNS.GetList()` silently falls back to `Domains.GetInfo()` on error 2019166 (FreeDNS domains)
104+
- `ParseDomain()` handles compound TLDs (`co.uk`, `gov.ua`) via `publicsuffix-go`
105+
- Default parking records (CNAME www→parkingpage.namecheap.com, URL @→http://www.domain) are returned by the API and must be filtered
106+
- `GetHosts` error checking uses `len(response.Errors) > 0` while all other methods use `response.Errors != nil && len(*response.Errors) > 0`
107+
108+
## Key Dependencies
109+
110+
- Go 1.21.5
111+
- `github.com/hashicorp/terraform-plugin-sdk/v2` v2.31.0
112+
- `github.com/namecheap/go-namecheap-sdk/v2` v2.4.0
113+
- `github.com/stretchr/testify` v1.8.4 (tests)

0 commit comments

Comments
 (0)