github.com/gabrielperezs/terraform@v0.7.0-rc2.0.20160715084931-f7da2612946f/builtin/providers/consul/resource_consul_keys.go (about)

     1  package consul
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  
     7  	consulapi "github.com/hashicorp/consul/api"
     8  	"github.com/hashicorp/terraform/helper/schema"
     9  )
    10  
    11  func resourceConsulKeys() *schema.Resource {
    12  	return &schema.Resource{
    13  		Create: resourceConsulKeysCreate,
    14  		Update: resourceConsulKeysUpdate,
    15  		Read:   resourceConsulKeysRead,
    16  		Delete: resourceConsulKeysDelete,
    17  
    18  		SchemaVersion: 1,
    19  		MigrateState:  resourceConsulKeysMigrateState,
    20  
    21  		Schema: map[string]*schema.Schema{
    22  			"datacenter": &schema.Schema{
    23  				Type:     schema.TypeString,
    24  				Optional: true,
    25  				Computed: true,
    26  				ForceNew: true,
    27  			},
    28  
    29  			"token": &schema.Schema{
    30  				Type:     schema.TypeString,
    31  				Optional: true,
    32  			},
    33  
    34  			"key": &schema.Schema{
    35  				Type:     schema.TypeSet,
    36  				Optional: true,
    37  				Elem: &schema.Resource{
    38  					Schema: map[string]*schema.Schema{
    39  						"name": &schema.Schema{
    40  							Type:     schema.TypeString,
    41  							Required: true,
    42  						},
    43  
    44  						"path": &schema.Schema{
    45  							Type:     schema.TypeString,
    46  							Required: true,
    47  						},
    48  
    49  						"value": &schema.Schema{
    50  							Type:     schema.TypeString,
    51  							Optional: true,
    52  							Computed: true,
    53  						},
    54  
    55  						"default": &schema.Schema{
    56  							Type:     schema.TypeString,
    57  							Optional: true,
    58  						},
    59  
    60  						"delete": &schema.Schema{
    61  							Type:     schema.TypeBool,
    62  							Optional: true,
    63  							Default:  false,
    64  						},
    65  					},
    66  				},
    67  			},
    68  
    69  			"var": &schema.Schema{
    70  				Type:     schema.TypeMap,
    71  				Computed: true,
    72  			},
    73  		},
    74  	}
    75  }
    76  
    77  func resourceConsulKeysCreate(d *schema.ResourceData, meta interface{}) error {
    78  	client := meta.(*consulapi.Client)
    79  	kv := client.KV()
    80  	token := d.Get("token").(string)
    81  	dc, err := getDC(d, client)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	keyClient := newKeyClient(kv, dc, token)
    87  
    88  	keys := d.Get("key").(*schema.Set).List()
    89  	for _, raw := range keys {
    90  		_, path, sub, err := parseKey(raw)
    91  		if err != nil {
    92  			return err
    93  		}
    94  
    95  		value := sub["value"].(string)
    96  		if value == "" {
    97  			continue
    98  		}
    99  
   100  		if err := keyClient.Put(path, value); err != nil {
   101  			return err
   102  		}
   103  	}
   104  
   105  	// The ID doesn't matter, since we use provider config, datacenter,
   106  	// and key paths to address consul properly. So we just need to fill it in
   107  	// with some value to indicate the resource has been created.
   108  	d.SetId("consul")
   109  
   110  	return resourceConsulKeysRead(d, meta)
   111  }
   112  
   113  func resourceConsulKeysUpdate(d *schema.ResourceData, meta interface{}) error {
   114  	client := meta.(*consulapi.Client)
   115  	kv := client.KV()
   116  	token := d.Get("token").(string)
   117  	dc, err := getDC(d, client)
   118  	if err != nil {
   119  		return err
   120  	}
   121  
   122  	keyClient := newKeyClient(kv, dc, token)
   123  
   124  	if d.HasChange("key") {
   125  		o, n := d.GetChange("key")
   126  		if o == nil {
   127  			o = new(schema.Set)
   128  		}
   129  		if n == nil {
   130  			n = new(schema.Set)
   131  		}
   132  
   133  		os := o.(*schema.Set)
   134  		ns := n.(*schema.Set)
   135  
   136  		remove := os.Difference(ns).List()
   137  		add := ns.Difference(os).List()
   138  
   139  		// We'll keep track of what keys we add so that if a key is
   140  		// in both the "remove" and "add" sets -- which will happen if
   141  		// its value is changed in-place -- we will avoid writing the
   142  		// value and then immediately removing it.
   143  		addedPaths := make(map[string]bool)
   144  
   145  		// We add before we remove because then it's possible to change
   146  		// a key name (which will result in both an add and a remove)
   147  		// without very temporarily having *neither* value in the store.
   148  		// Instead, both will briefly be present, which should be less
   149  		// disruptive in most cases.
   150  		for _, raw := range add {
   151  			_, path, sub, err := parseKey(raw)
   152  			if err != nil {
   153  				return err
   154  			}
   155  
   156  			value := sub["value"].(string)
   157  			if value == "" {
   158  				continue
   159  			}
   160  
   161  			if err := keyClient.Put(path, value); err != nil {
   162  				return err
   163  			}
   164  			addedPaths[path] = true
   165  		}
   166  
   167  		for _, raw := range remove {
   168  			_, path, sub, err := parseKey(raw)
   169  			if err != nil {
   170  				return err
   171  			}
   172  
   173  			// Don't delete something we've just added.
   174  			// (See explanation at the declaration of this variable above.)
   175  			if addedPaths[path] {
   176  				continue
   177  			}
   178  
   179  			shouldDelete, ok := sub["delete"].(bool)
   180  			if !ok || !shouldDelete {
   181  				continue
   182  			}
   183  
   184  			if err := keyClient.Delete(path); err != nil {
   185  				return err
   186  			}
   187  		}
   188  	}
   189  
   190  	// Store the datacenter on this resource, which can be helpful for reference
   191  	// in case it was read from the provider
   192  	d.Set("datacenter", dc)
   193  
   194  	return resourceConsulKeysRead(d, meta)
   195  }
   196  
   197  func resourceConsulKeysRead(d *schema.ResourceData, meta interface{}) error {
   198  	client := meta.(*consulapi.Client)
   199  	kv := client.KV()
   200  	token := d.Get("token").(string)
   201  	dc, err := getDC(d, client)
   202  	if err != nil {
   203  		return err
   204  	}
   205  
   206  	keyClient := newKeyClient(kv, dc, token)
   207  
   208  	vars := make(map[string]string)
   209  
   210  	keys := d.Get("key").(*schema.Set).List()
   211  	for _, raw := range keys {
   212  		key, path, sub, err := parseKey(raw)
   213  		if err != nil {
   214  			return err
   215  		}
   216  
   217  		value, err := keyClient.Get(path)
   218  		if err != nil {
   219  			return err
   220  		}
   221  
   222  		value = attributeValue(sub, value)
   223  		vars[key] = value
   224  
   225  		// If there is already a "value" attribute present for this key
   226  		// then it was created as a "write" block. We need to update the
   227  		// given value within the block itself so that Terraform can detect
   228  		// when the Consul-stored value has drifted from what was most
   229  		// recently written by Terraform.
   230  		// We don't do this for "read" blocks; that causes confusing diffs
   231  		// because "value" should not be set for read-only key blocks.
   232  		if oldValue := sub["value"]; oldValue != "" {
   233  			sub["value"] = value
   234  		}
   235  	}
   236  
   237  	if err := d.Set("var", vars); err != nil {
   238  		return err
   239  	}
   240  	if err := d.Set("key", keys); err != nil {
   241  		return err
   242  	}
   243  
   244  	// Store the datacenter on this resource, which can be helpful for reference
   245  	// in case it was read from the provider
   246  	d.Set("datacenter", dc)
   247  
   248  	return nil
   249  }
   250  
   251  func resourceConsulKeysDelete(d *schema.ResourceData, meta interface{}) error {
   252  	client := meta.(*consulapi.Client)
   253  	kv := client.KV()
   254  	token := d.Get("token").(string)
   255  	dc, err := getDC(d, client)
   256  	if err != nil {
   257  		return err
   258  	}
   259  
   260  	keyClient := newKeyClient(kv, dc, token)
   261  
   262  	// Clean up any keys that we're explicitly managing
   263  	keys := d.Get("key").(*schema.Set).List()
   264  	for _, raw := range keys {
   265  		_, path, sub, err := parseKey(raw)
   266  		if err != nil {
   267  			return err
   268  		}
   269  
   270  		// Skip if the key is non-managed
   271  		shouldDelete, ok := sub["delete"].(bool)
   272  		if !ok || !shouldDelete {
   273  			continue
   274  		}
   275  
   276  		if err := keyClient.Delete(path); err != nil {
   277  			return err
   278  		}
   279  	}
   280  
   281  	// Clear the ID
   282  	d.SetId("")
   283  	return nil
   284  }
   285  
   286  // parseKey is used to parse a key into a name, path, config or error
   287  func parseKey(raw interface{}) (string, string, map[string]interface{}, error) {
   288  	sub, ok := raw.(map[string]interface{})
   289  	if !ok {
   290  		return "", "", nil, fmt.Errorf("Failed to unroll: %#v", raw)
   291  	}
   292  
   293  	key, ok := sub["name"].(string)
   294  	if !ok {
   295  		return "", "", nil, fmt.Errorf("Failed to expand key '%#v'", sub)
   296  	}
   297  
   298  	path, ok := sub["path"].(string)
   299  	if !ok {
   300  		return "", "", nil, fmt.Errorf("Failed to get path for key '%s'", key)
   301  	}
   302  	return key, path, sub, nil
   303  }
   304  
   305  // attributeValue determines the value for a key, potentially
   306  // using a default value if provided.
   307  func attributeValue(sub map[string]interface{}, readValue string) string {
   308  	// Use the value if given
   309  	if readValue != "" {
   310  		return readValue
   311  	}
   312  
   313  	// Use a default if given
   314  	if raw, ok := sub["default"]; ok {
   315  		switch def := raw.(type) {
   316  		case string:
   317  			return def
   318  		case bool:
   319  			return strconv.FormatBool(def)
   320  		}
   321  	}
   322  
   323  	// No value
   324  	return ""
   325  }
   326  
   327  // getDC is used to get the datacenter of the local agent
   328  func getDC(d *schema.ResourceData, client *consulapi.Client) (string, error) {
   329  	if v, ok := d.GetOk("datacenter"); ok {
   330  		return v.(string), nil
   331  	}
   332  	info, err := client.Agent().Self()
   333  	if err != nil {
   334  		return "", fmt.Errorf("Failed to get datacenter from Consul agent: %v", err)
   335  	}
   336  	return info["Config"]["Datacenter"].(string), nil
   337  }