Merge pull request #53 from beatz174-bit/codex/harden-terraform-provider-dynu-for-production-readiness

Harden provider for production readiness
This commit is contained in:
beatz174-bit
2026-05-12 15:14:19 +10:00
committed by GitHub
7 changed files with 159 additions and 382 deletions
+22 -8
View File
@@ -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
+32
View File
@@ -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 }}
+19 -10
View File
@@ -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
+33
View File
@@ -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
+44 -47
View File
@@ -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.
@@ -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"
}
]
}
]
}
+9 -8
View File
@@ -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