diff --git a/internal/provider/acctest/resource_dns_record_test.go b/internal/provider/acctest/resource_dns_record_test.go index 2481cf3..3e192c6 100644 --- a/internal/provider/acctest/resource_dns_record_test.go +++ b/internal/provider/acctest/resource_dns_record_test.go @@ -85,7 +85,7 @@ func TestDNSRecord_basic(t *testing.T) { Config: testAccDnsRecordConfig(tc), Check: testAccDnsRecordCheckAttrs(tc), }, - pt.ImportStep(testDnsRecordResourceName), + pt.ImportStepWithSite(testDnsRecordResourceName), } AcceptanceTest(t, AcceptanceTestCase{ diff --git a/internal/provider/dns/datasource_dns_record.go b/internal/provider/dns/datasource_dns_record.go index 9063a2d..e93ed9f 100644 --- a/internal/provider/dns/datasource_dns_record.go +++ b/internal/provider/dns/datasource_dns_record.go @@ -100,7 +100,8 @@ func (d *dnsRecordDatasource) Read(ctx context.Context, req datasource.ReadReque resp.Diagnostics.AddError("Filter is required", "Filter is required. Validation should prevent this from happening.") return } - list, err := d.client.ListDNSRecord(ctx, d.client.Site) + site := d.client.ResolveSite(&state) + list, err := d.client.ListDNSRecord(ctx, site) if err != nil { resp.Diagnostics.AddError("Failed to list DNS records", err.Error()) return @@ -136,6 +137,8 @@ func (d *dnsRecordDatasource) Read(ctx context.Context, req datasource.ReadReque resp.Diagnostics.AddError("DNS record not found", "No DNS record found") return } - (&state.dnsRecordModel).merge(found) + (&state.dnsRecordModel).Merge(ctx, found) + state.SetID(found.ID) + state.SetSite(site) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } diff --git a/internal/provider/dns/datasource_dns_records.go b/internal/provider/dns/datasource_dns_records.go index e2fff86..9e962a7 100644 --- a/internal/provider/dns/datasource_dns_records.go +++ b/internal/provider/dns/datasource_dns_records.go @@ -44,6 +44,7 @@ func (d *dnsRecordsDatasource) Schema(_ context.Context, _ datasource.SchemaRequ resp.Schema = schema.Schema{ Description: "Retrieves information about a all DNS records.", Attributes: map[string]schema.Attribute{ + "site": base.SiteAttribute(), "result": schema.ListNestedAttribute{ Description: "The list of DNS records.", Computed: true, @@ -63,15 +64,18 @@ func (d *dnsRecordsDatasource) Read(ctx context.Context, req datasource.ReadRequ return } - records, err := d.client.ListDNSRecord(ctx, d.client.Site) + site := d.client.ResolveSite(&state) + records, err := d.client.ListDNSRecord(ctx, site) if err != nil { resp.Diagnostics.AddError("Failed to list DNS records", err.Error()) return } for _, record := range records { state.Records = append(state.Records, &dnsRecordModel{ - ID: types.StringValue(record.ID), - SiteID: types.StringValue(record.SiteID), + Model: base.Model{ + ID: types.StringValue(record.ID), + Site: types.StringValue(site), + }, Name: types.StringValue(record.Key), Record: types.StringValue(record.Value), Enabled: types.BoolValue(record.Enabled), diff --git a/internal/provider/dns/dns_record_model.go b/internal/provider/dns/dns_record_model.go index 9ab7e76..fd1c6a1 100644 --- a/internal/provider/dns/dns_record_model.go +++ b/internal/provider/dns/dns_record_model.go @@ -1,17 +1,20 @@ package dns import ( + "context" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/types" ) const resourceName = "dns_record" +var _ base.ResourceModel = &dnsRecordModel{} + type dnsRecordModel struct { - ID types.String `tfsdk:"id"` - SiteID types.String `tfsdk:"site_id"` + base.Model Name types.String `tfsdk:"name"` Record types.String `tfsdk:"record"` Enabled types.Bool `tfsdk:"enabled"` @@ -28,12 +31,25 @@ type dnsRecordDatasourceModel struct { } type dnsRecordsDatasourceModel struct { + Site types.String `tfsdk:"site"` Records []*dnsRecordModel `tfsdk:"result"` } +func (b *dnsRecordsDatasourceModel) GetSite() string { + return b.Site.ValueString() +} + +func (b *dnsRecordsDatasourceModel) GetRawSite() types.String { + return b.Site +} + +func (b *dnsRecordsDatasourceModel) SetSite(site string) { + b.Site = types.StringValue(site) +} + var dnsRecordDatasourceAttributes = map[string]schema.Attribute{ - "id": base.ID(), - "site_id": base.ID("The site ID where the DNS record is located."), + "id": base.ID(), + "site": base.SiteAttribute(), "name": schema.StringAttribute{ Description: "DNS record name.", Computed: true, @@ -73,10 +89,9 @@ type dnsRecordFilterModel struct { Record types.String `tfsdk:"record"` } -func (d *dnsRecordModel) asUnifiModel() *unifi.DNSRecord { +func (d *dnsRecordModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnostics) { return &unifi.DNSRecord{ ID: d.ID.ValueString(), - SiteID: d.SiteID.ValueString(), Key: d.Name.ValueString(), Value: d.Record.ValueString(), Enabled: d.Enabled.ValueBool(), @@ -85,12 +100,17 @@ func (d *dnsRecordModel) asUnifiModel() *unifi.DNSRecord { RecordType: d.Type.ValueString(), Ttl: int(d.TTL.ValueInt32()), Weight: int(d.Weight.ValueInt32()), - } + }, diag.Diagnostics{} } -func (d *dnsRecordModel) merge(other *unifi.DNSRecord) { +func (d *dnsRecordModel) Merge(ctx context.Context, i interface{}) diag.Diagnostics { + diags := diag.Diagnostics{} + other, ok := i.(*unifi.DNSRecord) + if !ok { + diags.AddError("Invalid model type", "Expected *unifi.DNSRecord") + return diags + } d.ID = types.StringValue(other.ID) - d.SiteID = types.StringValue(other.SiteID) d.Name = types.StringValue(other.Key) d.Record = types.StringValue(other.Value) d.Enabled = types.BoolValue(other.Enabled) @@ -99,4 +119,5 @@ func (d *dnsRecordModel) merge(other *unifi.DNSRecord) { d.Type = types.StringValue(other.RecordType) d.TTL = types.Int32Value(int32(other.Ttl)) d.Weight = types.Int32Value(int32(other.Weight)) + return diags } diff --git a/internal/provider/dns/resource_dns_record.go b/internal/provider/dns/resource_dns_record.go index ce0281f..efd4c05 100644 --- a/internal/provider/dns/resource_dns_record.go +++ b/internal/provider/dns/resource_dns_record.go @@ -2,12 +2,12 @@ package dns import ( "context" - "fmt" - + "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/filipowm/terraform-provider-unifi/internal/provider/validators" "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" - "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" @@ -16,35 +16,56 @@ import ( ) var ( - _ resource.Resource = &dnsRecordResource{} - _ resource.ResourceWithConfigure = &dnsRecordResource{} - _ resource.ResourceWithImportState = &dnsRecordResource{} - _ base.Resource = &dnsRecordResource{} + _ resource.Resource = &dnsRecordResource{} + _ resource.ResourceWithConfigure = &dnsRecordResource{} + _ resource.ResourceWithImportState = &dnsRecordResource{} + _ resource.ResourceWithModifyPlan = &dnsRecordResource{} + _ resource.ResourceWithConfigValidators = &dnsRecordResource{} + _ base.Resource = &dnsRecordResource{} ) type dnsRecordResource struct { - base.ControllerVersionValidator - client *base.Client + *base.GenericResource[*dnsRecordModel] } -func (d *dnsRecordResource) SetClient(client *base.Client) { - d.client = client +func (d *dnsRecordResource) ConfigValidators(ctx context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + validators.RequiredNoneIf(path.MatchRoot("type"), types.StringValue("A"), path.MatchRoot("priority"), path.MatchRoot("weight"), path.MatchRoot("port")), + validators.RequiredNoneIf(path.MatchRoot("type"), types.StringValue("AAAA"), path.MatchRoot("priority"), path.MatchRoot("weight"), path.MatchRoot("port")), + validators.RequiredNoneIf(path.MatchRoot("type"), types.StringValue("CNAME"), path.MatchRoot("priority"), path.MatchRoot("weight"), path.MatchRoot("port")), + validators.RequiredNoneIf(path.MatchRoot("type"), types.StringValue("MX"), path.MatchRoot("weight"), path.MatchRoot("port")), + validators.RequiredNoneIf(path.MatchRoot("type"), types.StringValue("NS"), path.MatchRoot("priority"), path.MatchRoot("weight"), path.MatchRoot("port")), + validators.RequiredNoneIf(path.MatchRoot("type"), types.StringValue("PTR"), path.MatchRoot("priority"), path.MatchRoot("weight"), path.MatchRoot("port")), + validators.RequiredNoneIf(path.MatchRoot("type"), types.StringValue("SOA"), path.MatchRoot("priority"), path.MatchRoot("weight"), path.MatchRoot("port")), + validators.RequiredNoneIf(path.MatchRoot("type"), types.StringValue("TXT"), path.MatchRoot("priority"), path.MatchRoot("weight"), path.MatchRoot("port")), + } } -func (d *dnsRecordResource) SetVersionValidator(validator base.ControllerVersionValidator) { - d.ControllerVersionValidator = validator +func (d *dnsRecordResource) ModifyPlan(_ context.Context, _ resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + resp.Diagnostics.Append(d.RequireMinVersion("8.2")...) } func NewDnsRecordResource() resource.Resource { - return &dnsRecordResource{} -} - -func (d *dnsRecordResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - base.ConfigureResource(d, req, resp) -} - -func (d *dnsRecordResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = fmt.Sprintf("%s_%s", req.ProviderTypeName, resourceName) + return &dnsRecordResource{ + GenericResource: base.NewGenericResource( + "unifi_dns_record", + func() *dnsRecordModel { return &dnsRecordModel{} }, + base.ResourceFunctions{ + Read: func(ctx context.Context, client *base.Client, site, id string) (interface{}, error) { + return client.GetDNSRecord(ctx, site, id) + }, + Create: func(ctx context.Context, client *base.Client, site string, model interface{}) (interface{}, error) { + return client.CreateDNSRecord(ctx, site, model.(*unifi.DNSRecord)) + }, + Update: func(ctx context.Context, client *base.Client, site string, model interface{}) (interface{}, error) { + return client.UpdateDNSRecord(ctx, site, model.(*unifi.DNSRecord)) + }, + Delete: func(ctx context.Context, client *base.Client, site, id string) error { + return client.DeleteDNSRecord(ctx, site, id) + }, + }, + ), + } } func (d *dnsRecordResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { @@ -57,8 +78,8 @@ func (d *dnsRecordResource) Schema(_ context.Context, _ resource.SchemaRequest, " * Adding TXT records for service verification\n\n", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site_id": base.ID("The site ID where the DNS record is located."), + "id": base.ID(), + "site": base.SiteAttribute(), "name": schema.StringAttribute{ MarkdownDescription: "DNS record name.", Required: true, @@ -123,134 +144,4 @@ func (d *dnsRecordResource) Schema(_ context.Context, _ resource.SchemaRequest, }, }, } - -} - -func (d *dnsRecordResource) checkSupportsDnsRecords(diag *diag.Diagnostics) { - if !d.client.SupportsDnsRecords() { - diag.AddError("DNS Records are not supported", fmt.Sprintf("The Unifi controller in version %q does not support DNS records. Required controller version: %q", d.client.Version, base.ControllerVersionDnsRecords)) - } -} - -func (d *dnsRecordResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - d.checkSupportsDnsRecords(&resp.Diagnostics) - if resp.Diagnostics.HasError() { - return - } - var plan dnsRecordModel - diags := req.Plan.Get(ctx, &plan) - resp.Diagnostics.Append(diags...) - - if resp.Diagnostics.HasError() { - return - } - body := plan.asUnifiModel() - - res, err := d.client.CreateDNSRecord(ctx, d.client.Site, body) - if err != nil { - resp.Diagnostics.AddError("Error creating DNS record", err.Error()) - return - } - plan.merge(res) - - resp.State.Set(ctx, plan) - resp.Diagnostics.Append(diags...) -} - -func (d *dnsRecordResource) read(ctx context.Context, state *dnsRecordModel, diag *diag.Diagnostics) { - res, err := d.client.GetDNSRecord(ctx, d.client.Site, state.ID.ValueString()) - if err != nil { - diag.AddError("Error reading DNS record", err.Error()) - return - } - state.merge(res) -} - -func (d *dnsRecordResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - d.checkSupportsDnsRecords(&resp.Diagnostics) - if resp.Diagnostics.HasError() { - return - } - var state dnsRecordModel - diags := req.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - d.read(ctx, &state, &resp.Diagnostics) - - if resp.Diagnostics.HasError() { - return - } - diags = resp.State.Set(ctx, state) - resp.Diagnostics.Append(diags...) -} - -func (d *dnsRecordResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - d.checkSupportsDnsRecords(&resp.Diagnostics) - if resp.Diagnostics.HasError() { - return - } - var plan, state dnsRecordModel - - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - - if resp.Diagnostics.HasError() { - return - } - - body := plan.asUnifiModel() - - res, err := d.client.UpdateDNSRecord(ctx, d.client.Site, body) - if err != nil { - resp.Diagnostics.AddError("Error updating DNS record", err.Error()) - return - } - state.merge(res) - diags := resp.State.Set(ctx, state) - resp.Diagnostics.Append(diags...) -} - -func (d *dnsRecordResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - d.checkSupportsDnsRecords(&resp.Diagnostics) - if resp.Diagnostics.HasError() { - return - } - var state dnsRecordModel - diags := req.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - - if resp.Diagnostics.HasError() { - return - } - - err := d.client.DeleteDNSRecord(ctx, d.client.Site, state.ID.ValueString()) - if err != nil { - resp.Diagnostics.AddError("Error deleting DNS record", err.Error()) - return - } -} - -func (d *dnsRecordResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - d.checkSupportsDnsRecords(&resp.Diagnostics) - if resp.Diagnostics.HasError() { - return - } - id := req.ID - if id == "" { - resp.Diagnostics.AddError("Invalid import ID", "The ID must be set") - return - } - - state := dnsRecordModel{ - ID: types.StringValue(id), - } - d.read(ctx, &state, &resp.Diagnostics) - - if resp.Diagnostics.HasError() { - return - } - diags := resp.State.Set(ctx, state) - resp.Diagnostics.Append(diags...) }