feat: support checking supported and enabled controller features (#41)
* feat: support checking supported and enabled controller features * linting
This commit is contained in:
committed by
GitHub
parent
4e6e9d97b7
commit
a5955a6358
@@ -2,6 +2,7 @@
|
||||
customizations:
|
||||
client:
|
||||
excludeResources:
|
||||
- "DescribedFeature"
|
||||
- "Dpi*"
|
||||
- "FirewallZoneMatrix"
|
||||
functions:
|
||||
@@ -296,6 +297,43 @@ customizations:
|
||||
returns:
|
||||
- "[]FirewallZoneMatrix"
|
||||
- "error"
|
||||
- name: "ListFeatures"
|
||||
resourceName: "DescribedFeature"
|
||||
comment: "ListFeatures returns all features of the UniFi controller."
|
||||
params:
|
||||
- name: "ctx"
|
||||
type: "context.Context"
|
||||
- name: "site"
|
||||
type: "string"
|
||||
returns:
|
||||
- "[]DescribedFeature"
|
||||
- "error"
|
||||
- name: "GetFeature"
|
||||
resourceName: "DescribedFeature"
|
||||
comment: "GetFeature returns a specific feature by it's name. Name is case-insensitive."
|
||||
params:
|
||||
- name: "ctx"
|
||||
type: "context.Context"
|
||||
- name: "site"
|
||||
type: "string"
|
||||
- name: "name"
|
||||
type: "string"
|
||||
returns:
|
||||
- "*DescribedFeature"
|
||||
- "error"
|
||||
- name: "IsFeatureEnabled"
|
||||
resourceName: "DescribedFeature"
|
||||
comment: "IsFeatureEnabled returns if a specific feature is enabled by it's name. Name is case-insensitive."
|
||||
params:
|
||||
- name: "ctx"
|
||||
type: "context.Context"
|
||||
- name: "site"
|
||||
type: "string"
|
||||
- name: "name"
|
||||
type: "string"
|
||||
returns:
|
||||
- "bool"
|
||||
- "error"
|
||||
resources:
|
||||
Account:
|
||||
fields:
|
||||
@@ -318,6 +356,8 @@ customizations:
|
||||
customUnmarshalType: "numberOrString"
|
||||
DNSRecord:
|
||||
resourcePath: "static-dns"
|
||||
DescribedFeature:
|
||||
resourcePath: "described-features?includeSystemFeatures=true" # TODO hack to get all features, because query params in requests are not yet supported
|
||||
Device:
|
||||
fields:
|
||||
_all:
|
||||
|
||||
4
codegen/v2/DescribedFeature.json
Normal file
4
codegen/v2/DescribedFeature.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"feature_exists": "true|false",
|
||||
"name": ""
|
||||
}
|
||||
@@ -114,3 +114,34 @@ for _, network := range networks {
|
||||
fmt.Printf("Network: %s\n", network.Name)
|
||||
}
|
||||
```
|
||||
|
||||
## Checking if features are supported and enabled
|
||||
|
||||
The UniFi Go SDK provides a way to check if a feature is supported and enabled/disabled on the UniFi Controller.
|
||||
This can be useful when you want to check if a feature is available before using it. Passed feature names are case-insensitive.
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
if c.IsFeatureEnabled(ctx, "default", "feature-name") {
|
||||
// Feature is enabled
|
||||
} else {
|
||||
// Feature is disabled
|
||||
}
|
||||
```
|
||||
|
||||
Library comes with a set of predefined feature names, which can be found in `github.com/filipowm/go-unifi/unifi/features` module. You can also use custom feature names.
|
||||
|
||||
For example, you can check if the `features.ZoneBasedFirewallMigration` is available on the controller (no `unifi.ErrNotFound` raised) and enabled:
|
||||
```go
|
||||
f, err := c.GetFeature(ctx, "default", features.ZoneBasedFirewallMigration)
|
||||
if err != nil {
|
||||
if errors.Is(err, unifi.ErrNotFound) {
|
||||
log.Printf("Feature %s unavailable (not found)", features.ZoneBasedFirewallMigration)
|
||||
} else {
|
||||
log.Fatalf("Error getting feature: %v", err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return f.FeatureExists // `FeatureExists` is a boolean indicating if the feature is enabled
|
||||
```
|
||||
|
||||
9
unifi/client.generated.go
generated
9
unifi/client.generated.go
generated
@@ -170,6 +170,15 @@ type Client interface {
|
||||
|
||||
// ==== end of client methods for Dashboard resource ====
|
||||
|
||||
// GetFeature returns a specific feature by it's name. Name is case-insensitive.
|
||||
GetFeature(ctx context.Context, site string, name string) (*DescribedFeature, error)
|
||||
|
||||
// IsFeatureEnabled returns if a specific feature is enabled by it's name. Name is case-insensitive.
|
||||
IsFeatureEnabled(ctx context.Context, site string, name string) (bool, error)
|
||||
|
||||
// ListFeatures returns all features of the UniFi controller.
|
||||
ListFeatures(ctx context.Context, site string) ([]DescribedFeature, error)
|
||||
|
||||
// ==== client methods for Device resource ====
|
||||
|
||||
// AdoptDevice adopts a device by MAC address.
|
||||
|
||||
100
unifi/described_feature.generated.go
generated
Normal file
100
unifi/described_feature.generated.go
generated
Normal file
@@ -0,0 +1,100 @@
|
||||
// Code generated from ace.jar fields *.json files
|
||||
// DO NOT EDIT.
|
||||
|
||||
package unifi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// just to fix compile issues with the import
|
||||
var (
|
||||
_ context.Context
|
||||
_ fmt.Formatter
|
||||
_ json.Marshaler
|
||||
)
|
||||
|
||||
type DescribedFeature struct {
|
||||
ID string `json:"_id,omitempty"`
|
||||
SiteID string `json:"site_id,omitempty"`
|
||||
|
||||
Hidden bool `json:"attr_hidden,omitempty"`
|
||||
HiddenID string `json:"attr_hidden_id,omitempty"`
|
||||
NoDelete bool `json:"attr_no_delete,omitempty"`
|
||||
NoEdit bool `json:"attr_no_edit,omitempty"`
|
||||
|
||||
FeatureExists bool `json:"feature_exists"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
func (dst *DescribedFeature) UnmarshalJSON(b []byte) error {
|
||||
type Alias DescribedFeature
|
||||
aux := &struct {
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(dst),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(b, &aux)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to unmarshal alias: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) listDescribedFeature(ctx context.Context, site string) ([]DescribedFeature, error) {
|
||||
var respBody []DescribedFeature
|
||||
|
||||
err := c.Get(ctx, fmt.Sprintf("%s/site/%s/described-features?includeSystemFeatures=true", c.apiPaths.ApiV2Path, site), nil, &respBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return respBody, nil
|
||||
}
|
||||
|
||||
func (c *client) getDescribedFeature(ctx context.Context, site, id string) (*DescribedFeature, error) {
|
||||
var respBody DescribedFeature
|
||||
|
||||
err := c.Get(ctx, fmt.Sprintf("%s/site/%s/described-features?includeSystemFeatures=true/%s", c.apiPaths.ApiV2Path, site, id), nil, &respBody)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if respBody.ID == "" {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return &respBody, nil
|
||||
}
|
||||
|
||||
func (c *client) deleteDescribedFeature(ctx context.Context, site, id string) error {
|
||||
err := c.Delete(ctx, fmt.Sprintf("%s/site/%s/described-features?includeSystemFeatures=true/%s", c.apiPaths.ApiV2Path, site, id), struct{}{}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) createDescribedFeature(ctx context.Context, site string, d *DescribedFeature) (*DescribedFeature, error) {
|
||||
var respBody DescribedFeature
|
||||
|
||||
err := c.Post(ctx, fmt.Sprintf("%s/site/%s/described-features?includeSystemFeatures=true", c.apiPaths.ApiV2Path, site), d, &respBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &respBody, nil
|
||||
}
|
||||
|
||||
func (c *client) updateDescribedFeature(ctx context.Context, site string, d *DescribedFeature) (*DescribedFeature, error) {
|
||||
var respBody DescribedFeature
|
||||
|
||||
err := c.Put(ctx, fmt.Sprintf("%s/site/%s/described-features?includeSystemFeatures=true/%s", c.apiPaths.ApiV2Path, site, d.ID), d, &respBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &respBody, nil
|
||||
}
|
||||
32
unifi/described_feature.go
Normal file
32
unifi/described_feature.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package unifi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *client) ListFeatures(ctx context.Context, site string) ([]DescribedFeature, error) {
|
||||
return c.listDescribedFeature(ctx, site)
|
||||
}
|
||||
|
||||
func (c *client) GetFeature(ctx context.Context, site string, name string) (*DescribedFeature, error) {
|
||||
features, err := c.ListFeatures(ctx, site)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lowerName := strings.ToLower(name)
|
||||
for _, f := range features {
|
||||
if strings.ToLower(f.Name) == lowerName {
|
||||
return &f, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
func (c *client) IsFeatureEnabled(ctx context.Context, site string, name string) (bool, error) {
|
||||
f, err := c.GetFeature(ctx, site, name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return f.FeatureExists, nil
|
||||
}
|
||||
74
unifi/features/const.go
Normal file
74
unifi/features/const.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package features
|
||||
|
||||
const (
|
||||
AdBlocking = "AD_BLOCKING"
|
||||
AllUnifiDevicesPage = "ALL_UNIFI_DEVICES_PAGE"
|
||||
CustomDohServers = "CUSTOM_DOH_SERVERS"
|
||||
Hotspot2Passpoint = "HOTSPOT2_PASSPOINT"
|
||||
IgmpProxy = "IGMP_PROXY"
|
||||
IpExclusionFromLeases = "IP_EXCLUSION_FROM_LEASES"
|
||||
Ips = "IPS"
|
||||
IpsEtPro = "IPS_ET_PRO"
|
||||
IpsSignatureReport = "IPS_SIGNATURE_REPORT"
|
||||
IpsecFqdn = "IPSEC_FQDN"
|
||||
Ipv4ActiveLeaseReporting = "IPV4_ACTIVE_LEASE_REPORTING"
|
||||
LegacyUiSupported = "LEGACY_UI_SUPPORTED"
|
||||
LimitIpsCategories = "LIMIT_IPS_CATEGORIES"
|
||||
LiveDeviceUpdates = "LIVE_DEVICE_UPDATES"
|
||||
LockAp = "LOCK_AP"
|
||||
NatPool = "NAT_POOL"
|
||||
Netflow = "NETFLOW"
|
||||
OspfDefaultRouteAnnouncement = "OSPF_DEFAULT_ROUTE_ANNOUNCEMENT"
|
||||
OspfRouting = "OSPF_ROUTING"
|
||||
OpenVpnClient = "OPENVPN_CLIENT"
|
||||
OpenVpnClientTrafficRoutes = "OPENVPN_CLIENT_TRAFFIC_ROUTES"
|
||||
OpenVpnEncryptionCiphers = "OPENVPN_ENCRYPTION_CIPHERS"
|
||||
OpenVpnRemoteDisconnect = "OPENVPN_REMOTE_DISCONNECT"
|
||||
OpenVpnServer = "OPENVPN_SERVER"
|
||||
RadiusBatchUsers = "RADIUS_BATCH_USERS"
|
||||
RadiusProfiles = "RADIUS_PROFILES"
|
||||
RadiusServer = "RADIUS_SERVER"
|
||||
ScorePage = "SCORE_PAGE"
|
||||
SdwanHubSpoke = "SDWAN_HUB_SPOKE"
|
||||
SdwanMesh = "SDWAN_MESH"
|
||||
SpeedTest = "SPEED_TEST"
|
||||
StaticDns = "STATIC_DNS"
|
||||
SwitchBgpRouting = "SWITCH_BGP_ROUTING"
|
||||
SwitchCustomAclRules = "SWITCH_CUSTOM_ACL_RULES"
|
||||
SwitchGlobalAclRules = "SWITCH_GLOBAL_ACL_RULES"
|
||||
Teleport = "TELEPORT"
|
||||
TrafficMap = "TRAFFIC_MAP"
|
||||
TrafficRouteKillSwitch = "TRAFFIC_ROUTE_KILL_SWITCH"
|
||||
TrafficRoutes = "TRAFFIC_ROUTES"
|
||||
TrafficRoutesIpsecS2sVpn = "TRAFFIC_ROUTES_IPSEC_S2S_VPN"
|
||||
TrafficRoutesOpenVpnS2sVpn = "TRAFFIC_ROUTES_OPENVPN_S2S_VPN"
|
||||
TrafficRuleAndRouteRegions = "TRAFFIC_RULE_AND_ROUTE_REGIONS"
|
||||
TrafficRuleRateLimiting = "TRAFFIC_RULE_RATE_LIMITING"
|
||||
TrafficRuleSchedules = "TRAFFIC_RULE_SCHEDULES"
|
||||
UdapiGetBlocks = "UDAPI_GET_BLOCKS"
|
||||
UcoreAutolinkDeviceUpdates = "UCORE_AUTOLINK_DEVICE_UPDATES"
|
||||
UcorePartialDeviceUpdates = "UCORE_PARTIAL_DEVICE_UPDATES"
|
||||
UidRadius = "UID_RADIUS"
|
||||
UidRadiusGroupPolicy = "UID_RADIUS_GROUP_POLICY"
|
||||
UidVpn = "UID_VPN"
|
||||
UidVpnAllowWanLocal = "UID_VPN_ALLOW_WAN_LOCAL"
|
||||
UidVpnOverrideDns = "UID_VPN_OVERRIDE_DNS"
|
||||
UidVpnStrictClientCommonName = "UID_VPN_STRICT_CLIENT_COMMON_NAME"
|
||||
UidVpnSupportUdp = "UID_VPN_SUPPORT_UDP"
|
||||
UidWifi = "UID_WIFI"
|
||||
UidWifiIot = "UID_WIFI_IOT"
|
||||
UidWifiRadiusGroupPolicy = "UID_WIFI_RADIUS_GROUP_POLICY"
|
||||
UnboundWanMonitor = "UNBOUND_WAN_MONITOR"
|
||||
UserDefinedNatRules = "USER_DEFINED_NAT_RULES"
|
||||
VisualProgramming = "VISUAL_PROGRAMMING"
|
||||
WanDhcpRequestCos = "WAN_DHCP_REQUEST_COS"
|
||||
WanDhcpv6Stateless = "WAN_DHCPV6_STATELESS"
|
||||
WanDsLite = "WAN_DS_LITE"
|
||||
WanLoadBalancingDistributedMode = "WAN_LOAD_BALANCING_DISTRIBUTED_MODE"
|
||||
WifiConfigCreated = "WIFI_CONFIG_CREATED"
|
||||
WifiMimo = "WIFI_MIMO"
|
||||
WireguardVpnClient = "WIREGUARD_VPN_CLIENT"
|
||||
WireguardVpnServer = "WIREGUARD_VPN_SERVER"
|
||||
ZoneBasedFirewall = "ZONE_BASED_FIREWALL"
|
||||
ZoneBasedFirewallMigration = "ZONE_BASED_FIREWALL_MIGRATION"
|
||||
)
|
||||
Reference in New Issue
Block a user