Merge pull request #4796 from svanharmelen/f-tweak-tags

provider/cloudstack: policing up the new `tags` code from @Carles-Figuerola
This commit is contained in:
Sander van Harmelen 2016-01-22 17:20:31 +01:00
commit 81df46a339
4 changed files with 61 additions and 70 deletions

View File

@ -178,7 +178,7 @@ func resourceCloudStackNetworkCreate(d *schema.ResourceData, meta interface{}) e
err = setTags(cs, d, "network") err = setTags(cs, d, "network")
if err != nil { if err != nil {
return fmt.Errorf("Error setting tags") return fmt.Errorf("Error setting tags: %s", err)
} }
return resourceCloudStackNetworkRead(d, meta) return resourceCloudStackNetworkRead(d, meta)
@ -206,7 +206,7 @@ func resourceCloudStackNetworkRead(d *schema.ResourceData, meta interface{}) err
d.Set("gateway", n.Gateway) d.Set("gateway", n.Gateway)
// Read the tags and store them in a map // Read the tags and store them in a map
tags := make(map[string]string) tags := make(map[string]interface{})
for item := range n.Tags { for item := range n.Tags {
tags[n.Tags[item].Key] = n.Tags[item].Value tags[n.Tags[item].Key] = n.Tags[item].Value
} }
@ -262,9 +262,11 @@ func resourceCloudStackNetworkUpdate(d *schema.ResourceData, meta interface{}) e
} }
// Update tags if they have changed // Update tags if they have changed
err = setTags(cs, d, "network") if d.HasChange("tags") {
if err != nil { err = setTags(cs, d, "network")
return fmt.Errorf("Error updating tags") if err != nil {
return fmt.Errorf("Error updating tags: %s", err)
}
} }
return resourceCloudStackNetworkRead(d, meta) return resourceCloudStackNetworkRead(d, meta)

View File

@ -163,13 +163,13 @@ func testAccCheckCloudStackNetworkDestroy(s *terraform.State) error {
var testAccCloudStackNetwork_basic = fmt.Sprintf(` var testAccCloudStackNetwork_basic = fmt.Sprintf(`
resource "cloudstack_network" "foo" { resource "cloudstack_network" "foo" {
name = "terraform-network" name = "terraform-network"
cidr = "%s" cidr = "%s"
network_offering = "%s" network_offering = "%s"
zone = "%s" zone = "%s"
tags = { tags = {
terraform-tag = "true" terraform-tag = "true"
} }
}`, }`,
CLOUDSTACK_NETWORK_2_CIDR, CLOUDSTACK_NETWORK_2_CIDR,
CLOUDSTACK_NETWORK_2_OFFERING, CLOUDSTACK_NETWORK_2_OFFERING,
@ -184,11 +184,11 @@ resource "cloudstack_vpc" "foobar" {
} }
resource "cloudstack_network" "foo" { resource "cloudstack_network" "foo" {
name = "terraform-network" name = "terraform-network"
cidr = "%s" cidr = "%s"
network_offering = "%s" network_offering = "%s"
vpc = "${cloudstack_vpc.foobar.name}" vpc = "${cloudstack_vpc.foobar.name}"
zone = "${cloudstack_vpc.foobar.zone}" zone = "${cloudstack_vpc.foobar.zone}"
}`, }`,
CLOUDSTACK_VPC_CIDR_1, CLOUDSTACK_VPC_CIDR_1,
CLOUDSTACK_VPC_OFFERING, CLOUDSTACK_VPC_OFFERING,

View File

@ -7,72 +7,67 @@ import (
"github.com/xanzy/go-cloudstack/cloudstack" "github.com/xanzy/go-cloudstack/cloudstack"
) )
// tagsSchema returns the schema to use for tags. // tagsSchema returns the schema to use for tags
//
func tagsSchema() *schema.Schema { func tagsSchema() *schema.Schema {
return &schema.Schema{ return &schema.Schema{
Type: schema.TypeMap, Type: schema.TypeMap,
Optional: true, Optional: true,
Computed: true,
} }
} }
// setTags is a helper to set the tags for a resource. It expects the // setTags is a helper to set the tags for a resource. It expects the
// tags field to be named "tags" // tags field to be named "tags"
func setTags(cs *cloudstack.CloudStackClient, d *schema.ResourceData, resourcetype string) error { func setTags(cs *cloudstack.CloudStackClient, d *schema.ResourceData, resourcetype string) error {
if d.HasChange("tags") { oraw, nraw := d.GetChange("tags")
oraw, nraw := d.GetChange("tags") o := oraw.(map[string]interface{})
o := oraw.(map[string]interface{}) n := nraw.(map[string]interface{})
n := nraw.(map[string]interface{})
create, remove := diffTags(tagsFromSchema(o), tagsFromSchema(n))
log.Printf("[DEBUG] tags to remove: %v", remove)
log.Printf("[DEBUG] tags to create: %v", create)
// Set tags remove, create := diffTags(tagsFromSchema(o), tagsFromSchema(n))
if len(remove) > 0 { log.Printf("[DEBUG] tags to remove: %v", remove)
log.Printf("[DEBUG] Removing tags: %v from %s", remove, d.Id()) log.Printf("[DEBUG] tags to create: %v", create)
p := cs.Resourcetags.NewDeleteTagsParams([]string{d.Id()}, resourcetype)
p.SetTags(remove) // First remove any obsolete tags
_, err := cs.Resourcetags.DeleteTags(p) if len(remove) > 0 {
if err != nil { log.Printf("[DEBUG] Removing tags: %v from %s", remove, d.Id())
return err p := cs.Resourcetags.NewDeleteTagsParams([]string{d.Id()}, resourcetype)
} p.SetTags(remove)
_, err := cs.Resourcetags.DeleteTags(p)
if err != nil {
return err
} }
if len(create) > 0 { }
log.Printf("[DEBUG] Creating tags: %v for %s", create, d.Id())
p := cs.Resourcetags.NewCreateTagsParams([]string{d.Id()}, resourcetype, create) // Then add any new tags
_, err := cs.Resourcetags.CreateTags(p) if len(create) > 0 {
if err != nil { log.Printf("[DEBUG] Creating tags: %v for %s", create, d.Id())
return err p := cs.Resourcetags.NewCreateTagsParams([]string{d.Id()}, resourcetype, create)
} _, err := cs.Resourcetags.CreateTags(p)
if err != nil {
return err
} }
} }
return nil return nil
} }
// diffTags takes our tags locally and the ones remotely and returns // diffTags takes the old and the new tag sets and returns the difference of
// the set of tags that must be destroyed, and the set of tags that must // both. The remaining tags are those that need to be removed and created
// be created.
func diffTags(oldTags, newTags map[string]string) (map[string]string, map[string]string) { func diffTags(oldTags, newTags map[string]string) (map[string]string, map[string]string) {
remove := make(map[string]string) for k, old := range oldTags {
for k, v := range oldTags { new, ok := newTags[k]
old, ok := newTags[k] if ok && old == new {
if !ok || old != v { // We should avoid removing or creating tags we already have
// Delete it! delete(oldTags, k)
remove[k] = v
} else {
// We need to remove the modified tags to create them again,
// but we should avoid creating what we already have
delete(newTags, k) delete(newTags, k)
} }
} }
return newTags, remove return oldTags, newTags
} }
// tagsFromSchema returns the tags for the given tags schema. // tagsFromSchema takes the raw schema tags and returns them as a
// It's needed to properly unpack all string:interface types // properly asserted map[string]string
// to a proper string:string map
func tagsFromSchema(m map[string]interface{}) map[string]string { func tagsFromSchema(m map[string]interface{}) map[string]string {
result := make(map[string]string, len(m)) result := make(map[string]string, len(m))
for k, v := range m { for k, v := range m {

View File

@ -45,27 +45,21 @@ func TestDiffTags(t *testing.T) {
} }
for i, tc := range cases { for i, tc := range cases {
c, r := diffTags(tagsFromSchema(tc.Old), tagsFromSchema(tc.New)) r, c := diffTags(tagsFromSchema(tc.Old), tagsFromSchema(tc.New))
if !reflect.DeepEqual(c, tc.Create) {
t.Fatalf("%d: bad create: %#v", i, c)
}
if !reflect.DeepEqual(r, tc.Remove) { if !reflect.DeepEqual(r, tc.Remove) {
t.Fatalf("%d: bad remove: %#v", i, r) t.Fatalf("%d: bad remove: %#v", i, r)
} }
if !reflect.DeepEqual(c, tc.Create) {
t.Fatalf("%d: bad create: %#v", i, c)
}
} }
} }
// testAccCheckTags can be used to check the tags on a resource. // testAccCheckTags can be used to check the tags on a resource.
func testAccCheckTags( func testAccCheckTags(tags map[string]string, key string, value string) error {
tags map[string]string, key string, value string) error {
v, ok := tags[key] v, ok := tags[key]
if value != "" && !ok { if !ok {
return fmt.Errorf("Missing tag: %s", key) return fmt.Errorf("Missing tag: %s", key)
} else if value == "" && ok {
return fmt.Errorf("Extra tag: %s", key)
}
if value == "" {
return nil
} }
if v != value { if v != value {