terraform/builtin/providers/consul/resource_consul_key_prefix.go

222 lines
5.5 KiB
Go

package consul
import (
"fmt"
consulapi "github.com/hashicorp/consul/api"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceConsulKeyPrefix() *schema.Resource {
return &schema.Resource{
Create: resourceConsulKeyPrefixCreate,
Update: resourceConsulKeyPrefixUpdate,
Read: resourceConsulKeyPrefixRead,
Delete: resourceConsulKeyPrefixDelete,
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,
},
"path_prefix": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"subkeys": &schema.Schema{
Type: schema.TypeMap,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
}
}
func resourceConsulKeyPrefixCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*consulapi.Client)
kv := client.KV()
token := d.Get("token").(string)
dc, err := getDC(d, client)
if err != nil {
return err
}
keyClient := newKeyClient(kv, dc, token)
pathPrefix := d.Get("path_prefix").(string)
subKeys := map[string]string{}
for k, vI := range d.Get("subkeys").(map[string]interface{}) {
subKeys[k] = vI.(string)
}
// To reduce the impact of mistakes, we will only "create" a prefix that
// is currently empty. This way we are less likely to accidentally
// conflict with other mechanisms managing the same prefix.
currentSubKeys, err := keyClient.GetUnderPrefix(pathPrefix)
if err != nil {
return err
}
if len(currentSubKeys) > 0 {
return fmt.Errorf(
"%d keys already exist under %s; delete them before managing this prefix with Terraform",
len(currentSubKeys), pathPrefix,
)
}
// Ideally we'd use d.Partial(true) here so we can correctly record
// a partial write, but that mechanism doesn't work for individual map
// members, so we record that the resource was created before we
// do anything and that way we can recover from errors by doing an
// Update on subsequent runs, rather than re-attempting Create with
// some keys possibly already present.
d.SetId(pathPrefix)
// Store the datacenter on this resource, which can be helpful for reference
// in case it was read from the provider
d.Set("datacenter", dc)
// Now we can just write in all the initial values, since we can expect
// that nothing should need deleting yet, as long as there isn't some
// other program racing us to write values... which we'll catch on a
// subsequent Read.
for k, v := range subKeys {
fullPath := pathPrefix + k
err := keyClient.Put(fullPath, v)
if err != nil {
return fmt.Errorf("error while writing %s: %s", fullPath, err)
}
}
return nil
}
func resourceConsulKeyPrefixUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*consulapi.Client)
kv := client.KV()
token := d.Get("token").(string)
dc, err := getDC(d, client)
if err != nil {
return err
}
keyClient := newKeyClient(kv, dc, token)
pathPrefix := d.Id()
if d.HasChange("subkeys") {
o, n := d.GetChange("subkeys")
if o == nil {
o = map[string]interface{}{}
}
if n == nil {
n = map[string]interface{}{}
}
om := o.(map[string]interface{})
nm := n.(map[string]interface{})
// First we'll write all of the stuff in the "new map" nm,
// and then we'll delete any keys that appear in the "old map" om
// and do not also appear in nm. This ordering means that if a subkey
// name is changed we will briefly have both the old and new names in
// Consul, as opposed to briefly having neither.
// Again, we'd ideally use d.Partial(true) here but it doesn't work
// for maps and so we'll just rely on a subsequent Read to tidy up
// after a partial write.
// Write new and changed keys
for k, vI := range nm {
v := vI.(string)
fullPath := pathPrefix + k
err := keyClient.Put(fullPath, v)
if err != nil {
return fmt.Errorf("error while writing %s: %s", fullPath, err)
}
}
// Remove deleted keys
for k, _ := range om {
if _, exists := nm[k]; exists {
continue
}
fullPath := pathPrefix + k
err := keyClient.Delete(fullPath)
if err != nil {
return fmt.Errorf("error while deleting %s: %s", fullPath, err)
}
}
}
// Store the datacenter on this resource, which can be helpful for reference
// in case it was read from the provider
d.Set("datacenter", dc)
return nil
}
func resourceConsulKeyPrefixRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*consulapi.Client)
kv := client.KV()
token := d.Get("token").(string)
dc, err := getDC(d, client)
if err != nil {
return err
}
keyClient := newKeyClient(kv, dc, token)
pathPrefix := d.Id()
subKeys, err := keyClient.GetUnderPrefix(pathPrefix)
if err != nil {
return err
}
d.Set("subkeys", subKeys)
// Store the datacenter on this resource, which can be helpful for reference
// in case it was read from the provider
d.Set("datacenter", dc)
return nil
}
func resourceConsulKeyPrefixDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*consulapi.Client)
kv := client.KV()
token := d.Get("token").(string)
dc, err := getDC(d, client)
if err != nil {
return err
}
keyClient := newKeyClient(kv, dc, token)
pathPrefix := d.Id()
// Delete everything under our prefix, since the entire set of keys under
// the given prefix is considered to be managed exclusively by Terraform.
err = keyClient.DeleteUnderPrefix(pathPrefix)
if err != nil {
return err
}
d.SetId("")
return nil
}