diff --git a/azurerm/config.go b/azurerm/config.go index 12d33e910a83..4f00558f7787 100644 --- a/azurerm/config.go +++ b/azurerm/config.go @@ -121,7 +121,8 @@ type ArmClient struct { deploymentsClient resources.DeploymentsClient - redisClient redis.GroupClient + redisClient redis.GroupClient + redisFirewallClient redis.FirewallRuleClient trafficManagerProfilesClient trafficmanager.ProfilesClient trafficManagerEndpointsClient trafficmanager.EndpointsClient @@ -579,12 +580,6 @@ func (c *Config) getArmClient() (*ArmClient, error) { tmec.Sender = sender client.trafficManagerEndpointsClient = tmec - rdc := redis.NewGroupClientWithBaseURI(endpoint, c.SubscriptionID) - setUserAgent(&rdc.Client) - rdc.Authorizer = auth - rdc.Sender = sender - client.redisClient = rdc - sesc := search.NewServicesClientWithBaseURI(endpoint, c.SubscriptionID) setUserAgent(&sesc.Client) sesc.Authorizer = auth @@ -661,6 +656,7 @@ func (c *Config) getArmClient() (*ArmClient, error) { client.registerDatabases(endpoint, c.SubscriptionID, auth, sender) client.registerDisks(endpoint, c.SubscriptionID, auth, sender) client.registerKeyVaultClients(endpoint, c.SubscriptionID, auth, keyVaultAuth, sender) + client.registerRedisClients(endpoint, c.SubscriptionID, auth, sender) return &client, nil } @@ -790,6 +786,20 @@ func (c *ArmClient) registerKeyVaultClients(endpoint, subscriptionId string, aut c.keyVaultManagementClient = keyVaultManagementClient } +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 +} + func (armClient *ArmClient) getKeyForStorageAccount(resourceGroupName, storageAccountName string) (string, bool, error) { accountKeys, err := armClient.storageServiceClient.ListKeys(resourceGroupName, storageAccountName) if accountKeys.StatusCode == http.StatusNotFound { diff --git a/azurerm/provider.go b/azurerm/provider.go index 8bf452d849a3..bfaf3628a694 100644 --- a/azurerm/provider.go +++ b/azurerm/provider.go @@ -140,6 +140,7 @@ func Provider() terraform.ResourceProvider { "azurerm_postgresql_server": resourceArmPostgreSQLServer(), "azurerm_public_ip": resourceArmPublicIp(), "azurerm_redis_cache": resourceArmRedisCache(), + "azurerm_redis_firewall_rule": resourceArmRedisFirewallRule(), "azurerm_resource_group": resourceArmResourceGroup(), "azurerm_role_assignment": resourceArmRoleAssignment(), "azurerm_role_definition": resourceArmRoleDefinition(), diff --git a/azurerm/resource_arm_redis_firewall_rule.go b/azurerm/resource_arm_redis_firewall_rule.go new file mode 100644 index 000000000000..f888475634e5 --- /dev/null +++ b/azurerm/resource_arm_redis_firewall_rule.go @@ -0,0 +1,150 @@ +package azurerm + +import ( + "fmt" + "log" + + "regexp" + + "github.com/Azure/azure-sdk-for-go/arm/redis" + "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmRedisFirewallRule() *schema.Resource { + return &schema.Resource{ + Create: resourceArmRedisFirewallRuleCreateUpdate, + Read: resourceArmRedisFirewallRuleRead, + Update: resourceArmRedisFirewallRuleCreateUpdate, + Delete: resourceArmRedisFirewallRuleDelete, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateRedisFirewallRuleName, + }, + + "redis_cache_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "resource_group_name": resourceGroupNameSchema(), + + "start_ip": { + Type: schema.TypeString, + Required: true, + }, + + "end_ip": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceArmRedisFirewallRuleCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).redisFirewallClient + log.Printf("[INFO] preparing arguments for AzureRM Redis Firewall Rule creation.") + + name := d.Get("name").(string) + cacheName := d.Get("redis_cache_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + startIP := d.Get("start_ip").(string) + endIP := d.Get("end_ip").(string) + + parameters := redis.FirewallRule{ + Name: &name, + FirewallRuleProperties: &redis.FirewallRuleProperties{ + StartIP: utils.String(startIP), + EndIP: utils.String(endIP), + }, + } + + _, err := client.CreateOrUpdate(resourceGroup, cacheName, name, parameters) + if err != nil { + return err + } + + read, err := client.Get(resourceGroup, cacheName, name) + if err != nil { + return err + } + if read.ID == nil { + return fmt.Errorf("Cannot read Redis Firewall Rule %q (cache %q / resource group %q) ID", name, cacheName, resourceGroup) + } + + d.SetId(*read.ID) + + return resourceArmRedisFirewallRuleRead(d, meta) +} + +func resourceArmRedisFirewallRuleRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).redisFirewallClient + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + cacheName := id.Path["Redis"] + name := id.Path["firewallRules"] + + resp, err := client.Get(resourceGroup, cacheName, name) + + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("[DEBUG] Redis Firewall Rule %q was not found in Cache %q / Resource Group %q - removing from state", name, cacheName, resourceGroup) + d.SetId("") + return nil + } + + return fmt.Errorf("Error making Read request on Azure Redis Firewall Rule %q: %+v", name, err) + } + + d.Set("name", name) + d.Set("redis_cache_name", cacheName) + d.Set("resource_group_name", resourceGroup) + if props := resp.FirewallRuleProperties; props != nil { + d.Set("start_ip", props.StartIP) + d.Set("end_ip", props.EndIP) + } + + return nil +} + +func resourceArmRedisFirewallRuleDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).redisFirewallClient + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resourceGroup := id.ResourceGroup + cacheName := id.Path["Redis"] + name := id.Path["firewallRules"] + + resp, err := client.Delete(resourceGroup, cacheName, name) + + if err != nil { + if !utils.ResponseWasNotFound(resp) { + return fmt.Errorf("Error issuing AzureRM delete request of Redis Firewall Rule %q (cache %q / resource group %q): %+v", name, cacheName, resourceGroup, err) + } + } + + return nil +} + +func validateRedisFirewallRuleName(v interface{}, k string) (ws []string, es []error) { + value := v.(string) + + if matched := regexp.MustCompile(`^[0-9a-zA-Z]+$`).Match([]byte(value)); !matched { + es = append(es, fmt.Errorf("%q may only contain alphanumeric characters", k)) + } + + return +} diff --git a/azurerm/resource_arm_redis_firewall_rule_test.go b/azurerm/resource_arm_redis_firewall_rule_test.go new file mode 100644 index 000000000000..8632ba81a16e --- /dev/null +++ b/azurerm/resource_arm_redis_firewall_rule_test.go @@ -0,0 +1,213 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAzureRMRedisFirewallRuleName_validation(t *testing.T) { + cases := []struct { + Value string + ErrCount int + }{ + { + Value: "ab", + ErrCount: 0, + }, + { + Value: "abc", + ErrCount: 0, + }, + { + Value: "webapp1", + ErrCount: 0, + }, + { + Value: "hello-world", + ErrCount: 1, + }, + { + Value: "hello_world", + ErrCount: 1, + }, + { + Value: "helloworld21!", + ErrCount: 1, + }, + } + + for _, tc := range cases { + _, errors := validateRedisFirewallRuleName(tc.Value, "azurerm_redis_firewall_rule") + + if len(errors) != tc.ErrCount { + t.Fatalf("Expected the Redis Firewall Rule Name to trigger a validation error for '%s'", tc.Value) + } + } +} + +func TestAccAzureRMRedisFirewallRule_basic(t *testing.T) { + resourceName := "azurerm_redis_firewall_rule.test" + ri := acctest.RandInt() + config := testAccAzureRMRedisFirewallRule_basic(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRedisFirewallRuleDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRedisFirewallRuleExists(resourceName), + ), + }, + }, + }) +} + +func TestAccAzureRMRedisFirewallRule_update(t *testing.T) { + resourceName := "azurerm_redis_firewall_rule.test" + ri := acctest.RandInt() + config := testAccAzureRMRedisFirewallRule_basic(ri, testLocation()) + updatedConfig := testAccAzureRMRedisFirewallRule_update(ri, testLocation()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRedisFirewallRuleDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRedisFirewallRuleExists(resourceName), + ), + }, + { + Config: updatedConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRedisFirewallRuleExists(resourceName), + ), + }, + }, + }) +} + +func testCheckAzureRMRedisFirewallRuleExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %q", name) + } + + name := rs.Primary.Attributes["name"] + cacheName := rs.Primary.Attributes["redis_cache_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + client := testAccProvider.Meta().(*ArmClient).redisFirewallClient + resp, err := client.Get(resourceGroup, cacheName, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Firewall Rule %q (cache %q resource group: %q) does not exist", name, cacheName, resourceGroup) + } + return fmt.Errorf("Bad: Get on redisFirewallClient: %+v", err) + } + + return nil + } +} + +func testCheckAzureRMRedisFirewallRuleDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*ArmClient).resourceGroupClient + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_redis_firewall_rule" { + continue + } + + resourceGroup := rs.Primary.ID + + resp, err := client.Get(resourceGroup) + if err != nil { + return nil + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("Firewall Rule still exists:\n%#v", resp.Properties) + } + } + + return nil +} + +func testAccAzureRMRedisFirewallRule_basic(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" + } +} + +resource "azurerm_redis_firewall_rule" "test" { + name = "fwrule%d" + redis_cache_name = "${azurerm_redis_cache.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + start_ip = "1.2.3.4" + end_ip = "2.3.4.5" +} +`, rInt, location, rInt, rInt) +} + +func testAccAzureRMRedisFirewallRule_update(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" + } +} + +resource "azurerm_redis_firewall_rule" "test" { + name = "fwrule%d" + redis_cache_name = "${azurerm_redis_cache.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + start_ip = "2.3.4.5" + end_ip = "6.7.8.9" +} +`, rInt, location, rInt, rInt) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index ece172e62462..c01de2a7752d 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -482,12 +482,15 @@ - > + > Redis Resources diff --git a/website/docs/r/redis_firewall_rule.html.markdown b/website/docs/r/redis_firewall_rule.html.markdown new file mode 100644 index 000000000000..212f13fe7bad --- /dev/null +++ b/website/docs/r/redis_firewall_rule.html.markdown @@ -0,0 +1,77 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_redis_firewall_rule" +sidebar_current: "docs-azurerm-resource-redis-firewall-rule" +description: |- + Manages a Firewall Rule associated with a Premium Redis Cache. + +--- + +# azurerm_redis_firewall_rule + +Manages a Firewall Rule associated with a Premium Redis Cache. + +~> **Note:** Redis Firewall Rules can only be assigned to a Redis Cache with a `Premium` SKU. + +## Example Usage + +```hcl +resource "random_id" "server" { + keepers = { + azi_id = 1 + } + + byte_length = 8 +} + +resource "azurerm_resource_group" "test" { + name = "redis-resourcegroup" + location = "West Europe" +} + +resource "azurerm_redis_cache" "test" { + name = "redis${random_id.server.hex}" + 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" + } +} + +resource "azurerm_redis_firewall_rule" "test" { + name = "someIPrange" + redis_cache_name = "${azurerm_redis_cache.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + start_ip = "1.2.3.4" + end_ip = "2.3.4.5" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the Firewall Rule. Changing this forces a new resource to be created. + +* `redis_cache_name` - (Required) The name of the Redis Cache. Changing this forces a new resource to be created. + +* `resource_group_name` - (Required) The name of the resource group in which this Redis Cache exists. + +* `start_ip` - (Required) The lowest IP address included in the range + +* `end_ip` - (Required) The highest IP address included in the range. + + +## Attributes Reference + +The following attributes are exported: + +* `id` - The Redis Firewall Rule ID.