Files
terraform-provider-unifi/internal/provider/base/features.go
Mateusz Filipowicz e9600c6e06 feat: add support for Intrution Prevention System (IPS) settings with unifi_setting_ips resource (#56)
* feat: add support for Intrution Prevention System (IPS) settings with `unifi_setting_ips` resource

* require IPS features enabled on controller

* require version 7.4

* require version 7.5 for advanced_filtering_preference

* feat: use Remember Me to prolong session for user/pass authentication

* run some setting mgmt tests on 7.0+ due to auto_upgrade_hour not working until device is adopted and auto upgrade logic is different and not supported
2025-03-16 12:53:46 +01:00

128 lines
3.7 KiB
Go

package base
import (
"context"
"fmt"
"strings"
"sync"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
)
const (
featureEnabled featureStatus = iota
featureDisabled
)
type featureStatus int
type FeatureValidator interface {
RequireFeaturesEnabled(ctx context.Context, site string, features ...string) diag.Diagnostics
RequireFeaturesEnabledForPath(ctx context.Context, site string, attrPath path.Path, config tfsdk.Config, features ...string) diag.Diagnostics
}
type Features map[string]featureStatus
func (v Features) IsEnabled(feature string) bool {
return !v.IsUnavailable(feature) && v[feature] == featureEnabled
}
func (v Features) IsDisabled(feature string) bool {
return !v.IsUnavailable(feature) && v[feature] == featureDisabled
}
func (v Features) IsUnavailable(feature string) bool {
if _, ok := v[feature]; ok {
return false
}
return true
}
type featureEnabledValidator struct {
client *Client
cache map[string]Features
lock sync.Mutex
}
func NewFeatureValidator(client *Client) FeatureValidator {
return &featureEnabledValidator{client: client, cache: make(map[string]Features), lock: sync.Mutex{}}
}
func (v *featureEnabledValidator) getFeatures(ctx context.Context, site string) Features {
if v.cache[site] != nil {
return v.cache[site]
}
v.lock.Lock()
defer v.lock.Unlock()
if v.cache[site] != nil {
return v.cache[site]
}
cache := make(map[string]featureStatus)
features, err := v.client.ListFeatures(ctx, site)
if err != nil {
// Return an empty Features map instead of nil to avoid potential nil pointer dereference
return Features{}
}
for _, feature := range features {
if feature.FeatureExists {
cache[feature.Name] = featureEnabled
} else {
cache[feature.Name] = featureDisabled
}
}
v.cache[site] = cache
return v.cache[site]
}
func (v *featureEnabledValidator) requireFeatures(ctx context.Context, site string, attrPath *path.Path, features ...string) diag.Diagnostics {
diags := diag.Diagnostics{}
if len(features) == 0 {
return diags
}
f := v.getFeatures(ctx, site)
var unavailableFeatures, disabledFeatures []string
for _, feature := range features {
if f.IsUnavailable(feature) {
unavailableFeatures = append(unavailableFeatures, feature)
}
if f.IsDisabled(feature) {
disabledFeatures = append(disabledFeatures, feature)
}
}
pathInfo := ""
if attrPath != nil {
pathInfo = fmt.Sprintf("%s is not supported. ", attrPath.String())
}
if len(unavailableFeatures) > 0 {
diags.AddError("Controller features not available", fmt.Sprintf("%sFeatures %s must be available on controller, but %s are not", pathInfo, strings.Join(features, ", "), strings.Join(unavailableFeatures, ", ")))
}
if len(disabledFeatures) > 0 {
diags.AddError("Controller features not disabled", fmt.Sprintf("%sFeatures %s must be enabled on controller, but %s are disabled", pathInfo, strings.Join(features, ", "), strings.Join(disabledFeatures, ", ")))
}
return diags
}
func (v *featureEnabledValidator) RequireFeaturesEnabled(ctx context.Context, site string, features ...string) diag.Diagnostics {
return v.requireFeatures(ctx, site, nil, features...)
}
func (v *featureEnabledValidator) RequireFeaturesEnabledForPath(ctx context.Context, site string, attrPath path.Path, config tfsdk.Config, features ...string) diag.Diagnostics {
diags := diag.Diagnostics{}
var val attr.Value
diags.Append(config.GetAttribute(context.Background(), attrPath, &val)...)
if diags.HasError() {
return diags
}
if !IsDefined(val) {
return diags
}
diags.Append(v.requireFeatures(ctx, site, &attrPath, features...)...)
return diags
}