Archived
bump to v0.2.0: complete provider implementation with live-tested DNS record types
ci / test (push) Failing after 5m55s
ci / test (push) Failing after 5m55s
- DYNU_API_KEY env var fallback in provider config - dynu_domain resource: add 9 computed attributes (unicode_name, ipv4/ipv6 flags, wildcard aliases, allow_zone_transfer, dnssec, created_on, updated_on) - dynu_dns_records data source: add 6 type-specific fields (priority, weight, port, flags, tag, value) - Fix normalizeRecordContentForState for MX/SRV/NS/PTR (read host field) and CAA (read value field) to avoid plan/state inconsistency - Allow underscore-prefixed DNS labels in hostname validator (RFC 2782 SRV) - Preserve plan/state hostname across create/update/read to prevent inconsistent result errors when API returns full FQDN for node_name-style records - Update all version references from 0.1.0 to 0.2.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LFZyrmtKsezBwFWGTWsb2y
This commit is contained in:
@@ -23,7 +23,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
dynu = {
|
dynu = {
|
||||||
source = "beatz174-bit/dynu"
|
source = "beatz174-bit/dynu"
|
||||||
version = "~> 0.1.0"
|
version = "~> 0.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ Use a disposable domain/subdomain only.
|
|||||||
|
|
||||||
- Terraform provider source address: `beatz174-bit/dynu`.
|
- Terraform provider source address: `beatz174-bit/dynu`.
|
||||||
- Repository name must remain `terraform-provider-dynu`.
|
- Repository name must remain `terraform-provider-dynu`.
|
||||||
- Releases are triggered by pushing a semantic version tag such as `v0.1.0`.
|
- Releases are triggered by pushing a semantic version tag such as `v0.2.0`.
|
||||||
- Required GitHub Actions secrets:
|
- Required GitHub Actions secrets:
|
||||||
- `GPG_PRIVATE_KEY`
|
- `GPG_PRIVATE_KEY`
|
||||||
- `PASSPHRASE`
|
- `PASSPHRASE`
|
||||||
|
|||||||
@@ -37,3 +37,9 @@ data "dynu_dns_records" "records" {
|
|||||||
- `updated_on` (String)
|
- `updated_on` (String)
|
||||||
- `group` (String)
|
- `group` (String)
|
||||||
- `host` (String)
|
- `host` (String)
|
||||||
|
- `priority` (Number) Priority for MX and SRV records.
|
||||||
|
- `weight` (Number) Weight for SRV records.
|
||||||
|
- `port` (Number) Port for SRV records.
|
||||||
|
- `flags` (Number) Flags for CAA records.
|
||||||
|
- `tag` (String) Tag for CAA records.
|
||||||
|
- `value` (String) Value for CAA records.
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
dynu = {
|
dynu = {
|
||||||
source = "beatz174-bit/dynu"
|
source = "beatz174-bit/dynu"
|
||||||
version = "~> 0.1.0"
|
version = "~> 0.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,15 @@ These fields can be configured, and also reflect values returned by Dynu.
|
|||||||
- `id` (Number) Dynu numeric domain ID.
|
- `id` (Number) Dynu numeric domain ID.
|
||||||
- `state` (String) Dynu state.
|
- `state` (String) Dynu state.
|
||||||
- `token` (String, Sensitive) Dynu domain token.
|
- `token` (String, Sensitive) Dynu domain token.
|
||||||
|
- `unicode_name` (String) Unicode representation of the domain name.
|
||||||
|
- `ipv4` (Boolean) Whether IPv4 support is enabled.
|
||||||
|
- `ipv6` (Boolean) Whether IPv6 support is enabled.
|
||||||
|
- `ipv4_wildcard_alias` (Boolean) Whether IPv4 wildcard alias is enabled.
|
||||||
|
- `ipv6_wildcard_alias` (Boolean) Whether IPv6 wildcard alias is enabled.
|
||||||
|
- `allow_zone_transfer` (Boolean) Whether zone transfer is allowed.
|
||||||
|
- `dnssec` (Boolean) Whether DNSSEC is enabled.
|
||||||
|
- `created_on` (String) Creation timestamp as returned by Dynu.
|
||||||
|
- `updated_on` (String) Last update timestamp as returned by Dynu.
|
||||||
|
|
||||||
## Import
|
## Import
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
dynu = {
|
dynu = {
|
||||||
source = "beatz174-bit/dynu"
|
source = "beatz174-bit/dynu"
|
||||||
version = "~> 0.1.0"
|
version = "~> 0.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
dynu = {
|
dynu = {
|
||||||
source = "beatz174-bit/dynu"
|
source = "beatz174-bit/dynu"
|
||||||
version = "~> 0.1.0"
|
version = "~> 0.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
dynu = {
|
dynu = {
|
||||||
source = "beatz174-bit/dynu"
|
source = "beatz174-bit/dynu"
|
||||||
version = "~> 0.1.0"
|
version = "~> 0.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
dynu = {
|
dynu = {
|
||||||
source = "beatz174-bit/dynu"
|
source = "beatz174-bit/dynu"
|
||||||
version = "~> 0.1.0"
|
version = "~> 0.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ type dnsRecordStateItem struct {
|
|||||||
UpdatedOn types.String `tfsdk:"updated_on"`
|
UpdatedOn types.String `tfsdk:"updated_on"`
|
||||||
Group types.String `tfsdk:"group"`
|
Group types.String `tfsdk:"group"`
|
||||||
Host types.String `tfsdk:"host"`
|
Host types.String `tfsdk:"host"`
|
||||||
|
Priority types.Int64 `tfsdk:"priority"`
|
||||||
|
Weight types.Int64 `tfsdk:"weight"`
|
||||||
|
Port types.Int64 `tfsdk:"port"`
|
||||||
|
Flags types.Int64 `tfsdk:"flags"`
|
||||||
|
Tag types.String `tfsdk:"tag"`
|
||||||
|
Value types.String `tfsdk:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDNSRecordsDataSource() datasource.DataSource {
|
func NewDNSRecordsDataSource() datasource.DataSource {
|
||||||
@@ -81,6 +87,12 @@ func (d *dnsRecordsDataSource) Schema(_ context.Context, _ datasource.SchemaRequ
|
|||||||
"updated_on": schema.StringAttribute{Computed: true, Description: "Last update timestamp as returned by Dynu."},
|
"updated_on": schema.StringAttribute{Computed: true, Description: "Last update timestamp as returned by Dynu."},
|
||||||
"group": schema.StringAttribute{Computed: true, Description: "Dynu group value for this record."},
|
"group": schema.StringAttribute{Computed: true, Description: "Dynu group value for this record."},
|
||||||
"host": schema.StringAttribute{Computed: true, Description: "Host field as returned by Dynu."},
|
"host": schema.StringAttribute{Computed: true, Description: "Host field as returned by Dynu."},
|
||||||
|
"priority": schema.Int64Attribute{Computed: true, Description: "Priority for MX and SRV records."},
|
||||||
|
"weight": schema.Int64Attribute{Computed: true, Description: "Weight for SRV records."},
|
||||||
|
"port": schema.Int64Attribute{Computed: true, Description: "Port for SRV records."},
|
||||||
|
"flags": schema.Int64Attribute{Computed: true, Description: "Flags for CAA records."},
|
||||||
|
"tag": schema.StringAttribute{Computed: true, Description: "Tag for CAA records."},
|
||||||
|
"value": schema.StringAttribute{Computed: true, Description: "Value for CAA records."},
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -145,6 +157,12 @@ func (d *dnsRecordsDataSource) Read(ctx context.Context, req datasource.ReadRequ
|
|||||||
UpdatedOn: mapString(record.UpdatedOn),
|
UpdatedOn: mapString(record.UpdatedOn),
|
||||||
Group: mapString(record.Group),
|
Group: mapString(record.Group),
|
||||||
Host: mapString(record.Host),
|
Host: mapString(record.Host),
|
||||||
|
Priority: types.Int64Value(record.Priority),
|
||||||
|
Weight: types.Int64Value(record.Weight),
|
||||||
|
Port: types.Int64Value(record.Port),
|
||||||
|
Flags: types.Int64Value(record.Flags),
|
||||||
|
Tag: mapString(record.Tag),
|
||||||
|
Value: mapString(record.Value),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ var (
|
|||||||
_ datasource.DataSourceWithConfigure = &domainDataSource{}
|
_ datasource.DataSourceWithConfigure = &domainDataSource{}
|
||||||
)
|
)
|
||||||
|
|
||||||
var hostnameValidator = regexp.MustCompile(`^([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`)
|
// Allows underscore-prefixed labels (RFC 2782 SRV, RFC 6763 DNS-SD, etc.)
|
||||||
|
var hostnameValidator = regexp.MustCompile(`^([a-zA-Z0-9_](?:[a-zA-Z0-9_-]{0,61}[a-zA-Z0-9_])?\.)+[a-zA-Z]{2,}$`)
|
||||||
|
|
||||||
type domainDataSource struct {
|
type domainDataSource struct {
|
||||||
clientProvider *providerData
|
clientProvider *providerData
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package provider
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||||
@@ -47,7 +48,7 @@ func (p *dynuProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp
|
|||||||
"api_key": schema.StringAttribute{
|
"api_key": schema.StringAttribute{
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Sensitive: true,
|
Sensitive: true,
|
||||||
Description: "Dynu API key. Set this explicitly, such as via a Terraform variable in terraform.tfvars.",
|
Description: "Dynu API key. Can also be set via the DYNU_API_KEY environment variable.",
|
||||||
},
|
},
|
||||||
"base_url": schema.StringAttribute{
|
"base_url": schema.StringAttribute{
|
||||||
Optional: true,
|
Optional: true,
|
||||||
@@ -82,9 +83,11 @@ func (p *dynuProvider) Configure(ctx context.Context, req provider.ConfigureRequ
|
|||||||
|
|
||||||
func resolveAPIKey(configValue types.String) string {
|
func resolveAPIKey(configValue types.String) string {
|
||||||
if !configValue.IsNull() && !configValue.IsUnknown() {
|
if !configValue.IsNull() && !configValue.IsUnknown() {
|
||||||
return strings.TrimSpace(configValue.ValueString())
|
if v := strings.TrimSpace(configValue.ValueString()); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return strings.TrimSpace(os.Getenv("DYNU_API_KEY"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *dynuProvider) DataSources(_ context.Context) []func() datasource.DataSource {
|
func (p *dynuProvider) DataSources(_ context.Context) []func() datasource.DataSource {
|
||||||
|
|||||||
@@ -128,6 +128,20 @@ func TestIntegrationDataSourceDNSRecords(t *testing.T) {
|
|||||||
if len(state.Records) != 2 || state.Records[0].ID.ValueInt64() != 10 {
|
if len(state.Records) != 2 || state.Records[0].ID.ValueInt64() != 10 {
|
||||||
t.Fatalf("unexpected records state: %#v", state.Records)
|
t.Fatalf("unexpected records state: %#v", state.Records)
|
||||||
}
|
}
|
||||||
|
for _, rec := range state.Records {
|
||||||
|
if rec.Priority.IsNull() || rec.Priority.IsUnknown() {
|
||||||
|
t.Fatalf("expected priority to be a known value (zero for non-MX), got null/unknown for record %d", rec.ID.ValueInt64())
|
||||||
|
}
|
||||||
|
if rec.Weight.IsNull() || rec.Weight.IsUnknown() {
|
||||||
|
t.Fatalf("expected weight to be a known value, got null/unknown for record %d", rec.ID.ValueInt64())
|
||||||
|
}
|
||||||
|
if rec.Port.IsNull() || rec.Port.IsUnknown() {
|
||||||
|
t.Fatalf("expected port to be a known value, got null/unknown for record %d", rec.ID.ValueInt64())
|
||||||
|
}
|
||||||
|
if rec.Flags.IsNull() || rec.Flags.IsUnknown() {
|
||||||
|
t.Fatalf("expected flags to be a known value, got null/unknown for record %d", rec.ID.ValueInt64())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationDataSourceDiagnosticsFromAPIError(t *testing.T) {
|
func TestIntegrationDataSourceDiagnosticsFromAPIError(t *testing.T) {
|
||||||
|
|||||||
@@ -16,16 +16,23 @@ func TestResolveAPIKey(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
config types.String
|
config types.String
|
||||||
|
envVal string
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{name: "configured", config: types.StringValue("config-key"), want: "config-key"},
|
{name: "configured", config: types.StringValue("config-key"), want: "config-key"},
|
||||||
{name: "null when missing", config: types.StringNull(), want: ""},
|
{name: "null when missing", config: types.StringNull(), want: ""},
|
||||||
{name: "trim spaces", config: types.StringValue(" config-key "), want: "config-key"},
|
{name: "trim spaces", config: types.StringValue(" config-key "), want: "config-key"},
|
||||||
{name: "unknown when pending", config: types.StringUnknown(), want: ""},
|
{name: "unknown when pending", config: types.StringUnknown(), want: ""},
|
||||||
|
{name: "env var fallback when null", config: types.StringNull(), envVal: "env-key", want: "env-key"},
|
||||||
|
{name: "config takes priority over env", config: types.StringValue("config-key"), envVal: "env-key", want: "config-key"},
|
||||||
|
{name: "env var fallback when unknown", config: types.StringUnknown(), envVal: "env-key", want: "env-key"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if tc.envVal != "" {
|
||||||
|
t.Setenv("DYNU_API_KEY", tc.envVal)
|
||||||
|
}
|
||||||
if got := resolveAPIKey(tc.config); got != tc.want {
|
if got := resolveAPIKey(tc.config); got != tc.want {
|
||||||
t.Fatalf("resolveAPIKey() = %q, want %q", got, tc.want)
|
t.Fatalf("resolveAPIKey() = %q, want %q", got, tc.want)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,6 +189,8 @@ func (r *dnsRecordResource) Create(ctx context.Context, req resource.CreateReque
|
|||||||
|
|
||||||
state := mapDNSRecordToState(*record, dynamicIntent)
|
state := mapDNSRecordToState(*record, dynamicIntent)
|
||||||
state.ID = types.StringValue(formatDNSRecordID(record.DomainID, record.ID))
|
state.ID = types.StringValue(formatDNSRecordID(record.DomainID, record.ID))
|
||||||
|
// Preserve plan hostname so node_name overrides don't produce a plan/state inconsistency.
|
||||||
|
state.Hostname = plan.Hostname
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,6 +221,8 @@ func (r *dnsRecordResource) Read(ctx context.Context, req resource.ReadRequest,
|
|||||||
dynamicIntent := inferDynamicIntentFromState(state.RecordType, state.Content, state.Dynamic)
|
dynamicIntent := inferDynamicIntentFromState(state.RecordType, state.Content, state.Dynamic)
|
||||||
nextState := mapDNSRecordToState(*record, dynamicIntent)
|
nextState := mapDNSRecordToState(*record, dynamicIntent)
|
||||||
nextState.ID = state.ID
|
nextState.ID = state.ID
|
||||||
|
// Preserve the stored hostname so node_name overrides remain stable across refreshes.
|
||||||
|
nextState.Hostname = state.Hostname
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &nextState)...)
|
resp.Diagnostics.Append(resp.State.Set(ctx, &nextState)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,6 +322,8 @@ func (r *dnsRecordResource) Update(ctx context.Context, req resource.UpdateReque
|
|||||||
|
|
||||||
nextState := mapDNSRecordToState(*record, dynamicIntent)
|
nextState := mapDNSRecordToState(*record, dynamicIntent)
|
||||||
nextState.ID = state.ID
|
nextState.ID = state.ID
|
||||||
|
// Preserve plan hostname so node_name overrides remain stable after updates.
|
||||||
|
nextState.Hostname = plan.Hostname
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &nextState)...)
|
resp.Diagnostics.Append(resp.State.Set(ctx, &nextState)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,7 +363,7 @@ func (r *dnsRecordResource) ImportState(ctx context.Context, req resource.Import
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mapDNSRecordToState(record dynuclient.DNSRecord, dynamicIntent bool) dnsRecordResourceModel {
|
func mapDNSRecordToState(record dynuclient.DNSRecord, dynamicIntent bool) dnsRecordResourceModel {
|
||||||
content := normalizeRecordContentForState(record.RecordType, record.Content, dynamicIntent, record.Host)
|
content := normalizeRecordContentForState(record.RecordType, record.Content, dynamicIntent, record.Host, record.Value)
|
||||||
return dnsRecordResourceModel{
|
return dnsRecordResourceModel{
|
||||||
Hostname: mapString(record.Hostname),
|
Hostname: mapString(record.Hostname),
|
||||||
RecordType: mapString(record.RecordType),
|
RecordType: mapString(record.RecordType),
|
||||||
@@ -676,7 +682,7 @@ func inferDynamicIntentFromState(recordType types.String, content types.String,
|
|||||||
return content.IsNull() || (content.IsUnknown())
|
return content.IsNull() || (content.IsUnknown())
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeRecordContentForState(recordType string, content string, dynamicIntent bool, host string) types.String {
|
func normalizeRecordContentForState(recordType string, content string, dynamicIntent bool, host string, value string) types.String {
|
||||||
if dynamicIntent {
|
if dynamicIntent {
|
||||||
return types.StringNull()
|
return types.StringNull()
|
||||||
}
|
}
|
||||||
@@ -696,11 +702,17 @@ func normalizeRecordContentForState(recordType string, content string, dynamicIn
|
|||||||
if addr, err := netip.ParseAddr(trimmed); err == nil && addr.Is4() {
|
if addr, err := netip.ParseAddr(trimmed); err == nil && addr.Is4() {
|
||||||
return types.StringValue(addr.String())
|
return types.StringValue(addr.String())
|
||||||
}
|
}
|
||||||
case "CNAME":
|
case "CNAME", "MX", "SRV", "NS", "PTR":
|
||||||
|
// API returns the target in host field; content contains zone-style prefix (e.g. "10 mail.example.com.")
|
||||||
if trimmedHost := strings.TrimSpace(host); trimmedHost != "" {
|
if trimmedHost := strings.TrimSpace(host); trimmedHost != "" {
|
||||||
return types.StringValue(strings.TrimSuffix(trimmedHost, "."))
|
return types.StringValue(strings.TrimSuffix(trimmedHost, "."))
|
||||||
}
|
}
|
||||||
return types.StringValue(strings.TrimSuffix(trimmed, "."))
|
return types.StringValue(strings.TrimSuffix(trimmed, "."))
|
||||||
|
case "CAA":
|
||||||
|
// API returns the certificate authority in value field; content contains "flags tag value"
|
||||||
|
if trimmedValue := strings.TrimSpace(value); trimmedValue != "" {
|
||||||
|
return types.StringValue(strings.TrimSuffix(trimmedValue, "."))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return types.StringValue(trimmed)
|
return types.StringValue(trimmed)
|
||||||
|
|||||||
@@ -148,18 +148,27 @@ func TestStringPointerFromOptionalContentForValidation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNormalizeRecordContentForState(t *testing.T) {
|
func TestNormalizeRecordContentForState(t *testing.T) {
|
||||||
if got := normalizeRecordContentForState("AAAA", "2001:0db8:0000:0000:0000:0000:0000:0123", false, ""); got.ValueString() != "2001:db8::123" {
|
if got := normalizeRecordContentForState("AAAA", "2001:0db8:0000:0000:0000:0000:0000:0123", false, "", ""); got.ValueString() != "2001:db8::123" {
|
||||||
t.Fatalf("expected canonical IPv6, got %q", got.ValueString())
|
t.Fatalf("expected canonical IPv6, got %q", got.ValueString())
|
||||||
}
|
}
|
||||||
if got := normalizeRecordContentForState("CNAME", "Example.COM.", false, ""); got.ValueString() != "Example.COM" {
|
if got := normalizeRecordContentForState("CNAME", "Example.COM.", false, "", ""); got.ValueString() != "Example.COM" {
|
||||||
t.Fatalf("expected trailing dot removed, got %q", got.ValueString())
|
t.Fatalf("expected trailing dot removed, got %q", got.ValueString())
|
||||||
}
|
}
|
||||||
if got := normalizeRecordContentForState("CNAME", "old.example.com", false, "new.example.co."); got.ValueString() != "new.example.co" {
|
if got := normalizeRecordContentForState("CNAME", "old.example.com", false, "new.example.co.", ""); got.ValueString() != "new.example.co" {
|
||||||
t.Fatalf("expected CNAME content to come from host, got %q", got.ValueString())
|
t.Fatalf("expected CNAME content to come from host, got %q", got.ValueString())
|
||||||
}
|
}
|
||||||
if got := normalizeRecordContentForState("A", "(167.179.167.166)", true, ""); !got.IsNull() {
|
if got := normalizeRecordContentForState("A", "(167.179.167.166)", true, "", ""); !got.IsNull() {
|
||||||
t.Fatalf("expected dynamic content to remain null, got %q", got.ValueString())
|
t.Fatalf("expected dynamic content to remain null, got %q", got.ValueString())
|
||||||
}
|
}
|
||||||
|
if got := normalizeRecordContentForState("MX", "10 mail.example.com.", false, "mail.example.com", ""); got.ValueString() != "mail.example.com" {
|
||||||
|
t.Fatalf("expected MX content from host field, got %q", got.ValueString())
|
||||||
|
}
|
||||||
|
if got := normalizeRecordContentForState("SRV", "10 5 5060 sip.example.com.", false, "sip.example.com", ""); got.ValueString() != "sip.example.com" {
|
||||||
|
t.Fatalf("expected SRV content from host field, got %q", got.ValueString())
|
||||||
|
}
|
||||||
|
if got := normalizeRecordContentForState("CAA", "0 issue letsencrypt.org", false, "", "letsencrypt.org"); got.ValueString() != "letsencrypt.org" {
|
||||||
|
t.Fatalf("expected CAA content from value field, got %q", got.ValueString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNormalizeDNSRecordUpdateRequestForType(t *testing.T) {
|
func TestNormalizeDNSRecordUpdateRequestForType(t *testing.T) {
|
||||||
|
|||||||
@@ -25,14 +25,23 @@ var (
|
|||||||
type domainResource struct{ clientProvider *providerData }
|
type domainResource struct{ clientProvider *providerData }
|
||||||
|
|
||||||
type domainResourceModel struct {
|
type domainResourceModel struct {
|
||||||
ID types.Int64 `tfsdk:"id"`
|
ID types.Int64 `tfsdk:"id"`
|
||||||
Name types.String `tfsdk:"name"`
|
Name types.String `tfsdk:"name"`
|
||||||
IPv4Address types.String `tfsdk:"ipv4_address"`
|
IPv4Address types.String `tfsdk:"ipv4_address"`
|
||||||
IPv6Address types.String `tfsdk:"ipv6_address"`
|
IPv6Address types.String `tfsdk:"ipv6_address"`
|
||||||
TTL types.Int64 `tfsdk:"ttl"`
|
TTL types.Int64 `tfsdk:"ttl"`
|
||||||
Group types.String `tfsdk:"group"`
|
Group types.String `tfsdk:"group"`
|
||||||
State types.String `tfsdk:"state"`
|
State types.String `tfsdk:"state"`
|
||||||
Token types.String `tfsdk:"token"`
|
Token types.String `tfsdk:"token"`
|
||||||
|
UnicodeName types.String `tfsdk:"unicode_name"`
|
||||||
|
IPv4 types.Bool `tfsdk:"ipv4"`
|
||||||
|
IPv6 types.Bool `tfsdk:"ipv6"`
|
||||||
|
IPv4WildcardAlias types.Bool `tfsdk:"ipv4_wildcard_alias"`
|
||||||
|
IPv6WildcardAlias types.Bool `tfsdk:"ipv6_wildcard_alias"`
|
||||||
|
AllowZoneTransfer types.Bool `tfsdk:"allow_zone_transfer"`
|
||||||
|
DNSSEC types.Bool `tfsdk:"dnssec"`
|
||||||
|
CreatedOn types.String `tfsdk:"created_on"`
|
||||||
|
UpdatedOn types.String `tfsdk:"updated_on"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDomainResource() resource.Resource { return &domainResource{} }
|
func NewDomainResource() resource.Resource { return &domainResource{} }
|
||||||
@@ -41,14 +50,23 @@ func (r *domainResource) Metadata(_ context.Context, req resource.MetadataReques
|
|||||||
}
|
}
|
||||||
func (r *domainResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
func (r *domainResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||||
resp.Schema = schema.Schema{Description: "Manages a Dynu DNS domain.", Attributes: map[string]schema.Attribute{
|
resp.Schema = schema.Schema{Description: "Manages a Dynu DNS domain.", Attributes: map[string]schema.Attribute{
|
||||||
"id": schema.Int64Attribute{Computed: true},
|
"id": schema.Int64Attribute{Computed: true},
|
||||||
"name": schema.StringAttribute{Required: true, PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
"name": schema.StringAttribute{Required: true, PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}},
|
||||||
"ipv4_address": schema.StringAttribute{Optional: true, Computed: true},
|
"ipv4_address": schema.StringAttribute{Optional: true, Computed: true},
|
||||||
"ipv6_address": schema.StringAttribute{Optional: true, Computed: true},
|
"ipv6_address": schema.StringAttribute{Optional: true, Computed: true},
|
||||||
"ttl": schema.Int64Attribute{Optional: true, Computed: true},
|
"ttl": schema.Int64Attribute{Optional: true, Computed: true},
|
||||||
"group": schema.StringAttribute{Optional: true, Computed: true},
|
"group": schema.StringAttribute{Optional: true, Computed: true},
|
||||||
"state": schema.StringAttribute{Computed: true},
|
"state": schema.StringAttribute{Computed: true},
|
||||||
"token": schema.StringAttribute{Computed: true, Sensitive: true},
|
"token": schema.StringAttribute{Computed: true, Sensitive: true},
|
||||||
|
"unicode_name": schema.StringAttribute{Computed: true, Description: "Unicode representation of the domain name."},
|
||||||
|
"ipv4": schema.BoolAttribute{Computed: true, Description: "Whether IPv4 support is enabled for this domain."},
|
||||||
|
"ipv6": schema.BoolAttribute{Computed: true, Description: "Whether IPv6 support is enabled for this domain."},
|
||||||
|
"ipv4_wildcard_alias": schema.BoolAttribute{Computed: true, Description: "Whether IPv4 wildcard alias is enabled."},
|
||||||
|
"ipv6_wildcard_alias": schema.BoolAttribute{Computed: true, Description: "Whether IPv6 wildcard alias is enabled."},
|
||||||
|
"allow_zone_transfer": schema.BoolAttribute{Computed: true, Description: "Whether zone transfer is allowed for this domain."},
|
||||||
|
"dnssec": schema.BoolAttribute{Computed: true, Description: "Whether DNSSEC is enabled for this domain."},
|
||||||
|
"created_on": schema.StringAttribute{Computed: true, Description: "Creation timestamp as returned by Dynu."},
|
||||||
|
"updated_on": schema.StringAttribute{Computed: true, Description: "Last update timestamp as returned by Dynu."},
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
func (r *domainResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
func (r *domainResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||||
@@ -138,5 +156,23 @@ func (r *domainResource) ImportState(ctx context.Context, req resource.ImportSta
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mapDomainResource(domain dynuclient.Domain) domainResourceModel {
|
func mapDomainResource(domain dynuclient.Domain) domainResourceModel {
|
||||||
return domainResourceModel{ID: types.Int64Value(domain.ID), Name: types.StringValue(strings.TrimSpace(domain.Name)), IPv4Address: mapString(domain.IPv4Address), IPv6Address: mapString(domain.IPv6Address), TTL: types.Int64Value(domain.TTL), Group: mapString(domain.Group), State: mapString(domain.State), Token: mapString(domain.Token)}
|
return domainResourceModel{
|
||||||
|
ID: types.Int64Value(domain.ID),
|
||||||
|
Name: types.StringValue(strings.TrimSpace(domain.Name)),
|
||||||
|
IPv4Address: mapString(domain.IPv4Address),
|
||||||
|
IPv6Address: mapString(domain.IPv6Address),
|
||||||
|
TTL: types.Int64Value(domain.TTL),
|
||||||
|
Group: mapString(domain.Group),
|
||||||
|
State: mapString(domain.State),
|
||||||
|
Token: mapString(domain.Token),
|
||||||
|
UnicodeName: mapString(domain.UnicodeName),
|
||||||
|
IPv4: types.BoolValue(domain.IPv4),
|
||||||
|
IPv6: types.BoolValue(domain.IPv6),
|
||||||
|
IPv4WildcardAlias: types.BoolValue(domain.IPv4WildcardAlias),
|
||||||
|
IPv6WildcardAlias: types.BoolValue(domain.IPv6WildcardAlias),
|
||||||
|
AllowZoneTransfer: types.BoolValue(domain.AllowZoneTransfer),
|
||||||
|
DNSSEC: types.BoolValue(domain.DNSSEC),
|
||||||
|
CreatedOn: mapString(domain.CreatedOn),
|
||||||
|
UpdatedOn: mapString(domain.UpdatedOn),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,15 @@ func TestIntegrationResourceDomainLifecycleAndImport(t *testing.T) {
|
|||||||
if state.ID.ValueInt64() == 0 {
|
if state.ID.ValueInt64() == 0 {
|
||||||
t.Fatal("expected created id")
|
t.Fatal("expected created id")
|
||||||
}
|
}
|
||||||
|
if state.State.IsNull() || state.State.IsUnknown() {
|
||||||
|
t.Fatal("expected state attribute to be populated after create")
|
||||||
|
}
|
||||||
|
if state.UnicodeName.IsNull() || state.UnicodeName.IsUnknown() {
|
||||||
|
t.Fatal("expected unicode_name to be populated after create")
|
||||||
|
}
|
||||||
|
if state.DNSSEC.IsNull() || state.DNSSEC.IsUnknown() {
|
||||||
|
t.Fatal("expected dnssec to be a known bool after create")
|
||||||
|
}
|
||||||
|
|
||||||
state.TTL = types.Int64Value(300)
|
state.TTL = types.Int64Value(300)
|
||||||
plan = tfsdk.Plan{Schema: schemaResp.Schema}
|
plan = tfsdk.Plan{Schema: schemaResp.Schema}
|
||||||
|
|||||||
Reference in New Issue
Block a user