github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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 }