diff --git a/Makefile b/Makefile index 84c0ad3..c32e040 100644 --- a/Makefile +++ b/Makefile @@ -13,4 +13,4 @@ build: .PHONY: testacc testacc: go build ./... - TF_LOG_PROVIDER=debug TF_ACC=1 go test $(TEST) -v -count $(TEST_COUNT) -timeout $(TEST_TIMEOUT) $(TESTARGS) + TF_LOG_PROVIDER=debug TF_ACC=1 go test $(TEST) -test.parallel 2 -v -count $(TEST_COUNT) -timeout $(TEST_TIMEOUT) $(TESTARGS) diff --git a/internal/provider/acctest/resource_setting_auto_speedtest_test.go b/internal/provider/acctest/resource_setting_auto_speedtest_test.go new file mode 100644 index 0000000..c02fe2c --- /dev/null +++ b/internal/provider/acctest/resource_setting_auto_speedtest_test.go @@ -0,0 +1,65 @@ +package acctest + +import ( + "fmt" + pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing" + "github.com/hashicorp/go-version" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "regexp" + "testing" +) + +func TestAccSettingAutoSpeedtest(t *testing.T) { + t.Skip("Auto Speedtest is not supported on test controller") + AcceptanceTest(t, AcceptanceTestCase{ + MinVersion: version.Must(version.NewVersion("7.2")), + VersionConstraint: "< 7.5", + Steps: []resource.TestStep{ + { + Config: testAccSettingAutoSpeedtestConfig(true, "0 0 * * *"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("unifi_setting_auto_speedtest.test", "enabled", "true"), + resource.TestCheckResourceAttr("unifi_setting_auto_speedtest.test", "cron", "0 0 * * *"), + ), + }, + pt.ImportStep("unifi_setting_auto_speedtest.test"), + { + Config: testAccSettingAutoSpeedtestConfig(false, "0 0 * * *"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("unifi_setting_auto_speedtest.test", "enabled", "false"), + resource.TestCheckResourceAttr("unifi_setting_auto_speedtest.test", "cron", "0 0 * * *"), + ), + }, + pt.ImportStep("unifi_setting_auto_speedtest.test"), + { + Config: testAccSettingAutoSpeedtestConfig(true, "5 0 * * *"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("unifi_setting_auto_speedtest.test", "enabled", "true"), + resource.TestCheckResourceAttr("unifi_setting_auto_speedtest.test", "cron", "5 0 * * *"), + ), + }, + }, + }) +} + +// TODO remove when controller changed from USG, which has removed support of speedtest since 7.4. Other controllers still have it. +func TestAccSettingAutoSpeedtest_unsupported(t *testing.T) { + AcceptanceTest(t, AcceptanceTestCase{ + VersionConstraint: ">= 7.5", + Steps: []resource.TestStep{ + { + Config: testAccSettingAutoSpeedtestConfig(true, "0 0 * * *"), + ExpectError: regexp.MustCompile("Auto Speedtest is not supported on this controller"), + }, + }, + }) +} + +func testAccSettingAutoSpeedtestConfig(enabled bool, cron string) string { + return fmt.Sprintf(` +resource "unifi_setting_auto_speedtest" "test" { + enabled = %t + cron = %q +} +`, enabled, cron) +} diff --git a/internal/provider/acctest/resource_setting_country_test.go b/internal/provider/acctest/resource_setting_country_test.go index acdf1aa..03d7f6b 100644 --- a/internal/provider/acctest/resource_setting_country_test.go +++ b/internal/provider/acctest/resource_setting_country_test.go @@ -19,6 +19,8 @@ func TestAccSettingCountry(t *testing.T) { { Config: testAccSettingCountryConfig("US"), Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("unifi_setting_country.test", "id"), + resource.TestCheckResourceAttr("unifi_setting_country.test", "site", "default"), resource.TestCheckResourceAttr("unifi_setting_country.test", "code", "US"), resource.TestCheckResourceAttr("unifi_setting_country.test", "code_numeric", "840"), ), @@ -28,6 +30,8 @@ func TestAccSettingCountry(t *testing.T) { { Config: testAccSettingCountryConfig("PL"), Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("unifi_setting_country.test", "id"), + resource.TestCheckResourceAttr("unifi_setting_country.test", "site", "default"), resource.TestCheckResourceAttr("unifi_setting_country.test", "code", "PL"), resource.TestCheckResourceAttr("unifi_setting_country.test", "code_numeric", "616"), ), @@ -37,8 +41,10 @@ func TestAccSettingCountry(t *testing.T) { }) } -var invalidCountryCodeErrorRegex = regexp.MustCompile("ISO 3166-1 alpha-2") -var stringLengthExactly2Regex = regexp.MustCompile("string length must be exactly 2") +var ( + invalidCountryCodeErrorRegex = regexp.MustCompile("ISO 3166-1 alpha-2") + stringLengthExactly2Regex = regexp.MustCompile("string length must be exactly 2") +) func TestAccSettingCountry_invalidCode(t *testing.T) { AcceptanceTest(t, AcceptanceTestCase{ diff --git a/internal/provider/base/base.go b/internal/provider/base/base.go index 5c8e78e..216d5eb 100644 --- a/internal/provider/base/base.go +++ b/internal/provider/base/base.go @@ -2,34 +2,59 @@ package base import ( "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" ) -type BaseData interface { +type Resource interface { SetClient(client *Client) } -type Site struct { +// ResourceModel defines the interface that all setting models must implement +type ResourceModel interface { + GetSite() string + GetRawSite() types.String + SetSite(string) + GetID() string + GetRawID() types.String + SetID(string) + AsUnifiModel() (interface{}, diag.Diagnostics) + Merge(interface{}) diag.Diagnostics +} + +type Model struct { + ID types.String `tfsdk:"id"` Site types.String `tfsdk:"site"` } -func NewSite(str string) Site { - return Site{ - Site: types.StringValue(str), - } +func (b *Model) GetID() string { + return b.ID.ValueString() } -func (s *Site) SetSite(site string) { - s.Site = types.StringValue(site) +func (b *Model) GetRawID() types.String { + return b.ID } -func (s *Site) AsString() string { - return s.Site.ValueString() +func (b *Model) SetID(id string) { + b.ID = types.StringValue(id) } -func ConfigureDatasource(base BaseData, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { +func (b *Model) GetSite() string { + return b.Site.ValueString() +} + +func (b *Model) GetRawSite() types.String { + return b.Site +} + +func (b *Model) SetSite(site string) { + b.Site = types.StringValue(site) +} + +func ConfigureDatasource(base Resource, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { if req.ProviderData == nil { return } @@ -46,7 +71,7 @@ func ConfigureDatasource(base BaseData, req datasource.ConfigureRequest, resp *d base.SetClient(cfg) } -func ConfigureResource(base BaseData, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { +func ConfigureResource(base Resource, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { if req.ProviderData == nil { return } diff --git a/internal/provider/base/client.go b/internal/provider/base/client.go index 1405d6b..6f85a26 100644 --- a/internal/provider/base/client.go +++ b/internal/provider/base/client.go @@ -80,11 +80,11 @@ type Client struct { Version *version.Version } -func (c *Client) ResolveSite(site *Site) string { - if site == nil || IsEmptyString(site.Site) { +func (c *Client) ResolveSite(res ResourceModel) string { + if res == nil || IsEmptyString(res.GetRawSite()) { return c.Site } - return site.AsString() + return res.GetSite() } func CreateHttpTransport(insecure bool) http.RoundTripper { diff --git a/internal/provider/base/errors.go b/internal/provider/base/errors.go new file mode 100644 index 0000000..6c24558 --- /dev/null +++ b/internal/provider/base/errors.go @@ -0,0 +1,12 @@ +package base + +import ( + "github.com/hashicorp/terraform-plugin-framework/diag" + "reflect" +) + +func ErrorInvalidModelMergeTarget(expectedType, actualType interface{}) diag.Diagnostic { + e := reflect.TypeOf(&expectedType).Elem().String() + a := reflect.TypeOf(&actualType).Elem().String() + return diag.NewErrorDiagnostic("Invalid model merge target", "Expected target type to be the same a receiver: "+e+". Was : "+a) +} diff --git a/internal/provider/dns/datasource_dns_record.go b/internal/provider/dns/datasource_dns_record.go index 05982f1..b213f22 100644 --- a/internal/provider/dns/datasource_dns_record.go +++ b/internal/provider/dns/datasource_dns_record.go @@ -18,7 +18,7 @@ import ( var ( _ datasource.DataSource = &dnsRecordDatasource{} _ datasource.DataSourceWithConfigure = &dnsRecordDatasource{} - _ base.BaseData = &dnsRecordDatasource{} + _ base.Resource = &dnsRecordDatasource{} _ datasource.DataSourceWithConfigValidators = &dnsRecordDatasource{} ) diff --git a/internal/provider/dns/datasource_dns_records.go b/internal/provider/dns/datasource_dns_records.go index 4f06928..bcc4c3c 100644 --- a/internal/provider/dns/datasource_dns_records.go +++ b/internal/provider/dns/datasource_dns_records.go @@ -12,7 +12,7 @@ import ( var ( _ datasource.DataSource = &dnsRecordsDatasource{} _ datasource.DataSourceWithConfigure = &dnsRecordsDatasource{} - _ base.BaseData = &dnsRecordsDatasource{} + _ base.Resource = &dnsRecordsDatasource{} ) type dnsRecordsDatasource struct { diff --git a/internal/provider/dns/resource_dns_record.go b/internal/provider/dns/resource_dns_record.go index e8989dd..9a20640 100644 --- a/internal/provider/dns/resource_dns_record.go +++ b/internal/provider/dns/resource_dns_record.go @@ -19,7 +19,7 @@ var ( _ resource.Resource = &dnsRecordResource{} _ resource.ResourceWithConfigure = &dnsRecordResource{} _ resource.ResourceWithImportState = &dnsRecordResource{} - _ base.BaseData = &dnsRecordResource{} + _ base.Resource = &dnsRecordResource{} ) type dnsRecordResource struct { diff --git a/internal/provider/provider_v2.go b/internal/provider/provider_v2.go index db311c6..9ab21d3 100644 --- a/internal/provider/provider_v2.go +++ b/internal/provider/provider_v2.go @@ -168,6 +168,7 @@ func (p *unifiProvider) Configure(ctx context.Context, req provider.ConfigureReq func (p *unifiProvider) Resources(_ context.Context) []func() resource.Resource { return []func() resource.Resource{ dns.NewDnsRecordResource, + settings.NewAutoSpeedtestResource, settings.NewCountryResource, } } diff --git a/internal/provider/settings/base_setting_resource.go b/internal/provider/settings/base_setting_resource.go new file mode 100644 index 0000000..0621f9e --- /dev/null +++ b/internal/provider/settings/base_setting_resource.go @@ -0,0 +1,189 @@ +package settings + +import ( + "context" + "errors" + "fmt" + + "github.com/filipowm/go-unifi/unifi" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +// BaseSettingResource provides common functionality for all setting resources +type BaseSettingResource[T base.ResourceModel] struct { + client *base.Client + typeName string + modelFactory func() T + getter func(context.Context, *base.Client, string) (interface{}, error) + updater func(context.Context, *base.Client, string, interface{}) (interface{}, error) +} + +// NewBaseSettingResource creates a new base setting resource +func NewBaseSettingResource[T base.ResourceModel]( + typeName string, + modelFactory func() T, + getter func(context.Context, *base.Client, string) (interface{}, error), + updater func(context.Context, *base.Client, string, interface{}) (interface{}, error), +) *BaseSettingResource[T] { + return &BaseSettingResource[T]{ + typeName: typeName, + modelFactory: modelFactory, + getter: getter, + updater: updater, + } +} + +// GetClient returns the UniFi client +func (b *BaseSettingResource[T]) GetClient() *base.Client { + return b.client +} + +// SetClient sets the UniFi client +func (b *BaseSettingResource[T]) SetClient(client *base.Client) { + b.client = client +} + +func (b *BaseSettingResource[T]) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + base.ConfigureResource(b, req, resp) +} + +func (b *BaseSettingResource[T]) Metadata(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = b.typeName +} + +func (b *BaseSettingResource[T]) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + id, site := base.ImportIDWithSite(req, resp) + if resp.Diagnostics.HasError() { + return + } + state := b.modelFactory() + state.SetID(id) + state.SetSite(site) + b.read(ctx, site, state, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (b *BaseSettingResource[T]) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + if b.client == nil { + resp.Diagnostics.AddError( + "Client Not Configured", + "Expected configured client. Please report this issue to the provider developers.", + ) + return + } + + var plan T + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + body, diags := plan.AsUnifiModel() + + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + site := b.client.ResolveSite(plan) + + res, err := b.updater(ctx, b.client, site, body) + if err != nil { + resp.Diagnostics.AddError("Error creating settings", err.Error()) + return + } + if res == nil { + resp.Diagnostics.AddError("Error creating settings", fmt.Sprintf("No %[1]s settings returned from the UniFi controller. %[1]s might not be supported on this controller", b.typeName)) + return + } + plan.Merge(res) + plan.SetSite(site) + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (b *BaseSettingResource[T]) read(ctx context.Context, site string, state T, diag *diag.Diagnostics) { + if b.client == nil { + diag.AddError( + "Client Not Configured", + "Expected configured client. Please report this issue to the provider developers.", + ) + return + } + + res, err := b.getter(ctx, b.client, site) + if err != nil { + if errors.Is(err, unifi.ErrNotFound) { + diag.AddError("Settings not found", "The settings were not found in the UniFi controller") + } else { + diag.AddError("Error reading settings", err.Error()) + } + return + } + if res != nil { + state.Merge(res) + } +} + +func (b *BaseSettingResource[T]) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + if b.client == nil { + resp.Diagnostics.AddError( + "Client Not Configured", + "Expected configured client. Please report this issue to the provider developers.", + ) + return + } + + var state T + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + site := b.client.ResolveSite(state) + b.read(ctx, site, state, &resp.Diagnostics) + + if resp.Diagnostics.HasError() { + return + } + state.SetSite(site) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (b *BaseSettingResource[T]) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + if b.client == nil { + resp.Diagnostics.AddError( + "Client Not Configured", + "Expected configured client. Please report this issue to the provider developers.", + ) + return + } + + var plan, state T + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + body, diags := plan.AsUnifiModel() + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + site := b.client.ResolveSite(plan) + + res, err := b.updater(ctx, b.client, site, body) + if err != nil { + resp.Diagnostics.AddError("Error updating settings", err.Error()) + return + } + state.Merge(res) + state.SetSite(site) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (b *BaseSettingResource[T]) Delete(_ context.Context, _ resource.DeleteRequest, _ *resource.DeleteResponse) { + // Not supported +} diff --git a/internal/provider/settings/resource_setting_auto_speedtest.go b/internal/provider/settings/resource_setting_auto_speedtest.go new file mode 100644 index 0000000..24a0b36 --- /dev/null +++ b/internal/provider/settings/resource_setting_auto_speedtest.go @@ -0,0 +1,96 @@ +package settings + +import ( + "context" + "fmt" + "github.com/filipowm/go-unifi/unifi" + "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ resource.Resource = &autoSpeedtestResource{} + _ resource.ResourceWithConfigure = &autoSpeedtestResource{} + _ resource.ResourceWithImportState = &autoSpeedtestResource{} + _ base.Resource = &autoSpeedtestResource{} +) + +type autoSpeedtestModel struct { + base.Model + CronExpression types.String `tfsdk:"cron"` + Enabled types.Bool `tfsdk:"enabled"` +} + +func (d *autoSpeedtestModel) AsUnifiModel() (interface{}, diag.Diagnostics) { + return &unifi.SettingAutoSpeedtest{ + ID: d.ID.ValueString(), + CronExpr: d.CronExpression.ValueString(), + Enabled: d.Enabled.ValueBool(), + }, diag.Diagnostics{} +} + +func (d *autoSpeedtestModel) Merge(other interface{}) diag.Diagnostics { + if typed, ok := other.(*unifi.SettingAutoSpeedtest); ok { + d.ID = types.StringValue(typed.ID) + d.CronExpression = types.StringValue(typed.CronExpr) + d.Enabled = types.BoolValue(typed.Enabled) + } + return diag.Diagnostics{} +} + +type autoSpeedtestResource struct { + *BaseSettingResource[*autoSpeedtestModel] +} + +func checkAutoSpeedtestUnsupportedError(err error) error { + if base.IsServerErrorContains(err, "api.err.SpeedTestNotSupported") { + return fmt.Errorf("Auto Speedtest is not supported on this controller") + } + return err +} + +func NewAutoSpeedtestResource() resource.Resource { + r := &autoSpeedtestResource{} + r.BaseSettingResource = NewBaseSettingResource( + "unifi_setting_auto_speedtest", + func() *autoSpeedtestModel { return &autoSpeedtestModel{} }, + func(ctx context.Context, client *base.Client, site string) (interface{}, error) { + res, err := client.GetSettingAutoSpeedtest(ctx, site) + if err != nil { + return nil, checkAutoSpeedtestUnsupportedError(err) + } + return res, nil + }, + func(ctx context.Context, client *base.Client, site string, body interface{}) (interface{}, error) { + res, err := client.UpdateSettingAutoSpeedtest(ctx, site, body.(*unifi.SettingAutoSpeedtest)) + if err != nil { + return nil, checkAutoSpeedtestUnsupportedError(err) + } + return res, nil + }, + ) + return r +} + +func (a *autoSpeedtestResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "The `unifi_setting_auto_speedtest` resource manages the automatic speedtest settings in the UniFi controller." + + "Automatic speedtests can be scheduled to run at regular intervals to monitor the network performance.\n\n" + + "**NOTE:** Automatic speedtests where not verified and tested on all UniFi controller versions due to limitations of controller used in acceptance testing. ", + Attributes: map[string]schema.Attribute{ + "id": base.ID(), + "site": base.SiteAttribute(), + "cron": schema.StringAttribute{ + MarkdownDescription: "Cron expression defining the schedule for automatic speedtests.", + Optional: true, + }, + "enabled": schema.BoolAttribute{ + MarkdownDescription: "Whether the automatic speedtest is enabled.", + Required: true, + }, + }, + } +} diff --git a/internal/provider/settings/resource_setting_country.go b/internal/provider/settings/resource_setting_country.go index 458c780..4de93a1 100644 --- a/internal/provider/settings/resource_setting_country.go +++ b/internal/provider/settings/resource_setting_country.go @@ -2,7 +2,6 @@ package settings import ( "context" - "errors" "github.com/biter777/countries" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" @@ -14,70 +13,54 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -type countryModel struct { - base.Site - ID types.String `tfsdk:"id"` - Code types.String `tfsdk:"code"` - CodeNumeric types.Int32 `tfsdk:"code_numeric"` -} - -func (d *countryModel) asUnifiModel() *unifi.SettingCountry { - code := countries.ByName(d.Code.ValueString()) - return &unifi.SettingCountry{ - ID: d.ID.ValueString(), - Code: int(code), - } -} - -func (d *countryModel) merge(other *unifi.SettingCountry) { - d.ID = types.StringValue(other.ID) - // UniFi uses numeric codes, so we need to convert the alpha-2 code to the numeric code, but we store both - code := countries.ByNumeric(other.Code) - d.Code = types.StringValue(code.Alpha2()) - d.CodeNumeric = types.Int32Value(int32(code)) -} - var ( _ resource.Resource = &countryResource{} _ resource.ResourceWithConfigure = &countryResource{} _ resource.ResourceWithImportState = &countryResource{} - _ base.BaseData = &countryResource{} + _ base.Resource = &countryResource{} ) -type countryResource struct { - client *base.Client +type countryModel struct { + base.Model + Code types.String `tfsdk:"code"` + CodeNumeric types.Int32 `tfsdk:"code_numeric"` } -func (c *countryResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - id, site := base.ImportIDWithSite(req, resp) - if resp.Diagnostics.HasError() { - return +func (d *countryModel) AsUnifiModel() (interface{}, diag.Diagnostics) { + code := countries.ByName(d.Code.ValueString()) + return &unifi.SettingCountry{ + ID: d.ID.ValueString(), + Code: int(code), + }, diag.Diagnostics{} +} + +func (d *countryModel) Merge(other interface{}) diag.Diagnostics { + if typed, ok := other.(*unifi.SettingCountry); ok { + d.ID = types.StringValue(typed.ID) + code := countries.ByNumeric(typed.Code) + d.Code = types.StringValue(code.Alpha2()) + d.CodeNumeric = types.Int32Value(int32(code)) } - state := countryModel{ - ID: types.StringValue(id), - Site: base.NewSite(site), - } - c.read(ctx, site, &state, &resp.Diagnostics) - if resp.Diagnostics.HasError() { - return - } - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + return diag.Diagnostics{} +} + +type countryResource struct { + *BaseSettingResource[*countryModel] } func NewCountryResource() resource.Resource { - return &countryResource{} -} - -func (c *countryResource) SetClient(client *base.Client) { - c.client = client -} - -func (c *countryResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - base.ConfigureResource(c, req, resp) -} - -func (c *countryResource) Metadata(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "unifi_setting_country" + r := &countryResource{} + r.BaseSettingResource = NewBaseSettingResource( + "unifi_setting_country", + func() *countryModel { return &countryModel{} }, + func(ctx context.Context, client *base.Client, site string) (interface{}, error) { + return client.GetSettingCountry(ctx, site) + }, + func(ctx context.Context, client *base.Client, site string, body interface{}) (interface{}, error) { + return client.UpdateSettingCountry(ctx, site, body.(*unifi.SettingCountry)) + }, + ) + return r } func (c *countryResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { @@ -101,76 +84,3 @@ func (c *countryResource) Schema(_ context.Context, _ resource.SchemaRequest, re }, } } - -func (c *countryResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var plan countryModel - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { - return - } - body := plan.asUnifiModel() - site := c.client.ResolveSite(&plan.Site) - - res, err := c.client.UpdateSettingCountry(ctx, site, body) - if err != nil { - resp.Diagnostics.AddError("Error creating country settings", err.Error()) - return - } - plan.merge(res) - plan.Site.SetSite(site) - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) -} - -func (c *countryResource) read(ctx context.Context, site string, state *countryModel, diag *diag.Diagnostics) { - res, err := c.client.GetSettingCountry(ctx, site) - - if err != nil { - if errors.Is(err, unifi.ErrNotFound) { - diag.AddError("Country settings not found", "The country settings were not found in the UniFi controller") - } else { - diag.AddError("Error reading country settings", err.Error()) - } - return - } - state.merge(res) -} - -func (c *countryResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var state countryModel - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - site := c.client.ResolveSite(&state.Site) - c.read(ctx, site, &state, &resp.Diagnostics) - - if resp.Diagnostics.HasError() { - return - } - (&state).Site.SetSite(site) - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) -} - -func (c *countryResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var plan, state countryModel - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - body := plan.asUnifiModel() - site := c.client.ResolveSite(&plan.Site) - - res, err := c.client.UpdateSettingCountry(ctx, site, body) - if err != nil { - resp.Diagnostics.AddError("Error updating country settings", err.Error()) - return - } - state.merge(res) - state.Site.SetSite(site) - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) -} - -func (c *countryResource) Delete(_ context.Context, _ resource.DeleteRequest, _ *resource.DeleteResponse) { - // Not supported -} diff --git a/internal/provider/settings/resource_setting_country_test.go b/internal/provider/settings/resource_setting_country_test.go index c61da61..cccd71c 100644 --- a/internal/provider/settings/resource_setting_country_test.go +++ b/internal/provider/settings/resource_setting_country_test.go @@ -26,8 +26,10 @@ func TestSettingCountry_ProperCountryCodeMappingFromModel(t *testing.T) { model := countryModel{ Code: types.StringValue(tc.code), } - unifiModel := model.asUnifiModel() - assert.Equal(t, tc.expectedNumericCode, unifiModel.Code) + unifiModel, _ := model.AsUnifiModel() + typed, ok := unifiModel.(*unifi.SettingCountry) + assert.True(t, ok) + assert.Equal(t, tc.expectedNumericCode, typed.Code) }) } } @@ -52,7 +54,7 @@ func TestSettingCountry_ProperCountryCodeMappingToModel(t *testing.T) { Code: tc.numericCode, } model := countryModel{} - model.merge(unifiModel) + model.Merge(unifiModel) assert.Equal(t, tc.expectedCode, model.Code.ValueString()) }) }