diff --git a/builtin/bins/provider-consul/main.go b/builtin/bins/provider-consul/main.go index 44a8b8e39..f922667a2 100644 --- a/builtin/bins/provider-consul/main.go +++ b/builtin/bins/provider-consul/main.go @@ -3,13 +3,10 @@ package main import ( "github.com/hashicorp/terraform/builtin/providers/consul" "github.com/hashicorp/terraform/plugin" - "github.com/hashicorp/terraform/terraform" ) func main() { plugin.Serve(&plugin.ServeOpts{ - ProviderFunc: func() terraform.ResourceProvider { - return new(consul.ResourceProvider) - }, + ProviderFunc: consul.Provider, }) } diff --git a/builtin/providers/consul/resource_consul_keys.go b/builtin/providers/consul/resource_consul_keys.go index 12da4747b..4f17cff7d 100644 --- a/builtin/providers/consul/resource_consul_keys.go +++ b/builtin/providers/consul/resource_consul_keys.go @@ -1,116 +1,212 @@ package consul import ( + "bytes" "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" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" ) -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 resourceConsulKeys() *schema.Resource { + return &schema.Resource{ + Create: resourceConsulKeysCreate, + Update: resourceConsulKeysCreate, + Read: resourceConsulKeysRead, + Delete: resourceConsulKeysDelete, + + Schema: map[string]*schema.Schema{ + "datacenter": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "keys": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "path": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "value": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "default": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "delete": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + Set: resourceConsulKeysHash, + }, + + "var": &schema.Schema{ + Type: schema.TypeMap, + Computed: true, + }, }, } } -func resource_consul_keys_update( - s *terraform.InstanceState, - d *terraform.InstanceDiff, - meta interface{}) (*terraform.InstanceState, error) { - return resource_consul_keys_create(s, d, meta) + +func resourceConsulKeysHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["path"].(string))) + return hashcode.String(buf.String()) } -func resource_consul_keys_create( - s *terraform.InstanceState, - d *terraform.InstanceDiff, - meta interface{}) (*terraform.InstanceState, error) { - p := meta.(*ResourceProvider) +func resourceConsulKeysCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*consulapi.Client) + kv := client.KV() - // Merge the diff into the state so that we have all the attributes - // properly. - rs := s.MergeDiff(d) - rs.ID = "consul" - - // Check if the datacenter should be computed - dc := rs.Attributes["datacenter"] - if aDiff, ok := d.Attributes["datacenter"]; ok && aDiff.NewComputed { + // Resolve the datacenter first, all the other keys are dependent + // on this. + var dc string + if v := d.Get("datacenter"); v != nil { + dc = v.(string) + log.Printf("[DEBUG] Consul datacenter: %s", dc) + } else { + log.Printf("[DEBUG] Resolving Consul datacenter...") var err error - dc, err = get_dc(p.client) + dc, err = get_dc(client) if err != nil { - return rs, fmt.Errorf("Failed to get agent datacenter: %v", err) + return err } - rs.Attributes["datacenter"] = dc } - // Get the keys - keys, ok := flatmap.Expand(rs.Attributes, "key").([]interface{}) - if !ok { - return rs, fmt.Errorf("Failed to unroll keys") - } - - kv := p.client.KV() + // Setup the operations using the datacenter qOpts := consulapi.QueryOptions{Datacenter: dc} wOpts := consulapi.WriteOptions{Datacenter: dc} - for idx, raw := range keys { + + // Store the computed vars + vars := make(map[string]string) + + // Extract the keys + keys := d.Get("keys").(*schema.Set).List() + for _, raw := range keys { key, path, sub, err := parse_key(raw) if err != nil { - return rs, err + return err } if valueRaw, ok := sub["value"]; ok { value, ok := valueRaw.(string) if !ok { - return rs, fmt.Errorf("Failed to get value for key '%s'", key) + return 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) + return fmt.Errorf("Failed to set Consul key '%s': %v", path, err) } - rs.Attributes[fmt.Sprintf("var.%s", key)] = value - rs.Attributes[fmt.Sprintf("key.%d.value", idx)] = value + vars[key] = value + sub["value"] = 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) + return fmt.Errorf("Failed to get Consul key '%s': %v", path, err) } - rs.Attributes[fmt.Sprintf("var.%s", key)] = attribute_value(sub, key, pair) + value := attribute_value(sub, key, pair) + vars[key] = value + sub["value"] = value } } - return rs, nil + + // Update the resource + d.SetId("consul") + d.Set("datacenter", dc) + d.Set("keys", keys) + d.Set("var", vars) + return nil } -func resource_consul_keys_destroy( - s *terraform.InstanceState, - meta interface{}) error { - p := meta.(*ResourceProvider) - client := p.client +func resourceConsulKeysRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*consulapi.Client) kv := client.KV() - // Get the keys - keys, ok := flatmap.Expand(s.Attributes, "key").([]interface{}) - if !ok { - return fmt.Errorf("Failed to unroll keys") + // Get the DC, error if not available. + var dc string + if v := d.Get("datacenter"); v != nil { + dc = v.(string) + log.Printf("[DEBUG] Consul datacenter: %s", dc) + } else { + return fmt.Errorf("Missing datacenter configuration") } - dc := s.Attributes["datacenter"] + // Setup the operations using the datacenter + qOpts := consulapi.QueryOptions{Datacenter: dc} + + // Store the computed vars + vars := make(map[string]string) + + // Extract the keys + keys := d.Get("keys").(*schema.Set).List() + for _, raw := range keys { + key, path, sub, err := parse_key(raw) + if err != nil { + return err + } + + log.Printf("[DEBUG] Refreshing value of key '%s' in %s", path, dc) + pair, _, err := kv.Get(path, &qOpts) + if err != nil { + return fmt.Errorf("Failed to get value for path '%s' from Consul: %v", path, err) + } + + value := attribute_value(sub, key, pair) + vars[key] = value + sub["value"] = value + } + + // Update the resource + d.Set("keys", keys) + d.Set("var", vars) + return nil +} + +func resourceConsulKeysDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*consulapi.Client) + kv := client.KV() + + // Get the DC, error if not available. + var dc string + if v := d.Get("datacenter"); v != nil { + dc = v.(string) + log.Printf("[DEBUG] Consul datacenter: %s", dc) + } else { + return fmt.Errorf("Missing datacenter configuration") + } + + // Setup the operations using the datacenter wOpts := consulapi.WriteOptions{Datacenter: dc} + + // Extract the keys + keys := d.Get("keys").(*schema.Set).List() for _, raw := range keys { _, path, sub, err := parse_key(raw) if err != nil { @@ -128,76 +224,12 @@ func resource_consul_keys_destroy( return fmt.Errorf("Failed to delete Consul key '%s': %v", path, err) } } + + // Clear the ID + d.SetId("") return nil } -func resource_consul_keys_diff( - s *terraform.InstanceState, - c *terraform.ResourceConfig, - meta interface{}) (*terraform.InstanceDiff, error) { - - // Determine the list of computed variables - var computed []string - keys, ok := flatmap.Expand(flatmap.Flatten(c.Config), "key").([]interface{}) - if !ok { - goto AFTER - } - for _, sub := range keys { - key, _, _, err := parse_key(sub) - if err != nil { - continue - } - computed = append(computed, "var."+key) - } - -AFTER: - b := &diff.ResourceBuilder{ - Attrs: map[string]diff.AttrType{ - "datacenter": diff.AttrTypeCreate, - "key": diff.AttrTypeUpdate, - }, - ComputedAttrsUpdate: computed, - } - return b.Diff(s, c) -} - -func resource_consul_keys_refresh( - s *terraform.InstanceState, - meta interface{}) (*terraform.InstanceState, 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 idx, raw := range keys { - key, path, sub, err := parse_key(raw) - if err != nil { - return s, err - } - - 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) - } - - setVal := attribute_value(sub, key, pair) - s.Attributes[fmt.Sprintf("var.%s", key)] = setVal - if _, ok := sub["value"]; ok { - s.Attributes[fmt.Sprintf("key.%d.value", idx)] = setVal - } - } - return s, nil -} - // parse_key is used to parse a key into a name, path, config or error func parse_key(raw interface{}) (string, string, map[string]interface{}, error) { sub, ok := raw.(map[string]interface{}) @@ -217,7 +249,8 @@ func parse_key(raw interface{}) (string, string, map[string]interface{}, error) return key, path, sub, nil } -// attribute_value determienes the value for a key +// attribute_value determines the value for a key, potentially +// using a default value if provided. func attribute_value(sub map[string]interface{}, key string, pair *consulapi.KVPair) string { // Use the value if given if pair != nil { diff --git a/builtin/providers/consul/resource_consul_keys_test.go b/builtin/providers/consul/resource_consul_keys_test.go index da872071d..2f52be83b 100644 --- a/builtin/providers/consul/resource_consul_keys_test.go +++ b/builtin/providers/consul/resource_consul_keys_test.go @@ -29,7 +29,7 @@ func TestAccConsulKeys(t *testing.T) { } func testAccCheckConsulKeysDestroy(s *terraform.State) error { - kv := testAccProvider.client.KV() + kv := testAccProvider.Meta().(*consulapi.Client).KV() opts := &consulapi.QueryOptions{Datacenter: "nyc1"} pair, _, err := kv.Get("test/set", opts) if err != nil { @@ -43,7 +43,7 @@ func testAccCheckConsulKeysDestroy(s *terraform.State) error { func testAccCheckConsulKeysExists() resource.TestCheckFunc { return func(s *terraform.State) error { - kv := testAccProvider.client.KV() + kv := testAccProvider.Meta().(*consulapi.Client).KV() opts := &consulapi.QueryOptions{Datacenter: "nyc1"} pair, _, err := kv.Get("test/set", opts) if err != nil { diff --git a/builtin/providers/consul/resource_provider.go b/builtin/providers/consul/resource_provider.go index 13200ebc1..28cbb9823 100644 --- a/builtin/providers/consul/resource_provider.go +++ b/builtin/providers/consul/resource_provider.go @@ -3,71 +3,40 @@ package consul import ( "log" - "github.com/armon/consul-api" - "github.com/hashicorp/terraform/helper/config" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" + "github.com/mitchellh/mapstructure" ) -type ResourceProvider struct { - Config Config - client *consulapi.Client -} +// Provider returns a terraform.ResourceProvider. +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "datacenter": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, -func (p *ResourceProvider) Input( - input terraform.UIInput, - c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { - return c, nil -} - -func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) { - v := &config.Validator{ - Optional: []string{ - "datacenter", - "address", + "address": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, }, + + ResourcesMap: map[string]*schema.Resource{ + "consul_keys": resourceConsulKeys(), + }, + + ConfigureFunc: providerConfigure, } - return v.Validate(c) } -func (p *ResourceProvider) ValidateResource( - t string, c *terraform.ResourceConfig) ([]string, []error) { - return resourceMap.Validate(t, c) -} - -func (p *ResourceProvider) Configure(c *terraform.ResourceConfig) error { - if _, err := config.Decode(&p.Config, c.Config); err != nil { - return err +func providerConfigure(d *schema.ResourceData) (interface{}, error) { + var config Config + configRaw := d.Get("").(map[string]interface{}) + if err := mapstructure.Decode(configRaw, &config); err != nil { + return nil, err } - log.Printf("[INFO] Initializing Consul client") - var err error - p.client, err = p.Config.Client() - if err != nil { - return err - } - return nil -} - -func (p *ResourceProvider) Apply( - info *terraform.InstanceInfo, - s *terraform.InstanceState, - d *terraform.InstanceDiff) (*terraform.InstanceState, error) { - return resourceMap.Apply(info, s, d, p) -} - -func (p *ResourceProvider) Diff( - info *terraform.InstanceInfo, - s *terraform.InstanceState, - c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { - return resourceMap.Diff(info, s, c, p) -} - -func (p *ResourceProvider) Refresh( - info *terraform.InstanceInfo, - s *terraform.InstanceState) (*terraform.InstanceState, error) { - return resourceMap.Refresh(info, s, p) -} - -func (p *ResourceProvider) Resources() []terraform.ResourceType { - return resourceMap.Resources() + return config.Client() } diff --git a/builtin/providers/consul/resource_provider_test.go b/builtin/providers/consul/resource_provider_test.go index ca1adcc5b..5e2746f98 100644 --- a/builtin/providers/consul/resource_provider_test.go +++ b/builtin/providers/consul/resource_provider_test.go @@ -1,30 +1,35 @@ package consul import ( - "reflect" "testing" "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) var testAccProviders map[string]terraform.ResourceProvider -var testAccProvider *ResourceProvider +var testAccProvider *schema.Provider func init() { - testAccProvider = new(ResourceProvider) - testAccProvider.Config.Address = "demo.consul.io:80" + testAccProvider = Provider().(*schema.Provider) testAccProviders = map[string]terraform.ResourceProvider{ "consul": testAccProvider, } } +func TestResourceProvider(t *testing.T) { + if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} + func TestResourceProvider_impl(t *testing.T) { - var _ terraform.ResourceProvider = new(ResourceProvider) + var _ terraform.ResourceProvider = Provider() } func TestResourceProvider_Configure(t *testing.T) { - rp := new(ResourceProvider) + rp := Provider() raw := map[string]interface{}{ "address": "demo.consul.io:80", @@ -40,13 +45,4 @@ func TestResourceProvider_Configure(t *testing.T) { if err != nil { t.Fatalf("err: %s", err) } - - expected := Config{ - Address: "demo.consul.io:80", - Datacenter: "nyc1", - } - - if !reflect.DeepEqual(rp.Config, expected) { - t.Fatalf("bad: %#v", rp.Config) - } } diff --git a/builtin/providers/consul/resources.go b/builtin/providers/consul/resources.go deleted file mode 100644 index 5829d059a..000000000 --- a/builtin/providers/consul/resources.go +++ /dev/null @@ -1,24 +0,0 @@ -package consul - -import ( - "github.com/hashicorp/terraform/helper/resource" -) - -// resourceMap is the mapping of resources we support to their basic -// operations. This makes it easy to implement new resource types. -var resourceMap *resource.Map - -func init() { - resourceMap = &resource.Map{ - Mapping: map[string]resource.Resource{ - "consul_keys": resource.Resource{ - 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, - }, - }, - } -}