refactor: reorganize code to make it more readable (#71)

This commit is contained in:
Mateusz Filipowicz
2025-03-21 11:52:55 +01:00
committed by GitHub
parent 86fdc0b27a
commit cdc0254289
69 changed files with 404 additions and 524 deletions

View File

@@ -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"`

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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.",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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`",

View File

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

View File

@@ -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.",

View File

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

View File

@@ -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`.",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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() == ""
}

View File

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

View File

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

View File

@@ -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() == ""
}

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package base
package utils
import (
"errors"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
package validators

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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