From 4675747f18a1d93f7dccbd921db76f6a35418f78 Mon Sep 17 00:00:00 2001 From: aled mehta delfino Date: Tue, 5 Sep 2023 08:07:10 -0400 Subject: [PATCH 01/11] adding keyvault secret reference support for container apps --- ...app_environment_dapr_component_resource.go | 2 +- ...container_app_environment_resource_test.go | 13 ++ .../containerapps/container_app_resource.go | 12 +- .../containerapps/helpers/container_apps.go | 146 +++++++++++++++--- website/docs/d/container_app.html.markdown | 6 +- website/docs/r/container_app.html.markdown | 8 +- 6 files changed, 161 insertions(+), 26 deletions(-) diff --git a/internal/services/containerapps/container_app_environment_dapr_component_resource.go b/internal/services/containerapps/container_app_environment_dapr_component_resource.go index 6ffe8538b74c..e37beffc0e33 100644 --- a/internal/services/containerapps/container_app_environment_dapr_component_resource.go +++ b/internal/services/containerapps/container_app_environment_dapr_component_resource.go @@ -27,7 +27,7 @@ type ContainerAppEnvironmentDaprComponentModel struct { Version string `tfschema:"version"` IgnoreErrors bool `tfschema:"ignore_errors"` InitTimeout string `tfschema:"init_timeout"` - Secrets []helpers.Secret `tfschema:"secret"` + Secrets []helpers.DaprSecret `tfschema:"secret"` Scopes []string `tfschema:"scopes"` Metadata []helpers.DaprMetadata `tfschema:"metadata"` } diff --git a/internal/services/containerapps/container_app_environment_resource_test.go b/internal/services/containerapps/container_app_environment_resource_test.go index c1c0bce55972..f3943f82b16a 100644 --- a/internal/services/containerapps/container_app_environment_resource_test.go +++ b/internal/services/containerapps/container_app_environment_resource_test.go @@ -194,6 +194,19 @@ resource "azurerm_container_app_environment" "test" { `, r.template(data), data.RandomInteger) } +func (r ContainerAppEnvironmentResource) basicNoProvider(data acceptance.TestData) string { + return fmt.Sprintf(` + +%[1]s + +resource "azurerm_container_app_environment" "test" { + name = "acctest-CAEnv%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} +`, r.template(data), data.RandomInteger) +} + func (r ContainerAppEnvironmentResource) requiresImport(data acceptance.TestData) string { return fmt.Sprintf(` diff --git a/internal/services/containerapps/container_app_resource.go b/internal/services/containerapps/container_app_resource.go index 833f38e5a312..66985fbaa3ce 100644 --- a/internal/services/containerapps/container_app_resource.go +++ b/internal/services/containerapps/container_app_resource.go @@ -191,13 +191,18 @@ func (r ContainerAppResource) Create() sdk.ResourceFunc { return fmt.Errorf("invalid registry config for %s: %+v", id, err) } + secrets, err := helpers.ExpandContainerSecrets(app.Secrets) + if err != nil { + return fmt.Errorf("invalid secrets config for %s: %+v", id, err) + } + containerApp := containerapps.ContainerApp{ Location: location.Normalize(env.Model.Location), Properties: &containerapps.ContainerAppProperties{ Configuration: &containerapps.Configuration{ Ingress: helpers.ExpandContainerAppIngress(app.Ingress, id.ContainerAppName), Dapr: helpers.ExpandContainerAppDapr(app.Dapr), - Secrets: helpers.ExpandContainerSecrets(app.Secrets), + Secrets: secrets, Registries: registries, }, ManagedEnvironmentId: pointer.To(app.ManagedEnvironmentId), @@ -387,7 +392,10 @@ func (r ContainerAppResource) Update() sdk.ResourceFunc { } if metadata.ResourceData.HasChange("secret") { - model.Properties.Configuration.Secrets = helpers.ExpandContainerSecrets(state.Secrets) + model.Properties.Configuration.Secrets, err = helpers.ExpandContainerSecrets(state.Secrets) + if err != nil { + return fmt.Errorf("invalid secrets config for %s: %+v", id, err) + } } if metadata.ResourceData.HasChange("identity") { diff --git a/internal/services/containerapps/helpers/container_apps.go b/internal/services/containerapps/helpers/container_apps.go index ba630c970b14..60701ede2453 100644 --- a/internal/services/containerapps/helpers/container_apps.go +++ b/internal/services/containerapps/helpers/container_apps.go @@ -2545,8 +2545,10 @@ func expandContainerProbes(input Container) *[]containerapps.ContainerAppProbe { } type Secret struct { - Name string `tfschema:"name"` - Value string `tfschema:"value"` + Identity string `tfschema:"identity"` + KeyVaultUrl string `tfschema:"key_vault_url"` + Name string `tfschema:"name"` + Value string `tfschema:"value"` } func SecretsSchema() *pluginsdk.Schema { @@ -2556,6 +2558,20 @@ func SecretsSchema() *pluginsdk.Schema { Sensitive: true, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ + "identity": { + Type: pluginsdk.TypeString, + Optional: true, + Sensitive: false, + Description: "The identity to use for accessing key vault reference.", + }, + + "key_vault_url": { + Type: pluginsdk.TypeString, + Optional: true, + Sensitive: true, + Description: "The key vault reference URL for this secret.", + }, + "name": { Type: pluginsdk.TypeString, Required: true, @@ -2566,7 +2582,8 @@ func SecretsSchema() *pluginsdk.Schema { "value": { Type: pluginsdk.TypeString, - Required: true, + Optional: true, + Computed: true, Sensitive: true, Description: "The value for this secret.", }, @@ -2582,6 +2599,20 @@ func SecretsDataSourceSchema() *pluginsdk.Schema { Sensitive: true, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ + "identity": { + Type: pluginsdk.TypeString, + Computed: true, + Sensitive: false, + Description: "The identity to use for accessing key vault reference.", + }, + + "key_vault_url": { + Type: pluginsdk.TypeString, + Computed: true, + Sensitive: true, + Description: "The key vault reference URL for this secret.", + }, + "name": { Type: pluginsdk.TypeString, Computed: true, @@ -2599,21 +2630,36 @@ func SecretsDataSourceSchema() *pluginsdk.Schema { } } -func ExpandContainerSecrets(input []Secret) *[]containerapps.Secret { +func ValidateContainerSecret(s Secret) error { + if s.KeyVaultUrl != "" && s.Value != "" { + return fmt.Errorf("key vault url and value are mutually exclusive") + } + if s.KeyVaultUrl != "" && s.Identity == "" { + return fmt.Errorf("must supply identity for key vault url") + } + return nil +} + +func ExpandContainerSecrets(input []Secret) (*[]containerapps.Secret, error) { if len(input) == 0 { - return nil + return nil, nil } result := make([]containerapps.Secret, 0) for _, v := range input { + if err := ValidateContainerSecret(v); err != nil { + return nil, err + } result = append(result, containerapps.Secret{ - Name: pointer.To(v.Name), - Value: pointer.To(v.Value), + Identity: pointer.To(v.Identity), + KeyVaultUrl: pointer.To(v.KeyVaultUrl), + Name: pointer.To(v.Name), + Value: pointer.To(v.Value), }) } - return &result + return &result, nil } func ExpandFormerContainerSecrets(metadata sdk.ResourceMetaData) *[]containerapps.Secret { @@ -2624,8 +2670,10 @@ func ExpandFormerContainerSecrets(metadata sdk.ResourceMetaData) *[]containerapp for _, secret := range secrets { if v, ok := secret.(map[string]interface{}); ok { result = append(result, containerapps.Secret{ - Name: pointer.To(v["name"].(string)), - Value: pointer.To(v["value"].(string)), + Identity: pointer.To(v["Identity"].(string)), + KeyVaultUrl: pointer.To(v["KeyVaultUrl"].(string)), + Name: pointer.To(v["name"].(string)), + Value: pointer.To(v["value"].(string)), }) } } @@ -2655,7 +2703,6 @@ func UnpackContainerDaprSecretsCollection(input *daprcomponents.DaprSecretsColle result := make([]daprcomponents.Secret, 0) for _, v := range input.Value { result = append(result, daprcomponents.Secret{ - // TODO: add support for Identity & KeyVaultUrl Name: v.Name, Value: v.Value, }) @@ -2664,7 +2711,62 @@ func UnpackContainerDaprSecretsCollection(input *daprcomponents.DaprSecretsColle return &result } -func ExpandDaprSecrets(input []Secret) *[]daprcomponents.Secret { +type DaprSecret struct { + Name string `tfschema:"name"` + Value string `tfschema:"value"` +} + +func DaprSecretsSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeSet, + Optional: true, + Sensitive: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validate.SecretName, + Sensitive: true, + Description: "The Secret name.", + }, + + "value": { + Type: pluginsdk.TypeString, + Required: true, + Sensitive: true, + Description: "The value for this secret.", + }, + }, + }, + } +} + +func DaprSecretsDataSourceSchema() *pluginsdk.Schema { + return &pluginsdk.Schema{ + Type: pluginsdk.TypeList, + Computed: true, + Sensitive: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Computed: true, + Description: "The Secret name.", + }, + + "value": { + Type: pluginsdk.TypeString, + Computed: true, + Sensitive: true, + Description: "The value for this secret.", + }, + }, + }, + } +} + +func ExpandDaprSecrets(input []DaprSecret) *[]daprcomponents.Secret { if len(input) == 0 { return nil } @@ -2681,8 +2783,8 @@ func ExpandDaprSecrets(input []Secret) *[]daprcomponents.Secret { return &result } -func FlattenSecrets(input []interface{}) []Secret { - secrets := make([]Secret, 0) +func FlattenSecrets(input []interface{}) []DaprSecret { + secrets := make([]DaprSecret, 0) for _, s := range input { secret := s.(map[string]interface{}) name, ok := secret["name"].(string) @@ -2693,7 +2795,7 @@ func FlattenSecrets(input []interface{}) []Secret { if val, ok := secret["value"].(string); ok { value = val } - secrets = append(secrets, Secret{ + secrets = append(secrets, DaprSecret{ Name: name, Value: value, }) @@ -2709,21 +2811,23 @@ func FlattenContainerAppSecrets(input *containerapps.SecretsCollection) []Secret result := make([]Secret, 0) for _, v := range input.Value { result = append(result, Secret{ - Name: pointer.From(v.Name), - Value: pointer.From(v.Value), + Identity: pointer.From(v.Identity), + KeyVaultUrl: pointer.From(v.KeyVaultUrl), + Name: pointer.From(v.Name), + Value: pointer.From(v.Value), }) } return result } -func FlattenContainerAppDaprSecrets(input *daprcomponents.DaprSecretsCollection) []Secret { +func FlattenContainerAppDaprSecrets(input *daprcomponents.DaprSecretsCollection) []DaprSecret { if input == nil || input.Value == nil { - return []Secret{} + return []DaprSecret{} } - result := make([]Secret, 0) + result := make([]DaprSecret, 0) for _, v := range input.Value { - result = append(result, Secret{ + result = append(result, DaprSecret{ Name: pointer.From(v.Name), Value: pointer.From(v.Value), }) diff --git a/website/docs/d/container_app.html.markdown b/website/docs/d/container_app.html.markdown index 9757534374e6..724e44e02b06 100644 --- a/website/docs/d/container_app.html.markdown +++ b/website/docs/d/container_app.html.markdown @@ -55,7 +55,11 @@ In addition to the Arguments listed above - the following Attributes are exporte A `secret` block supports the following: -* `name` - The Secret name. +* `identity` - The identity used for accessing Key Vault reference. + +* `key_vault_url` - The URL to the Key Vault secret. + +* `name` - The secret name. * `value` - The value for this secret. diff --git a/website/docs/r/container_app.html.markdown b/website/docs/r/container_app.html.markdown index 6b16b0d333c9..25ab5887e19b 100644 --- a/website/docs/r/container_app.html.markdown +++ b/website/docs/r/container_app.html.markdown @@ -85,9 +85,15 @@ The following arguments are supported: A `secret` block supports the following: +* `identity` - (Optional) The identity to use for accessing Key Vault reference (Required if `key_vault_url` is used). + +* `key_vault_url` - (Optional) The URL to a Key Vault secret. + * `name` - (Required) The Secret name. -* `value` - (Required) The value for this secret. +* `value` - (Optional) The value for this secret. + +!> **Note:** `value` and `key_vault_url`/`identity` are mutually exclusive. !> **Note:** Secrets cannot be removed from the service once added, attempting to do so will result in an error. Their values may be zeroed, i.e. set to `""`, but the named secret must persist. This is due to a technical limitation on the service which causes the service to become unmanageable. See [this issue](https://github.com/microsoft/azure-container-apps/issues/395) for more details. From e848760850f9517b939e4b4e5a6e681a14c4dacf Mon Sep 17 00:00:00 2001 From: aled mehta delfino Date: Tue, 5 Sep 2023 11:42:30 -0400 Subject: [PATCH 02/11] removing erroneous newline --- .../containerapps/container_app_environment_resource_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/services/containerapps/container_app_environment_resource_test.go b/internal/services/containerapps/container_app_environment_resource_test.go index f3943f82b16a..739b90a4a1ba 100644 --- a/internal/services/containerapps/container_app_environment_resource_test.go +++ b/internal/services/containerapps/container_app_environment_resource_test.go @@ -196,7 +196,6 @@ resource "azurerm_container_app_environment" "test" { func (r ContainerAppEnvironmentResource) basicNoProvider(data acceptance.TestData) string { return fmt.Sprintf(` - %[1]s resource "azurerm_container_app_environment" "test" { From 1d0a76bb2edd11e407f4c49acf14d0f76f18979f Mon Sep 17 00:00:00 2001 From: aled Date: Wed, 22 Nov 2023 16:03:21 -0500 Subject: [PATCH 03/11] updating primitive behaviours, secrets to list, renaming key_vault_secret_id, added validation for MI --- .../containerapps/container_app_resource.go | 5 +- .../containerapps/helpers/container_apps.go | 85 ++++++++++--------- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/internal/services/containerapps/container_app_resource.go b/internal/services/containerapps/container_app_resource.go index 66985fbaa3ce..e8ac82093257 100644 --- a/internal/services/containerapps/container_app_resource.go +++ b/internal/services/containerapps/container_app_resource.go @@ -17,7 +17,6 @@ import ( "github.com/hashicorp/go-azure-helpers/resourcemanager/tags" "github.com/hashicorp/go-azure-sdk/resource-manager/containerapps/2023-05-01/containerapps" "github.com/hashicorp/go-azure-sdk/resource-manager/containerapps/2023-05-01/managedenvironments" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" "github.com/hashicorp/terraform-provider-azurerm/internal/services/containerapps/helpers" "github.com/hashicorp/terraform-provider-azurerm/internal/services/containerapps/validate" @@ -475,8 +474,8 @@ func (r ContainerAppResource) CustomizeDiff() sdk.ResourceFunc { if metadata.ResourceDiff.HasChange("secret") { stateSecretsRaw, configSecretsRaw := metadata.ResourceDiff.GetChange("secret") - stateSecrets := stateSecretsRaw.(*schema.Set).List() - configSecrets := configSecretsRaw.(*schema.Set).List() + stateSecrets := stateSecretsRaw.([]interface{}) + configSecrets := configSecretsRaw.([]interface{}) // Check there's not less if len(configSecrets) < len(stateSecrets) { return fmt.Errorf("cannot remove secrets from Container Apps at this time due to a limitation in the Container Apps Service. Please see `https://github.com/microsoft/azure-container-apps/issues/395` for more details") diff --git a/internal/services/containerapps/helpers/container_apps.go b/internal/services/containerapps/helpers/container_apps.go index 60701ede2453..8d2eb23a05e9 100644 --- a/internal/services/containerapps/helpers/container_apps.go +++ b/internal/services/containerapps/helpers/container_apps.go @@ -8,11 +8,13 @@ import ( "strings" "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonids" "github.com/hashicorp/go-azure-sdk/resource-manager/containerapps/2023-05-01/containerapps" "github.com/hashicorp/go-azure-sdk/resource-manager/containerapps/2023-05-01/daprcomponents" "github.com/hashicorp/go-azure-sdk/resource-manager/containerapps/2023-05-01/managedenvironments" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" "github.com/hashicorp/terraform-provider-azurerm/internal/services/containerapps/validate" + keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" ) @@ -2545,45 +2547,46 @@ func expandContainerProbes(input Container) *[]containerapps.ContainerAppProbe { } type Secret struct { - Identity string `tfschema:"identity"` - KeyVaultUrl string `tfschema:"key_vault_url"` - Name string `tfschema:"name"` - Value string `tfschema:"value"` + Identity string `tfschema:"identity"` + KeyVaultSecretId string `tfschema:"key_vault_secret_id"` + Name string `tfschema:"name"` + Value string `tfschema:"value"` } func SecretsSchema() *pluginsdk.Schema { return &pluginsdk.Schema{ - Type: pluginsdk.TypeSet, + Type: pluginsdk.TypeList, Optional: true, Sensitive: true, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ "identity": { - Type: pluginsdk.TypeString, - Optional: true, - Sensitive: false, + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.Any( + commonids.ValidateUserAssignedIdentityID, + validation.StringInSlice([]string{"system"}, true), + ), Description: "The identity to use for accessing key vault reference.", }, - "key_vault_url": { - Type: pluginsdk.TypeString, - Optional: true, - Sensitive: true, - Description: "The key vault reference URL for this secret.", + "key_vault_secret_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: keyVaultValidate.NestedItemIdWithOptionalVersion, + Description: "The id of the key vault secret.", }, "name": { Type: pluginsdk.TypeString, Required: true, ValidateFunc: validate.SecretName, - Sensitive: true, - Description: "The Secret name.", + Description: "The secret name.", }, "value": { Type: pluginsdk.TypeString, Optional: true, - Computed: true, Sensitive: true, Description: "The value for this secret.", }, @@ -2601,27 +2604,25 @@ func SecretsDataSourceSchema() *pluginsdk.Schema { Schema: map[string]*pluginsdk.Schema{ "identity": { Type: pluginsdk.TypeString, - Computed: true, - Sensitive: false, + Optional: true, Description: "The identity to use for accessing key vault reference.", }, - "key_vault_url": { + "key_vault_secret_id": { Type: pluginsdk.TypeString, - Computed: true, - Sensitive: true, - Description: "The key vault reference URL for this secret.", + Optional: true, + Description: "The id of the key vault secret.", }, "name": { Type: pluginsdk.TypeString, - Computed: true, - Description: "The Secret name.", + Required: true, + Description: "The secret name.", }, "value": { Type: pluginsdk.TypeString, - Computed: true, + Optional: true, Sensitive: true, Description: "The value for this secret.", }, @@ -2630,12 +2631,15 @@ func SecretsDataSourceSchema() *pluginsdk.Schema { } } -func ValidateContainerSecret(s Secret) error { - if s.KeyVaultUrl != "" && s.Value != "" { - return fmt.Errorf("key vault url and value are mutually exclusive") +func validateContainerSecret(s Secret) error { + if s.KeyVaultSecretId != "" && s.Value != "" { + return fmt.Errorf("key vault secret id and value are mutually exclusive") + } + if s.KeyVaultSecretId != "" && s.Identity == "" { + return fmt.Errorf("must supply identity for key vault secret id") } - if s.KeyVaultUrl != "" && s.Identity == "" { - return fmt.Errorf("must supply identity for key vault url") + if s.KeyVaultSecretId == "" && s.Identity != "" { + return fmt.Errorf("must supply key vault secret id when specifying identity") } return nil } @@ -2648,12 +2652,12 @@ func ExpandContainerSecrets(input []Secret) (*[]containerapps.Secret, error) { result := make([]containerapps.Secret, 0) for _, v := range input { - if err := ValidateContainerSecret(v); err != nil { + if err := validateContainerSecret(v); err != nil { return nil, err } result = append(result, containerapps.Secret{ Identity: pointer.To(v.Identity), - KeyVaultUrl: pointer.To(v.KeyVaultUrl), + KeyVaultUrl: pointer.To(v.KeyVaultSecretId), Name: pointer.To(v.Name), Value: pointer.To(v.Value), }) @@ -2727,8 +2731,7 @@ func DaprSecretsSchema() *pluginsdk.Schema { Type: pluginsdk.TypeString, Required: true, ValidateFunc: validate.SecretName, - Sensitive: true, - Description: "The Secret name.", + Description: "The secret name.", }, "value": { @@ -2751,13 +2754,13 @@ func DaprSecretsDataSourceSchema() *pluginsdk.Schema { Schema: map[string]*pluginsdk.Schema{ "name": { Type: pluginsdk.TypeString, - Computed: true, - Description: "The Secret name.", + Required: true, + Description: "The secret name.", }, "value": { Type: pluginsdk.TypeString, - Computed: true, + Required: true, Sensitive: true, Description: "The value for this secret.", }, @@ -2811,10 +2814,10 @@ func FlattenContainerAppSecrets(input *containerapps.SecretsCollection) []Secret result := make([]Secret, 0) for _, v := range input.Value { result = append(result, Secret{ - Identity: pointer.From(v.Identity), - KeyVaultUrl: pointer.From(v.KeyVaultUrl), - Name: pointer.From(v.Name), - Value: pointer.From(v.Value), + Identity: pointer.From(v.Identity), + KeyVaultSecretId: pointer.From(v.KeyVaultUrl), + Name: pointer.From(v.Name), + Value: pointer.From(v.Value), }) } From 5faa1d84f753846ebc10b0af893c9694dec3ddd2 Mon Sep 17 00:00:00 2001 From: aled Date: Wed, 22 Nov 2023 19:04:02 -0500 Subject: [PATCH 04/11] adding docs, updating tests, fixing secret data schemas --- .../containerapps/helpers/container_apps.go | 15 ++++++--------- website/docs/d/container_app.html.markdown | 4 ++-- website/docs/r/container_app.html.markdown | 12 ++++++++---- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/internal/services/containerapps/helpers/container_apps.go b/internal/services/containerapps/helpers/container_apps.go index 8d2eb23a05e9..deb3c8ed611f 100644 --- a/internal/services/containerapps/helpers/container_apps.go +++ b/internal/services/containerapps/helpers/container_apps.go @@ -2604,25 +2604,25 @@ func SecretsDataSourceSchema() *pluginsdk.Schema { Schema: map[string]*pluginsdk.Schema{ "identity": { Type: pluginsdk.TypeString, - Optional: true, + Computed: true, Description: "The identity to use for accessing key vault reference.", }, "key_vault_secret_id": { Type: pluginsdk.TypeString, - Optional: true, + Computed: true, Description: "The id of the key vault secret.", }, "name": { Type: pluginsdk.TypeString, - Required: true, + Computed: true, Description: "The secret name.", }, "value": { Type: pluginsdk.TypeString, - Optional: true, + Computed: true, Sensitive: true, Description: "The value for this secret.", }, @@ -2632,9 +2632,6 @@ func SecretsDataSourceSchema() *pluginsdk.Schema { } func validateContainerSecret(s Secret) error { - if s.KeyVaultSecretId != "" && s.Value != "" { - return fmt.Errorf("key vault secret id and value are mutually exclusive") - } if s.KeyVaultSecretId != "" && s.Identity == "" { return fmt.Errorf("must supply identity for key vault secret id") } @@ -2729,14 +2726,14 @@ func DaprSecretsSchema() *pluginsdk.Schema { Schema: map[string]*pluginsdk.Schema{ "name": { Type: pluginsdk.TypeString, - Required: true, + Computed: true, ValidateFunc: validate.SecretName, Description: "The secret name.", }, "value": { Type: pluginsdk.TypeString, - Required: true, + Computed: true, Sensitive: true, Description: "The value for this secret.", }, diff --git a/website/docs/d/container_app.html.markdown b/website/docs/d/container_app.html.markdown index 724e44e02b06..5ce72c8c17de 100644 --- a/website/docs/d/container_app.html.markdown +++ b/website/docs/d/container_app.html.markdown @@ -55,9 +55,9 @@ In addition to the Arguments listed above - the following Attributes are exporte A `secret` block supports the following: -* `identity` - The identity used for accessing Key Vault reference. +* `identity` - The identity to use for accessing Key Vault reference. This can either be the Resource ID of a User Assigned Identity, or 'System' for the System Assigned Identity. -* `key_vault_url` - The URL to the Key Vault secret. +* `key_vault_secret_id` - The ID of a Key Vault secret. This can be a versioned or version-less ID. * `name` - The secret name. diff --git a/website/docs/r/container_app.html.markdown b/website/docs/r/container_app.html.markdown index 25ab5887e19b..c2cf0513dcc2 100644 --- a/website/docs/r/container_app.html.markdown +++ b/website/docs/r/container_app.html.markdown @@ -85,15 +85,19 @@ The following arguments are supported: A `secret` block supports the following: -* `identity` - (Optional) The identity to use for accessing Key Vault reference (Required if `key_vault_url` is used). +* `identity` - (Optional) The identity to use for accessing the Key Vault secret reference. This can either be the Resource ID of a User Assigned Identity, or 'System' for the System Assigned Identity. -* `key_vault_url` - (Optional) The URL to a Key Vault secret. +!> **Note:** `identity` must be used together with `key_vault_secret_id` -* `name` - (Required) The Secret name. +* `key_vault_secret_id` - (Optional) The ID of a Key Vault secret. This can be a versioned or version-less ID. + +!> **Note:** When using `key_vault_secret_id`, `ignore_changes` should be used to ignore any changes to `value`. + +* `name` - (Required) The secret name. * `value` - (Optional) The value for this secret. -!> **Note:** `value` and `key_vault_url`/`identity` are mutually exclusive. +!> **Note:** `value` will be ignored if `key_vault_secret_id` and `identity` are provided. !> **Note:** Secrets cannot be removed from the service once added, attempting to do so will result in an error. Their values may be zeroed, i.e. set to `""`, but the named secret must persist. This is due to a technical limitation on the service which causes the service to become unmanageable. See [this issue](https://github.com/microsoft/azure-container-apps/issues/395) for more details. From 908bac5f18030fea4f68a6b3e0361aeb06945ecf Mon Sep 17 00:00:00 2001 From: aled Date: Wed, 22 Nov 2023 19:36:27 -0500 Subject: [PATCH 05/11] fixing dapr secrets schema --- internal/services/containerapps/helpers/container_apps.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/services/containerapps/helpers/container_apps.go b/internal/services/containerapps/helpers/container_apps.go index deb3c8ed611f..e1d4caf3dc37 100644 --- a/internal/services/containerapps/helpers/container_apps.go +++ b/internal/services/containerapps/helpers/container_apps.go @@ -2726,14 +2726,14 @@ func DaprSecretsSchema() *pluginsdk.Schema { Schema: map[string]*pluginsdk.Schema{ "name": { Type: pluginsdk.TypeString, - Computed: true, + Required: true, ValidateFunc: validate.SecretName, Description: "The secret name.", }, "value": { Type: pluginsdk.TypeString, - Computed: true, + Required: true, Sensitive: true, Description: "The value for this secret.", }, @@ -2751,13 +2751,13 @@ func DaprSecretsDataSourceSchema() *pluginsdk.Schema { Schema: map[string]*pluginsdk.Schema{ "name": { Type: pluginsdk.TypeString, - Required: true, + Computed: true, Description: "The secret name.", }, "value": { Type: pluginsdk.TypeString, - Required: true, + Computed: true, Sensitive: true, Description: "The value for this secret.", }, From 511fedc75b452073e8247ff0198f8a033584936a Mon Sep 17 00:00:00 2001 From: delfino <67192579+x-delfino@users.noreply.github.com> Date: Tue, 19 Dec 2023 19:52:09 +0000 Subject: [PATCH 06/11] Update container app secret identity 'System' case sensitivity Co-authored-by: jackofallops <11830746+jackofallops@users.noreply.github.com> --- internal/services/containerapps/helpers/container_apps.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/containerapps/helpers/container_apps.go b/internal/services/containerapps/helpers/container_apps.go index e1d4caf3dc37..acbdc9f7b8c0 100644 --- a/internal/services/containerapps/helpers/container_apps.go +++ b/internal/services/containerapps/helpers/container_apps.go @@ -2565,7 +2565,7 @@ func SecretsSchema() *pluginsdk.Schema { Optional: true, ValidateFunc: validation.Any( commonids.ValidateUserAssignedIdentityID, - validation.StringInSlice([]string{"system"}, true), + validation.StringInSlice([]string{"System"}, false), ), Description: "The identity to use for accessing key vault reference.", }, From bddecb3ea41e3a9169c73db445582807dd883492 Mon Sep 17 00:00:00 2001 From: aled Date: Thu, 4 Jan 2024 14:16:13 -0500 Subject: [PATCH 07/11] reverting to set. dont store kv secret value --- .../containerapps/container_app_resource.go | 5 +++-- .../containerapps/helpers/container_apps.go | 13 ++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/internal/services/containerapps/container_app_resource.go b/internal/services/containerapps/container_app_resource.go index e8ac82093257..66985fbaa3ce 100644 --- a/internal/services/containerapps/container_app_resource.go +++ b/internal/services/containerapps/container_app_resource.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/go-azure-helpers/resourcemanager/tags" "github.com/hashicorp/go-azure-sdk/resource-manager/containerapps/2023-05-01/containerapps" "github.com/hashicorp/go-azure-sdk/resource-manager/containerapps/2023-05-01/managedenvironments" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" "github.com/hashicorp/terraform-provider-azurerm/internal/services/containerapps/helpers" "github.com/hashicorp/terraform-provider-azurerm/internal/services/containerapps/validate" @@ -474,8 +475,8 @@ func (r ContainerAppResource) CustomizeDiff() sdk.ResourceFunc { if metadata.ResourceDiff.HasChange("secret") { stateSecretsRaw, configSecretsRaw := metadata.ResourceDiff.GetChange("secret") - stateSecrets := stateSecretsRaw.([]interface{}) - configSecrets := configSecretsRaw.([]interface{}) + stateSecrets := stateSecretsRaw.(*schema.Set).List() + configSecrets := configSecretsRaw.(*schema.Set).List() // Check there's not less if len(configSecrets) < len(stateSecrets) { return fmt.Errorf("cannot remove secrets from Container Apps at this time due to a limitation in the Container Apps Service. Please see `https://github.com/microsoft/azure-container-apps/issues/395` for more details") diff --git a/internal/services/containerapps/helpers/container_apps.go b/internal/services/containerapps/helpers/container_apps.go index acbdc9f7b8c0..90d1a392e87f 100644 --- a/internal/services/containerapps/helpers/container_apps.go +++ b/internal/services/containerapps/helpers/container_apps.go @@ -2555,7 +2555,7 @@ type Secret struct { func SecretsSchema() *pluginsdk.Schema { return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, + Type: pluginsdk.TypeSet, Optional: true, Sensitive: true, Elem: &pluginsdk.Resource{ @@ -2597,7 +2597,7 @@ func SecretsSchema() *pluginsdk.Schema { func SecretsDataSourceSchema() *pluginsdk.Schema { return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, + Type: pluginsdk.TypeSet, Computed: true, Sensitive: true, Elem: &pluginsdk.Resource{ @@ -2810,12 +2810,15 @@ func FlattenContainerAppSecrets(input *containerapps.SecretsCollection) []Secret } result := make([]Secret, 0) for _, v := range input.Value { - result = append(result, Secret{ + secret := Secret{ Identity: pointer.From(v.Identity), KeyVaultSecretId: pointer.From(v.KeyVaultUrl), Name: pointer.From(v.Name), - Value: pointer.From(v.Value), - }) + } + if v.KeyVaultUrl == nil { + secret.Value = pointer.From(v.Value) + } + result = append(result, secret) } return result From 737d373212dff36f92483728b94074978d980802 Mon Sep 17 00:00:00 2001 From: hezijie Date: Tue, 6 Feb 2024 16:02:49 +0800 Subject: [PATCH 08/11] fix test --- .../container_app_resource_test.go | 572 ++++++++++++++++++ .../containerapps/helpers/container_apps.go | 23 +- 2 files changed, 573 insertions(+), 22 deletions(-) diff --git a/internal/services/containerapps/container_app_resource_test.go b/internal/services/containerapps/container_app_resource_test.go index e3440f999d60..6b5b769e4306 100644 --- a/internal/services/containerapps/container_app_resource_test.go +++ b/internal/services/containerapps/container_app_resource_test.go @@ -168,6 +168,57 @@ func TestAccContainerAppResource_withIdentityUpdate(t *testing.T) { }) } +func TestAccContainerAppResource_withKeyVaultSecretVersioningUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_container_app", "test") + r := ContainerAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withKeyVaultSecret(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withKeyVaultSecretVersionless(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccContainerAppResource_withKeyVaultSecretIdentityUpdate(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_container_app", "test") + r := ContainerAppResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.withKeyVaultSecretUserIdentity(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withKeyVaultSecretSystemIdentity(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.withKeyVaultSecretSystemAndUserIdentity(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccContainerAppResource_basicUpdate(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_container_app", "test") r := ContainerAppResource{} @@ -663,6 +714,523 @@ resource "azurerm_container_app" "test" { `, r.template(data), data.RandomInteger) } +func (r ContainerAppResource) withKeyVaultSecret(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = true + recover_soft_deleted_key_vaults = true + } + } +} + +%[1]s + +data "azurerm_client_config" "current" {} + +resource "azurerm_key_vault" "test" { + name = "acctest-kv-%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + soft_delete_retention_days = 7 + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + key_permissions = [ + "Create", + "Get", + ] + secret_permissions = [ + "Set", + "Get", + "Delete", + "Purge", + "Recover" + ] + } + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = azurerm_user_assigned_identity.test.principal_id + secret_permissions = [ + "Get", + ] + } +} + +resource "azurerm_key_vault_secret" "test" { + name = "secret-%[3]s" + value = "test-secret" + key_vault_id = azurerm_key_vault.test.id +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_container_app" "test" { + name = "acctest-capp-%[2]d" + resource_group_name = azurerm_resource_group.test.name + container_app_environment_id = azurerm_container_app_environment.test.id + revision_mode = "Single" + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.test.id] + } + + template { + container { + name = "acctest-cont-%[2]d" + image = "jackofallops/azure-containerapps-python-acctest:v0.0.1" + cpu = 0.25 + memory = "0.5Gi" + env { + name = "key-vault-secret" + secret_name = "key-vault-secret" + } + } + } + + secret { + name = "key-vault-secret" + identity = azurerm_user_assigned_identity.test.id + key_vault_secret_id = azurerm_key_vault_secret.test.id + } +} +`, r.templateNoProvider(data), data.RandomInteger, data.RandomString) +} + +func (r ContainerAppResource) withKeyVaultSecretVersionless(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = true + recover_soft_deleted_key_vaults = true + } + } +} + +%[1]s + +data "azurerm_client_config" "current" {} + +resource "azurerm_key_vault" "test" { + name = "acctest-kv-%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + soft_delete_retention_days = 7 + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + key_permissions = [ + "Create", + "Get", + ] + secret_permissions = [ + "Set", + "Get", + "Delete", + "Purge", + "Recover" + ] + } + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = azurerm_user_assigned_identity.test.principal_id + secret_permissions = [ + "Get", + ] + } +} + +resource "azurerm_key_vault_secret" "test" { + name = "secret-%[3]s" + value = "test-secret" + key_vault_id = azurerm_key_vault.test.id +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_container_app" "test" { + name = "acctest-capp-%[2]d" + resource_group_name = azurerm_resource_group.test.name + container_app_environment_id = azurerm_container_app_environment.test.id + revision_mode = "Single" + + identity { + type = "UserAssigned" + identity_ids = [azurerm_user_assigned_identity.test.id] + } + + template { + container { + name = "acctest-cont-%[2]d" + image = "jackofallops/azure-containerapps-python-acctest:v0.0.1" + cpu = 0.25 + memory = "0.5Gi" + env { + name = "key-vault-secret" + secret_name = "key-vault-secret" + } + } + } + + secret { + name = "key-vault-secret" + identity = azurerm_user_assigned_identity.test.id + key_vault_secret_id = azurerm_key_vault_secret.test.versionless_id + } +} +`, r.templateNoProvider(data), data.RandomInteger, data.RandomString) +} + +func (r ContainerAppResource) withKeyVaultSecretUserIdentity(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = true + recover_soft_deleted_key_vaults = true + } + } +} + +%[1]s + +data "azurerm_client_config" "current" {} + +resource "azurerm_key_vault" "test" { + name = "acctest-kv-%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + soft_delete_retention_days = 7 +} + +resource "azurerm_key_vault_secret" "test" { + name = "secret-%[3]s" + value = "test-secret" + key_vault_id = azurerm_key_vault.test.id + + depends_on = [ + azurerm_key_vault_access_policy.self_key_vault_admin + ] +} + +resource "azurerm_key_vault_access_policy" "self_key_vault_admin" { + 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 + + key_permissions = [ + "Create", + "Get", + ] + + secret_permissions = [ + "Set", + "Get", + "Delete", + "Purge", + "Recover" + ] +} + +resource "azurerm_key_vault_access_policy" "mi_key_vault_secrets" { + key_vault_id = azurerm_key_vault.test.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = azurerm_container_app.test.identity[0].principal_id + + secret_permissions = [ + "Get", + ] +} + +resource "azurerm_key_vault_access_policy" "user_mi_key_vault_secrets" { + key_vault_id = azurerm_key_vault.test.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = azurerm_user_assigned_identity.test.principal_id + + secret_permissions = [ + "Get", + ] +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_container_app" "test" { + name = "acctest-capp-%[2]d" + resource_group_name = azurerm_resource_group.test.name + container_app_environment_id = azurerm_container_app_environment.test.id + revision_mode = "Single" + + identity { + type = "SystemAssigned, UserAssigned" + identity_ids = [azurerm_user_assigned_identity.test.id] + } + + template { + container { + name = "acctest-cont-%[2]d" + image = "jackofallops/azure-containerapps-python-acctest:v0.0.1" + cpu = 0.25 + memory = "0.5Gi" + env { + name = "key-vault-secret" + secret_name = "key-vault-secret" + } + } + } + + secret { + name = "key-vault-secret" + identity = azurerm_user_assigned_identity.test.id + key_vault_secret_id = azurerm_key_vault_secret.test.id + } +} +`, r.templateNoProvider(data), data.RandomInteger, data.RandomString) +} + +func (r ContainerAppResource) withKeyVaultSecretSystemAndUserIdentity(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = true + recover_soft_deleted_key_vaults = true + } + } +} + +%[1]s + +data "azurerm_client_config" "current" {} + +resource "azurerm_key_vault" "test" { + name = "acctest-kv-%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + soft_delete_retention_days = 7 +} + +resource "azurerm_key_vault_secret" "test" { + name = "secret-%[3]s" + value = "test-secret" + key_vault_id = azurerm_key_vault.test.id + + depends_on = [ + azurerm_key_vault_access_policy.self_key_vault_admin + ] +} + +resource "azurerm_key_vault_access_policy" "self_key_vault_admin" { + 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 + + key_permissions = [ + "Create", + "Get", + ] + + secret_permissions = [ + "Set", + "Get", + "Delete", + "Purge", + "Recover" + ] +} + +resource "azurerm_key_vault_access_policy" "mi_key_vault_secrets" { + key_vault_id = azurerm_key_vault.test.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = azurerm_container_app.test.identity[0].principal_id + + secret_permissions = [ + "Get", + ] +} + +resource "azurerm_key_vault_access_policy" "user_mi_key_vault_secrets" { + key_vault_id = azurerm_key_vault.test.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = azurerm_user_assigned_identity.test.principal_id + + secret_permissions = [ + "Get", + ] +} + +resource "azurerm_user_assigned_identity" "test" { + name = "acct-%[2]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location +} + +resource "azurerm_container_app" "test" { + name = "acctest-capp-%[2]d" + resource_group_name = azurerm_resource_group.test.name + container_app_environment_id = azurerm_container_app_environment.test.id + revision_mode = "Single" + + identity { + type = "SystemAssigned, UserAssigned" + identity_ids = [azurerm_user_assigned_identity.test.id] + } + + template { + container { + name = "acctest-cont-%[2]d" + image = "jackofallops/azure-containerapps-python-acctest:v0.0.1" + cpu = 0.25 + memory = "0.5Gi" + env { + name = "key-vault-secret" + secret_name = "key-vault-secret" + } + env { + name = "key-vault-secret-system" + secret_name = "key-vault-secret-system" + } + } + } + + secret { + name = "key-vault-secret" + identity = azurerm_user_assigned_identity.test.id + key_vault_secret_id = azurerm_key_vault_secret.test.id + } + + secret { + name = "key-vault-secret-system" + identity = "System" + key_vault_secret_id = azurerm_key_vault_secret.test.id + } + + depends_on = [ + azurerm_key_vault_access_policy.user_mi_key_vault_secrets + ] +} +`, r.templateNoProvider(data), data.RandomInteger, data.RandomString) +} + +func (r ContainerAppResource) withKeyVaultSecretSystemIdentity(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = true + recover_soft_deleted_key_vaults = true + } + } +} + +%[1]s + +data "azurerm_client_config" "current" {} + +resource "azurerm_key_vault" "test" { + name = "acctest-kv-%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + soft_delete_retention_days = 7 +} + +resource "azurerm_key_vault_secret" "test" { + name = "secret-%[3]s" + value = "test-secret" + key_vault_id = azurerm_key_vault.test.id + + depends_on = [ + azurerm_key_vault_access_policy.self_key_vault_admin + ] +} + +resource "azurerm_key_vault_access_policy" "self_key_vault_admin" { + 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 + + key_permissions = [ + "Create", + "Get", + ] + + secret_permissions = [ + "Set", + "Get", + "Delete", + "Purge", + "Recover" + ] +} + +resource "azurerm_key_vault_access_policy" "mi_key_vault_secrets" { + key_vault_id = azurerm_key_vault.test.id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = azurerm_container_app.test.identity[0].principal_id + + secret_permissions = [ + "Get", + ] +} + +resource "azurerm_container_app" "test" { + name = "acctest-capp-%[2]d" + resource_group_name = azurerm_resource_group.test.name + container_app_environment_id = azurerm_container_app_environment.test.id + revision_mode = "Single" + + identity { + type = "SystemAssigned" + } + + template { + container { + name = "acctest-cont-%[2]d" + image = "jackofallops/azure-containerapps-python-acctest:v0.0.1" + cpu = 0.25 + memory = "0.5Gi" + env { + name = "key-vault-secret" + secret_name = "key-vault-secret" + } + } + } + + secret { + name = "key-vault-secret" + identity = "System" + key_vault_secret_id = azurerm_key_vault_secret.test.id + } +} +`, r.templateNoProvider(data), data.RandomInteger, data.RandomString) +} + func (r ContainerAppResource) basicUpdate(data acceptance.TestData) string { return fmt.Sprintf(` %s @@ -2006,6 +2574,10 @@ func (ContainerAppResource) template(data acceptance.TestData) string { return ContainerAppEnvironmentResource{}.basic(data) } +func (ContainerAppResource) templateNoProvider(data acceptance.TestData) string { + return ContainerAppEnvironmentResource{}.basicNoProvider(data) +} + func (ContainerAppResource) templateWorkloadProfile(data acceptance.TestData) string { return ContainerAppEnvironmentResource{}.completeWithWorkloadProfile(data) } diff --git a/internal/services/containerapps/helpers/container_apps.go b/internal/services/containerapps/helpers/container_apps.go index 90d1a392e87f..810b661bf89e 100644 --- a/internal/services/containerapps/helpers/container_apps.go +++ b/internal/services/containerapps/helpers/container_apps.go @@ -2574,7 +2574,7 @@ func SecretsSchema() *pluginsdk.Schema { Type: pluginsdk.TypeString, Optional: true, ValidateFunc: keyVaultValidate.NestedItemIdWithOptionalVersion, - Description: "The id of the key vault secret.", + Description: "The Key Vault Secret ID. Could be either one of `id` or `versionless_id`.", }, "name": { @@ -2783,27 +2783,6 @@ func ExpandDaprSecrets(input []DaprSecret) *[]daprcomponents.Secret { return &result } -func FlattenSecrets(input []interface{}) []DaprSecret { - secrets := make([]DaprSecret, 0) - for _, s := range input { - secret := s.(map[string]interface{}) - name, ok := secret["name"].(string) - if !ok { - continue - } - value := "" - if val, ok := secret["value"].(string); ok { - value = val - } - secrets = append(secrets, DaprSecret{ - Name: name, - Value: value, - }) - } - - return secrets -} - func FlattenContainerAppSecrets(input *containerapps.SecretsCollection) []Secret { if input == nil || input.Value == nil { return []Secret{} From 5000196df0959cdb60d5f3d894cb96ed5473707e Mon Sep 17 00:00:00 2001 From: hezijie Date: Tue, 6 Feb 2024 16:29:10 +0800 Subject: [PATCH 09/11] fix format issue --- .../container_app_resource_test.go | 138 +++++++++--------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/internal/services/containerapps/container_app_resource_test.go b/internal/services/containerapps/container_app_resource_test.go index 6b5b769e4306..11eee8873ae8 100644 --- a/internal/services/containerapps/container_app_resource_test.go +++ b/internal/services/containerapps/container_app_resource_test.go @@ -730,16 +730,16 @@ provider "azurerm" { data "azurerm_client_config" "current" {} resource "azurerm_key_vault" "test" { - name = "acctest-kv-%[3]s" - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location - tenant_id = data.azurerm_client_config.current.tenant_id + name = "acctest-kv-%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + tenant_id = data.azurerm_client_config.current.tenant_id sku_name = "premium" soft_delete_retention_days = 7 access_policy { - tenant_id = data.azurerm_client_config.current.tenant_id - object_id = data.azurerm_client_config.current.object_id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id key_permissions = [ "Create", "Get", @@ -753,8 +753,8 @@ resource "azurerm_key_vault" "test" { ] } access_policy { - tenant_id = data.azurerm_client_config.current.tenant_id - object_id = azurerm_user_assigned_identity.test.principal_id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = azurerm_user_assigned_identity.test.principal_id secret_permissions = [ "Get", ] @@ -822,17 +822,17 @@ provider "azurerm" { data "azurerm_client_config" "current" {} resource "azurerm_key_vault" "test" { - name = "acctest-kv-%[3]s" - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location - tenant_id = data.azurerm_client_config.current.tenant_id + name = "acctest-kv-%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + tenant_id = data.azurerm_client_config.current.tenant_id sku_name = "premium" soft_delete_retention_days = 7 access_policy { - tenant_id = data.azurerm_client_config.current.tenant_id - object_id = data.azurerm_client_config.current.object_id - key_permissions = [ + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + key_permissions = [ "Create", "Get", ] @@ -845,8 +845,8 @@ resource "azurerm_key_vault" "test" { ] } access_policy { - tenant_id = data.azurerm_client_config.current.tenant_id - object_id = azurerm_user_assigned_identity.test.principal_id + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = azurerm_user_assigned_identity.test.principal_id secret_permissions = [ "Get", ] @@ -914,10 +914,10 @@ provider "azurerm" { data "azurerm_client_config" "current" {} resource "azurerm_key_vault" "test" { - name = "acctest-kv-%[3]s" - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location - tenant_id = data.azurerm_client_config.current.tenant_id + name = "acctest-kv-%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + tenant_id = data.azurerm_client_config.current.tenant_id sku_name = "premium" soft_delete_retention_days = 7 } @@ -938,16 +938,16 @@ resource "azurerm_key_vault_access_policy" "self_key_vault_admin" { object_id = data.azurerm_client_config.current.object_id key_permissions = [ - "Create", - "Get", + "Create", + "Get", ] secret_permissions = [ - "Set", - "Get", - "Delete", - "Purge", - "Recover" + "Set", + "Get", + "Delete", + "Purge", + "Recover" ] } @@ -957,7 +957,7 @@ resource "azurerm_key_vault_access_policy" "mi_key_vault_secrets" { object_id = azurerm_container_app.test.identity[0].principal_id secret_permissions = [ - "Get", + "Get", ] } @@ -967,7 +967,7 @@ resource "azurerm_key_vault_access_policy" "user_mi_key_vault_secrets" { object_id = azurerm_user_assigned_identity.test.principal_id secret_permissions = [ - "Get", + "Get", ] } @@ -1026,10 +1026,10 @@ provider "azurerm" { data "azurerm_client_config" "current" {} resource "azurerm_key_vault" "test" { - name = "acctest-kv-%[3]s" - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location - tenant_id = data.azurerm_client_config.current.tenant_id + name = "acctest-kv-%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + tenant_id = data.azurerm_client_config.current.tenant_id sku_name = "premium" soft_delete_retention_days = 7 } @@ -1050,16 +1050,16 @@ resource "azurerm_key_vault_access_policy" "self_key_vault_admin" { object_id = data.azurerm_client_config.current.object_id key_permissions = [ - "Create", - "Get", + "Create", + "Get", ] secret_permissions = [ - "Set", - "Get", - "Delete", - "Purge", - "Recover" + "Set", + "Get", + "Delete", + "Purge", + "Recover" ] } @@ -1069,7 +1069,7 @@ resource "azurerm_key_vault_access_policy" "mi_key_vault_secrets" { object_id = azurerm_container_app.test.identity[0].principal_id secret_permissions = [ - "Get", + "Get", ] } @@ -1079,7 +1079,7 @@ resource "azurerm_key_vault_access_policy" "user_mi_key_vault_secrets" { object_id = azurerm_user_assigned_identity.test.principal_id secret_permissions = [ - "Get", + "Get", ] } @@ -1152,10 +1152,10 @@ provider "azurerm" { data "azurerm_client_config" "current" {} resource "azurerm_key_vault" "test" { - name = "acctest-kv-%[3]s" - resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location - tenant_id = data.azurerm_client_config.current.tenant_id + name = "acctest-kv-%[3]s" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + tenant_id = data.azurerm_client_config.current.tenant_id sku_name = "premium" soft_delete_retention_days = 7 } @@ -1176,16 +1176,16 @@ resource "azurerm_key_vault_access_policy" "self_key_vault_admin" { object_id = data.azurerm_client_config.current.object_id key_permissions = [ - "Create", - "Get", + "Create", + "Get", ] secret_permissions = [ - "Set", - "Get", - "Delete", - "Purge", - "Recover" + "Set", + "Get", + "Delete", + "Purge", + "Recover" ] } @@ -1195,7 +1195,7 @@ resource "azurerm_key_vault_access_policy" "mi_key_vault_secrets" { object_id = azurerm_container_app.test.identity[0].principal_id secret_permissions = [ - "Get", + "Get", ] } @@ -2779,29 +2779,29 @@ resource "azurerm_container_app" "test" { func (r ContainerAppResource) trafficBlockMoreThanOne() string { return ` - traffic_weight { - percentage = 50 - } - traffic_weight { - percentage = 50 - } +traffic_weight { + percentage = 50 +} +traffic_weight { + percentage = 50 +} ` } func (r ContainerAppResource) trafficBlockLatestRevisionNotSet() string { return ` - traffic_weight { - percentage = 100 - } +traffic_weight { + percentage = 100 +} ` } func (r ContainerAppResource) trafficBlockRevisionSuffixSet() string { return ` - traffic_weight { - percentage = 100 - latest_revision = true - revision_suffix = "foo" - } +traffic_weight { + percentage = 100 + latest_revision = true + revision_suffix = "foo" +} ` } From 902df5494579598ef62f8e6adc3a66031e66ed5a Mon Sep 17 00:00:00 2001 From: zjhe Date: Wed, 13 Mar 2024 09:47:09 +0800 Subject: [PATCH 10/11] adjust document as review suggested --- website/docs/d/container_app.html.markdown | 4 ++-- website/docs/r/container_app.html.markdown | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/d/container_app.html.markdown b/website/docs/d/container_app.html.markdown index 5ce72c8c17de..759efc268011 100644 --- a/website/docs/d/container_app.html.markdown +++ b/website/docs/d/container_app.html.markdown @@ -55,9 +55,9 @@ In addition to the Arguments listed above - the following Attributes are exporte A `secret` block supports the following: -* `identity` - The identity to use for accessing Key Vault reference. This can either be the Resource ID of a User Assigned Identity, or 'System' for the System Assigned Identity. +* `identity` - The identity used for accessing the Key Vault. -* `key_vault_secret_id` - The ID of a Key Vault secret. This can be a versioned or version-less ID. +* `key_vault_secret_id` - The ID of a Key Vault secret. * `name` - The secret name. diff --git a/website/docs/r/container_app.html.markdown b/website/docs/r/container_app.html.markdown index c2cf0513dcc2..cccffc2a23a2 100644 --- a/website/docs/r/container_app.html.markdown +++ b/website/docs/r/container_app.html.markdown @@ -85,7 +85,7 @@ The following arguments are supported: A `secret` block supports the following: -* `identity` - (Optional) The identity to use for accessing the Key Vault secret reference. This can either be the Resource ID of a User Assigned Identity, or 'System' for the System Assigned Identity. +* `identity` - (Optional) The identity to use for accessing the Key Vault secret reference. This can either be the Resource ID of a User Assigned Identity, or `System` for the System Assigned Identity. !> **Note:** `identity` must be used together with `key_vault_secret_id` From 1007c8ae75f928f9e5517f95f70c25d37bb928fb Mon Sep 17 00:00:00 2001 From: zjhe Date: Mon, 18 Mar 2024 10:00:41 +0800 Subject: [PATCH 11/11] update as code review suggested --- .../containerapps/container_app_resource.go | 9 +++++++++ .../containerapps/helpers/container_apps.go | 13 ------------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/internal/services/containerapps/container_app_resource.go b/internal/services/containerapps/container_app_resource.go index 66985fbaa3ce..8159e489b447 100644 --- a/internal/services/containerapps/container_app_resource.go +++ b/internal/services/containerapps/container_app_resource.go @@ -497,6 +497,15 @@ func (r ContainerAppResource) CustomizeDiff() sdk.ResourceFunc { } } } + + for _, s := range app.Secrets { + if s.KeyVaultSecretId != "" && s.Identity == "" { + return fmt.Errorf("secret %s must supply identity for key vault secret id", s.Name) + } + if s.KeyVaultSecretId == "" && s.Identity != "" { + return fmt.Errorf("secret %s must supply key vault secret id when specifying identity", s.Name) + } + } return nil }, } diff --git a/internal/services/containerapps/helpers/container_apps.go b/internal/services/containerapps/helpers/container_apps.go index 810b661bf89e..07ea4c97d88a 100644 --- a/internal/services/containerapps/helpers/container_apps.go +++ b/internal/services/containerapps/helpers/container_apps.go @@ -2631,16 +2631,6 @@ func SecretsDataSourceSchema() *pluginsdk.Schema { } } -func validateContainerSecret(s Secret) error { - if s.KeyVaultSecretId != "" && s.Identity == "" { - return fmt.Errorf("must supply identity for key vault secret id") - } - if s.KeyVaultSecretId == "" && s.Identity != "" { - return fmt.Errorf("must supply key vault secret id when specifying identity") - } - return nil -} - func ExpandContainerSecrets(input []Secret) (*[]containerapps.Secret, error) { if len(input) == 0 { return nil, nil @@ -2649,9 +2639,6 @@ func ExpandContainerSecrets(input []Secret) (*[]containerapps.Secret, error) { result := make([]containerapps.Secret, 0) for _, v := range input { - if err := validateContainerSecret(v); err != nil { - return nil, err - } result = append(result, containerapps.Secret{ Identity: pointer.To(v.Identity), KeyVaultUrl: pointer.To(v.KeyVaultSecretId),