github.com/koding/terraform@v0.6.4-0.20170608090606-5d7e0339779d/builtin/providers/azurerm/resource_arm_redis_cache.go (about) 1 package azurerm 2 3 import ( 4 "fmt" 5 "log" 6 7 "net/http" 8 "strings" 9 "time" 10 11 "github.com/Azure/azure-sdk-for-go/arm/redis" 12 "github.com/hashicorp/terraform/helper/resource" 13 "github.com/hashicorp/terraform/helper/schema" 14 "github.com/jen20/riviera/azure" 15 ) 16 17 func resourceArmRedisCache() *schema.Resource { 18 return &schema.Resource{ 19 Create: resourceArmRedisCacheCreate, 20 Read: resourceArmRedisCacheRead, 21 Update: resourceArmRedisCacheUpdate, 22 Delete: resourceArmRedisCacheDelete, 23 24 Schema: map[string]*schema.Schema{ 25 "name": { 26 Type: schema.TypeString, 27 Required: true, 28 ForceNew: true, 29 }, 30 31 "location": { 32 Type: schema.TypeString, 33 Required: true, 34 ForceNew: true, 35 StateFunc: azureRMNormalizeLocation, 36 }, 37 38 "resource_group_name": { 39 Type: schema.TypeString, 40 Required: true, 41 ForceNew: true, 42 }, 43 44 "capacity": { 45 Type: schema.TypeInt, 46 Required: true, 47 }, 48 49 "family": { 50 Type: schema.TypeString, 51 Required: true, 52 ValidateFunc: validateRedisFamily, 53 DiffSuppressFunc: ignoreCaseDiffSuppressFunc, 54 }, 55 56 "sku_name": { 57 Type: schema.TypeString, 58 Required: true, 59 ValidateFunc: validateRedisSku, 60 DiffSuppressFunc: ignoreCaseDiffSuppressFunc, 61 }, 62 63 "shard_count": { 64 Type: schema.TypeInt, 65 Optional: true, 66 }, 67 68 "enable_non_ssl_port": { 69 Type: schema.TypeBool, 70 Default: false, 71 Optional: true, 72 }, 73 74 "redis_configuration": { 75 Type: schema.TypeList, 76 Required: true, 77 MaxItems: 1, 78 Elem: &schema.Resource{ 79 Schema: map[string]*schema.Schema{ 80 "maxclients": { 81 Type: schema.TypeString, 82 Optional: true, 83 Computed: true, 84 }, 85 86 "maxmemory_delta": { 87 Type: schema.TypeString, 88 Optional: true, 89 Computed: true, 90 }, 91 92 "maxmemory_reserved": { 93 Type: schema.TypeString, 94 Optional: true, 95 Computed: true, 96 }, 97 98 "maxmemory_policy": { 99 Type: schema.TypeString, 100 Optional: true, 101 Default: "volatile-lru", 102 ValidateFunc: validateRedisMaxMemoryPolicy, 103 }, 104 }, 105 }, 106 }, 107 108 "hostname": { 109 Type: schema.TypeString, 110 Computed: true, 111 }, 112 113 "port": { 114 Type: schema.TypeInt, 115 Computed: true, 116 }, 117 118 "ssl_port": { 119 Type: schema.TypeInt, 120 Computed: true, 121 }, 122 123 "primary_access_key": { 124 Type: schema.TypeString, 125 Computed: true, 126 }, 127 128 "secondary_access_key": { 129 Type: schema.TypeString, 130 Computed: true, 131 }, 132 133 "tags": tagsSchema(), 134 }, 135 } 136 } 137 138 func resourceArmRedisCacheCreate(d *schema.ResourceData, meta interface{}) error { 139 client := meta.(*ArmClient).redisClient 140 log.Printf("[INFO] preparing arguments for Azure ARM Redis Cache creation.") 141 142 name := d.Get("name").(string) 143 location := d.Get("location").(string) 144 resGroup := d.Get("resource_group_name").(string) 145 146 enableNonSSLPort := d.Get("enable_non_ssl_port").(bool) 147 148 capacity := int32(d.Get("capacity").(int)) 149 family := redis.SkuFamily(d.Get("family").(string)) 150 sku := redis.SkuName(d.Get("sku_name").(string)) 151 152 tags := d.Get("tags").(map[string]interface{}) 153 expandedTags := expandTags(tags) 154 155 parameters := redis.CreateParameters{ 156 Name: &name, 157 Location: &location, 158 CreateProperties: &redis.CreateProperties{ 159 EnableNonSslPort: &enableNonSSLPort, 160 Sku: &redis.Sku{ 161 Capacity: &capacity, 162 Family: family, 163 Name: sku, 164 }, 165 RedisConfiguration: expandRedisConfiguration(d), 166 }, 167 Tags: expandedTags, 168 } 169 170 if v, ok := d.GetOk("shard_count"); ok { 171 shardCount := int32(v.(int)) 172 parameters.ShardCount = &shardCount 173 } 174 175 _, error := client.Create(resGroup, name, parameters, make(chan struct{})) 176 err := <-error 177 if err != nil { 178 return err 179 } 180 181 read, err := client.Get(resGroup, name) 182 if err != nil { 183 return err 184 } 185 if read.ID == nil { 186 return fmt.Errorf("Cannot read Redis Instance %s (resource group %s) ID", name, resGroup) 187 } 188 189 log.Printf("[DEBUG] Waiting for Redis Instance (%s) to become available", d.Get("name")) 190 stateConf := &resource.StateChangeConf{ 191 Pending: []string{"Updating", "Creating"}, 192 Target: []string{"Succeeded"}, 193 Refresh: redisStateRefreshFunc(client, resGroup, name), 194 Timeout: 60 * time.Minute, 195 MinTimeout: 15 * time.Second, 196 } 197 if _, err := stateConf.WaitForState(); err != nil { 198 return fmt.Errorf("Error waiting for Redis Instance (%s) to become available: %s", d.Get("name"), err) 199 } 200 201 d.SetId(*read.ID) 202 203 return resourceArmRedisCacheRead(d, meta) 204 } 205 206 func resourceArmRedisCacheUpdate(d *schema.ResourceData, meta interface{}) error { 207 client := meta.(*ArmClient).redisClient 208 log.Printf("[INFO] preparing arguments for Azure ARM Redis Cache update.") 209 210 name := d.Get("name").(string) 211 resGroup := d.Get("resource_group_name").(string) 212 213 enableNonSSLPort := d.Get("enable_non_ssl_port").(bool) 214 215 capacity := int32(d.Get("capacity").(int)) 216 family := redis.SkuFamily(d.Get("family").(string)) 217 sku := redis.SkuName(d.Get("sku_name").(string)) 218 219 tags := d.Get("tags").(map[string]interface{}) 220 expandedTags := expandTags(tags) 221 222 parameters := redis.UpdateParameters{ 223 UpdateProperties: &redis.UpdateProperties{ 224 EnableNonSslPort: &enableNonSSLPort, 225 Sku: &redis.Sku{ 226 Capacity: &capacity, 227 Family: family, 228 Name: sku, 229 }, 230 Tags: expandedTags, 231 }, 232 } 233 234 if v, ok := d.GetOk("shard_count"); ok { 235 if d.HasChange("shard_count") { 236 shardCount := int32(v.(int)) 237 parameters.ShardCount = &shardCount 238 } 239 } 240 241 if d.HasChange("redis_configuration") { 242 redisConfiguration := expandRedisConfiguration(d) 243 parameters.RedisConfiguration = redisConfiguration 244 } 245 246 _, err := client.Update(resGroup, name, parameters) 247 if err != nil { 248 return err 249 } 250 251 read, err := client.Get(resGroup, name) 252 if err != nil { 253 return err 254 } 255 if read.ID == nil { 256 return fmt.Errorf("Cannot read Redis Instance %s (resource group %s) ID", name, resGroup) 257 } 258 259 log.Printf("[DEBUG] Waiting for Redis Instance (%s) to become available", d.Get("name")) 260 stateConf := &resource.StateChangeConf{ 261 Pending: []string{"Updating", "Creating"}, 262 Target: []string{"Succeeded"}, 263 Refresh: redisStateRefreshFunc(client, resGroup, name), 264 Timeout: 60 * time.Minute, 265 MinTimeout: 15 * time.Second, 266 } 267 if _, err := stateConf.WaitForState(); err != nil { 268 return fmt.Errorf("Error waiting for Redis Instance (%s) to become available: %s", d.Get("name"), err) 269 } 270 271 d.SetId(*read.ID) 272 273 return resourceArmRedisCacheRead(d, meta) 274 } 275 276 func resourceArmRedisCacheRead(d *schema.ResourceData, meta interface{}) error { 277 client := meta.(*ArmClient).redisClient 278 279 id, err := parseAzureResourceID(d.Id()) 280 if err != nil { 281 return err 282 } 283 resGroup := id.ResourceGroup 284 name := id.Path["Redis"] 285 286 resp, err := client.Get(resGroup, name) 287 288 // covers if the resource has been deleted outside of TF, but is still in the state 289 if resp.StatusCode == http.StatusNotFound { 290 d.SetId("") 291 return nil 292 } 293 294 if err != nil { 295 return fmt.Errorf("Error making Read request on Azure Redis Cache %s: %s", name, err) 296 } 297 298 keysResp, err := client.ListKeys(resGroup, name) 299 if err != nil { 300 return fmt.Errorf("Error making ListKeys request on Azure Redis Cache %s: %s", name, err) 301 } 302 303 d.Set("name", name) 304 d.Set("resource_group_name", resGroup) 305 d.Set("location", azureRMNormalizeLocation(*resp.Location)) 306 d.Set("ssl_port", resp.SslPort) 307 d.Set("hostname", resp.HostName) 308 d.Set("port", resp.Port) 309 d.Set("enable_non_ssl_port", resp.EnableNonSslPort) 310 d.Set("capacity", resp.Sku.Capacity) 311 d.Set("family", resp.Sku.Family) 312 d.Set("sku_name", resp.Sku.Name) 313 314 if resp.ShardCount != nil { 315 d.Set("shard_count", resp.ShardCount) 316 } 317 318 redisConfiguration := flattenRedisConfiguration(resp.RedisConfiguration) 319 d.Set("redis_configuration", &redisConfiguration) 320 321 d.Set("primary_access_key", keysResp.PrimaryKey) 322 d.Set("secondary_access_key", keysResp.SecondaryKey) 323 324 flattenAndSetTags(d, resp.Tags) 325 326 return nil 327 } 328 329 func resourceArmRedisCacheDelete(d *schema.ResourceData, meta interface{}) error { 330 redisClient := meta.(*ArmClient).redisClient 331 332 id, err := parseAzureResourceID(d.Id()) 333 if err != nil { 334 return err 335 } 336 resGroup := id.ResourceGroup 337 name := id.Path["Redis"] 338 339 deleteResp, error := redisClient.Delete(resGroup, name, make(chan struct{})) 340 resp := <-deleteResp 341 err = <-error 342 343 if resp.StatusCode != http.StatusOK { 344 return fmt.Errorf("Error issuing Azure ARM delete request of Redis Cache Instance '%s': %s", name, err) 345 } 346 347 checkResp, _ := redisClient.Get(resGroup, name) 348 if checkResp.StatusCode != http.StatusNotFound { 349 return fmt.Errorf("Error issuing Azure ARM delete request of Redis Cache Instance '%s': it still exists after deletion", name) 350 } 351 352 return nil 353 } 354 355 func redisStateRefreshFunc(client redis.GroupClient, resourceGroupName string, sgName string) resource.StateRefreshFunc { 356 return func() (interface{}, string, error) { 357 res, err := client.Get(resourceGroupName, sgName) 358 if err != nil { 359 return nil, "", fmt.Errorf("Error issuing read request in redisStateRefreshFunc to Azure ARM for Redis Cache Instance '%s' (RG: '%s'): %s", sgName, resourceGroupName, err) 360 } 361 362 return res, *res.ProvisioningState, nil 363 } 364 } 365 366 func expandRedisConfiguration(d *schema.ResourceData) *map[string]*string { 367 configuration := d.Get("redis_configuration").([]interface{}) 368 369 output := make(map[string]*string) 370 371 if configuration == nil { 372 return &output 373 } 374 375 // TODO: can we use this to remove the below? \/ 376 //config := configuration[0].(map[string]interface{}) 377 378 for _, v := range configuration { 379 config := v.(map[string]interface{}) 380 381 maxClients := config["maxclients"].(string) 382 if maxClients != "" { 383 output["maxclients"] = azure.String(maxClients) 384 } 385 386 maxMemoryDelta := config["maxmemory_delta"].(string) 387 if maxMemoryDelta != "" { 388 output["maxmemory-delta"] = azure.String(maxMemoryDelta) 389 } 390 391 maxMemoryReserved := config["maxmemory_reserved"].(string) 392 if maxMemoryReserved != "" { 393 output["maxmemory-reserved"] = azure.String(maxMemoryReserved) 394 } 395 396 maxMemoryPolicy := config["maxmemory_policy"].(string) 397 if maxMemoryPolicy != "" { 398 output["maxmemory-policy"] = azure.String(maxMemoryPolicy) 399 } 400 } 401 402 return &output 403 } 404 405 func flattenRedisConfiguration(configuration *map[string]*string) map[string]*string { 406 redisConfiguration := make(map[string]*string, len(*configuration)) 407 config := *configuration 408 409 redisConfiguration["maxclients"] = config["maxclients"] 410 redisConfiguration["maxmemory_delta"] = config["maxmemory-delta"] 411 redisConfiguration["maxmemory_reserved"] = config["maxmemory-reserved"] 412 redisConfiguration["maxmemory_policy"] = config["maxmemory-policy"] 413 414 return redisConfiguration 415 } 416 417 func validateRedisFamily(v interface{}, k string) (ws []string, errors []error) { 418 value := strings.ToLower(v.(string)) 419 families := map[string]bool{ 420 "c": true, 421 "p": true, 422 } 423 424 if !families[value] { 425 errors = append(errors, fmt.Errorf("Redis Family can only be C or P")) 426 } 427 return 428 } 429 430 func validateRedisMaxMemoryPolicy(v interface{}, k string) (ws []string, errors []error) { 431 value := strings.ToLower(v.(string)) 432 families := map[string]bool{ 433 "noeviction": true, 434 "allkeys-lru": true, 435 "volatile-lru": true, 436 "allkeys-random": true, 437 "volatile-random": true, 438 "volatile-ttl": true, 439 } 440 441 if !families[value] { 442 errors = append(errors, fmt.Errorf("Redis Max Memory Policy can only be 'noeviction' / 'allkeys-lru' / 'volatile-lru' / 'allkeys-random' / 'volatile-random' / 'volatile-ttl'")) 443 } 444 445 return 446 } 447 448 func validateRedisSku(v interface{}, k string) (ws []string, errors []error) { 449 value := strings.ToLower(v.(string)) 450 skus := map[string]bool{ 451 "basic": true, 452 "standard": true, 453 "premium": true, 454 } 455 456 if !skus[value] { 457 errors = append(errors, fmt.Errorf("Redis SKU can only be Basic, Standard or Premium")) 458 } 459 return 460 }