Archived
Merge pull request #29 from beatz174-bit/codex/update-live-safe-dns-example-setup-es3oe1
examples: expand live_safe_dns_record to cover multiple record scenarios; add versioning and test fixes
This commit is contained in:
@@ -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:
|
||||
- `<test_prefix>-<test_suffix>.<dynu_root_domain>`
|
||||
- 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-<suffix>.<root_domain>`)
|
||||
2. `AAAA` record with IPv6 content (`codex-aaaa-<suffix>.<root_domain>`)
|
||||
3. `CNAME` record (`codex-cname-<suffix>.<root_domain>`)
|
||||
4. **Blank `A` record** with no content/IP (`codex-blank-a-<suffix>.<root_domain>`)
|
||||
5. **Blank `AAAA` record** with no content/IP (`codex-blank-aaaa-<suffix>.<root_domain>`)
|
||||
|
||||
> [!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`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 "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
|
||||
}
|
||||
}
|
||||
|
||||
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."
|
||||
}
|
||||
|
||||
@@ -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>-<test_suffix>.<dynu_root_domain>
|
||||
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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
terraform {
|
||||
required_version = ">= 1.5.0"
|
||||
|
||||
required_providers {
|
||||
dynu = {
|
||||
source = "dynu/dynu"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user