terraform/builtin/providers/azurerm/resource_arm_redis_cache.go

456 lines
12 KiB
Go

package azurerm
import (
"fmt"
"log"
"net/http"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/arm/redis"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
"github.com/jen20/riviera/azure"
)
func resourceArmRedisCache() *schema.Resource {
return &schema.Resource{
Create: resourceArmRedisCacheCreate,
Read: resourceArmRedisCacheRead,
Update: resourceArmRedisCacheUpdate,
Delete: resourceArmRedisCacheDelete,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"location": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
StateFunc: azureRMNormalizeLocation,
},
"resource_group_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"capacity": {
Type: schema.TypeInt,
Required: true,
},
"family": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validateRedisFamily,
},
"sku_name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validateRedisSku,
},
"shard_count": {
Type: schema.TypeInt,
Optional: true,
},
"enable_non_ssl_port": {
Type: schema.TypeBool,
Default: false,
Optional: true,
},
"redis_configuration": {
Type: schema.TypeList,
Required: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"maxclients": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"maxmemory_delta": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"maxmemory_reserved": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"maxmemory_policy": {
Type: schema.TypeString,
Optional: true,
Default: "volatile-lru",
ValidateFunc: validateRedisMaxMemoryPolicy,
},
},
},
},
"hostname": {
Type: schema.TypeString,
Computed: true,
},
"port": {
Type: schema.TypeInt,
Computed: true,
},
"ssl_port": {
Type: schema.TypeInt,
Computed: true,
},
"primary_access_key": {
Type: schema.TypeString,
Computed: true,
},
"secondary_access_key": {
Type: schema.TypeString,
Computed: true,
},
"tags": tagsSchema(),
},
}
}
func resourceArmRedisCacheCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).redisClient
log.Printf("[INFO] preparing arguments for Azure ARM Redis Cache creation.")
name := d.Get("name").(string)
location := d.Get("location").(string)
resGroup := d.Get("resource_group_name").(string)
enableNonSSLPort := d.Get("enable_non_ssl_port").(bool)
capacity := int32(d.Get("capacity").(int))
family := redis.SkuFamily(d.Get("family").(string))
sku := redis.SkuName(d.Get("sku_name").(string))
tags := d.Get("tags").(map[string]interface{})
expandedTags := expandTags(tags)
parameters := redis.CreateParameters{
Name: &name,
Location: &location,
CreateProperties: &redis.CreateProperties{
EnableNonSslPort: &enableNonSSLPort,
Sku: &redis.Sku{
Capacity: &capacity,
Family: family,
Name: sku,
},
RedisConfiguration: expandRedisConfiguration(d),
},
Tags: expandedTags,
}
if v, ok := d.GetOk("shard_count"); ok {
shardCount := int32(v.(int))
parameters.ShardCount = &shardCount
}
_, err := client.Create(resGroup, name, parameters, make(chan struct{}))
if err != nil {
return err
}
read, err := client.Get(resGroup, name)
if err != nil {
return err
}
if read.ID == nil {
return fmt.Errorf("Cannot read Redis Instance %s (resource group %s) ID", name, resGroup)
}
log.Printf("[DEBUG] Waiting for Redis Instance (%s) to become available", d.Get("name"))
stateConf := &resource.StateChangeConf{
Pending: []string{"Updating", "Creating"},
Target: []string{"Succeeded"},
Refresh: redisStateRefreshFunc(client, resGroup, name),
Timeout: 60 * time.Minute,
MinTimeout: 15 * time.Second,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf("Error waiting for Redis Instance (%s) to become available: %s", d.Get("name"), err)
}
d.SetId(*read.ID)
return resourceArmRedisCacheRead(d, meta)
}
func resourceArmRedisCacheUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).redisClient
log.Printf("[INFO] preparing arguments for Azure ARM Redis Cache update.")
name := d.Get("name").(string)
resGroup := d.Get("resource_group_name").(string)
enableNonSSLPort := d.Get("enable_non_ssl_port").(bool)
capacity := int32(d.Get("capacity").(int))
family := redis.SkuFamily(d.Get("family").(string))
sku := redis.SkuName(d.Get("sku_name").(string))
tags := d.Get("tags").(map[string]interface{})
expandedTags := expandTags(tags)
parameters := redis.UpdateParameters{
UpdateProperties: &redis.UpdateProperties{
EnableNonSslPort: &enableNonSSLPort,
Sku: &redis.Sku{
Capacity: &capacity,
Family: family,
Name: sku,
},
Tags: expandedTags,
},
}
if v, ok := d.GetOk("shard_count"); ok {
if d.HasChange("shard_count") {
shardCount := int32(v.(int))
parameters.ShardCount = &shardCount
}
}
if d.HasChange("redis_configuration") {
redisConfiguration := expandRedisConfiguration(d)
parameters.RedisConfiguration = redisConfiguration
}
_, err := client.Update(resGroup, name, parameters)
if err != nil {
return err
}
read, err := client.Get(resGroup, name)
if err != nil {
return err
}
if read.ID == nil {
return fmt.Errorf("Cannot read Redis Instance %s (resource group %s) ID", name, resGroup)
}
log.Printf("[DEBUG] Waiting for Redis Instance (%s) to become available", d.Get("name"))
stateConf := &resource.StateChangeConf{
Pending: []string{"Updating", "Creating"},
Target: []string{"Succeeded"},
Refresh: redisStateRefreshFunc(client, resGroup, name),
Timeout: 60 * time.Minute,
MinTimeout: 15 * time.Second,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf("Error waiting for Redis Instance (%s) to become available: %s", d.Get("name"), err)
}
d.SetId(*read.ID)
return resourceArmRedisCacheRead(d, meta)
}
func resourceArmRedisCacheRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient).redisClient
id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}
resGroup := id.ResourceGroup
name := id.Path["Redis"]
resp, err := client.Get(resGroup, name)
// covers if the resource has been deleted outside of TF, but is still in the state
if resp.StatusCode == http.StatusNotFound {
d.SetId("")
return nil
}
if err != nil {
return fmt.Errorf("Error making Read request on Azure Redis Cache %s: %s", name, err)
}
keysResp, err := client.ListKeys(resGroup, name)
if err != nil {
return fmt.Errorf("Error making ListKeys request on Azure Redis Cache %s: %s", name, err)
}
d.Set("name", name)
d.Set("resource_group_name", resGroup)
d.Set("location", azureRMNormalizeLocation(*resp.Location))
d.Set("ssl_port", resp.SslPort)
d.Set("hostname", resp.HostName)
d.Set("port", resp.Port)
d.Set("enable_non_ssl_port", resp.EnableNonSslPort)
d.Set("capacity", resp.Sku.Capacity)
d.Set("family", resp.Sku.Family)
d.Set("sku_name", resp.Sku.Name)
if resp.ShardCount != nil {
d.Set("shard_count", resp.ShardCount)
}
redisConfiguration := flattenRedisConfiguration(resp.RedisConfiguration)
d.Set("redis_configuration", &redisConfiguration)
d.Set("primary_access_key", keysResp.PrimaryKey)
d.Set("secondary_access_key", keysResp.SecondaryKey)
flattenAndSetTags(d, resp.Tags)
return nil
}
func resourceArmRedisCacheDelete(d *schema.ResourceData, meta interface{}) error {
redisClient := meta.(*ArmClient).redisClient
id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}
resGroup := id.ResourceGroup
name := id.Path["Redis"]
resp, err := redisClient.Delete(resGroup, name, make(chan struct{}))
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("Error issuing Azure ARM delete request of Redis Cache Instance '%s': %s", name, err)
}
checkResp, _ := redisClient.Get(resGroup, name)
if checkResp.StatusCode != http.StatusNotFound {
return fmt.Errorf("Error issuing Azure ARM delete request of Redis Cache Instance '%s': it still exists after deletion", name)
}
return nil
}
func redisStateRefreshFunc(client redis.GroupClient, resourceGroupName string, sgName string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
res, err := client.Get(resourceGroupName, sgName)
if err != nil {
return nil, "", fmt.Errorf("Error issuing read request in redisStateRefreshFunc to Azure ARM for Redis Cache Instance '%s' (RG: '%s'): %s", sgName, resourceGroupName, err)
}
return res, *res.ProvisioningState, nil
}
}
func expandRedisConfiguration(d *schema.ResourceData) *map[string]*string {
configuration := d.Get("redis_configuration").([]interface{})
output := make(map[string]*string)
if configuration == nil {
return &output
}
// TODO: can we use this to remove the below? \/
//config := configuration[0].(map[string]interface{})
for _, v := range configuration {
config := v.(map[string]interface{})
maxClients := config["maxclients"].(string)
if maxClients != "" {
output["maxclients"] = azure.String(maxClients)
}
maxMemoryDelta := config["maxmemory_delta"].(string)
if maxMemoryDelta != "" {
output["maxmemory-delta"] = azure.String(maxMemoryDelta)
}
maxMemoryReserved := config["maxmemory_reserved"].(string)
if maxMemoryReserved != "" {
output["maxmemory-reserved"] = azure.String(maxMemoryReserved)
}
maxMemoryPolicy := config["maxmemory_policy"].(string)
if maxMemoryPolicy != "" {
output["maxmemory-policy"] = azure.String(maxMemoryPolicy)
}
}
return &output
}
func flattenRedisConfiguration(configuration *map[string]*string) map[string]*string {
redisConfiguration := make(map[string]*string, len(*configuration))
config := *configuration
redisConfiguration["maxclients"] = config["maxclients"]
redisConfiguration["maxmemory_delta"] = config["maxmemory-delta"]
redisConfiguration["maxmemory_reserved"] = config["maxmemory-reserved"]
redisConfiguration["maxmemory_policy"] = config["maxmemory-policy"]
return redisConfiguration
}
func validateRedisFamily(v interface{}, k string) (ws []string, errors []error) {
value := strings.ToLower(v.(string))
families := map[string]bool{
"c": true,
"p": true,
}
if !families[value] {
errors = append(errors, fmt.Errorf("Redis Family can only be C or P"))
}
return
}
func validateRedisMaxMemoryPolicy(v interface{}, k string) (ws []string, errors []error) {
value := strings.ToLower(v.(string))
families := map[string]bool{
"noeviction": true,
"allkeys-lru": true,
"volatile-lru": true,
"allkeys-random": true,
"volatile-random": true,
"volatile-ttl": true,
}
if !families[value] {
errors = append(errors, fmt.Errorf("Redis Max Memory Policy can only be 'noeviction' / 'allkeys-lru' / 'volatile-lru' / 'allkeys-random' / 'volatile-random' / 'volatile-ttl'"))
}
return
}
func validateRedisSku(v interface{}, k string) (ws []string, errors []error) {
value := strings.ToLower(v.(string))
skus := map[string]bool{
"basic": true,
"standard": true,
"premium": true,
}
if !skus[value] {
errors = append(errors, fmt.Errorf("Redis SKU can only be Basic, Standard or Premium"))
}
return
}