docs: improve provider documentation (#29)

* docs: improve provider documentation

* fix accidentally changed type of stormctrl_ucast_rate

* docs: add badges and plans to readme
This commit is contained in:
Mateusz Filipowicz
2025-02-26 18:56:45 +01:00
committed by GitHub
parent e5e50f98c0
commit b1688313c0
49 changed files with 2454 additions and 900 deletions

View File

@@ -1,4 +1,4 @@
package network
package device
import (
"context"
@@ -9,27 +9,31 @@ import (
func DataPortProfile() *schema.Resource {
return &schema.Resource{
Description: "`unifi_port_profile` data source can be used to retrieve the ID for a port profile by name.",
Description: "`unifi_port_profile` data source can be used to retrieve port profile configurations from your UniFi network. " +
"Port profiles define settings and behaviors for switch ports, including VLANs, PoE settings, and other port-specific configurations. " +
"This data source is particularly useful when you need to reference existing port profiles in switch port configurations.",
ReadContext: dataPortProfileRead,
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of this port profile.",
Type: schema.TypeString,
Computed: true,
Description: "The unique identifier of the port profile. This is automatically assigned by UniFi and can be used " +
"to reference this port profile in other resources.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site the port profile is associated with.",
Description: "The name of the UniFi site where the port profile is configured. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
},
"name": {
Description: "The name of the port profile to look up.",
Type: schema.TypeString,
Optional: true,
Default: "All",
Description: "The name of the port profile to look up. This is the friendly name assigned to the profile in the UniFi controller. " +
"Defaults to \"All\" if not specified, which is the default port profile in UniFi.",
Type: schema.TypeString,
Optional: true,
Default: "All",
},
},
}

View File

@@ -4,12 +4,13 @@ import (
"context"
"errors"
"fmt"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/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/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
@@ -19,10 +20,11 @@ import (
func ResourceDevice() *schema.Resource {
return &schema.Resource{
Description: "`unifi_device` manages a device of the network.\n\n" +
"Devices are adopted by the controller, so it is not possible for this resource to be created through " +
"Terraform, the create operation instead will simply start managing the device specified by MAC address. " +
"It's safer to start this process with an explicit import of the device.",
Description: "The `unifi_device` resource manages UniFi network devices such as access points, switches, gateways, etc.\n\n" +
"Devices must first be adopted by the UniFi controller before they can be managed through Terraform. " +
"This resource cannot create new devices, but instead allows you to manage existing devices that have already been adopted. " +
"The recommended approach is to adopt devices through the UniFi controller UI first, then import them into Terraform using the device's MAC address.\n\n" +
"This resource supports managing device names, port configurations, and other device-specific settings.",
CreateContext: resourceDeviceCreate,
ReadContext: resourceDeviceRead,
@@ -34,19 +36,19 @@ func ResourceDevice() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the device.",
Description: "The unique identifier of the device in the UniFi controller.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the device with.",
Description: "The name of the UniFi site where the device is located. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"mac": {
Description: "The MAC address of the device. This can be specified so that the provider can take control of a device (since devices are created through adoption).",
Description: "The MAC address of the device in standard format (e.g., 'aa:bb:cc:dd:ee:ff'). This is used to identify and manage specific devices that have already been adopted by the controller.",
Type: schema.TypeString,
Optional: true,
Computed: true,
@@ -55,41 +57,66 @@ func ResourceDevice() *schema.Resource {
ValidateFunc: validation.StringMatch(utils.MacAddressRegexp, "Mac address is invalid"),
},
"name": {
Description: "The name of the device.",
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "A friendly name for the device that will be displayed in the UniFi controller UI. Examples:\n" +
"* 'Office-AP-1' for an access point\n" +
"* 'Core-Switch-01' for a switch\n" +
"* 'Main-Gateway' for a gateway\n" +
"Choose descriptive names that indicate location and purpose.",
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"disabled": {
Description: "Specifies whether this device should be disabled.",
Description: "Whether the device is administratively disabled. When true, the device will not forward traffic or provide services.",
Type: schema.TypeBool,
Computed: true,
},
"port_override": {
Description: "Settings overrides for specific switch ports.",
// TODO: this should really be a map or something when possible in the SDK
// see https://github.com/hashicorp/terraform-plugin-sdk/issues/62
Description: "A list of port-specific configuration overrides for UniFi switches. This allows you to customize individual port settings such as:\n" +
" * Port names and labels for easy identification\n" +
" * Port profiles for VLAN and security settings\n" +
" * Operating modes for special functions\n\n" +
"Common use cases include:\n" +
" * Setting up trunk ports for inter-switch connections\n" +
" * Configuring PoE settings for powered devices\n" +
" * Creating mirrored ports for network monitoring\n" +
" * Setting up link aggregation between switches or servers",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"number": {
Description: "Switch port number.",
Description: "The physical port number on the switch to configure.",
Type: schema.TypeInt,
Required: true,
},
"name": {
Description: "Human-readable name of the port.",
Type: schema.TypeString,
Optional: true,
Description: "A friendly name for the port that will be displayed in the UniFi controller UI. Examples:\n" +
" * 'Uplink to Core Switch'\n" +
" * 'Conference Room AP'\n" +
" * 'Server LACP Group 1'\n" +
" * 'VoIP Phone Port'",
Type: schema.TypeString,
Optional: true,
},
"port_profile_id": {
Description: "ID of the Port Profile used on this port.",
Description: "The ID of a pre-configured port profile to apply to this port. Port profiles define settings like VLANs, PoE, and other port-specific configurations.",
Type: schema.TypeString,
Optional: true,
},
"op_mode": {
Description: "Operating mode of the port, valid values are `switch`, `mirror`, and `aggregate`.",
Description: "The operating mode of the port. Valid values are:\n" +
" * `switch` - Normal switching mode (default)\n" +
" - Standard port operation for connecting devices\n" +
" - Supports VLANs and all standard switching features\n" +
" * `mirror` - Port mirroring for traffic analysis\n" +
" - Copies traffic from other ports for monitoring\n" +
" - Useful for network troubleshooting and security\n" +
" * `aggregate` - Link aggregation/bonding mode\n" +
" - Combines multiple ports for increased bandwidth\n" +
" - Used for switch uplinks or high-bandwidth servers",
Type: schema.TypeString,
Optional: true,
Default: "switch",
@@ -102,13 +129,29 @@ func ResourceDevice() *schema.Resource {
},
},
"poe_mode": {
Description: "PoE mode of the port; valid values are `auto`, `pasv24`, `passthrough`, and `off`.",
Description: "The Power over Ethernet (PoE) mode for the port. Valid values are:\n" +
"* `auto` - Automatically detect and power PoE devices (recommended)\n" +
" - Provides power based on device negotiation\n" +
" - Safest option for most PoE devices\n" +
"* `pasv24` - Passive 24V PoE\n" +
" - For older UniFi devices requiring passive 24V\n" +
" - Use with caution to avoid damage\n" +
"* `passthrough` - PoE passthrough mode\n" +
" - For daisy-chaining PoE devices\n" +
" - Available on select UniFi switches\n" +
"* `off` - Disable PoE on the port\n" +
" - For non-PoE devices\n" +
" - To prevent unwanted power delivery",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"auto", "pasv24", "passthrough", "off"}, false),
},
"aggregate_num_ports": {
Description: "Number of ports in the aggregate.",
Description: "The number of ports to include in a link aggregation group (LAG). Valid range: 2-8 ports. Used when:\n" +
"* Creating switch-to-switch uplinks for increased bandwidth\n" +
"* Setting up high-availability connections\n" +
"* Connecting to servers requiring more bandwidth\n" +
"Note: All ports in the LAG must be sequential and have matching configurations.",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(2, 8),
@@ -124,16 +167,24 @@ func ResourceDevice() *schema.Resource {
},
"allow_adoption": {
Description: "Specifies whether this resource should tell the controller to adopt the device on create.",
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Whether to automatically adopt the device when creating this resource. When true:\n" +
"* The controller will attempt to adopt the device\n" +
"* Device must be in a pending adoption state\n" +
"* Device must be accessible on the network\n" +
"Set to false if you want to manage adoption manually.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"forget_on_destroy": {
Description: "Specifies whether this resource should tell the controller to forget the device on destroy.",
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Whether to forget (un-adopt) the device when this resource is destroyed. When true:\n" +
"* The device will be removed from the controller\n" +
"* The device will need to be readopted to be managed again\n" +
"* Device configuration will be reset\n" +
"Set to false to keep the device adopted when removing from Terraform management.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
},
}

View File

@@ -1,8 +1,9 @@
package network
package device
import (
"context"
"errors"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/utils"
@@ -14,7 +15,14 @@ import (
func ResourcePortProfile() *schema.Resource {
return &schema.Resource{
Description: "`unifi_port_profile` manages a port profile for use on network switches.",
Description: "The `unifi_port_profile` resource manages port profiles that can be applied to UniFi switch ports.\n\n" +
"Port profiles define a collection of settings that can be applied to one or more switch ports, including:\n" +
" * Network and VLAN settings\n" +
" * Port speed and duplex settings\n" +
" * Security features like 802.1X authentication and port isolation\n" +
" * Rate limiting and QoS settings\n" +
" * Network protocols like LLDP and STP\n\n" +
"Creating port profiles allows for consistent configuration across multiple switch ports and easier management of port settings.",
CreateContext: resourcePortProfileCreate,
ReadContext: resourcePortProfileRead,
@@ -26,96 +34,121 @@ func ResourcePortProfile() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the port profile.",
Description: "The unique identifier of the port profile in the UniFi controller.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the port profile with.",
Description: "The name of the UniFi site where the port profile should be created. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"autoneg": {
Description: "Enable link auto negotiation for the port profile. When set to `true` this overrides `speed`.",
Description: "Enable automatic negotiation of port speed and duplex settings. When enabled, this overrides manual speed and duplex settings. Recommended for most use cases.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"dot1x_ctrl": {
Description: "The type of 802.1X control to use. Can be `auto`, `force_authorized`, `force_unauthorized`, `mac_based` or `multi_host`.",
Description: "802.1X port-based network access control (PNAC) mode. Valid values are:\n" +
" * `force_authorized` - Port allows all traffic, no authentication required (default)\n" +
" * `force_unauthorized` - Port blocks all traffic regardless of authentication\n" +
" * `auto` - Standard 802.1X authentication required before port access is granted\n" +
" * `mac_based` - Authentication based on client MAC address, useful for devices that don't support 802.1X\n" +
" * `multi_host` - Allows multiple devices after first successful authentication, common in VoIP phone setups\n\n" +
"Use 'auto' for highest security, 'mac_based' for legacy devices, and 'multi_host' when daisy-chaining devices.",
Type: schema.TypeString,
Optional: true,
Default: "force_authorized",
ValidateFunc: validation.StringInSlice([]string{"auto", "force_authorized", "force_unauthorized", "mac_based", "multi_host"}, false),
},
"dot1x_idle_timeout": {
Description: "The timeout, in seconds, to use when using the MAC Based 802.1X control. Can be between 0 and 65535",
Description: "The number of seconds before an inactive authenticated MAC address is removed when using MAC-based 802.1X control. Range: 0-65535 seconds.",
Type: schema.TypeInt,
Optional: true,
Default: 300,
ValidateFunc: validation.IntBetween(0, 65535),
},
"egress_rate_limit_kbps": {
Description: "The egress rate limit, in kpbs, for the port profile. Can be between `64` and `9999999`.",
Description: "The maximum outbound bandwidth allowed on the port in kilobits per second. Range: 64-9999999 kbps. Only applied when egress_rate_limit_kbps_enabled is true.",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(64, 9999999),
},
"egress_rate_limit_kbps_enabled": {
Description: "Enable egress rate limiting for the port profile.",
Description: "Enable outbound bandwidth rate limiting on the port. When enabled, traffic will be limited to the rate specified in egress_rate_limit_kbps.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"excluded_network_ids": {
Description: "List of network IDs to exclude on the port profile when forward is set to customize.",
Description: "List of network IDs to exclude when forward is set to 'customize'. This allows you to prevent specific networks from being accessible on ports using this profile.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"forward": {
Description: "The type forwarding to use for the port profile. Can be `all`, `native`, `customize` or `disabled`.",
Description: "VLAN forwarding mode for the port. Valid values are:\n" +
" * `all` - Forward all VLANs (trunk port)\n" +
" * `native` - Only forward untagged traffic (access port)\n" +
" * `customize` - Forward selected VLANs (use with `excluded_network_ids`)\n" +
" * `disabled` - Disable VLAN forwarding\n\n" +
"Examples:\n" +
" * Use 'all' for uplink ports or connections to VLAN-aware devices\n" +
" * Use 'native' for end-user devices or simple network connections\n" +
" * Use 'customize' to create a selective trunk port (e.g., for a server needing access to specific VLANs)",
Type: schema.TypeString,
Optional: true,
Default: "native",
ValidateFunc: validation.StringInSlice([]string{"all", "native", "customize", "disabled"}, false),
},
"full_duplex": {
Description: "Enable full duplex for the port profile.",
Description: "Enable full-duplex mode when auto-negotiation is disabled. Full duplex allows simultaneous two-way communication.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"isolation": {
Description: "Enable port isolation for the port profile.",
Description: "Enable port isolation. When enabled, devices connected to ports with this profile cannot communicate with each other, providing enhanced security.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"lldpmed_enabled": {
Description: "Enable LLDP-MED for the port profile.",
Description: "Enable Link Layer Discovery Protocol-Media Endpoint Discovery (LLDP-MED). This allows for automatic discovery and configuration of devices like VoIP phones.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"lldpmed_notify_enabled": {
Description: "Enable LLDP-MED topology change notifications for the port profile.",
Type: schema.TypeBool,
Optional: true,
//ValidateFunc: ,
Description: "Enable LLDP-MED topology change notifications. When enabled:\n" +
"* Network devices will be notified of topology changes\n" +
"* Useful for VoIP phones and other LLDP-MED capable devices\n" +
"* Helps maintain accurate network topology information\n" +
"* Facilitates faster device configuration and provisioning",
Type: schema.TypeBool,
Optional: true,
},
// TODO: rename to native_network_id
"native_networkconf_id": {
Description: "The ID of network to use as the main network on the port profile.",
Type: schema.TypeString,
Optional: true,
Description: "The ID of the network to use as the native (untagged) network on ports using this profile. " +
"This is typically used for:\n" +
"* Access ports where devices need untagged access\n" +
"* Trunk ports to specify the native VLAN\n" +
"* Management networks for network devices",
Type: schema.TypeString,
Optional: true,
},
"name": {
Description: "The name of the port profile.",
Type: schema.TypeString,
Optional: true,
Description: "A descriptive name for the port profile. Examples:\n" +
"* 'AP-Trunk-Port' - For access point uplinks\n" +
"* 'VoIP-Phone-Port' - For VoIP phone connections\n" +
"* 'User-Access-Port' - For standard user connections\n" +
"* 'IoT-Device-Port' - For IoT device connections",
Type: schema.TypeString,
Optional: true,
},
"op_mode": {
Description: "The operation mode for the port profile. Can only be `switch`",
@@ -131,52 +164,87 @@ func ResourcePortProfile() *schema.Resource {
ValidateFunc: validation.StringInSlice([]string{"auto", "passv24", "passthrough", "off"}, false),
},
"port_security_enabled": {
Description: "Enable port security for the port profile.",
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Enable MAC address-based port security. When enabled:\n" +
"* Only devices with specified MAC addresses can connect\n" +
"* Unauthorized devices will be blocked\n" +
"* Provides protection against unauthorized network access\n" +
"* Must be used with port_security_mac_address list",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"port_security_mac_address": {
Description: "The MAC addresses associated with the port security for the port profile.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Description: "List of allowed MAC addresses when port security is enabled. Each address should be:\n" +
"* In standard format (e.g., 'aa:bb:cc:dd:ee:ff')\n" +
"* Unique per device\n" +
"* Verified to belong to authorized devices\n" +
"Only effective when port_security_enabled is true",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"priority_queue1_level": {
Description: "The priority queue 1 level for the port profile. Can be between 0 and 100.",
Description: "Priority queue 1 level (0-100) for Quality of Service (QoS). Used for:\n" +
"* Low-priority background traffic\n" +
"* Bulk data transfers\n" +
"* Non-time-sensitive applications\n" +
"Higher values give more bandwidth to this queue",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(0, 100),
},
"priority_queue2_level": {
Description: "The priority queue 2 level for the port profile. Can be between 0 and 100.",
Description: "Priority queue 2 level (0-100) for Quality of Service (QoS). Used for:\n" +
"* Standard user traffic\n" +
"* Web browsing and email\n" +
"* General business applications\n" +
"Higher values give more bandwidth to this queue",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(0, 100),
},
"priority_queue3_level": {
Description: "The priority queue 3 level for the port profile. Can be between 0 and 100.",
Description: "Priority queue 3 level (0-100) for Quality of Service (QoS). Used for:\n" +
"* High-priority traffic\n" +
"* Voice and video conferencing\n" +
"* Time-sensitive applications\n" +
"Higher values give more bandwidth to this queue",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(0, 100),
},
"priority_queue4_level": {
Description: "The priority queue 4 level for the port profile. Can be between 0 and 100.",
Description: "Priority queue 4 level (0-100) for Quality of Service (QoS). Used for:\n" +
"* Highest priority traffic\n" +
"* Critical real-time applications\n" +
"* Emergency communications\n" +
"Higher values give more bandwidth to this queue",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(0, 100),
},
"speed": {
Description: "The link speed to set for the port profile in Mbps. Can be one of `10`, `100`, `1000`, `2500`, `5000`, `10000`, `20000`, `25000`, `40000`, `50000` or `100000`. When `autoneg` is true, this setting is ignored.",
Description: "Port speed in Mbps when auto-negotiation is disabled. Common values:\n" +
"* 10 - 10 Mbps (legacy devices)\n" +
"* 100 - 100 Mbps (Fast Ethernet)\n" +
"* 1000 - 1 Gbps (Gigabit Ethernet)\n" +
"* 2500 - 2.5 Gbps (Multi-Gigabit)\n" +
"* 5000 - 5 Gbps (Multi-Gigabit)\n" +
"* 10000 - 10 Gbps (10 Gigabit)\n" +
"Only used when autoneg is false",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntInSlice([]int{10, 100, 1000, 2500, 5000, 10000, 20000, 25000, 40000, 50000, 100000}),
},
"stormctrl_bcast_enabled": {
Description: "Enable broadcast Storm Control for the port profile.",
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Enable broadcast storm control. When enabled:\n" +
"* Limits broadcast traffic to prevent network flooding\n" +
"* Protects against broadcast storms\n" +
"* Helps maintain network stability\n" +
"Use with stormctrl_bcast_rate to set threshold",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"stormctrl_bcast_level": {
Description: "The broadcast Storm Control level for the port profile. Can be between 0 and 100.",
@@ -186,16 +254,24 @@ func ResourcePortProfile() *schema.Resource {
ConflictsWith: []string{"stormctrl_bcast_rate"},
},
"stormctrl_bcast_rate": {
Description: "The broadcast Storm Control rate for the port profile. Can be between 0 and 14880000.",
Description: "Maximum broadcast traffic rate in packets per second (0 - 14880000). Used to:\n" +
"* Control broadcast traffic levels\n" +
"* Prevent network congestion\n" +
"* Balance between necessary broadcasts and network protection\n" +
"Only effective when `stormctrl_bcast_enabled` is true",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(0, 14880000),
},
"stormctrl_mcast_enabled": {
Description: "Enable multicast Storm Control for the port profile.",
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Enable multicast storm control. When enabled:\n" +
"* Limits multicast traffic to prevent network flooding\n" +
"* Important for networks with multicast applications\n" +
"* Helps maintain quality of service\n" +
"Use with `stormctrl_mcast_rate` to set threshold",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"stormctrl_mcast_level": {
Description: "The multicast Storm Control level for the port profile. Can be between 0 and 100.",
@@ -205,7 +281,11 @@ func ResourcePortProfile() *schema.Resource {
ConflictsWith: []string{"stormctrl_mcast_rate"},
},
"stormctrl_mcast_rate": {
Description: "The multicast Storm Control rate for the port profile. Can be between 0 and 14880000.",
Description: "Maximum multicast traffic rate in packets per second (0 - 14880000). Used to:\n" +
"* Control multicast traffic levels\n" +
"* Ensure bandwidth for critical multicast services\n" +
"* Prevent multicast traffic from overwhelming the network\n" +
"Only effective when stormctrl_mcast_enabled is true",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(0, 14880000),
@@ -217,10 +297,14 @@ func ResourcePortProfile() *schema.Resource {
ValidateFunc: validation.StringInSlice([]string{"level", "rate"}, false),
},
"stormctrl_ucast_enabled": {
Description: "Enable unknown unicast Storm Control for the port profile.",
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Enable unknown unicast storm control. When enabled:\n" +
"* Limits unknown unicast traffic to prevent flooding\n" +
"* Protects against MAC spoofing attacks\n" +
"* Helps maintain network performance\n" +
"Use with stormctrl_ucast_rate to set threshold",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"stormctrl_ucast_level": {
Description: "The unknown unicast Storm Control level for the port profile. Can be between 0 and 100.",
@@ -230,28 +314,56 @@ func ResourcePortProfile() *schema.Resource {
ConflictsWith: []string{"stormctrl_ucast_rate"},
},
"stormctrl_ucast_rate": {
Description: "The unknown unicast Storm Control rate for the port profile. Can be between 0 and 14880000.",
Description: "Maximum unknown unicast traffic rate in packets per second (0 - 14880000). Used to:\n" +
"* Control unknown unicast traffic levels\n" +
"* Prevent network saturation from unknown destinations\n" +
"* Balance security with network usability\n" +
"Only effective when stormctrl_ucast_enabled is true",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(0, 14880000),
},
"stp_port_mode": {
Description: "Enable spanning tree protocol on the port profile.",
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Spanning Tree Protocol (STP) configuration for the port. When enabled:\n" +
"* Prevents network loops in switch-to-switch connections\n" +
"* Provides automatic failover in redundant topologies\n" +
"* Helps maintain network stability\n\n" +
"Best practices:\n" +
"* Enable on switch uplink ports\n" +
"* Enable on ports connecting to other switches\n" +
"* Can be disabled on end-device ports for faster initialization",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"tagged_vlan_mgmt": {
Description: "The VLAN management type for the port profile. Can be one of 'auto', 'block_all', or 'custom'.",
Description: "VLAN tagging behavior for the port. Valid values are:\n" +
"* `auto` - Automatically handle VLAN tags (recommended)\n" +
" - Intelligently manages tagged and untagged traffic\n" +
" - Best for most deployments\n" +
"* `block_all` - Block all VLAN tagged traffic\n" +
" - Use for security-sensitive ports\n" +
" - Prevents VLAN hopping attacks\n" +
"* `custom` - Custom VLAN configuration\n" +
" - Manual control over VLAN behavior\n" +
" - For specific VLAN requirements",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"auto", "block_all", "custom"}, false),
},
// TODO: rename to voice_network_id
"voice_networkconf_id": {
Description: "The ID of network to use as the voice network on the port profile.",
Type: schema.TypeString,
Optional: true,
Description: "The ID of the network to use for Voice over IP (VoIP) traffic. Used for:\n" +
"* Automatic VoIP VLAN configuration\n" +
"* Voice traffic prioritization\n" +
"* QoS settings for voice packets\n\n" +
"Common scenarios:\n" +
"* IP phone deployments with separate voice VLAN\n" +
"* Unified communications systems\n" +
"* Converged voice/data networks\n\n" +
"Works in conjunction with LLDP-MED for automatic phone provisioning.",
Type: schema.TypeString,
Optional: true,
},
},
}

View File

@@ -3,6 +3,7 @@ package dns
import (
"context"
"fmt"
"github.com/filipowm/go-unifi/unifi"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/utils"
@@ -52,22 +53,26 @@ func (d *dnsRecordDatasource) Metadata(_ context.Context, req datasource.Metadat
func (d *dnsRecordDatasource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Retrieves information about a specific DNS record.",
Attributes: dnsRecordDatasourceAttributes,
Description: "Retrieves information about a specific DNS record configured in your UniFi network. " +
"This data source allows you to look up DNS records by either their name or record content. " +
"It's particularly useful for validating existing DNS configurations or referencing DNS records in other resources.",
Attributes: dnsRecordDatasourceAttributes,
Blocks: map[string]schema.Block{
"filter": schema.SingleNestedBlock{
Description: "Filter to apply to the DNS record.",
Description: "Filter criteria to identify the specific DNS record. You must specify either the record name or content.",
Validators: []validator.Object{
objectvalidator.IsRequired(),
},
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Description: "DNS record name.",
Optional: true,
Description: "The DNS record name to look up (e.g., 'example.com', 'subdomain.example.com'). " +
"Cannot be specified together with `record`.",
Optional: true,
},
"record": schema.StringAttribute{
Description: "DNS record content.",
Optional: true,
Description: "The DNS record content to look up (e.g., IP address for A records, target hostname for CNAME records). " +
"Cannot be specified together with `name`.",
Optional: true,
},
},
},

View File

@@ -3,6 +3,7 @@ package dns
import (
"context"
"fmt"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/utils"
"github.com/hashicorp/terraform-plugin-framework-validators/int32validator"
@@ -44,7 +45,12 @@ func (d *dnsRecordResource) Metadata(_ context.Context, req resource.MetadataReq
func (d *dnsRecordResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "Manages a DNS record in the Unifi controller.",
MarkdownDescription: "The `unifi_dns_record` resource manages DNS records in the UniFi controller's DNS server.\n\n" +
"This resource allows you to configure various types of DNS records for local name resolution. Common use cases include:\n" +
" * Creating A records for local servers and devices\n" +
" * Setting up CNAME aliases for internal services\n" +
" * Configuring MX records for local mail servers\n" +
" * Adding TXT records for service verification\n\n",
Attributes: map[string]schema.Attribute{
"id": utils.ID(),
@@ -54,17 +60,22 @@ func (d *dnsRecordResource) Schema(_ context.Context, _ resource.SchemaRequest,
Required: true,
},
"record": schema.StringAttribute{
MarkdownDescription: "DNS record content.",
Required: true,
MarkdownDescription: "The content of the DNS record. The expected value depends on the record type:\n" +
" * For A records: IPv4 address (e.g., '192.168.1.10')\n" +
" * For AAAA records: IPv6 address\n" +
" * For CNAME records: Canonical name (e.g., 'server1.example.com')\n" +
" * For MX records: Mail server hostname\n" +
" * For TXT records: Text content (e.g., 'v=spf1 include:_spf.example.com ~all')",
Required: true,
},
"enabled": schema.BoolAttribute{
MarkdownDescription: "Whether the DNS record is enabled.",
MarkdownDescription: "Whether the DNS record is active. Defaults to true. Set to false to temporarily disable resolution without removing the record.",
Optional: true,
Computed: true,
Default: booldefault.StaticBool(true),
},
"port": schema.Int32Attribute{
MarkdownDescription: "The port of the DNS record.",
MarkdownDescription: "The port number for SRV records. Valid values are between 1 and 65535. Only used with SRV records.",
Optional: true,
Computed: true,
Validators: []validator.Int32{
@@ -72,7 +83,7 @@ func (d *dnsRecordResource) Schema(_ context.Context, _ resource.SchemaRequest,
},
},
"priority": schema.Int32Attribute{
MarkdownDescription: "Required for MX and SRV records; unused by other record types. Records with lower priorities are preferred",
MarkdownDescription: "Priority value for MX and SRV records. Lower values indicate higher priority. Required for MX and SRV records, ignored for other types.",
Optional: true,
Computed: true,
Validators: []validator.Int32{
@@ -80,19 +91,29 @@ func (d *dnsRecordResource) Schema(_ context.Context, _ resource.SchemaRequest,
},
},
"type": schema.StringAttribute{
MarkdownDescription: "The type of the DNS record.",
Required: true,
MarkdownDescription: "The type of DNS record. Valid values are:\n" +
" * `A` - Maps a hostname to IPv4 address\n" +
" * `AAAA` - Maps a hostname to IPv6 address\n" +
" * `CNAME` - Creates an alias for another domain name\n" +
" * `MX` - Specifies mail servers for the domain\n" +
" * `NS` - Delegates a subdomain to a set of name servers\n" +
" * `PTR` - Creates a pointer to a canonical name (reverse DNS)\n" +
" * `SOA` - Specifies authoritative information about the domain\n" +
" * `SRV` - Specifies location of services (hostname and port)\n" +
" * `TXT` - Holds descriptive text",
Required: true,
Validators: []validator.String{
stringvalidator.OneOf("A", "AAAA", "CNAME", "MX", "NS", "PTR", "SOA", "SRV", "TXT"),
},
},
"ttl": schema.Int32Attribute{
MarkdownDescription: "Time To Live (TTL) of the DNS record in seconds. Setting to 0 means 'automatic'.",
Optional: true,
Computed: true,
MarkdownDescription: "Time To Live (TTL) in seconds, determines how long DNS resolvers should cache this record. Set to 0 for automatic TTL. " +
"Common values: 300 (5 minutes), 3600 (1 hour), 86400 (1 day).",
Optional: true,
Computed: true,
},
"weight": schema.Int32Attribute{
MarkdownDescription: "A numeric value indicating the relative weight of the record.",
MarkdownDescription: "A relative weight for SRV records with the same priority. Higher values get proportionally more traffic. Only used with SRV records.",
Optional: true,
Computed: true,
},

View File

@@ -3,6 +3,7 @@ package dns
import (
"context"
"errors"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/utils"
@@ -13,7 +14,17 @@ import (
func ResourceDynamicDNS() *schema.Resource {
return &schema.Resource{
Description: "`unifi_dynamic_dns` manages dynamic DNS settings for different providers.",
Description: "The `unifi_dynamic_dns` resource manages Dynamic DNS (DDNS).\n\n" +
"Dynamic DNS allows you to access your network using a domain name even when your public IP address changes. This is useful for:\n" +
" * Remote access to your network\n" +
" * Hosting services from your home/office network\n" +
" * VPN connections to your network\n\n" +
"The resource supports various DDNS providers including:\n" +
" * DynDNS\n" +
" * No-IP\n" +
" * Duck DNS\n" +
" * And many others\n\n" +
"Each DDNS configuration can be associated with either the primary (WAN) or secondary (WAN2) interface.",
CreateContext: resourceDynamicDNSCreate,
ReadContext: resourceDynamicDNSRead,
@@ -25,47 +36,53 @@ func ResourceDynamicDNS() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the dynamic DNS.",
Description: "The unique identifier of the dynamic DNS configuration in the UniFi controller.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the dynamic DNS with.",
Description: "The name of the UniFi site where the dynamic DNS configuration should be created. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"interface": {
Description: "The interface for the dynamic DNS. Can be `wan` or `wan2`.",
Type: schema.TypeString,
Optional: true,
Default: "wan",
ForceNew: true,
Description: "The WAN interface to use for the dynamic DNS updates. Valid values are:\n" +
" * `wan` - Primary WAN interface (default)\n" +
" * `wan2` - Secondary WAN interface",
Type: schema.TypeString,
Optional: true,
Default: "wan",
ForceNew: true,
},
"service": {
Description: "The Dynamic DNS service provider, various values are supported (for example `dyndns`, etc.).",
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The Dynamic DNS service provider. Common values include:\n" +
" * `dyndns` - DynDNS service\n" +
" * `noip` - No-IP service\n" +
" * `duckdns` - Duck DNS service\n" +
"Check your UniFi controller for the complete list of supported providers.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"host_name": {
Description: "The host name to update in the dynamic DNS service.",
Description: "The fully qualified domain name to update with your current public IP address (e.g., 'myhouse.dyndns.org' or 'myoffice.no-ip.com').",
Type: schema.TypeString,
Required: true,
},
"server": {
Description: "The server for the dynamic DNS service.",
Description: "The update server hostname for your DDNS provider. Usually not required as the UniFi controller knows the correct servers for common providers.",
Type: schema.TypeString,
Optional: true,
},
"login": {
Description: "The server for the dynamic DNS service.",
Description: "The username or login for your DDNS provider account.",
Type: schema.TypeString,
Optional: true,
},
"password": {
Description: "The server for the dynamic DNS service.",
Description: "The password or token for your DDNS provider account. This value will be stored securely and not displayed in logs.",
Type: schema.TypeString,
Optional: true,
Sensitive: true,

View File

@@ -3,6 +3,7 @@ package firewall
import (
"context"
"errors"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/utils"
@@ -14,7 +15,16 @@ import (
func ResourceFirewallGroup() *schema.Resource {
return &schema.Resource{
Description: "`unifi_firewall_group` manages groups of addresses or ports for use in firewall rules (`unifi_firewall_rule`).",
Description: "The `unifi_firewall_group` resource manages reusable groups of addresses or ports that can be referenced in firewall rules (`unifi_firewall_rule`).\n\n" +
"Firewall groups help organize and simplify firewall rule management by allowing you to:\n" +
" * Create collections of IP addresses or networks\n" +
" * Define sets of ports for specific services\n" +
" * Group IPv6 addresses for IPv6-specific rules\n\n" +
"Common use cases include:\n" +
" * Creating groups of trusted IP addresses\n" +
" * Defining port groups for specific applications\n" +
" * Managing access control lists\n" +
" * Simplifying rule maintenance by using groups instead of individual IPs/ports",
CreateContext: resourceFirewallGroupCreate,
ReadContext: resourceFirewallGroupRead,
@@ -26,33 +36,40 @@ func ResourceFirewallGroup() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the firewall group.",
Description: "The unique identifier of the firewall group in the UniFi controller.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the firewall group with.",
Description: "The name of the UniFi site where the firewall group should be created. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"name": {
Description: "The name of the firewall group.",
Type: schema.TypeString,
Required: true,
Description: "A friendly name for the firewall group to help identify its purpose (e.g., 'Trusted IPs' or 'Web Server Ports'). " +
"Must be unique within the site.",
Type: schema.TypeString,
Required: true,
},
"type": {
Description: "The type of the firewall group. Must be one of: `address-group`, `port-group`, or `ipv6-address-group`.",
Description: "The type of firewall group. Valid values are:\n" +
" * `address-group` - Group of IPv4 addresses and/or networks (e.g., '192.168.1.10', '10.0.0.0/8')\n" +
" * `port-group` - Group of ports or port ranges (e.g., '80', '443', '8000-8080')\n" +
" * `ipv6-address-group` - Group of IPv6 addresses and/or networks",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"address-group", "port-group", "ipv6-address-group"}, false),
},
"members": {
Description: "The members of the firewall group.",
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
Description: "List of members in the group. The format depends on the group type:\n" +
" * For address-group: IPv4 addresses or CIDR notation (e.g., ['192.168.1.10', '10.0.0.0/8'])\n" +
" * For port-group: Port numbers or ranges (e.g., ['80', '443', '8000-8080'])\n" +
" * For ipv6-address-group: IPv6 addresses or CIDR notation",
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
}

View File

@@ -3,9 +3,10 @@ package firewall
import (
"context"
"errors"
"regexp"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/utils"
"regexp"
"github.com/filipowm/go-unifi/unifi"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
@@ -19,7 +20,12 @@ var firewallRuleICMPv6TypenameRegexp = regexp.MustCompile("^$|address-unreachabl
func ResourceFirewallRule() *schema.Resource {
return &schema.Resource{
Description: "`unifi_firewall_rule` manages an individual firewall rule on the gateway.",
Description: "The `unifi_firewall_rule` resource manages firewall rules.\n\n" +
"This resource allows you to create and manage firewall rules that control traffic flow between different network segments (WAN, LAN, Guest) " +
"for both IPv4 and IPv6 traffic. Rules can be configured to allow, drop, or reject traffic based on various criteria including protocols, " +
"ports, and IP addresses.\n\n" +
"Rules are processed in order based on their `rule_index`, with lower numbers being processed first. Custom rules should use indices between " +
"2000-2999 or 4000-4999 to avoid conflicts with system rules.",
CreateContext: resourceFirewallRuleCreate,
ReadContext: resourceFirewallRuleRead,
@@ -31,68 +37,101 @@ func ResourceFirewallRule() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the firewall rule.",
Description: "The unique identifier of the firewall rule in the UniFi controller.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the firewall rule with.",
Description: "The name of the UniFi site where the firewall rule should be created. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"name": {
Description: "The name of the firewall rule.",
Description: "A friendly name for the firewall rule. This helps identify the rule's purpose in the UniFi controller UI.",
Type: schema.TypeString,
Required: true,
},
"action": {
Description: "The action of the firewall rule. Must be one of `drop`, `accept`, or `reject`.",
Description: "The action to take when traffic matches this rule. Valid values are:\n" +
" * `accept` - Allow the traffic\n" +
" * `drop` - Silently block the traffic\n" +
" * `reject` - Block the traffic and send an ICMP rejection message",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"drop", "accept", "reject"}, false),
},
"ruleset": {
Description: "The ruleset for the rule. This is from the perspective of the security gateway. " +
"Must be one of `WAN_IN`, `WAN_OUT`, `WAN_LOCAL`, `LAN_IN`, `LAN_OUT`, `LAN_LOCAL`, `GUEST_IN`, " +
"`GUEST_OUT`, `GUEST_LOCAL`, `WANv6_IN`, `WANv6_OUT`, `WANv6_LOCAL`, `LANv6_IN`, `LANv6_OUT`, " +
"`LANv6_LOCAL`, `GUESTv6_IN`, `GUESTv6_OUT`, or `GUESTv6_LOCAL`.",
Description: "Defines which traffic flow this rule applies to. The format is [NETWORK]_[DIRECTION], where:\n" +
" * NETWORK can be: WAN, LAN, GUEST (or their IPv6 variants WANv6, LANv6, GUESTv6)\n" +
" * DIRECTION can be:\n" +
" * IN - Traffic entering the network\n" +
" * OUT - Traffic leaving the network\n" +
" * LOCAL - Traffic destined for the USG/UDM itself\n\n" +
"Examples: WAN_IN (incoming WAN traffic), LAN_OUT (outgoing LAN traffic), GUEST_LOCAL (traffic to Controller from guest network)",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"WAN_IN", "WAN_OUT", "WAN_LOCAL", "LAN_IN", "LAN_OUT", "LAN_LOCAL", "GUEST_IN", "GUEST_OUT", "GUEST_LOCAL", "WANv6_IN", "WANv6_OUT", "WANv6_LOCAL", "LANv6_IN", "LANv6_OUT", "LANv6_LOCAL", "GUESTv6_IN", "GUESTv6_OUT", "GUESTv6_LOCAL"}, false),
},
"rule_index": {
Description: "The index of the rule. Must be >= 2000 < 3000 or >= 4000 < 5000.",
Type: schema.TypeInt,
Required: true,
Description: "The processing order for this rule. Lower numbers are processed first. Custom rules should use:\n" +
" * 2000-2999 for rules processed before auto-generated rules\n" +
" * 4000-4999 for rules processed after auto-generated rules",
Type: schema.TypeInt,
Required: true,
// 2[0-9]{3}|4[0-9]{3}
},
"protocol": {
Description: "The protocol of the rule.",
Description: "The IPv4 protocol this rule applies to. Common values (not all are listed) include:\n" +
" * `all` - Match all protocols\n" +
" * `tcp` - TCP traffic only (e.g., web, email)\n" +
" * `udp` - UDP traffic only (e.g., DNS, VoIP)\n" +
" * `tcp_udp` - Both TCP and UDP\n" +
" * `icmp` - ICMP traffic (ping, traceroute)\n" +
" * Protocol numbers (1-255) for other protocols\n\n" +
"Examples:\n" +
" * Use 'tcp' for web server rules (ports 80, 443)\n" +
" * Use 'udp' for VoIP or gaming traffic\n" +
" * Use 'all' for general network access rules",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringMatch(firewallRuleProtocolRegexp, "must be a valid IPv4 protocol"),
},
"protocol_v6": {
Description: "The IPv6 protocol of the rule.",
Description: "The IPv6 protocol this rule applies to. Similar to 'protocol' but for IPv6 traffic. Common values (not all are listed) include:\n" +
" * `all` - Match all protocols\n" +
" * `tcp` - TCP traffic only\n" +
" * `udp` - UDP traffic only\n" +
" * `tcp_udp` - Both TCP and UDP traffic\n" +
" * `ipv6-icmp` - ICMPv6 traffic",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringMatch(firewallRuleProtocolV6Regexp, "must be a valid IPv6 protocol"),
},
"icmp_typename": {
Description: "ICMP type name.",
Type: schema.TypeString,
Optional: true,
Description: "The ICMP type name when protocol is set to 'icmp'. Common values include:\n" +
" * `echo-request` - ICMP ping requests\n" +
" * `echo-reply` - ICMP ping replies\n" +
" * `destination-unreachable` - Host/network unreachable messages\n" +
" * `time-exceeded` - TTL exceeded messages (traceroute)",
Type: schema.TypeString,
Optional: true,
},
"icmp_v6_typename": {
Description: "ICMPv6 type name.",
Description: "The ICMPv6 type name when protocol_v6 is set to 'ipv6-icmp'. Common values (not all are listed) include:\n" +
" * `echo-request` - IPv6 ping requests\n" +
" * `echo-reply` - IPv6 ping replies\n" +
" * `neighbor-solicitation` - IPv6 neighbor discovery\n" +
" * `neighbor-advertisement` - IPv6 neighbor announcements\n" +
" * `destination-unreachable` - Host/network unreachable messages\n" +
" * `packet-too-big` - Path MTU discovery messages",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringMatch(firewallRuleICMPv6TypenameRegexp, "must be a ICMPv6 type"),
},
"enabled": {
Description: "Specifies whether the rule should be enabled.",
Description: "Whether this firewall rule is active (true) or disabled (false). Defaults to true.",
Type: schema.TypeBool,
Optional: true,
Default: true,
@@ -100,76 +139,100 @@ func ResourceFirewallRule() *schema.Resource {
// sources
"src_network_id": {
Description: "The source network ID for the firewall rule.",
Type: schema.TypeString,
Optional: true,
Description: "The ID of the source network this rule applies to. This can be found in the URL when viewing the network " +
"in the UniFi controller, or by using the network's name in the form `[site]/[network_name]`.",
Type: schema.TypeString,
Optional: true,
},
"src_network_type": {
Description: "The source network type of the firewall rule. Can be one of `ADDRv4` or `NETv4`.",
Description: "The type of source network address. Valid values are:\n" +
" * `ADDRv4` - Single IPv4 address\n" +
" * `NETv4` - IPv4 network in CIDR notation",
Type: schema.TypeString,
Optional: true,
Default: "NETv4",
ValidateFunc: validation.StringInSlice([]string{"ADDRv4", "NETv4"}, false),
},
"src_firewall_group_ids": {
Description: "The source firewall group IDs for the firewall rule.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Description: "A list of firewall group IDs to use as sources. Groups can contain:\n" +
" * IP Address Groups - For matching specific IP addresses\n" +
" * Network Groups - For matching entire subnets\n" +
" * Port Groups - For matching specific port numbers\n\n" +
"Example uses:\n" +
" * Group of trusted admin IPs for remote access\n" +
" * Group of IoT device networks for isolation\n" +
" * Group of common service ports for allowing specific applications",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"src_address": {
Description: "The source address for the firewall rule.",
Description: "The source IPv4 address for the firewall rule.",
Type: schema.TypeString,
Optional: true,
},
"src_address_ipv6": {
Description: "The IPv6 source address for the firewall rule.",
Type: schema.TypeString,
Optional: true,
Description: "The source IPv6 address or network in CIDR notation (e.g., '2001:db8::1' or '2001:db8::/64'). " +
"Used for IPv6 firewall rules.",
Type: schema.TypeString,
Optional: true,
},
"src_port": {
Description: "The source port of the firewall rule.",
Description: "The source port(s) for this rule. Can be:\n" +
" * A single port number (e.g., '80')\n" +
" * A port range (e.g., '8000:8080')\n" +
" * A list of ports/ranges separated by commas",
Type: schema.TypeString,
Optional: true,
ValidateFunc: utils.ValidatePortRange,
},
"src_mac": {
Description: "The source MAC address of the firewall rule.",
Type: schema.TypeString,
Optional: true,
Description: "The source MAC address this rule applies to. Use this to create rules that match specific devices " +
"regardless of their IP address. Format: 'XX:XX:XX:XX:XX:XX'. MAC addresses are case-insensitive.",
Type: schema.TypeString,
Optional: true,
},
// destinations
"dst_network_id": {
Description: "The destination network ID of the firewall rule.",
Type: schema.TypeString,
Optional: true,
Description: "The ID of the destination network this rule applies to. This can be found in the URL when viewing the network " +
"in the UniFi controller.",
Type: schema.TypeString,
Optional: true,
},
"dst_network_type": {
Description: "The destination network type of the firewall rule. Can be one of `ADDRv4` or `NETv4`.",
Description: "The type of destination network address. Valid values are:\n" +
" * `ADDRv4` - Single IPv4 address\n" +
" * `NETv4` - IPv4 network in CIDR notation",
Type: schema.TypeString,
Optional: true,
Default: "NETv4",
ValidateFunc: validation.StringInSlice([]string{"ADDRv4", "NETv4"}, false),
},
"dst_firewall_group_ids": {
Description: "The destination firewall group IDs of the firewall rule.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Description: "A list of firewall group IDs to use as destinations. Groups can contain IP addresses, networks, or port numbers. " +
"This allows you to create reusable sets of addresses/ports and reference them in multiple rules.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"dst_address": {
Description: "The destination address of the firewall rule.",
Type: schema.TypeString,
Optional: true,
Description: "The destination IPv4 address or network in CIDR notation (e.g., '192.168.1.10' or '192.168.0.0/24'). " +
"The format must match dst_network_type - use a single IP for ADDRv4 or CIDR for NETv4.",
Type: schema.TypeString,
Optional: true,
},
"dst_address_ipv6": {
Description: "The IPv6 destination address of the firewall rule.",
Type: schema.TypeString,
Optional: true,
Description: "The destination IPv6 address or network in CIDR notation (e.g., '2001:db8::1' or '2001:db8::/64'). " +
"Used for IPv6 firewall rules.",
Type: schema.TypeString,
Optional: true,
},
"dst_port": {
Description: "The destination port of the firewall rule.",
Description: "The destination port(s) for this rule. Can be:\n" +
" * A single port number (e.g., '80')\n" +
" * A port range (e.g., '8000:8080')\n" +
" * A list of ports/ranges separated by commas",
Type: schema.TypeString,
Optional: true,
ValidateFunc: utils.ValidatePortRange,
@@ -182,9 +245,13 @@ func ResourceFirewallRule() *schema.Resource {
Optional: true,
},
"state_established": {
Description: "Match where the state is established.",
Type: schema.TypeBool,
Optional: true,
Description: "Match established connections. When enabled:\n" +
" * Rule only applies to packets that are part of an existing connection\n" +
" * Useful for allowing return traffic without creating separate rules\n" +
" * Common in WAN_IN rules to allow responses to outbound connections\n\n" +
"Example: Allow established connections from WAN while blocking new incoming connections",
Type: schema.TypeBool,
Optional: true,
},
"state_invalid": {
Description: "Match where the state is invalid.",
@@ -202,7 +269,7 @@ func ResourceFirewallRule() *schema.Resource {
Optional: true,
},
"ip_sec": {
Description: "Specify whether the rule matches on IPsec packets. Can be one of `match-ipset` or `match-none`.",
Description: "Specify whether the rule matches on IPsec packets. Can be one of `match-ipsec` or `match-none`.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"match-ipsec", "match-none"}, false),

View File

@@ -2,6 +2,7 @@ package network
import (
"context"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/utils"

View File

@@ -4,11 +4,12 @@ import (
"context"
"errors"
"fmt"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/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/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -46,7 +47,18 @@ var (
func ResourceNetwork() *schema.Resource {
return &schema.Resource{
Description: "`unifi_network` manages WAN/LAN/VLAN networks.",
Description: "The `unifi_network` resource manages networks in your UniFi environment, including WAN, LAN, and VLAN networks. " +
"This resource enables you to:\n\n" +
"* Create and manage different types of networks (corporate, guest, WAN, VLAN-only)\n" +
"* Configure network addressing and DHCP settings\n" +
"* Set up IPv6 networking features\n" +
"* Manage DHCP relay and DNS settings\n" +
"* Configure network groups and VLANs\n\n" +
"Common use cases include:\n" +
"* Setting up corporate and guest networks with different security policies\n" +
"* Configuring WAN connectivity with various authentication methods\n" +
"* Creating VLANs for network segmentation\n" +
"* Managing DHCP and DNS services for network clients",
CreateContext: resourceNetworkCreate,
ReadContext: resourceNetworkRead,
@@ -70,62 +82,88 @@ func ResourceNetwork() *schema.Resource {
ForceNew: true,
},
"name": {
Description: "The name of the network.",
Type: schema.TypeString,
Required: true,
Description: "The name of the network. This should be a descriptive name that helps identify the network's purpose, " +
"such as 'Corporate-Main', 'Guest-Network', or 'IoT-VLAN'.",
Type: schema.TypeString,
Required: true,
},
"purpose": {
Description: "The purpose of the network. Must be one of `corporate`, `guest`, `wan`, or `vlan-only`.",
Description: "The purpose/type of the network. Must be one of:\n" +
"* `corporate` - Standard network for corporate use with full access\n" +
"* `guest` - Isolated network for guest access with limited permissions\n" +
"* `wan` - External network connection (WAN uplink)\n" +
"* `vlan-only` - VLAN network without DHCP services",
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{"corporate", "guest", "wan", "vlan-only"}, false),
},
"vlan_id": {
Description: "The VLAN ID of the network.",
Description: "The VLAN ID for this network. Valid range is 0-4096. Common uses:\n" +
"* 1-4094: Standard VLAN range for network segmentation\n" +
"* 0: Untagged/native VLAN\n" +
"* >4094: Reserved for special purposes",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validateVLANID,
},
"subnet": {
Description: "The subnet of the network. Must be a valid CIDR address.",
Description: "The IPv4 subnet for this network in CIDR notation (e.g., '192.168.1.0/24'). " +
"This defines the network's address space and determines the range of IP addresses available for DHCP.",
Type: schema.TypeString,
Optional: true,
DiffSuppressFunc: utils.CidrDiffSuppress,
ValidateFunc: utils.CidrValidate,
},
"network_group": {
Description: "The group of the network.",
Type: schema.TypeString,
Optional: true,
Default: "LAN",
Description: "The network group for this network. Default is 'LAN'. For WAN networks, use 'WAN' or 'WAN2'. " +
"Network groups help organize and apply policies to multiple networks.",
Type: schema.TypeString,
Optional: true,
Default: "LAN",
},
"dhcp_start": {
Description: "The IPv4 address where the DHCP range of addresses starts.",
Description: "The starting IPv4 address of the DHCP range. Examples:\n" +
"* For subnet 192.168.1.0/24, typical start: '192.168.1.100'\n" +
"* For subnet 10.0.0.0/24, typical start: '10.0.0.100'\n" +
"Ensure this address is within the network's subnet.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsIPv4Address,
},
"dhcp_stop": {
Description: "The IPv4 address where the DHCP range of addresses stops.",
Description: "The ending IPv4 address of the DHCP range. Examples:\n" +
"* For subnet 192.168.1.0/24, typical stop: '192.168.1.254'\n" +
"* For subnet 10.0.0.0/24, typical stop: '10.0.0.254'\n" +
"Must be greater than dhcp_start and within the network's subnet.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsIPv4Address,
},
"dhcp_enabled": {
Description: "Specifies whether DHCP is enabled or not on this network.",
Type: schema.TypeBool,
Optional: true,
Description: "Controls whether DHCP server is enabled for this network. When enabled:\n" +
"* The network will automatically assign IP addresses to clients\n" +
"* DHCP options (DNS, lease time) will be provided to clients\n" +
"* Static IP assignments can still be made outside the DHCP range",
Type: schema.TypeBool,
Optional: true,
},
"dhcp_lease": {
Description: "Specifies the lease time for DHCP addresses in seconds.",
Type: schema.TypeInt,
Optional: true,
Default: 86400,
Description: "The DHCP lease time in seconds. Common values:\n" +
"* 86400 (1 day) - Default, suitable for most networks\n" +
"* 3600 (1 hour) - For testing or temporary networks\n" +
"* 604800 (1 week) - For stable networks with static clients\n" +
"* 2592000 (30 days) - For very stable networks",
Type: schema.TypeInt,
Optional: true,
Default: 86400,
},
"dhcp_dns": {
Description: "Specifies the IPv4 addresses for the DNS server to be returned from the DHCP " +
"server. Leave blank to disable this feature.",
Description: "List of IPv4 DNS server addresses to be provided to DHCP clients. Examples:\n" +
"* Use ['8.8.8.8', '8.8.4.4'] for Google DNS\n" +
"* Use ['1.1.1.1', '1.0.0.1'] for Cloudflare DNS\n" +
"* Use internal DNS servers for corporate networks\n" +
"Maximum 4 servers can be specified.",
Type: schema.TypeList,
Optional: true,
MaxItems: 4,
@@ -133,35 +171,48 @@ func ResourceNetwork() *schema.Resource {
Type: schema.TypeString,
ValidateFunc: validation.All(
validation.IsIPv4Address,
// this doesn't let blank through
validation.StringLenBetween(1, 50),
),
},
},
"dhcpd_boot_enabled": {
Description: "Toggles on the DHCP boot options. Should be set to true when you want to have dhcpd_boot_filename, and dhcpd_boot_server to take effect.",
Type: schema.TypeBool,
Optional: true,
Description: "Enables DHCP boot options for PXE boot or network boot configurations. When enabled:\n" +
"* Allows network devices to boot from a TFTP server\n" +
"* Requires dhcpd_boot_server and dhcpd_boot_filename to be set\n" +
"* Commonly used for diskless workstations or network installations",
Type: schema.TypeBool,
Optional: true,
},
"dhcpd_boot_server": {
Description: "Specifies the IPv4 address of a TFTP server to network boot from.",
Type: schema.TypeString,
Description: "The IPv4 address of the TFTP server for network boot. This setting:\n" +
"* Is required when dhcpd_boot_enabled is true\n" +
"* Should be a reliable, always-on server\n" +
"* Must be accessible to all clients that need to boot",
Type: schema.TypeString,
// TODO: IPv4 validation?
Optional: true,
},
"dhcpd_boot_filename": {
Description: "Specifies the file to PXE boot from on the dhcpd_boot_server.",
Type: schema.TypeString,
Optional: true,
Description: "The boot filename to be loaded from the TFTP server. Examples:\n" +
"* 'pxelinux.0' - Standard PXE boot loader\n" +
"* 'undionly.kpxe' - iPXE boot loader\n" +
"* Custom paths for specific boot images",
Type: schema.TypeString,
Optional: true,
},
"dhcp_relay_enabled": {
Description: "Specifies whether DHCP relay is enabled or not on this network.",
Type: schema.TypeBool,
Optional: true,
Description: "Enables DHCP relay for this network. When enabled:\n" +
"* DHCP requests are forwarded to an external DHCP server\n" +
"* Local DHCP server is disabled\n" +
"* Useful for centralized DHCP management",
Type: schema.TypeBool,
Optional: true,
},
"dhcp_v6_dns": {
Description: "Specifies the IPv6 addresses for the DNS server to be returned from the DHCP " +
"server. Used if `dhcp_v6_dns_auto` is set to `false`.",
Description: "List of IPv6 DNS server addresses for DHCPv6 clients. Examples:\n" +
"* Use ['2001:4860:4860::8888', '2001:4860:4860::8844'] for Google DNS\n" +
"* Use ['2606:4700:4700::1111', '2606:4700:4700::1001'] for Cloudflare DNS\n" +
"Only used when dhcp_v6_dns_auto is false. Maximum of 4 addresses are allowed.",
Type: schema.TypeList,
Optional: true,
MaxItems: 4,
@@ -172,209 +223,309 @@ func ResourceNetwork() *schema.Resource {
},
},
"dhcp_v6_dns_auto": {
Description: "Specifies DNS source to propagate. If set `false` the entries in `dhcp_v6_dns` are used, the upstream entries otherwise",
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Controls DNS server source for DHCPv6 clients:\n" +
"* true - Use upstream DNS servers (recommended)\n" +
"* false - Use manually specified servers from dhcp_v6_dns\n" +
"Default is true for easier management.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"dhcp_v6_enabled": {
Description: "Enable stateful DHCPv6 for static configuration.",
Type: schema.TypeBool,
Optional: true,
Description: "Enables stateful DHCPv6 for IPv6 address assignment. When enabled:\n" +
"* Provides IPv6 addresses to clients\n" +
"* Works alongside SLAAC if configured\n" +
"* Allows for more controlled IPv6 addressing",
Type: schema.TypeBool,
Optional: true,
},
"dhcp_v6_lease": {
Description: "Specifies the lease time for DHCPv6 addresses in seconds.",
Type: schema.TypeInt,
Optional: true,
Default: 86400,
Description: "The DHCPv6 lease time in seconds. Common values:\n" +
"* 86400 (1 day) - Default setting\n" +
"* 3600 (1 hour) - For testing\n" +
"* 604800 (1 week) - For stable networks\n" +
"Typically longer than IPv4 DHCP leases.",
Type: schema.TypeInt,
Optional: true,
Default: 86400,
},
"dhcp_v6_start": {
Description: "start address of the DHCPv6 range. Used in static DHCPv6 configuration.",
Description: "The starting IPv6 address for the DHCPv6 range. Used in static DHCPv6 configuration.\n" +
"Must be a valid IPv6 address within your allocated IPv6 subnet.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsIPv6Address,
},
"dhcp_v6_stop": {
Description: "End address of the DHCPv6 range. Used in static DHCPv6 configuration.",
Description: "The ending IPv6 address for the DHCPv6 range. Used in static DHCPv6 configuration.\n" +
"Must be after dhcp_v6_start in the IPv6 address space.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsIPv6Address,
},
"domain_name": {
Description: "The domain name of this network.",
Type: schema.TypeString,
Optional: true,
Description: "The domain name for this network. Examples:\n" +
"* 'corp.example.com' - For corporate networks\n" +
"* 'guest.example.com' - For guest networks\n" +
"* 'iot.example.com' - For IoT networks\n" +
"Used for internal DNS resolution and DHCP options.",
Type: schema.TypeString,
Optional: true,
},
"enabled": {
Description: "Specifies whether this network is enabled or not.",
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Controls whether this network is active. When disabled:\n" +
"* Network will not be available to clients\n" +
"* DHCP services will be stopped\n" +
"* Existing clients will be disconnected\n" +
"Useful for temporary network maintenance or security measures.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"igmp_snooping": {
Description: "Specifies whether IGMP snooping is enabled or not.",
Type: schema.TypeBool,
Optional: true,
Description: "Enables IGMP (Internet Group Management Protocol) snooping. When enabled:\n" +
"* Optimizes multicast traffic flow\n" +
"* Reduces network congestion\n" +
"* Improves performance for multicast applications (e.g., IPTV)\n" +
"Recommended for networks with multicast traffic.",
Type: schema.TypeBool,
Optional: true,
},
"ipv6_interface_type": {
Description: "Specifies which type of IPv6 connection to use. Must be one of either `static`, `pd`, or `none`.",
Description: "Specifies the IPv6 connection type. Must be one of:\n" +
"* `none` - IPv6 disabled (default)\n" +
"* `static` - Static IPv6 addressing\n" +
"* `pd` - Prefix Delegation from upstream\n\n" +
"Choose based on your IPv6 deployment strategy and ISP capabilities.",
Type: schema.TypeString,
Optional: true,
Default: "none",
ValidateFunc: validateIpV6InterfaceType,
},
"ipv6_static_subnet": {
Description: "Specifies the static IPv6 subnet when `ipv6_interface_type` is 'static'.",
Type: schema.TypeString,
Optional: true,
Description: "The static IPv6 subnet in CIDR notation (e.g., '2001:db8::/64') when using static IPv6.\n" +
"Only applicable when `ipv6_interface_type` is 'static'.\n" +
"Must be a valid IPv6 subnet allocated to your organization.",
Type: schema.TypeString,
Optional: true,
},
"ipv6_pd_interface": {
Description: "Specifies which WAN interface to use for IPv6 PD. Must be one of either `wan` or `wan2`.",
Description: "The WAN interface to use for IPv6 Prefix Delegation. Options:\n" +
"* `wan` - Primary WAN interface\n" +
"* `wan2` - Secondary WAN interface\n" +
"Only applicable when `ipv6_interface_type` is 'pd'.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateWANV6NetworkGroup,
},
"ipv6_pd_prefixid": {
Description: "Specifies the IPv6 Prefix ID.",
Type: schema.TypeString,
Optional: true,
Description: "The IPv6 Prefix ID for Prefix Delegation. Used to:\n" +
"* Differentiate multiple delegated prefixes\n" +
"* Create unique subnets from the delegated prefix\n" +
"Typically a hexadecimal value (e.g., '0', '1', 'a1').",
Type: schema.TypeString,
Optional: true,
},
"ipv6_pd_start": {
Description: "start address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`.",
Description: "The starting IPv6 address for Prefix Delegation range.\n" +
"Only used when `ipv6_interface_type` is 'pd'.\n" +
"Must be within the delegated prefix range.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsIPv6Address,
},
"ipv6_pd_stop": {
Description: "End address of the DHCPv6 range. Used if `ipv6_interface_type` is set to `pd`.",
Description: "The ending IPv6 address for Prefix Delegation range.\n" +
"Only used when `ipv6_interface_type` is 'pd'.\n" +
"Must be after `ipv6_pd_start` within the delegated prefix.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsIPv6Address,
},
"ipv6_ra_enable": {
Description: "Specifies whether to enable router advertisements or not.",
Type: schema.TypeBool,
Optional: true,
Description: "Enables IPv6 Router Advertisements (RA). When enabled:\n" +
"* Announces IPv6 prefix information to clients\n" +
"* Enables SLAAC address configuration\n" +
"* Required for most IPv6 deployments",
Type: schema.TypeBool,
Optional: true,
},
"internet_access_enabled": {
Description: "Specifies whether this network should be allowed to access the internet or not.",
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Controls internet access for this network. When disabled:\n" +
"* Clients cannot access external networks\n" +
"* Internal network access remains available\n" +
"* Useful for creating isolated or secure networks",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"network_isolation_enabled": {
Description: "Specifies whether this network should be isolated from other networks or not.",
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Enables network isolation. When enabled:\n" +
"* Prevents communication between clients on this network\n" +
"* Each client can only communicate with the gateway\n" +
"* Commonly used for guest networks or IoT devices",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"ipv6_ra_preferred_lifetime": {
Description: "Lifetime in which the address can be used. Address becomes deprecated afterwards. Must be lower than or equal to `ipv6_ra_valid_lifetime`",
Type: schema.TypeInt,
Optional: true,
Default: 14400,
Description: "The preferred lifetime (in seconds) for IPv6 addresses in Router Advertisements.\n" +
"* Must be less than or equal to `ipv6_ra_valid_lifetime`\n" +
"* Default: 14400 (4 hours)\n" +
"* After this time, addresses become deprecated but still usable",
Type: schema.TypeInt,
Optional: true,
Default: 14400,
},
"ipv6_ra_priority": {
Description: "IPv6 router advertisement priority. Must be one of either `high`, `medium`, or `low`",
Description: "Sets the priority for IPv6 Router Advertisements. Options:\n" +
"* `high` - Preferred for primary networks\n" +
"* `medium` - Standard priority\n" +
"* `low` - For backup or secondary networks\n" +
"Affects router selection when multiple IPv6 routers exist.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateIpV6RAPriority,
},
"ipv6_ra_valid_lifetime": {
Description: "Total lifetime in which the address can be used. Must be equal to or greater than `ipv6_ra_preferred_lifetime`.",
Type: schema.TypeInt,
Optional: true,
Default: 86400,
Description: "The valid lifetime (in seconds) for IPv6 addresses in Router Advertisements.\n" +
"* Must be greater than or equal to `ipv6_ra_preferred_lifetime`\n" +
"* Default: 86400 (24 hours)\n" +
"* After this time, addresses become invalid",
Type: schema.TypeInt,
Optional: true,
Default: 86400,
},
"multicast_dns": {
Description: "Specifies whether Multicast DNS (mDNS) is enabled or not on the network (Controller >=v7).",
Type: schema.TypeBool,
Optional: true,
Description: "Enables Multicast DNS (mDNS/Bonjour/Avahi) on the network. When enabled:\n" +
"* Allows device discovery (e.g., printers, Chromecasts)\n" +
"* Supports zero-configuration networking\n" +
"* Available on Controller version 7 and later",
Type: schema.TypeBool,
Optional: true,
},
"wan_ip": {
Description: "The IPv4 address of the WAN.",
Description: "The static IPv4 address for WAN interface.\n" +
"Required when `wan_type` is 'static'.\n" +
"Must be a valid public IP address assigned by your ISP.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsIPv4Address,
},
"wan_netmask": {
Description: "The IPv4 netmask of the WAN.",
Description: "The IPv4 netmask for WAN interface (e.g., '255.255.255.0').\n" +
"Required when `wan_type` is 'static'.\n" +
"Must match the subnet mask provided by your ISP.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsIPv4Address,
},
"wan_gateway": {
Description: "The IPv4 gateway of the WAN.",
Description: "The IPv4 gateway address for WAN interface.\n" +
"Required when `wan_type` is 'static'.\n" +
"Typically the ISP's router IP address.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsIPv4Address,
},
"wan_dns": {
Description: "DNS servers IPs of the WAN.",
Type: schema.TypeList,
Optional: true,
MaxItems: 4,
Description: "List of IPv4 DNS servers for WAN interface. Examples:\n" +
"* ISP provided DNS servers\n" +
"* Public DNS services (e.g., 8.8.8.8, 1.1.1.1)\n" +
"* Maximum 4 servers can be specified",
Type: schema.TypeList,
Optional: true,
MaxItems: 4,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.IsIPv4Address,
},
},
"wan_type": {
Description: "Specifies the IPV4 WAN connection type. Must be one of either `disabled`, `static`, `dhcp`, or `pppoe`.",
Description: "The IPv4 WAN connection type. Options:\n" +
"* `disabled` - WAN interface disabled\n" +
"* `static` - Static IP configuration\n" +
"* `dhcp` - Dynamic IP from ISP\n" +
"* `pppoe` - PPPoE connection (common for DSL)\n" +
"Choose based on your ISP's requirements.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateWANType,
},
"wan_networkgroup": {
Description: "Specifies the WAN network group. Must be one of either `WAN`, `WAN2` or `WAN_LTE_FAILOVER`.",
Description: "The WAN interface group assignment. Options:\n" +
"* `WAN` - Primary WAN interface\n" +
"* `WAN2` - Secondary WAN interface\n" +
"* `WAN_LTE_FAILOVER` - LTE backup connection\n" +
"Used for dual WAN and failover configurations.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateWANNetworkGroup,
},
"wan_egress_qos": {
Description: "Specifies the WAN egress quality of service.",
Type: schema.TypeInt,
Optional: true,
Default: 0,
Description: "Quality of Service (QoS) priority for WAN egress traffic (0-7).\n" +
"* 0 (default) - Best effort\n" +
"* 1-4 - Increasing priority\n" +
"* 5-7 - Highest priority, use sparingly\n" +
"Higher values get preferential treatment.",
Type: schema.TypeInt,
Optional: true,
Default: 0,
},
"wan_username": {
Description: "Specifies the IPV4 WAN username.",
Description: "Username for WAN authentication.\n" +
"* Required for PPPoE connections\n" +
"* May be needed for some ISP configurations\n" +
"* Cannot contain spaces or special characters",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateWANUsername,
},
"x_wan_password": {
Description: "Specifies the IPV4 WAN password.",
Description: "Password for WAN authentication.\n" +
"* Required for PPPoE connections\n" +
"* May be needed for some ISP configurations\n" +
"* Must be kept secret",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateWANPassword,
},
"wan_type_v6": {
Description: "Specifies the IPV6 WAN connection type. Must be one of either `disabled`, `static`, or `dhcpv6`.",
Description: "The IPv6 WAN connection type. Options:\n" +
"* `disabled` - IPv6 disabled\n" +
"* `static` - Static IPv6 configuration\n" +
"* `dhcpv6` - Dynamic IPv6 from ISP\n" +
"Choose based on your ISP's requirements.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validateWANTypeV6,
},
"wan_dhcp_v6_pd_size": {
Description: "Specifies the IPv6 prefix size to request from ISP. Must be between 48 and 64.",
Description: "The IPv6 prefix size to request from ISP. Must be between 48 and 64.\n" +
"Only applicable when `wan_type_v6` is 'dhcpv6'.",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(48, 64),
},
"wan_ipv6": {
Description: "The IPv6 address of the WAN.",
Description: "The static IPv6 address for WAN interface.\n" +
"Required when `wan_type_v6` is 'static'.\n" +
"Must be a valid public IPv6 address assigned by your ISP.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsIPv6Address,
},
"wan_gateway_v6": {
Description: "The IPv6 gateway of the WAN.",
Description: "The IPv6 gateway address for WAN interface.\n" +
"Required when `wan_type_v6` is 'static'.\n" +
"Typically the ISP's router IPv6 address.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsIPv6Address,
},
"wan_prefixlen": {
Description: "The IPv6 prefix length of the WAN. Must be between 1 and 128.",
Description: "The IPv6 prefix length for WAN interface. Must be between 1 and 128.\n" +
"Only applicable when `wan_type_v6` is 'static'.",
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntBetween(1, 128),

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/utils"
@@ -20,7 +21,12 @@ var (
func ResourceWLAN() *schema.Resource {
return &schema.Resource{
Description: "`unifi_wlan` manages a WiFi network / SSID.",
Description: "The `unifi_wlan` resource manages wireless networks (SSIDs) on UniFi access points.\n\n" +
"This resource allows you to create and manage WiFi networks with various security options including WPA2, WPA3, " +
"and enterprise authentication. You can configure features such as guest policies, minimum data rates, band steering, " +
"and scheduled availability.\n\n" +
"Each WLAN can be customized with different security settings, VLAN assignments, and client options to meet specific " +
"networking requirements.",
CreateContext: resourceWLANCreate,
ReadContext: resourceWLANRead,
@@ -32,80 +38,87 @@ func ResourceWLAN() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the network.",
Description: "The unique identifier of the wireless network in the UniFi controller.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the wlan with.",
Description: "The name of the UniFi site where the wireless network should be created. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"name": {
Description: "The SSID of the network. SSID length must be between 1 and 32 characters.",
Description: "The SSID (network name) that will be broadcast by the access points. Must be between 1 and 32 characters long.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 32),
},
"user_group_id": {
Description: "ID of the user group to use for this network.",
Description: "The ID of the user group that defines the rate limiting and firewall rules for clients on this network.",
Type: schema.TypeString,
Required: true,
},
"security": {
Description: "The type of WiFi security for this network. Valid values are: `wpapsk`, `wpaeap`, and `open`.",
Description: "The security protocol for the wireless network. Valid values are:\n" +
" * `wpapsk` - WPA Personal (PSK) with WPA2/WPA3 options\n" +
" * `wpaeap` - WPA Enterprise (802.1x)\n" +
" * `open` - Open network (no encryption)",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"wpapsk", "wpaeap", "open"}, false),
},
"wpa3_support": {
Description: "Enable WPA 3 support (security must be `wpapsk` and PMF must be turned on).",
Description: "Enable WPA3 security protocol. Requires security to be set to `wpapsk` and PMF mode to be enabled. WPA3 provides enhanced security features over WPA2.",
Type: schema.TypeBool,
Optional: true,
},
"wpa3_transition": {
Description: "Enable WPA 3 and WPA 2 support (security must be `wpapsk` and `wpa3_support` must be true).",
Type: schema.TypeBool,
Optional: true,
Description: "Enable WPA3 transition mode, which allows both WPA2 and WPA3 clients to connect. This provides backward compatibility while gradually transitioning to WPA3." +
" Requires security to be set to `wpapsk` and `wpa3_support` to be true.",
Type: schema.TypeBool,
Optional: true,
},
"pmf_mode": {
Description: "Enable Protected Management Frames. This cannot be disabled if using WPA 3. Valid values are `required`, `optional` and `disabled`.",
Description: "Protected Management Frames (PMF) mode. It cannot be disabled if using WPA3. Valid values are:\n" +
" * `required` - All clients must support PMF (required for WPA3)\n" +
" * `optional` - Clients can optionally use PMF (recommended when transitioning from WPA2 to WPA3)\n" +
" * `disabled` - PMF is disabled (not compatible with WPA3)",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"required", "optional", "disabled"}, false),
Default: "disabled",
},
"passphrase": {
Description: "The passphrase for the network, this is only required if `security` is not set to `open`.",
Description: "The WPA pre-shared key (password) for the network. Required when security is not set to `open`.",
Type: schema.TypeString,
// only required if security != open
Optional: true,
Sensitive: true,
},
"hide_ssid": {
Description: "Indicates whether or not to hide the SSID from broadcast.",
Description: "When enabled, the access points will not broadcast the network name (SSID). Clients will need to manually enter the SSID to connect.",
Type: schema.TypeBool,
Optional: true,
},
"is_guest": {
Description: "Indicates that this is a guest WLAN and should use guest behaviors.",
Description: "Mark this as a guest network. Guest networks are isolated from other networks and can have special restrictions like captive portals.",
Type: schema.TypeBool,
Optional: true,
},
"multicast_enhance": {
Description: "Indicates whether or not Multicast Enhance is turned of for the network.",
Description: "Enable multicast enhancement to convert multicast traffic to unicast for better reliability and performance, especially for applications like video streaming.",
Type: schema.TypeBool,
Optional: true,
},
"mac_filter_enabled": {
Description: "Indicates whether or not the MAC filter is turned of for the network.",
Description: "Enable MAC address filtering to control network access based on client MAC addresses. Works in conjunction with `mac_filter_list` and `mac_filter_policy`.",
Type: schema.TypeBool,
Optional: true,
},
"mac_filter_list": {
Description: "List of MAC addresses to filter (only valid if `mac_filter_enabled` is `true`).",
Description: "List of MAC addresses to filter in XX:XX:XX:XX:XX:XX format. Only applied when `mac_filter_enabled` is true. MAC addresses are case-insensitive.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{
@@ -115,51 +128,52 @@ func ResourceWLAN() *schema.Resource {
},
},
"mac_filter_policy": {
Description: "MAC address filter policy (only valid if `mac_filter_enabled` is `true`).",
Description: "MAC address filter policy. Valid values are:\n" +
" * `allow` - Only allow listed MAC addresses\n" +
" * `deny` - Block listed MAC addresses",
Type: schema.TypeString,
Optional: true,
Default: "deny",
ValidateFunc: validation.StringInSlice([]string{"allow", "deny"}, false),
},
"radius_profile_id": {
Description: "ID of the RADIUS profile to use when security `wpaeap`. You can query this via the " +
"`unifi_radius_profile` data source.",
Type: schema.TypeString,
Optional: true,
Description: "ID of the RADIUS profile to use for WPA Enterprise authentication (when security is 'wpaeap'). Reference existing profiles using the `unifi_radius_profile` data source.",
Type: schema.TypeString,
Optional: true,
},
"schedule": {
Description: "start and stop schedules for the WLAN",
Description: "Time-based access control configuration for the wireless network. Allows automatic enabling/disabling of the network on specified schedules.",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"day_of_week": {
Description: "Day of week for the block. Valid values are `sun`, `mon`, `tue`, `wed`, `thu`, `fri`, `sat`.",
Description: "Day of week. Valid values: `sun`, `mon`, `tue`, `wed`, `thu`, `fri`, `sat`.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"sun", "mon", "tue", "wed", "thu", "fri", "sat", "sun"}, false),
ValidateFunc: validation.StringInSlice([]string{"sun", "mon", "tue", "wed", "thu", "fri", "sat"}, false),
},
"start_hour": {
Description: "start hour for the block (0-23).",
Description: "Start hour in 24-hour format (0-23).",
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntBetween(0, 23),
},
"start_minute": {
Description: "start minute for the block (0-59).",
Description: "Start minute (0-59).",
Type: schema.TypeInt,
Optional: true,
Default: 0,
ValidateFunc: validation.IntBetween(0, 59),
},
"duration": {
Description: "Length of the block in minutes.",
Description: "Duration in minutes that the network should remain active.",
Type: schema.TypeInt,
Required: true,
ValidateFunc: validation.IntAtLeast(1),
},
"name": {
Description: "Name of the block.",
Description: "Friendly name for this schedule block (e.g., 'Business Hours', 'Weekend Access').",
Type: schema.TypeString,
Optional: true,
},
@@ -167,73 +181,78 @@ func ResourceWLAN() *schema.Resource {
},
},
"no2ghz_oui": {
Description: "Connect high performance clients to 5 GHz only.",
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "When enabled, devices from specific manufacturers (identified by their OUI - Organizationally Unique Identifier) " +
"will be prevented from connecting on 2.4GHz and forced to use 5GHz. This improves overall network performance by " +
"ensuring capable devices use the less congested 5GHz band. Common examples include newer smartphones and laptops.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"l2_isolation": {
Description: "Isolates stations on layer 2 (ethernet) level.",
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Isolates wireless clients from each other at layer 2 (ethernet) level. When enabled, devices on this WLAN " +
"cannot communicate directly with each other, improving security especially for guest networks or IoT devices. " +
"Each client can only communicate with the gateway/router.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"proxy_arp": {
Description: "Reduces airtime usage by allowing APs to \"proxy\" common broadcast frames as unicast.",
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Enable ARP proxy on this WLAN. When enabled, the UniFi controller will respond to ARP requests on behalf " +
"of clients, reducing broadcast traffic and potentially improving network performance. This is particularly useful " +
"in high-density wireless environments.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"bss_transition": {
Description: "Improves client transitions between APs when they have a weak signal.",
Description: "Enable BSS Transition Management to help clients roam between APs more efficiently.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"uapsd": {
Description: "Enable Unscheduled Automatic Power Save Delivery.",
Description: "Enable Unscheduled Automatic Power Save Delivery to improve battery life for mobile devices.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"fast_roaming_enabled": {
Description: "Enables 802.11r fast roaming.",
Description: "Enable 802.11r Fast BSS Transition for seamless roaming between APs. Requires client device support.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"minimum_data_rate_2g_kbps": {
Description: "Set minimum data rate control for 2G devices, in Kbps. " +
"Use `0` to disable minimum data rates. " +
"Valid values are: " + utils.MarkdownValueListInt(wlanValidMinimumDataRate2g) + ".",
Type: schema.TypeInt,
Optional: true,
// TODO: this validation is from the UI, if other values work, perhaps remove this is set it to a range instead?
Description: "Minimum data rate for 2.4GHz devices in Kbps. Use `0` to disable. Valid values: " +
utils.MarkdownValueListInt(wlanValidMinimumDataRate2g),
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntInSlice(append([]int{0}, wlanValidMinimumDataRate2g...)),
},
"minimum_data_rate_5g_kbps": {
Description: "Set minimum data rate control for 5G devices, in Kbps. " +
"Use `0` to disable minimum data rates. " +
"Valid values are: " + utils.MarkdownValueListInt(wlanValidMinimumDataRate5g) + ".",
Type: schema.TypeInt,
Optional: true,
// TODO: this validation is from the UI, if other values work, perhaps remove this is set it to a range instead?
Description: "Minimum data rate for 5GHz devices in Kbps. Use `0` to disable. Valid values: " +
utils.MarkdownValueListInt(wlanValidMinimumDataRate5g),
Type: schema.TypeInt,
Optional: true,
ValidateFunc: validation.IntInSlice(append([]int{0}, wlanValidMinimumDataRate5g...)),
},
"wlan_band": {
Description: "Radio band your WiFi network will use.",
Description: "Radio band selection. Valid values:\n" +
" * `both` - Both 2.4GHz and 5GHz (default)\n" +
" * `2g` - 2.4GHz only\n" +
" * `5g` - 5GHz only",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"2g", "5g", "both"}, false),
Default: "both",
},
"network_id": {
Description: "ID of the network for this SSID",
Description: "ID of the network (VLAN) for this SSID. Used to assign the WLAN to a specific network segment.",
Type: schema.TypeString,
Optional: true,
},
"ap_group_ids": {
Description: "IDs of the AP groups to use for this network.",
Description: "IDs of the AP groups that should broadcast this SSID. Used to control which access points broadcast this network.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{

View File

@@ -95,7 +95,7 @@ func New(version string) func() *schema.Provider {
DataSourcesMap: map[string]*schema.Resource{
"unifi_ap_group": apgroup.DataAPGroup(),
"unifi_network": network.DataNetwork(),
"unifi_port_profile": network.DataPortProfile(),
"unifi_port_profile": device.DataPortProfile(),
"unifi_radius_profile": radius.DataRADIUSProfile(),
"unifi_user_group": user.DataUserGroup(),
"unifi_user": user.DataUser(),
@@ -111,7 +111,7 @@ func New(version string) func() *schema.Provider {
"unifi_port_forward": routing.ResourcePortForward(),
"unifi_static_route": routing.ResourceStaticRoute(),
"unifi_wlan": network.ResourceWLAN(),
"unifi_port_profile": network.ResourcePortProfile(),
"unifi_port_profile": device.ResourcePortProfile(),
"unifi_site": site.ResourceSite(),
"unifi_account": radius.ResourceAccount(),
"unifi_radius_profile": radius.ResourceRadiusProfile(),

View File

@@ -2,6 +2,7 @@ package radius
import (
"context"
"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"

View File

@@ -3,6 +3,7 @@ package radius
import (
"context"
"errors"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/utils"
@@ -14,11 +15,23 @@ import (
func ResourceAccount() *schema.Resource {
return &schema.Resource{
Description: "`unifi_account` manages a RADIUS user account\n\n" +
"To authenticate devices based on MAC address, use the MAC address as the username and password under client creation. \n" +
"Convert lowercase letters to uppercase, and also remove colons or periods from the MAC address. \n\n" +
"ATTENTION: If the user profile does not include a VLAN, the client will fall back to the untagged VLAN. \n\n" +
"NOTE: MAC-based authentication accounts can only be used for wireless and wired clients. L2TP remote access does not apply.",
Description: "The `unifi_account` resource manages RADIUS user accounts in the UniFi controller's built-in RADIUS server.\n\n" +
"This resource is used for:\n" +
" * WPA2/WPA3-Enterprise wireless authentication\n" +
" * 802.1X wired authentication\n" +
" * MAC-based device authentication\n" +
" * VLAN assignment through RADIUS attributes\n\n" +
"Important Notes:\n" +
"1. For MAC-based authentication:\n" +
" * Use the device's MAC address as both username and password\n" +
" * Convert MAC address to uppercase with no separators (e.g., '00:11:22:33:44:55' becomes '001122334455')\n" +
"2. VLAN Assignment:\n" +
" * If no VLAN is specified in the profile, clients will use the network's untagged VLAN\n" +
" * VLAN assignment uses standard RADIUS tunnel attributes\n\n" +
"Limitations:\n" +
" * MAC-based authentication works only for wireless and wired clients\n" +
" * L2TP remote access VPN is not supported with MAC authentication\n" +
" * Accounts must be unique within a site",
CreateContext: resourceAccountCreate,
ReadContext: resourceAccountRead,
@@ -30,46 +43,57 @@ func ResourceAccount() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the account.",
Description: "The unique identifier of the RADIUS account in the UniFi controller. This is automatically assigned.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the account with.",
Description: "The name of the UniFi site where this RADIUS account should be created. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"name": {
Description: "The name of the account.",
Type: schema.TypeString,
Required: true,
Description: "The username for this RADIUS account. For regular users, this can be any unique identifier. For MAC-based " +
"authentication, this must be the device's MAC address in uppercase with no separators (e.g., '001122334455').",
Type: schema.TypeString,
Required: true,
},
"password": {
Description: "The password of the account.",
Type: schema.TypeString,
Required: true,
Sensitive: true,
Description: "The password for this RADIUS account. For MAC-based authentication, this must match the username (the MAC address). " +
"For regular users, this should be a secure password following your organization's password policies.",
Type: schema.TypeString,
Required: true,
Sensitive: true,
},
"tunnel_type": {
Description: "See [RFC 2868](https://www.rfc-editor.org/rfc/rfc2868) section 3.1", // @TODO: better documentation https://help.ui.com/hc/en-us/articles/360015268353-UniFi-USG-UDM-Configuring-RADIUS-Server#6
Description: "The RADIUS tunnel type attribute ([RFC 2868](https://tools.ietf.org/html/rfc2868), section 3.1). Common values:\n" +
" * `13` - VLAN (default)\n" +
" * `1` - Point-to-Point Protocol (PPTP)\n" +
" * `9` - Point-to-Point Protocol (L2TP)\n\n" +
"Only change this if you need specific tunneling behavior.",
Type: schema.TypeInt,
Optional: true,
Default: 13,
ValidateFunc: validation.IntBetween(1, 13),
},
"tunnel_medium_type": {
Description: "See [RFC 2868](https://www.rfc-editor.org/rfc/rfc2868) section 3.2", // @TODO: better documentation https://help.ui.com/hc/en-us/articles/360015268353-UniFi-USG-UDM-Configuring-RADIUS-Server#6
Description: "The RADIUS tunnel medium type attribute ([RFC 2868](https://tools.ietf.org/html/rfc2868), section 3.2). Common values:\n" +
" * `6` - 802 (includes Ethernet, Token Ring, FDDI) (default)\n" +
" * `1` - IPv4\n" +
" * `2` - IPv6\n\n" +
"Only change this if you need specific tunneling behavior.",
Type: schema.TypeInt,
Optional: true,
Default: 6,
ValidateFunc: validation.IntBetween(1, 15),
},
"network_id": {
Description: "ID of the network for this account",
Type: schema.TypeString,
Optional: true,
Description: "The ID of the network (VLAN) to assign to clients authenticating with this account. This is used in " +
"conjunction with the tunnel attributes to provide VLAN assignment via RADIUS.",
Type: schema.TypeString,
Optional: true,
},
},
}

View File

@@ -4,9 +4,10 @@ import (
"context"
"errors"
"fmt"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"strings"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/go-unifi/unifi"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -15,7 +16,16 @@ import (
func ResourceRadiusProfile() *schema.Resource {
return &schema.Resource{
Description: "`unifi_radius_profile` manages RADIUS profiles.",
Description: "The `unifi_radius_profile` resource manages RADIUS authentication profiles for UniFi networks.\n\n" +
"RADIUS (Remote Authentication Dial-In User Service) profiles enable enterprise-grade authentication and authorization for:\n" +
" * 802.1X network access control\n" +
" * WPA2/WPA3-Enterprise wireless networks\n" +
" * Dynamic VLAN assignment\n" +
" * User activity accounting\n\n" +
"Each profile can be configured with:\n" +
" * Multiple authentication and accounting servers\n" +
" * VLAN assignment settings\n" +
" * Accounting update intervals",
CreateContext: resourceRadiusProfileCreate,
ReadContext: resourceRadiusProfileRead,
@@ -27,117 +37,133 @@ func ResourceRadiusProfile() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the settings.",
Description: "The unique identifier of the RADIUS profile in the UniFi controller.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the settings with.",
Description: "The name of the UniFi site where the RADIUS profile should be created. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"name": {
Description: "The name of the profile.",
Description: "A friendly name for the RADIUS profile to help identify its purpose (e.g., 'Corporate Users' or 'Guest Access').",
Type: schema.TypeString,
Required: true,
},
"accounting_enabled": {
Description: "Specifies whether to use RADIUS accounting.",
Description: "Enable RADIUS accounting to track user sessions, including login/logout times and data usage. Useful for billing and audit purposes.",
Type: schema.TypeBool,
Default: false,
Optional: true,
},
"interim_update_enabled": {
Description: "Specifies whether to use interim_update.",
Description: "Enable periodic updates during active sessions. This allows tracking of ongoing session data like bandwidth usage.",
Type: schema.TypeBool,
Default: false,
Optional: true,
},
"interim_update_interval": {
Description: "Specifies interim_update interval.",
Description: "The interval (in seconds) between interim updates when `interim_update_enabled` is true. Default is 3600 seconds (1 hour).",
Type: schema.TypeInt,
Default: 3600,
Optional: true,
},
"use_usg_acct_server": {
Description: "Specifies whether to use usg as a RADIUS accounting server.",
Description: "Use the controller as a RADIUS accounting server. This allows local accounting without an external RADIUS server.",
Type: schema.TypeBool,
Default: false,
Optional: true,
},
"use_usg_auth_server": {
Description: "Specifies whether to use usg as a RADIUS authentication server.",
Description: "Use the controller as a RADIUS authentication server. This allows local authentication without an external RADIUS server.",
Type: schema.TypeBool,
Default: false,
Optional: true,
},
"vlan_enabled": {
Description: "Specifies whether to use vlan on wired connections.",
Description: "Enable VLAN assignment for wired clients based on RADIUS attributes. This allows network segmentation based on user authentication.",
Type: schema.TypeBool,
Default: false,
Optional: true,
},
"vlan_wlan_mode": {
Description: "Specifies whether to use vlan on wireless connections. Must be one of `disabled`, `optional`, or `required`.",
Description: "VLAN assignment mode for wireless networks. Valid values are:\n" +
" * `disabled` - Do not use RADIUS-assigned VLANs\n" +
" * `optional` - Use RADIUS-assigned VLAN if provided\n" +
" * `required` - Require RADIUS-assigned VLAN for authentication to succeed",
Type: schema.TypeString,
Default: "",
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"disabled", "optional", "required"}, false),
},
"auth_server": {
Description: "RADIUS authentication servers.",
Type: schema.TypeList,
Optional: true,
Description: "List of RADIUS authentication servers to use with this profile. Multiple servers provide failover - if the first " +
"server is unreachable, the system will try the next server in the list. Each server requires:\n" +
" * IP address of the RADIUS server\n" +
" * Shared secret for secure communication",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"ip": {
Description: "IP address of authentication service server.",
Description: "The IPv4 address of the RADIUS authentication server (e.g., '192.168.1.100'). Must be reachable from " +
"your UniFi network.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.IsIPAddress,
},
"port": {
Description: "Port of authentication service.",
Description: "The UDP port number where the RADIUS authentication service is listening. The standard port is 1812, " +
"but this can be changed if needed to match your server configuration.",
Type: schema.TypeInt,
Optional: true,
Default: 1812,
ValidateFunc: validation.IsPortNumber,
},
"xsecret": {
Description: "RADIUS secret.",
Type: schema.TypeString,
Required: true,
Sensitive: true,
Description: "The shared secret key used to secure communication between the UniFi controller and the RADIUS server. " +
"This must match the secret configured on your RADIUS server.",
Type: schema.TypeString,
Required: true,
Sensitive: true,
},
},
},
},
"acct_server": {
Description: "RADIUS accounting servers.",
Type: schema.TypeList,
Optional: true,
Description: "List of RADIUS accounting servers to use with this profile. Accounting servers track session data like " +
"connection time and data usage. Each server requires:\n" +
" * IP address of the RADIUS server\n" +
" * Port number (default: 1813)\n" +
" * Shared secret for secure communication",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"ip": {
Description: "IP address of accounting service server.",
Description: "The IPv4 address of the RADIUS accounting server (e.g., '192.168.1.100'). Must be reachable from " +
"your UniFi network.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.IsIPAddress,
},
"port": {
Description: "Port of accounting service.",
Description: "The UDP port number where the RADIUS accounting service is listening. The standard port is 1813, " +
"but this can be changed if needed to match your server configuration.",
Type: schema.TypeInt,
Optional: true,
Default: 1813,
ValidateFunc: validation.IsPortNumber,
},
"xsecret": {
Description: "RADIUS secret.",
Type: schema.TypeString,
Required: true,
Sensitive: true,
Description: "The shared secret key used to secure communication between the UniFi controller and the RADIUS server. " +
"This must match the secret configured on your RADIUS server.",
Type: schema.TypeString,
Required: true,
Sensitive: true,
},
},
},

View File

@@ -3,6 +3,7 @@ package routing
import (
"context"
"errors"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/utils"
@@ -14,7 +15,13 @@ import (
func ResourcePortForward() *schema.Resource {
return &schema.Resource{
Description: "`unifi_port_forward` manages a port forwarding rule on the gateway.",
Description: "The `unifi_port_forward` resource manages port forwarding rules on UniFi controllers.\n\n" +
"Port forwarding allows external traffic to reach services hosted on your internal network by mapping external ports to internal IP addresses and ports. " +
"This is commonly used for:\n" +
" * Hosting web servers, game servers, or other services\n" +
" * Remote access to internal services\n" +
" * Application-specific requirements\n\n" +
"Each rule can be configured with source IP restrictions, protocol selection, and logging options for enhanced security and monitoring.",
CreateContext: resourcePortForwardCreate,
ReadContext: resourcePortForwardRead,
@@ -26,19 +33,19 @@ func ResourcePortForward() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the port forwarding rule.",
Description: "The unique identifier of the port forwarding rule in the UniFi controller.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the port forwarding rule with.",
Description: "The name of the UniFi site where the port forwarding rule should be created. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"dst_port": {
Description: "The destination port for the forwarding.",
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,
@@ -53,46 +60,53 @@ func ResourcePortForward() *schema.Resource {
"port forwarding rule you can remove it from your configuration.",
},
"fwd_ip": {
Description: "The IPv4 address to forward traffic to.",
Description: "The internal IPv4 address of the device or service that will receive the forwarded traffic (e.g., '192.168.1.100').",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsIPv4Address,
},
"fwd_port": {
Description: "The port to forward traffic to.",
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,
},
"log": {
Description: "Specifies whether to log forwarded traffic or not.",
Description: "Enable logging of traffic matching this port forwarding rule. Useful for monitoring and troubleshooting.",
Type: schema.TypeBool,
Default: false,
Optional: true,
},
"name": {
Description: "The name of the port forwarding rule.",
Description: "A friendly name for the port forwarding rule to help identify its purpose (e.g., 'Web Server' or 'Game Server').",
Type: schema.TypeString,
Optional: true,
},
"port_forward_interface": {
Description: "The port forwarding interface. Can be `wan`, `wan2`, or `both`.",
Description: "The WAN interface to apply the port forwarding rule to. Valid values are:\n" +
" * `wan` - Primary WAN interface\n" +
" * `wan2` - Secondary WAN interface\n" +
" * `both` - Both WAN interfaces",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"wan", "wan2", "both"}, false),
},
"protocol": {
Description: "The protocol for the port forwarding rule. Can be `tcp`, `udp`, or `tcp_udp`.",
Description: "The network protocol(s) this rule applies to. Valid values are:\n" +
" * `tcp_udp` - Both TCP and UDP (default)\n" +
" * `tcp` - TCP only\n" +
" * `udp` - UDP only",
Type: schema.TypeString,
Optional: true,
Default: "tcp_udp",
ValidateFunc: validation.StringInSlice([]string{"tcp_udp", "tcp", "udp"}, false),
},
"src_ip": {
Description: "The source IPv4 address (or CIDR) of the port forwarding rule. For all traffic, specify `any`.",
Type: schema.TypeString,
Optional: true,
Default: "any",
Description: "The source IP address or network in CIDR notation that is allowed to use this port forward. Use 'any' to allow all source IPs. " +
"Examples: '203.0.113.1' for a single IP, '203.0.113.0/24' for a network, or 'any' for all IPs.",
Type: schema.TypeString,
Optional: true,
Default: "any",
ValidateFunc: validation.Any(
validation.StringInSlice([]string{"any"}, false),
validation.IsIPv4Address,

View File

@@ -15,7 +15,13 @@ import (
func ResourceStaticRoute() *schema.Resource {
return &schema.Resource{
Description: "`unifi_static_route` manages a static route.",
Description: "The `unifi_static_route` resource manages static routes on UniFi Security Gateways (USG) and UniFi Dream Machines (UDM/UDM-Pro).\n\n" +
"Static routes allow you to manually configure routing paths for specific networks. This is useful for:\n" +
" * Connecting to networks not directly connected to your UniFi gateway\n" +
" * Creating backup routes for redundancy\n" +
" * Implementing policy-based routing\n" +
" * Blocking traffic to specific networks using blackhole routes\n\n" +
"Routes can be configured to use either a next-hop IP address, a specific interface, or as a blackhole route.",
CreateContext: resourceStaticRouteCreate,
ReadContext: resourceStaticRouteRead,
@@ -27,52 +33,58 @@ func ResourceStaticRoute() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the static route.",
Description: "The unique identifier of the static route in the UniFi controller.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the static route with.",
Description: "The name of the UniFi site where the static route should be created. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"name": {
Description: "The name of the static route.",
Description: "A friendly name for the static route to help identify its purpose (e.g., 'Backup DC Link' or 'Cloud VPN Route').",
Type: schema.TypeString,
Required: true,
},
"network": {
Description: "The network subnet address.",
Description: "The destination network in CIDR notation that this route will direct traffic to (e.g., '10.0.0.0/16' or '192.168.100.0/24').",
Type: schema.TypeString,
Required: true,
ValidateFunc: utils.CidrValidate,
DiffSuppressFunc: utils.CidrDiffSuppress,
},
"type": {
Description: "The type of static route. Can be `interface-route`, `nexthop-route`, or `blackhole`.",
Description: "The type of static route. Valid values are:\n" +
" * `interface-route` - Route traffic through a specific interface\n" +
" * `nexthop-route` - Route traffic to a specific next-hop IP address\n" +
" * `blackhole` - Drop all traffic to this network",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"interface-route", "nexthop-route", "blackhole"}, false),
},
"distance": {
Description: "The distance of the static route.",
Description: "The administrative distance for this route. Lower values are preferred. Use this to control route selection when multiple routes to the same destination exist.",
Type: schema.TypeInt,
Required: true,
},
"next_hop": {
Description: "The next hop of the static route (only valid for `nexthop-route` type).",
Description: "The IP address of the next hop router for this route. Only used when type is set to 'nexthop-route'. This should be an IP address that is directly reachable from your UniFi gateway.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsIPAddress,
},
"interface": {
Description: "The interface of the static route (only valid for `interface-route` type). This can be `WAN1`, `WAN2`, or a network ID.",
Type: schema.TypeString,
Optional: true,
Description: "The outbound interface to use for this route. Only used when type is set to 'interface-route'. Can be:\n" +
" * `WAN1` - Primary WAN interface\n" +
" * `WAN2` - Secondary WAN interface\n" +
" * A network ID for internal networks",
Type: schema.TypeString,
Optional: true,
},
},
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/utils"
@@ -17,7 +18,16 @@ import (
func ResourceSettingMgmt() *schema.Resource {
return &schema.Resource{
Description: "`unifi_setting_mgmt` manages settings for a unifi site.",
Description: "The `unifi_setting_mgmt` resource manages site-wide management settings in the UniFi controller.\n\n" +
"This resource allows you to configure important management features including:\n" +
" * Automatic firmware upgrades for UniFi devices\n" +
" * SSH access for advanced configuration and troubleshooting\n" +
" * SSH key management for secure remote access\n\n" +
"These settings affect how the UniFi controller manages devices at the site level. " +
"They are particularly important for:\n" +
" * Maintaining device security through automatic updates\n" +
" * Enabling secure remote administration\n" +
" * Implementing SSH key-based authentication",
CreateContext: resourceSettingMgmtCreate,
ReadContext: resourceSettingMgmtRead,
@@ -29,50 +39,57 @@ func ResourceSettingMgmt() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the settings.",
Description: "The unique identifier of the management settings configuration in the UniFi controller.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the settings with.",
Description: "The name of the UniFi site where these management settings should be applied. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"auto_upgrade": {
Description: "Automatically upgrade device firmware.",
Type: schema.TypeBool,
Optional: true,
Description: "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.",
Type: schema.TypeBool,
Optional: true,
},
"ssh_enabled": {
Description: "Enable SSH authentication.",
Type: schema.TypeBool,
Optional: true,
Description: "Enable SSH access to UniFi devices at this site. When enabled, you can connect to devices using SSH for advanced " +
"configuration and troubleshooting. It's recommended to only enable this temporarily when needed.",
Type: schema.TypeBool,
Optional: true,
},
"ssh_key": {
Description: "SSH key.",
Type: schema.TypeSet,
Optional: true,
Description: "List of SSH public keys that are allowed to connect to UniFi devices when SSH is enabled. Using SSH keys is more " +
"secure than password authentication.",
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Description: "Name of SSH key.",
Description: "A friendly name for the SSH key to help identify its owner or purpose (e.g., 'admin-laptop' or 'backup-server').",
Type: schema.TypeString,
Required: true,
},
"type": {
Description: "Type of SSH key, e.g. ssh-rsa.",
Type: schema.TypeString,
Required: true,
Description: "The type of SSH key. Common values include:\n" +
" * `ssh-rsa` - RSA key (most common)\n" +
" * `ssh-ed25519` - Ed25519 key (more secure)\n" +
" * `ecdsa-sha2-nistp256` - ECDSA key",
Type: schema.TypeString,
Required: true,
},
"key": {
Description: "Public SSH key.",
Type: schema.TypeString,
Optional: true,
Description: "The public key string. This is the content that would normally go in an authorized_keys file, " +
"excluding the type and comment (e.g., 'AAAAB3NzaC1yc2EA...').",
Type: schema.TypeString,
Optional: true,
},
"comment": {
Description: "Comment.",
Description: "An optional comment to provide additional context about the key (e.g., 'generated on 2024-01-01' or 'expires 2025-12-31').",
Type: schema.TypeString,
Optional: true,
},

View File

@@ -3,6 +3,7 @@ package settings
import (
"context"
"errors"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/utils"
@@ -14,7 +15,16 @@ import (
func ResourceSettingRadius() *schema.Resource {
return &schema.Resource{
Description: "`unifi_setting_radius` manages settings for the built-in RADIUS server.",
Description: "The `unifi_setting_radius` resource manages the built-in RADIUS server configuration in the UniFi controller.\n\n" +
"This resource allows you to configure:\n" +
" * Authentication settings for network access control\n" +
" * Accounting settings for tracking user sessions\n" +
" * Security features like tunneled replies\n\n" +
"The RADIUS server is commonly used for:\n" +
" * Enterprise WPA2/WPA3-Enterprise wireless networks\n" +
" * 802.1X port-based network access control\n" +
" * Centralized user authentication and accounting\n\n" +
"When enabled, the RADIUS server can authenticate clients using the UniFi user database or external authentication sources.",
CreateContext: resourceSettingRadiusCreate,
ReadContext: resourceSettingRadiusRead,
@@ -26,61 +36,68 @@ func ResourceSettingRadius() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the settings.",
Description: "The unique identifier of the RADIUS settings configuration in the UniFi controller.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the settings with.",
Description: "The name of the UniFi site where these RADIUS settings should be applied. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"accounting_enabled": {
Description: "Enable RADIUS accounting",
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Enable RADIUS accounting to track user sessions, including connection time, data usage, and other metrics. " +
"This information can be useful for billing, capacity planning, and security auditing.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"accounting_port": {
Description: "The port for accounting communications.",
Description: "The UDP port number for RADIUS accounting communications. The standard port is 1813. Only change this if you " +
"need to avoid port conflicts or match specific network requirements.",
Type: schema.TypeInt,
Optional: true,
Default: 1813,
ValidateFunc: validation.IsPortNumber,
},
"auth_port": {
Description: "The port for authentication communications.",
Description: "The UDP port number for RADIUS authentication communications. The standard port is 1812. Only change this if you " +
"need to avoid port conflicts or match specific network requirements.",
Type: schema.TypeInt,
Optional: true,
Default: 1812,
ValidateFunc: validation.IsPortNumber,
},
"interim_update_interval": {
Description: "Statistics will be collected from connected clients at this interval.",
Type: schema.TypeInt,
Optional: true,
Default: 3600,
Description: "The interval (in seconds) at which the RADIUS server collects and updates statistics from connected clients. " +
"Default is 3600 seconds (1 hour). Lower values provide more frequent updates but increase server load.",
Type: schema.TypeInt,
Optional: true,
Default: 3600,
},
"tunneled_reply": {
Description: "Encrypt communication between the server and the client.",
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Enable encrypted communication between the RADIUS server and clients using RADIUS tunneling. This adds an extra " +
"layer of security by protecting RADIUS attributes in transit.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"secret": {
Description: "RAIDUS secret passphrase.",
Type: schema.TypeString,
Sensitive: true,
Optional: true,
Default: "",
Description: "The shared secret passphrase used to authenticate RADIUS clients (like wireless access points) with the RADIUS server. " +
"This should be a strong, random string known only to the server and its clients.",
Type: schema.TypeString,
Sensitive: true,
Optional: true,
Default: "",
},
"enabled": {
Description: "RAIDUS server enabled.",
Type: schema.TypeBool,
Default: true,
Optional: true,
Description: "Enable or disable the built-in RADIUS server. When disabled, no RADIUS authentication or accounting services " +
"will be provided, affecting any network services that rely on RADIUS (like WPA2-Enterprise networks).",
Type: schema.TypeBool,
Default: true,
Optional: true,
},
},
}

View File

@@ -4,9 +4,10 @@ import (
"context"
"errors"
"fmt"
"sync"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/utils"
"sync"
"github.com/filipowm/go-unifi/unifi"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
@@ -26,7 +27,15 @@ func resourceSettingUsgLocker(f func(context.Context, *schema.ResourceData, inte
func ResourceSettingUsg() *schema.Resource {
return &schema.Resource{
Description: "`unifi_setting_usg` manages settings for a Unifi Security Gateway.",
Description: "The `unifi_setting_usg` resource manages advanced settings for UniFi Security Gateways (USG) and UniFi Dream Machines (UDM/UDM-Pro).\n\n" +
"This resource allows you to configure gateway-specific features including:\n" +
" * Multicast DNS (mDNS) for service discovery\n" +
" * DHCP relay for forwarding DHCP requests to external servers\n\n" +
"These settings are particularly useful for:\n" +
" * Enabling device discovery across VLANs (using mDNS)\n" +
" * Centralizing DHCP management in enterprise environments\n" +
" * Integration with existing network infrastructure\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+.",
CreateContext: resourceSettingUsgLocker(resourceSettingUsgUpsert),
ReadContext: resourceSettingUsgLocker(resourceSettingUsgRead),
@@ -38,29 +47,32 @@ func ResourceSettingUsg() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the settings.",
Description: "The unique identifier of the USG settings configuration in the UniFi controller.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the settings with.",
Description: "The name of the UniFi site where these USG settings should be applied. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"multicast_dns_enabled": {
Description: "Whether multicast DNS is enabled.",
Type: schema.TypeBool,
Optional: true,
Computed: true,
Description: "Enable multicast DNS (mDNS/Bonjour/Avahi) forwarding across VLANs. This allows devices to discover services " +
"(like printers, Chromecasts, etc.) even when they are on different networks. Note: Not supported on UniFi OS v7+.",
Type: schema.TypeBool,
Optional: true,
Computed: true,
},
"dhcp_relay_servers": {
Description: "The DHCP relay servers.",
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 5,
Description: "List of up to 5 DHCP relay servers (specified by IP address) that will receive forwarded DHCP requests. " +
"This is useful when you want to use external DHCP servers instead of the built-in DHCP server. " +
"Example: ['192.168.1.5', '192.168.2.5']",
Type: schema.TypeList,
Optional: true,
Computed: true,
MaxItems: 5,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.All(

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/filipowm/go-unifi/unifi"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
@@ -12,7 +13,17 @@ import (
func ResourceSite() *schema.Resource {
return &schema.Resource{
Description: "`unifi_site` manages Unifi sites",
Description: "The `unifi_site` resource manages UniFi sites, which are logical groupings of UniFi devices and their configurations.\n\n" +
"Sites in UniFi are used to:\n" +
" * Organize network devices and settings for different physical locations\n" +
" * Isolate configurations between different networks or customers\n" +
" * Apply different policies and configurations to different groups of devices\n\n" +
"Each site maintains its own:\n" +
" * Network configurations\n" +
" * Wireless networks (WLANs)\n" +
" * Security policies\n" +
" * Device configurations\n\n" +
"A UniFi controller can manage multiple sites, making it ideal for multi-tenant or distributed network deployments.",
CreateContext: resourceSiteCreate,
ReadContext: resourceSiteRead,
@@ -24,19 +35,21 @@ func ResourceSite() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the site.",
Description: "The unique identifier of the site in the UniFi controller. This is automatically generated when the site is created.",
Type: schema.TypeString,
Computed: true,
},
"description": {
Description: "The description of the site.",
Type: schema.TypeString,
Required: true,
Description: "A human-readable description of the site (e.g., 'Main Office', 'Remote Branch', 'Client A Network'). " +
"This is used as the display name in the UniFi controller interface.",
Type: schema.TypeString,
Required: true,
},
"name": {
Description: "The name of the site.",
Type: schema.TypeString,
Computed: true,
Description: "The site's internal name in the UniFi system. This is automatically generated based on the description " +
"and is used in API calls and configurations. It's typically a lowercase, hyphenated version of the description.",
Type: schema.TypeString,
Computed: true,
},
},
}

View File

@@ -3,6 +3,7 @@ package user
import (
"context"
"errors"
"github.com/filipowm/go-unifi/unifi"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/utils"
@@ -13,10 +14,21 @@ import (
func ResourceUser() *schema.Resource {
return &schema.Resource{
Description: "`unifi_user` manages a user (or \"client\" in the UI) of the network, these are identified " +
"by unique MAC addresses.\n\n" +
"Users are created in the controller when observed on the network, so the resource defaults to allowing " +
"itself to just take over management of a MAC address, but this can be turned off.",
Description: "The `unifi_user` resource manages network clients in the UniFi controller, which are identified by their unique MAC addresses.\n\n" +
"This resource allows you to manage:\n" +
" * Fixed IP assignments\n" +
" * User groups and network access\n" +
" * Network blocking and restrictions\n" +
" * Local DNS records\n\n" +
"Important Notes:\n" +
" * Users are automatically created in the controller when devices connect to the network\n" +
" * By default, this resource can take over management of existing users (controlled by `allow_existing`)\n" +
" * Users can be 'forgotten' on destroy (controlled by `skip_forget_on_destroy`)\n\n" +
"This resource is particularly useful for:\n" +
" * Managing static IP assignments\n" +
" * Implementing access control\n" +
" * Setting up local DNS records\n" +
" * Organizing devices into user groups",
CreateContext: resourceUserCreate,
ReadContext: resourceUserRead,
@@ -28,19 +40,20 @@ func ResourceUser() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the user.",
Description: "The unique identifier of the user in the UniFi controller. This is automatically assigned.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the user with.",
Description: "The name of the UniFi site where this user should be managed. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"mac": {
Description: "The MAC address of the user.",
Description: "The MAC address of the device/client. This is used as the unique identifier and cannot be changed " +
"after creation. Must be a valid MAC address format (e.g., '00:11:22:33:44:55'). MAC addresses are case-insensitive.",
Type: schema.TypeString,
Required: true,
ForceNew: true,
@@ -48,36 +61,42 @@ func ResourceUser() *schema.Resource {
ValidateFunc: validation.StringMatch(utils.MacAddressRegexp, "Mac address is invalid"),
},
"name": {
Description: "The name of the user.",
Type: schema.TypeString,
Required: true,
Description: "A friendly name for the device/client. This helps identify the device in the UniFi interface " +
"(eg. 'Living Room TV', 'John's Laptop').",
Type: schema.TypeString,
Required: true,
},
"user_group_id": {
Description: "The user group ID for the user.",
Type: schema.TypeString,
Optional: true,
Description: "The ID of the user group this client belongs to. User groups can be used to apply common " +
"settings and restrictions to multiple clients.",
Type: schema.TypeString,
Optional: true,
},
"note": {
Description: "A note with additional information for the user.",
Type: schema.TypeString,
Optional: true,
Description: "Additional information about the client that you want to record (e.g., 'Company asset tag #12345', " +
"'Guest device - expires 2024-03-01').",
Type: schema.TypeString,
Optional: true,
},
// TODO: combine this with output IP for a single attribute ip_address?
"fixed_ip": {
Description: "A fixed IPv4 address for this user.",
Description: "A static IPv4 address to assign to this client. Ensure this IP is within the client's network range " +
"and not already assigned to another device.",
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.IsIPv4Address,
},
"network_id": {
Description: "The network ID for this user.",
Type: schema.TypeString,
Optional: true,
Description: "The ID of the network this client should be associated with. This is particularly important " +
"when using VLANs or multiple networks.",
Type: schema.TypeString,
Optional: true,
},
"blocked": {
Description: "Specifies whether this user should be blocked from the network.",
Type: schema.TypeBool,
Optional: true,
Description: "When true, this client will be blocked from accessing the network. Useful for temporarily " +
"or permanently restricting network access for specific devices.",
Type: schema.TypeBool,
Optional: true,
},
"dev_id_override": {
Description: "Override the device fingerprint.",
@@ -85,23 +104,29 @@ func ResourceUser() *schema.Resource {
Optional: true,
},
"local_dns_record": {
Description: "Specifies the local DNS record for this user.",
Type: schema.TypeString,
Optional: true,
Description: "A local DNS hostname for this client. When set, other devices on the network can resolve " +
"this name to the client's IP address (e.g., 'printer.local', 'nas.home.arpa'). Such DNS record is automatically added to controller's DNS records.",
Type: schema.TypeString,
Optional: true,
},
// these are "meta" attributes that control TF UX
"allow_existing": {
Description: "Specifies whether this resource should just take over control of an existing user.",
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Allow this resource to take over management of an existing user in the UniFi controller. When true:\n" +
" * The resource can manage users that were automatically created when devices connected\n" +
" * Existing settings will be overwritten with the values specified in this resource\n" +
" * If false, attempting to manage an existing user will result in an error\n\n" +
"Use with caution as it can modify settings for devices already connected to your network.",
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"skip_forget_on_destroy": {
Description: "Specifies whether this resource should tell the controller to \"forget\" the user on destroy.",
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "When false (default), the client will be 'forgotten' by the controller when this resource is destroyed. " +
"Set to true to keep the client's history in the controller after the resource is removed from Terraform.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
// computed only attributes

View File

@@ -3,6 +3,7 @@ package user
import (
"context"
"errors"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"github.com/filipowm/terraform-provider-unifi/internal/utils"
@@ -13,8 +14,20 @@ import (
func ResourceUserGroup() *schema.Resource {
return &schema.Resource{
Description: "`unifi_user_group` manages a user group (called \"client group\" in the UI), which can be used " +
"to limit bandwidth for groups of users.",
Description: "The `unifi_user_group` resource manages client groups in the UniFi controller, which allow you to apply " +
"common settings and restrictions to multiple network clients.\n\n" +
"User groups are primarily used for:\n" +
" * Implementing Quality of Service (QoS) policies\n" +
" * Setting bandwidth limits for different types of users\n" +
" * Organizing clients into logical groups (e.g., Staff, Guests, IoT devices)\n\n" +
"Key features include:\n" +
" * Download rate limiting\n" +
" * Upload rate limiting\n" +
" * Group-based policy application\n\n" +
"User groups are particularly useful in:\n" +
" * Educational environments (different policies for staff and students)\n" +
" * Guest networks (limiting guest bandwidth)\n" +
" * Shared office spaces (managing different tenant groups)",
CreateContext: resourceUserGroupCreate,
ReadContext: resourceUserGroupRead,
@@ -26,34 +39,37 @@ func ResourceUserGroup() *schema.Resource {
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the user group.",
Description: "The unique identifier of the user group in the UniFi controller. This is automatically assigned.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the user group with.",
Description: "The name of the UniFi site where this user group should be created. If not specified, the default site will be used.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"name": {
Description: "The name of the user group.",
Type: schema.TypeString,
Required: true,
Description: "A descriptive name for the user group (e.g., 'Staff', 'Guests', 'IoT Devices'). This name will be " +
"displayed in the UniFi controller interface and used when assigning clients to the group.",
Type: schema.TypeString,
Required: true,
},
"qos_rate_max_down": {
Description: "The QOS maximum download rate.",
Type: schema.TypeInt,
Optional: true,
Default: -1,
Description: "The maximum allowed download speed in Kbps (kilobits per second) for clients in this group. " +
"Set to -1 for unlimited. Note: Values of 0 or 1 are not allowed.",
Type: schema.TypeInt,
Optional: true,
Default: -1,
// TODO: validate does not equal 0,1
},
"qos_rate_max_up": {
Description: "The QOS maximum upload rate.",
Type: schema.TypeInt,
Optional: true,
Default: -1,
Description: "The maximum allowed upload speed in Kbps (kilobits per second) for clients in this group. " +
"Set to -1 for unlimited. Note: Values of 0 or 1 are not allowed.",
Type: schema.TypeInt,
Optional: true,
Default: -1,
// TODO: validate does not equal 0,1
},
},