diff --git a/internal/services/cosmos/cosmosdb_account_resource.go b/internal/services/cosmos/cosmosdb_account_resource.go index 5a90d15c347d..48ed257e2af4 100644 --- a/internal/services/cosmos/cosmosdb_account_resource.go +++ b/internal/services/cosmos/cosmosdb_account_resource.go @@ -110,6 +110,53 @@ func resourceCosmosDbAccount() *pluginsdk.Resource { }, true), }, + "analytical_storage": { + Type: pluginsdk.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "schema_type": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(documentdb.AnalyticalStorageSchemaTypeWellDefined), + string(documentdb.AnalyticalStorageSchemaTypeFullFidelity), + }, false), + }, + }, + }, + }, + + "capacity": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "total_throughput_limit": { + Type: pluginsdk.TypeInt, + Required: true, + ValidateFunc: validation.IntAtLeast(-1), + }, + }, + }, + }, + + "default_identity_type": { + Type: pluginsdk.TypeString, + Optional: true, + Default: "FirstPartyIdentity", + ValidateFunc: validation.Any( + validation.StringMatch(regexp.MustCompile(`^UserAssignedIdentity(.)+$`), "It may start with `UserAssignedIdentity`"), + validation.StringInSlice([]string{ + "FirstPartyIdentity", + "SystemAssignedIdentity", + }, false), + ), + }, + "kind": { Type: pluginsdk.TypeString, Optional: true, @@ -371,6 +418,17 @@ func resourceCosmosDbAccount() *pluginsdk.Resource { Computed: true, ValidateFunc: validation.IntBetween(8, 720), }, + + "storage_redundancy": { + Type: pluginsdk.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{ + string(documentdb.BackupStorageRedundancyGeo), + string(documentdb.BackupStorageRedundancyLocal), + string(documentdb.BackupStorageRedundancyZone), + }, false), + }, }, }, }, @@ -576,10 +634,19 @@ func resourceCosmosDbAccountCreate(d *pluginsdk.ResourceData, meta interface{}) NetworkACLBypass: networkByPass, NetworkACLBypassResourceIds: utils.ExpandStringSlice(d.Get("network_acl_bypass_ids").([]interface{})), DisableLocalAuth: utils.Bool(disableLocalAuthentication), + DefaultIdentity: utils.String(d.Get("default_identity_type").(string)), }, Tags: tags.Expand(t), } + if v, ok := d.GetOk("analytical_storage"); ok { + account.DatabaseAccountCreateUpdateProperties.AnalyticalStorageConfiguration = expandCosmosDBAccountAnalyticalStorageConfiguration(v.([]interface{})) + } + + if v, ok := d.GetOk("capacity"); ok { + account.DatabaseAccountCreateUpdateProperties.Capacity = expandCosmosDBAccountCapacity(v.([]interface{})) + } + if v, ok := d.GetOk("mongo_server_version"); ok { account.DatabaseAccountCreateUpdateProperties.APIProperties = &documentdb.APIProperties{ ServerVersion: documentdb.ServerVersion(v.(string)), @@ -710,10 +777,19 @@ func resourceCosmosDbAccountUpdate(d *pluginsdk.ResourceData, meta interface{}) NetworkACLBypass: networkByPass, NetworkACLBypassResourceIds: utils.ExpandStringSlice(d.Get("network_acl_bypass_ids").([]interface{})), DisableLocalAuth: utils.Bool(disableLocalAuthentication), + DefaultIdentity: utils.String(d.Get("default_identity_type").(string)), }, Tags: tags.Expand(t), } + if v, ok := d.GetOk("analytical_storage"); ok { + account.DatabaseAccountCreateUpdateProperties.AnalyticalStorageConfiguration = expandCosmosDBAccountAnalyticalStorageConfiguration(v.([]interface{})) + } + + if v, ok := d.GetOk("capacity"); ok { + account.DatabaseAccountCreateUpdateProperties.Capacity = expandCosmosDBAccountCapacity(v.([]interface{})) + } + if keyVaultKeyIDRaw, ok := d.GetOk("key_vault_key_id"); ok { keyVaultKey, err := keyVaultParse.ParseOptionallyVersionedNestedItemID(keyVaultKeyIDRaw.(string)) if err != nil { @@ -832,6 +908,7 @@ func resourceCosmosDbAccountRead(d *pluginsdk.ResourceData, meta interface{}) er d.Set("enable_free_tier", props.EnableFreeTier) d.Set("analytical_storage_enabled", props.EnableAnalyticalStorage) d.Set("public_network_access_enabled", props.PublicNetworkAccess == documentdb.PublicNetworkAccessEnabled) + d.Set("default_identity_type", props.DefaultIdentity) if v := resp.IsVirtualNetworkFilterEnabled; v != nil { d.Set("is_virtual_network_filter_enabled", props.IsVirtualNetworkFilterEnabled) @@ -849,6 +926,14 @@ func resourceCosmosDbAccountRead(d *pluginsdk.ResourceData, meta interface{}) er d.Set("enable_multiple_write_locations", props.EnableMultipleWriteLocations) } + if err := d.Set("analytical_storage", flattenCosmosDBAccountAnalyticalStorageConfiguration(props.AnalyticalStorageConfiguration)); err != nil { + return fmt.Errorf("setting `analytical_storage`: %+v", err) + } + + if err := d.Set("capacity", flattenCosmosDBAccountCapacity(props.Capacity)); err != nil { + return fmt.Errorf("setting `capacity`: %+v", err) + } + if err = d.Set("consistency_policy", flattenAzureRmCosmosDBAccountConsistencyPolicy(props.ConsistencyPolicy)); err != nil { return fmt.Errorf("setting CosmosDB Account %q `consistency_policy` (Resource Group %q): %+v", id.Name, id.ResourceGroup, err) } @@ -1317,6 +1402,9 @@ func expandCosmosdbAccountBackup(input []interface{}, backupHasChange bool) (doc if v := attr["retention_in_hours"].(int); v != 0 && !backupHasChange { return nil, fmt.Errorf("`retention_in_hours` can not be set when `type` in `backup` is `Continuous`") } + if v := attr["storage_redundancy"].(string); v != "" && !backupHasChange { + return nil, fmt.Errorf("`storage_redundancy` can not be set when `type` in `backup` is `Continuous`") + } return documentdb.ContinuousModeBackupPolicy{ Type: documentdb.TypeContinuous, }, nil @@ -1327,6 +1415,7 @@ func expandCosmosdbAccountBackup(input []interface{}, backupHasChange bool) (doc PeriodicModeProperties: &documentdb.PeriodicModeProperties{ BackupIntervalInMinutes: utils.Int32(int32(attr["interval_in_minutes"].(int))), BackupRetentionIntervalInHours: utils.Int32(int32(attr["retention_in_hours"].(int))), + BackupStorageRedundancy: documentdb.BackupStorageRedundancy(attr["storage_redundancy"].(string)), }, }, nil @@ -1360,11 +1449,16 @@ func flattenCosmosdbAccountBackup(input documentdb.BasicBackupPolicy) ([]interfa if v := policy.PeriodicModeProperties.BackupRetentionIntervalInHours; v != nil { retention = int(*v) } + var storageRedundancy documentdb.BackupStorageRedundancy + if policy.PeriodicModeProperties.BackupStorageRedundancy != "" { + storageRedundancy = policy.PeriodicModeProperties.BackupStorageRedundancy + } return []interface{}{ map[string]interface{}{ "type": string(documentdb.TypePeriodic), "interval_in_minutes": interval, "retention_in_hours": retention, + "storage_redundancy": storageRedundancy, }, }, nil @@ -1408,3 +1502,61 @@ func flattenAzureRmdocumentdbMachineIdentity(identity *documentdb.ManagedService }, } } + +func expandCosmosDBAccountAnalyticalStorageConfiguration(input []interface{}) *documentdb.AnalyticalStorageConfiguration { + if len(input) == 0 { + return nil + } + + v := input[0].(map[string]interface{}) + + return &documentdb.AnalyticalStorageConfiguration{ + SchemaType: documentdb.AnalyticalStorageSchemaType(v["schema_type"].(string)), + } +} + +func expandCosmosDBAccountCapacity(input []interface{}) *documentdb.Capacity { + if len(input) == 0 { + return nil + } + + v := input[0].(map[string]interface{}) + + return &documentdb.Capacity{ + TotalThroughputLimit: utils.Int32(int32(v["total_throughput_limit"].(int))), + } +} + +func flattenCosmosDBAccountAnalyticalStorageConfiguration(input *documentdb.AnalyticalStorageConfiguration) []interface{} { + if input == nil { + return make([]interface{}, 0) + } + + var schemaType documentdb.AnalyticalStorageSchemaType + if input.SchemaType != "" { + schemaType = input.SchemaType + } + + return []interface{}{ + map[string]interface{}{ + "schema_type": schemaType, + }, + } +} + +func flattenCosmosDBAccountCapacity(input *documentdb.Capacity) []interface{} { + if input == nil { + return make([]interface{}, 0) + } + + var totalThroughputLimit int32 + if input.TotalThroughputLimit != nil { + totalThroughputLimit = *input.TotalThroughputLimit + } + + return []interface{}{ + map[string]interface{}{ + "total_throughput_limit": totalThroughputLimit, + }, + } +} diff --git a/internal/services/cosmos/cosmosdb_account_resource_test.go b/internal/services/cosmos/cosmosdb_account_resource_test.go index d774b018a676..82c2119a7922 100644 --- a/internal/services/cosmos/cosmosdb_account_resource_test.go +++ b/internal/services/cosmos/cosmosdb_account_resource_test.go @@ -140,6 +140,42 @@ func TestAccCosmosDBAccount_keyVaultUriUpdateConsistancy(t *testing.T) { }) } +func TestAccCosmosDBAccount_updateDefaultIdentity(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_cosmosdb_account", "test") + r := CosmosDBAccountResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, documentdb.DatabaseAccountKindGlobalDocumentDB, documentdb.DefaultConsistencyLevelEventual), + Check: acceptance.ComposeAggregateTestCheckFunc( + checkAccCosmosDBAccount_basic(data, documentdb.DefaultConsistencyLevelEventual, 1), + ), + }, + data.ImportStep(), + { + Config: r.defaultIdentity(data, documentdb.DatabaseAccountKindGlobalDocumentDB, "FirstPartyIdentity", documentdb.DefaultConsistencyLevelEventual), + Check: acceptance.ComposeAggregateTestCheckFunc( + checkAccCosmosDBAccount_basic(data, documentdb.DefaultConsistencyLevelEventual, 1), + ), + }, + data.ImportStep(), + { + Config: r.updateDefaultIdentity(data, documentdb.DatabaseAccountKindGlobalDocumentDB, "SystemAssignedIdentity", documentdb.DefaultConsistencyLevelEventual), + Check: acceptance.ComposeAggregateTestCheckFunc( + checkAccCosmosDBAccount_basic(data, documentdb.DefaultConsistencyLevelEventual, 1), + ), + }, + data.ImportStep(), + { + Config: r.basic(data, documentdb.DatabaseAccountKindGlobalDocumentDB, documentdb.DefaultConsistencyLevelEventual), + Check: acceptance.ComposeAggregateTestCheckFunc( + checkAccCosmosDBAccount_basic(data, documentdb.DefaultConsistencyLevelEventual, 1), + ), + }, + data.ImportStep(), + }) +} + func testAccCosmosDBAccount_basicWith(t *testing.T, kind documentdb.DatabaseAccountKind, consistency documentdb.DefaultConsistencyLevel) { data := acceptance.BuildTestData(t, "azurerm_cosmosdb_account", "test") r := CosmosDBAccountResource{} @@ -617,6 +653,78 @@ func TestAccCosmosDBAccount_analyticalStorage(t *testing.T) { }) } +func TestAccCosmosDBAccount_updateAnalyticalStorage(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_cosmosdb_account", "test") + r := CosmosDBAccountResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, documentdb.DatabaseAccountKindGlobalDocumentDB, documentdb.DefaultConsistencyLevelEventual), + Check: acceptance.ComposeAggregateTestCheckFunc( + checkAccCosmosDBAccount_basic(data, documentdb.DefaultConsistencyLevelEventual, 1), + ), + }, + data.ImportStep(), + { + Config: r.updateAnalyticalStorage(data, documentdb.DatabaseAccountKindGlobalDocumentDB, documentdb.AnalyticalStorageSchemaTypeWellDefined, documentdb.DefaultConsistencyLevelEventual), + Check: acceptance.ComposeAggregateTestCheckFunc( + checkAccCosmosDBAccount_basic(data, documentdb.DefaultConsistencyLevelEventual, 1), + ), + }, + data.ImportStep(), + { + Config: r.updateAnalyticalStorage(data, documentdb.DatabaseAccountKindGlobalDocumentDB, documentdb.AnalyticalStorageSchemaTypeFullFidelity, documentdb.DefaultConsistencyLevelEventual), + Check: acceptance.ComposeAggregateTestCheckFunc( + checkAccCosmosDBAccount_basic(data, documentdb.DefaultConsistencyLevelEventual, 1), + ), + }, + data.ImportStep(), + { + Config: r.basic(data, documentdb.DatabaseAccountKindGlobalDocumentDB, documentdb.DefaultConsistencyLevelEventual), + Check: acceptance.ComposeAggregateTestCheckFunc( + checkAccCosmosDBAccount_basic(data, documentdb.DefaultConsistencyLevelEventual, 1), + ), + }, + data.ImportStep(), + }) +} + +func TestAccCosmosDBAccount_updateCapacity(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_cosmosdb_account", "test") + r := CosmosDBAccountResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, documentdb.DatabaseAccountKindGlobalDocumentDB, documentdb.DefaultConsistencyLevelEventual), + Check: acceptance.ComposeAggregateTestCheckFunc( + checkAccCosmosDBAccount_basic(data, documentdb.DefaultConsistencyLevelEventual, 1), + ), + }, + data.ImportStep(), + { + Config: r.updateCapacity(data, documentdb.DatabaseAccountKindGlobalDocumentDB, -1, documentdb.DefaultConsistencyLevelEventual), + Check: acceptance.ComposeAggregateTestCheckFunc( + checkAccCosmosDBAccount_basic(data, documentdb.DefaultConsistencyLevelEventual, 1), + ), + }, + data.ImportStep(), + { + Config: r.updateCapacity(data, documentdb.DatabaseAccountKindGlobalDocumentDB, 200, documentdb.DefaultConsistencyLevelEventual), + Check: acceptance.ComposeAggregateTestCheckFunc( + checkAccCosmosDBAccount_basic(data, documentdb.DefaultConsistencyLevelEventual, 1), + ), + }, + data.ImportStep(), + { + Config: r.basic(data, documentdb.DatabaseAccountKindGlobalDocumentDB, documentdb.DefaultConsistencyLevelEventual), + Check: acceptance.ComposeAggregateTestCheckFunc( + checkAccCosmosDBAccount_basic(data, documentdb.DefaultConsistencyLevelEventual, 1), + ), + }, + data.ImportStep(), + }) +} + func TestAccCosmosDBAccount_vNetFilters(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_cosmosdb_account", "test") r := CosmosDBAccountResource{} @@ -677,6 +785,7 @@ func TestAccCosmosDBAccount_backup(t *testing.T) { check.That(data.ResourceName).Key("backup.0.type").HasValue("Periodic"), check.That(data.ResourceName).Key("backup.0.interval_in_minutes").HasValue("240"), check.That(data.ResourceName).Key("backup.0.retention_in_hours").HasValue("8"), + check.That(data.ResourceName).Key("backup.0.storage_redundancy").HasValue("Geo"), ), }, data.ImportStep(), @@ -1989,6 +2098,7 @@ resource "azurerm_cosmosdb_account" "test" { type = "Periodic" interval_in_minutes = 120 retention_in_hours = 10 + storage_redundancy = "Geo" } } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, string(kind), string(consistency)) @@ -2025,6 +2135,7 @@ resource "azurerm_cosmosdb_account" "test" { type = "Periodic" interval_in_minutes = 60 retention_in_hours = 8 + storage_redundancy = "Local" } } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, string(kind), string(consistency)) @@ -2297,3 +2408,139 @@ resource "azurerm_cosmosdb_account" "test" { } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, string(kind), string(consistency)) } + +func (CosmosDBAccountResource) updateAnalyticalStorage(data acceptance.TestData, kind documentdb.DatabaseAccountKind, schemaType documentdb.AnalyticalStorageSchemaType, consistency documentdb.DefaultConsistencyLevel) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-cosmos-%d" + location = "%s" +} + +resource "azurerm_cosmosdb_account" "test" { + name = "acctest-ca-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + offer_type = "Standard" + kind = "%s" + analytical_storage_enabled = false + + analytical_storage { + schema_type = "%s" + } + + consistency_policy { + consistency_level = "%s" + } + + geo_location { + location = azurerm_resource_group.test.location + failover_priority = 0 + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, string(kind), string(schemaType), string(consistency)) +} + +func (CosmosDBAccountResource) updateCapacity(data acceptance.TestData, kind documentdb.DatabaseAccountKind, totalThroughputLimit int, consistency documentdb.DefaultConsistencyLevel) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-cosmos-%d" + location = "%s" +} + +resource "azurerm_cosmosdb_account" "test" { + name = "acctest-ca-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + offer_type = "Standard" + kind = "%s" + analytical_storage_enabled = false + + capacity { + total_throughput_limit = %d + } + + consistency_policy { + consistency_level = "%s" + } + + geo_location { + location = azurerm_resource_group.test.location + failover_priority = 0 + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, string(kind), totalThroughputLimit, string(consistency)) +} + +func (CosmosDBAccountResource) defaultIdentity(data acceptance.TestData, kind documentdb.DatabaseAccountKind, defaultIdentity string, consistency documentdb.DefaultConsistencyLevel) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-cosmos-%d" + location = "%s" +} + +resource "azurerm_cosmosdb_account" "test" { + name = "acctest-ca-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + offer_type = "Standard" + kind = "%s" + default_identity_type = "%s" + + consistency_policy { + consistency_level = "%s" + } + + geo_location { + location = azurerm_resource_group.test.location + failover_priority = 0 + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, string(kind), defaultIdentity, string(consistency)) +} + +func (CosmosDBAccountResource) updateDefaultIdentity(data acceptance.TestData, kind documentdb.DatabaseAccountKind, defaultIdentity string, consistency documentdb.DefaultConsistencyLevel) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-cosmos-%d" + location = "%s" +} + +resource "azurerm_cosmosdb_account" "test" { + name = "acctest-ca-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + offer_type = "Standard" + kind = "%s" + default_identity_type = "%s" + + identity { + type = "SystemAssigned" + } + + consistency_policy { + consistency_level = "%s" + } + + geo_location { + location = azurerm_resource_group.test.location + failover_priority = 0 + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, string(kind), defaultIdentity, string(consistency)) +} diff --git a/website/docs/r/cosmosdb_account.html.markdown b/website/docs/r/cosmosdb_account.html.markdown index 06402018a111..c9ff64c61900 100644 --- a/website/docs/r/cosmosdb_account.html.markdown +++ b/website/docs/r/cosmosdb_account.html.markdown @@ -80,6 +80,12 @@ The following arguments are supported: * `offer_type` - (Required) Specifies the Offer Type to use for this CosmosDB Account - currently this can only be set to `Standard`. +* `analytical_storage` - (Optional) An `analytical_storage` block as defined below. + +* `capacity` - (Optional) A `capacity` block as defined below. + +* `default_identity_type` - (Optional) The default identity for accessing Key Vault. Possible values are `FirstPartyIdentity`, `SystemAssignedIdentity` or start with `UserAssignedIdentity`. Defaults to `FirstPartyIdentity`. + * `kind` - (Optional) Specifies the Kind of CosmosDB to create - possible values are `GlobalDocumentDB` and `MongoDB`. Defaults to `GlobalDocumentDB`. Changing this forces a new resource to be created. * `consistency_policy` - (Required) Specifies a `consistency_policy` resource, used to define the consistency policy for this CosmosDB account. @@ -164,6 +170,18 @@ The following arguments are supported: --- +A `analytical_storage` block supports the following: + +* `schema_type` - (Required) The schema type of the Analytical Storage for this Cosmos DB account. Possible values are `FullFidelity` and `WellDefined`. + +--- + +A `capacity` block supports the following: + +* `total_throughput_limit` - (Required) The total throughput limit imposed on this Cosmos DB account (RU/s). Possible values are at least `-1`. `-1` means no limit. + +--- + A `backup` block supports the following: * `type` - (Required) The type of the `backup`. Possible values are `Continuous` and `Periodic`. Defaults to `Periodic`. Migration of `Periodic` to `Continuous` is one-way, changing `Continuous` to `Periodic` forces a new resource to be created. @@ -172,6 +190,8 @@ A `backup` block supports the following: * `retention_in_hours` - (Optional) The time in hours that each backup is retained. This is configurable only when `type` is `Periodic`. Possible values are between 8 and 720. +* `storage_redundancy` - (Optional) The storage redundancy which is used to indicate type of backup residency. This is configurable only when `type` is `Periodic`. Possible values are `Geo`, `Local` and `Zone`. + --- A `cors_rule` block supports the following: