diff --git a/azurerm/resource_arm_redis_cache.go b/azurerm/resource_arm_redis_cache.go index 93505ec91ffd..e643e33eaf26 100644 --- a/azurerm/resource_arm_redis_cache.go +++ b/azurerm/resource_arm_redis_cache.go @@ -3,14 +3,15 @@ package azurerm import ( "fmt" "log" - "net/http" + "strconv" "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/hashicorp/terraform/helper/validation" "github.com/jen20/riviera/azure" ) @@ -54,9 +55,13 @@ func resourceArmRedisCache() *schema.Resource { }, "sku_name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validateRedisSku, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(redis.Basic), + string(redis.Standard), + string(redis.Premium), + }, true), DiffSuppressFunc: ignoreCaseDiffSuppressFunc, }, @@ -78,19 +83,19 @@ func resourceArmRedisCache() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "maxclients": { - Type: schema.TypeString, + Type: schema.TypeInt, Optional: true, Computed: true, }, "maxmemory_delta": { - Type: schema.TypeString, + Type: schema.TypeInt, Optional: true, Computed: true, }, "maxmemory_reserved": { - Type: schema.TypeString, + Type: schema.TypeInt, Optional: true, Computed: true, }, @@ -101,6 +106,23 @@ func resourceArmRedisCache() *schema.Resource { Default: "volatile-lru", ValidateFunc: validateRedisMaxMemoryPolicy, }, + "rdb_backup_enabled": { + Type: schema.TypeBool, + Optional: true, + }, + "rdb_backup_frequency": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validateRedisBackupFrequency, + }, + "rdb_backup_max_snapshot_count": { + Type: schema.TypeInt, + Optional: true, + }, + "rdb_storage_connection_string": { + Type: schema.TypeString, + Optional: true, + }, }, }, }, @@ -364,39 +386,45 @@ func redisStateRefreshFunc(client redis.GroupClient, resourceGroupName string, s } func expandRedisConfiguration(d *schema.ResourceData) *map[string]*string { - configuration := d.Get("redis_configuration").([]interface{}) - output := make(map[string]*string) - if configuration == nil { - return &output + if v, ok := d.GetOk("redis_configuration.0.maxclients"); ok { + clients := strconv.Itoa(v.(int)) + output["maxclients"] = azure.String(clients) } - // TODO: can we use this to remove the below? \/ - //config := configuration[0].(map[string]interface{}) + if v, ok := d.GetOk("redis_configuration.0.maxmemory_delta"); ok { + delta := strconv.Itoa(v.(int)) + output["maxmemory-delta"] = azure.String(delta) + } - for _, v := range configuration { - config := v.(map[string]interface{}) + if v, ok := d.GetOk("redis_configuration.0.maxmemory_reserved"); ok { + delta := strconv.Itoa(v.(int)) + output["maxmemory-reserved"] = azure.String(delta) + } - maxClients := config["maxclients"].(string) - if maxClients != "" { - output["maxclients"] = azure.String(maxClients) - } + if v, ok := d.GetOk("redis_configuration.0.maxmemory_policy"); ok { + output["maxmemory-policy"] = azure.String(v.(string)) + } - maxMemoryDelta := config["maxmemory_delta"].(string) - if maxMemoryDelta != "" { - output["maxmemory-delta"] = azure.String(maxMemoryDelta) - } + // Backup + if v, ok := d.GetOk("redis_configuration.0.rdb_backup_enabled"); ok { + delta := strconv.FormatBool(v.(bool)) + output["rdb-backup-enabled"] = azure.String(delta) + } - maxMemoryReserved := config["maxmemory_reserved"].(string) - if maxMemoryReserved != "" { - output["maxmemory-reserved"] = azure.String(maxMemoryReserved) - } + if v, ok := d.GetOk("redis_configuration.0.rdb_backup_frequency"); ok { + delta := strconv.Itoa(v.(int)) + output["rdb-backup-frequency"] = azure.String(delta) + } - maxMemoryPolicy := config["maxmemory_policy"].(string) - if maxMemoryPolicy != "" { - output["maxmemory-policy"] = azure.String(maxMemoryPolicy) - } + if v, ok := d.GetOk("redis_configuration.0.rdb_backup_max_snapshot_count"); ok { + delta := strconv.Itoa(v.(int)) + output["rdb-backup-max-snapshot-count"] = azure.String(delta) + } + + if v, ok := d.GetOk("redis_configuration.0.rdb_storage_connection_string"); ok { + output["rdb-storage-connection-string"] = azure.String(v.(string)) } return &output @@ -411,6 +439,11 @@ func flattenRedisConfiguration(configuration *map[string]*string) map[string]*st redisConfiguration["maxmemory_reserved"] = config["maxmemory-reserved"] redisConfiguration["maxmemory_policy"] = config["maxmemory-policy"] + redisConfiguration["rdb_backup_enabled"] = config["rdb-backup-enabled"] + redisConfiguration["rdb_backup_frequency"] = config["rdb-backup-frequency"] + redisConfiguration["rdb_backup_max_snapshot_count"] = config["rdb-backup-max-snapshot-count"] + redisConfiguration["rdb_storage_connection_string"] = config["rdb-storage-connection-string"] + return redisConfiguration } @@ -445,16 +478,20 @@ func validateRedisMaxMemoryPolicy(v interface{}, k string) (ws []string, errors 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, +func validateRedisBackupFrequency(v interface{}, k string) (ws []string, errors []error) { + value := v.(int) + families := map[int]bool{ + 15: true, + 30: true, + 60: true, + 360: true, + 720: true, + 1440: true, } - if !skus[value] { - errors = append(errors, fmt.Errorf("Redis SKU can only be Basic, Standard or Premium")) + if !families[value] { + errors = append(errors, fmt.Errorf("Redis Backup Frequency can only be '15', '30', '60', '360', '720' or '1440'")) } + return } diff --git a/azurerm/resource_arm_redis_cache_test.go b/azurerm/resource_arm_redis_cache_test.go index dacafe135686..a69291da321f 100644 --- a/azurerm/resource_arm_redis_cache_test.go +++ b/azurerm/resource_arm_redis_cache_test.go @@ -77,41 +77,35 @@ func TestAccAzureRMRedisCacheMaxMemoryPolicy_validation(t *testing.T) { } } -func TestAccAzureRMRedisCacheSku_validation(t *testing.T) { +func TestAccAzureRMRedisCacheBackupFrequency_validation(t *testing.T) { cases := []struct { - Value string + Value int ErrCount int }{ - { - Value: "Basic", - ErrCount: 0, - }, - { - Value: "Standard", - ErrCount: 0, - }, - { - Value: "Premium", - ErrCount: 0, - }, - { - Value: "Random", - ErrCount: 1, - }, + {Value: 1, ErrCount: 1}, + {Value: 15, ErrCount: 0}, + {Value: 30, ErrCount: 0}, + {Value: 45, ErrCount: 1}, + {Value: 60, ErrCount: 0}, + {Value: 120, ErrCount: 1}, + {Value: 240, ErrCount: 1}, + {Value: 360, ErrCount: 0}, + {Value: 720, ErrCount: 0}, + {Value: 1440, ErrCount: 0}, } for _, tc := range cases { - _, errors := validateRedisSku(tc.Value, "azurerm_redis_cache") + _, errors := validateRedisBackupFrequency(tc.Value, "azurerm_redis_cache") if len(errors) != tc.ErrCount { - t.Fatalf("Expected the Azure RM Redis Cache Sku to trigger a validation error") + t.Fatalf("Expected the AzureRM Redis Cache Backup Frequency to trigger a validation error for '%d'", tc.Value) } } } func TestAccAzureRMRedisCache_basic(t *testing.T) { ri := acctest.RandInt() - config := fmt.Sprintf(testAccAzureRMRedisCache_basic, ri, ri) + config := testAccAzureRMRedisCache_basic(ri) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -130,7 +124,7 @@ func TestAccAzureRMRedisCache_basic(t *testing.T) { func TestAccAzureRMRedisCache_standard(t *testing.T) { ri := acctest.RandInt() - config := fmt.Sprintf(testAccAzureRMRedisCache_standard, ri, ri) + config := testAccAzureRMRedisCache_standard(ri) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -149,7 +143,7 @@ func TestAccAzureRMRedisCache_standard(t *testing.T) { func TestAccAzureRMRedisCache_premium(t *testing.T) { ri := acctest.RandInt() - config := fmt.Sprintf(testAccAzureRMRedisCache_premium, ri, ri) + config := testAccAzureRMRedisCache_premium(ri) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -168,7 +162,7 @@ func TestAccAzureRMRedisCache_premium(t *testing.T) { func TestAccAzureRMRedisCache_premiumSharded(t *testing.T) { ri := acctest.RandInt() - config := fmt.Sprintf(testAccAzureRMRedisCache_premiumSharded, ri, ri) + config := testAccAzureRMRedisCache_premiumSharded(ri) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -194,14 +188,14 @@ func TestAccAzureRMRedisCache_NonStandardCasing(t *testing.T) { Providers: testAccProviders, CheckDestroy: testCheckAzureRMRedisCacheDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: config, Check: resource.ComposeTestCheckFunc( testCheckAzureRMRedisCacheExists("azurerm_redis_cache.test"), ), }, - resource.TestStep{ + { Config: config, PlanOnly: true, ExpectNonEmptyPlan: false, @@ -210,6 +204,72 @@ func TestAccAzureRMRedisCache_NonStandardCasing(t *testing.T) { }) } +func TestAccAzureRMRedisCache_BackupDisabled(t *testing.T) { + ri := acctest.RandInt() + config := testAccAzureRMRedisCacheBackupDisabled(ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRedisCacheDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRedisCacheExists("azurerm_redis_cache.test"), + ), + }, + }, + }) +} + +func TestAccAzureRMRedisCache_BackupEnabled(t *testing.T) { + ri := acctest.RandInt() + rs := acctest.RandString(4) + config := testAccAzureRMRedisCacheBackupEnabled(ri, rs) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRedisCacheDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRedisCacheExists("azurerm_redis_cache.test"), + ), + }, + }, + }) +} + +func TestAccAzureRMRedisCache_BackupEnabledDisabled(t *testing.T) { + ri := acctest.RandInt() + rs := acctest.RandString(4) + config := testAccAzureRMRedisCacheBackupEnabled(ri, rs) + updatedConfig := testAccAzureRMRedisCacheBackupDisabled(ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRedisCacheDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRedisCacheExists("azurerm_redis_cache.test"), + ), + }, + { + Config: updatedConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRedisCacheExists("azurerm_redis_cache.test"), + ), + }, + }, + }) +} + func testCheckAzureRMRedisCacheExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { // Ensure we have enough information in state to look up in API @@ -264,7 +324,8 @@ func testCheckAzureRMRedisCacheDestroy(s *terraform.State) error { return nil } -var testAccAzureRMRedisCache_basic = ` +func testAccAzureRMRedisCache_basic(rInt int) string { + return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-%d" location = "West US" @@ -283,9 +344,11 @@ resource "azurerm_redis_cache" "test" { maxclients = "256" } } -` +`, rInt, rInt) +} -var testAccAzureRMRedisCache_standard = ` +func testAccAzureRMRedisCache_standard(rInt int) string { + return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-%d" location = "West US" @@ -307,9 +370,11 @@ resource "azurerm_redis_cache" "test" { environment = "production" } } -` +`, rInt, rInt) +} -var testAccAzureRMRedisCache_premium = ` +func testAccAzureRMRedisCache_premium(rInt int) string { + return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-%d" location = "West US" @@ -324,15 +389,17 @@ resource "azurerm_redis_cache" "test" { sku_name = "Premium" enable_non_ssl_port = false redis_configuration { - maxclients = "256", - maxmemory_reserved = "2", - maxmemory_delta = "2" + maxclients = 256, + maxmemory_reserved = 2, + maxmemory_delta = 2 maxmemory_policy = "allkeys-lru" } } -` +`, rInt, rInt) +} -var testAccAzureRMRedisCache_premiumSharded = ` +func testAccAzureRMRedisCache_premiumSharded(rInt int) string { + return fmt.Sprintf(` resource "azurerm_resource_group" "test" { name = "acctestRG-%d" location = "West US" @@ -349,12 +416,13 @@ resource "azurerm_redis_cache" "test" { shard_count = 3 redis_configuration { maxclients = "256", - maxmemory_reserved = "2", - maxmemory_delta = "2" + maxmemory_reserved = 2, + maxmemory_delta = 2 maxmemory_policy = "allkeys-lru" } } -` +`, rInt, rInt) +} func testAccAzureRMRedisCacheNonStandardCasing(ri int) string { return fmt.Sprintf(` @@ -376,3 +444,61 @@ resource "azurerm_redis_cache" "test" { } `, ri, ri) } + +func testAccAzureRMRedisCacheBackupDisabled(ri int) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "West US" +} +resource "azurerm_redis_cache" "test" { + name = "acctestRedis-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + capacity = 3 + family = "P" + sku_name = "Premium" + enable_non_ssl_port = false + redis_configuration { + maxclients = "256" + rdb_backup_enabled = false + } +} +`, ri, ri) +} + +func testAccAzureRMRedisCacheBackupEnabled(ri int, rs string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "West US" +} +resource "azurerm_storage_account" "test" { + name = "unlikely23exst2acct%s" + resource_group_name = "${azurerm_resource_group.test.name}" + + location = "westus" + account_type = "Standard_GRS" + + tags { + environment = "staging" + } +} +resource "azurerm_redis_cache" "test" { + name = "acctestRedis-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + capacity = 3 + family = "P" + sku_name = "Premium" + enable_non_ssl_port = false + redis_configuration { + maxclients = "256" + rdb_backup_enabled = true + rdb_backup_frequency = 60 + rdb_backup_max_snapshot_count = 1 + rdb_storage_connection_string = "DefaultEndpointsProtocol=https;BlobEndpoint=${azurerm_storage_account.test.primary_blob_endpoint};AccountName=${azurerm_storage_account.test.name};AccountKey=${azurerm_storage_account.test.primary_access_key}" + } +} +`, ri, rs, ri) +} diff --git a/website/docs/r/redis_cache.html.markdown b/website/docs/r/redis_cache.html.markdown index ddb904ddc719..f45d65c5b5e2 100644 --- a/website/docs/r/redis_cache.html.markdown +++ b/website/docs/r/redis_cache.html.markdown @@ -28,7 +28,7 @@ resource "azurerm_redis_cache" "test" { enable_non_ssl_port = false redis_configuration { - maxclients = "256" + maxclients = 256 } } ``` @@ -51,7 +51,7 @@ resource "azurerm_redis_cache" "test" { enable_non_ssl_port = false redis_configuration { - maxclients = "1000" + maxclients = 1000 } } ``` @@ -75,14 +75,45 @@ resource "azurerm_redis_cache" "test" { shard_count = 3 redis_configuration { - maxclients = "7500" - maxmemory_reserved = "2" - maxmemory_delta = "2" + maxclients = 7500 + maxmemory_reserved = 2 + maxmemory_delta = 2 maxmemory_policy = "allkeys-lru" } } ``` +## Example Usage (Premium with Backup) + +```hcl +resource "azurerm_resource_group" "test" { + name = "redisrg" + location = "West US" +} +resource "azurerm_storage_account" "test" { + name = "redissa" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + account_type = "Standard_GRS" +} +resource "azurerm_redis_cache" "test" { + name = "example-redis" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + capacity = 3 + family = "P" + sku_name = "Premium" + enable_non_ssl_port = false + redis_configuration { + maxclients = 256 + rdb_backup_enabled = true + rdb_backup_frequency = 60 + rdb_backup_max_snapshot_count = 1 + rdb_storage_connection_string = "DefaultEndpointsProtocol=https;BlobEndpoint=${azurerm_storage_account.test.primary_blob_endpoint};AccountName=${azurerm_storage_account.test.name};AccountKey=${azurerm_storage_account.test.primary_access_key}" + } +} +``` + ## Argument Reference The following arguments are supported: @@ -107,13 +138,27 @@ The pricing group for the Redis Family - either "C" or "P" at present. * `shard_count` - (Optional) *Only available when using the Premium SKU* The number of Shards to create on the Redis Cluster. -* `redis_configuration` - (Required) Potential Redis configuration values - with some limitations by SKU - defaults/details are shown below. +* `redis_configuration` - (Required) A `redis_configuration` as defined below - with some limitations by SKU - defaults/details are shown below. + +--- + +* `redis_configuration` supports the following: + +* `maxclients` - (Optional) Set the max number of connected clients at the same time. Defaults are shown below. +* `maxmemory_reserve` - (Optional) Value in megabytes reserved for non-cache usage e.g. failover. Defaults are shown below. +* `maxmemory_delta` - (Optional) The max-memory delta for this Redis instance. Defaults are shown below. +* `maxmemory_policy` - (Optional) How Redis will select what to remove when `maxmemory` is reached. Defaults are shown below. + +* `rdb_backup_enabled` - (Optional) Is Backup Enabled? Only supported on Premium SKU's. +* `rdb_backup_frequency` - (Optional) The Backup Frequency in Minutes. Only supported on Premium SKU's. Possible values are: `15`, `30`, `60`, `360`, `720` and `1440`. +* `rdb_backup_max_snapshot_count` - (Optional) The maximum number of snapshots to create as a backup. Only supported for Premium SKU's. +* `rdb_storage_connection_string` - (Optional) The Connection String to the Storage Account. Only supported for Premium SKU's. In the format: `DefaultEndpointsProtocol=https;BlobEndpoint=${azurerm_storage_account.test.primary_blob_endpoint};AccountName=${azurerm_storage_account.test.name};AccountKey=${azurerm_storage_account.test.primary_access_key}`. ```hcl redis_configuration { - maxclients = "512" - maxmemory_reserve = "10" - maxmemory_delta = "2" + maxclients = 512 + maxmemory_reserve = 10 + maxmemory_delta = 2 maxmemory_policy = "allkeys-lru" } ```