feat: add Teleport support with unifi_setting_teleport resource (#39)

* feat: add Telepor support with `unifi_setting_teleport` resource

* add cidr validators

* fix teleport tests by specifying version constraints

* fix teleport version needed

* require version 7.1

* lint
This commit is contained in:
Mateusz Filipowicz
2025-03-03 21:08:50 +01:00
committed by GitHub
parent 5da978a5d3
commit 7856ec4764
8 changed files with 1181 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
package acctest
import (
"fmt"
pt "github.com/filipowm/terraform-provider-unifi/internal/provider/testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/plancheck"
"sync"
"testing"
)
var settingTeleportLock = &sync.Mutex{}
func TestAccSettingTeleport(t *testing.T) {
AcceptanceTest(t, AcceptanceTestCase{
VersionConstraint: ">= 7.1",
Lock: settingTeleportLock,
Steps: []resource.TestStep{
{
Config: testAccSettingTeleportConfig(true, ""),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet("unifi_setting_teleport.test", "id"),
resource.TestCheckResourceAttr("unifi_setting_teleport.test", "site", "default"),
resource.TestCheckResourceAttr("unifi_setting_teleport.test", "enabled", "true"),
resource.TestCheckResourceAttr("unifi_setting_teleport.test", "subnet", ""),
),
ConfigPlanChecks: pt.CheckResourceActions("unifi_setting_teleport.test", plancheck.ResourceActionCreate),
},
pt.ImportStepWithSite("unifi_setting_teleport.test"),
{
Config: testAccSettingTeleportConfig(true, "192.168.100.0/24"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("unifi_setting_teleport.test", "enabled", "true"),
resource.TestCheckResourceAttr("unifi_setting_teleport.test", "subnet", "192.168.100.0/24"),
),
ConfigPlanChecks: pt.CheckResourceActions("unifi_setting_teleport.test", plancheck.ResourceActionUpdate),
},
{
Config: testAccSettingTeleportConfigWithoutSubnet(false),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("unifi_setting_teleport.test", "enabled", "false"),
resource.TestCheckResourceAttr("unifi_setting_teleport.test", "subnet", ""),
),
ConfigPlanChecks: pt.CheckResourceActions("unifi_setting_teleport.test", plancheck.ResourceActionUpdate),
},
},
})
}
func testAccSettingTeleportConfig(enabled bool, subnetCidr string) string {
return fmt.Sprintf(`
resource "unifi_setting_teleport" "test" {
enabled = %t
subnet = %q
}
`, enabled, subnetCidr)
}
func testAccSettingTeleportConfigWithoutSubnet(enabled bool) string {
return fmt.Sprintf(`
resource "unifi_setting_teleport" "test" {
enabled = %t
}
`, enabled)
}

View File

@@ -9,6 +9,12 @@ func asVersion(versionString string) *version.Version {
return version.Must(version.NewVersion(versionString))
}
// AsVersion converts a string version to a *version.Version
// This is a utility function for consumers of this package
func AsVersion(versionString string) *version.Version {
return asVersion(versionString)
}
var (
ControllerV6 = asVersion("6.0.0")
ControllerV7 = asVersion("7.0.0")

View File

@@ -181,6 +181,7 @@ func (p *unifiProvider) Resources(_ context.Context) []func() resource.Resource
settings.NewNetworkOptimizationResource,
settings.NewNtpResource,
settings.NewSslInspectionResource,
settings.NewTeleportResource,
}
}

View File

@@ -0,0 +1,103 @@
package settings
import (
"context"
"github.com/filipowm/go-unifi/unifi"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/provider/validators"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)
type teleportModel struct {
base.Model
Enabled types.Bool `tfsdk:"enabled"`
Subnet types.String `tfsdk:"subnet"`
}
func (d *teleportModel) AsUnifiModel() (interface{}, diag.Diagnostics) {
diags := diag.Diagnostics{}
model := &unifi.SettingTeleport{
ID: d.ID.ValueString(),
Enabled: d.Enabled.ValueBool(),
SubnetCidr: d.Subnet.ValueString(),
}
return model, diags
}
func (d *teleportModel) Merge(other interface{}) diag.Diagnostics {
diags := diag.Diagnostics{}
model, ok := other.(*unifi.SettingTeleport)
if !ok {
diags.AddError("Cannot merge", "Cannot merge type that is not *unifi.SettingTeleport")
return diags
}
d.ID = types.StringValue(model.ID)
d.Enabled = types.BoolValue(model.Enabled)
d.Subnet = types.StringValue(model.SubnetCidr)
return diags
}
var (
_ base.ResourceModel = &teleportModel{}
_ resource.Resource = &teleportResource{}
_ resource.ResourceWithConfigure = &teleportResource{}
_ resource.ResourceWithImportState = &teleportResource{}
_ resource.ResourceWithConfigValidators = &teleportResource{}
)
type teleportResource struct {
*BaseSettingResource[*teleportModel]
}
func (r *teleportResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
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(),
"enabled": schema.BoolAttribute{
MarkdownDescription: "Whether Teleport is enabled.",
Required: true,
},
"subnet": schema.StringAttribute{
MarkdownDescription: "The subnet CIDR for Teleport (e.g., `192.168.1.0/24`). Can be empty but must be set explicitly.",
Optional: true,
Computed: true,
Validators: []validator.String{
validators.CIDROrEmpty(),
},
},
},
}
}
func (r *teleportResource) ConfigValidators(_ context.Context) []resource.ConfigValidator {
return []resource.ConfigValidator{
validators.ResourceRequireMinVersion(r.GetClient(), "7.1", "Teleport requires UniFi controller version 7.1 or higher"),
}
}
func NewTeleportResource() resource.Resource {
r := &teleportResource{}
r.BaseSettingResource = NewBaseSettingResource(
"unifi_setting_teleport",
func() *teleportModel { return &teleportModel{} },
func(ctx context.Context, client *base.Client, site string) (interface{}, error) {
return client.GetSettingTeleport(ctx, site)
},
func(ctx context.Context, client *base.Client, site string, body interface{}) (interface{}, error) {
return client.UpdateSettingTeleport(ctx, site, body.(*unifi.SettingTeleport))
},
)
return r
}

View File

@@ -0,0 +1,67 @@
package validators
import (
"context"
"fmt"
"net"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)
// CIDR returns a validator which ensures that a string value is a valid CIDR notation.
func CIDR() validator.String {
return cidrValidator{
allowEmpty: false,
}
}
// CIDROrEmpty returns a validator which ensures that a string value is either empty or a valid CIDR notation.
func CIDROrEmpty() validator.String {
return cidrValidator{
allowEmpty: true,
}
}
var (
_ validator.String = cidrValidator{}
)
type cidrValidator struct {
allowEmpty bool
}
func (v cidrValidator) Description(ctx 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 {
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) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}
value := req.ConfigValue.ValueString()
if value == "" {
if !v.allowEmpty {
resp.Diagnostics.AddAttributeError(
req.Path,
"Invalid CIDR Notation",
"CIDR notation cannot be empty",
)
}
return
}
_, _, err := net.ParseCIDR(value)
if err != nil {
resp.Diagnostics.AddAttributeError(
req.Path,
"Invalid CIDR Notation",
fmt.Sprintf("Value %q is not a valid CIDR notation: %v", value, err),
)
return
}
}

View File

@@ -0,0 +1,127 @@
package validators_test
import (
"context"
"testing"
"github.com/filipowm/terraform-provider-unifi/internal/provider/validators"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stretchr/testify/require"
)
func TestCIDR(t *testing.T) {
t.Parallel()
type testCase struct {
val types.String
expectError bool
}
tests := map[string]testCase{
"unknown": {
val: types.StringUnknown(),
expectError: false,
},
"null": {
val: types.StringNull(),
expectError: false,
},
"empty": {
val: types.StringValue(""),
expectError: true,
},
"valid-ipv4": {
val: types.StringValue("192.168.1.0/24"),
expectError: false,
},
"invalid-ipv4": {
val: types.StringValue("192.168.1.0"),
expectError: true,
},
"valid-ipv6": {
val: types.StringValue("2001:db8::/32"),
expectError: false,
},
"invalid-ipv6": {
val: types.StringValue("2001:db8::"),
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.CIDR().ValidateString(context.Background(), request, &response)
if test.expectError {
require.NotEmpty(t, response.Diagnostics)
return
}
require.Empty(t, response.Diagnostics)
})
}
}
func TestCIDROrEmpty(t *testing.T) {
t.Parallel()
type testCase struct {
val types.String
expectError bool
}
tests := map[string]testCase{
"unknown": {
val: types.StringUnknown(),
expectError: false,
},
"null": {
val: types.StringNull(),
expectError: false,
},
"empty": {
val: types.StringValue(""),
expectError: false,
},
"valid-ipv4": {
val: types.StringValue("192.168.1.0/24"),
expectError: false,
},
"invalid-ipv4": {
val: types.StringValue("192.168.1.0"),
expectError: true,
},
"valid-ipv6": {
val: types.StringValue("2001:db8::/32"),
expectError: false,
},
"invalid-ipv6": {
val: types.StringValue("2001:db8::"),
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.CIDROrEmpty().ValidateString(context.Background(), request, &response)
if test.expectError {
require.NotEmpty(t, response.Diagnostics)
return
}
require.Empty(t, response.Diagnostics)
})
}
}

View File

@@ -0,0 +1,437 @@
package validators
import (
"context"
"fmt"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
)
var (
_ resource.ConfigValidator = &ControllerVersionValidator{}
_ datasource.ConfigValidator = &ControllerVersionValidator{}
_ validator.String = &ControllerVersionValidator{}
_ validator.Bool = &ControllerVersionValidator{}
_ validator.Int64 = &ControllerVersionValidator{}
_ validator.Float64 = &ControllerVersionValidator{}
_ validator.List = &ControllerVersionValidator{}
_ validator.Map = &ControllerVersionValidator{}
_ validator.Object = &ControllerVersionValidator{}
_ validator.Set = &ControllerVersionValidator{}
)
// ControllerVersionValidator is a validator that checks if the UniFi controller version
// matches the specified constraints.
type ControllerVersionValidator struct {
client *base.Client
minVersion *version.Version
maxVersion *version.Version
exactVersion *version.Version
conditionMessage string
}
// Description returns a description of the validator.
func (v ControllerVersionValidator) Description(ctx context.Context) string {
return v.MarkdownDescription(ctx)
}
// MarkdownDescription returns a markdown description of the validator.
func (v ControllerVersionValidator) MarkdownDescription(_ context.Context) string {
if v.exactVersion != nil {
return fmt.Sprintf("Validates that the controller version is exactly %s", v.exactVersion)
}
if v.minVersion != nil && v.maxVersion != nil {
return fmt.Sprintf("Validates that the controller version is between %s and %s", v.minVersion, v.maxVersion)
}
if v.minVersion != nil {
return fmt.Sprintf("Validates that the controller version is at least %s", v.minVersion)
}
if v.maxVersion != nil {
return fmt.Sprintf("Validates that the controller version is at most %s", v.maxVersion)
}
return "Validates the controller version"
}
// ValidateResource validates the resource configuration.
func (v ControllerVersionValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
if v.client == nil || v.client.Version == nil {
resp.Diagnostics.AddWarning("Controller version not available", "Provider was not initialized properly. UniFi client or controller version is not available")
return
}
v.validateVersion(ctx, &resp.Diagnostics)
}
// ValidateDataSource validates the datasource configuration.
func (v ControllerVersionValidator) ValidateDataSource(ctx context.Context, req datasource.ValidateConfigRequest, resp *datasource.ValidateConfigResponse) {
if v.client == nil || v.client.Version == nil {
resp.Diagnostics.AddWarning("Controller version not available", "Provider was not initialized properly. UniFi client or controller version is not available")
return
}
v.validateVersion(ctx, &resp.Diagnostics)
}
// validateVersion checks if the controller version meets the constraints
func (v ControllerVersionValidator) validateVersion(_ context.Context, diags *diag.Diagnostics) {
controllerVersion := v.client.Version
message := v.conditionMessage
if message == "" {
message = "Controller version does not meet requirements"
}
if v.exactVersion != nil && !controllerVersion.Equal(v.exactVersion) {
diags.AddError(
message,
fmt.Sprintf("Controller version %s does not match required version %s", controllerVersion, v.exactVersion),
)
return
}
if v.minVersion != nil && controllerVersion.LessThan(v.minVersion) {
diags.AddError(
message,
fmt.Sprintf("Controller version %s is less than minimum required version %s", controllerVersion, v.minVersion),
)
return
}
if v.maxVersion != nil && controllerVersion.GreaterThan(v.maxVersion) {
diags.AddError(
message,
fmt.Sprintf("Controller version %s is greater than maximum allowed version %s", controllerVersion, v.maxVersion),
)
return
}
}
// validateAttributeVersion is a helper function for attribute validators
func (v ControllerVersionValidator) validateAttributeVersion(ctx context.Context, req path.Path) diag.Diagnostics {
diags := diag.Diagnostics{}
if v.client == nil || v.client.Version == nil {
diags.AddWarning("Controller version not available", "Provider was not initialized properly. UniFi client or controller version is not available")
return diags
}
controllerVersion := v.client.Version
message := v.conditionMessage
if message == "" {
message = "Controller version does not meet requirements"
}
if v.exactVersion != nil && !controllerVersion.Equal(v.exactVersion) {
diags.Append(validatordiag.InvalidAttributeValueDiagnostic(
req,
message,
fmt.Sprintf("Controller version %s does not match required version %s to use given attribute", controllerVersion, v.exactVersion),
))
return diags
}
if v.minVersion != nil && controllerVersion.LessThan(v.minVersion) {
diags.Append(validatordiag.InvalidAttributeValueDiagnostic(
req,
message,
fmt.Sprintf("Controller version %s is less than minimum required version %s to use given attribute", controllerVersion, v.minVersion),
))
return diags
}
if v.maxVersion != nil && controllerVersion.GreaterThan(v.maxVersion) {
diags.Append(validatordiag.InvalidAttributeValueDiagnostic(
req,
message,
fmt.Sprintf("Controller version %s is greater than maximum allowed version %s to use given attribute", controllerVersion, v.maxVersion),
))
return diags
}
return diags
}
// ValidateString implements validator.String
func (v ControllerVersionValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}
resp.Diagnostics.Append(v.validateAttributeVersion(ctx, req.Path)...)
}
// ValidateBool implements validator.Bool
func (v ControllerVersionValidator) ValidateBool(ctx context.Context, req validator.BoolRequest, resp *validator.BoolResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}
resp.Diagnostics.Append(v.validateAttributeVersion(ctx, req.Path)...)
}
// ValidateInt64 implements validator.Int64
func (v ControllerVersionValidator) ValidateInt64(ctx context.Context, req validator.Int64Request, resp *validator.Int64Response) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}
resp.Diagnostics.Append(v.validateAttributeVersion(ctx, req.Path)...)
}
// ValidateFloat64 implements validator.Float64
func (v ControllerVersionValidator) ValidateFloat64(ctx context.Context, req validator.Float64Request, resp *validator.Float64Response) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}
resp.Diagnostics.Append(v.validateAttributeVersion(ctx, req.Path)...)
}
// ValidateList implements validator.List
func (v ControllerVersionValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}
resp.Diagnostics.Append(v.validateAttributeVersion(ctx, req.Path)...)
}
// ValidateMap implements validator.Map
func (v ControllerVersionValidator) ValidateMap(ctx context.Context, req validator.MapRequest, resp *validator.MapResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}
resp.Diagnostics.Append(v.validateAttributeVersion(ctx, req.Path)...)
}
// ValidateObject implements validator.Object
func (v ControllerVersionValidator) ValidateObject(ctx context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}
resp.Diagnostics.Append(v.validateAttributeVersion(ctx, req.Path)...)
}
// ValidateSet implements validator.Set
func (v ControllerVersionValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) {
if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() {
return
}
resp.Diagnostics.Append(v.validateAttributeVersion(ctx, req.Path)...)
}
// ResourceRequireMinVersion returns a resource validator that checks if the controller version
// is at least the specified version.
func ResourceRequireMinVersion(client *base.Client, minVersion string, conditionMessage string) resource.ConfigValidator {
return ControllerVersionValidator{
client: client,
minVersion: base.AsVersion(minVersion),
conditionMessage: conditionMessage,
}
}
// ResourceRequireMaxVersion returns a resource validator that checks if the controller version
// is at most the specified version.
func ResourceRequireMaxVersion(client *base.Client, maxVersion string, conditionMessage string) resource.ConfigValidator {
return ControllerVersionValidator{
client: client,
maxVersion: base.AsVersion(maxVersion),
conditionMessage: conditionMessage,
}
}
// ResourceRequireVersionRange returns a resource validator that checks if the controller version
// is within the specified range (inclusive).
func ResourceRequireVersionRange(client *base.Client, minVersion, maxVersion string, conditionMessage string) resource.ConfigValidator {
return ControllerVersionValidator{
client: client,
minVersion: base.AsVersion(minVersion),
maxVersion: base.AsVersion(maxVersion),
conditionMessage: conditionMessage,
}
}
// ResourceRequireExactVersion returns a resource validator that checks if the controller version
// matches the specified version exactly.
func ResourceRequireExactVersion(client *base.Client, exactVersion string, conditionMessage string) resource.ConfigValidator {
return ControllerVersionValidator{
client: client,
exactVersion: base.AsVersion(exactVersion),
conditionMessage: conditionMessage,
}
}
// DatasourceRequireMinVersion returns a datasource validator that checks if the controller version
// is at least the specified version.
func DatasourceRequireMinVersion(client *base.Client, minVersion string, conditionMessage string) datasource.ConfigValidator {
return ControllerVersionValidator{
client: client,
minVersion: base.AsVersion(minVersion),
conditionMessage: conditionMessage,
}
}
// DatasourceRequireMaxVersion returns a datasource validator that checks if the controller version
// is at most the specified version.
func DatasourceRequireMaxVersion(client *base.Client, maxVersion string, conditionMessage string) datasource.ConfigValidator {
return ControllerVersionValidator{
client: client,
maxVersion: base.AsVersion(maxVersion),
conditionMessage: conditionMessage,
}
}
// DatasourceRequireVersionRange returns a datasource validator that checks if the controller version
// is within the specified range (inclusive).
func DatasourceRequireVersionRange(client *base.Client, minVersion, maxVersion string, conditionMessage string) datasource.ConfigValidator {
return ControllerVersionValidator{
client: client,
minVersion: base.AsVersion(minVersion),
maxVersion: base.AsVersion(maxVersion),
conditionMessage: conditionMessage,
}
}
// DatasourceRequireExactVersion returns a datasource validator that checks if the controller version
// matches the specified version exactly.
func DatasourceRequireExactVersion(client *base.Client, exactVersion string, conditionMessage string) datasource.ConfigValidator {
return ControllerVersionValidator{
client: client,
exactVersion: base.AsVersion(exactVersion),
conditionMessage: conditionMessage,
}
}
// StringRequireMinVersion returns a string validator that checks if the controller version
// is at least the specified version.
func StringRequireMinVersion(client *base.Client, minVersion string, conditionMessage string) validator.String {
return ControllerVersionValidator{
client: client,
minVersion: base.AsVersion(minVersion),
conditionMessage: conditionMessage,
}
}
// StringRequireMaxVersion returns a string validator that checks if the controller version
// is at most the specified version.
func StringRequireMaxVersion(client *base.Client, maxVersion string, conditionMessage string) validator.String {
return ControllerVersionValidator{
client: client,
maxVersion: base.AsVersion(maxVersion),
conditionMessage: conditionMessage,
}
}
// StringRequireVersionRange returns a string validator that checks if the controller version
// is within the specified range (inclusive).
func StringRequireVersionRange(client *base.Client, minVersion, maxVersion string, conditionMessage string) validator.String {
return ControllerVersionValidator{
client: client,
minVersion: base.AsVersion(minVersion),
maxVersion: base.AsVersion(maxVersion),
conditionMessage: conditionMessage,
}
}
// StringRequireExactVersion returns a string validator that checks if the controller version
// matches the specified version exactly.
func StringRequireExactVersion(client *base.Client, exactVersion string, conditionMessage string) validator.String {
return ControllerVersionValidator{
client: client,
exactVersion: base.AsVersion(exactVersion),
conditionMessage: conditionMessage,
}
}
// BoolRequireMinVersion returns a bool validator that checks if the controller version
// is at least the specified version.
func BoolRequireMinVersion(client *base.Client, minVersion string, conditionMessage string) validator.Bool {
return ControllerVersionValidator{
client: client,
minVersion: base.AsVersion(minVersion),
conditionMessage: conditionMessage,
}
}
// BoolRequireMaxVersion returns a bool validator that checks if the controller version
// is at most the specified version.
func BoolRequireMaxVersion(client *base.Client, maxVersion string, conditionMessage string) validator.Bool {
return ControllerVersionValidator{
client: client,
maxVersion: base.AsVersion(maxVersion),
conditionMessage: conditionMessage,
}
}
// BoolRequireVersionRange returns a bool validator that checks if the controller version
// is within the specified range (inclusive).
func BoolRequireVersionRange(client *base.Client, minVersion, maxVersion string, conditionMessage string) validator.Bool {
return ControllerVersionValidator{
client: client,
minVersion: base.AsVersion(minVersion),
maxVersion: base.AsVersion(maxVersion),
conditionMessage: conditionMessage,
}
}
// BoolRequireExactVersion returns a bool validator that checks if the controller version
// matches the specified version exactly.
func BoolRequireExactVersion(client *base.Client, exactVersion string, conditionMessage string) validator.Bool {
return ControllerVersionValidator{
client: client,
exactVersion: base.AsVersion(exactVersion),
conditionMessage: conditionMessage,
}
}
// Int64RequireMinVersion returns an int64 validator that checks if the controller version
// is at least the specified version.
func Int64RequireMinVersion(client *base.Client, minVersion string, conditionMessage string) validator.Int64 {
return ControllerVersionValidator{
client: client,
minVersion: base.AsVersion(minVersion),
conditionMessage: conditionMessage,
}
}
// Int64RequireMaxVersion returns an int64 validator that checks if the controller version
// is at most the specified version.
func Int64RequireMaxVersion(client *base.Client, maxVersion string, conditionMessage string) validator.Int64 {
return ControllerVersionValidator{
client: client,
maxVersion: base.AsVersion(maxVersion),
conditionMessage: conditionMessage,
}
}
// Int64RequireVersionRange returns an int64 validator that checks if the controller version
// is within the specified range (inclusive).
func Int64RequireVersionRange(client *base.Client, minVersion, maxVersion string, conditionMessage string) validator.Int64 {
return ControllerVersionValidator{
client: client,
minVersion: base.AsVersion(minVersion),
maxVersion: base.AsVersion(maxVersion),
conditionMessage: conditionMessage,
}
}
// Int64RequireExactVersion returns an int64 validator that checks if the controller version
// matches the specified version exactly.
func Int64RequireExactVersion(client *base.Client, exactVersion string, conditionMessage string) validator.Int64 {
return ControllerVersionValidator{
client: client,
exactVersion: base.AsVersion(exactVersion),
conditionMessage: conditionMessage,
}
}

View File

@@ -0,0 +1,375 @@
package validators
import (
"context"
"testing"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/stretchr/testify/assert"
)
func TestControllerVersionValidator_Description(t *testing.T) {
tests := []struct {
name string
validator ControllerVersionValidator
expected string
description string
}{
{
name: "exact version",
validator: ControllerVersionValidator{
exactVersion: base.AsVersion("7.0.0"),
},
expected: "Validates that the controller version is exactly 7.0.0",
description: "Should describe exact version check",
},
{
name: "min version",
validator: ControllerVersionValidator{
minVersion: base.AsVersion("7.0.0"),
},
expected: "Validates that the controller version is at least 7.0.0",
description: "Should describe minimum version check",
},
{
name: "max version",
validator: ControllerVersionValidator{
maxVersion: base.AsVersion("7.0.0"),
},
expected: "Validates that the controller version is at most 7.0.0",
description: "Should describe maximum version check",
},
{
name: "version range",
validator: ControllerVersionValidator{
minVersion: base.AsVersion("7.0.0"),
maxVersion: base.AsVersion("8.0.0"),
},
expected: "Validates that the controller version is between 7.0.0 and 8.0.0",
description: "Should describe version range check",
},
{
name: "no constraint",
validator: ControllerVersionValidator{},
expected: "Validates the controller version",
description: "Should provide generic description when no constraints",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := context.Background()
actual := test.validator.Description(ctx)
assert.Equal(t, test.expected, actual, test.description)
})
}
}
func TestControllerVersionValidator_ValidateResource(t *testing.T) {
tests := []struct {
name string
controllerVersion string
validator ControllerVersionValidator
expectError bool
description string
}{
{
name: "exact version match",
controllerVersion: "7.0.0",
validator: ControllerVersionValidator{
exactVersion: base.AsVersion("7.0.0"),
},
expectError: false,
description: "Should pass when exact version matches",
},
{
name: "exact version mismatch",
controllerVersion: "7.0.0",
validator: ControllerVersionValidator{
exactVersion: base.AsVersion("7.1.0"),
},
expectError: true,
description: "Should fail when exact version doesn't match",
},
{
name: "min version satisfied",
controllerVersion: "7.5.0",
validator: ControllerVersionValidator{
minVersion: base.AsVersion("7.0.0"),
},
expectError: false,
description: "Should pass when version meets minimum",
},
{
name: "min version not satisfied",
controllerVersion: "6.5.0",
validator: ControllerVersionValidator{
minVersion: base.AsVersion("7.0.0"),
},
expectError: true,
description: "Should fail when version doesn't meet minimum",
},
{
name: "max version satisfied",
controllerVersion: "6.5.0",
validator: ControllerVersionValidator{
maxVersion: base.AsVersion("7.0.0"),
},
expectError: false,
description: "Should pass when version is below maximum",
},
{
name: "max version not satisfied",
controllerVersion: "7.5.0",
validator: ControllerVersionValidator{
maxVersion: base.AsVersion("7.0.0"),
},
expectError: true,
description: "Should fail when version exceeds maximum",
},
{
name: "version in range",
controllerVersion: "7.5.0",
validator: ControllerVersionValidator{
minVersion: base.AsVersion("7.0.0"),
maxVersion: base.AsVersion("8.0.0"),
},
expectError: false,
description: "Should pass when version is in range",
},
{
name: "version below range",
controllerVersion: "6.5.0",
validator: ControllerVersionValidator{
minVersion: base.AsVersion("7.0.0"),
maxVersion: base.AsVersion("8.0.0"),
},
expectError: true,
description: "Should fail when version is below range",
},
{
name: "version above range",
controllerVersion: "8.5.0",
validator: ControllerVersionValidator{
minVersion: base.AsVersion("7.0.0"),
maxVersion: base.AsVersion("8.0.0"),
},
expectError: true,
description: "Should fail when version is above range",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := context.Background()
// Create a mock client with the specified version
mockClient := &base.Client{
Version: version.Must(version.NewVersion(test.controllerVersion)),
}
// Update the validator with the mock client
test.validator.client = mockClient
// Create request and response objects
req := resource.ValidateConfigRequest{}
resp := resource.ValidateConfigResponse{
Diagnostics: diag.Diagnostics{},
}
// Call the validator
test.validator.ValidateResource(ctx, req, &resp)
// Check if the result matches expectations
if test.expectError {
assert.True(t, resp.Diagnostics.HasError(), test.description)
} else {
assert.False(t, resp.Diagnostics.HasError(), test.description)
}
})
}
}
func TestResourceHelperFunctions(t *testing.T) {
mockClient := &base.Client{
Version: base.AsVersion("7.5.0"),
}
tests := []struct {
name string
validator resource.ConfigValidator
expectError bool
description string
}{
{
name: "ResourceRequireMinVersion passing",
validator: ResourceRequireMinVersion(mockClient, "7.0.0", ""),
expectError: false,
description: "ResourceRequireMinVersion should pass with sufficient version",
},
{
name: "ResourceRequireMinVersion failing",
validator: ResourceRequireMinVersion(mockClient, "8.0.0", ""),
expectError: true,
description: "ResourceRequireMinVersion should fail with insufficient version",
},
{
name: "ResourceRequireMaxVersion passing",
validator: ResourceRequireMaxVersion(mockClient, "8.0.0", ""),
expectError: false,
description: "ResourceRequireMaxVersion should pass with acceptable version",
},
{
name: "ResourceRequireMaxVersion failing",
validator: ResourceRequireMaxVersion(mockClient, "7.0.0", ""),
expectError: true,
description: "ResourceRequireMaxVersion should fail with too high version",
},
{
name: "ResourceRequireVersionRange passing",
validator: ResourceRequireVersionRange(mockClient, "7.0.0", "8.0.0", ""),
expectError: false,
description: "ResourceRequireVersionRange should pass with version in range",
},
{
name: "ResourceRequireVersionRange failing (below)",
validator: ResourceRequireVersionRange(mockClient, "7.6.0", "8.0.0", ""),
expectError: true,
description: "ResourceRequireVersionRange should fail with version below range",
},
{
name: "ResourceRequireVersionRange failing (above)",
validator: ResourceRequireVersionRange(mockClient, "6.0.0", "7.0.0", ""),
expectError: true,
description: "ResourceRequireVersionRange should fail with version above range",
},
{
name: "ResourceRequireExactVersion passing",
validator: ResourceRequireExactVersion(mockClient, "7.5.0", ""),
expectError: false,
description: "ResourceRequireExactVersion should pass with exact version match",
},
{
name: "ResourceRequireExactVersion failing",
validator: ResourceRequireExactVersion(mockClient, "7.5.1", ""),
expectError: true,
description: "ResourceRequireExactVersion should fail with version mismatch",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := context.Background()
// Create request and response objects
req := resource.ValidateConfigRequest{}
resp := resource.ValidateConfigResponse{
Diagnostics: diag.Diagnostics{},
}
// Call the validator
test.validator.(ControllerVersionValidator).ValidateResource(ctx, req, &resp)
// Check if the result matches expectations
if test.expectError {
assert.True(t, resp.Diagnostics.HasError(), test.description)
} else {
assert.False(t, resp.Diagnostics.HasError(), test.description)
}
})
}
}
func TestDatasourceHelperFunctions(t *testing.T) {
mockClient := &base.Client{
Version: base.AsVersion("7.5.0"),
}
tests := []struct {
name string
validator datasource.ConfigValidator
expectError bool
description string
}{
{
name: "DatasourceRequireMinVersion passing",
validator: DatasourceRequireMinVersion(mockClient, "7.0.0", ""),
expectError: false,
description: "DatasourceRequireMinVersion should pass with sufficient version",
},
{
name: "DatasourceRequireMinVersion failing",
validator: DatasourceRequireMinVersion(mockClient, "8.0.0", ""),
expectError: true,
description: "DatasourceRequireMinVersion should fail with insufficient version",
},
{
name: "DatasourceRequireMaxVersion passing",
validator: DatasourceRequireMaxVersion(mockClient, "8.0.0", ""),
expectError: false,
description: "DatasourceRequireMaxVersion should pass with acceptable version",
},
{
name: "DatasourceRequireMaxVersion failing",
validator: DatasourceRequireMaxVersion(mockClient, "7.0.0", ""),
expectError: true,
description: "DatasourceRequireMaxVersion should fail with too high version",
},
{
name: "DatasourceRequireVersionRange passing",
validator: DatasourceRequireVersionRange(mockClient, "7.0.0", "8.0.0", ""),
expectError: false,
description: "DatasourceRequireVersionRange should pass with version in range",
},
{
name: "DatasourceRequireVersionRange failing (below)",
validator: DatasourceRequireVersionRange(mockClient, "7.6.0", "8.0.0", ""),
expectError: true,
description: "DatasourceRequireVersionRange should fail with version below range",
},
{
name: "DatasourceRequireVersionRange failing (above)",
validator: DatasourceRequireVersionRange(mockClient, "6.0.0", "7.0.0", ""),
expectError: true,
description: "DatasourceRequireVersionRange should fail with version above range",
},
{
name: "DatasourceRequireExactVersion passing",
validator: DatasourceRequireExactVersion(mockClient, "7.5.0", ""),
expectError: false,
description: "DatasourceRequireExactVersion should pass with exact version match",
},
{
name: "DatasourceRequireExactVersion failing",
validator: DatasourceRequireExactVersion(mockClient, "7.5.1", ""),
expectError: true,
description: "DatasourceRequireExactVersion should fail with version mismatch",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := context.Background()
// Create request and response objects
req := datasource.ValidateConfigRequest{}
resp := datasource.ValidateConfigResponse{
Diagnostics: diag.Diagnostics{},
}
// Call the validator
test.validator.(ControllerVersionValidator).ValidateDataSource(ctx, req, &resp)
// Check if the result matches expectations
if test.expectError {
assert.True(t, resp.Diagnostics.HasError(), test.description)
} else {
assert.False(t, resp.Diagnostics.HasError(), test.description)
}
})
}
}