github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/consul/resource_consul_key_prefix.go (about) 1 package consul 2 3 import ( 4 "fmt" 5 6 consulapi "github.com/hashicorp/consul/api" 7 "github.com/hashicorp/terraform/helper/schema" 8 ) 9 10 func resourceConsulKeyPrefix() *schema.Resource { 11 return &schema.Resource{ 12 Create: resourceConsulKeyPrefixCreate, 13 Update: resourceConsulKeyPrefixUpdate, 14 Read: resourceConsulKeyPrefixRead, 15 Delete: resourceConsulKeyPrefixDelete, 16 17 Schema: map[string]*schema.Schema{ 18 "datacenter": &schema.Schema{ 19 Type: schema.TypeString, 20 Optional: true, 21 Computed: true, 22 ForceNew: true, 23 }, 24 25 "token": &schema.Schema{ 26 Type: schema.TypeString, 27 Optional: true, 28 }, 29 30 "path_prefix": &schema.Schema{ 31 Type: schema.TypeString, 32 Required: true, 33 ForceNew: true, 34 }, 35 36 "subkeys": &schema.Schema{ 37 Type: schema.TypeMap, 38 Required: true, 39 Elem: &schema.Schema{ 40 Type: schema.TypeString, 41 }, 42 }, 43 }, 44 } 45 } 46 47 func resourceConsulKeyPrefixCreate(d *schema.ResourceData, meta interface{}) error { 48 client := meta.(*consulapi.Client) 49 kv := client.KV() 50 token := d.Get("token").(string) 51 dc, err := getDC(d, client) 52 if err != nil { 53 return err 54 } 55 56 keyClient := newKeyClient(kv, dc, token) 57 58 pathPrefix := d.Get("path_prefix").(string) 59 subKeys := map[string]string{} 60 for k, vI := range d.Get("subkeys").(map[string]interface{}) { 61 subKeys[k] = vI.(string) 62 } 63 64 // To reduce the impact of mistakes, we will only "create" a prefix that 65 // is currently empty. This way we are less likely to accidentally 66 // conflict with other mechanisms managing the same prefix. 67 currentSubKeys, err := keyClient.GetUnderPrefix(pathPrefix) 68 if err != nil { 69 return err 70 } 71 if len(currentSubKeys) > 0 { 72 return fmt.Errorf( 73 "%d keys already exist under %s; delete them before managing this prefix with Terraform", 74 len(currentSubKeys), pathPrefix, 75 ) 76 } 77 78 // Ideally we'd use d.Partial(true) here so we can correctly record 79 // a partial write, but that mechanism doesn't work for individual map 80 // members, so we record that the resource was created before we 81 // do anything and that way we can recover from errors by doing an 82 // Update on subsequent runs, rather than re-attempting Create with 83 // some keys possibly already present. 84 d.SetId(pathPrefix) 85 86 // Store the datacenter on this resource, which can be helpful for reference 87 // in case it was read from the provider 88 d.Set("datacenter", dc) 89 90 // Now we can just write in all the initial values, since we can expect 91 // that nothing should need deleting yet, as long as there isn't some 92 // other program racing us to write values... which we'll catch on a 93 // subsequent Read. 94 for k, v := range subKeys { 95 fullPath := pathPrefix + k 96 err := keyClient.Put(fullPath, v) 97 if err != nil { 98 return fmt.Errorf("error while writing %s: %s", fullPath, err) 99 } 100 } 101 102 return nil 103 } 104 105 func resourceConsulKeyPrefixUpdate(d *schema.ResourceData, meta interface{}) error { 106 client := meta.(*consulapi.Client) 107 kv := client.KV() 108 token := d.Get("token").(string) 109 dc, err := getDC(d, client) 110 if err != nil { 111 return err 112 } 113 114 keyClient := newKeyClient(kv, dc, token) 115 116 pathPrefix := d.Id() 117 118 if d.HasChange("subkeys") { 119 o, n := d.GetChange("subkeys") 120 if o == nil { 121 o = map[string]interface{}{} 122 } 123 if n == nil { 124 n = map[string]interface{}{} 125 } 126 127 om := o.(map[string]interface{}) 128 nm := n.(map[string]interface{}) 129 130 // First we'll write all of the stuff in the "new map" nm, 131 // and then we'll delete any keys that appear in the "old map" om 132 // and do not also appear in nm. This ordering means that if a subkey 133 // name is changed we will briefly have both the old and new names in 134 // Consul, as opposed to briefly having neither. 135 136 // Again, we'd ideally use d.Partial(true) here but it doesn't work 137 // for maps and so we'll just rely on a subsequent Read to tidy up 138 // after a partial write. 139 140 // Write new and changed keys 141 for k, vI := range nm { 142 v := vI.(string) 143 fullPath := pathPrefix + k 144 err := keyClient.Put(fullPath, v) 145 if err != nil { 146 return fmt.Errorf("error while writing %s: %s", fullPath, err) 147 } 148 } 149 150 // Remove deleted keys 151 for k, _ := range om { 152 if _, exists := nm[k]; exists { 153 continue 154 } 155 fullPath := pathPrefix + k 156 err := keyClient.Delete(fullPath) 157 if err != nil { 158 return fmt.Errorf("error while deleting %s: %s", fullPath, err) 159 } 160 } 161 162 } 163 164 // Store the datacenter on this resource, which can be helpful for reference 165 // in case it was read from the provider 166 d.Set("datacenter", dc) 167 168 return nil 169 } 170 171 func resourceConsulKeyPrefixRead(d *schema.ResourceData, meta interface{}) error { 172 client := meta.(*consulapi.Client) 173 kv := client.KV() 174 token := d.Get("token").(string) 175 dc, err := getDC(d, client) 176 if err != nil { 177 return err 178 } 179 180 keyClient := newKeyClient(kv, dc, token) 181 182 pathPrefix := d.Id() 183 184 subKeys, err := keyClient.GetUnderPrefix(pathPrefix) 185 if err != nil { 186 return err 187 } 188 189 d.Set("subkeys", subKeys) 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 nil 196 } 197 198 func resourceConsulKeyPrefixDelete(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 pathPrefix := d.Id() 210 211 // Delete everything under our prefix, since the entire set of keys under 212 // the given prefix is considered to be managed exclusively by Terraform. 213 err = keyClient.DeleteUnderPrefix(pathPrefix) 214 if err != nil { 215 return err 216 } 217 218 d.SetId("") 219 220 return nil 221 }