feat: add country setting resource support with unifi_setting_country resource (#31)
* feat: add country setting resource support with `unifi_setting_country` resource * linting
This commit is contained in:
committed by
GitHub
parent
ccac6edebe
commit
a36940b019
@@ -162,7 +162,7 @@ func TestDNSRecord_Update(t *testing.T) {
|
||||
{
|
||||
Config: testAccDnsRecordConfig(updated),
|
||||
Check: testAccDnsRecordCheckAttrs(updated),
|
||||
ConfigPlanChecks: pt.CheckResourceAction(testDnsRecordResourceName, plancheck.ResourceActionUpdate),
|
||||
ConfigPlanChecks: pt.CheckResourceActions(testDnsRecordResourceName, plancheck.ResourceActionUpdate),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
69
internal/provider/acctest/resource_setting_country_test.go
Normal file
69
internal/provider/acctest/resource_setting_country_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package acctest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing"
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-testing/plancheck"
|
||||
"regexp"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var settingCountryLock = &sync.Mutex{}
|
||||
|
||||
func TestAccSettingCountry(t *testing.T) {
|
||||
AcceptanceTest(t, AcceptanceTestCase{
|
||||
Lock: settingCountryLock,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccSettingCountryConfig("US"),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("unifi_setting_country.test", "code", "US"),
|
||||
resource.TestCheckResourceAttr("unifi_setting_country.test", "code_numeric", "840"),
|
||||
),
|
||||
ConfigPlanChecks: pt.CheckResourceActions("unifi_setting_country.test", plancheck.ResourceActionCreate),
|
||||
},
|
||||
pt.ImportStepWithSite("unifi_setting_country.test"),
|
||||
{
|
||||
Config: testAccSettingCountryConfig("PL"),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("unifi_setting_country.test", "code", "PL"),
|
||||
resource.TestCheckResourceAttr("unifi_setting_country.test", "code_numeric", "616"),
|
||||
),
|
||||
ConfigPlanChecks: pt.CheckResourceActions("unifi_setting_country.test", plancheck.ResourceActionUpdate),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
var invalidCountryCodeErrorRegex = regexp.MustCompile("ISO 3166-1 alpha-2")
|
||||
var stringLengthExactly2Regex = regexp.MustCompile("string length must be exactly 2")
|
||||
|
||||
func TestAccSettingCountry_invalidCode(t *testing.T) {
|
||||
AcceptanceTest(t, AcceptanceTestCase{
|
||||
Lock: settingCountryLock,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccSettingCountryConfig("WP"),
|
||||
ExpectError: invalidCountryCodeErrorRegex,
|
||||
},
|
||||
{
|
||||
Config: testAccSettingCountryConfig("Too long"),
|
||||
ExpectError: stringLengthExactly2Regex,
|
||||
},
|
||||
{
|
||||
Config: testAccSettingCountryConfig(""),
|
||||
ExpectError: stringLengthExactly2Regex,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccSettingCountryConfig(code string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "unifi_setting_country" "test" {
|
||||
code = %q
|
||||
}
|
||||
`, code)
|
||||
}
|
||||
@@ -4,12 +4,31 @@ import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
type BaseData interface {
|
||||
SetClient(client *Client)
|
||||
}
|
||||
|
||||
type Site struct {
|
||||
Site types.String `tfsdk:"site"`
|
||||
}
|
||||
|
||||
func NewSite(str string) Site {
|
||||
return Site{
|
||||
Site: types.StringValue(str),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Site) SetSite(site string) {
|
||||
s.Site = types.StringValue(site)
|
||||
}
|
||||
|
||||
func (s *Site) AsString() string {
|
||||
return s.Site.ValueString()
|
||||
}
|
||||
|
||||
func ConfigureDatasource(base BaseData, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
|
||||
@@ -80,6 +80,13 @@ type Client struct {
|
||||
Version *version.Version
|
||||
}
|
||||
|
||||
func (c *Client) ResolveSite(site *Site) string {
|
||||
if site == nil || IsEmptyString(site.Site) {
|
||||
return c.Site
|
||||
}
|
||||
return site.AsString()
|
||||
}
|
||||
|
||||
func CreateHttpTransport(insecure bool) http.RoundTripper {
|
||||
return &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
|
||||
36
internal/provider/base/importer.go
Normal file
36
internal/provider/base/importer.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ImportSiteAndID(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) {
|
||||
if id := d.Id(); strings.Contains(id, ":") {
|
||||
importParts := strings.SplitN(id, ":", 2)
|
||||
d.SetId(importParts[1])
|
||||
d.Set("site", importParts[0])
|
||||
}
|
||||
return []*schema.ResourceData{d}, nil
|
||||
}
|
||||
|
||||
func ImportIDWithSite(req resource.ImportStateRequest, resp *resource.ImportStateResponse) (string, string) {
|
||||
id := req.ID
|
||||
if id == "" {
|
||||
resp.Diagnostics.AddError("Invalid ID", "ID is required")
|
||||
return "", ""
|
||||
}
|
||||
|
||||
if strings.Contains(id, ":") {
|
||||
importParts := strings.SplitN(id, ":", 2)
|
||||
if len(importParts) == 2 {
|
||||
return importParts[1], importParts[0]
|
||||
}
|
||||
resp.Diagnostics.AddError("Invalid ID", "ID contains too many colon-separated parts. Format should be 'site:id'")
|
||||
return "", ""
|
||||
}
|
||||
resp.Diagnostics.AddError("Invalid ID", "ID does not contain site part. Format should be 'site:id'")
|
||||
return id, ""
|
||||
}
|
||||
57
internal/provider/base/types.go
Normal file
57
internal/provider/base/types.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/attr"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// ID generates an attribute definition suitable for the always-present `id` attribute.
|
||||
func ID(desc ...string) schema.StringAttribute {
|
||||
a := schema.StringAttribute{
|
||||
Description: "The unique identifier of this resource.",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
}
|
||||
|
||||
if len(desc) > 0 {
|
||||
a.Description = desc[0]
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func SiteAttribute(desc ...string) schema.StringAttribute {
|
||||
s := schema.StringAttribute{
|
||||
MarkdownDescription: "The name of the UniFi site where this resource should be applied. If not specified, the default site will be used.",
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
}
|
||||
|
||||
if len(desc) > 0 {
|
||||
s.Description = desc[0]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ShouldBeRemoved evaluates if an attribute should be removed from the plan during update.
|
||||
func ShouldBeRemoved(plan attr.Value, state attr.Value, isClone bool) bool {
|
||||
return !IsDefined(plan) && IsDefined(state) && !isClone
|
||||
}
|
||||
|
||||
// IsDefined returns true if attribute is known and not null.
|
||||
func IsDefined(v attr.Value) bool {
|
||||
return !v.IsNull() && !v.IsUnknown()
|
||||
}
|
||||
|
||||
func IsEmptyString(s types.String) bool {
|
||||
return s.IsNull() || s.IsUnknown() || s.ValueString() == ""
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func ResourcePortProfile() *schema.Resource {
|
||||
UpdateContext: resourcePortProfileUpdate,
|
||||
DeleteContext: resourcePortProfileDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: utils.ImportSiteAndID,
|
||||
StateContext: base.ImportSiteAndID,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
||||
@@ -2,7 +2,7 @@ package dns
|
||||
|
||||
import (
|
||||
"github.com/filipowm/go-unifi/unifi"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/utils"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
@@ -32,8 +32,8 @@ type dnsRecordsDatasourceModel struct {
|
||||
}
|
||||
|
||||
var dnsRecordDatasourceAttributes = map[string]schema.Attribute{
|
||||
"id": utils.ID(),
|
||||
"site_id": utils.ID("The site ID where the DNS record is located."),
|
||||
"id": base.ID(),
|
||||
"site_id": base.ID("The site ID where the DNS record is located."),
|
||||
"name": schema.StringAttribute{
|
||||
Description: "DNS record name.",
|
||||
Computed: true,
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/utils"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/int32validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
@@ -53,8 +52,8 @@ func (d *dnsRecordResource) Schema(_ context.Context, _ resource.SchemaRequest,
|
||||
" * Adding TXT records for service verification\n\n",
|
||||
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": utils.ID(),
|
||||
"site_id": utils.ID("The site ID where the DNS record is located."),
|
||||
"id": base.ID(),
|
||||
"site_id": base.ID("The site ID where the DNS record is located."),
|
||||
"name": schema.StringAttribute{
|
||||
MarkdownDescription: "DNS record name.",
|
||||
Required: true,
|
||||
@@ -244,11 +243,6 @@ func (d *dnsRecordResource) ImportState(ctx context.Context, req resource.Import
|
||||
}
|
||||
d.read(ctx, &state, &resp.Diagnostics)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
d.read(ctx, &state, &resp.Diagnostics)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,10 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/utils"
|
||||
|
||||
"github.com/filipowm/go-unifi/unifi"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
@@ -31,7 +29,7 @@ func ResourceDynamicDNS() *schema.Resource {
|
||||
UpdateContext: resourceDynamicDNSUpdate,
|
||||
DeleteContext: resourceDynamicDNSDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: utils.ImportSiteAndID,
|
||||
StateContext: base.ImportSiteAndID,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
||||
@@ -31,7 +31,7 @@ func ResourceFirewallGroup() *schema.Resource {
|
||||
UpdateContext: resourceFirewallGroupUpdate,
|
||||
DeleteContext: resourceFirewallGroupDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: utils.ImportSiteAndID,
|
||||
StateContext: base.ImportSiteAndID,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
||||
@@ -32,7 +32,7 @@ func ResourceFirewallRule() *schema.Resource {
|
||||
UpdateContext: resourceFirewallRuleUpdate,
|
||||
DeleteContext: resourceFirewallRuleDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: utils.ImportSiteAndID,
|
||||
StateContext: base.ImportSiteAndID,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
||||
@@ -33,7 +33,7 @@ func ResourceWLAN() *schema.Resource {
|
||||
UpdateContext: resourceWLANUpdate,
|
||||
DeleteContext: resourceWLANDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: utils.ImportSiteAndID,
|
||||
StateContext: base.ImportSiteAndID,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/dns"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/settings"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/utils"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
@@ -167,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.NewCountryResource,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/utils"
|
||||
|
||||
"github.com/filipowm/go-unifi/unifi"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
|
||||
@@ -38,7 +36,7 @@ func ResourceAccount() *schema.Resource {
|
||||
UpdateContext: resourceAccountUpdate,
|
||||
DeleteContext: resourceAccountDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: utils.ImportSiteAndID,
|
||||
StateContext: base.ImportSiteAndID,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
||||
@@ -28,7 +28,7 @@ func ResourcePortForward() *schema.Resource {
|
||||
UpdateContext: resourcePortForwardUpdate,
|
||||
DeleteContext: resourcePortForwardDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: utils.ImportSiteAndID,
|
||||
StateContext: base.ImportSiteAndID,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
||||
@@ -28,7 +28,7 @@ func ResourceStaticRoute() *schema.Resource {
|
||||
UpdateContext: resourceStaticRouteUpdate,
|
||||
DeleteContext: resourceStaticRouteDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: utils.ImportSiteAndID,
|
||||
StateContext: base.ImportSiteAndID,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
||||
176
internal/provider/settings/resource_setting_country.go
Normal file
176
internal/provider/settings/resource_setting_country.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/biter777/countries"
|
||||
"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/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"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{}
|
||||
)
|
||||
|
||||
type countryResource struct {
|
||||
client *base.Client
|
||||
}
|
||||
|
||||
func (c *countryResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
id, site := base.ImportIDWithSite(req, resp)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
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)...)
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
func (c *countryResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
MarkdownDescription: "The `unifi_setting_country` resource allows you to configure the country settings for your UniFi network. ",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": base.ID(),
|
||||
"site": base.SiteAttribute(),
|
||||
"code": schema.StringAttribute{
|
||||
Description: "The country code to set for the UniFi site. The country code must be a valid ISO 3166-1 alpha-2 code.",
|
||||
Required: true,
|
||||
Validators: []validator.String{
|
||||
validators.StringLengthExactly(2),
|
||||
validators.CountryCodeAlpha2(),
|
||||
},
|
||||
},
|
||||
"code_numeric": schema.Int32Attribute{
|
||||
Description: "The numeric representation in ISO 3166-1 of the country code.",
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
59
internal/provider/settings/resource_setting_country_test.go
Normal file
59
internal/provider/settings/resource_setting_country_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"github.com/filipowm/go-unifi/unifi"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSettingCountry_ProperCountryCodeMappingFromModel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
code string
|
||||
expectedNumericCode int
|
||||
}{
|
||||
{"Poland", "PL", 616},
|
||||
{"United States", "US", 840},
|
||||
{"Unknown", "WP", 0},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
model := countryModel{
|
||||
Code: types.StringValue(tc.code),
|
||||
}
|
||||
unifiModel := model.asUnifiModel()
|
||||
assert.Equal(t, tc.expectedNumericCode, unifiModel.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettingCountry_ProperCountryCodeMappingToModel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
numericCode int
|
||||
expectedCode string
|
||||
}{
|
||||
{"Poland", 616, "PL"},
|
||||
{"United States", 840, "US"},
|
||||
{"Unknown", 0, "Unknown"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
unifiModel := &unifi.SettingCountry{
|
||||
Code: tc.numericCode,
|
||||
}
|
||||
model := countryModel{}
|
||||
model.merge(unifiModel)
|
||||
assert.Equal(t, tc.expectedCode, model.Code.ValueString())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,8 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/utils"
|
||||
|
||||
"github.com/filipowm/go-unifi/unifi"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
@@ -34,9 +32,10 @@ func ResourceSettingMgmt() *schema.Resource {
|
||||
UpdateContext: resourceSettingMgmtUpdate,
|
||||
DeleteContext: resourceSettingMgmtDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: utils.ImportSiteAndID,
|
||||
StateContext: base.ImportSiteAndID,
|
||||
},
|
||||
|
||||
// TODO add more
|
||||
Schema: map[string]*schema.Schema{
|
||||
"id": {
|
||||
Description: "The unique identifier of the management settings configuration in the UniFi controller.",
|
||||
|
||||
@@ -4,10 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/utils"
|
||||
|
||||
"github.com/filipowm/go-unifi/unifi"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
|
||||
@@ -31,7 +29,7 @@ func ResourceSettingRadius() *schema.Resource {
|
||||
UpdateContext: resourceSettingRadiusUpdate,
|
||||
DeleteContext: schema.NoopContext,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: utils.ImportSiteAndID,
|
||||
StateContext: base.ImportSiteAndID,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
||||
@@ -42,7 +42,7 @@ func ResourceSettingUsg() *schema.Resource {
|
||||
UpdateContext: resourceSettingUsgLocker(resourceSettingUsgUpsert),
|
||||
DeleteContext: schema.NoopContext,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: utils.ImportSiteAndID,
|
||||
StateContext: base.ImportSiteAndID,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
||||
@@ -20,6 +20,21 @@ func MarkAccTest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func ImportStepWithSite(name string, ignore ...string) resource.TestStep {
|
||||
step := &resource.TestStep{
|
||||
ResourceName: name,
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
ImportStateIdFunc: SiteAndIDImportStateIDFunc(name),
|
||||
}
|
||||
|
||||
if len(ignore) > 0 {
|
||||
step.ImportStateVerifyIgnore = ignore
|
||||
}
|
||||
|
||||
return *step
|
||||
}
|
||||
|
||||
func ImportStep(name string, ignore ...string) resource.TestStep {
|
||||
step := resource.TestStep{
|
||||
ResourceName: name,
|
||||
@@ -69,8 +84,12 @@ func CheckPlanPreApply(checks ...plancheck.PlanCheck) resource.ConfigPlanChecks
|
||||
}
|
||||
}
|
||||
|
||||
func CheckResourceAction(resourceAddress string, action plancheck.ResourceActionType) resource.ConfigPlanChecks {
|
||||
return CheckPlanPreApply(plancheck.ExpectResourceAction(resourceAddress, action))
|
||||
func CheckResourceActions(resourceAddress string, actions ...plancheck.ResourceActionType) resource.ConfigPlanChecks {
|
||||
var checks []plancheck.PlanCheck
|
||||
for _, a := range actions {
|
||||
checks = append(checks, plancheck.ExpectResourceAction(resourceAddress, a))
|
||||
}
|
||||
return CheckPlanPreApply(checks...)
|
||||
}
|
||||
|
||||
func ComposeConfig(configs ...string) string {
|
||||
|
||||
@@ -35,7 +35,7 @@ func ResourceUser() *schema.Resource {
|
||||
UpdateContext: resourceUserUpdate,
|
||||
DeleteContext: resourceUserDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: utils.ImportSiteAndID,
|
||||
StateContext: base.ImportSiteAndID,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
||||
@@ -4,10 +4,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/utils"
|
||||
|
||||
"github.com/filipowm/go-unifi/unifi"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
)
|
||||
@@ -34,7 +32,7 @@ func ResourceUserGroup() *schema.Resource {
|
||||
UpdateContext: resourceUserGroupUpdate,
|
||||
DeleteContext: resourceUserGroupDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: utils.ImportSiteAndID,
|
||||
StateContext: base.ImportSiteAndID,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
|
||||
39
internal/provider/validators/country_code.go
Normal file
39
internal/provider/validators/country_code.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package validators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/biter777/countries"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
)
|
||||
|
||||
func CountryCodeAlpha2() validator.String {
|
||||
return countryCodeAlpha2Validator{}
|
||||
}
|
||||
|
||||
type countryCodeAlpha2Validator struct{}
|
||||
|
||||
func (c countryCodeAlpha2Validator) Description(_ context.Context) string {
|
||||
return "The country code must be a valid ISO 3166-1 alpha-2 code."
|
||||
}
|
||||
|
||||
func (c countryCodeAlpha2Validator) MarkdownDescription(ctx context.Context) string {
|
||||
return c.Description(ctx)
|
||||
}
|
||||
|
||||
func (c countryCodeAlpha2Validator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
code := req.ConfigValue
|
||||
if base.IsEmptyString(code) {
|
||||
return
|
||||
}
|
||||
|
||||
codeString := code.ValueString()
|
||||
if len(codeString) != 2 || countries.ByName(codeString) == countries.Unknown {
|
||||
resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
c.Description(ctx),
|
||||
codeString,
|
||||
))
|
||||
}
|
||||
}
|
||||
34
internal/provider/validators/country_code_test.go
Normal file
34
internal/provider/validators/country_code_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package validators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCountryCodeValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
code string
|
||||
validationFailed bool
|
||||
}{
|
||||
{"Poland", "PL", false},
|
||||
{"United States", "US", false},
|
||||
{"Empty", "", false},
|
||||
{"Too long", "ABC", true},
|
||||
{"Too short", "A", true},
|
||||
{"Unknown", "WP", true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
v := countryCodeAlpha2Validator{}
|
||||
req, resp := newStringValidatorRequestResponse(tc.code)
|
||||
v.ValidateString(context.Background(), req, resp)
|
||||
assert.Equal(t, tc.validationFailed, resp.Diagnostics.HasError())
|
||||
})
|
||||
}
|
||||
}
|
||||
19
internal/provider/validators/helpers_test.go
Normal file
19
internal/provider/validators/helpers_test.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package validators
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/diag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
func newStringValidatorRequestResponse(value string) (validator.StringRequest, *validator.StringResponse) {
|
||||
req := validator.StringRequest{
|
||||
ConfigValue: types.StringValue(value),
|
||||
Path: path.Empty(),
|
||||
}
|
||||
resp := validator.StringResponse{
|
||||
Diagnostics: []diag.Diagnostic{},
|
||||
}
|
||||
return req, &resp
|
||||
}
|
||||
57
internal/provider/validators/string_length_exactly.go
Normal file
57
internal/provider/validators/string_length_exactly.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package validators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
|
||||
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
|
||||
)
|
||||
|
||||
func StringLengthExactly(len int) validator.String {
|
||||
return stringLengthExactlyValidator{len: len}
|
||||
}
|
||||
|
||||
type stringLengthExactlyValidator struct {
|
||||
len int
|
||||
}
|
||||
|
||||
func (v stringLengthExactlyValidator) invalidUsageMessage() string {
|
||||
return "length cannot be less than zero"
|
||||
}
|
||||
|
||||
func (v stringLengthExactlyValidator) Description(_ context.Context) string {
|
||||
return fmt.Sprintf("string length must be exactly %d", v.len)
|
||||
}
|
||||
|
||||
func (v stringLengthExactlyValidator) MarkdownDescription(ctx context.Context) string {
|
||||
return v.Description(ctx)
|
||||
}
|
||||
|
||||
func (v stringLengthExactlyValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
|
||||
if v.len < 0 {
|
||||
resp.Diagnostics.Append(
|
||||
validatordiag.InvalidValidatorUsageDiagnostic(
|
||||
req.Path,
|
||||
"StringLengthExactly",
|
||||
v.invalidUsageMessage(),
|
||||
),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
value := req.ConfigValue
|
||||
if !base.IsDefined(value) {
|
||||
return
|
||||
}
|
||||
val := value.ValueString()
|
||||
if len(val) != v.len {
|
||||
resp.Diagnostics.Append(
|
||||
validatordiag.InvalidAttributeValueDiagnostic(
|
||||
req.Path,
|
||||
v.Description(ctx),
|
||||
fmt.Sprintf("%s (length: %d)", val, len(val)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
35
internal/provider/validators/string_length_exactly_test.go
Normal file
35
internal/provider/validators/string_length_exactly_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package validators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringLengthExactlyValidation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
value string
|
||||
length int
|
||||
validationFailed bool
|
||||
}{
|
||||
{"", 0, false},
|
||||
{"", 1, true},
|
||||
{"a", 0, true},
|
||||
{"a", 1, false},
|
||||
{"a", 2, true},
|
||||
{"ab", 2, false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("%s-expected-length-%d", tc.value, tc.length), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
v := StringLengthExactly(tc.length)
|
||||
req, resp := newStringValidatorRequestResponse(tc.value)
|
||||
v.ValidateString(context.Background(), req, resp)
|
||||
assert.Equal(t, tc.validationFailed, resp.Diagnostics.HasError())
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user