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..739b90a4a1ba 100644 --- a/internal/services/containerapps/container_app_environment_resource_test.go +++ b/internal/services/containerapps/container_app_environment_resource_test.go @@ -194,6 +194,18 @@ 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..8159e489b447 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") { @@ -489,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/container_app_resource_test.go b/internal/services/containerapps/container_app_resource_test.go index e3440f999d60..11eee8873ae8 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) } @@ -2207,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" +} ` } diff --git a/internal/services/containerapps/helpers/container_apps.go b/internal/services/containerapps/helpers/container_apps.go index ba630c970b14..07ea4c97d88a 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,8 +2547,10 @@ func expandContainerProbes(input Container) *[]containerapps.ContainerAppProbe { } type Secret struct { - 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 { @@ -2556,17 +2560,33 @@ func SecretsSchema() *pluginsdk.Schema { Sensitive: true, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ + "identity": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.Any( + commonids.ValidateUserAssignedIdentityID, + validation.StringInSlice([]string{"System"}, false), + ), + Description: "The identity to use for accessing key vault reference.", + }, + + "key_vault_secret_id": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: keyVaultValidate.NestedItemIdWithOptionalVersion, + Description: "The Key Vault Secret ID. Could be either one of `id` or `versionless_id`.", + }, + "name": { Type: pluginsdk.TypeString, Required: true, ValidateFunc: validate.SecretName, - Sensitive: true, - Description: "The Secret name.", + Description: "The secret name.", }, "value": { Type: pluginsdk.TypeString, - Required: true, + Optional: true, Sensitive: true, Description: "The value for this secret.", }, @@ -2577,15 +2597,27 @@ func SecretsSchema() *pluginsdk.Schema { func SecretsDataSourceSchema() *pluginsdk.Schema { return &pluginsdk.Schema{ - Type: pluginsdk.TypeList, + Type: pluginsdk.TypeSet, Computed: true, Sensitive: true, Elem: &pluginsdk.Resource{ Schema: map[string]*pluginsdk.Schema{ + "identity": { + Type: pluginsdk.TypeString, + Computed: true, + Description: "The identity to use for accessing key vault reference.", + }, + + "key_vault_secret_id": { + Type: pluginsdk.TypeString, + Computed: true, + Description: "The id of the key vault secret.", + }, + "name": { Type: pluginsdk.TypeString, Computed: true, - Description: "The Secret name.", + Description: "The secret name.", }, "value": { @@ -2599,21 +2631,23 @@ func SecretsDataSourceSchema() *pluginsdk.Schema { } } -func ExpandContainerSecrets(input []Secret) *[]containerapps.Secret { +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 { result = append(result, containerapps.Secret{ - Name: pointer.To(v.Name), - Value: pointer.To(v.Value), + Identity: pointer.To(v.Identity), + KeyVaultUrl: pointer.To(v.KeyVaultSecretId), + Name: pointer.To(v.Name), + Value: pointer.To(v.Value), }) } - return &result + return &result, nil } func ExpandFormerContainerSecrets(metadata sdk.ResourceMetaData) *[]containerapps.Secret { @@ -2624,8 +2658,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 +2691,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 +2699,61 @@ 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, + 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,49 +2770,33 @@ func ExpandDaprSecrets(input []Secret) *[]daprcomponents.Secret { return &result } -func FlattenSecrets(input []interface{}) []Secret { - secrets := make([]Secret, 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, Secret{ - Name: name, - Value: value, - }) - } - - return secrets -} - func FlattenContainerAppSecrets(input *containerapps.SecretsCollection) []Secret { if input == nil || input.Value == nil { return []Secret{} } result := make([]Secret, 0) for _, v := range input.Value { - result = append(result, Secret{ - Name: pointer.From(v.Name), - Value: pointer.From(v.Value), - }) + secret := Secret{ + Identity: pointer.From(v.Identity), + KeyVaultSecretId: pointer.From(v.KeyVaultUrl), + Name: pointer.From(v.Name), + } + if v.KeyVaultUrl == nil { + secret.Value = pointer.From(v.Value) + } + result = append(result, secret) } 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..759efc268011 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 the Key Vault. + +* `key_vault_secret_id` - The ID of a 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..cccffc2a23a2 100644 --- a/website/docs/r/container_app.html.markdown +++ b/website/docs/r/container_app.html.markdown @@ -85,9 +85,19 @@ The following arguments are supported: A `secret` block supports the following: -* `name` - (Required) The Secret name. +* `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. -* `value` - (Required) The value for this secret. +!> **Note:** `identity` must be used together with `key_vault_secret_id` + +* `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` 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.