From 85bf2ce4e609444450d372de8dd218a0050ef058 Mon Sep 17 00:00:00 2001 From: Jonathan Sokolowski Date: Wed, 15 Feb 2023 12:22:49 +1100 Subject: [PATCH] azurerm_container_app: add identity field to registry block Fixes #20412 --- .../containerapps/container_app_resource.go | 13 +++- .../containerapps/helpers/container_apps.go | 45 +++++++++---- .../helpers/container_apps_test.go | 66 +++++++++++++++++++ website/docs/r/container_app.html.markdown | 11 +++- 4 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 internal/services/containerapps/helpers/container_apps_test.go diff --git a/internal/services/containerapps/container_app_resource.go b/internal/services/containerapps/container_app_resource.go index a48b28f7b107..7a74a66e78f4 100644 --- a/internal/services/containerapps/container_app_resource.go +++ b/internal/services/containerapps/container_app_resource.go @@ -177,6 +177,11 @@ func (r ContainerAppResource) Create() sdk.ResourceFunc { return fmt.Errorf("reading %s for %s: %+v", *envId, id, err) } + registries, err := helpers.ExpandContainerAppRegistries(app.Registries) + if err != nil { + return fmt.Errorf("invalid registry config for %s: %+v", id, err) + } + containerApp := containerapps.ContainerApp{ Location: location.Normalize(env.Model.Location), Properties: &containerapps.ContainerAppProperties{ @@ -184,7 +189,7 @@ func (r ContainerAppResource) Create() sdk.ResourceFunc { Ingress: helpers.ExpandContainerAppIngress(app.Ingress, id.ContainerAppName), Dapr: helpers.ExpandContainerAppDapr(app.Dapr), Secrets: helpers.ExpandContainerSecrets(app.Secrets), - Registries: helpers.ExpandContainerAppRegistries(app.Registries), + Registries: registries, }, ManagedEnvironmentId: pointer.To(app.ManagedEnvironmentId), Template: helpers.ExpandContainerAppTemplate(app.Template, metadata), @@ -347,8 +352,10 @@ func (r ContainerAppResource) Update() sdk.ResourceFunc { } if metadata.ResourceData.HasChange("registry") { - model.Properties.Configuration.Registries = helpers.ExpandContainerAppRegistries(state.Registries) - + model.Properties.Configuration.Registries, err = helpers.ExpandContainerAppRegistries(state.Registries) + if err != nil { + return fmt.Errorf("invalid registry config for %s: %+v", id, err) + } } if metadata.ResourceData.HasChange("dapr") { diff --git a/internal/services/containerapps/helpers/container_apps.go b/internal/services/containerapps/helpers/container_apps.go index bd059740e204..e46c786c7fdc 100644 --- a/internal/services/containerapps/helpers/container_apps.go +++ b/internal/services/containerapps/helpers/container_apps.go @@ -18,6 +18,7 @@ type Registry struct { PasswordSecretRef string `tfschema:"password_secret_name"` Server string `tfschema:"server"` UserName string `tfschema:"username"` + Identity string `tfschema:"identity"` } func ContainerAppRegistrySchema() *pluginsdk.Schema { @@ -35,38 +36,59 @@ func ContainerAppRegistrySchema() *pluginsdk.Schema { }, "username": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - Description: "The username to use for this Container Registry.", + Type: pluginsdk.TypeString, + Optional: true, + Description: "The username to use for this Container Registry.", }, "password_secret_name": { - Type: pluginsdk.TypeString, - Required: true, - ValidateFunc: validation.StringIsNotEmpty, - Description: "The name of the Secret Reference containing the password value for this user on the Container Registry.", + Type: pluginsdk.TypeString, + Optional: true, + Description: "The name of the Secret Reference containing the password value for this user on the Container Registry.", + }, + + "identity": { + Type: pluginsdk.TypeString, + Optional: true, + Description: "ID of the System or User Managed Identity used to pull images from the Container Registry", }, }, }, } } -func ExpandContainerAppRegistries(input []Registry) *[]containerapps.RegistryCredentials { +func ValidateContainerAppRegistry(r Registry) error { + if r.Identity != "" && (r.UserName != "" || r.PasswordSecretRef != "") { + return fmt.Errorf("identity and username/password_secret_name are mutually exclusive") + } + if r.Identity == "" && r.UserName == "" && r.PasswordSecretRef == "" { + return fmt.Errorf("must supply either identity or username/password_secret_name") + } + if (r.UserName != "" && r.PasswordSecretRef == "") || (r.UserName == "" && r.PasswordSecretRef != "") { + return fmt.Errorf("must supply both username and password_secret_name") + } + return nil +} + +func ExpandContainerAppRegistries(input []Registry) (*[]containerapps.RegistryCredentials, error) { if input == nil { - return nil + return nil, nil } registries := make([]containerapps.RegistryCredentials, 0) for _, v := range input { + if err := ValidateContainerAppRegistry(v); err != nil { + return nil, err + } registries = append(registries, containerapps.RegistryCredentials{ Server: pointer.To(v.Server), Username: pointer.To(v.UserName), PasswordSecretRef: pointer.To(v.PasswordSecretRef), + Identity: pointer.To(v.Identity), }) } - return ®istries + return ®istries, nil } func FlattenContainerAppRegistries(input *[]containerapps.RegistryCredentials) []Registry { @@ -80,6 +102,7 @@ func FlattenContainerAppRegistries(input *[]containerapps.RegistryCredentials) [ PasswordSecretRef: pointer.From(v.PasswordSecretRef), Server: pointer.From(v.Server), UserName: pointer.From(v.Username), + Identity: pointer.From(v.Identity), }) } diff --git a/internal/services/containerapps/helpers/container_apps_test.go b/internal/services/containerapps/helpers/container_apps_test.go new file mode 100644 index 000000000000..787ca1958a97 --- /dev/null +++ b/internal/services/containerapps/helpers/container_apps_test.go @@ -0,0 +1,66 @@ +package helpers + +import ( + "testing" +) + +func TestValidateContainerAppRegistry(t *testing.T) { + cases := []struct { + Input Registry + Valid bool + }{ + { + Input: Registry{ + Server: "registry.example.com", + UserName: "user", + PasswordSecretRef: "secretref", + }, + Valid: true, + }, + { + Input: Registry{ + Server: "registry.example.com", + Identity: "identity", + }, + Valid: true, + }, + { + Input: Registry{ + Server: "registry.example.com", + }, + Valid: false, + }, + { + Input: Registry{ + Server: "registry.example.com", + UserName: "user", + PasswordSecretRef: "secretref", + Identity: "identity", + }, + Valid: false, + }, + { + Input: Registry{ + Server: "registry.example.com", + PasswordSecretRef: "secretref", + }, + Valid: false, + }, + { + Input: Registry{ + Server: "registry.example.com", + UserName: "user", + }, + Valid: false, + }, + } + + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + err := ValidateContainerAppRegistry(tc.Input) + valid := err == nil + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t for %s", tc.Valid, valid, tc.Input) + } + } +} diff --git a/website/docs/r/container_app.html.markdown b/website/docs/r/container_app.html.markdown index fc522c623f16..aa467cf019e6 100644 --- a/website/docs/r/container_app.html.markdown +++ b/website/docs/r/container_app.html.markdown @@ -315,11 +315,16 @@ A `dapr` block supports the following: A `registry` block supports the following: -* `password_secret_name` - (Required) The name of the Secret Reference containing the password value for this user on the Container Registry. - * `server` - (Required) The hostname for the Container Registry. -* `username` - (Required) The username to use for this Container Registry. +The authentication details must also be supplied, `identity` and `username`/`password_secret_name` are mutually exclusive. + +* `identity` - (Optional) Resource ID for the User Assigned Managed identity to use when pulling from the Container Registry. + +* `password_secret_name` - (Optional) The name of the Secret Reference containing the password value for this user on the Container Registry, `username` must also be supplied. + +* `username` - (Optional) The username to use for this Container Registry, `password_secret_name` must also be supplied.. + ## Attributes Reference