Finish up unifi_user

This commit is contained in:
Paul Tyng
2019-12-30 20:07:25 -05:00
parent ae962b16bf
commit 294a2a7982
7 changed files with 172 additions and 31 deletions

View File

@@ -19,7 +19,10 @@ func (c *lazyClient) init() error {
var err error
c.once.Do(func() {
c.inner = &unifi.Client{}
c.inner.SetBaseURL(c.baseURL)
err = c.inner.SetBaseURL(c.baseURL)
if err != nil {
return
}
err = c.inner.Login(c.user, c.pass)
})
@@ -82,6 +85,10 @@ func (c *lazyClient) GetUser(site, id string) (*unifi.User, error) {
c.init()
return c.inner.GetUser(site, id)
}
func (c *lazyClient) GetUserByMAC(site, mac string) (*unifi.User, error) {
c.init()
return c.inner.GetUserByMAC(site, mac)
}
func (c *lazyClient) CreateUser(site string, d *unifi.User) (*unifi.User, error) {
c.init()
return c.inner.CreateUser(site, d)

View File

@@ -93,7 +93,7 @@ type unifiClient interface {
GetWLAN(site, id string) (*unifi.WLAN, error)
GetUser(site, id string) (*unifi.User, error)
// GetUserByMAC(site, mac string) (*unifi.User, error)
GetUserByMAC(site, mac string) (*unifi.User, error)
CreateUser(site string, d *unifi.User) (*unifi.User, error)
BlockUserByMAC(site, mac string) error
UnblockUserByMAC(site, mac string) error

View File

@@ -28,7 +28,7 @@ func TestMain(m *testing.M) {
pass := os.Getenv("UNIFI_PASSWORD")
baseURL := os.Getenv("UNIFI_API")
testClient := &unifi.Client{}
testClient = &unifi.Client{}
testClient.SetBaseURL(baseURL)
err := testClient.Login(user, pass)
if err != nil {

View File

@@ -1,7 +1,6 @@
package provider
import (
"fmt"
"strings"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
@@ -57,13 +56,17 @@ func resourceUser() *schema.Resource {
Optional: true,
},
// this is a "meta" attribute that controls TF UX
// these are "meta" attributes that control TF UX
"allow_existing": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},
// TODO: "skip_forget_on_destroy": {
"skip_forget_on_destroy": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}
@@ -81,11 +84,24 @@ func resourceUserCreate(d *schema.ResourceData, meta interface{}) error {
resp, err := c.c.CreateUser(c.site, req)
if err != nil {
apiErr, ok := err.(*unifi.APIError)
if !ok || (apiErr.Message != "api.err.MacUsed" && !allowExisting) {
if !ok || (apiErr.Message != "api.err.MacUsed" || !allowExisting) {
return err
}
// mac in use, just absorb it
mac := d.Get("mac").(string)
existing, err := c.c.GetUserByMAC(c.site, mac)
if err != nil {
return err
}
req.ID = existing.ID
req.SiteID = existing.SiteID
resp, err = c.c.UpdateUser(c.site, req)
if err != nil {
return err
}
// TODO: handle mac in use flow
return fmt.Errorf("allow_existing not yet implemented")
}
d.SetId(resp.ID)
@@ -189,6 +205,11 @@ func resourceUserDelete(d *schema.ResourceData, meta interface{}) error {
id := d.Id()
if d.Get("skip_forget_on_destroy").(bool) {
return nil
}
// lookup MAC instead of trusting state
u, err := c.c.GetUser(c.site, id)
if _, ok := err.(*unifi.NotFoundError); ok {
return nil

View File

@@ -2,11 +2,21 @@ package provider
import (
"fmt"
"regexp"
"testing"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
"github.com/paultyng/terraform-provider-unifi/unifi"
)
func userImportStep(name string) resource.TestStep {
return importStep(name, "allow_existing", "skip_forget_on_destroy")
}
// for test MAC addresses, see https://tools.ietf.org/html/rfc7042#section-2.1.2
func TestAccUser_basic(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
Providers: providers,
@@ -20,14 +30,14 @@ func TestAccUser_basic(t *testing.T) {
resource.TestCheckResourceAttr("unifi_user.test", "note", "tfacc note"),
),
},
importStep("unifi_user.test", "allow_existing"),
userImportStep("unifi_user.test"),
{
Config: testAccUserConfig("00:00:5E:00:53:00", "tfacc-2", "tfacc note 2"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("unifi_user.test", "note", "tfacc note 2"),
),
},
importStep("unifi_user.test", "allow_existing"),
userImportStep("unifi_user.test"),
},
})
}
@@ -45,7 +55,7 @@ func TestAccUser_fixed_ip(t *testing.T) {
resource.TestCheckResourceAttr("unifi_user.test", "fixed_ip", ""),
),
},
importStep("unifi_user.test", "allow_existing"),
userImportStep("unifi_user.test"),
{
Config: testAccUserConfig_fixedIP("00:00:5E:00:53:10"),
Check: resource.ComposeTestCheckFunc(
@@ -53,7 +63,7 @@ func TestAccUser_fixed_ip(t *testing.T) {
resource.TestCheckResourceAttr("unifi_user.test", "fixed_ip", "10.1.10.50"),
),
},
importStep("unifi_user.test", "allow_existing"),
userImportStep("unifi_user.test"),
{
// this passes the network again even though its not used
// to avoid a destroy order of operations issue, can
@@ -64,7 +74,7 @@ func TestAccUser_fixed_ip(t *testing.T) {
resource.TestCheckResourceAttr("unifi_user.test", "fixed_ip", ""),
),
},
importStep("unifi_user.test", "allow_existing"),
userImportStep("unifi_user.test"),
},
})
}
@@ -82,7 +92,7 @@ func TestAccUser_blocking(t *testing.T) {
resource.TestCheckResourceAttr("unifi_user.test", "blocked", "false"),
),
},
importStep("unifi_user.test", "allow_existing"),
userImportStep("unifi_user.test"),
{
Config: testAccUserConfig_block("00:00:5E:00:53:20", true),
Check: resource.ComposeTestCheckFunc(
@@ -90,7 +100,7 @@ func TestAccUser_blocking(t *testing.T) {
resource.TestCheckResourceAttr("unifi_user.test", "blocked", "true"),
),
},
importStep("unifi_user.test", "allow_existing"),
userImportStep("unifi_user.test"),
{
Config: testAccUserConfig_block("00:00:5E:00:53:20", false),
Check: resource.ComposeTestCheckFunc(
@@ -98,14 +108,76 @@ func TestAccUser_blocking(t *testing.T) {
resource.TestCheckResourceAttr("unifi_user.test", "blocked", "false"),
),
},
importStep("unifi_user.test", "allow_existing"),
userImportStep("unifi_user.test"),
},
})
}
// for test MAC addresses, see https://tools.ietf.org/html/rfc7042#section-2.1.2
// func TestAccUser_existing_mac_allow(t *testing.T) {
// func TestAccUser_existing_mac_deny(t *testing.T) {
func TestAccUser_existing_mac_allow(t *testing.T) {
testMAC := "00:00:5e:00:53:30"
resource.ParallelTest(t, resource.TestCase{
Providers: providers,
PreCheck: func() {
preCheck(t)
_, err := testClient.CreateUser("default", &unifi.User{
MAC: testMAC,
Name: "tfacc-existing",
Note: "tfacc-existing",
})
if err != nil {
t.Fatal(err)
}
},
CheckDestroy: func(*terraform.State) error {
// TODO: CheckDestroy: ,
return testClient.DeleteUserByMAC("default", testMAC)
},
Steps: []resource.TestStep{
{
Config: testAccUserConfig_existing(testMAC, "tfacc", "tfacc note", true, true),
Check: resource.ComposeTestCheckFunc(
// testCheckNetworkExists(t, "name"),
resource.TestCheckResourceAttr("unifi_user.test", "note", "tfacc note"),
),
},
userImportStep("unifi_user.test"),
},
})
}
func TestAccUser_existing_mac_deny(t *testing.T) {
testMAC := "00:00:5e:00:53:40"
resource.ParallelTest(t, resource.TestCase{
Providers: providers,
PreCheck: func() {
preCheck(t)
_, err := testClient.CreateUser("default", &unifi.User{
MAC: testMAC,
Name: "tfacc-existing",
Note: "tfacc-existing",
})
if err != nil {
t.Fatal(err)
}
},
CheckDestroy: func(*terraform.State) error {
// TODO: CheckDestroy: ,
return testClient.DeleteUserByMAC("default", testMAC)
},
Steps: []resource.TestStep{
{
Config: testAccUserConfig_existing(testMAC, "tfacc", "tfacc note", false, false),
ExpectError: regexp.MustCompile("api\\.err\\.MacUsed"),
},
},
})
}
func testAccUserConfig(mac, name, note string) string {
return fmt.Sprintf(`
@@ -158,3 +230,16 @@ resource "unifi_user" "test" {
}
`, mac, blocked, blocked)
}
func testAccUserConfig_existing(mac, name, note string, allow, skip bool) string {
return fmt.Sprintf(`
resource "unifi_user" "test" {
mac = "%s"
name = "%s"
note = "%s"
allow_existing = %t
skip_forget_on_destroy = %t
}
`, mac, name, note, allow, skip)
}

View File

@@ -35,8 +35,13 @@ type Client struct {
baseURL *url.URL
}
func (c *Client) SetBaseURL(base string) {
c.baseURL, _ = url.Parse(base)
func (c *Client) SetBaseURL(base string) error {
var err error
c.baseURL, err = url.Parse(base)
if err != nil {
return err
}
return nil
}
func (c *Client) Login(user, pass string) error {
@@ -95,7 +100,11 @@ func (c *Client) do(method, relativeURL string, reqBody interface{}, respBody in
reqReader = bytes.NewReader(reqBytes)
}
reqURL, _ := url.Parse(relativeURL)
reqURL, err := url.Parse(relativeURL)
if err != nil {
return err
}
url := c.baseURL.ResolveReference(reqURL)
req, err := http.NewRequest(method, url.String(), reqReader)

View File

@@ -122,7 +122,7 @@ func (c *Client) CreateUser(site string, d *User) (*User, error) {
return &new, nil
}
func (c *Client) stamgr(site, cmd string, data map[string]interface{}) error {
func (c *Client) stamgr(site, cmd string, data map[string]interface{}) ([]User, error) {
reqBody := map[string]interface{}{}
for k, v := range data {
@@ -138,24 +138,36 @@ func (c *Client) stamgr(site, cmd string, data map[string]interface{}) error {
err := c.do("POST", fmt.Sprintf("s/%s/cmd/stamgr", site), reqBody, &respBody)
if err != nil {
return err
return nil, err
}
// TODO: confirm count/state of returned Data?
return nil
return respBody.Data, nil
}
func (c *Client) BlockUserByMAC(site, mac string) error {
return c.stamgr(site, "block-sta", map[string]interface{}{
users, err := c.stamgr(site, "block-sta", map[string]interface{}{
"mac": mac,
})
if err != nil {
return err
}
if len(users) != 1 {
return &NotFoundError{}
}
return nil
}
func (c *Client) UnblockUserByMAC(site, mac string) error {
return c.stamgr(site, "unblock-sta", map[string]interface{}{
users, err := c.stamgr(site, "unblock-sta", map[string]interface{}{
"mac": mac,
})
if err != nil {
return err
}
if len(users) != 1 {
return &NotFoundError{}
}
return nil
}
func (c *Client) UpdateUser(site string, d *User) (*User, error) {
@@ -179,7 +191,14 @@ func (c *Client) UpdateUser(site string, d *User) (*User, error) {
}
func (c *Client) DeleteUserByMAC(site, mac string) error {
return c.stamgr(site, "forget-sta", map[string]interface{}{
users, err := c.stamgr(site, "forget-sta", map[string]interface{}{
"macs": []string{mac},
})
if err != nil {
return err
}
if len(users) != 1 {
return &NotFoundError{}
}
return nil
}