|
|
|
|
@ -1,201 +1,250 @@
|
|
|
|
|
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,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
"token": &schema.Schema{
|
|
|
|
|
Type: schema.TypeString,
|
|
|
|
|
Optional: 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 resource_consul_keys_create(
|
|
|
|
|
s *terraform.InstanceState,
|
|
|
|
|
d *terraform.InstanceDiff,
|
|
|
|
|
meta interface{}) (*terraform.InstanceState, error) {
|
|
|
|
|
p := meta.(*ResourceProvider)
|
|
|
|
|
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())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Merge the diff into the state so that we have all the attributes
|
|
|
|
|
// properly.
|
|
|
|
|
rs := s.MergeDiff(d)
|
|
|
|
|
rs.ID = "consul"
|
|
|
|
|
func resourceConsulKeysCreate(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
|
client := meta.(*consulapi.Client)
|
|
|
|
|
kv := client.KV()
|
|
|
|
|
|
|
|
|
|
// 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, ok := d.GetOk("datacenter"); ok {
|
|
|
|
|
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")
|
|
|
|
|
var token string
|
|
|
|
|
if v, ok := d.GetOk("token"); ok {
|
|
|
|
|
token = v.(string)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kv := p.client.KV()
|
|
|
|
|
qOpts := consulapi.QueryOptions{Datacenter: dc}
|
|
|
|
|
wOpts := consulapi.WriteOptions{Datacenter: dc}
|
|
|
|
|
for idx, raw := range keys {
|
|
|
|
|
// Setup the operations using the datacenter
|
|
|
|
|
qOpts := consulapi.QueryOptions{Datacenter: dc, Token: token}
|
|
|
|
|
wOpts := consulapi.WriteOptions{Datacenter: dc, Token: token}
|
|
|
|
|
|
|
|
|
|
// 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, ok := d.GetOk("datacenter"); ok {
|
|
|
|
|
dc = v.(string)
|
|
|
|
|
log.Printf("[DEBUG] Consul datacenter: %s", dc)
|
|
|
|
|
} else {
|
|
|
|
|
return fmt.Errorf("Missing datacenter configuration")
|
|
|
|
|
}
|
|
|
|
|
var token string
|
|
|
|
|
if v, ok := d.GetOk("token"); ok {
|
|
|
|
|
token = v.(string)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dc := s.Attributes["datacenter"]
|
|
|
|
|
wOpts := consulapi.WriteOptions{Datacenter: dc}
|
|
|
|
|
// Setup the operations using the datacenter
|
|
|
|
|
qOpts := consulapi.QueryOptions{Datacenter: dc, Token: token}
|
|
|
|
|
|
|
|
|
|
// Store the computed vars
|
|
|
|
|
vars := make(map[string]string)
|
|
|
|
|
|
|
|
|
|
// Extract the keys
|
|
|
|
|
keys := d.Get("keys").(*schema.Set).List()
|
|
|
|
|
for _, raw := range keys {
|
|
|
|
|
_, path, sub, err := parse_key(raw)
|
|
|
|
|
key, path, sub, err := parse_key(raw)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ignore if the key is non-managed
|
|
|
|
|
shouldDelete, ok := sub["delete"].(bool)
|
|
|
|
|
if !ok || !shouldDelete {
|
|
|
|
|
continue
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.Printf("[DEBUG] Deleting key '%s' in %s", path, dc)
|
|
|
|
|
if _, err := kv.Delete(path, &wOpts); err != nil {
|
|
|
|
|
return fmt.Errorf("Failed to delete Consul key '%s': %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 resource_consul_keys_diff(
|
|
|
|
|
s *terraform.InstanceState,
|
|
|
|
|
c *terraform.ResourceConfig,
|
|
|
|
|
meta interface{}) (*terraform.InstanceDiff, error) {
|
|
|
|
|
func resourceConsulKeysDelete(d *schema.ResourceData, meta interface{}) error {
|
|
|
|
|
client := meta.(*consulapi.Client)
|
|
|
|
|
kv := client.KV()
|
|
|
|
|
|
|
|
|
|
// Determine the list of computed variables
|
|
|
|
|
var computed []string
|
|
|
|
|
keys, ok := flatmap.Expand(flatmap.Flatten(c.Config), "key").([]interface{})
|
|
|
|
|
if !ok {
|
|
|
|
|
goto AFTER
|
|
|
|
|
// Get the DC, error if not available.
|
|
|
|
|
var dc string
|
|
|
|
|
if v, ok := d.GetOk("datacenter"); ok {
|
|
|
|
|
dc = v.(string)
|
|
|
|
|
log.Printf("[DEBUG] Consul datacenter: %s", dc)
|
|
|
|
|
} else {
|
|
|
|
|
return fmt.Errorf("Missing datacenter configuration")
|
|
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
|
var token string
|
|
|
|
|
if v, ok := d.GetOk("token"); ok {
|
|
|
|
|
token = v.(string)
|
|
|
|
|
}
|
|
|
|
|
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()
|
|
|
|
|
// Setup the operations using the datacenter
|
|
|
|
|
wOpts := consulapi.WriteOptions{Datacenter: dc, Token: token}
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
// Extract the keys
|
|
|
|
|
keys := d.Get("keys").(*schema.Set).List()
|
|
|
|
|
for _, raw := range keys {
|
|
|
|
|
_, path, sub, err := parse_key(raw)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return s, err
|
|
|
|
|
return 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)
|
|
|
|
|
// Ignore if the key is non-managed
|
|
|
|
|
shouldDelete, ok := sub["delete"].(bool)
|
|
|
|
|
if !ok || !shouldDelete {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
log.Printf("[DEBUG] Deleting key '%s' in %s", path, dc)
|
|
|
|
|
if _, err := kv.Delete(path, &wOpts); err != nil {
|
|
|
|
|
return fmt.Errorf("Failed to delete Consul key '%s': %v", path, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return s, nil
|
|
|
|
|
|
|
|
|
|
// Clear the ID
|
|
|
|
|
d.SetId("")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// parse_key is used to parse a key into a name, path, config or error
|
|
|
|
|
@ -217,7 +266,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 {
|
|
|
|
|
|