diff --git a/azurerm/config.go b/azurerm/config.go index 4f00558f7787..73f2794c55f0 100644 --- a/azurerm/config.go +++ b/azurerm/config.go @@ -121,8 +121,9 @@ type ArmClient struct { deploymentsClient resources.DeploymentsClient - redisClient redis.GroupClient - redisFirewallClient redis.FirewallRuleClient + redisClient redis.GroupClient + redisFirewallClient redis.FirewallRuleClient + redisPatchSchedulesClient redis.PatchSchedulesClient trafficManagerProfilesClient trafficmanager.ProfilesClient trafficManagerEndpointsClient trafficmanager.EndpointsClient @@ -787,17 +788,23 @@ func (c *ArmClient) registerKeyVaultClients(endpoint, subscriptionId string, aut } func (c *ArmClient) registerRedisClients(endpoint, subscriptionId string, auth autorest.Authorizer, sender autorest.Sender) { - rdc := redis.NewGroupClientWithBaseURI(endpoint, subscriptionId) - setUserAgent(&rdc.Client) - rdc.Authorizer = auth - rdc.Sender = sender - c.redisClient = rdc - - rdfc := redis.NewFirewallRuleClientWithBaseURI(endpoint, subscriptionId) - setUserAgent(&rdfc.Client) - rdfc.Authorizer = auth - rdfc.Sender = sender - c.redisFirewallClient = rdfc + groupsClient := redis.NewGroupClientWithBaseURI(endpoint, subscriptionId) + setUserAgent(&groupsClient.Client) + groupsClient.Authorizer = auth + groupsClient.Sender = sender + c.redisClient = groupsClient + + firewallRuleClient := redis.NewFirewallRuleClientWithBaseURI(endpoint, subscriptionId) + setUserAgent(&firewallRuleClient.Client) + firewallRuleClient.Authorizer = auth + firewallRuleClient.Sender = sender + c.redisFirewallClient = firewallRuleClient + + patchSchedulesClient := redis.NewPatchSchedulesClientWithBaseURI(endpoint, subscriptionId) + setUserAgent(&patchSchedulesClient.Client) + patchSchedulesClient.Authorizer = auth + patchSchedulesClient.Sender = sender + c.redisPatchSchedulesClient = patchSchedulesClient } func (armClient *ArmClient) getKeyForStorageAccount(resourceGroupName, storageAccountName string) (string, bool, error) { diff --git a/azurerm/resource_arm_redis_cache.go b/azurerm/resource_arm_redis_cache.go index f25c321d66d0..cbbe20cfe7f7 100644 --- a/azurerm/resource_arm_redis_cache.go +++ b/azurerm/resource_arm_redis_cache.go @@ -123,6 +123,34 @@ func resourceArmRedisCache() *schema.Resource { }, }, + "patch_schedule": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "day_of_week": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: ignoreCaseDiffSuppressFunc, + ValidateFunc: validation.StringInSlice([]string{ + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Sunday", + }, true), + }, + "start_hour_utc": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntBetween(0, 23), + }, + }, + }, + }, + "hostname": { Type: schema.TypeString, Computed: true, @@ -170,6 +198,11 @@ func resourceArmRedisCacheCreate(d *schema.ResourceData, meta interface{}) error tags := d.Get("tags").(map[string]interface{}) expandedTags := expandTags(tags) + patchSchedule, err := expandRedisPatchSchedule(d) + if err != nil { + return fmt.Errorf("Error parsing Patch Schedule: %+v", err) + } + parameters := redis.CreateParameters{ Name: &name, Location: &location, @@ -191,7 +224,7 @@ func resourceArmRedisCacheCreate(d *schema.ResourceData, meta interface{}) error } _, error := client.Create(resGroup, name, parameters, make(chan struct{})) - err := <-error + err = <-error if err != nil { return err } @@ -218,6 +251,14 @@ func resourceArmRedisCacheCreate(d *schema.ResourceData, meta interface{}) error d.SetId(*read.ID) + if schedule := patchSchedule; schedule != nil { + patchClient := meta.(*ArmClient).redisPatchSchedulesClient + _, err = patchClient.CreateOrUpdate(resGroup, name, *schedule) + if err != nil { + return fmt.Errorf("Error setting Redis Patch Schedule: %+v", err) + } + } + return resourceArmRedisCacheRead(d, meta) } @@ -288,6 +329,24 @@ func resourceArmRedisCacheUpdate(d *schema.ResourceData, meta interface{}) error d.SetId(*read.ID) + patchSchedule, err := expandRedisPatchSchedule(d) + if err != nil { + return fmt.Errorf("Error parsing Patch Schedule: %+v", err) + } + + patchClient := meta.(*ArmClient).redisPatchSchedulesClient + if patchSchedule == nil || len(*patchSchedule.ScheduleEntries.ScheduleEntries) == 0 { + _, err = patchClient.Delete(resGroup, name) + if err != nil { + return fmt.Errorf("Error deleting Redis Patch Schedule: %+v", err) + } + } else { + _, err = patchClient.CreateOrUpdate(resGroup, name, *patchSchedule) + if err != nil { + return fmt.Errorf("Error setting Redis Patch Schedule: %+v", err) + } + } + return resourceArmRedisCacheRead(d, meta) } @@ -318,6 +377,16 @@ func resourceArmRedisCacheRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf("Error making ListKeys request on Azure Redis Cache %s: %s", name, err) } + patchSchedulesClient := meta.(*ArmClient).redisPatchSchedulesClient + + schedule, err := patchSchedulesClient.Get(resGroup, name) + if err == nil { + patchSchedule := flattenRedisPatchSchedules(schedule) + if err := d.Set("patch_schedule", patchSchedule); err != nil { + return fmt.Errorf("Error setting `patch_schedule`: %+v", err) + } + } + d.Set("name", name) d.Set("resource_group_name", resGroup) d.Set("location", azureRMNormalizeLocation(*resp.Location)) @@ -325,9 +394,12 @@ func resourceArmRedisCacheRead(d *schema.ResourceData, meta interface{}) error { 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 sku := resp.Sku; sku != nil { + d.Set("capacity", sku.Capacity) + d.Set("family", sku.Family) + d.Set("sku_name", sku.Name) + } if resp.ShardCount != nil { d.Set("shard_count", resp.ShardCount) @@ -426,6 +498,34 @@ func expandRedisConfiguration(d *schema.ResourceData) *map[string]*string { return &output } +func expandRedisPatchSchedule(d *schema.ResourceData) (*redis.PatchSchedule, error) { + v, ok := d.GetOk("patch_schedule") + if !ok { + return nil, nil + } + + scheduleValues := v.([]interface{}) + entries := make([]redis.ScheduleEntry, 0) + for _, scheduleValue := range scheduleValues { + vals := scheduleValue.(map[string]interface{}) + dayOfWeek := vals["day_of_week"].(string) + startHourUtc := vals["start_hour_utc"].(int) + + entry := redis.ScheduleEntry{ + DayOfWeek: redis.DayOfWeek(dayOfWeek), + StartHourUtc: utils.Int32(int32(startHourUtc)), + } + entries = append(entries, entry) + } + + schedule := redis.PatchSchedule{ + ScheduleEntries: &redis.ScheduleEntries{ + ScheduleEntries: &entries, + }, + } + return &schedule, nil +} + func flattenRedisConfiguration(configuration *map[string]*string) map[string]*string { redisConfiguration := make(map[string]*string, len(*configuration)) config := *configuration @@ -443,6 +543,21 @@ func flattenRedisConfiguration(configuration *map[string]*string) map[string]*st return redisConfiguration } +func flattenRedisPatchSchedules(schedule redis.PatchSchedule) []interface{} { + outputs := make([]interface{}, 0) + + for _, entry := range *schedule.ScheduleEntries.ScheduleEntries { + output := make(map[string]interface{}, 0) + + output["day_of_week"] = string(entry.DayOfWeek) + output["start_hour_utc"] = int(*entry.StartHourUtc) + + outputs = append(outputs, output) + } + + return outputs +} + func validateRedisFamily(v interface{}, k string) (ws []string, errors []error) { value := strings.ToLower(v.(string)) families := map[string]bool{ diff --git a/azurerm/resource_arm_redis_cache_test.go b/azurerm/resource_arm_redis_cache_test.go index a0f1d3b19879..2effd416314f 100644 --- a/azurerm/resource_arm_redis_cache_test.go +++ b/azurerm/resource_arm_redis_cache_test.go @@ -271,6 +271,53 @@ func TestAccAzureRMRedisCache_BackupEnabledDisabled(t *testing.T) { }) } +func TestAccAzureRMRedisCache_PatchSchedule(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + config := testAccAzureRMRedisCachePatchSchedule(ri, location) + + 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_PatchScheduleUpdated(t *testing.T) { + ri := acctest.RandInt() + location := testLocation() + config := testAccAzureRMRedisCachePatchSchedule(ri, location) + updatedConfig := testAccAzureRMRedisCache_premium(ri, location) + + 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 @@ -507,3 +554,33 @@ resource "azurerm_redis_cache" "test" { } `, rInt, location, rString, rInt) } + +func testAccAzureRMRedisCachePatchSchedule(rInt int, location string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_redis_cache" "test" { + name = "acctestRedis-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + capacity = 1 + family = "P" + sku_name = "Premium" + enable_non_ssl_port = false + redis_configuration { + maxclients = 256, + maxmemory_reserved = 2, + maxmemory_delta = 2 + maxmemory_policy = "allkeys-lru" + } + + patch_schedule { + day_of_week = "Tuesday" + start_hour_utc = 8 + } +} +`, rInt, location, rInt) +} diff --git a/website/docs/r/redis_cache.html.markdown b/website/docs/r/redis_cache.html.markdown index b4ca4c020ff5..7e53a18895ae 100644 --- a/website/docs/r/redis_cache.html.markdown +++ b/website/docs/r/redis_cache.html.markdown @@ -111,9 +111,10 @@ resource "azurerm_redis_cache" "test" { ```hcl resource "azurerm_resource_group" "test" { - name = "redisrg" - location = "West US" + name = "redisrg" + location = "West US" } + resource "azurerm_storage_account" "test" { name = "redissa" resource_group_name = "${azurerm_resource_group.test.name}" @@ -121,21 +122,22 @@ resource "azurerm_storage_account" "test" { account_tier = "Standard" account_replication_type = "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}" - } + 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}" + } } ``` @@ -165,6 +167,8 @@ The pricing group for the Redis Family - either "C" or "P" at present. * `redis_configuration` - (Required) A `redis_configuration` as defined below - with some limitations by SKU - defaults/details are shown below. +* `patch_schedule` - (Optional) A list of `patch_schedule` blocks as defined below - only available for Premium SKU's. + --- * `redis_configuration` supports the following: @@ -198,6 +202,13 @@ redis_configuration { _*Important*: The maxmemory_reserved setting is only available for Standard and Premium caches. More details are available in the Relevant Links section below._ +* `patch_schedule` supports the following: + +* `day_of_week` (Required) the Weekday name - possible values include `Monday`, `Tuesday`, `Wednesday` etc. +* `start_hour_utc` - (Optional) the Start Hour for maintenance in UTC - possible values range from `0 - 23`. + +~> **Note:** The Patch Window lasts for 5 hours from the `start_hour_utc`. + ## Attributes Reference The following attributes are exported: