diff --git a/internal/provider/base/base.go b/internal/provider/base/base.go index 8e8c0d8..474d249 100644 --- a/internal/provider/base/base.go +++ b/internal/provider/base/base.go @@ -3,11 +3,8 @@ package base import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" - "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" ) @@ -44,20 +41,6 @@ type DatasourceModel interface { Merge(context.Context, interface{}) diag.Diagnostics } -type NestedObject interface { - AttributeTypes() map[string]attr.Type -} - -func ObjectNull(obj interface{}) (basetypes.ObjectValue, diag.Diagnostics) { - diags := diag.Diagnostics{} - if nested, ok := obj.(NestedObject); ok { - obj := types.ObjectNull(nested.AttributeTypes()) - return obj, diags - } - diags.AddError("Invalid object type", fmt.Sprintf("Expected NestedObject, got: %T", obj)) - return types.ObjectNull(map[string]attr.Type{}), diags -} - type Model struct { ID types.String `tfsdk:"id"` Site types.String `tfsdk:"site"` diff --git a/internal/provider/base/client.go b/internal/provider/base/client.go index 4100235..335f160 100644 --- a/internal/provider/base/client.go +++ b/internal/provider/base/client.go @@ -5,6 +5,8 @@ import ( "crypto/tls" "fmt" "github.com/filipowm/go-unifi/unifi" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" @@ -90,7 +92,7 @@ func (c *RetryableUnifiClient) relogin(err error) error { func (c *RetryableUnifiClient) Do(ctx context.Context, method string, apiPath string, reqBody interface{}, respBody interface{}) error { err := c.Client.Do(ctx, method, apiPath, reqBody, respBody) - if err != nil && IsServerErrorStatusCode(err, 401) { + if err != nil && utils.IsServerErrorStatusCode(err, 401) { err := c.relogin(err) if err != nil { return err @@ -107,7 +109,7 @@ type Client struct { } func (c *Client) ResolveSite(res SiteAware) string { - if res == nil || IsEmptyString(res.GetRawSite()) { + if res == nil || ut.IsEmptyString(res.GetRawSite()) { return c.Site } return res.GetSite() @@ -119,7 +121,7 @@ func (c *Client) ResolveSiteFromConfig(ctx context.Context, config tfsdk.Config) if diags.HasError() { return "", diags } - if IsEmptyString(site) { + if ut.IsEmptyString(site) { return c.Site, diags } return site.ValueString(), diags diff --git a/internal/provider/base/controller_version.go b/internal/provider/base/controller_version.go index 681a161..5bf10ae 100644 --- a/internal/provider/base/controller_version.go +++ b/internal/provider/base/controller_version.go @@ -3,6 +3,7 @@ package base import ( "context" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -29,18 +30,6 @@ var ( ControllerVersionWPA3 = AsVersion("6.1.61") ) -func (c *Client) IsControllerV6() bool { - return c.Version.GreaterThanOrEqual(ControllerV6) -} - -func (c *Client) IsControllerV7() bool { - return c.Version.GreaterThanOrEqual(ControllerV7) -} - -func (c *Client) IsControllerV9() bool { - return c.Version.GreaterThanOrEqual(ControllerV9) -} - func (c *Client) SupportsApiKeyAuthentication() bool { return c.Version.GreaterThanOrEqual(ControllerVersionApiKeyAuth) } @@ -149,7 +138,7 @@ func (v controllerVersionValidator) requireVersionForPath(req versionRequirement if diags.HasError() { return diags } - if !IsDefined(val) { + if !types.IsDefined(val) { return diags } diags.Append(v.requireVersion(req, &attrPath)...) diff --git a/internal/provider/base/features.go b/internal/provider/base/features.go index b219acb..d30be50 100644 --- a/internal/provider/base/features.go +++ b/internal/provider/base/features.go @@ -3,6 +3,7 @@ package base import ( "context" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "strings" "sync" @@ -119,7 +120,7 @@ func (v *featureEnabledValidator) RequireFeaturesEnabledForPath(ctx context.Cont if diags.HasError() { return diags } - if !IsDefined(val) { + if !types.IsDefined(val) { return diags } diags.Append(v.requireFeatures(ctx, site, &attrPath, features...)...) diff --git a/internal/provider/base/features_test.go b/internal/provider/base/features_test.go index d1fc140..d683fb1 100644 --- a/internal/provider/base/features_test.go +++ b/internal/provider/base/features_test.go @@ -3,6 +3,7 @@ package base import ( "context" "errors" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "sync" "testing" @@ -574,7 +575,7 @@ func TestIsDefined(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.expected, IsDefined(tt.value)) + assert.Equal(t, tt.expected, ut.IsDefined(tt.value)) }) } } @@ -640,7 +641,7 @@ func (v *testFeatureValidator) TestRequireFeaturesEnabledForPath(ctx context.Con return diags } - if !IsDefined(v.attrValue) { + if !ut.IsDefined(v.attrValue) { return diags } diff --git a/internal/provider/base/importer.go b/internal/provider/base/importer.go index 3a1e679..b59f8fd 100644 --- a/internal/provider/base/importer.go +++ b/internal/provider/base/importer.go @@ -7,6 +7,7 @@ import ( "strings" ) +// for Terraform Plugin SDK v2 func ImportSiteAndID(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) { if id := d.Id(); strings.Contains(id, ":") { importParts := strings.SplitN(id, ":", 2) @@ -16,6 +17,7 @@ func ImportSiteAndID(_ context.Context, d *schema.ResourceData, _ interface{}) ( return []*schema.ResourceData{d}, nil } +// for Terraform Plugin Framework func ImportIDWithSite(req resource.ImportStateRequest, resp *resource.ImportStateResponse) (string, string) { id := req.ID if id == "" { diff --git a/internal/provider/device/resource_device.go b/internal/provider/device/resource_device.go index b2b4cfe..3564222 100644 --- a/internal/provider/device/resource_device.go +++ b/internal/provider/device/resource_device.go @@ -4,14 +4,13 @@ import ( "context" "errors" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" "strconv" "strings" "time" - "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/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -316,7 +315,7 @@ func resourceDeviceDelete(ctx context.Context, d *schema.ResourceData, meta inte if internalErr == nil { return nil } - if base.IsServerErrorContains(internalErr, "api.err.DeviceBusy") { + if utils.IsServerErrorContains(internalErr, "api.err.DeviceBusy") { return retry.RetryableError(internalErr) } return retry.NonRetryableError(internalErr) diff --git a/internal/provider/device/resource_port_profile.go b/internal/provider/device/resource_port_profile.go index c30bbfb..6868623 100644 --- a/internal/provider/device/resource_port_profile.go +++ b/internal/provider/device/resource_port_profile.go @@ -3,11 +3,10 @@ package device import ( "context" "errors" - - "github.com/filipowm/terraform-provider-unifi/internal/provider/base" - "github.com/filipowm/terraform-provider-unifi/internal/utils" + "github.com/filipowm/terraform-provider-unifi/internal/provider/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" diff --git a/internal/provider/dns/datasource_dns_record.go b/internal/provider/dns/datasource_dns_record.go index e134eac..0c87129 100644 --- a/internal/provider/dns/datasource_dns_record.go +++ b/internal/provider/dns/datasource_dns_record.go @@ -3,10 +3,10 @@ package dns import ( "context" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" - "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/hashicorp/terraform-plugin-framework-validators/datasourcevalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" diff --git a/internal/provider/dns/datasource_dns_records.go b/internal/provider/dns/datasource_dns_records.go index ee25535..bfaa095 100644 --- a/internal/provider/dns/datasource_dns_records.go +++ b/internal/provider/dns/datasource_dns_records.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/types" @@ -49,7 +50,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(), + "site": ut.SiteAttribute(), "result": schema.ListNestedAttribute{ Description: "The list of DNS records.", Computed: true, diff --git a/internal/provider/dns/dns_record_model.go b/internal/provider/dns/dns_record_model.go index 6d6c808..ad15fac 100644 --- a/internal/provider/dns/dns_record_model.go +++ b/internal/provider/dns/dns_record_model.go @@ -4,6 +4,7 @@ import ( "context" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -46,8 +47,8 @@ func (b *dnsRecordsDatasourceModel) SetSite(site string) { } var dnsRecordDatasourceAttributes = map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "name": schema.StringAttribute{ Description: "DNS record name.", Optional: true, diff --git a/internal/provider/dns/resource_dns_record.go b/internal/provider/dns/resource_dns_record.go index efd4c05..224e6c1 100644 --- a/internal/provider/dns/resource_dns_record.go +++ b/internal/provider/dns/resource_dns_record.go @@ -4,6 +4,7 @@ import ( "context" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "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" @@ -78,8 +79,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": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "name": schema.StringAttribute{ MarkdownDescription: "DNS record name.", Required: true, diff --git a/internal/provider/firewall/datasource_firewall_zone.go b/internal/provider/firewall/datasource_firewall_zone.go index e8a74ea..702d12b 100644 --- a/internal/provider/firewall/datasource_firewall_zone.go +++ b/internal/provider/firewall/datasource_firewall_zone.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" @@ -52,8 +53,8 @@ func (d *firewallZoneDatasource) Schema(_ context.Context, _ datasource.SchemaRe resp.Schema = schema.Schema{ MarkdownDescription: "The `unifi_firewall_zone` datsources allows retrieving existing firewall zone details from the UniFi controller by the zone name.", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "name": schema.StringAttribute{ MarkdownDescription: "The name of the firewall zone.", Required: true, diff --git a/internal/provider/firewall/resource_firewall_group.go b/internal/provider/firewall/resource_firewall_group.go index cfeed4b..3f7f8f8 100644 --- a/internal/provider/firewall/resource_firewall_group.go +++ b/internal/provider/firewall/resource_firewall_group.go @@ -3,11 +3,10 @@ package firewall import ( "context" "errors" - - "github.com/filipowm/terraform-provider-unifi/internal/provider/base" - "github.com/filipowm/terraform-provider-unifi/internal/utils" + "github.com/filipowm/terraform-provider-unifi/internal/provider/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" @@ -90,7 +89,7 @@ func resourceFirewallGroupCreate(ctx context.Context, d *schema.ResourceData, me resp, err := c.CreateFirewallGroup(ctx, site, req) if err != nil { - if base.IsServerErrorContains(err, "api.err.FirewallGroupExisted") { + if utils.IsServerErrorContains(err, "api.err.FirewallGroupExisted") { return diag.Errorf("firewall groups must have unique names: %s", err) } return diag.FromErr(err) diff --git a/internal/provider/firewall/resource_firewall_rule.go b/internal/provider/firewall/resource_firewall_rule.go index 9bc956d..2b58e14 100644 --- a/internal/provider/firewall/resource_firewall_rule.go +++ b/internal/provider/firewall/resource_firewall_rule.go @@ -3,12 +3,12 @@ package firewall import ( "context" "errors" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" + "github.com/filipowm/terraform-provider-unifi/internal/provider/validators" "regexp" - "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" @@ -184,7 +184,7 @@ func ResourceFirewallRule() *schema.Resource { " * A list of ports/ranges separated by commas", Type: schema.TypeString, Optional: true, - ValidateFunc: utils.ValidatePortRange, + ValidateFunc: validators.PortRangeV2, }, "src_mac": { Description: "The source MAC address this rule applies to. Use this to create rules that match specific devices " + @@ -235,7 +235,7 @@ func ResourceFirewallRule() *schema.Resource { " * A list of ports/ranges separated by commas", Type: schema.TypeString, Optional: true, - ValidateFunc: utils.ValidatePortRange, + ValidateFunc: validators.PortRangeV2, }, // advanced @@ -293,7 +293,7 @@ func resourceFirewallRuleCreate(ctx context.Context, d *schema.ResourceData, met resp, err := c.CreateFirewallRule(ctx, site, req) if err != nil { - if base.IsServerErrorContains(err, "api.err.FirewallGroupTypeExists") { + if utils.IsServerErrorContains(err, "api.err.FirewallGroupTypeExists") { return diag.Errorf("firewall rule groups must be of different group types (ie. a port group and address group): %s", err) } diff --git a/internal/provider/firewall/resource_firewall_zone.go b/internal/provider/firewall/resource_firewall_zone.go index d73dcf2..ddcffd4 100644 --- a/internal/provider/firewall/resource_firewall_zone.go +++ b/internal/provider/firewall/resource_firewall_zone.go @@ -3,11 +3,11 @@ package firewall import ( "context" "github.com/filipowm/go-unifi/unifi/features" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" - "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/types" @@ -33,7 +33,7 @@ func (m *firewallZoneModel) AsUnifiModel(_ context.Context) (interface{}, diag.D diags := diag.Diagnostics{} var networkIDs []string - diags.Append(utils.ListElementsAs(m.Networks, &networkIDs)...) + diags.Append(ut.ListElementsAs(m.Networks, &networkIDs)...) if diags.HasError() { return nil, diags } @@ -111,8 +111,8 @@ func (r *firewallZoneResource) Schema(_ context.Context, _ resource.SchemaReques "This resource allows you to create, update, and delete firewall zones.", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "name": schema.StringAttribute{ MarkdownDescription: "The name of the firewall zone.", Required: true, @@ -122,7 +122,7 @@ func (r *firewallZoneResource) Schema(_ context.Context, _ resource.SchemaReques Optional: true, Computed: true, ElementType: types.StringType, - Default: utils.DefaultEmptyList(types.StringType), + Default: ut.DefaultEmptyList(types.StringType), }, }, } diff --git a/internal/provider/network/data_network.go b/internal/provider/network/data_network.go index 0b910e0..48aa005 100644 --- a/internal/provider/network/data_network.go +++ b/internal/provider/network/data_network.go @@ -2,10 +2,9 @@ package network import ( "context" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" - "github.com/filipowm/terraform-provider-unifi/internal/utils" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) diff --git a/internal/provider/network/resource_network.go b/internal/provider/network/resource_network.go index 415e6f3..80369b0 100644 --- a/internal/provider/network/resource_network.go +++ b/internal/provider/network/resource_network.go @@ -4,13 +4,12 @@ import ( "context" "errors" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" "regexp" "strings" - "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" diff --git a/internal/provider/network/resource_wlan.go b/internal/provider/network/resource_wlan.go index ce40f1e..f9c6509 100644 --- a/internal/provider/network/resource_wlan.go +++ b/internal/provider/network/resource_wlan.go @@ -4,11 +4,10 @@ import ( "context" "errors" "fmt" - - "github.com/filipowm/terraform-provider-unifi/internal/provider/base" - "github.com/filipowm/terraform-provider-unifi/internal/utils" + "github.com/filipowm/terraform-provider-unifi/internal/provider/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" diff --git a/internal/provider/provider_v2.go b/internal/provider/provider_v2.go index 38f123f..0ab4109 100644 --- a/internal/provider/provider_v2.go +++ b/internal/provider/provider_v2.go @@ -6,8 +6,8 @@ import ( "github.com/filipowm/terraform-provider-unifi/internal/provider/dns" "github.com/filipowm/terraform-provider-unifi/internal/provider/firewall" "github.com/filipowm/terraform-provider-unifi/internal/provider/settings" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" "github.com/filipowm/terraform-provider-unifi/internal/provider/validators" - "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/path" diff --git a/internal/provider/routing/resource_port_forward.go b/internal/provider/routing/resource_port_forward.go index 94127eb..54b2aca 100644 --- a/internal/provider/routing/resource_port_forward.go +++ b/internal/provider/routing/resource_port_forward.go @@ -3,11 +3,11 @@ package routing import ( "context" "errors" - - "github.com/filipowm/terraform-provider-unifi/internal/provider/base" - "github.com/filipowm/terraform-provider-unifi/internal/utils" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" + "github.com/filipowm/terraform-provider-unifi/internal/provider/validators" "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" @@ -48,7 +48,7 @@ func ResourcePortForward() *schema.Resource { Description: "The external port(s) that will be forwarded. Can be a single port (e.g., '80') or a port range (e.g., '8080:8090').", Type: schema.TypeString, Optional: true, - ValidateFunc: utils.ValidatePortRange, + ValidateFunc: validators.PortRangeV2, }, // TODO: remove this, disabled rules should just be deleted. "enabled": { @@ -69,7 +69,7 @@ func ResourcePortForward() *schema.Resource { Description: "The internal port(s) that will receive the forwarded traffic. Can be a single port (e.g., '8080') or a port range (e.g., '8080:8090').", Type: schema.TypeString, Optional: true, - ValidateFunc: utils.ValidatePortRange, + ValidateFunc: validators.PortRangeV2, }, "log": { Description: "Enable logging of traffic matching this port forwarding rule. Useful for monitoring and troubleshooting.", diff --git a/internal/provider/routing/resource_static_route.go b/internal/provider/routing/resource_static_route.go index 03627a8..5a09e50 100644 --- a/internal/provider/routing/resource_static_route.go +++ b/internal/provider/routing/resource_static_route.go @@ -4,10 +4,9 @@ import ( "context" "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/filipowm/terraform-provider-unifi/internal/provider/utils" "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" diff --git a/internal/provider/settings/resource_setting_auto_speedtest.go b/internal/provider/settings/resource_setting_auto_speedtest.go index 2d1d191..dae3a96 100644 --- a/internal/provider/settings/resource_setting_auto_speedtest.go +++ b/internal/provider/settings/resource_setting_auto_speedtest.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -46,7 +48,7 @@ type autoSpeedtestResource struct { } func checkAutoSpeedtestUnsupportedError(err error) error { - if base.IsServerErrorContains(err, "api.err.SpeedTestNotSupported") { + if utils.IsServerErrorContains(err, "api.err.SpeedTestNotSupported") { return fmt.Errorf("Auto Speedtest is not supported on this controller") } return err @@ -81,8 +83,8 @@ func (a *autoSpeedtestResource) Schema(_ context.Context, _ resource.SchemaReque "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(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "cron": schema.StringAttribute{ MarkdownDescription: "Cron expression defining the schedule for automatic speedtests.", Optional: true, diff --git a/internal/provider/settings/resource_setting_country.go b/internal/provider/settings/resource_setting_country.go index 09db483..317a13e 100644 --- a/internal/provider/settings/resource_setting_country.go +++ b/internal/provider/settings/resource_setting_country.go @@ -5,6 +5,7 @@ import ( "github.com/biter777/countries" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/filipowm/terraform-provider-unifi/internal/provider/validators" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -67,8 +68,8 @@ func (c *countryResource) Schema(_ context.Context, _ resource.SchemaRequest, re 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(), + "id": ut.ID(), + "site": ut.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, diff --git a/internal/provider/settings/resource_setting_dpi.go b/internal/provider/settings/resource_setting_dpi.go index 62e8d00..8ad48e9 100644 --- a/internal/provider/settings/resource_setting_dpi.go +++ b/internal/provider/settings/resource_setting_dpi.go @@ -2,6 +2,7 @@ package settings import ( "context" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" @@ -60,8 +61,8 @@ func (r *dpiResource) Schema(_ context.Context, _ resource.SchemaRequest, resp * resp.Schema = schema.Schema{ MarkdownDescription: "Manages Deep Packet Inspection (DPI) settings for a UniFi site. DPI is a feature that allows the UniFi controller to analyze network traffic and identify applications and services being used on the network.", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "enabled": schema.BoolAttribute{ MarkdownDescription: "Whether Deep Packet Inspection is enabled.", Required: true, diff --git a/internal/provider/settings/resource_setting_guest_access.go b/internal/provider/settings/resource_setting_guest_access.go index 13a380d..3a50375 100644 --- a/internal/provider/settings/resource_setting_guest_access.go +++ b/internal/provider/settings/resource_setting_guest_access.go @@ -3,7 +3,7 @@ package settings import ( "context" "fmt" - "github.com/filipowm/terraform-provider-unifi/internal/utils" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" @@ -355,7 +355,7 @@ func (d *guestAccessModel) AsUnifiModel(ctx context.Context) (interface{}, diag. VoucherCustomized: d.VoucherCustomized.ValueBool(), VoucherEnabled: d.VoucherEnabled.ValueBool(), } - if base.IsEmptyString(d.Password) { + if ut.IsEmptyString(d.Password) { model.PasswordEnabled = false } else { model.PasswordEnabled = true @@ -365,7 +365,7 @@ func (d *guestAccessModel) AsUnifiModel(ctx context.Context) (interface{}, diag. if diags.HasError() { return nil, diags } - if base.IsDefined(d.Redirect) { + if ut.IsDefined(d.Redirect) { var redirect *redirectModel diags.Append(d.Redirect.As(ctx, &redirect, basetypes.ObjectAsOptions{})...) if diags.HasError() { @@ -379,7 +379,7 @@ func (d *guestAccessModel) AsUnifiModel(ctx context.Context) (interface{}, diag. model.RedirectEnabled = false } - if base.IsDefined(d.Facebook) { + if ut.IsDefined(d.Facebook) { var facebook *facebookModel diags.Append(d.Facebook.As(ctx, &facebook, basetypes.ObjectAsOptions{})...) if diags.HasError() { @@ -393,7 +393,7 @@ func (d *guestAccessModel) AsUnifiModel(ctx context.Context) (interface{}, diag. model.FacebookEnabled = false } - if base.IsDefined(d.Google) { + if ut.IsDefined(d.Google) { var google *googleModel diags.Append(d.Google.As(ctx, &google, basetypes.ObjectAsOptions{})...) if diags.HasError() { @@ -408,7 +408,7 @@ func (d *guestAccessModel) AsUnifiModel(ctx context.Context) (interface{}, diag. model.GoogleEnabled = false } - if base.IsDefined(d.Radius) { + if ut.IsDefined(d.Radius) { var radius *radiusModel diags.Append(d.Radius.As(ctx, &radius, basetypes.ObjectAsOptions{})...) if diags.HasError() { @@ -423,7 +423,7 @@ func (d *guestAccessModel) AsUnifiModel(ctx context.Context) (interface{}, diag. model.RADIUSEnabled = false } - if base.IsDefined(d.Wechat) { + if ut.IsDefined(d.Wechat) { var wechat *wechatModel diags.Append(d.Wechat.As(ctx, &wechat, basetypes.ObjectAsOptions{})...) if diags.HasError() { @@ -438,7 +438,7 @@ func (d *guestAccessModel) AsUnifiModel(ctx context.Context) (interface{}, diag. model.WechatEnabled = false } - if base.IsDefined(d.FacebookWifi) { + if ut.IsDefined(d.FacebookWifi) { var facebookWifi *facebookWifiModel diags.Append(d.FacebookWifi.As(ctx, &facebookWifi, basetypes.ObjectAsOptions{})...) if diags.HasError() { @@ -450,9 +450,9 @@ func (d *guestAccessModel) AsUnifiModel(ctx context.Context) (interface{}, diag. model.XFacebookWifiGwSecret = facebookWifi.GwSecret.ValueString() } - if base.IsDefined(d.RestrictedDNSServers) { + if ut.IsDefined(d.RestrictedDNSServers) { var servers []string - diags := utils.ListElementsAs(d.RestrictedDNSServers, &servers) + diags.Append(ut.ListElementsAs(d.RestrictedDNSServers, &servers)...) if diags.HasError() { return nil, diags } @@ -464,14 +464,14 @@ func (d *guestAccessModel) AsUnifiModel(ctx context.Context) (interface{}, diag. model.RestrictedDNSEnabled = false } - if base.IsDefined(d.PortalCustomization) { + if ut.IsDefined(d.PortalCustomization) { var portalCustomization *portalCustomizationModel diags.Append(d.PortalCustomization.As(ctx, &portalCustomization, basetypes.ObjectAsOptions{})...) if diags.HasError() { return nil, diags } var languages []string - diags := utils.ListElementsAs(portalCustomization.Languages, &languages) + diags := ut.ListElementsAs(portalCustomization.Languages, &languages) if diags.HasError() { return nil, diags } @@ -511,7 +511,7 @@ func (d *guestAccessModel) AsUnifiModel(ctx context.Context) (interface{}, diag. func (d *guestAccessModel) paymentAsUnifiModel(ctx context.Context, model *unifi.SettingGuestAccess) diag.Diagnostics { diags := diag.Diagnostics{} - if base.IsEmptyString(d.PaymentGateway) { + if ut.IsEmptyString(d.PaymentGateway) { model.PaymentEnabled = false } else { gateway := d.PaymentGateway.ValueString() @@ -524,7 +524,7 @@ func (d *guestAccessModel) paymentAsUnifiModel(ctx context.Context, model *unifi if diags.HasError() { return diags } - if base.IsDefined(authorize.UseSandbox) { + if ut.IsDefined(authorize.UseSandbox) { model.AuthorizeUseSandbox = authorize.UseSandbox.ValueBool() } model.XAuthorizeLoginid = authorize.LoginID.ValueString() @@ -535,7 +535,7 @@ func (d *guestAccessModel) paymentAsUnifiModel(ctx context.Context, model *unifi if diags.HasError() { return diags } - if base.IsDefined(ippay.UseSandbox) { + if ut.IsDefined(ippay.UseSandbox) { model.IPpayUseSandbox = ippay.UseSandbox.ValueBool() } model.XIPpayTerminalid = ippay.TerminalID.ValueString() @@ -545,7 +545,7 @@ func (d *guestAccessModel) paymentAsUnifiModel(ctx context.Context, model *unifi if diags.HasError() { return diags } - if base.IsDefined(merchantWarrior.UseSandbox) { + if ut.IsDefined(merchantWarrior.UseSandbox) { model.MerchantwarriorUseSandbox = merchantWarrior.UseSandbox.ValueBool() } model.XMerchantwarriorApikey = merchantWarrior.ApiKey.ValueString() @@ -557,7 +557,7 @@ func (d *guestAccessModel) paymentAsUnifiModel(ctx context.Context, model *unifi if diags.HasError() { return diags } - if base.IsDefined(paypal.UseSandbox) { + if ut.IsDefined(paypal.UseSandbox) { model.PaypalUseSandbox = paypal.UseSandbox.ValueBool() } model.XPaypalPassword = paypal.Password.ValueString() @@ -569,7 +569,7 @@ func (d *guestAccessModel) paymentAsUnifiModel(ctx context.Context, model *unifi if diags.HasError() { return diags } - if base.IsDefined(quickpay.UseSandbox) { + if ut.IsDefined(quickpay.UseSandbox) { model.QuickpayTestmode = quickpay.UseSandbox.ValueBool() } model.XQuickpayAgreementid = quickpay.AgreementID.ValueString() @@ -667,17 +667,17 @@ func (d *guestAccessModel) Merge(ctx context.Context, unifiModel interface{}) di d.PaymentEnabled = types.BoolValue(model.PaymentEnabled) var od diag.Diagnostics - d.Authorize, od = base.ObjectNull(&authorizeModel{}) + d.Authorize, od = ut.ObjectNull(&authorizeModel{}) diags.Append(od...) - d.Paypal, od = base.ObjectNull(&paypalModel{}) + d.Paypal, od = ut.ObjectNull(&paypalModel{}) diags.Append(od...) - d.IPpay, od = base.ObjectNull(&ipPayModel{}) + d.IPpay, od = ut.ObjectNull(&ipPayModel{}) diags.Append(od...) - d.MerchantWarrior, od = base.ObjectNull(&merchantWarriorModel{}) + d.MerchantWarrior, od = ut.ObjectNull(&merchantWarriorModel{}) diags.Append(od...) - d.Quickpay, od = base.ObjectNull(&quickpayModel{}) + d.Quickpay, od = ut.ObjectNull(&quickpayModel{}) diags.Append(od...) - d.Stripe, od = base.ObjectNull(&stripeModel{}) + d.Stripe, od = ut.ObjectNull(&stripeModel{}) diags.Append(od...) if diags.HasError() { return diags @@ -697,7 +697,7 @@ func (d *guestAccessModel) Merge(ctx context.Context, unifiModel interface{}) di } d.RedirectEnabled = types.BoolValue(model.RedirectEnabled) - d.Redirect, diags = base.ObjectNull(&redirectModel{}) + d.Redirect, diags = ut.ObjectNull(&redirectModel{}) if diags.HasError() { return diags } @@ -714,7 +714,7 @@ func (d *guestAccessModel) Merge(ctx context.Context, unifiModel interface{}) di } d.FacebookEnabled = types.BoolValue(model.FacebookEnabled) - d.Facebook, diags = base.ObjectNull(&facebookModel{}) + d.Facebook, diags = ut.ObjectNull(&facebookModel{}) if diags.HasError() { return diags } @@ -731,7 +731,7 @@ func (d *guestAccessModel) Merge(ctx context.Context, unifiModel interface{}) di } d.GoogleEnabled = types.BoolValue(model.GoogleEnabled) - d.Google, diags = base.ObjectNull(&googleModel{}) + d.Google, diags = ut.ObjectNull(&googleModel{}) if diags.HasError() { return diags } @@ -749,7 +749,7 @@ func (d *guestAccessModel) Merge(ctx context.Context, unifiModel interface{}) di } d.RadiusEnabled = types.BoolValue(model.RADIUSEnabled) - d.Radius, diags = base.ObjectNull(&radiusModel{}) + d.Radius, diags = ut.ObjectNull(&radiusModel{}) if diags.HasError() { return diags } @@ -767,7 +767,7 @@ func (d *guestAccessModel) Merge(ctx context.Context, unifiModel interface{}) di } d.WechatEnabled = types.BoolValue(model.WechatEnabled) - d.Wechat, diags = base.ObjectNull(&wechatModel{}) + d.Wechat, diags = ut.ObjectNull(&wechatModel{}) if diags.HasError() { return diags } @@ -784,7 +784,7 @@ func (d *guestAccessModel) Merge(ctx context.Context, unifiModel interface{}) di } } - d.FacebookWifi, diags = base.ObjectNull(&facebookWifiModel{}) + d.FacebookWifi, diags = ut.ObjectNull(&facebookWifiModel{}) if diags.HasError() { return diags } @@ -808,7 +808,7 @@ func (d *guestAccessModel) Merge(ctx context.Context, unifiModel interface{}) di return diags } } else { - d.RestrictedDNSServers = utils.EmptyList(types.StringType) + d.RestrictedDNSServers = ut.EmptyList(types.StringType) } languages, diags := types.ListValueFrom(ctx, types.StringType, model.PortalCustomizedLanguages) @@ -950,8 +950,8 @@ func (g *guestAccessResource) Schema(_ context.Context, _ resource.SchemaRequest resp.Schema = schema.Schema{ MarkdownDescription: "The `unifi_setting_guest_access` resource manages the guest access settings in the UniFi controller.\n\nThis resource allows you to configure all aspects of guest network access including authentication methods, portal customization, and payment options.", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "allowed_subnet": schema.StringAttribute{ MarkdownDescription: "Subnet allowed for guest access.", Optional: true, @@ -1194,7 +1194,7 @@ func (g *guestAccessResource) Schema(_ context.Context, _ resource.SchemaRequest Required: true, Sensitive: true, Validators: []validator.String{ - validators.Email(), + validators.Email, }, }, }, @@ -1248,7 +1248,7 @@ func (g *guestAccessResource) Schema(_ context.Context, _ resource.SchemaRequest Optional: true, Computed: true, Validators: []validator.String{ - validators.HexColor(), + validators.HexColor, }, }, "bg_image_tile": schema.BoolAttribute{ @@ -1272,7 +1272,7 @@ func (g *guestAccessResource) Schema(_ context.Context, _ resource.SchemaRequest Optional: true, Computed: true, Validators: []validator.String{ - validators.HexColor(), + validators.HexColor, }, }, "box_link_color": schema.StringAttribute{ @@ -1280,7 +1280,7 @@ func (g *guestAccessResource) Schema(_ context.Context, _ resource.SchemaRequest Optional: true, Computed: true, Validators: []validator.String{ - validators.HexColor(), + validators.HexColor, }, }, "box_opacity": schema.Int32Attribute{ @@ -1304,7 +1304,7 @@ func (g *guestAccessResource) Schema(_ context.Context, _ resource.SchemaRequest Optional: true, Computed: true, Validators: []validator.String{ - validators.HexColor(), + validators.HexColor, }, }, "button_color": schema.StringAttribute{ @@ -1312,7 +1312,7 @@ func (g *guestAccessResource) Schema(_ context.Context, _ resource.SchemaRequest Optional: true, Computed: true, Validators: []validator.String{ - validators.HexColor(), + validators.HexColor, }, }, "button_text": schema.StringAttribute{ @@ -1325,7 +1325,7 @@ func (g *guestAccessResource) Schema(_ context.Context, _ resource.SchemaRequest Optional: true, Computed: true, Validators: []validator.String{ - validators.HexColor(), + validators.HexColor, }, }, "languages": schema.ListAttribute{ @@ -1339,7 +1339,7 @@ func (g *guestAccessResource) Schema(_ context.Context, _ resource.SchemaRequest Optional: true, Computed: true, Validators: []validator.String{ - validators.HexColor(), + validators.HexColor, }, }, "logo_position": schema.StringAttribute{ @@ -1368,7 +1368,7 @@ func (g *guestAccessResource) Schema(_ context.Context, _ resource.SchemaRequest Optional: true, Computed: true, Validators: []validator.String{ - validators.HexColor(), + validators.HexColor, }, }, "title": schema.StringAttribute{ @@ -1534,7 +1534,7 @@ func (g *guestAccessResource) Schema(_ context.Context, _ resource.SchemaRequest Optional: true, Computed: true, ElementType: types.StringType, - Default: listdefault.StaticValue(utils.EmptyList(types.StringType)), + Default: listdefault.StaticValue(ut.EmptyList(types.StringType)), Validators: []validator.List{ listvalidator.ValueStringsAre(validators.IPv4()), }, diff --git a/internal/provider/settings/resource_setting_ips.go b/internal/provider/settings/resource_setting_ips.go index d4f22d6..2a81d49 100644 --- a/internal/provider/settings/resource_setting_ips.go +++ b/internal/provider/settings/resource_setting_ips.go @@ -5,8 +5,9 @@ import ( "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/go-unifi/unifi/features" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" "github.com/filipowm/terraform-provider-unifi/internal/provider/validators" - "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -185,23 +186,23 @@ func (d *ipsModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnost } var enabledCategories []string - diags.Append(utils.ListElementsAs(d.EnabledCategories, &enabledCategories)...) + diags.Append(ut.ListElementsAs(d.EnabledCategories, &enabledCategories)...) if diags.HasError() { return nil, diags } model.EnabledCategories = enabledCategories var enabledNetworks []string - diags.Append(utils.ListElementsAs(d.EnabledNetworks, &enabledNetworks)...) + diags.Append(ut.ListElementsAs(d.EnabledNetworks, &enabledNetworks)...) if diags.HasError() { return nil, diags } model.EnabledNetworks = enabledNetworks // Handle AdBlockedNetworks - if any networks are configured, set AdBlockingEnabled to true - if base.IsDefined(d.AdBlockedNetworks) { + if ut.IsDefined(d.AdBlockedNetworks) { var adBlockedNetworks []string - diags.Append(utils.ListElementsAs(d.AdBlockedNetworks, &adBlockedNetworks)...) + diags.Append(ut.ListElementsAs(d.AdBlockedNetworks, &adBlockedNetworks)...) if diags.HasError() { return nil, diags } @@ -221,9 +222,9 @@ func (d *ipsModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnost } // Handle DNSFilters - if any filters are configured, set DNSFiltering to true - if base.IsDefined(d.DNSFilters) { + if ut.IsDefined(d.DNSFilters) { var dnsFiltersObjects []DNSFilterModel - diags.Append(utils.ListElementsAs(d.DNSFilters, &dnsFiltersObjects)...) + diags.Append(ut.ListElementsAs(d.DNSFilters, &dnsFiltersObjects)...) if diags.HasError() { return nil, diags } @@ -248,9 +249,9 @@ func (d *ipsModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnost // Handle allowed sites var allowedSites, blockedSites, blockedTlds []string - diags.Append(utils.ListElementsAs(filterObj.AllowedSites, &allowedSites)...) - diags.Append(utils.ListElementsAs(filterObj.BlockedSites, &blockedSites)...) - diags.Append(utils.ListElementsAs(filterObj.BlockedTld, &blockedTlds)...) + diags.Append(ut.ListElementsAs(filterObj.AllowedSites, &allowedSites)...) + diags.Append(ut.ListElementsAs(filterObj.BlockedSites, &blockedSites)...) + diags.Append(ut.ListElementsAs(filterObj.BlockedTld, &blockedTlds)...) if diags.HasError() { return nil, diags } @@ -266,9 +267,9 @@ func (d *ipsModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnost } // Handle honeypot - if base.IsDefined(d.Honeypots) { + if ut.IsDefined(d.Honeypots) { var honeypotObjects []HoneypotModel - diags.Append(utils.ListElementsAs(d.Honeypots, &honeypotObjects)...) + diags.Append(ut.ListElementsAs(d.Honeypots, &honeypotObjects)...) if diags.HasError() { return nil, diags } @@ -293,7 +294,7 @@ func (d *ipsModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnost } // Handle suppression - if base.IsDefined(d.Suppression) { + if ut.IsDefined(d.Suppression) { var suppressionObj SuppressionModel diags.Append(d.Suppression.As(ctx, &suppressionObj, basetypes.ObjectAsOptions{})...) if diags.HasError() { @@ -301,7 +302,7 @@ func (d *ipsModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnost } var alerts []AlertsModel - diags.Append(utils.ListElementsAs(suppressionObj.Alerts, &alerts)...) + diags.Append(ut.ListElementsAs(suppressionObj.Alerts, &alerts)...) if diags.HasError() { return nil, diags } @@ -317,13 +318,13 @@ func (d *ipsModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnost // Handle tracking var trackings []TrackingModel - diags.Append(utils.ListElementsAs(alertObj.Tracking, &trackings)...) + diags.Append(ut.ListElementsAs(alertObj.Tracking, &trackings)...) if diags.HasError() { return nil, diags } alert.Tracking = make([]unifi.SettingIpsTracking, 0) for _, trackingObj := range trackings { - if base.IsEmptyString(trackingObj.Direction) || base.IsEmptyString(trackingObj.Mode) || base.IsEmptyString(trackingObj.Value) { + if ut.IsEmptyString(trackingObj.Direction) || ut.IsEmptyString(trackingObj.Mode) || ut.IsEmptyString(trackingObj.Value) { continue } alert.Tracking = append(alert.Tracking, unifi.SettingIpsTracking{ @@ -335,7 +336,7 @@ func (d *ipsModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnost } var whitelists []WhitelistModel - diags.Append(utils.ListElementsAs(suppressionObj.Whitelist, &whitelists)...) + diags.Append(ut.ListElementsAs(suppressionObj.Whitelist, &whitelists)...) if diags.HasError() { return nil, diags } @@ -379,10 +380,10 @@ func (d *ipsModel) Merge(ctx context.Context, other interface{}) diag.Diagnostic if diags.HasError() { return diags } - if base.IsDefined(enabledCategoriesList) { + if ut.IsDefined(enabledCategoriesList) { d.EnabledCategories = enabledCategoriesList } else { - d.EnabledCategories = utils.EmptyList(types.StringType) + d.EnabledCategories = ut.EmptyList(types.StringType) } // Handle enabled networks @@ -390,10 +391,10 @@ func (d *ipsModel) Merge(ctx context.Context, other interface{}) diag.Diagnostic if diags.HasError() { return diags } - if base.IsDefined(enabledNetworksList) { + if ut.IsDefined(enabledNetworksList) { d.EnabledNetworks = enabledNetworksList } else { - d.EnabledNetworks = utils.EmptyList(types.StringType) + d.EnabledNetworks = ut.EmptyList(types.StringType) } //Handle AdBlockedNetworks - extract network IDs from AdBlockingConfigurations @@ -561,8 +562,8 @@ func (r *ipsResource) Schema(_ context.Context, _ resource.SchemaRequest, resp * resp.Schema = schema.Schema{ MarkdownDescription: "The `unifi_setting_ips` resource allows you to configure the Intrusion Prevention System (IPS) settings for your UniFi network. IPS provides network threat protection by monitoring, detecting, and preventing malicious traffic based on configured rules and policies. Requires controller version 7.4 or later", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "ad_blocked_networks": schema.ListAttribute{ MarkdownDescription: "List of network IDs to enable ad blocking for. If any networks are configured, ad blocking will be automatically enabled. Each entry should be a valid network ID from your UniFi configuration. Leave empty to disable ad blocking.", ElementType: types.StringType, diff --git a/internal/provider/settings/resource_setting_lcm.go b/internal/provider/settings/resource_setting_lcm.go index 86eec85..15205f0 100644 --- a/internal/provider/settings/resource_setting_lcm.go +++ b/internal/provider/settings/resource_setting_lcm.go @@ -2,6 +2,7 @@ package settings import ( "context" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/filipowm/terraform-provider-unifi/internal/provider/validators" @@ -102,8 +103,8 @@ func (r *lcmResource) Schema(_ context.Context, _ resource.SchemaRequest, resp * resp.Schema = schema.Schema{ MarkdownDescription: "Manages LCD Monitor (LCM) settings for UniFi devices with built-in displays, such as the UniFi Dream Machine Pro (UDM Pro) and UniFi Network Video Recorder (UNVR).", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "enabled": schema.BoolAttribute{ MarkdownDescription: "Whether the LCD display is enabled.", Required: true, diff --git a/internal/provider/settings/resource_setting_locale.go b/internal/provider/settings/resource_setting_locale.go index 8546048..b6debd2 100644 --- a/internal/provider/settings/resource_setting_locale.go +++ b/internal/provider/settings/resource_setting_locale.go @@ -2,6 +2,7 @@ package settings import ( "context" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/filipowm/go-unifi/unifi" @@ -59,8 +60,8 @@ func (r *localeResource) Schema(_ context.Context, _ resource.SchemaRequest, res resp.Schema = schema.Schema{ MarkdownDescription: "Manages locale settings for a UniFi site.", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "timezone": schema.StringAttribute{ Required: true, MarkdownDescription: "Timezone for the UniFi controller, e.g., `America/Los_Angeles`", diff --git a/internal/provider/settings/resource_setting_magic_site_to_site_vpn.go b/internal/provider/settings/resource_setting_magic_site_to_site_vpn.go index 315c902..34b33e8 100644 --- a/internal/provider/settings/resource_setting_magic_site_to_site_vpn.go +++ b/internal/provider/settings/resource_setting_magic_site_to_site_vpn.go @@ -2,6 +2,7 @@ package settings import ( "context" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" @@ -57,8 +58,8 @@ func (r *magicSiteToSiteVpnResource) Schema(_ context.Context, _ resource.Schema resp.Schema = schema.Schema{ MarkdownDescription: "Manages Magic Site to Site VPN settings for a UniFi site.", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "enabled": schema.BoolAttribute{ MarkdownDescription: "Whether the Magic Site to Site VPN is enabled.", Required: true, diff --git a/internal/provider/settings/resource_setting_mgmt.go b/internal/provider/settings/resource_setting_mgmt.go index a147c1d..f2673c2 100644 --- a/internal/provider/settings/resource_setting_mgmt.go +++ b/internal/provider/settings/resource_setting_mgmt.go @@ -3,6 +3,7 @@ package settings import ( "context" "fmt" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" @@ -212,8 +213,8 @@ func (r *mgmtResource) Schema(_ context.Context, _ resource.SchemaRequest, resp " * Enabling secure remote administration\n" + " * Implementing SSH key-based authentication", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "auto_upgrade": schema.BoolAttribute{ MarkdownDescription: "Enable automatic firmware upgrades for all UniFi devices at this site. When enabled, devices will automatically " + "update to the latest stable firmware version approved for your controller version.", diff --git a/internal/provider/settings/resource_setting_network_optimization.go b/internal/provider/settings/resource_setting_network_optimization.go index 19e3b96..1aa0b24 100644 --- a/internal/provider/settings/resource_setting_network_optimization.go +++ b/internal/provider/settings/resource_setting_network_optimization.go @@ -2,6 +2,7 @@ package settings import ( "context" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" @@ -58,8 +59,8 @@ func (r *networkOptimizationResource) Schema(_ context.Context, _ resource.Schem MarkdownDescription: "Manages Network Optimization settings for a UniFi site. UniFi network optimization is a feature designed to automatically enhance the performance of a UniFi network" + " by making automatic adjustments to various settings such as channel selection, transmit power, or frequency usage", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "enabled": schema.BoolAttribute{ MarkdownDescription: "Whether the Network Optimization is enabled.", Required: true, diff --git a/internal/provider/settings/resource_setting_ntp.go b/internal/provider/settings/resource_setting_ntp.go index 4f9539e..1aa1eac 100644 --- a/internal/provider/settings/resource_setting_ntp.go +++ b/internal/provider/settings/resource_setting_ntp.go @@ -4,6 +4,7 @@ import ( "context" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/filipowm/terraform-provider-unifi/internal/provider/validators" "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -39,16 +40,16 @@ func (d *ntpModel) AsUnifiModel(_ context.Context) (interface{}, diag.Diagnostic model.NtpServer3 = "" model.NtpServer4 = "" } else { - if !base.IsEmptyString(d.NtpServer1) { + if !ut.IsEmptyString(d.NtpServer1) { model.NtpServer1 = d.NtpServer1.ValueString() } - if !base.IsEmptyString(d.NtpServer2) { + if !ut.IsEmptyString(d.NtpServer2) { model.NtpServer2 = d.NtpServer2.ValueString() } - if !base.IsEmptyString(d.NtpServer3) { + if !ut.IsEmptyString(d.NtpServer3) { model.NtpServer3 = d.NtpServer3.ValueString() } - if !base.IsEmptyString(d.NtpServer4) { + if !ut.IsEmptyString(d.NtpServer4) { model.NtpServer4 = d.NtpServer4.ValueString() } } @@ -115,8 +116,8 @@ func (r *ntpResource) Schema(_ context.Context, _ resource.SchemaRequest, resp * MarkdownDescription: "The `unifi_setting_ntp` resource allows you to configure Network Time Protocol (NTP) server settings for your UniFi network.\n\n" + "NTP servers provide time synchronization for your network devices. This resource supports both automatic and manual NTP configuration modes.", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "ntp_server_1": schema.StringAttribute{ MarkdownDescription: "Primary NTP server hostname or IP address. Must be a valid hostname (e.g., `pool.ntp.org`) or IPv4 address. " + "Only applicable when `mode` is set to `manual`.", diff --git a/internal/provider/settings/resource_setting_rsyslogd.go b/internal/provider/settings/resource_setting_rsyslogd.go index 82dc590..9570f0d 100644 --- a/internal/provider/settings/resource_setting_rsyslogd.go +++ b/internal/provider/settings/resource_setting_rsyslogd.go @@ -2,8 +2,8 @@ package settings import ( "context" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/filipowm/terraform-provider-unifi/internal/provider/validators" - "github.com/filipowm/terraform-provider-unifi/internal/utils" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -82,7 +82,7 @@ func (d *rsyslogdModel) AsUnifiModel(_ context.Context) (interface{}, diag.Diagn if !d.Contents.IsNull() { var contents []string - diags.Append(utils.ListElementsAs(d.Contents, &contents)...) + diags.Append(ut.ListElementsAs(d.Contents, &contents)...) if diags.HasError() { return nil, diags } @@ -133,7 +133,7 @@ func (d *rsyslogdModel) Merge(ctx context.Context, other interface{}) diag.Diagn d.Port = types.Int64Null() d.ThisController = types.BoolNull() d.ThisControllerEncryptedOnly = types.BoolNull() - d.Contents = utils.EmptyList(types.StringType) + d.Contents = ut.EmptyList(types.StringType) } return diags @@ -178,8 +178,8 @@ func (r *rsyslogdResource) Schema(_ context.Context, _ resource.SchemaRequest, r resp.Schema = schema.Schema{ MarkdownDescription: "Manages Remote Syslog (rsyslogd) settings for UniFi devices. Controller version 8.5 or later is required.", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "enabled": schema.BoolAttribute{ MarkdownDescription: "Whether remote syslog is enabled.", Required: true, diff --git a/internal/provider/settings/resource_setting_ssl_inspection.go b/internal/provider/settings/resource_setting_ssl_inspection.go index f61e1d4..6a8c957 100644 --- a/internal/provider/settings/resource_setting_ssl_inspection.go +++ b/internal/provider/settings/resource_setting_ssl_inspection.go @@ -2,6 +2,7 @@ package settings import ( "context" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" @@ -59,8 +60,8 @@ func (r *sslInspectionResource) Schema(_ context.Context, _ resource.SchemaReque resp.Schema = schema.Schema{ MarkdownDescription: "Manages SSL Inspection settings for a UniFi site. SSL inspection is a security feature that allows the UniFi Security Gateway (USG) to inspect encrypted traffic for security threats.", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "state": schema.StringAttribute{ MarkdownDescription: "The mode of SSL inspection. Valid values are: `off`, `simple`, or `advanced`.", Required: true, diff --git a/internal/provider/settings/resource_setting_teleport.go b/internal/provider/settings/resource_setting_teleport.go index 42dcfbc..4bd5876 100644 --- a/internal/provider/settings/resource_setting_teleport.go +++ b/internal/provider/settings/resource_setting_teleport.go @@ -2,6 +2,7 @@ package settings import ( "context" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" @@ -67,8 +68,8 @@ func (r *teleportResource) Schema(_ context.Context, _ resource.SchemaRequest, r resp.Schema = schema.Schema{ MarkdownDescription: "Manages Teleport settings for a UniFi site. Teleport is a secure remote access technology that allows authorized users to connect to UniFi devices from anywhere.", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "enabled": schema.BoolAttribute{ MarkdownDescription: "Whether Teleport is enabled.", Required: true, diff --git a/internal/provider/settings/resource_setting_usg.go b/internal/provider/settings/resource_setting_usg.go index 6361021..f652ff7 100644 --- a/internal/provider/settings/resource_setting_usg.go +++ b/internal/provider/settings/resource_setting_usg.go @@ -2,6 +2,7 @@ package settings import ( "context" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" @@ -11,7 +12,6 @@ import ( "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/filipowm/terraform-provider-unifi/internal/utils" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -206,7 +206,7 @@ func (d *usgModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnost // Extract DHCP relay servers from the list var dhcpRelayServers []string - diags.Append(utils.ListElementsAs(d.DhcpRelayServers, &dhcpRelayServers)...) + diags.Append(ut.ListElementsAs(d.DhcpRelayServers, &dhcpRelayServers)...) if diags.HasError() { return nil, diags } @@ -233,7 +233,7 @@ func (d *usgModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnost // TODO end of deprecated // Assign Geo IP filtering attributes - if base.IsDefined(d.GeoIPFiltering) { + if ut.IsDefined(d.GeoIPFiltering) { var geoIPFiltering *GeoIPFilteringModel diags.Append(d.GeoIPFiltering.As(ctx, &geoIPFiltering, basetypes.ObjectAsOptions{})...) if diags.HasError() { @@ -242,7 +242,7 @@ func (d *usgModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnost model.GeoIPFilteringBlock = geoIPFiltering.Mode.ValueString() model.GeoIPFilteringTrafficDirection = geoIPFiltering.TrafficDirection.ValueString() - countries, diags := utils.ListElementsToString(ctx, geoIPFiltering.Countries) + countries, diags := ut.ListElementsToString(ctx, geoIPFiltering.Countries) if diags.HasError() { return nil, diags } @@ -253,7 +253,7 @@ func (d *usgModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnost } // Assign UPNP attributes - if base.IsDefined(d.Upnp) { + if ut.IsDefined(d.Upnp) { var upnp *UpnpModel diags.Append(d.Upnp.As(ctx, &upnp, basetypes.ObjectAsOptions{})...) if diags.HasError() { @@ -268,7 +268,7 @@ func (d *usgModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnost model.UpnpEnabled = false } - if base.IsDefined(d.TcpTimeouts) { + if ut.IsDefined(d.TcpTimeouts) { var tcpTimeouts *TCPTimeoutModel diags.Append(d.TcpTimeouts.As(ctx, &tcpTimeouts, basetypes.ObjectAsOptions{})...) if diags.HasError() { @@ -286,7 +286,7 @@ func (d *usgModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnost } // Assign DNS Verification attributes - if base.IsDefined(d.DnsVerification) { + if ut.IsDefined(d.DnsVerification) { var dnsVerification *DNSVerificationModel diags.Append(d.DnsVerification.As(ctx, &dnsVerification, basetypes.ObjectAsOptions{})...) if diags.HasError() { @@ -301,7 +301,7 @@ func (d *usgModel) AsUnifiModel(ctx context.Context) (interface{}, diag.Diagnost } } - if base.IsDefined(d.DhcpRelay) { + if ut.IsDefined(d.DhcpRelay) { var dhcpRelay *DHCPRelayModel diags.Append(d.DhcpRelay.As(ctx, &dhcpRelay, basetypes.ObjectAsOptions{})...) if diags.HasError() { @@ -365,7 +365,7 @@ func (d *usgModel) Merge(ctx context.Context, other interface{}) diag.Diagnostic TrafficDirection: types.StringValue(model.GeoIPFilteringTrafficDirection), } - countries, diags := utils.StringToListElements(ctx, model.GeoIPFilteringCountries) + countries, diags := ut.StringToListElements(ctx, model.GeoIPFilteringCountries) if diags.HasError() { return diags } @@ -507,8 +507,8 @@ func (r *usgResource) Schema(_ context.Context, _ resource.SchemaRequest, resp * " * MSS clamping for optimizing MTU issues\n\n" + "Note: Some settings may not be available on all controller versions. For example, multicast_dns_enabled is not supported on UniFi OS v7+. Changes to certain attributes may not be reflected in the plan unless explicitly modified in the configuration.", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "multicast_dns_enabled": schema.BoolAttribute{ MarkdownDescription: "Enable multicast DNS (mDNS/Bonjour/Avahi) forwarding across VLANs. This allows devices to discover services " + "(like printers, Chromecasts, Apple devices, etc.) even when they are on different networks or VLANs. " + @@ -533,7 +533,7 @@ func (r *usgResource) Schema(_ context.Context, _ resource.SchemaRequest, resp * PlanModifiers: []planmodifier.List{ listplanmodifier.UseStateForUnknown(), }, - Default: utils.DefaultEmptyList(types.StringType), + Default: ut.DefaultEmptyList(types.StringType), Validators: []validator.List{ listvalidator.SizeAtMost(5), listvalidator.ValueStringsAre(validators.IPv4()), diff --git a/internal/provider/settings/resource_setting_usw.go b/internal/provider/settings/resource_setting_usw.go index 952e1ab..e30154e 100644 --- a/internal/provider/settings/resource_setting_usw.go +++ b/internal/provider/settings/resource_setting_usw.go @@ -2,6 +2,7 @@ package settings import ( "context" + ut "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" @@ -57,8 +58,8 @@ func (r *uswResource) Schema(_ context.Context, _ resource.SchemaRequest, resp * resp.Schema = schema.Schema{ MarkdownDescription: "Manages UniFi Switch (USW) settings for a UniFi site. These settings control global switch behaviors such as DHCP snooping.", Attributes: map[string]schema.Attribute{ - "id": base.ID(), - "site": base.SiteAttribute(), + "id": ut.ID(), + "site": ut.SiteAttribute(), "dhcp_snoop": schema.BoolAttribute{ MarkdownDescription: "Whether DHCP snooping is enabled. DHCP snooping is a security feature that filters untrusted DHCP messages and builds a binding database of valid hosts.", Required: true, diff --git a/internal/provider/testing/test_helpers.go b/internal/provider/testing/test_helpers.go index b4a6214..6c6ffa0 100644 --- a/internal/provider/testing/test_helpers.go +++ b/internal/provider/testing/test_helpers.go @@ -1,7 +1,11 @@ package testing import ( + "context" + "errors" "fmt" + "github.com/filipowm/go-unifi/unifi" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -67,8 +71,8 @@ func SiteAndIDImportStateIDFunc(resourceName string) func(*terraform.State) (str // PreCheck checks if provided environment variables are set. If not, it will fail the test. func PreCheck(t *testing.T) { variables := []string{ - //"UNIFI_USERNAME", - //"UNIFI_PASSWORD", + "UNIFI_USERNAME", + "UNIFI_PASSWORD", "UNIFI_API", } @@ -109,3 +113,28 @@ func SkipIfEnvLocalMissing(t *testing.T, msg string) { t.Helper() SkipIfEnvMissing(t, msg, TfAccLocal) } + +func CheckDestroy(resourceType string, read func(ctx context.Context, site, id string) error) func(s *terraform.State) error { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type == "" || rs.Type != resourceType { + continue + } + site := "default" + if s, ok := rs.Primary.Attributes["site"]; ok { + if s != "" { + site = s + } + } + err := read(context.Background(), site, rs.Primary.ID) + if err == nil { + return fmt.Errorf("Resource with id %q still exists.", rs.Primary.ID) + } + if utils.IsServerErrorStatusCode(err, 404) || errors.Is(err, unifi.ErrNotFound) { + continue + } + return err + } + return nil + } +} diff --git a/internal/provider/base/types.go b/internal/provider/types/attributes.go similarity index 63% rename from internal/provider/base/types.go rename to internal/provider/types/attributes.go index 0d1e0ea..69f1df5 100644 --- a/internal/provider/base/types.go +++ b/internal/provider/types/attributes.go @@ -1,11 +1,9 @@ -package base +package types 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. @@ -37,21 +35,7 @@ func SiteAttribute(desc ...string) schema.StringAttribute { } if len(desc) > 0 { - s.Description = desc[0] + s.MarkdownDescription = 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() == "" -} diff --git a/internal/utils/lists.go b/internal/provider/types/lists.go similarity index 91% rename from internal/utils/lists.go rename to internal/provider/types/lists.go index aac02d8..fb8a61a 100644 --- a/internal/utils/lists.go +++ b/internal/provider/types/lists.go @@ -1,8 +1,8 @@ -package utils +package types import ( "context" - "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults" @@ -20,7 +20,7 @@ func EmptyList(elementType attr.Type) types.List { func ListElementsAs(list types.List, target interface{}) diag.Diagnostics { diags := diag.Diagnostics{} - if !base.IsDefined(list) { + if !IsDefined(list) { return diags } if diagErr := list.ElementsAs(context.Background(), target, false); diagErr != nil { @@ -31,7 +31,7 @@ func ListElementsAs(list types.List, target interface{}) diag.Diagnostics { func ListElementsToString(ctx context.Context, list types.List) (string, diag.Diagnostics) { diags := diag.Diagnostics{} - if !base.IsDefined(list) { + if !IsDefined(list) { return "", diags } if list.ElementType(ctx) == types.StringType { @@ -40,14 +40,14 @@ func ListElementsToString(ctx context.Context, list types.List) (string, diag.Di if diags.HasError() { return "", diags } - return JoinNonEmpty(target, ","), diags + return utils.JoinNonEmpty(target, ","), diags } diags.AddError("List is not a list of types.StringType", "List is not a list of strings") return "", diags } func StringToListElements(ctx context.Context, value string) (types.List, diag.Diagnostics) { - countries := SplitAndTrim(value, ",") + countries := utils.SplitAndTrim(value, ",") if len(countries) == 0 { return types.ListNull(types.StringType), diag.Diagnostics{} } diff --git a/internal/provider/types/objects.go b/internal/provider/types/objects.go new file mode 100644 index 0000000..627f552 --- /dev/null +++ b/internal/provider/types/objects.go @@ -0,0 +1,47 @@ +package types + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "strings" +) + +type NestedObject interface { + AttributeTypes() map[string]attr.Type +} + +func ObjectNull(obj interface{}) (basetypes.ObjectValue, diag.Diagnostics) { + diags := diag.Diagnostics{} + if nested, ok := obj.(NestedObject); ok { + obj := types.ObjectNull(nested.AttributeTypes()) + return obj, diags + } + diags.AddError("Invalid object type", fmt.Sprintf("Expected NestedObject, got: %T", obj)) + return types.ObjectNull(map[string]attr.Type{}), diags +} + +func ObjectValueMust(ctx context.Context, obj interface{}) basetypes.ObjectValue { + if nested, ok := obj.(NestedObject); ok { + val, diags := types.ObjectValueFrom(ctx, nested.AttributeTypes(), obj) + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("ObjectValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + return val + } + panic(fmt.Sprintf("ObjectValueMust received invalid object type. Expected NestedObject, got: %T", obj)) +} diff --git a/internal/provider/types/utils.go b/internal/provider/types/utils.go new file mode 100644 index 0000000..6602a56 --- /dev/null +++ b/internal/provider/types/utils.go @@ -0,0 +1,20 @@ +package types + +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// 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() == "" +} diff --git a/internal/provider/user/data_user.go b/internal/provider/user/data_user.go index d1d25eb..ab09b65 100644 --- a/internal/provider/user/data_user.go +++ b/internal/provider/user/data_user.go @@ -3,7 +3,7 @@ package user import ( "context" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" - "github.com/filipowm/terraform-provider-unifi/internal/utils" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" diff --git a/internal/provider/user/resource_user.go b/internal/provider/user/resource_user.go index 0da5d7e..02c9e74 100644 --- a/internal/provider/user/resource_user.go +++ b/internal/provider/user/resource_user.go @@ -3,10 +3,10 @@ package user import ( "context" "errors" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" "github.com/filipowm/go-unifi/unifi" "github.com/filipowm/terraform-provider-unifi/internal/provider/base" - "github.com/filipowm/terraform-provider-unifi/internal/utils" "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" @@ -163,7 +163,7 @@ func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interf resp, err := c.CreateUser(ctx, site, req) if err != nil { - if !base.IsServerErrorContains(err, "api.err.MacUsed") || !allowExisting { + if !utils.IsServerErrorContains(err, "api.err.MacUsed") || !allowExisting { return diag.FromErr(err) } diff --git a/internal/utils/cidr.go b/internal/provider/utils/cidr.go similarity index 100% rename from internal/utils/cidr.go rename to internal/provider/utils/cidr.go diff --git a/internal/utils/cidr_test.go b/internal/provider/utils/cidr_test.go similarity index 100% rename from internal/utils/cidr_test.go rename to internal/provider/utils/cidr_test.go diff --git a/internal/utils/env.go b/internal/provider/utils/env.go similarity index 100% rename from internal/utils/env.go rename to internal/provider/utils/env.go diff --git a/internal/provider/base/errors.go b/internal/provider/utils/errors.go similarity index 98% rename from internal/provider/base/errors.go rename to internal/provider/utils/errors.go index 68c83b0..759969f 100644 --- a/internal/provider/base/errors.go +++ b/internal/provider/utils/errors.go @@ -1,4 +1,4 @@ -package base +package utils import ( "errors" diff --git a/internal/utils/mac.go b/internal/provider/utils/mac.go similarity index 100% rename from internal/utils/mac.go rename to internal/provider/utils/mac.go diff --git a/internal/provider/utils/markdown.go b/internal/provider/utils/markdown.go new file mode 100644 index 0000000..9156a24 --- /dev/null +++ b/internal/provider/utils/markdown.go @@ -0,0 +1,27 @@ +package utils + +import "strconv" + +func MarkdownValueList[T any](strMapper func(T) string, values []T) string { + switch { + case len(values) == 0: + return "" + case len(values) == 1: + return "`" + strMapper(values[0]) + "`" + default: + s := "" + for i := 0; i < len(values)-1; i++ { + s += "`" + strMapper(values[i]) + "`, " + } + s += " and `" + strMapper(values[len(values)-1]) + "`" + return s + } +} + +func MarkdownValueListInt(values []int) string { + return MarkdownValueList(func(i int) string { return strconv.Itoa(i) }, values) +} + +func MarkdownValueListString(values []string) string { + return MarkdownValueList(func(s string) string { return s }, values) +} diff --git a/internal/utils/strings.go b/internal/provider/utils/strings.go similarity index 88% rename from internal/utils/strings.go rename to internal/provider/utils/strings.go index 9ef4155..3f8361f 100644 --- a/internal/utils/strings.go +++ b/internal/provider/utils/strings.go @@ -3,6 +3,7 @@ package utils import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "slices" "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -71,3 +72,13 @@ func SplitAndTrim(s string, separator string) []string { return result } + +func RemoveElements[S ~[]E, E comparable](first S, second S) S { + var result S + for _, category := range first { + if !slices.Contains(second, category) { + result = append(result, category) + } + } + return result +} diff --git a/internal/provider/validators/cidr.go b/internal/provider/validators/cidr.go index 4433832..c448fa0 100644 --- a/internal/provider/validators/cidr.go +++ b/internal/provider/validators/cidr.go @@ -30,15 +30,15 @@ type cidrValidator struct { allowEmpty bool } -func (v cidrValidator) Description(ctx context.Context) string { +func (v cidrValidator) Description(_ context.Context) string { return "value must be a valid CIDR notation (e.g., '192.168.1.0/24')" } -func (v cidrValidator) MarkdownDescription(ctx context.Context) string { +func (v cidrValidator) MarkdownDescription(_ context.Context) string { return "value must be a valid CIDR notation (e.g., `192.168.1.0/24`)" } -func (v cidrValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { +func (v cidrValidator) ValidateString(_ context.Context, req validator.StringRequest, resp *validator.StringResponse) { if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { return } diff --git a/internal/provider/validators/country_code.go b/internal/provider/validators/country_code.go index ad0ddd4..3892d15 100644 --- a/internal/provider/validators/country_code.go +++ b/internal/provider/validators/country_code.go @@ -3,7 +3,7 @@ package validators import ( "context" "github.com/biter777/countries" - "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) @@ -24,7 +24,7 @@ func (c countryCodeAlpha2Validator) MarkdownDescription(ctx context.Context) str func (c countryCodeAlpha2Validator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { code := req.ConfigValue - if base.IsEmptyString(code) { + if types.IsEmptyString(code) { return } diff --git a/internal/provider/validators/email.go b/internal/provider/validators/email.go deleted file mode 100644 index 6a8122d..0000000 --- a/internal/provider/validators/email.go +++ /dev/null @@ -1,48 +0,0 @@ -package validators - -import ( - "context" - "fmt" - "regexp" - - "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" -) - -// A common regex pattern for validating email addresses -// This is a simplified version and may not catch all edge cases -var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`) - -// Email returns a validator which ensures that the string value is a valid email address. -func Email() validator.String { - return emailValidator{} -} - -type emailValidator struct{} - -func (v emailValidator) Description(_ context.Context) string { - return "must be a valid email address" -} - -func (v emailValidator) MarkdownDescription(ctx context.Context) string { - return v.Description(ctx) -} - -func (v emailValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { - value := req.ConfigValue - if !base.IsDefined(value) { - return - } - - val := value.ValueString() - if !emailRegex.MatchString(val) { - resp.Diagnostics.Append( - validatordiag.InvalidAttributeValueDiagnostic( - req.Path, - v.Description(ctx), - fmt.Sprintf("%q is not a valid email address", val), - ), - ) - } -} diff --git a/internal/provider/validators/email_test.go b/internal/provider/validators/email_test.go deleted file mode 100644 index 12546dc..0000000 --- a/internal/provider/validators/email_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package validators_test - -import ( - "context" - "github.com/filipowm/terraform-provider-unifi/internal/provider/validators" - "testing" - - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" -) - -func TestEmailValidator(t *testing.T) { - t.Parallel() - - type testCase struct { - val types.String - expectError bool - } - tests := map[string]testCase{ - "unknown": { - val: types.StringUnknown(), - }, - "null": { - val: types.StringNull(), - }, - "valid-simple": { - val: types.StringValue("test@example.com"), - }, - "valid-with-dots": { - val: types.StringValue("john.doe@example.com"), - }, - "valid-with-plus": { - val: types.StringValue("john+test@example.com"), - }, - "valid-with-subdomain": { - val: types.StringValue("john@sub.example.com"), - }, - "valid-with-numbers": { - val: types.StringValue("user123@example.com"), - }, - "invalid-no-at": { - val: types.StringValue("testexample.com"), - expectError: true, - }, - "invalid-no-domain": { - val: types.StringValue("test@"), - expectError: true, - }, - "invalid-no-tld": { - val: types.StringValue("test@example"), - expectError: true, - }, - "invalid-space": { - val: types.StringValue("test user@example.com"), - expectError: true, - }, - "invalid-special-chars": { - val: types.StringValue("test*user@example.com"), - expectError: true, - }, - } - - for name, test := range tests { - name, test := name, test - t.Run(name, func(t *testing.T) { - t.Parallel() - request := validator.StringRequest{ - ConfigValue: test.val, - } - response := validator.StringResponse{} - validators.Email().ValidateString(context.Background(), request, &response) - - if !response.Diagnostics.HasError() && test.expectError { - t.Fatal("expected error, got no error") - } - - if response.Diagnostics.HasError() && !test.expectError { - t.Fatalf("got unexpected error: %s", response.Diagnostics) - } - }) - } -} diff --git a/internal/provider/validators/hex_color.go b/internal/provider/validators/hex_color.go deleted file mode 100644 index 0f81224..0000000 --- a/internal/provider/validators/hex_color.go +++ /dev/null @@ -1,47 +0,0 @@ -package validators - -import ( - "context" - "fmt" - "regexp" - - "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" -) - -var hexColorRegex = regexp.MustCompile("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$") - -// HexColor returns a validator which ensures that the string value is a valid hex color code. -// Valid formats are: #RGB or #RRGGBB -func HexColor() validator.String { - return hexColorValidator{} -} - -type hexColorValidator struct{} - -func (v hexColorValidator) Description(_ context.Context) string { - return "must be a valid hex color code (e.g., #FFF or #FFFFFF)" -} - -func (v hexColorValidator) MarkdownDescription(ctx context.Context) string { - return v.Description(ctx) -} - -func (v hexColorValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { - value := req.ConfigValue - if !base.IsDefined(value) { - return - } - - val := value.ValueString() - if !hexColorRegex.MatchString(val) { - resp.Diagnostics.Append( - validatordiag.InvalidAttributeValueDiagnostic( - req.Path, - v.Description(ctx), - fmt.Sprintf("%q is not a valid hex color code", val), - ), - ) - } -} diff --git a/internal/provider/validators/hex_color_test.go b/internal/provider/validators/hex_color_test.go deleted file mode 100644 index 53385ca..0000000 --- a/internal/provider/validators/hex_color_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package validators_test - -import ( - "context" - "github.com/filipowm/terraform-provider-unifi/internal/provider/validators" - "testing" - - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" -) - -func TestHexColorValidator(t *testing.T) { - t.Parallel() - - type testCase struct { - val types.String - expectError bool - } - tests := map[string]testCase{ - "unknown": { - val: types.StringUnknown(), - }, - "null": { - val: types.StringNull(), - }, - "valid-6-digits": { - val: types.StringValue("#123456"), - }, - "valid-3-digits": { - val: types.StringValue("#123"), - }, - "valid-uppercase": { - val: types.StringValue("#ABCDEF"), - }, - "valid-mixed-case": { - val: types.StringValue("#aBcDeF"), - }, - "invalid-missing-hash": { - val: types.StringValue("123456"), - expectError: true, - }, - "invalid-too-short": { - val: types.StringValue("#12"), - expectError: true, - }, - "invalid-too-long": { - val: types.StringValue("#1234567"), - expectError: true, - }, - "invalid-wrong-chars": { - val: types.StringValue("#12345G"), - expectError: true, - }, - } - - for name, test := range tests { - name, test := name, test - t.Run(name, func(t *testing.T) { - t.Parallel() - request := validator.StringRequest{ - ConfigValue: test.val, - } - response := validator.StringResponse{} - validators.HexColor().ValidateString(context.Background(), request, &response) - - if !response.Diagnostics.HasError() && test.expectError { - t.Fatal("expected error, got no error") - } - - if response.Diagnostics.HasError() && !test.expectError { - t.Fatalf("got unexpected error: %s", response.Diagnostics) - } - }) - } -} diff --git a/internal/provider/validators/hostname.go b/internal/provider/validators/hostname.go index 5c1360b..256e42d 100644 --- a/internal/provider/validators/hostname.go +++ b/internal/provider/validators/hostname.go @@ -3,10 +3,10 @@ package validators import ( "context" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "regexp" "strings" - "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" ) @@ -32,7 +32,7 @@ func (v hostnameValidator) MarkdownDescription(ctx context.Context) string { func (v hostnameValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { value := req.ConfigValue - if !base.IsDefined(value) { + if !types.IsDefined(value) { return } diff --git a/internal/provider/validators/ipv4.go b/internal/provider/validators/ipv4.go index 32e91cf..0697c2a 100644 --- a/internal/provider/validators/ipv4.go +++ b/internal/provider/validators/ipv4.go @@ -3,7 +3,7 @@ package validators import ( "context" "fmt" - "github.com/filipowm/terraform-provider-unifi/internal/utils" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) diff --git a/internal/provider/validators/ipv6.go b/internal/provider/validators/ipv6.go index e56c675..bb82b01 100644 --- a/internal/provider/validators/ipv6.go +++ b/internal/provider/validators/ipv6.go @@ -3,7 +3,7 @@ package validators import ( "context" "fmt" - "github.com/filipowm/terraform-provider-unifi/internal/utils" + "github.com/filipowm/terraform-provider-unifi/internal/provider/utils" "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) diff --git a/internal/provider/validators/port_range.go b/internal/provider/validators/port_range.go new file mode 100644 index 0000000..3e4d37f --- /dev/null +++ b/internal/provider/validators/port_range.go @@ -0,0 +1 @@ +package validators diff --git a/internal/provider/validators/regex.go b/internal/provider/validators/regex.go new file mode 100644 index 0000000..c86439c --- /dev/null +++ b/internal/provider/validators/regex.go @@ -0,0 +1,27 @@ +package validators + +import ( + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "regexp" +) + +var ( + TimeFormatRegex = regexp.MustCompile(`^[0-9][0-9]:[0-9][0-9]$`) + TimeFormat = stringvalidator.RegexMatches(TimeFormatRegex, "must be a valid time in 24-hour format (HH:MM)") + + DateFormatRegex = regexp.MustCompile(`^(20[0-9]{2})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$`) + DateFormat = stringvalidator.RegexMatches(DateFormatRegex, "must be a valid date in the format YYYY-MM-DD") + + PortRangeRegexp = regexp.MustCompile("(([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])|([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])-([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5]))+(,([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])|,([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])-([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])){0,14}") + PortRangeV2 = validation.StringMatch(PortRangeRegexp, "invalid port range") + + MacRegex = regexp.MustCompile(`^([0-9A-Fa-f]{2}:){5}([0-9A-Fa-f]{2})$`) + Mac = stringvalidator.RegexMatches(MacRegex, "invalid MAC address") + + HexColorRegex = regexp.MustCompile("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$") + HexColor = stringvalidator.RegexMatches(HexColorRegex, "invalid hex color code") + + EmailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`) + Email = stringvalidator.RegexMatches(EmailRegex, "invalid email address") +) diff --git a/internal/provider/validators/required_together_if.go b/internal/provider/validators/required_together_if.go index 8b74405..f29b9a8 100644 --- a/internal/provider/validators/required_together_if.go +++ b/internal/provider/validators/required_together_if.go @@ -3,6 +3,7 @@ package validators import ( "context" "fmt" + "strings" "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -182,6 +183,33 @@ func RequiredTogetherIf(conditionPath path.Expression, conditionValue attr.Value } } +func mapPathToExpression(p string) path.Expression { + parts := strings.Split(p, ".") + root := parts[0] + exp := path.MatchRoot(root) + for _, part := range parts[1:] { + exp = exp.AtName(part) + } + return exp +} + +func mapPathsToExpressions(paths ...string) []path.Expression { + var expressions []path.Expression + for _, p := range paths { + expressions = append(expressions, mapPathToExpression(p)) + } + return expressions +} + +func RequiredSimpleTogetherIf(conditionPath string, conditionValue attr.Value, targetExpressions ...string) RequiredTogetherIfValidator { + return RequiredTogetherIfValidator{ + ConditionPath: mapPathToExpression(conditionPath), + ConditionValue: conditionValue, + TargetExpressions: mapPathsToExpressions(targetExpressions...), + CheckOnlyIfSet: false, + } +} + // RequiredTogetherIfSet creates a validator that ensures a set of target attributes // are configured together if a condition attribute is set (not null), regardless of its value. func RequiredTogetherIfSet(conditionPath path.Expression, targetExpressions ...path.Expression) RequiredTogetherIfValidator { diff --git a/internal/provider/validators/string_length_exactly.go b/internal/provider/validators/string_length_exactly.go index c5f2fe4..859629e 100644 --- a/internal/provider/validators/string_length_exactly.go +++ b/internal/provider/validators/string_length_exactly.go @@ -3,7 +3,7 @@ package validators import ( "context" "fmt" - "github.com/filipowm/terraform-provider-unifi/internal/provider/base" + "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" "github.com/hashicorp/terraform-plugin-framework/schema/validator" ) @@ -41,7 +41,7 @@ func (v stringLengthExactlyValidator) ValidateString(ctx context.Context, req va } value := req.ConfigValue - if !base.IsDefined(value) { + if !types.IsDefined(value) { return } val := value.ValueString() diff --git a/internal/provider/validators/timezone.go b/internal/provider/validators/timezone.go index 58727ea..b46087b 100644 --- a/internal/provider/validators/timezone.go +++ b/internal/provider/validators/timezone.go @@ -3,10 +3,10 @@ package validators import ( "context" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "strings" "time" - "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" ) @@ -29,7 +29,7 @@ func (v timezoneValidator) MarkdownDescription(ctx context.Context) string { func (v timezoneValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { value := req.ConfigValue - if !base.IsDefined(value) { + if !types.IsDefined(value) { return } diff --git a/internal/provider/validators/url.go b/internal/provider/validators/url.go index 2b5c6ef..7c62f25 100644 --- a/internal/provider/validators/url.go +++ b/internal/provider/validators/url.go @@ -3,9 +3,9 @@ package validators import ( "context" "fmt" + "github.com/filipowm/terraform-provider-unifi/internal/provider/types" "net/url" - "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" ) @@ -37,7 +37,7 @@ func (v urlValidator) MarkdownDescription(ctx context.Context) string { func (v urlValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { value := req.ConfigValue - if !base.IsDefined(value) { + if !types.IsDefined(value) { return } diff --git a/internal/utils/markdown.go b/internal/utils/markdown.go deleted file mode 100644 index e210f5a..0000000 --- a/internal/utils/markdown.go +++ /dev/null @@ -1,19 +0,0 @@ -package utils - -import "strconv" - -func MarkdownValueListInt(values []int) string { - switch { - case len(values) == 0: - return "" - case len(values) == 1: - return "`" + strconv.Itoa(values[0]) + "`" - default: - s := "" - for i := 0; i < len(values)-1; i++ { - s += "`" + strconv.Itoa(values[i]) + "`, " - } - s += " and `" + strconv.Itoa(values[len(values)-1]) + "`" - return s - } -} diff --git a/internal/utils/port_range.go b/internal/utils/port_range.go deleted file mode 100644 index a07d4af..0000000 --- a/internal/utils/port_range.go +++ /dev/null @@ -1,12 +0,0 @@ -package utils - -import ( - "regexp" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" -) - -var ( - PortRangeRegexp = regexp.MustCompile("(([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])|([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])-([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5]))+(,([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])|,([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])-([1-9][0-9]{0,3}|[1-5][0-9]{4}|[6][0-4][0-9]{3}|[6][5][0-4][0-9]{2}|[6][5][5][0-2][0-9]|[6][5][5][3][0-5])){0,14}") - ValidatePortRange = validation.StringMatch(PortRangeRegexp, "invalid port range") -)