diff --git a/azurerm/internal/services/apimanagement/api_management_named_value_resource.go b/azurerm/internal/services/apimanagement/api_management_named_value_resource.go index 870d199a9d9a..2667474a40a4 100644 --- a/azurerm/internal/services/apimanagement/api_management_named_value_resource.go +++ b/azurerm/internal/services/apimanagement/api_management_named_value_resource.go @@ -11,6 +11,7 @@ import ( "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/apimanagement/parse" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/apimanagement/schemaz" + keyVaultValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/keyvault/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" @@ -46,11 +47,33 @@ func resourceApiManagementNamedValue() *pluginsdk.Resource { ValidateFunc: validation.StringIsNotEmpty, }, + "value_from_key_vault": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: []string{"value", "value_from_key_vault"}, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "secret_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: keyVaultValidate.NestedItemId, + }, + "identity_client_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.IsUUID, + }, + }, + }, + }, + "value": { Type: pluginsdk.TypeString, - Required: true, + Optional: true, Sensitive: true, ValidateFunc: validation.StringIsNotEmpty, + ExactlyOneOf: []string{"value", "value_from_key_vault"}, }, "secret": { @@ -96,10 +119,14 @@ func resourceApiManagementNamedValueCreateUpdate(d *pluginsdk.ResourceData, meta NamedValueCreateContractProperties: &apimanagement.NamedValueCreateContractProperties{ DisplayName: utils.String(d.Get("display_name").(string)), Secret: utils.Bool(d.Get("secret").(bool)), - Value: utils.String(d.Get("value").(string)), + KeyVault: expandApiManagementNamedValueKeyVault(d.Get("value_from_key_vault").([]interface{})), }, } + if v, ok := d.GetOk("value"); ok { + parameters.NamedValueCreateContractProperties.Value = utils.String(v.(string)) + } + if tags, ok := d.GetOk("tags"); ok { parameters.NamedValueCreateContractProperties.Tags = utils.ExpandStringSlice(tags.([]interface{})) } @@ -160,6 +187,9 @@ func resourceApiManagementNamedValueRead(d *pluginsdk.ResourceData, meta interfa if properties.Secret != nil && !*properties.Secret { d.Set("value", properties.Value) } + if err := d.Set("value_from_key_vault", flattenApiManagementNamedValueKeyVault(properties.KeyVault)); err != nil { + return fmt.Errorf("setting `value_from_key_vault`: %+v", err) + } d.Set("tags", properties.Tags) } @@ -187,3 +217,37 @@ func resourceApiManagementNamedValueDelete(d *pluginsdk.ResourceData, meta inter return nil } + +func expandApiManagementNamedValueKeyVault(inputs []interface{}) *apimanagement.KeyVaultContractCreateProperties { + if len(inputs) == 0 { + return nil + } + input := inputs[0].(map[string]interface{}) + + return &apimanagement.KeyVaultContractCreateProperties{ + SecretIdentifier: utils.String(input["secret_id"].(string)), + IdentityClientID: utils.String(input["identity_client_id"].(string)), + } +} + +func flattenApiManagementNamedValueKeyVault(input *apimanagement.KeyVaultContractProperties) []interface{} { + if input == nil { + return []interface{}{} + } + + var secretId, clientId string + if input.SecretIdentifier != nil { + secretId = *input.SecretIdentifier + } + + if input.IdentityClientID != nil { + clientId = *input.IdentityClientID + } + + return []interface{}{ + map[string]interface{}{ + "secret_id": secretId, + "identity_client_id": clientId, + }, + } +} diff --git a/azurerm/internal/services/apimanagement/api_management_named_value_resource_test.go b/azurerm/internal/services/apimanagement/api_management_named_value_resource_test.go index dce67ced2d3d..481c1d721c28 100644 --- a/azurerm/internal/services/apimanagement/api_management_named_value_resource_test.go +++ b/azurerm/internal/services/apimanagement/api_management_named_value_resource_test.go @@ -31,6 +31,50 @@ func TestAccApiManagementNamedValue_basic(t *testing.T) { }) } +func TestAccApiManagementNamedValue_keyVault(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_api_management_named_value", "test") + r := ApiManagementNamedValueResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.keyVault(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccApiManagementNamedValue_keyVaultUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_api_management_named_value", "test") + r := ApiManagementNamedValueResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.keyVault(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.keyVaultUpdate(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.keyVaultUpdateToValue(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccApiManagementNamedValue_update(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_api_management_named_value", "test") r := ApiManagementNamedValueResource{} @@ -70,7 +114,7 @@ func (ApiManagementNamedValueResource) Exists(ctx context.Context, clients *clie return utils.Bool(resp.ID != nil), nil } -func (ApiManagementNamedValueResource) basic(data acceptance.TestData) string { +func (ApiManagementNamedValueResource) template(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { features {} @@ -90,6 +134,12 @@ resource "azurerm_api_management" "test" { sku_name = "Consumption_0" } +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} + +func (r ApiManagementNamedValueResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s resource "azurerm_api_management_named_value" "test" { name = "acctestAMProperty-%d" @@ -99,38 +149,186 @@ resource "azurerm_api_management_named_value" "test" { value = "Test Value" tags = ["tag1", "tag2"] } -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) +`, r.template(data), data.RandomInteger, data.RandomInteger) +} + +func (r ApiManagementNamedValueResource) update(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_api_management_named_value" "test" { + name = "acctestAMProperty-%d" + resource_group_name = azurerm_resource_group.test.name + api_management_name = azurerm_api_management.test.name + display_name = "TestProperty2%d" + value = "Test Value2" + secret = true + tags = ["tag3", "tag4"] +} +`, r.template(data), data.RandomInteger, data.RandomInteger) } -func (ApiManagementNamedValueResource) update(data acceptance.TestData) string { +func (r ApiManagementNamedValueResource) keyVaultTemplate(data acceptance.TestData) string { return fmt.Sprintf(` provider "azurerm" { - features {} + features { + key_vault { + purge_soft_delete_on_destroy = true + } + } } resource "azurerm_resource_group" "test" { - name = "acctestRG-%d" - location = "%s" + name = "acctestRG-Apim-%[1]d" + location = "%[2]s" +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acctestUAI-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name } resource "azurerm_api_management" "test" { - name = "acctestAM-%d" + name = "acctestAM-%[1]d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name publisher_name = "pub1" publisher_email = "pub1@email.com" sku_name = "Consumption_0" + + identity { + type = "UserAssigned" + identity_ids = [ + azurerm_user_assigned_identity.test.id, + ] + } +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_key_vault" "test" { + name = "acctestKV-%[3]s" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "standard" +} + +resource "azurerm_key_vault_access_policy" "test" { + key_vault_id = azurerm_key_vault.test.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + certificate_permissions = [ + "Create", + "Delete", + "Deleteissuers", + "Get", + "Getissuers", + "Import", + "List", + "Listissuers", + "Managecontacts", + "Manageissuers", + "Setissuers", + "Update", + "Purge", + ] + secret_permissions = [ + "Get", + "Delete", + "List", + "Purge", + "Recover", + "Set", + ] +} + +resource "azurerm_key_vault_access_policy" "test2" { + key_vault_id = azurerm_key_vault.test.id + tenant_id = azurerm_user_assigned_identity.test.tenant_id + object_id = azurerm_user_assigned_identity.test.principal_id + secret_permissions = [ + "Get", + "List", + ] +} + +resource "azurerm_key_vault_secret" "test" { + name = "secret-%[3]s" + value = "rick-and-morty" + key_vault_id = azurerm_key_vault.test.id + + depends_on = [azurerm_key_vault_access_policy.test] +} + +resource "azurerm_key_vault_secret" "test2" { + name = "secret2-%[3]s" + value = "rick-and-morty2" + key_vault_id = azurerm_key_vault.test.id + + depends_on = [azurerm_key_vault_access_policy.test] +} + +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} + +func (r ApiManagementNamedValueResource) keyVault(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +resource "azurerm_api_management_named_value" "test" { + name = "acctestAMProperty-%[2]d" + resource_group_name = azurerm_resource_group.test.name + api_management_name = azurerm_api_management.test.name + display_name = "TestKeyVault%[2]d" + secret = true + value_from_key_vault { + secret_id = azurerm_key_vault_secret.test.id + identity_client_id = azurerm_user_assigned_identity.test.client_id + } + + tags = ["tag1", "tag2"] + + depends_on = [azurerm_key_vault_access_policy.test2] +} +`, r.keyVaultTemplate(data), data.RandomInteger) } +func (r ApiManagementNamedValueResource) keyVaultUpdate(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + resource "azurerm_api_management_named_value" "test" { - name = "acctestAMProperty-%d" - resource_group_name = azurerm_api_management.test.resource_group_name + name = "acctestAMProperty-%[2]d" + resource_group_name = azurerm_resource_group.test.name api_management_name = azurerm_api_management.test.name - display_name = "TestProperty2%d" - value = "Test Value2" + display_name = "TestKeyVault%[2]d" secret = true - tags = ["tag3", "tag4"] + value_from_key_vault { + secret_id = azurerm_key_vault_secret.test2.id + identity_client_id = azurerm_user_assigned_identity.test.client_id + } + tags = ["tag3", "tag4"] + + depends_on = [azurerm_key_vault_access_policy.test2] +} +`, r.keyVaultTemplate(data), data.RandomInteger) +} + +func (r ApiManagementNamedValueResource) keyVaultUpdateToValue(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +resource "azurerm_api_management_named_value" "test" { + name = "acctestAMProperty-%[2]d" + resource_group_name = azurerm_resource_group.test.name + api_management_name = azurerm_api_management.test.name + display_name = "TestKeyVault%[2]d" + secret = false + value = "Key Vault to Value" + tags = ["tag5", "tag6"] } -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) +`, r.keyVaultTemplate(data), data.RandomInteger) } diff --git a/website/docs/r/api_management_named_value.html.markdown b/website/docs/r/api_management_named_value.html.markdown index 8fecd5feaf51..21178de2825a 100644 --- a/website/docs/r/api_management_named_value.html.markdown +++ b/website/docs/r/api_management_named_value.html.markdown @@ -51,7 +51,9 @@ The following arguments are supported: * `display_name` - (Required) The display name of this API Management Named Value. -* `value` - (Required) The value of this API Management Named Value. +* `value` - (Optional) The value of this API Management Named Value. + +* `value_from_key_vault` - (Optional) A `value_from_key_vault` block as defined below. * `secret` - (Optional) Specifies whether the API Management Named Value is secret. Valid values are `true` or `false`. The default value is `false`. @@ -59,6 +61,14 @@ The following arguments are supported: * `tags` - (Optional) A list of tags to be applied to the API Management Named Value. +--- + +A `value_from_key_vault` block supports the following: + +* `secret_id` - (Required) The resource ID of the Key Vault Secret. + +* `identity_client_id` - (Required) The client ID of the System Assigned Identity, or User Assigned Identity, for the API Management Service, which will be used to access the key vault secret. + ## Attributes Reference In addition to all arguments above, the following attributes are exported: