453 lines
11 KiB
Go
453 lines
11 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, 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 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)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("Error making Read request on Azure Redis Cache %s: %s", name, err)
|
||
|
}
|
||
|
if resp.StatusCode == http.StatusNotFound {
|
||
|
d.SetId("")
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
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("host_name", 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.Client, 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
|
||
|
}
|