Files
terraform-provider-unifi/internal/provider/radius/resource_radius_profile.go
Mateusz Filipowicz e7164c0460 feat: add DNS record resource and datasources (#25)
* add DNS record

* revamp tests

* lint

* cleanup

* feat dns test

* chore: add DNS Record tests

* linting

* f
2025-02-26 01:17:59 +01:00

419 lines
12 KiB
Go

package radius
import (
"context"
"errors"
"fmt"
"github.com/filipowm/terraform-provider-unifi/internal/provider/base"
"strings"
"github.com/filipowm/go-unifi/unifi"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)
func ResourceRadiusProfile() *schema.Resource {
return &schema.Resource{
Description: "`unifi_radius_profile` manages RADIUS profiles.",
CreateContext: resourceRadiusProfileCreate,
ReadContext: resourceRadiusProfileRead,
UpdateContext: resourceRadiusProfileUpdate,
DeleteContext: resourceRadiusProfileDelete,
Importer: &schema.ResourceImporter{
StateContext: importRadiusProfile,
},
Schema: map[string]*schema.Schema{
"id": {
Description: "The ID of the settings.",
Type: schema.TypeString,
Computed: true,
},
"site": {
Description: "The name of the site to associate the settings with.",
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
},
"name": {
Description: "The name of the profile.",
Type: schema.TypeString,
Required: true,
},
"accounting_enabled": {
Description: "Specifies whether to use RADIUS accounting.",
Type: schema.TypeBool,
Default: false,
Optional: true,
},
"interim_update_enabled": {
Description: "Specifies whether to use interim_update.",
Type: schema.TypeBool,
Default: false,
Optional: true,
},
"interim_update_interval": {
Description: "Specifies interim_update interval.",
Type: schema.TypeInt,
Default: 3600,
Optional: true,
},
"use_usg_acct_server": {
Description: "Specifies whether to use usg as a RADIUS accounting server.",
Type: schema.TypeBool,
Default: false,
Optional: true,
},
"use_usg_auth_server": {
Description: "Specifies whether to use usg as a RADIUS authentication server.",
Type: schema.TypeBool,
Default: false,
Optional: true,
},
"vlan_enabled": {
Description: "Specifies whether to use vlan on wired connections.",
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`.",
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,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"ip": {
Description: "IP address of authentication service server.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.IsIPAddress,
},
"port": {
Description: "Port of authentication service.",
Type: schema.TypeInt,
Optional: true,
Default: 1812,
ValidateFunc: validation.IsPortNumber,
},
"xsecret": {
Description: "RADIUS secret.",
Type: schema.TypeString,
Required: true,
Sensitive: true,
},
},
},
},
"acct_server": {
Description: "RADIUS accounting servers.",
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"ip": {
Description: "IP address of accounting service server.",
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.IsIPAddress,
},
"port": {
Description: "Port of accounting service.",
Type: schema.TypeInt,
Optional: true,
Default: 1813,
ValidateFunc: validation.IsPortNumber,
},
"xsecret": {
Description: "RADIUS secret.",
Type: schema.TypeString,
Required: true,
Sensitive: true,
},
},
},
},
},
}
}
func setToAuthServers(set []interface{}) ([]unifi.RADIUSProfileAuthServers, error) {
var authServers []unifi.RADIUSProfileAuthServers
for _, item := range set {
data, ok := item.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("unexpected data in block")
}
authServer, err := toAuthServer(data)
if err != nil {
return nil, fmt.Errorf("unable to create port override: %w", err)
}
authServers = append(authServers, authServer)
}
return authServers, nil
}
func setToAcctServers(set []interface{}) ([]unifi.RADIUSProfileAcctServers, error) {
var acctServers []unifi.RADIUSProfileAcctServers
for _, item := range set {
data, ok := item.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("unexpected data in block")
}
accServer, err := toAcctServer(data)
if err != nil {
return nil, fmt.Errorf("unable to create port override: %w", err)
}
acctServers = append(acctServers, accServer)
}
return acctServers, nil
}
func toAuthServer(data map[string]interface{}) (unifi.RADIUSProfileAuthServers, error) {
return unifi.RADIUSProfileAuthServers{
IP: data["ip"].(string),
Port: data["port"].(int),
XSecret: data["xsecret"].(string),
}, nil
}
func toAcctServer(data map[string]interface{}) (unifi.RADIUSProfileAcctServers, error) {
return unifi.RADIUSProfileAcctServers{
IP: data["ip"].(string),
Port: data["port"].(int),
XSecret: data["xsecret"].(string),
}, nil
}
func setFromAuthServers(authServers []unifi.RADIUSProfileAuthServers) ([]map[string]interface{}, error) {
list := make([]map[string]interface{}, 0, len(authServers))
for _, authServer := range authServers {
v, err := fromAuthServer(authServer)
if err != nil {
return nil, fmt.Errorf("unable to parse ssh key: %w", err)
}
list = append(list, v)
}
return list, nil
}
func setFromAcctServers(acctServers []unifi.RADIUSProfileAcctServers) ([]map[string]interface{}, error) {
list := make([]map[string]interface{}, 0, len(acctServers))
for _, acctServer := range acctServers {
v, err := fromAcctServer(acctServer)
if err != nil {
return nil, fmt.Errorf("unable to parse ssh key: %w", err)
}
list = append(list, v)
}
return list, nil
}
func fromAuthServer(sshKey unifi.RADIUSProfileAuthServers) (map[string]interface{}, error) {
return map[string]interface{}{
"ip": sshKey.IP,
"port": sshKey.Port,
"xsecret": sshKey.XSecret,
}, nil
}
func fromAcctServer(sshKey unifi.RADIUSProfileAcctServers) (map[string]interface{}, error) {
return map[string]interface{}{
"ip": sshKey.IP,
"port": sshKey.Port,
"xsecret": sshKey.XSecret,
}, nil
}
func resourceRadiusProfileCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
c := meta.(*base.Client)
req, err := resourceRadiusProfileGetResourceData(d)
if err != nil {
return diag.FromErr(err)
}
site := d.Get("site").(string)
if site == "" {
site = c.Site
}
resp, err := c.CreateRADIUSProfile(ctx, site, req)
if err != nil {
return diag.FromErr(err)
}
d.SetId(resp.ID)
return resourceRadiusProfileSetResourceData(resp, d, site)
}
func resourceRadiusProfileGetResourceData(d *schema.ResourceData) (*unifi.RADIUSProfile, error) {
authServers, err := setToAuthServers(d.Get("auth_server").([]interface{}))
if err != nil {
return nil, fmt.Errorf("unable to auth_server ssh_key block: %w", err)
}
acctServers, err := setToAcctServers(d.Get("acct_server").([]interface{}))
if err != nil {
return nil, fmt.Errorf("unable to acct_server ssh_key block: %w", err)
}
return &unifi.RADIUSProfile{
Name: d.Get("name").(string),
InterimUpdateEnabled: d.Get("interim_update_enabled").(bool),
InterimUpdateInterval: d.Get("interim_update_interval").(int),
AccountingEnabled: d.Get("accounting_enabled").(bool),
UseUsgAcctServer: d.Get("use_usg_acct_server").(bool),
UseUsgAuthServer: d.Get("use_usg_auth_server").(bool),
VLANEnabled: d.Get("vlan_enabled").(bool),
VLANWLANMode: d.Get("vlan_wlan_mode").(string),
AuthServers: authServers,
AcctServers: acctServers,
}, nil
}
func resourceRadiusProfileSetResourceData(resp *unifi.RADIUSProfile, d *schema.ResourceData, site string) diag.Diagnostics {
authServers, err := setFromAuthServers(resp.AuthServers)
if err != nil {
return diag.FromErr(err)
}
acctServers, err := setFromAcctServers(resp.AcctServers)
if err != nil {
return diag.FromErr(err)
}
d.Set("site", site)
d.Set("name", resp.Name)
d.Set("interim_update_enabled", resp.InterimUpdateEnabled)
d.Set("interim_update_interval", resp.InterimUpdateInterval)
d.Set("accounting_enabled", resp.AccountingEnabled)
d.Set("use_usg_acct_server", resp.UseUsgAcctServer)
d.Set("use_usg_auth_server", resp.UseUsgAuthServer)
d.Set("vlan_enabled", resp.VLANEnabled)
d.Set("vlan_wlan_mode", resp.VLANWLANMode)
d.Set("auth_server", authServers)
d.Set("acct_server", acctServers)
return nil
}
func resourceRadiusProfileRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
c := meta.(*base.Client)
id := d.Id()
site := d.Get("site").(string)
if site == "" {
site = c.Site
}
resp, err := c.GetRADIUSProfile(ctx, site, id)
if errors.Is(err, unifi.ErrNotFound) {
d.SetId("")
return nil
}
if err != nil {
return diag.FromErr(err)
}
return resourceRadiusProfileSetResourceData(resp, d, site)
}
func resourceRadiusProfileUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
c := meta.(*base.Client)
req, err := resourceRadiusProfileGetResourceData(d)
if err != nil {
return diag.FromErr(err)
}
req.ID = d.Id()
site := d.Get("site").(string)
if site == "" {
site = c.Site
}
req.SiteID = site
resp, err := c.UpdateRADIUSProfile(ctx, site, req)
if err != nil {
return diag.FromErr(err)
}
return resourceRadiusProfileSetResourceData(resp, d, site)
}
func resourceRadiusProfileDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
c := meta.(*base.Client)
id := d.Id()
site := d.Get("site").(string)
if site == "" {
site = c.Site
}
err := c.DeleteRADIUSProfile(ctx, site, id)
return diag.FromErr(err)
}
func importRadiusProfile(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
c := meta.(*base.Client)
id := d.Id()
site := d.Get("site").(string)
if site == "" {
site = c.Site
}
if strings.Contains(id, ":") {
importParts := strings.SplitN(id, ":", 2)
site = importParts[0]
id = importParts[1]
}
if strings.HasPrefix(id, "name=") {
targetName := strings.TrimPrefix(id, "name=")
var err error
if id, err = getRadiusProfileIDByName(ctx, c.Client, targetName, site); err != nil {
return nil, err
}
}
if id != "" {
d.SetId(id)
}
if site != "" {
d.Set("site", site)
}
return []*schema.ResourceData{d}, nil
}
func getRadiusProfileIDByName(ctx context.Context, client unifi.Client, profileName, site string) (string, error) {
radiusProfiles, err := client.ListRADIUSProfile(ctx, site)
if err != nil {
return "", err
}
idMatchingName := ""
allNames := []string{}
for _, profile := range radiusProfiles {
allNames = append(allNames, profile.Name)
if profile.Name != profileName {
continue
}
if idMatchingName != "" {
return "", fmt.Errorf("Found multiple RADIUS profiles with name '%s'", profileName)
}
idMatchingName = profile.ID
}
if idMatchingName == "" {
return "", fmt.Errorf("Found no RADIUS profile with name '%s', found: %s", profileName, strings.Join(allNames, ", "))
}
return idMatchingName, nil
}