diff --git a/examples/live_safe_dns_record/README.md b/examples/live_safe_dns_record/README.md index f4836f1..a746810 100644 --- a/examples/live_safe_dns_record/README.md +++ b/examples/live_safe_dns_record/README.md @@ -1,57 +1,56 @@ # Live Safe DNS Record Example -This example is an **opt-in live test** for writable Dynu provider functionality. -It creates exactly one disposable DNS `A` record under a user-supplied Dynu root domain, -then you remove it with `terraform destroy`. +This example is an **opt-in live test** for writable Dynu provider functionality. It creates real Dynu DNS records and is designed for disposable, test-owned hostnames only. -## Safety model +## What this creates -- Creates a **new disposable subdomain** using configurable `test_prefix` and `test_suffix` values. -- Hostname format is: - - `-.` -- Creates exactly one record: - - `A` record, default content `1.1.1.1`, default TTL `300`. -- Does **not** import, update, replace, or delete existing production hostnames/records. -- `terraform destroy` removes only the disposable record in this state. +Using a single suffix (`test_suffix`), this example creates five DNS record scenarios under `dynu_root_domain`: + +1. `A` record with IPv4 content (`codex-a-.`) +2. `AAAA` record with IPv6 content (`codex-aaaa-.`) +3. `CNAME` record (`codex-cname-.`) +4. **Blank `A` record** with no content/IP (`codex-blank-a-.`) +5. **Blank `AAAA` record** with no content/IP (`codex-blank-aaaa-.`) > [!WARNING] -> Do not repurpose this example to target existing production subdomains. -> Always provide only a root Dynu-managed domain (for example, `example.com`) via -> `dynu_root_domain`. +> Do not use a suffix that overlaps important existing hostnames. This example is intended only for disposable test records that you can safely destroy. ## Prerequisites -- Local provider binary and Terraform `dev_overrides` set up for `dynu/dynu`. -- `DYNU_API_KEY` exported (or set `dynu_api_key` in `terraform.tfvars`). -- A Dynu-managed root domain you control. +- Local provider binary + Terraform `dev_overrides` for `dynu/dynu` +- `DYNU_API_KEY` exported, or `dynu_api_key` set in `terraform.tfvars` +- A Dynu-managed root domain you control -## Workflow - -This example intentionally uses only the `dynu/dynu` provider (no `hashicorp/random`). -If you want per-run uniqueness, pass `test_suffix` explicitly. +## Configure ```bash -go build -o terraform-provider-dynu -cd examples/live_safe_dns_record cp terraform.tfvars.example terraform.tfvars -# edit terraform.tfvars and set dynu_root_domain (and optionally test_suffix) -terraform validate -TEST_SUFFIX="$(date +%s)" -terraform plan -var="test_suffix=${TEST_SUFFIX}" -terraform apply -var="test_suffix=${TEST_SUFFIX}" ``` -After apply, inspect outputs to confirm the disposable hostname and record ID, -then clean up using the **same** suffix value: +Edit `terraform.tfvars` and set at least: + +- `dynu_root_domain` +- `test_suffix` (use a unique value per run) + +Optional overrides include `test_ipv4`, `test_ipv6`, and `test_cname_target`. + +## Run ```bash -terraform destroy -var="test_suffix=${TEST_SUFFIX}" +terraform init +terraform validate +terraform plan +terraform apply +terraform destroy ``` -## Cleanup guidance +## Notes -- Always run `terraform destroy` after testing. -- If apply is interrupted or partially succeeds, rerun `terraform destroy` from this same - directory with the same `terraform.tfvars`, CLI vars, and state files. -- If re-running without destroying first, Terraform may keep state for the previous - disposable record until it is destroyed. +- `terraform apply` should create all five record scenarios. +- `terraform destroy` should remove all five records created by this state. +- If you need to target a single scenario, resources are explicitly named: + - `dynu_dns_record.a_ipv4` + - `dynu_dns_record.aaaa_ipv6` + - `dynu_dns_record.cname` + - `dynu_dns_record.blank_a` + - `dynu_dns_record.blank_aaaa` diff --git a/examples/live_safe_dns_record/main.tf b/examples/live_safe_dns_record/main.tf index b898765..5a91f2c 100644 --- a/examples/live_safe_dns_record/main.tf +++ b/examples/live_safe_dns_record/main.tf @@ -1,24 +1,54 @@ -terraform { - required_providers { - dynu = { - source = "dynu/dynu" - } - } -} - +# Local development note: +# Keep source as "dynu/dynu" and use ~/.terraformrc dev_overrides to point to +# your local terraform-provider-dynu binary during provider development. provider "dynu" { api_key = var.dynu_api_key } locals { - disposable_label = "${var.test_prefix}-${var.test_suffix}" - disposable_hostname = "${local.disposable_label}.${var.dynu_root_domain}" + hostname_a_ipv4 = "codex-a-${var.test_suffix}.${var.dynu_root_domain}" + hostname_aaaa_ipv6 = "codex-aaaa-${var.test_suffix}.${var.dynu_root_domain}" + hostname_cname = "codex-cname-${var.test_suffix}.${var.dynu_root_domain}" + hostname_blank_a = "codex-blank-a-${var.test_suffix}.${var.dynu_root_domain}" + hostname_blank_aaaa = "codex-blank-aaaa-${var.test_suffix}.${var.dynu_root_domain}" } -resource "dynu_dns_record" "safe_live_test" { - hostname = local.disposable_hostname +resource "dynu_dns_record" "a_ipv4" { + hostname = local.hostname_a_ipv4 record_type = "A" - content = var.test_ip + content = var.test_ipv4 + ttl = var.test_ttl + state = true +} + +resource "dynu_dns_record" "aaaa_ipv6" { + hostname = local.hostname_aaaa_ipv6 + record_type = "AAAA" + content = var.test_ipv6 + ttl = var.test_ttl + state = true +} + +resource "dynu_dns_record" "cname" { + hostname = local.hostname_cname + record_type = "CNAME" + content = var.test_cname_target + ttl = var.test_ttl + state = true +} + +# Deliberate blank A record scenario: A type with no content/IP value. +resource "dynu_dns_record" "blank_a" { + hostname = local.hostname_blank_a + record_type = "A" + ttl = var.test_ttl + state = true +} + +# Deliberate blank AAAA record scenario: AAAA type with no content/IP value. +resource "dynu_dns_record" "blank_aaaa" { + hostname = local.hostname_blank_aaaa + record_type = "AAAA" ttl = var.test_ttl state = true } diff --git a/examples/live_safe_dns_record/outputs.tf b/examples/live_safe_dns_record/outputs.tf index c5a6ac4..c827fcc 100644 --- a/examples/live_safe_dns_record/outputs.tf +++ b/examples/live_safe_dns_record/outputs.tf @@ -1,19 +1,47 @@ -output "disposable_hostname" { - description = "Disposable hostname created by this run." - value = local.disposable_hostname +output "record_hostnames" { + description = "Disposable hostnames created for each live-safe DNS scenario." + value = { + a_ipv4 = dynu_dns_record.a_ipv4.hostname + aaaa_ipv6 = dynu_dns_record.aaaa_ipv6.hostname + cname = dynu_dns_record.cname.hostname + blank_a = dynu_dns_record.blank_a.hostname + blank_aaaa = dynu_dns_record.blank_aaaa.hostname + } } -output "disposable_record_id" { - description = "Dynu DNS record ID in domain_id/record_id format for the disposable record." - value = dynu_dns_record.safe_live_test.id +output "record_ids" { + description = "Dynu record IDs (domain_id/record_id) for each scenario." + value = { + a_ipv4 = dynu_dns_record.a_ipv4.id + aaaa_ipv6 = dynu_dns_record.aaaa_ipv6.id + cname = dynu_dns_record.cname.id + blank_a = dynu_dns_record.blank_a.id + blank_aaaa = dynu_dns_record.blank_aaaa.id + } } -output "root_domain" { - description = "Dynu root domain used for this live-safe test." - value = var.dynu_root_domain -} - -output "cleanup_reminder" { - description = "Reminder to destroy the disposable record after verification." - value = "Run terraform destroy in this same directory/state to remove only this disposable record." +output "record_values" { + description = "Record type/content summary for each scenario; blank records intentionally omit content." + value = { + a_ipv4 = { + type = dynu_dns_record.a_ipv4.record_type + content = dynu_dns_record.a_ipv4.content + } + aaaa_ipv6 = { + type = dynu_dns_record.aaaa_ipv6.record_type + content = dynu_dns_record.aaaa_ipv6.content + } + cname = { + type = dynu_dns_record.cname.record_type + content = dynu_dns_record.cname.content + } + blank_a = { + type = dynu_dns_record.blank_a.record_type + content = dynu_dns_record.blank_a.content + } + blank_aaaa = { + type = dynu_dns_record.blank_aaaa.record_type + content = dynu_dns_record.blank_aaaa.content + } + } } diff --git a/examples/live_safe_dns_record/terraform.tfvars.example b/examples/live_safe_dns_record/terraform.tfvars.example index b958b7f..1158cca 100644 --- a/examples/live_safe_dns_record/terraform.tfvars.example +++ b/examples/live_safe_dns_record/terraform.tfvars.example @@ -1,14 +1,16 @@ -# Required: use only a Dynu-managed root domain/zone that you control. -# Do NOT put an existing full production hostname here. +# Optional if DYNU_API_KEY is exported in your shell. +# dynu_api_key = "REPLACE_ME" + +# Required: a Dynu-managed root domain/zone that you control. +# Do NOT put an existing production hostname here. dynu_root_domain = "example.com" -# Optional naming for disposable hostname labels. -# Final hostname format: -. -test_prefix = "tfacc" +# Set to a unique value per run so records are clearly disposable. test_suffix = "manual" -# Optional: documentation/test IP for disposable A record content. -test_ip = "1.1.1.1" +# Safe documentation values for live test scenarios. +test_ipv4 = "192.0.2.123" +test_ipv6 = "2001:db8::123" +test_cname_target = "example.com" -# Optional TTL for disposable A record. test_ttl = 300 diff --git a/examples/live_safe_dns_record/variables.tf b/examples/live_safe_dns_record/variables.tf index e6bcc85..b0e1a9c 100644 --- a/examples/live_safe_dns_record/variables.tf +++ b/examples/live_safe_dns_record/variables.tf @@ -1,35 +1,66 @@ variable "dynu_api_key" { - description = "Dynu API key. Leave null to use DYNU_API_KEY from the environment." + description = "Dynu API key. Leave null to use DYNU_API_KEY from environment." type = string default = null sensitive = true } variable "dynu_root_domain" { - description = "Dynu-managed root domain for creating a disposable live test subdomain (for example: example.com)." + description = "Dynu-managed root domain used to create disposable live test records (for example: example.com)." type = string -} -variable "test_prefix" { - description = "Prefix for the disposable test hostname label." - type = string - default = "tfacc" + validation { + condition = trimspace(var.dynu_root_domain) != "" + error_message = "dynu_root_domain must be a non-empty Dynu-managed root domain." + } } variable "test_suffix" { - description = "Suffix for disposable hostname uniqueness (set explicitly per run if needed)." + description = "Suffix used to make this run's disposable record hostnames unique." type = string default = "manual" + + validation { + condition = trimspace(var.test_suffix) != "" + error_message = "test_suffix must be non-empty." + } } -variable "test_ip" { - description = "IPv4 value for the disposable A record. Dynu rejects reserved documentation ranges (for example 198.51.100.0/24), so this default uses a public resolver address." +variable "test_ipv4" { + description = "IPv4 value for the disposable A record scenario." type = string - default = "1.1.1.1" + default = "192.0.2.123" + + validation { + condition = trimspace(var.test_ipv4) != "" + error_message = "test_ipv4 must be non-empty for the A record scenario." + } +} + +variable "test_ipv6" { + description = "IPv6 value for the disposable AAAA record scenario." + type = string + default = "2001:db8::123" + + validation { + condition = trimspace(var.test_ipv6) != "" + error_message = "test_ipv6 must be non-empty for the AAAA record scenario." + } +} + +variable "test_cname_target" { + description = "CNAME target for the disposable CNAME record scenario." + type = string + default = "example.com" + + validation { + condition = trimspace(var.test_cname_target) != "" + error_message = "test_cname_target must be non-empty for the CNAME scenario." + } } variable "test_ttl" { - description = "TTL in seconds for the disposable DNS record." + description = "TTL in seconds for disposable DNS records." type = number default = 300 } diff --git a/examples/live_safe_dns_record/versions.tf b/examples/live_safe_dns_record/versions.tf new file mode 100644 index 0000000..572584b --- /dev/null +++ b/examples/live_safe_dns_record/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.5.0" + + required_providers { + dynu = { + source = "dynu/dynu" + } + } +} diff --git a/internal/provider/provider_acc_test.go b/internal/provider/provider_acc_test.go index 5443b7b..50fc517 100644 --- a/internal/provider/provider_acc_test.go +++ b/internal/provider/provider_acc_test.go @@ -13,6 +13,10 @@ import ( "github.com/dynu/terraform-provider-dynu/internal/dynuclient" ) +func stringPtr(s string) *string { + return &s +} + func testAccPreCheck(t *testing.T) { t.Helper() if os.Getenv("TF_ACC") != "1" { @@ -139,7 +143,7 @@ func TestAccDNSRecordCNAMELifecycle(t *testing.T) { created, err := client.CreateDNSRecord(context.Background(), domainID, dynuclient.CreateDNSRecordRequest{ NodeName: nodeName, RecordType: "CNAME", - Content: "target1.example.com", + Content: stringPtr("target1.example.com"), TTL: 120, }) if err != nil { @@ -150,7 +154,7 @@ func TestAccDNSRecordCNAMELifecycle(t *testing.T) { updated, err := client.UpdateDNSRecord(context.Background(), domainID, created.ID, dynuclient.UpdateDNSRecordRequest{ NodeName: nodeName, RecordType: "CNAME", - Content: "target2.example.com", + Content: stringPtr("target2.example.com"), TTL: 300, }) if err != nil {