Finish up unifi_user
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user