From 1980d1f08a8a468c229c1a6f4ee9fbe8cd5e5358 Mon Sep 17 00:00:00 2001 From: beatz174-bit Date: Tue, 12 May 2026 15:12:44 +1000 Subject: [PATCH] Harden CI, release, and acceptance test safety --- .github/workflows/ci.yml | 30 +- .github/workflows/release.yml | 32 ++ .gitignore | 29 +- .goreleaser.yml | 33 ++ README.md | 91 +++--- .../terraform.tfstate.backup | 309 ------------------ scripts/testacc.sh | 17 +- 7 files changed, 159 insertions(+), 382 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .goreleaser.yml delete mode 100644 examples/live_safe_dns_record/terraform.tfstate.backup diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16e6b9f..d2e3c78 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,7 @@ name: ci on: push: + branches: [main] pull_request: jobs: @@ -16,14 +17,27 @@ jobs: with: go-version-file: go.mod - - name: Verify formatting and run quality gate - run: ./scripts/check.sh + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 - - name: Build provider binary (stamped metadata) + - name: Verify go mod tidy is clean run: | - VERSION="${GITHUB_REF_NAME:-dev}" - COMMIT="$(git rev-parse --short HEAD)" - DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + go mod tidy + git diff --exit-code go.mod go.sum - go build -o terraform-provider-dynu \ - -ldflags="-X main.version=${VERSION} -X main.commit=${COMMIT} -X main.date=${DATE}" + - name: Verify gofmt + run: | + files="$(git ls-files '*.go')" + test -z "$(gofmt -l ${files})" + + - name: Go vet + run: go vet ./... + + - name: Go tests + run: go test ./... + + - name: Terraform fmt check + run: terraform fmt -check -recursive examples + + - name: Repository quality gate + run: ./scripts/check.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..bf8b2d5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,32 @@ +name: release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: '~> v2' + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 1736106..47591bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,12 @@ -# If you prefer the allow list template instead of the deny list, see community template: -# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore -# # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib +terraform-provider-dynu +terraform-provider-dynu_v* +dist/ # Test binary, built with `go test -c` *.test @@ -17,9 +17,6 @@ coverage.* *.coverprofile profile.cov -# Dependency directories (remove the comment below to include it) -# vendor/ - # Go workspace file go.work go.work.sum @@ -27,10 +24,22 @@ go.work.sum # env file .env +# Terraform local artifacts +.terraform/ +**/.terraform/ +*.tfvars +!*.tfvars.example +terraform.tfvars +*.tfstate +*.tfstate.* +.terraform.lock.hcl + +# Codex/dev artifacts +.codex/bin/ +.codex/tmp/ +.codex/cache/ +.codex/logs/ + # Editor/IDE # .idea/ # .vscode/ - -*.tfvars -!*.tfvars.example -*.tfstate \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..f118938 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,33 @@ +version: 2 +project_name: terraform-provider-dynu + +before: + hooks: + - go mod tidy + - go test ./... + +builds: + - id: terraform-provider-dynu + binary: terraform-provider-dynu + main: ./main.go + env: + - CGO_ENABLED=0 + ldflags: + - -s -w -X main.version={{ .Version }} -X main.commit={{ .ShortCommit }} -X main.date={{ .Date }} + goos: [linux, darwin, windows] + goarch: [amd64, arm64] + ignore: + - goos: windows + goarch: arm64 + +archives: + - id: provider-archives + builds: [terraform-provider-dynu] + format: zip + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' + +checksum: + name_template: 'terraform-provider-dynu_{{ .Version }}_SHA256SUMS' + +changelog: + use: git diff --git a/README.md b/README.md index eadcaa7..7afe33e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,11 @@ A standalone Terraform provider for Dynu DNS and domain management. - `dynu_domains` - `dynu_domain` - `dynu_dns_records` -- Provider authentication via `api_key`. +- Provider authentication via explicit `api_key` configuration. + +## Important safety note + +Deleting `dynu_domain` deletes the full Dynu DNS zone for that domain. Treat destroy plans carefully. ## Minimal usage example @@ -31,22 +35,8 @@ variable "dynu_api_key" { type = string sensitive = true } - -resource "dynu_domain" "example" { - name = "my-test-domain.example" - ttl = 300 -} - -resource "dynu_dns_record" "www" { - hostname = "www.${dynu_domain.example.name}" - record_type = "A" - content = "198.51.100.20" - ttl = 300 -} ``` -For a live end-to-end workflow that exercises multiple record types, see `examples/live_safe_dns_record/README.md`. - ## Resources - `dynu_domain` @@ -58,43 +48,15 @@ For a live end-to-end workflow that exercises multiple record types, see `exampl - `dynu_domain` - `dynu_dns_records` -## Testing - -Run Go unit/integration tests: - -```bash -go test ./... -``` - -Run repository checks: - -```bash -./scripts/fix.sh -./scripts/check.sh -``` - -Run the live end-to-end Terraform example (opt-in, uses real Dynu account data): - -```bash -cd examples/live_safe_dns_record -cp terraform.tfvars.example terraform.tfvars -terraform validate -terraform plan -# terraform apply -# terraform destroy -``` - -## Development +## Local development and dev overrides This provider is not yet published to the Terraform Registry. Use `dev_overrides` with a local build. -1. Build the provider binary: - ```bash go build -o terraform-provider-dynu ``` -2. Configure `~/.terraformrc`: +`~/.terraformrc`: ```hcl provider_installation { @@ -106,7 +68,7 @@ provider_installation { } ``` -3. Validate locally without relying on registry publishing: +Validate locally: ```bash cd examples/read_only @@ -115,4 +77,39 @@ terraform validate terraform plan ``` -When provider code/config changes, rebuild `terraform-provider-dynu` before re-running Terraform commands. +If provider code/config changes, rebuild `terraform-provider-dynu` before running Terraform again. + +## Testing + +```bash +./scripts/fix.sh +./scripts/check.sh +go test ./... +go vet ./... +terraform fmt -check -recursive examples +``` + +### Optional live acceptance tests + +Live tests are opt-in and destructive for test records. They never run by default. + +```bash +DYNU_ACC=1 \ +DYNU_ACC_API_KEY="***" \ +DYNU_ACC_TEST_DOMAIN="example.com" \ +./scripts/testacc.sh --live +``` + +Use a disposable domain/subdomain only. + +## Release + +Build a local stamped binary: + +```bash +./build.sh v0.1.0 +``` + +Tagged releases (`v*`) run `.github/workflows/release.yml` with GoReleaser to produce multi-platform archives and checksums. + +Terraform Registry signing is not configured in this repository yet. diff --git a/examples/live_safe_dns_record/terraform.tfstate.backup b/examples/live_safe_dns_record/terraform.tfstate.backup deleted file mode 100644 index eb057b1..0000000 --- a/examples/live_safe_dns_record/terraform.tfstate.backup +++ /dev/null @@ -1,309 +0,0 @@ -{ - "version": 4, - "terraform_version": "1.14.9", - "serial": 127, - "lineage": "c95cc108-bbc0-0021-4151-832dec79ca2c", - "outputs": { - "record_hostnames": { - "value": { - "a_ipv4": "codex-a-manual.lan.ddnsgeek.com", - "aaaa_ipv6": "codex-aaaa-manual.lan.ddnsgeek.com", - "cname": "codex-cname-manual.lan.ddnsgeek.com", - "dynamic_a": "codex-dynamic-a-manual.lan.ddnsgeek.com", - "dynamic_aaaa": "codex-dynamic-aaaa-manual.lan.ddnsgeek.com" - }, - "type": [ - "object", - { - "a_ipv4": "string", - "aaaa_ipv6": "string", - "cname": "string", - "dynamic_a": "string", - "dynamic_aaaa": "string" - } - ] - }, - "record_ids": { - "value": { - "a_ipv4": "9695470/19453289", - "aaaa_ipv6": "9695470/19453288", - "cname": "9695470/19453146", - "dynamic_a": "9695470/19453149", - "dynamic_aaaa": "9695470/19453145" - }, - "type": [ - "object", - { - "a_ipv4": "string", - "aaaa_ipv6": "string", - "cname": "string", - "dynamic_a": "string", - "dynamic_aaaa": "string" - } - ] - }, - "record_values": { - "value": { - "a_ipv4": { - "content": "192.0.2.123", - "type": "A" - }, - "aaaa_ipv6": { - "content": "2001:db8::123", - "type": "AAAA" - }, - "cname": { - "content": "example.com", - "type": "CNAME" - }, - "dynamic_a": { - "content": null, - "type": "A" - }, - "dynamic_aaaa": { - "content": null, - "type": "AAAA" - } - }, - "type": [ - "object", - { - "a_ipv4": [ - "object", - { - "content": "string", - "type": "string" - } - ], - "aaaa_ipv6": [ - "object", - { - "content": "string", - "type": "string" - } - ], - "cname": [ - "object", - { - "content": "string", - "type": "string" - } - ], - "dynamic_a": [ - "object", - { - "content": "string", - "type": "string" - } - ], - "dynamic_aaaa": [ - "object", - { - "content": "string", - "type": "string" - } - ] - } - ] - } - }, - "resources": [ - { - "mode": "managed", - "type": "dynu_dns_record", - "name": "a_ipv4", - "provider": "provider[\"registry.terraform.io/dynu/dynu\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "content": "192.0.2.123", - "domain_id": 9695470, - "domain_name": "lan.ddnsgeek.com", - "dynamic": false, - "enabled": true, - "group": null, - "host": null, - "hostname": "codex-a-manual.lan.ddnsgeek.com", - "id": "9695470/19453289", - "node_name": "codex-a-manual", - "record_type": "A", - "ttl": 300, - "updated_on": "2026-04-29T06:17:45" - }, - "sensitive_attributes": [], - "identity_schema_version": 0 - } - ] - }, - { - "mode": "managed", - "type": "dynu_dns_record", - "name": "aaaa_ipv6", - "provider": "provider[\"registry.terraform.io/dynu/dynu\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "content": "2001:db8::123", - "domain_id": 9695470, - "domain_name": "lan.ddnsgeek.com", - "dynamic": false, - "enabled": true, - "group": null, - "host": null, - "hostname": "codex-aaaa-manual.lan.ddnsgeek.com", - "id": "9695470/19453288", - "node_name": "codex-aaaa-manual", - "record_type": "AAAA", - "ttl": 300, - "updated_on": "2026-04-29T06:16:25" - }, - "sensitive_attributes": [], - "identity_schema_version": 0 - } - ] - }, - { - "mode": "managed", - "type": "dynu_dns_record", - "name": "cname", - "provider": "provider[\"registry.terraform.io/dynu/dynu\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "content": "example.com", - "domain_id": 9695470, - "domain_name": "lan.ddnsgeek.com", - "dynamic": false, - "enabled": true, - "group": null, - "host": "example.com", - "hostname": "codex-cname-manual.lan.ddnsgeek.com", - "id": "9695470/19453146", - "node_name": "codex-cname-manual", - "record_type": "CNAME", - "ttl": 300, - "updated_on": "2026-04-29T06:16:25" - }, - "sensitive_attributes": [], - "identity_schema_version": 0 - } - ] - }, - { - "mode": "managed", - "type": "dynu_dns_record", - "name": "dynamic_a", - "provider": "provider[\"registry.terraform.io/dynu/dynu\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "content": null, - "domain_id": 9695470, - "domain_name": "lan.ddnsgeek.com", - "dynamic": true, - "enabled": true, - "group": "home", - "host": null, - "hostname": "codex-dynamic-a-manual.lan.ddnsgeek.com", - "id": "9695470/19453149", - "node_name": "codex-dynamic-a-manual", - "record_type": "A", - "ttl": 300, - "updated_on": "2026-04-29T06:16:27" - }, - "sensitive_attributes": [], - "identity_schema_version": 0 - } - ] - }, - { - "mode": "managed", - "type": "dynu_dns_record", - "name": "dynamic_aaaa", - "provider": "provider[\"registry.terraform.io/dynu/dynu\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "content": null, - "domain_id": 9695470, - "domain_name": "lan.ddnsgeek.com", - "dynamic": true, - "enabled": true, - "group": null, - "host": null, - "hostname": "codex-dynamic-aaaa-manual.lan.ddnsgeek.com", - "id": "9695470/19453145", - "node_name": "codex-dynamic-aaaa-manual", - "record_type": "AAAA", - "ttl": 300, - "updated_on": "2026-04-29T06:16:25" - }, - "sensitive_attributes": [], - "identity_schema_version": 0 - } - ] - } - ], - "check_results": [ - { - "object_kind": "var", - "config_addr": "var.dynu_root_domain", - "status": "pass", - "objects": [ - { - "object_addr": "var.dynu_root_domain", - "status": "pass" - } - ] - }, - { - "object_kind": "var", - "config_addr": "var.test_suffix", - "status": "pass", - "objects": [ - { - "object_addr": "var.test_suffix", - "status": "pass" - } - ] - }, - { - "object_kind": "var", - "config_addr": "var.test_ipv4", - "status": "pass", - "objects": [ - { - "object_addr": "var.test_ipv4", - "status": "pass" - } - ] - }, - { - "object_kind": "var", - "config_addr": "var.test_ipv6", - "status": "pass", - "objects": [ - { - "object_addr": "var.test_ipv6", - "status": "pass" - } - ] - }, - { - "object_kind": "var", - "config_addr": "var.test_cname_target", - "status": "pass", - "objects": [ - { - "object_addr": "var.test_cname_target", - "status": "pass" - } - ] - } - ] -} diff --git a/scripts/testacc.sh b/scripts/testacc.sh index 252c148..12d353c 100755 --- a/scripts/testacc.sh +++ b/scripts/testacc.sh @@ -11,19 +11,20 @@ if [[ "${mode}" == "mock" ]]; then exec "$(dirname "$0")/test-integration.sh" fi -if [[ "${TF_ACC:-}" != "1" ]]; then - echo "[testacc][error] live mode requires TF_ACC=1" >&2 +if [[ "${DYNU_ACC:-}" != "1" ]]; then + echo "[testacc][error] live mode requires DYNU_ACC=1" >&2 exit 1 fi -if [[ -z "${DYNU_API_KEY:-}" ]]; then - echo "[testacc][error] live mode requires DYNU_API_KEY" >&2 +if [[ -z "${DYNU_ACC_API_KEY:-}" ]]; then + echo "[testacc][error] live mode requires DYNU_ACC_API_KEY" >&2 exit 1 fi -if [[ -z "${DYNU_DOMAIN:-}" ]]; then - echo "[testacc][warn] DYNU_DOMAIN not set; domain-specific acceptance tests will skip" +if [[ -z "${DYNU_ACC_TEST_DOMAIN:-}" ]]; then + echo "[testacc][error] live mode requires DYNU_ACC_TEST_DOMAIN" >&2 + exit 1 fi -echo "[testacc] running live acceptance tests" -go test ./internal/provider -run '^TestAcc' -count=1 -v +echo "[testacc] running live acceptance tests (destructive: creates/updates/deletes records)" +TF_ACC=1 DYNU_API_KEY="${DYNU_ACC_API_KEY}" DYNU_DOMAIN="${DYNU_ACC_TEST_DOMAIN}" go test ./internal/provider -run '^TestAcc' -count=1 -v