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