From e4450fcd517980797a796b321a7731ca96c71633 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Sat, 26 Jul 2014 15:14:41 -0400 Subject: [PATCH] provider/consul: Vast simplification --- .../providers/consul/resource_consul_key.go | 394 ------------------ .../providers/consul/resource_consul_keys.go | 288 +++++++++++++ .../consul/resource_consul_keys_test.go | 17 +- builtin/providers/consul/resource_provider.go | 7 +- .../consul/resource_provider_test.go | 2 +- builtin/providers/consul/resources.go | 11 +- 6 files changed, 306 insertions(+), 413 deletions(-) delete mode 100644 builtin/providers/consul/resource_consul_key.go create mode 100644 builtin/providers/consul/resource_consul_keys.go diff --git a/builtin/providers/consul/resource_consul_key.go b/builtin/providers/consul/resource_consul_key.go deleted file mode 100644 index 31a0d6c0e..000000000 --- a/builtin/providers/consul/resource_consul_key.go +++ /dev/null @@ -1,394 +0,0 @@ -package consul - -import ( - "fmt" - "log" - - "github.com/armon/consul-api" - "github.com/hashicorp/terraform/terraform" - "github.com/mitchellh/mapstructure" -) - -type consulKeys map[string]*consulKey - -type consulKey struct { - Key string - Value string - Default string - Delete bool - - SetValue bool `mapstructure:"-"` - SetDefault bool `mapstructure:"-"` -} - -func resource_consul_keys_validate(c *terraform.ResourceConfig) (ws []string, es []error) { - conf := c.Raw - for k, v := range conf { - // datacenter is special and can be ignored - if k == "datacenter" { - continue - } - - keyList, ok := v.([]map[string]interface{}) - if !ok { - es = append(es, fmt.Errorf("Field '%s' must be map containing a key", k)) - continue - } - if len(keyList) > 1 { - es = append(es, fmt.Errorf("Field '%s' is defined more than once", k)) - continue - } - key := keyList[0] - - for sub, val := range key { - // Verify the sub-key is supported - switch sub { - case "key": - case "value": - case "default": - case "delete": - default: - es = append(es, fmt.Errorf("Field '%s' has unsupported config '%s'", k, sub)) - continue - } - - // Verify value is of the correct type - _, isStr := val.(string) - _, isBool := val.(bool) - if !isStr && sub != "delete" { - es = append(es, fmt.Errorf("Field '%s' must set '%s' as a string", key, sub)) - } - if !isBool && sub == "delete" { - es = append(es, fmt.Errorf("Field '%s' must set '%s' as a bool", key, sub)) - } - } - } - return -} - -func resource_consul_keys_create( - s *terraform.ResourceState, - d *terraform.ResourceDiff, - meta interface{}) (*terraform.ResourceState, error) { - p := meta.(*ResourceProvider) - if s.Attributes == nil { - s.Attributes = make(map[string]string) - } - - // Load the configuration - var config map[string]interface{} - for _, attr := range d.Attributes { - if attr.NewExtra != nil { - config = attr.NewExtra.(map[string]interface{}) - break - } - } - if config == nil { - return s, fmt.Errorf("Missing configuration state") - } - dc, keys, err := partsFromConfig(config) - if err != nil { - return s, err - } - - // Check if we are missing a datacenter - if dc == "" { - dc, err = get_dc(p.client) - } - s.Attributes["datacenter"] = dc - - // Handle each of the keys - kv := p.client.KV() - qOpts := consulapi.QueryOptions{Datacenter: dc} - wOpts := consulapi.WriteOptions{Datacenter: dc} - for name, conf := range keys { - if conf.SetValue { - log.Printf("[DEBUG] Setting key '%s' to '%v' in %s", conf.Key, conf.Value, dc) - pair := consulapi.KVPair{Key: conf.Key, Value: []byte(conf.Value)} - if _, err := kv.Put(&pair, &wOpts); err != nil { - return s, fmt.Errorf("Failed to set Consul key '%s': %v", conf.Key, err) - } - s.Attributes[name] = conf.Value - } else { - log.Printf("[DEBUG] Getting key '%s' in %s", conf.Key, dc) - pair, _, err := kv.Get(conf.Key, &qOpts) - if err != nil { - return s, fmt.Errorf("Failed to get Consul key '%s': %v", conf.Key, err) - } - if pair == nil && conf.SetDefault { - s.Attributes[name] = conf.Default - } else if pair == nil { - s.Attributes[name] = "" - } else { - s.Attributes[name] = string(pair.Value) - } - } - } - - // Set an ID, store the config - s.ID = "consul" - s.Extra = config - return s, nil -} - -func resource_consul_keys_destroy( - s *terraform.ResourceState, - meta interface{}) error { - p := meta.(*ResourceProvider) - client := p.client - kv := client.KV() - - // Restore our configuration - dc, keys, err := partsFromConfig(s.Extra) - if err != nil { - return err - } - - // Load the DC if not given - if dc == "" { - dc = s.Attributes["datacenter"] - } - opts := consulapi.WriteOptions{Datacenter: dc} - for _, key := range keys { - // Skip any non-managed keys - if !key.Delete { - continue - } - log.Printf("[DEBUG] Deleting key '%s' in %s", key.Key, dc) - if _, err := kv.Delete(key.Key, &opts); err != nil { - return fmt.Errorf("Failed to delete Consul key '%s': %v", key.Key, err) - } - } - return nil -} - -func resource_consul_keys_update( - s *terraform.ResourceState, - d *terraform.ResourceDiff, - meta interface{}) (*terraform.ResourceState, error) { - p := meta.(*ResourceProvider) - - // Load the configuration - var config map[string]interface{} - for _, attr := range d.Attributes { - if attr.NewExtra != nil { - config = attr.NewExtra.(map[string]interface{}) - break - } - } - if config == nil { - return s, fmt.Errorf("Missing configuration state") - } - dc, keys, err := partsFromConfig(config) - if err != nil { - return s, err - } - - // Check if we are missing a datacenter - if dc == "" { - dc, err = get_dc(p.client) - } - s.Attributes["datacenter"] = dc - - // Handle each of the updated keys - kv := p.client.KV() - qOpts := consulapi.QueryOptions{Datacenter: dc} - wOpts := consulapi.WriteOptions{Datacenter: dc} - for name := range d.Attributes { - if name == "datacenter" { - continue - } - conf := keys[name] - if conf.SetValue { - log.Printf("[DEBUG] Setting key '%s' to '%v' in %s", conf.Key, conf.Value, dc) - pair := consulapi.KVPair{Key: conf.Key, Value: []byte(conf.Value)} - if _, err := kv.Put(&pair, &wOpts); err != nil { - return s, fmt.Errorf("Failed to set Consul key '%s': %v", conf.Key, err) - } - s.Attributes[name] = conf.Value - } else { - log.Printf("[DEBUG] Getting key '%s' in %s", conf.Key, dc) - pair, _, err := kv.Get(conf.Key, &qOpts) - if err != nil { - return s, fmt.Errorf("Failed to get Consul key '%s': %v", conf.Key, err) - } - if pair == nil && conf.SetDefault { - s.Attributes[name] = conf.Default - } else if pair == nil { - s.Attributes[name] = "" - } else { - s.Attributes[name] = string(pair.Value) - } - } - } - - // Update the config - s.Extra = config - return s, nil -} - -func resource_consul_keys_diff( - s *terraform.ResourceState, - c *terraform.ResourceConfig, - meta interface{}) (*terraform.ResourceDiff, error) { - // Parse the configuration - dc, keys, err := partsFromConfig(c.Config) - if err != nil { - return nil, err - } - - // Get the old values - oldValues := s.Attributes - - // Initialize the diff set - attrs := make(map[string]*terraform.ResourceAttrDiff) - diff := &terraform.ResourceDiff{Attributes: attrs} - - // Handle removed attributes - for key, oldVal := range oldValues { - if key == "datacenter" { - continue - } - if _, keep := keys[key]; !keep { - attrs[key] = &terraform.ResourceAttrDiff{ - Old: oldVal, - NewRemoved: true, - } - } - } - - // Handle added or changed attributes - for key, conf := range keys { - aDiff := &terraform.ResourceAttrDiff{ - Type: terraform.DiffAttrInput, - } - oldVal, ok := oldValues[key] - if conf.SetValue { - aDiff.New = conf.Value - } else { - aDiff.NewComputed = true - } - if ok { - aDiff.Old = oldVal - } - - // If this is new or changed we need to refresh - if !ok || (conf.SetValue && oldVal != conf.Value) { - attrs[key] = aDiff - } - } - - // If the DC has changed, require a destroy! - if old := oldValues["datacenter"]; dc != old { - aDiff := &terraform.ResourceAttrDiff{ - Old: old, - New: dc, - RequiresNew: true, - Type: terraform.DiffAttrInput, - } - if aDiff.New == "" { - aDiff.NewComputed = true - } - attrs["datacenter"] = aDiff - } - - // Make sure one of the attributes contains the configuration - if len(attrs) > 0 { - for _, aDiff := range attrs { - aDiff.NewExtra = c.Config - break - } - } - return diff, nil -} - -func resource_consul_keys_refresh( - s *terraform.ResourceState, - meta interface{}) (*terraform.ResourceState, error) { - p := meta.(*ResourceProvider) - client := p.client - kv := client.KV() - - // Restore our configuration - dc, keys, err := partsFromConfig(s.Extra) - if err != nil { - return s, err - } - - // Check if we are missing a datacenter - if dc == "" { - dc, err = get_dc(p.client) - if err != nil { - return s, err - } - } - - // Update the attributes - s.Attributes["datacenter"] = dc - opts := consulapi.QueryOptions{Datacenter: dc} - for name, key := range keys { - pair, _, err := kv.Get(key.Key, &opts) - if err != nil { - return s, fmt.Errorf("Failed to get key '%s' from Consul: %v", key.Key, err) - } - if pair == nil && key.SetDefault { - s.Attributes[name] = key.Default - } else if pair == nil { - s.Attributes[name] = "" - } else { - s.Attributes[name] = string(pair.Value) - } - } - return s, nil -} - -// partsFromConfig extracts the relevant configuration from the raw format -func partsFromConfig(raw map[string]interface{}) (string, consulKeys, error) { - var dc string - keys := make(map[string]*consulKey) - for k, v := range raw { - // datacenter is special and can be ignored - if k == "datacenter" { - vStr, ok := v.(string) - if !ok { - return "", nil, fmt.Errorf("datacenter must be a string") - } - dc = vStr - continue - } - - confs, ok := v.([]map[string]interface{}) - if !ok { - return "", nil, fmt.Errorf("Field '%s' must be map containing a key", k) - } - if len(confs) > 1 { - return "", nil, fmt.Errorf("Field '%s' has duplicate definitions", k) - } - conf := confs[0] - - key := &consulKey{} - if err := mapstructure.WeakDecode(conf, key); err != nil { - return "", nil, fmt.Errorf("Field '%s' failed to decode: %v", k, err) - } - for sub := range conf { - switch sub { - case "value": - key.SetValue = true - case "default": - key.SetDefault = true - } - } - keys[k] = key - } - return dc, keys, nil -} - -// get_dc is used to get the datacenter of the local agent -func get_dc(client *consulapi.Client) (string, error) { - info, err := client.Agent().Self() - if err != nil { - return "", fmt.Errorf("Failed to get datacenter from Consul agent: %v", err) - } - dc := info["Config"]["Datacenter"].(string) - return dc, nil -} diff --git a/builtin/providers/consul/resource_consul_keys.go b/builtin/providers/consul/resource_consul_keys.go new file mode 100644 index 000000000..8a007f578 --- /dev/null +++ b/builtin/providers/consul/resource_consul_keys.go @@ -0,0 +1,288 @@ +package consul + +import ( + "fmt" + "log" + "strconv" + + "github.com/armon/consul-api" + "github.com/hashicorp/terraform/flatmap" + "github.com/hashicorp/terraform/helper/config" + "github.com/hashicorp/terraform/helper/diff" + "github.com/hashicorp/terraform/terraform" +) + +func resource_consul_keys_validation() *config.Validator { + return &config.Validator{ + Required: []string{ + "key.*.name", + "key.*.path", + }, + Optional: []string{ + "datacenter", + "key.*.value", + "key.*.default", + "key.*.delete", + }, + } +} + +func resource_consul_keys_create( + s *terraform.ResourceState, + d *terraform.ResourceDiff, + meta interface{}) (*terraform.ResourceState, error) { + p := meta.(*ResourceProvider) + + // Merge the diff into the state so that we have all the attributes + // properly. + rs := s.MergeDiff(d) + + // Check if the datacenter should be computed + dc := rs.Attributes["datacenter"] + if aDiff, ok := d.Attributes["datacenter"]; ok && aDiff.NewComputed { + var err error + dc, err = get_dc(p.client) + if err != nil { + return rs, fmt.Errorf("Failed to get agent datacenter: %v", err) + } + rs.Attributes["datacenter"] = dc + } + + // Get the keys + keys, ok := flatmap.Expand(rs.Attributes, "key").([]interface{}) + if !ok { + return s, fmt.Errorf("Failed to unroll keys") + } + + kv := p.client.KV() + qOpts := consulapi.QueryOptions{Datacenter: dc} + wOpts := consulapi.WriteOptions{Datacenter: dc} + for _, raw := range keys { + sub := raw.(map[string]interface{}) + if !ok { + return s, fmt.Errorf("Failed to unroll: %#v", raw) + } + + key, ok := sub["name"].(string) + if !ok { + return s, fmt.Errorf("Failed to expand key '%#v'", sub) + } + + path, ok := sub["path"].(string) + if !ok { + return s, fmt.Errorf("Failed to get path for key '%s'", key) + } + + valueRaw, shouldSet := sub["value"] + if shouldSet { + value, ok := valueRaw.(string) + if !ok { + return rs, fmt.Errorf("Failed to get value for key '%s'", key) + } + + log.Printf("[DEBUG] Setting key '%s' to '%v' in %s", path, value, dc) + pair := consulapi.KVPair{Key: path, Value: []byte(value)} + if _, err := kv.Put(&pair, &wOpts); err != nil { + return rs, fmt.Errorf("Failed to set Consul key '%s': %v", path, err) + } + rs.Attributes[fmt.Sprintf("var.%s", key)] = value + } else { + log.Printf("[DEBUG] Getting key '%s' in %s", path, dc) + pair, _, err := kv.Get(path, &qOpts) + if err != nil { + return rs, fmt.Errorf("Failed to get Consul key '%s': %v", path, err) + } + + // Check for a default value + var defaultVal string + setDefault := false + if raw, ok := sub["default"]; ok { + switch def := raw.(type) { + case string: + setDefault = true + defaultVal = def + case bool: + setDefault = true + defaultVal = strconv.FormatBool(def) + } + } + + if pair == nil && setDefault { + rs.Attributes[fmt.Sprintf("var.%s", key)] = defaultVal + } else if pair == nil { + rs.Attributes[fmt.Sprintf("var.%s", key)] = "" + } else { + rs.Attributes[fmt.Sprintf("var.%s", key)] = string(pair.Value) + } + } + } + + // Set an ID + rs.ID = "consul" + return rs, nil +} + +func resource_consul_keys_destroy( + s *terraform.ResourceState, + meta interface{}) error { + p := meta.(*ResourceProvider) + client := p.client + kv := client.KV() + + // Get the keys + keys, ok := flatmap.Expand(s.Attributes, "key").([]interface{}) + if !ok { + return fmt.Errorf("Failed to unroll keys") + } + + dc := s.Attributes["datacenter"] + wOpts := consulapi.WriteOptions{Datacenter: dc} + for _, raw := range keys { + sub := raw.(map[string]interface{}) + if !ok { + return fmt.Errorf("Failed to unroll: %#v", raw) + } + + // Ignore if the key is non-managed + shouldDelete, ok := sub["delete"].(bool) + if !ok || !shouldDelete { + continue + } + + key, ok := sub["name"].(string) + if !ok { + return fmt.Errorf("Failed to expand key '%#v'", sub) + } + + path, ok := sub["path"].(string) + if !ok { + return fmt.Errorf("Failed to get path for key '%s'", key) + } + + log.Printf("[DEBUG] Deleting key '%s' in %s", path, dc) + _, err := kv.Delete(path, &wOpts) + if err != nil { + return fmt.Errorf("Failed to delete Consul key '%s': %v", path, err) + } + } + return nil +} + +func resource_consul_keys_update( + s *terraform.ResourceState, + d *terraform.ResourceDiff, + meta interface{}) (*terraform.ResourceState, error) { + panic("cannot update") + return s, nil +} + +func resource_consul_keys_diff( + s *terraform.ResourceState, + c *terraform.ResourceConfig, + meta interface{}) (*terraform.ResourceDiff, error) { + + // Get the list of keys + var computed []string + keys, ok := flatmap.Expand(flatmap.Flatten(c.Config), "key").([]interface{}) + if !ok { + goto AFTER + } + for _, sub := range keys { + subMap, ok := sub.(map[string]interface{}) + if !ok { + continue + } + nameRaw, ok := subMap["name"] + if !ok { + continue + } + name, ok := nameRaw.(string) + if !ok { + continue + } + computed = append(computed, "var."+name) + } + +AFTER: + b := &diff.ResourceBuilder{ + Attrs: map[string]diff.AttrType{ + "datacenter": diff.AttrTypeCreate, + "key": diff.AttrTypeUpdate, + }, + ComputedAttrs: computed, + } + return b.Diff(s, c) +} + +func resource_consul_keys_refresh( + s *terraform.ResourceState, + meta interface{}) (*terraform.ResourceState, error) { + p := meta.(*ResourceProvider) + client := p.client + kv := client.KV() + + // Get the list of keys + keys, ok := flatmap.Expand(s.Attributes, "key").([]interface{}) + if !ok { + return s, fmt.Errorf("Failed to unroll keys") + } + + // Update each key + dc := s.Attributes["datacenter"] + opts := consulapi.QueryOptions{Datacenter: dc} + for _, raw := range keys { + sub := raw.(map[string]interface{}) + if !ok { + return s, fmt.Errorf("Failed to unroll: %#v", raw) + } + + key, ok := sub["name"].(string) + if !ok { + return s, fmt.Errorf("Failed to expand key '%#v'", sub) + } + + path, ok := sub["path"].(string) + if !ok { + return s, fmt.Errorf("Failed to get path for key '%s'", key) + } + + log.Printf("[DEBUG] Refreshing value of key '%s' in %s", path, dc) + pair, _, err := kv.Get(path, &opts) + if err != nil { + return s, fmt.Errorf("Failed to get value for path '%s' from Consul: %v", path, err) + } + + // Check for a default value + var defaultVal string + setDefault := false + if raw, ok := sub["default"]; ok { + switch def := raw.(type) { + case string: + setDefault = true + defaultVal = def + case bool: + setDefault = true + defaultVal = strconv.FormatBool(def) + } + } + + if pair == nil && setDefault { + s.Attributes[fmt.Sprintf("var.%s", key)] = defaultVal + } else if pair == nil { + s.Attributes[fmt.Sprintf("var.%s", key)] = "" + } else { + s.Attributes[fmt.Sprintf("var.%s", key)] = string(pair.Value) + } + } + return s, nil +} + +// get_dc is used to get the datacenter of the local agent +func get_dc(client *consulapi.Client) (string, error) { + info, err := client.Agent().Self() + if err != nil { + return "", fmt.Errorf("Failed to get datacenter from Consul agent: %v", err) + } + dc := info["Config"]["Datacenter"].(string) + return dc, nil +} diff --git a/builtin/providers/consul/resource_consul_keys_test.go b/builtin/providers/consul/resource_consul_keys_test.go index f7bef6163..84429ee75 100644 --- a/builtin/providers/consul/resource_consul_keys_test.go +++ b/builtin/providers/consul/resource_consul_keys_test.go @@ -62,7 +62,7 @@ func testAccCheckConsulKeysValue(n, attr, val string) resource.TestCheckFunc { if !ok { return fmt.Errorf("Resource not found") } - out, ok := rn.Attributes[attr] + out, ok := rn.Attributes["var."+attr] if !ok { return fmt.Errorf("Attribute '%s' not found: %#v", attr, rn.Attributes) } @@ -79,15 +79,18 @@ func testAccCheckConsulKeysValue(n, attr, val string) resource.TestCheckFunc { const testAccConsulKeysConfig = ` resource "consul_keys" "app" { datacenter = "nyc1" - time { - key = "global/time" + key { + name = "time" + path = "global/time" } - enabled { - key = "test/enabled" + key { + name = "enabled" + path = "test/enabled" default = "true" } - set { - key = "test/set" + key { + name = "set" + path = "test/set" value = "acceptance" delete = true } diff --git a/builtin/providers/consul/resource_provider.go b/builtin/providers/consul/resource_provider.go index 2ca299abf..c2100385d 100644 --- a/builtin/providers/consul/resource_provider.go +++ b/builtin/providers/consul/resource_provider.go @@ -25,12 +25,7 @@ func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []er func (p *ResourceProvider) ValidateResource( t string, c *terraform.ResourceConfig) ([]string, []error) { - switch t { - case "consul_keys": - return resource_consul_keys_validate(c) - default: - return resourceMap.Validate(t, c) - } + return resourceMap.Validate(t, c) } func (p *ResourceProvider) Configure(c *terraform.ResourceConfig) error { diff --git a/builtin/providers/consul/resource_provider_test.go b/builtin/providers/consul/resource_provider_test.go index ca1adcc5b..0ce5087dc 100644 --- a/builtin/providers/consul/resource_provider_test.go +++ b/builtin/providers/consul/resource_provider_test.go @@ -13,7 +13,7 @@ var testAccProvider *ResourceProvider func init() { testAccProvider = new(ResourceProvider) - testAccProvider.Config.Address = "demo.consul.io:80" + //testAccProvider.Config.Address = "demo.consul.io:80" testAccProviders = map[string]terraform.ResourceProvider{ "consul": testAccProvider, } diff --git a/builtin/providers/consul/resources.go b/builtin/providers/consul/resources.go index 1de2c6b2b..5829d059a 100644 --- a/builtin/providers/consul/resources.go +++ b/builtin/providers/consul/resources.go @@ -12,11 +12,12 @@ func init() { resourceMap = &resource.Map{ Mapping: map[string]resource.Resource{ "consul_keys": resource.Resource{ - Create: resource_consul_keys_create, - Destroy: resource_consul_keys_destroy, - Update: resource_consul_keys_update, - Diff: resource_consul_keys_diff, - Refresh: resource_consul_keys_refresh, + ConfigValidator: resource_consul_keys_validation(), + Create: resource_consul_keys_create, + Destroy: resource_consul_keys_destroy, + Update: resource_consul_keys_update, + Diff: resource_consul_keys_diff, + Refresh: resource_consul_keys_refresh, }, }, }