222 lines
5.5 KiB
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
|
||
|
}
|