diff --git a/builtin/providers/clc/provider.go b/builtin/providers/clc/provider.go index 0791981c7784..2ebe10d97389 100644 --- a/builtin/providers/clc/provider.go +++ b/builtin/providers/clc/provider.go @@ -100,13 +100,13 @@ func waitStatus(client *clc.Client, id string) error { return nil } -func dcGroups(dcname string, meta interface{}) (map[string]string, error) { - client := meta.(*clc.Client) +func dcGroups(dcname string, client *clc.Client) (map[string]string, error) { dc, _ := client.DC.Get(dcname) _, id := dc.Links.GetID("group") m := map[string]string{} resp, _ := client.Group.Get(id) m[resp.Name] = resp.ID // top + m[resp.ID] = resp.ID for _, x := range resp.Groups { deepGroups(x, &m) } @@ -115,11 +115,25 @@ func dcGroups(dcname string, meta interface{}) (map[string]string, error) { func deepGroups(g group.Groups, m *map[string]string) { (*m)[g.Name] = g.ID + (*m)[g.ID] = g.ID for _, sg := range g.Groups { deepGroups(sg, m) } } +// resolveGroupByNameOrId takes a reference to a group (either name or guid) +// and returns the guid of the group +func resolveGroupByNameOrId(ref, dc string, client *clc.Client) (string, error) { + m, err := dcGroups(dc, client) + if err != nil { + return "", fmt.Errorf("Failed pulling groups in location %v - %v", dc, err) + } + if id, ok := m[ref]; ok { + return id, nil + } + return "", fmt.Errorf("Failed resolving group '%v' in location %v", ref, dc) +} + func stateFromString(st string) server.PowerState { switch st { case "on", "started": diff --git a/builtin/providers/clc/resource_clc_group.go b/builtin/providers/clc/resource_clc_group.go index 7ef82e6a04d2..174017a53d11 100644 --- a/builtin/providers/clc/resource_clc_group.go +++ b/builtin/providers/clc/resource_clc_group.go @@ -3,9 +3,12 @@ package clc import ( "fmt" "log" + "time" "github.com/CenturyLinkCloud/clc-sdk" + "github.com/CenturyLinkCloud/clc-sdk/api" "github.com/CenturyLinkCloud/clc-sdk/group" + "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" ) @@ -16,14 +19,6 @@ func resourceCLCGroup() *schema.Resource { Update: resourceCLCGroupUpdate, Delete: resourceCLCGroupDelete, Schema: map[string]*schema.Schema{ - "location_id": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "parent": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, "name": &schema.Schema{ Type: schema.TypeString, Required: true, @@ -33,9 +28,13 @@ func resourceCLCGroup() *schema.Resource { Optional: true, Default: "", }, - "id": &schema.Schema{ + "parent": &schema.Schema{ Type: schema.TypeString, - Computed: true, + Required: true, + }, + "location_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, }, "parent_group_id": &schema.Schema{ Type: schema.TypeString, @@ -52,30 +51,34 @@ func resourceCLCGroup() *schema.Resource { func resourceCLCGroupCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*clc.Client) - dc := d.Get("location_id").(string) - m, err := dcGroups(dc, meta) - if err != nil { - return fmt.Errorf("Failed pulling groups in location %v - %v", dc, err) - } name := d.Get("name").(string) - // use an existing group if we have one - if id, ok := m[name]; ok { - log.Printf("[INFO] Using EXISTING group: %v => %v", name, id) - d.SetId(id) + desc := d.Get("description").(string) + parent := d.Get("parent").(string) + dc := d.Get("location_id").(string) + + // clc doesn't enforce uniqueness by name + // so skip the trad'l error we'd raise + e, err := resolveGroupByNameOrId(name, dc, client) + if e != "" { + log.Printf("[INFO] Resolved existing group: %v => %v", name, e) + d.SetId(e) return nil } - // otherwise, we're creating one. we'll need a parent - p := d.Get("parent").(string) - if parent, ok := m[p]; ok { - d.Set("parent_group_id", parent) + + var pgid string + p, err := resolveGroupByNameOrId(parent, dc, client) + if p != "" { + log.Printf("[INFO] Resolved parent group: %v => %v", parent, p) + pgid = p } else { - return fmt.Errorf("Failed resolving parent group %s - %s", p, m) + return fmt.Errorf("Failed resolving parent group %s - %s err:%s", parent, p, err) } + d.Set("parent_group_id", pgid) spec := group.Group{ Name: name, - Description: d.Get("description").(string), - ParentGroupID: d.Get("parent_group_id").(string), + Description: desc, + ParentGroupID: pgid, } resp, err := client.Group.Create(spec) if err != nil { @@ -88,21 +91,60 @@ func resourceCLCGroupCreate(d *schema.ResourceData, meta interface{}) error { func resourceCLCGroupRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*clc.Client) - id := d.Get("id").(string) + id := d.Id() g, err := client.Group.Get(id) if err != nil { - return fmt.Errorf("Failed to find the specified group with id: %s - %s", id, err) + log.Printf("[INFO] Failed finding group: %s - %s. Marking destroyed", id, err) + d.SetId("") + return nil } d.Set("name", g.Name) d.Set("description", g.Description) - // need to traverse links? - //d.Set("parent_group_id", g.ParentGroupID) + d.Set("parent_group_id", g.ParentGroupID()) return nil } func resourceCLCGroupUpdate(d *schema.ResourceData, meta interface{}) error { - // unimplemented - return nil + client := meta.(*clc.Client) + id := d.Id() + var err error + var patches []api.Update + + g, err := client.Group.Get(id) + if err != nil { + return fmt.Errorf("Failed fetching group: %v - %v", id, err) + } + + if delta, orig := d.Get("name").(string), g.Name; delta != orig { + patches = append(patches, group.UpdateName(delta)) + } + if delta, orig := d.Get("description").(string), g.Description; delta != orig { + patches = append(patches, group.UpdateDescription(delta)) + } + newParent := d.Get("parent").(string) + pgid, err := resolveGroupByNameOrId(newParent, g.Locationid, client) + log.Printf("[DEBUG] PARENT current:%v new:%v resolved:%v", g.ParentGroupID(), newParent, pgid) + if pgid == "" { + return fmt.Errorf("Unable to resolve parent group %v: %v", newParent, err) + } else if newParent != g.ParentGroupID() { + patches = append(patches, group.UpdateParentGroupID(pgid)) + } + + if len(patches) == 0 { + return nil + } + _, err = client.Group.Update(id, patches...) + if err != nil { + return fmt.Errorf("Failed updating group %v: %v", id, err) + } + //return resourceCLCGroupRead(d, meta) + return resource.Retry(1*time.Minute, func() error { + _, err := client.Group.Get(id) + if err == nil { + return resourceCLCGroupRead(d, meta) + } + return &resource.RetryError{Err: err} + }) } func resourceCLCGroupDelete(d *schema.ResourceData, meta interface{}) error { diff --git a/builtin/providers/clc/resource_clc_group_test.go b/builtin/providers/clc/resource_clc_group_test.go index 6aaa174a4f8d..c4d250b42ac8 100644 --- a/builtin/providers/clc/resource_clc_group_test.go +++ b/builtin/providers/clc/resource_clc_group_test.go @@ -26,7 +26,29 @@ func TestAccGroupBasic(t *testing.T) { Config: testAccCheckGroupConfigBasic, Check: resource.ComposeTestCheckFunc( testAccCheckGroupExists("clc_group.acc_test_group", &resp), - testAccCheckGroupParent(&resp), + testAccCheckGroupParent(&resp, "Default Group"), + resource.TestCheckResourceAttr( + "clc_group.acc_test_group", "name", "okcomputer"), + resource.TestCheckResourceAttr( + "clc_group.acc_test_group", "location_id", "WA1"), + ), + }, + resource.TestStep{ + Config: testAccCheckGroupConfigUpdate, + Check: resource.ComposeTestCheckFunc( + testAccCheckGroupExists("clc_group.acc_test_group", &resp), + testAccCheckGroupParent(&resp, "Default Group"), + resource.TestCheckResourceAttr( + "clc_group.acc_test_group", "name", "foobar"), + resource.TestCheckResourceAttr( + "clc_group.acc_test_group", "location_id", "WA1"), + ), + }, + resource.TestStep{ + Config: testAccCheckGroupConfigReparent, + Check: resource.ComposeTestCheckFunc( + testAccCheckGroupExists("clc_group.acc_test_group", &resp), + testAccCheckGroupParent(&resp, "reparent"), resource.TestCheckResourceAttr( "clc_group.acc_test_group", "name", "foobar"), resource.TestCheckResourceAttr( @@ -51,19 +73,19 @@ func testAccCheckGroupDestroy(s *terraform.State) error { return nil } -func testAccCheckGroupParent(resp *group.Response) resource.TestCheckFunc { +func testAccCheckGroupParent(resp *group.Response, expectedName string) resource.TestCheckFunc { return func(s *terraform.State) error { client := testAccProvider.Meta().(*clc.Client) ok, l := resp.Links.GetLink("parentGroup") if !ok { return fmt.Errorf("Missing parent group: %v", resp) } - resp, err := client.Group.Get(l.ID) + parent, err := client.Group.Get(l.ID) if err != nil { return fmt.Errorf("Failed fetching parent %v: %v", l.ID, err) } - if resp.Name != "Default Group" { - return fmt.Errorf("Incorrect parent %v: %v", l, err) + if parent.Name != expectedName { + return fmt.Errorf("Incorrect parent found:'%v' expected:'%v'", parent.Name, expectedName) } // would be good to test parent but we'd have to make a bunch of calls return nil @@ -96,7 +118,31 @@ func testAccCheckGroupExists(n string, resp *group.Response) resource.TestCheckF const testAccCheckGroupConfigBasic = ` resource "clc_group" "acc_test_group" { - location_id = "WA1" - name = "foobar" - parent = "Default Group" + location_id = "WA1" + name = "okcomputer" + description = "mishaps happening" + parent = "Default Group" +}` + +const testAccCheckGroupConfigUpdate = ` +resource "clc_group" "acc_test_group" { + location_id = "WA1" + name = "foobar" + description = "update test" + parent = "Default Group" }` + +const testAccCheckGroupConfigReparent = ` +resource "clc_group" "acc_test_group_reparent" { + location_id = "WA1" + name = "reparent" + description = "introduce a parent group in place" + parent = "Default Group" +} +resource "clc_group" "acc_test_group" { + location_id = "WA1" + name = "foobar" + description = "update test" + parent = "${clc_group.acc_test_group_reparent.id}" +} +` diff --git a/website/source/docs/providers/clc/r/group.html.markdown b/website/source/docs/providers/clc/r/group.html.markdown index 1627440c88fc..b8927b47ae4e 100644 --- a/website/source/docs/providers/clc/r/group.html.markdown +++ b/website/source/docs/providers/clc/r/group.html.markdown @@ -34,7 +34,7 @@ output "group_id" { The following arguments are supported: -* `name` - (Required, string) The name of this server group. Will resolve to existing if present. +* `name` - (Required, string) The name (or GUID) of this server group. Will resolve to existing if present. * `parent` - (Required, string) The name or ID of the parent group. Will error if absent or unable to resolve. * `location_id` - (Required, string) The datacenter location of both parent group and this group. Examples: "WA1", "VA1"