diff --git a/azurerm/internal/services/cosmos/cosmosdb_account_resource.go b/azurerm/internal/services/cosmos/cosmosdb_account_resource.go index 7f8327bad37d..2f1a51c8533e 100644 --- a/azurerm/internal/services/cosmos/cosmosdb_account_resource.go +++ b/azurerm/internal/services/cosmos/cosmosdb_account_resource.go @@ -136,7 +136,7 @@ func resourceCosmosDbAccount() *schema.Resource { Optional: true, ForceNew: true, DiffSuppressFunc: diffSuppressIgnoreKeyVaultKeyVersion, - ValidateFunc: keyVaultValidate.NestedItemIdWithOptionalVersion, + ValidateFunc: keyVaultValidate.VersionlessNestedItemId, }, "consistency_policy": { diff --git a/azurerm/internal/services/cosmos/cosmosdb_account_resource_test.go b/azurerm/internal/services/cosmos/cosmosdb_account_resource_test.go index 0ef89a1d6fa6..12ff2fcf283d 100644 --- a/azurerm/internal/services/cosmos/cosmosdb_account_resource_test.go +++ b/azurerm/internal/services/cosmos/cosmosdb_account_resource_test.go @@ -1133,7 +1133,11 @@ resource "azurerm_cosmosdb_account" "test" { func (CosmosDBAccountResource) key_vault_uri(data acceptance.TestData, kind documentdb.DatabaseAccountKind, consistency documentdb.DefaultConsistencyLevel) string { return fmt.Sprintf(` provider "azurerm" { - features {} + features { + key_vault { + purge_soft_delete_on_destroy = false + } + } } resource "azurerm_resource_group" "test" { @@ -1154,8 +1158,9 @@ resource "azurerm_key_vault" "test" { tenant_id = data.azurerm_client_config.current.tenant_id sku_name = "standard" - purge_protection_enabled = true - soft_delete_enabled = true + purge_protection_enabled = true + soft_delete_enabled = true + soft_delete_retention_days = 7 access_policy { tenant_id = data.azurerm_client_config.current.tenant_id @@ -1221,7 +1226,11 @@ resource "azurerm_cosmosdb_account" "test" { resource_group_name = azurerm_resource_group.test.name offer_type = "Standard" kind = "%s" - key_vault_key_id = azurerm_key_vault_key.test.id + key_vault_key_id = azurerm_key_vault_key.test.versionless_id + + capabilities { + name = "EnableMongo" + } consistency_policy { consistency_level = "%s" diff --git a/azurerm/internal/services/keyvault/key_vault_key_resource.go b/azurerm/internal/services/keyvault/key_vault_key_resource.go index caa0e623d1fa..0abd73bec0eb 100644 --- a/azurerm/internal/services/keyvault/key_vault_key_resource.go +++ b/azurerm/internal/services/keyvault/key_vault_key_resource.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "fmt" "log" + "strings" "time" "github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault" @@ -130,6 +131,11 @@ func resourceKeyVaultKey() *schema.Resource { Computed: true, }, + "versionless_id": { + Type: schema.TypeString, + Computed: true, + }, + "n": { Type: schema.TypeString, Computed: true, @@ -413,6 +419,7 @@ func resourceKeyVaultKeyRead(d *schema.ResourceData, meta interface{}) error { // Computed d.Set("version", id.Version) + d.Set("versionless_id", fmt.Sprintf("%s/%s/%s", strings.TrimSuffix(id.KeyVaultBaseUrl, "/"), id.NestedItemType, id.Name)) return tags.FlattenAndSet(d, resp.Tags) } diff --git a/azurerm/internal/services/keyvault/key_vault_key_resource_test.go b/azurerm/internal/services/keyvault/key_vault_key_resource_test.go index d777cdcb8a80..e3f72040fc45 100644 --- a/azurerm/internal/services/keyvault/key_vault_key_resource_test.go +++ b/azurerm/internal/services/keyvault/key_vault_key_resource_test.go @@ -125,6 +125,7 @@ func TestAccKeyVaultKey_complete(t *testing.T) { check.That(data.ResourceName).Key("expiration_date").HasValue("2021-01-01T01:02:03Z"), check.That(data.ResourceName).Key("tags.%").HasValue("1"), check.That(data.ResourceName).Key("tags.hello").HasValue("world"), + check.That(data.ResourceName).Key("versionless_id").HasValue(fmt.Sprintf("https://acctestkv-%s.vault.azure.net/keys/key-%s", data.RandomString, data.RandomString)), ), }, data.ImportStep("key_size"), diff --git a/azurerm/internal/services/keyvault/parse/nested_item.go b/azurerm/internal/services/keyvault/parse/nested_item.go index 3b3b1106751b..85519f571b17 100644 --- a/azurerm/internal/services/keyvault/parse/nested_item.go +++ b/azurerm/internal/services/keyvault/parse/nested_item.go @@ -37,7 +37,15 @@ func NewNestedItemID(keyVaultBaseUrl, nestedItemType, name, version string) (*Ne func (n NestedItemId) ID() string { // example: https://tharvey-keyvault.vault.azure.net/type/bird/fdf067c93bbb4b22bff4d8b7a9a56217 - return fmt.Sprintf("%s/%s/%s/%s", n.KeyVaultBaseUrl, n.NestedItemType, n.Name, n.Version) + segments := []string{ + strings.TrimSuffix(n.KeyVaultBaseUrl, "/"), + n.NestedItemType, + n.Name, + } + if n.Version != "" { + segments = append(segments, n.Version) + } + return strings.TrimSuffix(strings.Join(segments, "/"), "/") } // ParseNestedItemID parses a Key Vault Nested Item ID (such as a Certificate, Key or Secret) diff --git a/azurerm/internal/services/keyvault/parse/nested_item_test.go b/azurerm/internal/services/keyvault/parse/nested_item_test.go index db8d5b6287ab..41dfc87936ea 100644 --- a/azurerm/internal/services/keyvault/parse/nested_item_test.go +++ b/azurerm/internal/services/keyvault/parse/nested_item_test.go @@ -100,11 +100,11 @@ func TestParseNestedItemID(t *testing.T) { for _, tc := range cases { secretId, err := ParseNestedItemID(tc.Input) if err != nil { - if !tc.ExpectError { - t.Fatalf("Got error for ID '%s': %+v", tc.Input, err) + if tc.ExpectError { + continue } - return + t.Fatalf("Got error for ID '%s': %+v", tc.Input, err) } if secretId == nil { @@ -122,6 +122,10 @@ func TestParseNestedItemID(t *testing.T) { if tc.Expected.Version != secretId.Version { t.Fatalf("Expected 'Version' to be '%s', got '%s' for ID '%s'", tc.Expected.Version, secretId.Version, tc.Input) } + + if tc.Input != secretId.ID() { + t.Fatalf("Expected 'ID()' to be '%s', got '%s'", tc.Input, secretId.ID()) + } } } @@ -184,11 +188,11 @@ func TestParseOptionallyVersionedNestedItemID(t *testing.T) { for _, tc := range cases { secretId, err := ParseOptionallyVersionedNestedItemID(tc.Input) if err != nil { - if !tc.ExpectError { - t.Fatalf("Got error for ID '%s': %+v", tc.Input, err) + if tc.ExpectError { + continue } - return + t.Fatalf("Got error for ID '%s': %+v", tc.Input, err) } if secretId == nil { @@ -206,5 +210,9 @@ func TestParseOptionallyVersionedNestedItemID(t *testing.T) { if tc.Expected.Version != secretId.Version { t.Fatalf("Expected 'Version' to be '%s', got '%s' for ID '%s'", tc.Expected.Version, secretId.Version, tc.Input) } + + if tc.Input != secretId.ID() { + t.Fatalf("Expected 'ID()' to be '%s', got '%s'", tc.Input, secretId.ID()) + } } } diff --git a/azurerm/internal/services/keyvault/validate/nested_item_id.go b/azurerm/internal/services/keyvault/validate/nested_item_id.go index 9ce3bc230337..b8474e34739e 100644 --- a/azurerm/internal/services/keyvault/validate/nested_item_id.go +++ b/azurerm/internal/services/keyvault/validate/nested_item_id.go @@ -26,6 +26,30 @@ func NestedItemId(i interface{}, k string) (warnings []string, errors []error) { return warnings, errors } +func VersionlessNestedItemId(i interface{}, k string) (warnings []string, errors []error) { + if warnings, errors = validation.StringIsNotEmpty(i, k); len(errors) > 0 { + return warnings, errors + } + + v, ok := i.(string) + if !ok { + errors = append(errors, fmt.Errorf("Expected %s to be a string!", k)) + return warnings, errors + } + + id, err := keyVaultParse.ParseOptionallyVersionedNestedItemID(v) + if err != nil { + errors = append(errors, fmt.Errorf("parsing %q: %s", v, err)) + return warnings, errors + } + + if id.Version != "" { + errors = append(errors, fmt.Errorf("expected %s to not have a version", k)) + } + + return warnings, errors +} + func NestedItemIdWithOptionalVersion(i interface{}, k string) (warnings []string, errors []error) { if warnings, errors = validation.StringIsNotEmpty(i, k); len(errors) > 0 { return warnings, errors diff --git a/azurerm/internal/services/keyvault/validate/nested_item_id_test.go b/azurerm/internal/services/keyvault/validate/nested_item_id_test.go index cc204df29370..74f2cb1e2ffe 100644 --- a/azurerm/internal/services/keyvault/validate/nested_item_id_test.go +++ b/azurerm/internal/services/keyvault/validate/nested_item_id_test.go @@ -57,6 +57,59 @@ func TestNestedItemId(t *testing.T) { } } +func TestVersionlessNestedItemId(t *testing.T) { + cases := []struct { + Input string + ExpectError bool + }{ + { + Input: "", + ExpectError: true, + }, + { + Input: "https://my-keyvault.vault.azure.net/secrets", + ExpectError: true, + }, + { + Input: "https://my-keyvault.vault.azure.net/secrets/bird", + ExpectError: false, + }, + { + Input: "https://my-keyvault.vault.azure.net/secrets/bird/fdf067c93bbb4b22bff4d8b7a9a56217", + ExpectError: true, + }, + { + Input: "https://my-keyvault.vault.azure.net/certificates/hello/world", + ExpectError: true, + }, + { + Input: "https://my-keyvault.vault.azure.net/keys/castle/1492", + ExpectError: true, + }, + { + Input: "https://my-keyvault.vault.azure.net/secrets/bird/fdf067c93bbb4b22bff4d8b7a9a56217/XXX", + ExpectError: true, + }, + } + + for _, tc := range cases { + warnings, err := VersionlessNestedItemId(tc.Input, "example") + if err != nil { + if tc.ExpectError { + continue + } + + t.Fatalf("Got error for input %q: %+v", tc.Input, err) + } + + if tc.ExpectError && len(warnings) == 0 { + t.Fatalf("Got no errors for input %q but expected some", tc.Input) + } else if !tc.ExpectError && len(warnings) > 0 { + t.Fatalf("Got %d errors for input %q when didn't expect any", len(warnings), tc.Input) + } + } +} + func TestNestedItemIdWithOptionalVersion(t *testing.T) { cases := []struct { Input string diff --git a/website/docs/d/key_vault_key.html.markdown b/website/docs/d/key_vault_key.html.markdown index d02dcc30b050..ff74c661f215 100644 --- a/website/docs/d/key_vault_key.html.markdown +++ b/website/docs/d/key_vault_key.html.markdown @@ -57,6 +57,8 @@ The following attributes are exported: * `version` - The current version of the Key Vault Key. +* `versionless_id` - The Base ID of the Key Vault Key. + ## Timeouts diff --git a/website/docs/r/cosmosdb_account.html.markdown b/website/docs/r/cosmosdb_account.html.markdown index 769c25a954be..2fab940be465 100644 --- a/website/docs/r/cosmosdb_account.html.markdown +++ b/website/docs/r/cosmosdb_account.html.markdown @@ -96,9 +96,10 @@ The following arguments are supported: * `is_virtual_network_filter_enabled` - (Optional) Enables virtual network filtering for this Cosmos DB account. -* `key_vault_key_id` - (Optional) A Key Vault Key ID for CMK encryption. Changing this forces a new resource to be created. +* `key_vault_key_id` - (Optional) A versionless Key Vault Key ID for CMK encryption. Changing this forces a new resource to be created. + +~> **NOTE:** When referencing an `azurerm_key_vault_key` resource, use `versionless_id` instead of `id` -~> **NOTE:** The CosmosDB service always uses the latest version of the specified key, so terraform ignores the version specified in the Key Vault Key ID. ~> **NOTE:** In order to use a `Custom Key` from Key Vault for encryption you must grant Azure Cosmos DB Service access to your key vault. For instuctions on how to configure your Key Vault correctly please refer to the [product documentation](https://docs.microsoft.com/en-us/azure/cosmos-db/how-to-setup-cmk#add-an-access-policy-to-your-azure-key-vault-instance) * `virtual_network_rule` - (Optional) Specifies a `virtual_network_rules` resource, used to define which subnets are allowed to access this CosmosDB account. diff --git a/website/docs/r/key_vault_key.html.markdown b/website/docs/r/key_vault_key.html.markdown index 15a944c16b1f..54f089a9d0b8 100644 --- a/website/docs/r/key_vault_key.html.markdown +++ b/website/docs/r/key_vault_key.html.markdown @@ -91,6 +91,7 @@ The following attributes are exported: * `id` - The Key Vault Key ID. * `version` - The current version of the Key Vault Key. +* `versionless_id` - The Base ID of the Key Vault Key. * `n` - The RSA modulus of this Key Vault Key. * `e` - The RSA public exponent of this Key Vault Key. * `x` - The EC X component of this Key Vault Key.