Archived
Harden read-only provider with tests, docs, and CI
This commit is contained in:
1 parent
cf47a6ed48
commit
6cc1b206dc
24 files changed
+711
-246
No files matched your search
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
@@ -52,30 +53,34 @@ func (d *dnsRecordsDataSource) Metadata(_ context.Context, req datasource.Metada
|
||||
|
||||
func (d *dnsRecordsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Get DNS records from Dynu for the domain resolved from a hostname.",
|
||||
Description: "Lists DNS records for the Dynu root domain resolved from a hostname.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"hostname": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "Any hostname under the target root domain.",
|
||||
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
|
||||
Description: "Fully-qualified hostname under the target root domain.",
|
||||
Validators: []validator.String{
|
||||
stringvalidator.LengthAtLeast(1),
|
||||
stringvalidator.RegexMatches(hostnameValidator, "must be a valid fully-qualified hostname"),
|
||||
},
|
||||
},
|
||||
"domain_id": schema.Int64Attribute{Computed: true},
|
||||
"domain_name": schema.StringAttribute{Computed: true},
|
||||
"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."},
|
||||
"records": schema.ListNestedAttribute{
|
||||
Computed: true,
|
||||
Computed: true,
|
||||
Description: "Sorted DNS records for the resolved domain. Timestamps are returned as Dynu provides them.",
|
||||
NestedObject: schema.NestedAttributeObject{Attributes: map[string]schema.Attribute{
|
||||
"id": schema.Int64Attribute{Computed: true},
|
||||
"domain_id": schema.Int64Attribute{Computed: true},
|
||||
"domain_name": schema.StringAttribute{Computed: true},
|
||||
"node_name": schema.StringAttribute{Computed: true},
|
||||
"hostname": schema.StringAttribute{Computed: true},
|
||||
"record_type": schema.StringAttribute{Computed: true},
|
||||
"ttl": schema.Int64Attribute{Computed: true},
|
||||
"state": schema.BoolAttribute{Computed: true},
|
||||
"content": schema.StringAttribute{Computed: true},
|
||||
"updated_on": schema.StringAttribute{Computed: true},
|
||||
"group": schema.StringAttribute{Computed: true},
|
||||
"host": schema.StringAttribute{Computed: true},
|
||||
"id": schema.Int64Attribute{Computed: true, Description: "Dynu DNS record ID."},
|
||||
"domain_id": schema.Int64Attribute{Computed: true, Description: "Dynu domain ID for this record."},
|
||||
"domain_name": schema.StringAttribute{Computed: true, Description: "Domain name for this record."},
|
||||
"node_name": schema.StringAttribute{Computed: true, Description: "Node/label portion of the record."},
|
||||
"hostname": schema.StringAttribute{Computed: true, Description: "Fully-qualified hostname for the record."},
|
||||
"record_type": schema.StringAttribute{Computed: true, Description: "DNS record type (A, CNAME, TXT, etc.)."},
|
||||
"ttl": schema.Int64Attribute{Computed: true, Description: "DNS TTL in seconds."},
|
||||
"state": schema.BoolAttribute{Computed: true, Description: "Whether this DNS record is active."},
|
||||
"content": schema.StringAttribute{Computed: true, Description: "Record content/value."},
|
||||
"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."},
|
||||
"host": schema.StringAttribute{Computed: true, Description: "Host field as returned by Dynu."},
|
||||
}},
|
||||
},
|
||||
},
|
||||
@@ -101,6 +106,11 @@ func (d *dnsRecordsDataSource) Read(ctx context.Context, req datasource.ReadRequ
|
||||
return
|
||||
}
|
||||
|
||||
if state.Hostname.IsUnknown() || state.Hostname.IsNull() {
|
||||
resp.Diagnostics.AddAttributeError(path.Root("hostname"), "Invalid hostname", "The hostname must be known and non-null.")
|
||||
return
|
||||
}
|
||||
|
||||
domainID, domainName, err := d.clientProvider.client.GetRootDomain(ctx, state.Hostname.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Unable to resolve Dynu domain from hostname", err.Error())
|
||||
@@ -113,6 +123,8 @@ func (d *dnsRecordsDataSource) Read(ctx context.Context, req datasource.ReadRequ
|
||||
return
|
||||
}
|
||||
|
||||
sortDNSRecords(records)
|
||||
|
||||
state.DomainID = types.Int64Value(domainID)
|
||||
state.DomainName = types.StringValue(domainName)
|
||||
state.Records = make([]dnsRecordStateItem, 0, len(records))
|
||||
|
||||
@@ -3,9 +3,9 @@ package provider
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
@@ -18,6 +18,8 @@ var (
|
||||
_ 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,}$`)
|
||||
|
||||
type domainDataSource struct {
|
||||
clientProvider *providerData
|
||||
}
|
||||
@@ -37,16 +39,19 @@ func (d *domainDataSource) Metadata(_ context.Context, req datasource.MetadataRe
|
||||
|
||||
func (d *domainDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Get a Dynu DNS domain by hostname.",
|
||||
Description: "Resolves a hostname to its Dynu root domain and returns domain details.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"hostname": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "Hostname to resolve to a root domain.",
|
||||
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
|
||||
Description: "Fully-qualified hostname, such as www.example.com.",
|
||||
Validators: []validator.String{
|
||||
stringvalidator.LengthAtLeast(1),
|
||||
stringvalidator.RegexMatches(hostnameValidator, "must be a valid fully-qualified hostname"),
|
||||
},
|
||||
},
|
||||
"domain": schema.SingleNestedAttribute{
|
||||
Computed: true,
|
||||
Description: "Resolved Dynu DNS domain details.",
|
||||
Description: "Dynu root domain details for the supplied hostname.",
|
||||
Attributes: domainAttributes(),
|
||||
},
|
||||
},
|
||||
@@ -75,7 +80,7 @@ func (d *domainDataSource) Read(ctx context.Context, req datasource.ReadRequest,
|
||||
}
|
||||
|
||||
if state.Hostname.IsUnknown() || state.Hostname.IsNull() {
|
||||
resp.Diagnostics.AddAttributeError(path.Root("hostname"), "Invalid hostname", "hostname must be known and non-null.")
|
||||
resp.Diagnostics.AddAttributeError(path.Root("hostname"), "Invalid hostname", "The hostname must be known and non-null.")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -91,47 +96,7 @@ func (d *domainDataSource) Read(ctx context.Context, req datasource.ReadRequest,
|
||||
return
|
||||
}
|
||||
|
||||
domainValue := mapDomain(*domain)
|
||||
domainObject, diags := types.ObjectValue(
|
||||
map[string]attr.Type{
|
||||
"id": types.Int64Type,
|
||||
"name": types.StringType,
|
||||
"unicode_name": types.StringType,
|
||||
"token": types.StringType,
|
||||
"state": types.StringType,
|
||||
"group": types.StringType,
|
||||
"ipv4_address": types.StringType,
|
||||
"ipv6_address": types.StringType,
|
||||
"ttl": types.Int64Type,
|
||||
"ipv4": types.BoolType,
|
||||
"ipv6": types.BoolType,
|
||||
"ipv4_wildcard_alias": types.BoolType,
|
||||
"ipv6_wildcard_alias": types.BoolType,
|
||||
"allow_zone_transfer": types.BoolType,
|
||||
"dnssec": types.BoolType,
|
||||
"created_on": types.StringType,
|
||||
"updated_on": types.StringType,
|
||||
},
|
||||
map[string]attr.Value{
|
||||
"id": domainValue.ID,
|
||||
"name": domainValue.Name,
|
||||
"unicode_name": domainValue.UnicodeName,
|
||||
"token": domainValue.Token,
|
||||
"state": domainValue.State,
|
||||
"group": domainValue.Group,
|
||||
"ipv4_address": domainValue.IPv4Address,
|
||||
"ipv6_address": domainValue.IPv6Address,
|
||||
"ttl": domainValue.TTL,
|
||||
"ipv4": domainValue.IPv4,
|
||||
"ipv6": domainValue.IPv6,
|
||||
"ipv4_wildcard_alias": domainValue.IPv4WildcardAlias,
|
||||
"ipv6_wildcard_alias": domainValue.IPv6WildcardAlias,
|
||||
"allow_zone_transfer": domainValue.AllowZoneTransfer,
|
||||
"dnssec": domainValue.DNSSEC,
|
||||
"created_on": domainValue.CreatedOn,
|
||||
"updated_on": domainValue.UpdatedOn,
|
||||
},
|
||||
)
|
||||
domainObject, diags := domainObjectValue(*domain)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
|
||||
@@ -52,11 +52,11 @@ func (d *domainsDataSource) Metadata(_ context.Context, req datasource.MetadataR
|
||||
|
||||
func (d *domainsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "List DNS domains from Dynu.",
|
||||
Description: "Lists DNS domains visible to the configured Dynu API key.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"domains": schema.ListNestedAttribute{
|
||||
Computed: true,
|
||||
Description: "List of Dynu DNS domains.",
|
||||
Description: "Sorted list of domains. Timestamps are returned exactly as Dynu provides them.",
|
||||
NestedObject: schema.NestedAttributeObject{Attributes: domainAttributes()},
|
||||
},
|
||||
},
|
||||
@@ -84,6 +84,8 @@ func (d *domainsDataSource) Read(ctx context.Context, _ datasource.ReadRequest,
|
||||
return
|
||||
}
|
||||
|
||||
sortDomains(domains)
|
||||
|
||||
state := domainsDataSourceModel{Domains: make([]domainModel, 0, len(domains))}
|
||||
for _, domain := range domains {
|
||||
state.Domains = append(state.Domains, mapDomain(domain))
|
||||
@@ -94,22 +96,22 @@ func (d *domainsDataSource) Read(ctx context.Context, _ datasource.ReadRequest,
|
||||
|
||||
func domainAttributes() map[string]schema.Attribute {
|
||||
return map[string]schema.Attribute{
|
||||
"id": schema.Int64Attribute{Computed: true},
|
||||
"name": schema.StringAttribute{Computed: true},
|
||||
"unicode_name": schema.StringAttribute{Computed: true},
|
||||
"token": schema.StringAttribute{Computed: true, Sensitive: true},
|
||||
"state": schema.StringAttribute{Computed: true},
|
||||
"group": schema.StringAttribute{Computed: true},
|
||||
"ipv4_address": schema.StringAttribute{Computed: true},
|
||||
"ipv6_address": schema.StringAttribute{Computed: true},
|
||||
"ttl": schema.Int64Attribute{Computed: true},
|
||||
"ipv4": schema.BoolAttribute{Computed: true},
|
||||
"ipv6": schema.BoolAttribute{Computed: true},
|
||||
"ipv4_wildcard_alias": schema.BoolAttribute{Computed: true},
|
||||
"ipv6_wildcard_alias": schema.BoolAttribute{Computed: true},
|
||||
"allow_zone_transfer": schema.BoolAttribute{Computed: true},
|
||||
"dnssec": schema.BoolAttribute{Computed: true},
|
||||
"created_on": schema.StringAttribute{Computed: true},
|
||||
"updated_on": schema.StringAttribute{Computed: true},
|
||||
"id": schema.Int64Attribute{Computed: true, Description: "Dynu domain ID."},
|
||||
"name": schema.StringAttribute{Computed: true, Description: "Primary domain name."},
|
||||
"unicode_name": schema.StringAttribute{Computed: true, Description: "Unicode representation of the domain."},
|
||||
"token": schema.StringAttribute{Computed: true, Sensitive: true, Description: "Dynu domain token."},
|
||||
"state": schema.StringAttribute{Computed: true, Description: "Dynu domain state."},
|
||||
"group": schema.StringAttribute{Computed: true, Description: "Dynu domain group."},
|
||||
"ipv4_address": schema.StringAttribute{Computed: true, Description: "Configured IPv4 address, if any."},
|
||||
"ipv6_address": schema.StringAttribute{Computed: true, Description: "Configured IPv6 address, if any."},
|
||||
"ttl": schema.Int64Attribute{Computed: true, Description: "DNS TTL in seconds."},
|
||||
"ipv4": schema.BoolAttribute{Computed: true, Description: "Whether IPv4 support is enabled."},
|
||||
"ipv6": schema.BoolAttribute{Computed: true, Description: "Whether IPv6 support is enabled."},
|
||||
"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."},
|
||||
"dnssec": schema.BoolAttribute{Computed: true, Description: "Whether DNSSEC is enabled."},
|
||||
"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."},
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,35 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
|
||||
"github.com/dynu/terraform-provider-dynu/internal/dynuclient"
|
||||
)
|
||||
|
||||
var domainObjectTypes = map[string]attr.Type{
|
||||
"id": types.Int64Type,
|
||||
"name": types.StringType,
|
||||
"unicode_name": types.StringType,
|
||||
"token": types.StringType,
|
||||
"state": types.StringType,
|
||||
"group": types.StringType,
|
||||
"ipv4_address": types.StringType,
|
||||
"ipv6_address": types.StringType,
|
||||
"ttl": types.Int64Type,
|
||||
"ipv4": types.BoolType,
|
||||
"ipv6": types.BoolType,
|
||||
"ipv4_wildcard_alias": types.BoolType,
|
||||
"ipv6_wildcard_alias": types.BoolType,
|
||||
"allow_zone_transfer": types.BoolType,
|
||||
"dnssec": types.BoolType,
|
||||
"created_on": types.StringType,
|
||||
"updated_on": types.StringType,
|
||||
}
|
||||
|
||||
func mapDomain(domain dynuclient.Domain) domainModel {
|
||||
return domainModel{
|
||||
ID: types.Int64Value(domain.ID),
|
||||
@@ -14,8 +38,8 @@ func mapDomain(domain dynuclient.Domain) domainModel {
|
||||
Token: types.StringValue(domain.Token),
|
||||
State: types.StringValue(domain.State),
|
||||
Group: types.StringValue(domain.Group),
|
||||
IPv4Address: types.StringValue(domain.IPv4Address),
|
||||
IPv6Address: types.StringValue(domain.IPv6Address),
|
||||
IPv4Address: mapString(domain.IPv4Address),
|
||||
IPv6Address: mapString(domain.IPv6Address),
|
||||
TTL: types.Int64Value(domain.TTL),
|
||||
IPv4: types.BoolValue(domain.IPv4),
|
||||
IPv6: types.BoolValue(domain.IPv6),
|
||||
@@ -23,14 +47,59 @@ func mapDomain(domain dynuclient.Domain) domainModel {
|
||||
IPv6WildcardAlias: types.BoolValue(domain.IPv6WildcardAlias),
|
||||
AllowZoneTransfer: types.BoolValue(domain.AllowZoneTransfer),
|
||||
DNSSEC: types.BoolValue(domain.DNSSEC),
|
||||
CreatedOn: types.StringValue(domain.CreatedOn),
|
||||
UpdatedOn: types.StringValue(domain.UpdatedOn),
|
||||
CreatedOn: mapString(domain.CreatedOn),
|
||||
UpdatedOn: mapString(domain.UpdatedOn),
|
||||
}
|
||||
}
|
||||
|
||||
func domainObjectValue(domain dynuclient.Domain) (types.Object, diag.Diagnostics) {
|
||||
mapped := mapDomain(domain)
|
||||
|
||||
return types.ObjectValue(domainObjectTypes, map[string]attr.Value{
|
||||
"id": mapped.ID,
|
||||
"name": mapped.Name,
|
||||
"unicode_name": mapped.UnicodeName,
|
||||
"token": mapped.Token,
|
||||
"state": mapped.State,
|
||||
"group": mapped.Group,
|
||||
"ipv4_address": mapped.IPv4Address,
|
||||
"ipv6_address": mapped.IPv6Address,
|
||||
"ttl": mapped.TTL,
|
||||
"ipv4": mapped.IPv4,
|
||||
"ipv6": mapped.IPv6,
|
||||
"ipv4_wildcard_alias": mapped.IPv4WildcardAlias,
|
||||
"ipv6_wildcard_alias": mapped.IPv6WildcardAlias,
|
||||
"allow_zone_transfer": mapped.AllowZoneTransfer,
|
||||
"dnssec": mapped.DNSSEC,
|
||||
"created_on": mapped.CreatedOn,
|
||||
"updated_on": mapped.UpdatedOn,
|
||||
})
|
||||
}
|
||||
|
||||
func mapString(in string) types.String {
|
||||
if in == "" {
|
||||
return types.StringNull()
|
||||
}
|
||||
return types.StringValue(in)
|
||||
}
|
||||
|
||||
func sortDomains(domains []dynuclient.Domain) {
|
||||
sort.Slice(domains, func(i, j int) bool {
|
||||
if domains[i].Name != domains[j].Name {
|
||||
return domains[i].Name < domains[j].Name
|
||||
}
|
||||
return domains[i].ID < domains[j].ID
|
||||
})
|
||||
}
|
||||
|
||||
func sortDNSRecords(records []dynuclient.DNSRecord) {
|
||||
sort.Slice(records, func(i, j int) bool {
|
||||
if records[i].Hostname != records[j].Hostname {
|
||||
return records[i].Hostname < records[j].Hostname
|
||||
}
|
||||
if records[i].RecordType != records[j].RecordType {
|
||||
return records[i].RecordType < records[j].RecordType
|
||||
}
|
||||
return records[i].ID < records[j].ID
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/dynu/terraform-provider-dynu/internal/dynuclient"
|
||||
)
|
||||
|
||||
func TestSortDomains(t *testing.T) {
|
||||
domains := []dynuclient.Domain{
|
||||
{ID: 2, Name: "z.example.com"},
|
||||
{ID: 1, Name: "a.example.com"},
|
||||
{ID: 3, Name: "a.example.com"},
|
||||
}
|
||||
|
||||
sortDomains(domains)
|
||||
|
||||
if domains[0].ID != 1 || domains[1].ID != 3 || domains[2].ID != 2 {
|
||||
t.Fatalf("unexpected domain sort order: %#v", domains)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortDNSRecords(t *testing.T) {
|
||||
records := []dynuclient.DNSRecord{
|
||||
{ID: 3, Hostname: "b.example.com", RecordType: "TXT"},
|
||||
{ID: 1, Hostname: "a.example.com", RecordType: "A"},
|
||||
{ID: 2, Hostname: "a.example.com", RecordType: "A"},
|
||||
}
|
||||
|
||||
sortDNSRecords(records)
|
||||
|
||||
if records[0].ID != 1 || records[1].ID != 2 || records[2].ID != 3 {
|
||||
t.Fatalf("unexpected record sort order: %#v", records)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapString(t *testing.T) {
|
||||
if !mapString("").IsNull() {
|
||||
t.Fatal("expected empty string to map to null")
|
||||
}
|
||||
if mapString("value").ValueString() != "value" {
|
||||
t.Fatal("expected non-empty string to map to value")
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package provider
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
@@ -21,7 +22,8 @@ type dynuProvider struct {
|
||||
}
|
||||
|
||||
type dynuProviderModel struct {
|
||||
APIKey types.String `tfsdk:"api_key"`
|
||||
APIKey types.String `tfsdk:"api_key"`
|
||||
BaseURL types.String `tfsdk:"base_url"`
|
||||
}
|
||||
|
||||
type providerData struct {
|
||||
@@ -41,12 +43,16 @@ func (p *dynuProvider) Metadata(_ context.Context, _ provider.MetadataRequest, r
|
||||
|
||||
func (p *dynuProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Terraform provider for Dynu DNS read-only operations.",
|
||||
Description: "Terraform provider for Dynu DNS read-only data sources.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"api_key": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
Description: "Dynu API key. Can also be provided using DYNU_API_KEY.",
|
||||
Description: "Dynu API key. If omitted, the provider uses the DYNU_API_KEY environment variable.",
|
||||
},
|
||||
"base_url": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Description: "Override Dynu API base URL. Primarily intended for automated tests.",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -60,25 +66,28 @@ func (p *dynuProvider) Configure(ctx context.Context, req provider.ConfigureRequ
|
||||
return
|
||||
}
|
||||
|
||||
apiKey := os.Getenv("DYNU_API_KEY")
|
||||
if !data.APIKey.IsNull() {
|
||||
apiKey = data.APIKey.ValueString()
|
||||
}
|
||||
|
||||
apiKey := resolveAPIKey(data.APIKey, os.Getenv("DYNU_API_KEY"))
|
||||
if apiKey == "" {
|
||||
resp.Diagnostics.AddAttributeError(
|
||||
path.Root("api_key"),
|
||||
"Missing Dynu API key",
|
||||
"Set api_key in the provider configuration or DYNU_API_KEY in the environment.",
|
||||
"Configure api_key in the provider block or set the DYNU_API_KEY environment variable.",
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
providerData := &providerData{client: dynuclient.New(apiKey)}
|
||||
providerData := &providerData{client: newDynuClient(apiKey, data.BaseURL)}
|
||||
resp.DataSourceData = providerData
|
||||
resp.ResourceData = nil
|
||||
}
|
||||
|
||||
func resolveAPIKey(configValue types.String, envValue string) string {
|
||||
if !configValue.IsNull() && !configValue.IsUnknown() {
|
||||
return strings.TrimSpace(configValue.ValueString())
|
||||
}
|
||||
return strings.TrimSpace(envValue)
|
||||
}
|
||||
|
||||
func (p *dynuProvider) DataSources(_ context.Context) []func() datasource.DataSource {
|
||||
return []func() datasource.DataSource{
|
||||
NewDomainsDataSource,
|
||||
@@ -90,3 +99,11 @@ func (p *dynuProvider) DataSources(_ context.Context) []func() datasource.DataSo
|
||||
func (p *dynuProvider) Resources(_ context.Context) []func() resource.Resource {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newDynuClient(apiKey string, baseURL types.String) *dynuclient.Client {
|
||||
if !baseURL.IsNull() && !baseURL.IsUnknown() && strings.TrimSpace(baseURL.ValueString()) != "" {
|
||||
return dynuclient.New(apiKey, dynuclient.WithBaseURL(strings.TrimSpace(baseURL.ValueString())))
|
||||
}
|
||||
|
||||
return dynuclient.New(apiKey)
|
||||
}
|
||||
@@ -1,18 +1,77 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/dynu/terraform-provider-dynu/internal/dynuclient"
|
||||
)
|
||||
|
||||
func TestAccScaffold(t *testing.T) {
|
||||
if os.Getenv("TF_ACC") != "1" || os.Getenv("DYNU_API_KEY") == "" {
|
||||
t.Skip("set TF_ACC=1 and DYNU_API_KEY to enable acceptance tests")
|
||||
func testAccPreCheck(t *testing.T) {
|
||||
t.Helper()
|
||||
if os.Getenv("TF_ACC") != "1" {
|
||||
t.Skip("set TF_ACC=1 to run acceptance tests")
|
||||
}
|
||||
if os.Getenv("DYNU_API_KEY") == "" {
|
||||
t.Skip("set DYNU_API_KEY to run acceptance tests")
|
||||
}
|
||||
}
|
||||
|
||||
func testAccDomainFromEnv(t *testing.T) string {
|
||||
t.Helper()
|
||||
domain := os.Getenv("DYNU_DOMAIN")
|
||||
if domain == "" {
|
||||
t.Skip("set DYNU_DOMAIN to run domain-specific acceptance tests")
|
||||
}
|
||||
return domain
|
||||
}
|
||||
|
||||
func TestAccDataSourceDomains(t *testing.T) {
|
||||
testAccPreCheck(t)
|
||||
|
||||
client := dynuclient.New(os.Getenv("DYNU_API_KEY"))
|
||||
domains, err := client.ListDomains(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("ListDomains() failed: %v", err)
|
||||
}
|
||||
if len(domains) == 0 {
|
||||
t.Fatal("expected at least one domain")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccDataSourceDomain(t *testing.T) {
|
||||
testAccPreCheck(t)
|
||||
hostname := testAccDomainFromEnv(t)
|
||||
|
||||
client := dynuclient.New(os.Getenv("DYNU_API_KEY"))
|
||||
domainID, _, err := client.GetRootDomain(context.Background(), hostname)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRootDomain() failed: %v", err)
|
||||
}
|
||||
domain, err := client.GetDomainByID(context.Background(), domainID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDomainByID() failed: %v", err)
|
||||
}
|
||||
if domain.ID == 0 || domain.Name == "" {
|
||||
t.Fatalf("unexpected domain payload: %#v", domain)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccDataSourceDNSRecords(t *testing.T) {
|
||||
testAccPreCheck(t)
|
||||
hostname := testAccDomainFromEnv(t)
|
||||
|
||||
client := dynuclient.New(os.Getenv("DYNU_API_KEY"))
|
||||
domainID, _, err := client.GetRootDomain(context.Background(), hostname)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRootDomain() failed: %v", err)
|
||||
}
|
||||
records, err := client.ListDNSRecords(context.Background(), domainID)
|
||||
if err != nil {
|
||||
t.Fatalf("ListDNSRecords() failed: %v", err)
|
||||
}
|
||||
if records == nil {
|
||||
t.Fatal("expected records slice, got nil")
|
||||
}
|
||||
|
||||
// Optional: DYNU_DOMAIN may be used by future acceptance test cases.
|
||||
_ = os.Getenv("DYNU_DOMAIN")
|
||||
|
||||
// Acceptance tests for read-only data sources are intentionally scaffolded in phase 1.
|
||||
// Add terraform-plugin-testing based test cases in a follow-up with stable fixtures.
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
func TestResolveAPIKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config types.String
|
||||
env string
|
||||
want string
|
||||
}{
|
||||
{name: "config wins", config: types.StringValue("config-key"), env: "env-key", want: "config-key"},
|
||||
{name: "env fallback", config: types.StringNull(), env: "env-key", want: "env-key"},
|
||||
{name: "trim spaces", config: types.StringValue(" config-key "), env: " env-key ", want: "config-key"},
|
||||
{name: "unknown uses env", config: types.StringUnknown(), env: "env-key", want: "env-key"},
|
||||
{name: "empty when missing", config: types.StringNull(), env: "", want: ""},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := resolveAPIKey(tc.config, tc.env); got != tc.want {
|
||||
t.Fatalf("resolveAPIKey() = %q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user