From 7e67444349ad91970792d01a62193c13f13b4d47 Mon Sep 17 00:00:00 2001 From: Pat Gavlin Date: Thu, 20 Sep 2018 15:42:14 -0700 Subject: [PATCH 1/2] Add support for vnet rules to CosmosDB accounts. These changes add support for virtual network filters to CosmosDB accounts. Using these filters requires two new prooperties: - `is_virtual_network_filter_enabled`, which enables virutal network filters for the account - `virtual_network_rules`, which specifies the subnets that are allowed to access the account --- azurerm/data_source_cosmos_db_account.go | 29 ++++++ azurerm/data_source_cosmos_db_account_test.go | 33 +++++++ azurerm/resource_arm_cosmos_db_account.go | 97 ++++++++++++++++--- .../resource_arm_cosmos_db_account_test.go | 64 ++++++++++++ website/docs/d/cosmosdb_account.html.markdown | 8 ++ website/docs/r/cosmosdb_account.html.markdown | 8 ++ 6 files changed, 226 insertions(+), 13 deletions(-) diff --git a/azurerm/data_source_cosmos_db_account.go b/azurerm/data_source_cosmos_db_account.go index eee7f2ece62b..622446823203 100644 --- a/azurerm/data_source_cosmos_db_account.go +++ b/azurerm/data_source_cosmos_db_account.go @@ -105,6 +105,24 @@ func dataSourceArmCosmosDBAccount() *schema.Resource { }, }, + "is_virtual_network_filter_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + + "virtual_network_rule": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "endpoint": { Type: schema.TypeString, Computed: true, @@ -183,6 +201,7 @@ func dataSourceArmCosmosDBAccountRead(d *schema.ResourceData, meta interface{}) d.Set("offer_type", string(props.DatabaseAccountOfferType)) d.Set("ip_range_filter", props.IPRangeFilter) d.Set("endpoint", props.DocumentEndpoint) + d.Set("is_virtual_network_filter_enabled", resp.IsVirtualNetworkFilterEnabled) d.Set("enable_automatic_failover", resp.EnableAutomaticFailover) if err := d.Set("consistency_policy", flattenAzureRmCosmosDBAccountConsistencyPolicy(resp.ConsistencyPolicy)); err != nil { @@ -206,6 +225,16 @@ func dataSourceArmCosmosDBAccountRead(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error setting `capabilities`: %+v", err) } + virtualNetworkRules := make([]map[string]interface{}, len(*props.VirtualNetworkRules)) + for i, r := range *props.VirtualNetworkRules { + virtualNetworkRules[i] = map[string]interface{}{ + "id": *r.ID, + } + } + if err := d.Set("virtual_network_rule", virtualNetworkRules); err != nil { + return fmt.Errorf("Error setting `virtual_network_rule`: %+v", err) + } + readEndpoints := make([]string, 0) if locations := props.ReadLocations; locations != nil { for _, l := range *locations { diff --git a/azurerm/data_source_cosmos_db_account_test.go b/azurerm/data_source_cosmos_db_account_test.go index ef7fdb204b04..27506a8ebaa1 100644 --- a/azurerm/data_source_cosmos_db_account_test.go +++ b/azurerm/data_source_cosmos_db_account_test.go @@ -53,6 +53,28 @@ func TestAccDataSourceAzureRMCosmosDBAccount_geoReplicated_customId(t *testing.T }) } +func TestAccDataSourceAzureRMCosmosDBAccount_virtualNetworkFilter(t *testing.T) { + ri := acctest.RandInt() + dataSourceName := "data.azurerm_cosmosdb_account.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMCosmosDBAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAzureRMCosmosDBAccount_virtualNetworkFilter(ri, testLocation()), + Check: resource.ComposeAggregateTestCheckFunc( + checkAccAzureRMCosmosDBAccount_basic(dataSourceName, testLocation(), string(documentdb.BoundedStaleness), 1), + resource.TestCheckResourceAttr(dataSourceName, "is_virtual_network_filter_enabled", "true"), + resource.TestCheckResourceAttrSet(dataSourceName, "virtual_network_rule.0.id"), + resource.TestCheckResourceAttrSet(dataSourceName, "virtual_network_rule.1.id"), + ), + }, + }, + }) +} + func TestAccDataSourceAzureRMCosmosDBAccount_complete(t *testing.T) { ri := acctest.RandInt() dataSourceName := "data.azurerm_cosmosdb_account.test" @@ -110,3 +132,14 @@ data "azurerm_cosmosdb_account" "test" { } `, testAccAzureRMCosmosDBAccount_complete(rInt, location, altLocation)) } + +func testAccDataSourceAzureRMCosmosDBAccount_virtualNetworkFilter(rInt int, location string) string { + return fmt.Sprintf(` +%s + +data "azurerm_cosmosdb_account" "test" { + name = "${azurerm_cosmosdb_account.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" +} +`, testAccAzureRMCosmosDBAccount_virtualNetworkFilter(rInt, location)) +} diff --git a/azurerm/resource_arm_cosmos_db_account.go b/azurerm/resource_arm_cosmos_db_account.go index eef0d0507855..7d13922f4852 100644 --- a/azurerm/resource_arm_cosmos_db_account.go +++ b/azurerm/resource_arm_cosmos_db_account.go @@ -6,15 +6,16 @@ import ( "fmt" "log" "regexp" + "strings" "time" "github.com/Azure/azure-sdk-for-go/services/cosmos-db/mgmt/2015-04-08/documentdb" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" - "github.com/hashicorp/terraform/helper/schema" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -205,6 +206,26 @@ func resourceArmCosmosDBAccount() *schema.Resource { Set: resourceAzureRMCosmosDBAccountCapabilitiesHash, }, + "is_virtual_network_filter_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "virtual_network_rule": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + Set: resourceAzureRMCosmosDBAccountVirtualNetworkRuleHash, + }, + //computed "endpoint": { Type: schema.TypeString, @@ -275,6 +296,7 @@ func resourceArmCosmosDBAccountCreate(d *schema.ResourceData, meta interface{}) kind := d.Get("kind").(string) offerType := d.Get("offer_type").(string) ipRangeFilter := d.Get("ip_range_filter").(string) + isVirtualNetworkFilterEnabled := d.Get("is_virtual_network_filter_enabled").(bool) enableAutomaticFailover := d.Get("enable_automatic_failover").(bool) r, err := client.CheckNameExists(ctx, name) @@ -306,12 +328,14 @@ func resourceArmCosmosDBAccountCreate(d *schema.ResourceData, meta interface{}) Location: utils.String(location), Kind: documentdb.DatabaseAccountKind(kind), DatabaseAccountCreateUpdateProperties: &documentdb.DatabaseAccountCreateUpdateProperties{ - DatabaseAccountOfferType: utils.String(offerType), - IPRangeFilter: utils.String(ipRangeFilter), - EnableAutomaticFailover: utils.Bool(enableAutomaticFailover), - ConsistencyPolicy: expandAzureRmCosmosDBAccountConsistencyPolicy(d), - Locations: &geoLocations, - Capabilities: expandAzureRmCosmosDBAccountCapabilities(d), + DatabaseAccountOfferType: utils.String(offerType), + IPRangeFilter: utils.String(ipRangeFilter), + IsVirtualNetworkFilterEnabled: utils.Bool(isVirtualNetworkFilterEnabled), + EnableAutomaticFailover: utils.Bool(enableAutomaticFailover), + ConsistencyPolicy: expandAzureRmCosmosDBAccountConsistencyPolicy(d), + Locations: &geoLocations, + Capabilities: expandAzureRmCosmosDBAccountCapabilities(d), + VirtualNetworkRules: expandAzureRmCosmosDBAccountVirtualNetworkRules(d), }, Tags: expandTags(tags), } @@ -345,6 +369,7 @@ func resourceArmCosmosDBAccountUpdate(d *schema.ResourceData, meta interface{}) kind := d.Get("kind").(string) offerType := d.Get("offer_type").(string) ipRangeFilter := d.Get("ip_range_filter").(string) + isVirtualNetworkFilterEnabled := d.Get("is_virtual_network_filter_enabled").(bool) enableAutomaticFailover := d.Get("enable_automatic_failover").(bool) //hacky, todo fix up once deprecated field 'failover_policy' is removed @@ -390,12 +415,14 @@ func resourceArmCosmosDBAccountUpdate(d *schema.ResourceData, meta interface{}) Location: utils.String(location), Kind: documentdb.DatabaseAccountKind(kind), DatabaseAccountCreateUpdateProperties: &documentdb.DatabaseAccountCreateUpdateProperties{ - DatabaseAccountOfferType: utils.String(offerType), - IPRangeFilter: utils.String(ipRangeFilter), - EnableAutomaticFailover: utils.Bool(enableAutomaticFailover), - Capabilities: expandAzureRmCosmosDBAccountCapabilities(d), - ConsistencyPolicy: expandAzureRmCosmosDBAccountConsistencyPolicy(d), - Locations: &oldLocations, + DatabaseAccountOfferType: utils.String(offerType), + IPRangeFilter: utils.String(ipRangeFilter), + IsVirtualNetworkFilterEnabled: utils.Bool(isVirtualNetworkFilterEnabled), + EnableAutomaticFailover: utils.Bool(enableAutomaticFailover), + Capabilities: expandAzureRmCosmosDBAccountCapabilities(d), + ConsistencyPolicy: expandAzureRmCosmosDBAccountConsistencyPolicy(d), + Locations: &oldLocations, + VirtualNetworkRules: expandAzureRmCosmosDBAccountVirtualNetworkRules(d), }, Tags: expandTags(tags), } @@ -493,6 +520,10 @@ func resourceArmCosmosDBAccountRead(d *schema.ResourceData, meta interface{}) er d.Set("ip_range_filter", resp.IPRangeFilter) d.Set("endpoint", resp.DocumentEndpoint) + if v := resp.IsVirtualNetworkFilterEnabled; v != nil { + d.Set("is_virtual_network_filter_enabled", resp.IsVirtualNetworkFilterEnabled) + } + if v := resp.EnableAutomaticFailover; v != nil { d.Set("enable_automatic_failover", resp.EnableAutomaticFailover) } @@ -515,6 +546,10 @@ func resourceArmCosmosDBAccountRead(d *schema.ResourceData, meta interface{}) er return fmt.Errorf("Error setting `capabilities`: %+v", err) } + if err := d.Set("virtual_network_rule", flattenAzureRmCosmosDBAccountVirtualNetworkRules(resp.VirtualNetworkRules)); err != nil { + return fmt.Errorf("Error setting `virtual_network_rule`: %+v", err) + } + if p := resp.ReadLocations; p != nil { readEndpoints := []string{} for _, l := range *p { @@ -790,6 +825,17 @@ func expandAzureRmCosmosDBAccountCapabilities(d *schema.ResourceData) *[]documen return &s } +func expandAzureRmCosmosDBAccountVirtualNetworkRules(d *schema.ResourceData) *[]documentdb.VirtualNetworkRule { + virtualNetworkRules := d.Get("virtual_network_rule").(*schema.Set).List() + + s := make([]documentdb.VirtualNetworkRule, len(virtualNetworkRules)) + for i, r := range virtualNetworkRules { + m := r.(map[string]interface{}) + s[i] = documentdb.VirtualNetworkRule{ID: utils.String(m["id"].(string))} + } + return &s +} + func flattenAzureRmCosmosDBAccountConsistencyPolicy(policy *documentdb.ConsistencyPolicy) []interface{} { result := map[string]interface{}{} @@ -873,6 +919,21 @@ func flattenAzureRmCosmosDBAccountCapabilities(capabilities *[]documentdb.Capabi return &s } +func flattenAzureRmCosmosDBAccountVirtualNetworkRules(rules *[]documentdb.VirtualNetworkRule) *schema.Set { + results := schema.Set{ + F: resourceAzureRMCosmosDBAccountVirtualNetworkRuleHash, + } + + for _, r := range *rules { + rule := map[string]interface{}{ + "id": *r.ID, + } + results.Add(rule) + } + + return &results +} + //todo remove once deprecated field `failover_policy` is removed func resourceAzureRMCosmosDBAccountFailoverPolicyHash(v interface{}) int { var buf bytes.Buffer @@ -913,3 +974,13 @@ func resourceAzureRMCosmosDBAccountCapabilitiesHash(v interface{}) int { return hashcode.String(buf.String()) } + +func resourceAzureRMCosmosDBAccountVirtualNetworkRuleHash(v interface{}) int { + var buf bytes.Buffer + + if m, ok := v.(map[string]interface{}); ok { + buf.WriteString(strings.ToLower(m["id"].(string))) + } + + return hashcode.String(buf.String()) +} diff --git a/azurerm/resource_arm_cosmos_db_account_test.go b/azurerm/resource_arm_cosmos_db_account_test.go index b98e7f1c1a56..2e8bb3a49967 100644 --- a/azurerm/resource_arm_cosmos_db_account_test.go +++ b/azurerm/resource_arm_cosmos_db_account_test.go @@ -425,6 +425,28 @@ func TestAccAzureRMCosmosDBAccount_geoReplicated_rename(t *testing.T) { }) } +func TestAccAzureRMCosmosDBAccount_virtualNetworkFilter(t *testing.T) { + ri := acctest.RandInt() + resourceName := "azurerm_cosmosdb_account.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMCosmosDBAccountDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMCosmosDBAccount_virtualNetworkFilter(ri, testLocation()), + Check: checkAccAzureRMCosmosDBAccount_basic(resourceName, testLocation(), string(documentdb.BoundedStaleness), 1), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + //basic --> complete ( func TestAccAzureRMCosmosDBAccount_complete(t *testing.T) { ri := acctest.RandInt() @@ -608,6 +630,48 @@ func testAccAzureRMCosmosDBAccount_complete(rInt int, location string, altLocati `, rInt, altLocation)) } +func testAccAzureRMCosmosDBAccount_virtualNetworkFilter(rInt int, location string) string { + vnetConfig := fmt.Sprintf(` +resource "azurerm_virtual_network" "test" { + name = "acctest-%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + address_space = ["10.0.0.0/16"] + location = "${azurerm_resource_group.test.location}" + dns_servers = ["10.0.0.4", "10.0.0.5"] +} + +resource "azurerm_subnet" "subnet1" { + name = "acctest-%[1]d-1" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.1.0/24" + service_endpoints = ["Microsoft.AzureCosmosDB"] +} + +resource "azurerm_subnet" "subnet2" { + name = "acctest-%[1]d-2" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.2.0/24" + service_endpoints = ["Microsoft.AzureCosmosDB"] +} +`, rInt) + + basic := testAccAzureRMCosmosDBAccount_basic(rInt, location, string(documentdb.BoundedStaleness), "", ` + is_virtual_network_filter_enabled = true + + virtual_network_rule { + id = "${azurerm_subnet.subnet1.id}" + } + + virtual_network_rule { + id = "${azurerm_subnet.subnet2.id}" + } + `) + + return vnetConfig + basic +} + func checkAccAzureRMCosmosDBAccount_basic(resourceName string, location string, consistency string, locationCount int) resource.TestCheckFunc { return resource.ComposeTestCheckFunc( testCheckAzureRMCosmosDBAccountExists(resourceName), diff --git a/website/docs/d/cosmosdb_account.html.markdown b/website/docs/d/cosmosdb_account.html.markdown index 2ded09f0608f..75f733ef95c7 100644 --- a/website/docs/d/cosmosdb_account.html.markdown +++ b/website/docs/d/cosmosdb_account.html.markdown @@ -51,6 +51,10 @@ The following attributes are exported: * `capabilities` - Capabilities enabled on this Cosmos DB account. +* `is_virtual_network_filter_enabled` - If virtual network filtering is enabled for this Cosmos DB account. + +* `virtual_network_rule` - Subnets that are allowed to access this CosmosDB account. + `consistency_policy` The current consistency Settings for this CosmosDB account with the following properties: * `consistency_level` - The Consistency Level used by this CosmosDB Account. @@ -64,6 +68,10 @@ The following attributes are exported: * `location` - The name of the Azure region hosting replicated data. * `priority` - The locations fail over priority. +`virtual_network_rule` The virtual network subnets allowed to access this Cosmos DB account with the following properties: + +* `id` - The ID of the virtual network subnet. + * `endpoint` - The endpoint used to connect to the CosmosDB account. * `read_endpoints` - A list of read endpoints available for this CosmosDB account. diff --git a/website/docs/r/cosmosdb_account.html.markdown b/website/docs/r/cosmosdb_account.html.markdown index aad74eb628ba..7d8b5321728d 100644 --- a/website/docs/r/cosmosdb_account.html.markdown +++ b/website/docs/r/cosmosdb_account.html.markdown @@ -77,6 +77,10 @@ The following arguments are supported: * `capabilities` - (Optional) Enable capabilities for this Cosmos DB account. Possible values are `EnableTable` and `EnableGremlin`. +* `is_virtual_network_filter_enabled` - (Optional) Enables virtual network filtering for this Cosmos DB account. + +* `virtual_network_rule` - (Optional) Specifies a `virtual_network_rules` resource, used to define which subnets are allowed to access this CosmosDB account. + `consistency_policy` Configures the database consistency and supports the following: * `consistency_level` - (Required) The Consistency Level to use for this CosmosDB Account - can be either `BoundedStaleness`, `Eventual`, `Session`, `Strong` or `ConsistentPrefix`. @@ -93,6 +97,10 @@ The following arguments are supported: **NOTE:** The `prefix` and `failover_priority` fields of a location cannot be changed for the location with a failover priority of `0`. +`virtual_network_rule` Configures the virtual network subnets allowed to access this Cosmos DB account and supports the following: + +* `id` - (Required) The ID of the virtual network subnet. + ## Attributes Reference The following attributes are exported: From d5a07c525b400cf33e2e7457f8a4cf256987c431 Mon Sep 17 00:00:00 2001 From: Pat Gavlin Date: Fri, 21 Sep 2018 01:29:15 -0700 Subject: [PATCH 2/2] PR feedback. --- azurerm/data_source_cosmos_db_account.go | 22 +++++++++++++++------- azurerm/resource_arm_cosmos_db_account.go | 16 ++++++++++------ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/azurerm/data_source_cosmos_db_account.go b/azurerm/data_source_cosmos_db_account.go index 622446823203..caf45873a431 100644 --- a/azurerm/data_source_cosmos_db_account.go +++ b/azurerm/data_source_cosmos_db_account.go @@ -225,13 +225,7 @@ func dataSourceArmCosmosDBAccountRead(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error setting `capabilities`: %+v", err) } - virtualNetworkRules := make([]map[string]interface{}, len(*props.VirtualNetworkRules)) - for i, r := range *props.VirtualNetworkRules { - virtualNetworkRules[i] = map[string]interface{}{ - "id": *r.ID, - } - } - if err := d.Set("virtual_network_rule", virtualNetworkRules); err != nil { + if err := d.Set("virtual_network_rule", flattenAzureRmCosmosDBAccountVirtualNetworkRulesAsList(props.VirtualNetworkRules)); err != nil { return fmt.Errorf("Error setting `virtual_network_rule`: %+v", err) } @@ -285,3 +279,17 @@ func flattenAzureRmCosmosDBAccountCapabilitiesAsList(capabilities *[]documentdb. return &slice } + +func flattenAzureRmCosmosDBAccountVirtualNetworkRulesAsList(rules *[]documentdb.VirtualNetworkRule) []map[string]interface{} { + if rules == nil { + return []map[string]interface{}{} + } + + virtualNetworkRules := make([]map[string]interface{}, len(*rules)) + for i, r := range *rules { + virtualNetworkRules[i] = map[string]interface{}{ + "id": *r.ID, + } + } + return virtualNetworkRules +} diff --git a/azurerm/resource_arm_cosmos_db_account.go b/azurerm/resource_arm_cosmos_db_account.go index 7d13922f4852..61cbe3e8f39d 100644 --- a/azurerm/resource_arm_cosmos_db_account.go +++ b/azurerm/resource_arm_cosmos_db_account.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" ) @@ -218,8 +219,9 @@ func resourceArmCosmosDBAccount() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "id": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: azure.ValidateResourceID, }, }, }, @@ -924,11 +926,13 @@ func flattenAzureRmCosmosDBAccountVirtualNetworkRules(rules *[]documentdb.Virtua F: resourceAzureRMCosmosDBAccountVirtualNetworkRuleHash, } - for _, r := range *rules { - rule := map[string]interface{}{ - "id": *r.ID, + if rules != nil { + for _, r := range *rules { + rule := map[string]interface{}{ + "id": *r.ID, + } + results.Add(rule) } - results.Add(rule) } return &results