Remove unsupported location field from DNS record resource

This commit is contained in:
beatz174-bit
2026-04-29 16:10:50 +10:00
parent 1dbd948a22
commit 5eed12a594
9 changed files with 18 additions and 87 deletions
+1 -3
View File
@@ -166,7 +166,6 @@ Arguments:
- `enabled` (Bool, optional, defaults to `true`) - `enabled` (Bool, optional, defaults to `true`)
- `group` (String, optional) - `group` (String, optional)
- `host` (String, optional) - `host` (String, optional)
- `location` (String, optional; A/AAAA only)
- `node_name` (String, optional) - `node_name` (String, optional)
Attributes: Attributes:
@@ -185,7 +184,6 @@ resource "dynu_dns_record" "txt" {
content = "198.51.100.10" content = "198.51.100.10"
ttl = 90 ttl = 90
enabled = false enabled = false
location = "us"
} }
resource "dynu_dns_record" "dynamic_a" { resource "dynu_dns_record" "dynamic_a" {
@@ -246,7 +244,7 @@ Attributes:
- `domain_name` (String) - `domain_name` (String)
- `records` (List(Object)) with: - `records` (List(Object)) with:
- `id`, `domain_id`, `domain_name`, `node_name`, `hostname`, `record_type` - `id`, `domain_id`, `domain_name`, `node_name`, `hostname`, `record_type`
- `ttl`, `state`, `content`, `updated_on`, `group`, `host`, `location` - `ttl`, `state`, `content`, `updated_on`, `group`, `host`
Example: Example:
+3 -4
View File
@@ -6,8 +6,8 @@ This example is an **opt-in live test** for writable Dynu provider functionality
Using a single suffix (`test_suffix`), this example creates five DNS record scenarios under `dynu_root_domain`: Using a single suffix (`test_suffix`), this example creates five DNS record scenarios under `dynu_root_domain`:
1. `A` record with IPv4 content, `location`, and minimum TTL=90 (`codex-a-<suffix>.<root_domain>`) 1. `A` record with IPv4 content, minimum TTL=90 (`codex-a-<suffix>.<root_domain>`)
2. `AAAA` record with IPv6 content and `location` (`codex-aaaa-<suffix>.<root_domain>`) 2. `AAAA` record with IPv6 content (`codex-aaaa-<suffix>.<root_domain>`)
3. `CNAME` record created disabled (`enabled = false`) (`codex-cname-<suffix>.<root_domain>`) 3. `CNAME` record created disabled (`enabled = false`) (`codex-cname-<suffix>.<root_domain>`)
4. **Dynamic `A` record** with omitted content (`codex-dynamic-a-<suffix>.<root_domain>`) 4. **Dynamic `A` record** with omitted content (`codex-dynamic-a-<suffix>.<root_domain>`)
5. **Dynamic `AAAA` record** with omitted content (`codex-dynamic-aaaa-<suffix>.<root_domain>`) 5. **Dynamic `AAAA` record** with omitted content (`codex-dynamic-aaaa-<suffix>.<root_domain>`)
@@ -32,10 +32,9 @@ Edit `terraform.tfvars` and set at least:
- `dynu_root_domain` - `dynu_root_domain`
- `test_suffix` (use a unique value per run) - `test_suffix` (use a unique value per run)
Optional overrides include `test_ipv4`, `test_ipv6`, `test_cname_target`, `test_ttl`, and `test_location`. Optional overrides include `test_ipv4`, `test_ipv6`, `test_cname_target`, `test_ttl`.
- `test_ttl` must be `0` (provider/API default) or `>= 90`. - `test_ttl` must be `0` (provider/API default) or `>= 90`.
- `test_location` applies only to A/AAAA records.
## Run ## Run
-2
View File
@@ -19,7 +19,6 @@ resource "dynu_dns_record" "a_ipv4" {
content = var.test_ipv4 content = var.test_ipv4
ttl = 90 ttl = 90
enabled = true enabled = true
location = var.test_location
} }
resource "dynu_dns_record" "aaaa_ipv6" { resource "dynu_dns_record" "aaaa_ipv6" {
@@ -28,7 +27,6 @@ resource "dynu_dns_record" "aaaa_ipv6" {
content = var.test_ipv6 content = var.test_ipv6
ttl = var.test_ttl ttl = var.test_ttl
enabled = true enabled = true
location = var.test_location
} }
resource "dynu_dns_record" "cname" { resource "dynu_dns_record" "cname" {
@@ -14,4 +14,3 @@ test_ipv6 = "2001:db8::123"
test_cname_target = "example.com" test_cname_target = "example.com"
test_ttl = 300 test_ttl = 300
test_location = "us"
@@ -64,9 +64,3 @@ variable "test_ttl" {
type = number type = number
default = 300 default = 300
} }
variable "test_location" {
description = "Optional Dynu location hint for A/AAAA records."
type = string
default = "us"
}
@@ -1,8 +1,7 @@
resource "dynu_dns_record" "a_location_min_ttl" { resource "dynu_dns_record" "a_enabled_min_ttl" {
hostname = "api.example.com" hostname = "api.example.com"
record_type = "A" record_type = "A"
content = "198.51.100.10" content = "198.51.100.10"
ttl = 90 ttl = 90
enabled = false enabled = false
location = "us"
} }
+3 -10
View File
@@ -106,7 +106,6 @@ type DNSRecord struct {
UpdatedOn string `json:"updatedOn"` UpdatedOn string `json:"updatedOn"`
Group string `json:"group"` Group string `json:"group"`
Host string `json:"host"` Host string `json:"host"`
Location string `json:"location"`
} }
type CreateDNSRecordRequest struct { type CreateDNSRecordRequest struct {
@@ -117,7 +116,6 @@ type CreateDNSRecordRequest struct {
State *bool `json:"state,omitempty"` State *bool `json:"state,omitempty"`
Group string `json:"group,omitempty"` Group string `json:"group,omitempty"`
Host string `json:"host,omitempty"` Host string `json:"host,omitempty"`
Location string `json:"location,omitempty"`
} }
type UpdateDNSRecordRequest struct { type UpdateDNSRecordRequest struct {
@@ -128,7 +126,6 @@ type UpdateDNSRecordRequest struct {
State *bool `json:"state,omitempty"` State *bool `json:"state,omitempty"`
Group string `json:"group,omitempty"` Group string `json:"group,omitempty"`
Host string `json:"host,omitempty"` Host string `json:"host,omitempty"`
Location string `json:"location,omitempty"`
} }
type listDomainsResponse struct { type listDomainsResponse struct {
@@ -210,7 +207,7 @@ func (c *Client) GetDNSRecord(ctx context.Context, domainID int64, recordID int6
func (c *Client) CreateDNSRecord(ctx context.Context, domainID int64, req CreateDNSRecordRequest) (*DNSRecord, error) { func (c *Client) CreateDNSRecord(ctx context.Context, domainID int64, req CreateDNSRecordRequest) (*DNSRecord, error) {
var resp getDNSRecordResponse var resp getDNSRecordResponse
if err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/dns/%d/record", domainID), buildDNSRecordUpsertPayload(req.RecordType, req.NodeName, req.Content, req.TTL, req.State, req.Group, req.Host, req.Location), &resp); err != nil { if err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/dns/%d/record", domainID), buildDNSRecordUpsertPayload(req.RecordType, req.NodeName, req.Content, req.TTL, req.State, req.Group, req.Host), &resp); err != nil {
return nil, err return nil, err
} }
normalizeDNSRecord(&resp.DNSRecord) normalizeDNSRecord(&resp.DNSRecord)
@@ -219,7 +216,7 @@ func (c *Client) CreateDNSRecord(ctx context.Context, domainID int64, req Create
func (c *Client) UpdateDNSRecord(ctx context.Context, domainID int64, recordID int64, req UpdateDNSRecordRequest) (*DNSRecord, error) { func (c *Client) UpdateDNSRecord(ctx context.Context, domainID int64, recordID int64, req UpdateDNSRecordRequest) (*DNSRecord, error) {
var resp getDNSRecordResponse var resp getDNSRecordResponse
if err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/dns/%d/record/%d", domainID, recordID), buildDNSRecordUpsertPayload(req.RecordType, req.NodeName, req.Content, req.TTL, req.State, req.Group, req.Host, req.Location), &resp); err != nil { if err := c.doRequest(ctx, http.MethodPost, fmt.Sprintf("/dns/%d/record/%d", domainID, recordID), buildDNSRecordUpsertPayload(req.RecordType, req.NodeName, req.Content, req.TTL, req.State, req.Group, req.Host), &resp); err != nil {
return nil, err return nil, err
} }
normalizeDNSRecord(&resp.DNSRecord) normalizeDNSRecord(&resp.DNSRecord)
@@ -322,10 +319,9 @@ type dnsRecordUpsertPayload struct {
State *bool `json:"state,omitempty"` State *bool `json:"state,omitempty"`
Group string `json:"group,omitempty"` Group string `json:"group,omitempty"`
Host string `json:"host,omitempty"` Host string `json:"host,omitempty"`
Location string `json:"location,omitempty"`
} }
func buildDNSRecordUpsertPayload(recordType string, nodeName string, content *string, ttl int64, state *bool, group string, host string, location string) dnsRecordUpsertPayload { func buildDNSRecordUpsertPayload(recordType string, nodeName string, content *string, ttl int64, state *bool, group string, host string) dnsRecordUpsertPayload {
normalizedType := strings.ToUpper(strings.TrimSpace(recordType)) normalizedType := strings.ToUpper(strings.TrimSpace(recordType))
normalizedContent := normalizeOptionalContent(content) normalizedContent := normalizeOptionalContent(content)
payload := dnsRecordUpsertPayload{ payload := dnsRecordUpsertPayload{
@@ -335,7 +331,6 @@ func buildDNSRecordUpsertPayload(recordType string, nodeName string, content *st
State: state, State: state,
Group: group, Group: group,
Host: host, Host: host,
Location: location,
} }
switch normalizedType { switch normalizedType {
@@ -351,10 +346,8 @@ func buildDNSRecordUpsertPayload(recordType string, nodeName string, content *st
if normalizedContent != nil { if normalizedContent != nil {
payload.Host = *normalizedContent payload.Host = *normalizedContent
} }
payload.Location = ""
default: default:
payload.Content = normalizedContent payload.Content = normalizedContent
payload.Location = ""
} }
return payload return payload
-40
View File
@@ -43,7 +43,6 @@ type dnsRecordResourceModel struct {
Enabled types.Bool `tfsdk:"enabled"` Enabled types.Bool `tfsdk:"enabled"`
Group types.String `tfsdk:"group"` Group types.String `tfsdk:"group"`
Host types.String `tfsdk:"host"` Host types.String `tfsdk:"host"`
Location types.String `tfsdk:"location"`
NodeName types.String `tfsdk:"node_name"` NodeName types.String `tfsdk:"node_name"`
DomainID types.Int64 `tfsdk:"domain_id"` DomainID types.Int64 `tfsdk:"domain_id"`
DomainName types.String `tfsdk:"domain_name"` DomainName types.String `tfsdk:"domain_name"`
@@ -83,7 +82,6 @@ func (r *dnsRecordResource) Schema(_ context.Context, _ resource.SchemaRequest,
"enabled": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true), Description: "Whether this DNS record is enabled/active."}, "enabled": schema.BoolAttribute{Optional: true, Computed: true, Default: booldefault.StaticBool(true), Description: "Whether this DNS record is enabled/active."},
"group": schema.StringAttribute{Optional: true, Computed: true, Description: "Dynu group value for this record."}, "group": schema.StringAttribute{Optional: true, Computed: true, Description: "Dynu group value for this record."},
"host": schema.StringAttribute{Optional: true, Computed: true, Description: "Host field for supported Dynu record types."}, "host": schema.StringAttribute{Optional: true, Computed: true, Description: "Host field for supported Dynu record types."},
"location": schema.StringAttribute{Optional: true, Computed: true, Description: "Dynu location hint for A/AAAA records only."},
"node_name": schema.StringAttribute{Optional: true, Computed: true, Description: "Node/label portion of the record."}, "node_name": schema.StringAttribute{Optional: true, Computed: true, Description: "Node/label portion of the record."},
"domain_id": schema.Int64Attribute{Computed: true, Description: "Dynu domain ID resolved from hostname."}, "domain_id": schema.Int64Attribute{Computed: true, Description: "Dynu domain ID resolved from hostname."},
"domain_name": schema.StringAttribute{Computed: true, Description: "Dynu root domain name resolved from hostname."}, "domain_name": schema.StringAttribute{Computed: true, Description: "Dynu root domain name resolved from hostname."},
@@ -122,7 +120,6 @@ func (r *dnsRecordResource) ValidateConfig(ctx context.Context, req resource.Val
} }
validateDNSRecordContentForTypeWithKnowledge(recordType, content, contentKnown, dynamicIntent, &resp.Diagnostics) validateDNSRecordContentForTypeWithKnowledge(recordType, content, contentKnown, dynamicIntent, &resp.Diagnostics)
validateDNSRecordTTL(config.TTL, &resp.Diagnostics) validateDNSRecordTTL(config.TTL, &resp.Diagnostics)
validateDNSRecordLocation(recordType, config.Location, &resp.Diagnostics)
} }
func (r *dnsRecordResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { func (r *dnsRecordResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
@@ -152,7 +149,6 @@ func (r *dnsRecordResource) Create(ctx context.Context, req resource.CreateReque
State: boolPointerFromOptional(plan.Enabled), State: boolPointerFromOptional(plan.Enabled),
Group: stringFromOptional(plan.Group), Group: stringFromOptional(plan.Group),
Host: stringFromOptional(plan.Host), Host: stringFromOptional(plan.Host),
Location: stringFromOptional(plan.Location),
} }
createReq = normalizeDNSRecordCreateRequestForType(createReq) createReq = normalizeDNSRecordCreateRequestForType(createReq)
if !validateDNSRecordContentForType(createReq.RecordType, createReq.Content, dynamicIntent, &resp.Diagnostics) { if !validateDNSRecordContentForType(createReq.RecordType, createReq.Content, dynamicIntent, &resp.Diagnostics) {
@@ -161,9 +157,6 @@ func (r *dnsRecordResource) Create(ctx context.Context, req resource.CreateReque
if !validateRecordTTLSeconds(createReq.TTL, &resp.Diagnostics) { if !validateRecordTTLSeconds(createReq.TTL, &resp.Diagnostics) {
return return
} }
if !validateLocationForType(createReq.RecordType, createReq.Location, &resp.Diagnostics) {
return
}
record, err := r.clientProvider.client.CreateDNSRecord(ctx, domainID, createReq) record, err := r.clientProvider.client.CreateDNSRecord(ctx, domainID, createReq)
if err != nil && dynamicIntent && isUnsupportedEmptyContentError(err) { if err != nil && dynamicIntent && isUnsupportedEmptyContentError(err) {
@@ -256,7 +249,6 @@ func (r *dnsRecordResource) Update(ctx context.Context, req resource.UpdateReque
State: boolPointerFromOptional(preferKnownBool(plan.Enabled, state.Enabled)), State: boolPointerFromOptional(preferKnownBool(plan.Enabled, state.Enabled)),
Group: stringFromOptional(preferKnownString(plan.Group, state.Group)), Group: stringFromOptional(preferKnownString(plan.Group, state.Group)),
Host: stringFromOptional(preferKnownString(plan.Host, state.Host)), Host: stringFromOptional(preferKnownString(plan.Host, state.Host)),
Location: locationForUpdate(recordType, plan.Location, state.Location),
} }
updateReq = normalizeDNSRecordUpdateRequestForType(updateReq) updateReq = normalizeDNSRecordUpdateRequestForType(updateReq)
if !validateDNSRecordContentForType(updateReq.RecordType, updateReq.Content, dynamicIntent, &resp.Diagnostics) { if !validateDNSRecordContentForType(updateReq.RecordType, updateReq.Content, dynamicIntent, &resp.Diagnostics) {
@@ -265,9 +257,6 @@ func (r *dnsRecordResource) Update(ctx context.Context, req resource.UpdateReque
if !validateRecordTTLSeconds(updateReq.TTL, &resp.Diagnostics) { if !validateRecordTTLSeconds(updateReq.TTL, &resp.Diagnostics) {
return return
} }
if !validateLocationForType(updateReq.RecordType, updateReq.Location, &resp.Diagnostics) {
return
}
if _, err := r.clientProvider.client.UpdateDNSRecord(ctx, domainID, recordID, updateReq); err != nil { if _, err := r.clientProvider.client.UpdateDNSRecord(ctx, domainID, recordID, updateReq); err != nil {
if dynamicIntent && isUnsupportedEmptyContentError(err) { if dynamicIntent && isUnsupportedEmptyContentError(err) {
@@ -279,7 +268,6 @@ func (r *dnsRecordResource) Update(ctx context.Context, req resource.UpdateReque
State: updateReq.State, State: updateReq.State,
Group: updateReq.Group, Group: updateReq.Group,
Host: updateReq.Host, Host: updateReq.Host,
Location: updateReq.Location,
}, &resp.Diagnostics); retryOK { }, &resp.Diagnostics); retryOK {
updateReq.Content = retryReq.Content updateReq.Content = retryReq.Content
updateReq.Group = retryReq.Group updateReq.Group = retryReq.Group
@@ -349,7 +337,6 @@ func mapDNSRecordToState(record dynuclient.DNSRecord, dynamicIntent bool) dnsRec
Enabled: types.BoolValue(record.State), Enabled: types.BoolValue(record.State),
Group: mapString(record.Group), Group: mapString(record.Group),
Host: mapString(record.Host), Host: mapString(record.Host),
Location: mapString(record.Location),
NodeName: mapString(record.NodeName), NodeName: mapString(record.NodeName),
DomainID: types.Int64Value(record.DomainID), DomainID: types.Int64Value(record.DomainID),
DomainName: mapString(record.DomainName), DomainName: mapString(record.DomainName),
@@ -583,33 +570,6 @@ func validateRecordTTLSeconds(ttl int64, diagnostics *diag.Diagnostics) bool {
return true return true
} }
func validateDNSRecordLocation(recordType string, location types.String, diagnostics *diag.Diagnostics) {
if location.IsNull() || location.IsUnknown() {
return
}
validateLocationForType(recordType, location.ValueString(), diagnostics)
}
func validateLocationForType(recordType string, location string, diagnostics *diag.Diagnostics) bool {
if strings.TrimSpace(location) == "" {
return true
}
normalizedType := strings.ToUpper(strings.TrimSpace(recordType))
if normalizedType != "A" && normalizedType != "AAAA" {
diagnostics.AddError("Invalid location", fmt.Sprintf("location is only supported for A and AAAA records, got %q.", normalizedType))
return false
}
return true
}
func locationForUpdate(recordType string, planLocation types.String, stateLocation types.String) string {
normalizedType := strings.ToUpper(strings.TrimSpace(recordType))
if normalizedType != "A" && normalizedType != "AAAA" {
return stringFromOptional(planLocation)
}
return stringFromOptional(preferKnownString(planLocation, stateLocation))
}
func resolveDynamicIntent(recordType string, content types.String, dynamic types.Bool, diagnostics *diag.Diagnostics) (bool, bool) { func resolveDynamicIntent(recordType string, content types.String, dynamic types.Bool, diagnostics *diag.Diagnostics) (bool, bool) {
normalizedType := strings.ToUpper(strings.TrimSpace(recordType)) normalizedType := strings.ToUpper(strings.TrimSpace(recordType))
contentPtr := stringPointerFromOptionalContent(content) contentPtr := stringPointerFromOptionalContent(content)
+10 -19
View File
@@ -1,11 +1,13 @@
package provider package provider
import ( import (
"context"
"strings" "strings"
"testing" "testing"
"github.com/dynu/terraform-provider-dynu/internal/dynuclient" "github.com/dynu/terraform-provider-dynu/internal/dynuclient"
"github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
) )
@@ -192,26 +194,15 @@ func TestValidateRecordTTLSeconds(t *testing.T) {
} }
} }
func TestValidateLocationForType(t *testing.T) { func TestDNSRecordSchemaHasEnabledAndNoLocation(t *testing.T) {
diags := diag.Diagnostics{} r := NewDNSRecordResource()
if !validateLocationForType("A", "us", &diags) || diags.HasError() { resp := &resource.SchemaResponse{}
t.Fatal("expected location for A to be valid") r.Schema(context.Background(), resource.SchemaRequest{}, resp)
if _, ok := resp.Schema.Attributes["enabled"]; !ok {
t.Fatal("expected enabled attribute in schema")
} }
diags = diag.Diagnostics{} if _, ok := resp.Schema.Attributes["location"]; ok {
if validateLocationForType("CNAME", "us", &diags) || !diags.HasError() { t.Fatal("expected location attribute to be removed from schema")
t.Fatal("expected location for CNAME to fail")
}
}
func TestLocationForUpdate(t *testing.T) {
if got := locationForUpdate("A", types.StringNull(), types.StringValue("us")); got != "us" {
t.Fatalf("expected A record update to preserve state location, got %q", got)
}
if got := locationForUpdate("CNAME", types.StringNull(), types.StringValue("us")); got != "" {
t.Fatalf("expected non-A/AAAA type to ignore prior state location when omitted, got %q", got)
}
if got := locationForUpdate("TXT", types.StringValue("eu"), types.StringValue("us")); got != "eu" {
t.Fatalf("expected explicit plan location to be returned as-is, got %q", got)
} }
} }