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
|
# Live Safe DNS Record Example
|
||||||
|
|
||||||
This example is an **opt-in live test** for writable Dynu provider functionality.
|
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.
|
||||||
It creates exactly one disposable DNS `A` record under a user-supplied Dynu root domain,
|
|
||||||
then you remove it with `terraform destroy`.
|
|
||||||
|
|
||||||
## Safety model
|
## What this creates
|
||||||
|
|
||||||
- Creates a **new disposable subdomain** using configurable `test_prefix` and `test_suffix` values.
|
Using a single suffix (`test_suffix`), this example creates five DNS record scenarios under `dynu_root_domain`:
|
||||||
- Hostname format is:
|
|
||||||
- `<test_prefix>-<test_suffix>.<dynu_root_domain>`
|
1. `A` record with IPv4 content (`codex-a-<suffix>.<root_domain>`)
|
||||||
- Creates exactly one record:
|
2. `AAAA` record with IPv6 content (`codex-aaaa-<suffix>.<root_domain>`)
|
||||||
- `A` record, default content `1.1.1.1`, default TTL `300`.
|
3. `CNAME` record (`codex-cname-<suffix>.<root_domain>`)
|
||||||
- Does **not** import, update, replace, or delete existing production hostnames/records.
|
4. **Blank `A` record** with no content/IP (`codex-blank-a-<suffix>.<root_domain>`)
|
||||||
- `terraform destroy` removes only the disposable record in this state.
|
5. **Blank `AAAA` record** with no content/IP (`codex-blank-aaaa-<suffix>.<root_domain>`)
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> Do not repurpose this example to target existing production subdomains.
|
> Do not use a suffix that overlaps important existing hostnames. This example is intended only for disposable test records that you can safely destroy.
|
||||||
> Always provide only a root Dynu-managed domain (for example, `example.com`) via
|
|
||||||
> `dynu_root_domain`.
|
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Local provider binary and Terraform `dev_overrides` set up for `dynu/dynu`.
|
- Local provider binary + Terraform `dev_overrides` for `dynu/dynu`
|
||||||
- `DYNU_API_KEY` exported (or set `dynu_api_key` in `terraform.tfvars`).
|
- `DYNU_API_KEY` exported, or `dynu_api_key` set in `terraform.tfvars`
|
||||||
- A Dynu-managed root domain you control.
|
- A Dynu-managed root domain you control
|
||||||
|
|
||||||
## Workflow
|
## Configure
|
||||||
|
|
||||||
This example intentionally uses only the `dynu/dynu` provider (no `hashicorp/random`).
|
|
||||||
If you want per-run uniqueness, pass `test_suffix` explicitly.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go build -o terraform-provider-dynu
|
|
||||||
cd examples/live_safe_dns_record
|
|
||||||
cp terraform.tfvars.example terraform.tfvars
|
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,
|
Edit `terraform.tfvars` and set at least:
|
||||||
then clean up using the **same** suffix value:
|
|
||||||
|
- `dynu_root_domain`
|
||||||
|
- `test_suffix` (use a unique value per run)
|
||||||
|
|
||||||
|
Optional overrides include `test_ipv4`, `test_ipv6`, and `test_cname_target`.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
```bash
|
```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.
|
- `terraform apply` should create all five record scenarios.
|
||||||
- If apply is interrupted or partially succeeds, rerun `terraform destroy` from this same
|
- `terraform destroy` should remove all five records created by this state.
|
||||||
directory with the same `terraform.tfvars`, CLI vars, and state files.
|
- If you need to target a single scenario, resources are explicitly named:
|
||||||
- If re-running without destroying first, Terraform may keep state for the previous
|
- `dynu_dns_record.a_ipv4`
|
||||||
disposable record until it is destroyed.
|
- `dynu_dns_record.aaaa_ipv6`
|
||||||
|
- `dynu_dns_record.cname`
|
||||||
|
- `dynu_dns_record.blank_a`
|
||||||
|
- `dynu_dns_record.blank_aaaa`
|
||||||
|
|||||||
@@ -1,24 +1,54 @@
|
|||||||
terraform {
|
# Local development note:
|
||||||
required_providers {
|
# Keep source as "dynu/dynu" and use ~/.terraformrc dev_overrides to point to
|
||||||
dynu = {
|
# your local terraform-provider-dynu binary during provider development.
|
||||||
source = "dynu/dynu"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
provider "dynu" {
|
provider "dynu" {
|
||||||
api_key = var.dynu_api_key
|
api_key = var.dynu_api_key
|
||||||
}
|
}
|
||||||
|
|
||||||
locals {
|
locals {
|
||||||
disposable_label = "${var.test_prefix}-${var.test_suffix}"
|
hostname_a_ipv4 = "codex-a-${var.test_suffix}.${var.dynu_root_domain}"
|
||||||
disposable_hostname = "${local.disposable_label}.${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" {
|
resource "dynu_dns_record" "a_ipv4" {
|
||||||
hostname = local.disposable_hostname
|
hostname = local.hostname_a_ipv4
|
||||||
record_type = "A"
|
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
|
ttl = var.test_ttl
|
||||||
state = true
|
state = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,47 @@
|
|||||||
output "disposable_hostname" {
|
output "record_hostnames" {
|
||||||
description = "Disposable hostname created by this run."
|
description = "Disposable hostnames created for each live-safe DNS scenario."
|
||||||
value = local.disposable_hostname
|
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" {
|
output "record_ids" {
|
||||||
description = "Dynu DNS record ID in domain_id/record_id format for the disposable record."
|
description = "Dynu record IDs (domain_id/record_id) for each scenario."
|
||||||
value = dynu_dns_record.safe_live_test.id
|
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" {
|
output "record_values" {
|
||||||
description = "Dynu root domain used for this live-safe test."
|
description = "Record type/content summary for each scenario; blank records intentionally omit content."
|
||||||
value = var.dynu_root_domain
|
value = {
|
||||||
}
|
a_ipv4 = {
|
||||||
|
type = dynu_dns_record.a_ipv4.record_type
|
||||||
output "cleanup_reminder" {
|
content = dynu_dns_record.a_ipv4.content
|
||||||
description = "Reminder to destroy the disposable record after verification."
|
}
|
||||||
value = "Run terraform destroy in this same directory/state to remove only this disposable record."
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
# Required: use only a Dynu-managed root domain/zone that you control.
|
# Optional if DYNU_API_KEY is exported in your shell.
|
||||||
# Do NOT put an existing full production hostname here.
|
# 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"
|
dynu_root_domain = "example.com"
|
||||||
|
|
||||||
# Optional naming for disposable hostname labels.
|
# Set to a unique value per run so records are clearly disposable.
|
||||||
# Final hostname format: <test_prefix>-<test_suffix>.<dynu_root_domain>
|
|
||||||
test_prefix = "tfacc"
|
|
||||||
test_suffix = "manual"
|
test_suffix = "manual"
|
||||||
|
|
||||||
# Optional: documentation/test IP for disposable A record content.
|
# Safe documentation values for live test scenarios.
|
||||||
test_ip = "1.1.1.1"
|
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
|
test_ttl = 300
|
||||||
|
|||||||
@@ -1,35 +1,66 @@
|
|||||||
variable "dynu_api_key" {
|
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
|
type = string
|
||||||
default = null
|
default = null
|
||||||
sensitive = true
|
sensitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "dynu_root_domain" {
|
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
|
type = string
|
||||||
}
|
|
||||||
|
|
||||||
variable "test_prefix" {
|
validation {
|
||||||
description = "Prefix for the disposable test hostname label."
|
condition = trimspace(var.dynu_root_domain) != ""
|
||||||
type = string
|
error_message = "dynu_root_domain must be a non-empty Dynu-managed root domain."
|
||||||
default = "tfacc"
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "test_suffix" {
|
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
|
type = string
|
||||||
default = "manual"
|
default = "manual"
|
||||||
|
|
||||||
|
validation {
|
||||||
|
condition = trimspace(var.test_suffix) != ""
|
||||||
|
error_message = "test_suffix must be non-empty."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
variable "test_ip" {
|
variable "test_ipv4" {
|
||||||
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."
|
description = "IPv4 value for the disposable A record scenario."
|
||||||
type = string
|
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" {
|
variable "test_ttl" {
|
||||||
description = "TTL in seconds for the disposable DNS record."
|
description = "TTL in seconds for disposable DNS records."
|
||||||
type = number
|
type = number
|
||||||
default = 300
|
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"
|
"github.com/dynu/terraform-provider-dynu/internal/dynuclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func stringPtr(s string) *string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
func testAccPreCheck(t *testing.T) {
|
func testAccPreCheck(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if os.Getenv("TF_ACC") != "1" {
|
if os.Getenv("TF_ACC") != "1" {
|
||||||
@@ -139,7 +143,7 @@ func TestAccDNSRecordCNAMELifecycle(t *testing.T) {
|
|||||||
created, err := client.CreateDNSRecord(context.Background(), domainID, dynuclient.CreateDNSRecordRequest{
|
created, err := client.CreateDNSRecord(context.Background(), domainID, dynuclient.CreateDNSRecordRequest{
|
||||||
NodeName: nodeName,
|
NodeName: nodeName,
|
||||||
RecordType: "CNAME",
|
RecordType: "CNAME",
|
||||||
Content: "target1.example.com",
|
Content: stringPtr("target1.example.com"),
|
||||||
TTL: 120,
|
TTL: 120,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -150,7 +154,7 @@ func TestAccDNSRecordCNAMELifecycle(t *testing.T) {
|
|||||||
updated, err := client.UpdateDNSRecord(context.Background(), domainID, created.ID, dynuclient.UpdateDNSRecordRequest{
|
updated, err := client.UpdateDNSRecord(context.Background(), domainID, created.ID, dynuclient.UpdateDNSRecordRequest{
|
||||||
NodeName: nodeName,
|
NodeName: nodeName,
|
||||||
RecordType: "CNAME",
|
RecordType: "CNAME",
|
||||||
Content: "target2.example.com",
|
Content: stringPtr("target2.example.com"),
|
||||||
TTL: 300,
|
TTL: 300,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user