fix: invalid handling of site of DNS resource and datasources (#52)

This commit is contained in:
Mateusz Filipowicz
2025-03-14 01:03:09 +01:00
committed by GitHub
parent 5d1643ed80
commit 34c495021f
5 changed files with 88 additions and 169 deletions

View File

@@ -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{

View File

@@ -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)...)
}

View File

@@ -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),

View File

@@ -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
}

View File

@@ -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...)
}