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