From 0de99ebc59d5f4cdcba05a0964fcd36ca72f8e62 Mon Sep 17 00:00:00 2001 From: jan-mrm <67435696+jan-mrm@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:37:07 +0200 Subject: [PATCH 01/12] feat: add new resource azurerm_container_registry_credential_set --- internal/services/containers/client/client.go | 9 + ...tainer_registry_credential_set_resource.go | 331 ++++++++++++++++++ ...r_registry_credential_set_resource_test.go | 161 +++++++++ internal/services/containers/registration.go | 1 + ...iner_registry_credential_set.html.markdown | 148 ++++++++ 5 files changed, 650 insertions(+) create mode 100644 internal/services/containers/container_registry_credential_set_resource.go create mode 100644 internal/services/containers/container_registry_credential_set_resource_test.go create mode 100644 website/docs/r/container_registry_credential_set.html.markdown diff --git a/internal/services/containers/client/client.go b/internal/services/containers/client/client.go index db7db8b9c03d..fb476501d5ed 100644 --- a/internal/services/containers/client/client.go +++ b/internal/services/containers/client/client.go @@ -10,6 +10,7 @@ import ( containerregistry_v2019_06_01_preview "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2019-06-01-preview" containerregistry_v2023_06_01_preview "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2023-06-01-preview" "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2023-07-01/cacherules" + "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2023-07-01/credentialsets" "github.com/hashicorp/go-azure-sdk/resource-manager/containerservice/2019-08-01/containerservices" "github.com/hashicorp/go-azure-sdk/resource-manager/containerservice/2024-04-01/fleetupdatestrategies" "github.com/hashicorp/go-azure-sdk/resource-manager/containerservice/2024-04-01/updateruns" @@ -28,6 +29,7 @@ type Client struct { AgentPoolsClient *agentpools.AgentPoolsClient ContainerInstanceClient *containerinstance.ContainerInstanceClient CacheRulesClient *cacherules.CacheRulesClient + CredentialSetsClient *credentialsets.CredentialSetsClient ContainerRegistryClient_v2023_06_01_preview *containerregistry_v2023_06_01_preview.Client // v2019_06_01_preview is needed for container registry agent pools and tasks ContainerRegistryClient_v2019_06_01_preview *containerregistry_v2019_06_01_preview.Client @@ -69,6 +71,12 @@ func NewContainersClient(o *common.ClientOptions) (*Client, error) { } o.Configure(cacheRulesClient.Client, o.Authorizers.ResourceManager) + credentialSetsClient, err := credentialsets.NewCredentialSetsClientWithBaseURI(o.Environment.ResourceManager) + if err != nil { + return nil, fmt.Errorf("building Credential Sets client: %+v", err) + } + o.Configure(credentialSetsClient.Client, o.Authorizers.ResourceManager) + // AKS fleetUpdateRunsClient, err := updateruns.NewUpdateRunsClientWithBaseURI(o.Environment.ResourceManager) if err != nil { @@ -128,6 +136,7 @@ func NewContainersClient(o *common.ClientOptions) (*Client, error) { AgentPoolsClient: agentPoolsClient, ContainerInstanceClient: containerInstanceClient, CacheRulesClient: cacheRulesClient, + CredentialSetsClient: credentialSetsClient, ContainerRegistryClient_v2023_06_01_preview: containerRegistryClient_v2023_06_01_preview, ContainerRegistryClient_v2019_06_01_preview: containerRegistryClient_v2019_06_01_preview, FleetUpdateRunsClient: fleetUpdateRunsClient, diff --git a/internal/services/containers/container_registry_credential_set_resource.go b/internal/services/containers/container_registry_credential_set_resource.go new file mode 100644 index 000000000000..985fed84d418 --- /dev/null +++ b/internal/services/containers/container_registry_credential_set_resource.go @@ -0,0 +1,331 @@ +package containers + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-helpers/lang/response" + "github.com/hashicorp/go-azure-helpers/resourcemanager/identity" + "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2023-06-01-preview/registries" + "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2023-07-01/credentialsets" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +var _ sdk.Resource = ContainerRegistryCredentialSetResource{} + +type ContainerRegistryCredentialSetResource struct{} + +func (ContainerRegistryCredentialSetResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + Description: "The name of the credential set.", + }, + "container_registry_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: registries.ValidateRegistryID, + }, + "login_server": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + }, + "auth_credentials": { + Type: pluginsdk.TypeList, + Required: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*schema.Schema{ + "username_secret_identifier": { + Type: pluginsdk.TypeString, + Required: true, + }, + "password_secret_identifier": { + Type: pluginsdk.TypeString, + Required: true, + }, + }, + }, + }, + } +} + +func (ContainerRegistryCredentialSetResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "identity": { + Type: pluginsdk.TypeList, + Computed: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*schema.Schema{ + "type": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "tenant_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + "principal_id": { + Type: pluginsdk.TypeString, + Computed: true, + }, + }, + }, + }, + } +} + +type Identity struct { + Type string `tfschema:"type"` + TenantId string `tfschema:"tenant_id"` + PrincipalId string `tfschema:"principal_id"` +} + +type AuthCredential struct { + UsernameSecretIdentifier string `tfschema:"username_secret_identifier"` + PasswordSecretIdentifier string `tfschema:"password_secret_identifier"` +} + +type ContainerRegistryCredentialSetModel struct { + Name string `tfschema:"name"` + ContainerRegistryId string `tfschema:"container_registry_id"` + LoginServer string `tfschema:"login_server"` + AuthCredentials []AuthCredential `tfschema:"auth_credentials"` + Identity []Identity `tfschema:"identity"` +} + +func (ContainerRegistryCredentialSetResource) ModelObject() interface{} { + return &ContainerRegistryCredentialSetModel{} +} + +func (ContainerRegistryCredentialSetResource) ResourceType() string { + return "azurerm_container_registry_credential_set" +} + +func (r ContainerRegistryCredentialSetResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Containers.CredentialSetsClient + subscriptionId := metadata.Client.Account.SubscriptionId + + var config ContainerRegistryCredentialSetModel + if err := metadata.Decode(&config); err != nil { + return err + } + + log.Printf("[INFO] preparing arguments for Container Registry Credential Set creation.") + + registryId, err := registries.ParseRegistryID(metadata.ResourceData.Get("container_registry_id").(string)) + if err != nil { + return err + } + + id := credentialsets.NewCredentialSetID(subscriptionId, + registryId.ResourceGroupName, + registryId.RegistryName, + metadata.ResourceData.Get("name").(string), + ) + + existing, err := client.Get(ctx, id) + if err != nil && !response.WasNotFound(existing.HttpResponse) { + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) + } + if !response.WasNotFound(existing.HttpResponse) { + return metadata.ResourceRequiresImport(r.ResourceType(), id) + } + + param := credentialsets.CredentialSet{ + Name: &id.CredentialSetName, + Properties: &credentialsets.CredentialSetProperties{ + LoginServer: pointer.To(config.LoginServer), + AuthCredentials: expandAuthCredentials(config.AuthCredentials), + }, + Identity: &identity.SystemAndUserAssignedMap{ + Type: identity.TypeSystemAssigned, + }, + } + + if err := client.CreateThenPoll(ctx, id, param); err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + metadata.SetID(id) + return nil + }, + } +} + +func (r ContainerRegistryCredentialSetResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Containers.CredentialSetsClient + id, err := credentialsets.ParseCredentialSetID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + existing, err := client.Get(ctx, *id) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", id, err) + } + if existing.Model == nil { + return fmt.Errorf("retrieving %s: `model` was nil", id) + } + if existing.Model.Properties == nil { + return fmt.Errorf("retrieving %s: `properties` was nil", id) + } + + var model ContainerRegistryCredentialSetModel + if err := metadata.Decode(&model); err != nil { + return err + } + + if metadata.ResourceData.HasChange("login_server") { + existing.Model.Properties.LoginServer = pointer.To(model.LoginServer) + } + if metadata.ResourceData.HasChange("auth_credentials") { + existing.Model.Properties.AuthCredentials = expandAuthCredentials(model.AuthCredentials) + } + + param := credentialsets.CredentialSetUpdateParameters{ + Identity: expandIdentity(model.Identity), + Properties: &credentialsets.CredentialSetUpdateProperties{ + AuthCredentials: existing.Model.Properties.AuthCredentials, + }, + } + if err := client.UpdateThenPoll(ctx, *id, param); err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + return nil + }, + } +} + +func (ContainerRegistryCredentialSetResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Containers.CredentialSetsClient + id, err := credentialsets.ParseCredentialSetID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, *id) + if err != nil { + if response.WasNotFound(resp.HttpResponse) { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("retrieving %s: %+v", id, err) + } + + registryId := registries.NewRegistryID(id.SubscriptionId, id.ResourceGroupName, id.RegistryName) + + var config ContainerRegistryCredentialSetModel + if err := metadata.Decode(&config); err != nil { + return err + } + + config.Name = id.CredentialSetName + config.ContainerRegistryId = registryId.ID() + + if model := resp.Model; model != nil { + config.Identity = flattenIdentity(model.Identity) + if props := model.Properties; props != nil { + config.LoginServer = pointer.From(props.LoginServer) + config.AuthCredentials = flattenAuthCredentials(props.AuthCredentials) + } + } + if err := metadata.Encode(&config); err != nil { + return fmt.Errorf("encoding: %+v", err) + } + return nil + }, + } +} + +func (ContainerRegistryCredentialSetResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.Containers.CredentialSetsClient + id, err := credentialsets.ParseCredentialSetID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + if err := client.DeleteThenPoll(ctx, *id); err != nil { + return fmt.Errorf("deleting %s: %+v", *id, err) + } + return nil + }, + } +} + +func (ContainerRegistryCredentialSetResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return credentialsets.ValidateCredentialSetID +} + +func expandAuthCredentials(input []AuthCredential) *[]credentialsets.AuthCredential { + if len(input) == 0 { + return nil + } + output := make([]credentialsets.AuthCredential, 0, len(input)) + for _, v := range input { + output = append(output, credentialsets.AuthCredential{ + Name: pointer.To(credentialsets.CredentialNameCredentialOne), + UsernameSecretIdentifier: pointer.To(v.UsernameSecretIdentifier), + PasswordSecretIdentifier: pointer.To(v.PasswordSecretIdentifier), + }) + } + return &output +} + +func flattenAuthCredentials(input *[]credentialsets.AuthCredential) []AuthCredential { + if input == nil { + return nil + } + output := make([]AuthCredential, len(*input)) + for i, v := range *input { + output[i] = AuthCredential{ + UsernameSecretIdentifier: pointer.From(v.UsernameSecretIdentifier), + PasswordSecretIdentifier: pointer.From(v.PasswordSecretIdentifier), + } + } + return output +} + +func flattenIdentity(input *identity.SystemAndUserAssignedMap) []Identity { + if input == nil { + return nil + } + output := make([]Identity, 1) + output[0] = Identity{ + Type: string(input.Type), + TenantId: input.TenantId, + PrincipalId: input.PrincipalId, + } + return output +} + +func expandIdentity(input []Identity) *identity.SystemAndUserAssignedMap { + if len(input) == 0 { + return nil + } + output := identity.SystemAndUserAssignedMap{ + Type: identity.TypeSystemAssigned, + } + return &output +} diff --git a/internal/services/containers/container_registry_credential_set_resource_test.go b/internal/services/containers/container_registry_credential_set_resource_test.go new file mode 100644 index 000000000000..979ffb840318 --- /dev/null +++ b/internal/services/containers/container_registry_credential_set_resource_test.go @@ -0,0 +1,161 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package containers_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2023-07-01/credentialsets" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +type ContainerRegistryCredentialSetResource struct{} + +func TestAccContainerRegistryCredentialSet_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_container_registry_credential_set", "test") + r := ContainerRegistryCredentialSetResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccContainerRegistryCredentialSet_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_container_registry_credential_set", "test") + r := ContainerRegistryCredentialSetResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + { + Config: r.requiresImport(data), + ExpectError: acceptance.RequiresImportError("azurerm_container_registry_credential_set"), + }, + }) +} + +func TestAccContainerRegistryCredentialSet_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_container_registry_credential_set", "test") + r := ContainerRegistryCredentialSetResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.update(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (ContainerRegistryCredentialSetResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := credentialsets.ParseCredentialSetID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.Containers.CredentialSetsClient.Get(ctx, *id) + if err != nil { + return nil, fmt.Errorf("retrieving %s: %+v", id, err) + } + + return pointer.To(resp.Model != nil), nil +} + +func (ContainerRegistryCredentialSetResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "accTestRG-acr-credetial-set-%d" + location = "%s" +} + +resource "azurerm_container_registry" "test" { + name = "testacccr%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku = "Basic" +} + +resource "azurerm_container_registry_credential_set" "test" { + name = "testacc-acr-credential-set-%d" + container_registry_id = azurerm_container_registry.test.id + login_server = "docker.io" + auth_credentials { + username_secret_identifier = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-name" + password_secret_identifier = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-password" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) +} + +func (r ContainerRegistryCredentialSetResource) requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_container_registry_credential_set" "import" { + name = azurerm_container_registry_credential_set.test.name + container_registry_id = azurerm_container_registry_credential_set.test.container_registry_id + login_server = azurerm_container_registry_credential_set.test.login_server + auth_credentials = azurerm_container_registry_credential_set.test.auth_credentials +} +`, r.basic(data)) +} + +func (ContainerRegistryCredentialSetResource) update(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "accTestRG-acr-credetial-set-%d" + location = "%s" +} + +resource "azurerm_container_registry" "test" { + name = "testacccr%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku = "Basic" +} + +resource "azurerm_container_registry_credential_set" "test" { + name = "testacc-acr-credential-set-%d" + container_registry_id = azurerm_container_registry.test.id + login_server = "docker.io" + auth_credentials { + username_secret_identifier = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-name-changed" + password_secret_identifier = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-password" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) +} diff --git a/internal/services/containers/registration.go b/internal/services/containers/registration.go index 32ff3d85aa5a..4b0f875a5cee 100644 --- a/internal/services/containers/registration.go +++ b/internal/services/containers/registration.go @@ -71,6 +71,7 @@ func (r Registration) Resources() []sdk.Resource { resources := []sdk.Resource{ ContainerRegistryCacheRule{}, ContainerRegistryTaskResource{}, + ContainerRegistryCredentialSetResource{}, ContainerRegistryTaskScheduleResource{}, ContainerRegistryTokenPasswordResource{}, ContainerConnectedRegistryResource{}, diff --git a/website/docs/r/container_registry_credential_set.html.markdown b/website/docs/r/container_registry_credential_set.html.markdown new file mode 100644 index 000000000000..43774e13e5e7 --- /dev/null +++ b/website/docs/r/container_registry_credential_set.html.markdown @@ -0,0 +1,148 @@ +--- +subcategory: "Container" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_container_registry_credential_set" +description: |- + Manages a Container Registry Credential Set. +--- + +# azurerm_container_registry_credential_set + +Manages a Container Registry Credential Set. + +## Example Usage (minimal) + +~> NOTE: Be aware that you will need to permit the Identity that is created for the Container Registry to have `get` on secrets to the Key Vault, e.g. using the `azurerm_key_vault_access_policy` resource. + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_container_registry" "example" { + name = "exampleContainerRegistry" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + sku = "Basic" +} + +resource "azurerm_container_registry_credential_set" "example" { + name = "exampleCredentialSet" + container_registry_id = "azurerm_container_registry.example.id" + login_server = "docker.io" + username_secret_identifier = "https://example-keyvault.vault.azure.net/secrets/example-user-name" + password_secret_identifier = "https://example-keyvault.vault.azure.net/secrets/example-user-password" +} +``` + +## Example Usage (full) + +This example provisions a key vault with two secrets, a container registry, a container registry credential set, and an access policy to allow the container registry to read the secrets from the key vault. + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_key_vault" "example" { + name = "examplekeyvault" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "standard" +} + +resource "azurerm_key_vault_secret" "example_user" { + key_vault_id = azurerm_key_vault.example.id + name = "example-user-name" + value = "name" +} + +resource "azurerm_key_vault_secret" "example_password" { + key_vault_id = azurerm_key_vault.example.id + name = "example-user-password" + value = "password" +} + +resource "azurerm_container_registry" "example" { + name = "exampleContainerRegistry" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + sku = "Basic" +} + +resource "azurerm_container_registry_credential_set" "example" { + name = "exampleCredentialSet" + container_registry_id = "azurerm_container_registry.example.id" + login_server = "docker.io" + username_secret_identifier = azurerm_key_vault_secret.example_user.resource_versionless_id + password_secret_identifier = azurerm_key_vault_secret.example_password.resource_versionless_id +} + +resource "azurerm_key_vault_access_policy" "read_secrets" { + key_vault_id = azurerm_key_vault.example.id + tenant_id = azurerm_container_registry_credential_set.example.identity[0].tenant_id + object_id = azurerm_container_registry_credential_set.example.identity[0].principal_id + secret_permissions = ["Get"] +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name which should be used for this Container Registry Credential Set. Changing this forces a new Container Registry Credential Set to be created. + +* `container_registry_id` - (Required) The ID of the Container Registry. Changing this forces a new Container Registry Credential Set to be created. + +* `login_server` - (Required) The login server for the Credential Set. Changing this forces a new Container Registry Credential Set to be created. + +* `auth_credentials` - (Required) A `auth_credentials` block as defined below. + +--- + +A `auth_credentials` block supports the following: + +* `username_secret_identifier` - (Required) The URI of the secret containing the user's name in a Key Vault. + +* `password_secret_identifier` - (Required) The URI of the secret containing the user's password in a Key Vault. + + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Container Registry Credential Set. + +* `identity` - A `identity` block as defined below. + +--- + +A `identity` block exports the following: + +* `principal_id` - The principal ID of the Identity. + +* `tenant_id` - The tenant ID of the Identity. + +* `type` - The type of the Identity. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Container Registry Credential Set. +* `read` - (Defaults to 5 minutes) Used when retrieving the Container Registry Credential Set. +* `update` - (Defaults to 30 minutes) Used when updating the Container Registry Credential Set. +* `delete` - (Defaults to 30 minutes) Used when deleting the Container Registry Credential Set. + +## Import + +Container Registry Credential Sets can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_container_registry_credential_set.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.ContainerRegistry/registries/registry1/credentialSets/credentialSet1 +``` From e54f1c598972833277f4b1f48beba7184a8c0b52 Mon Sep 17 00:00:00 2001 From: jan-mrm <67435696+jan-mrm@users.noreply.github.com> Date: Fri, 27 Sep 2024 18:04:50 +0200 Subject: [PATCH 02/12] fix: set auth_credentials correctly in import test --- .../container_registry_credential_set_resource_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/services/containers/container_registry_credential_set_resource_test.go b/internal/services/containers/container_registry_credential_set_resource_test.go index 979ffb840318..537c6fbf6596 100644 --- a/internal/services/containers/container_registry_credential_set_resource_test.go +++ b/internal/services/containers/container_registry_credential_set_resource_test.go @@ -125,7 +125,10 @@ resource "azurerm_container_registry_credential_set" "import" { name = azurerm_container_registry_credential_set.test.name container_registry_id = azurerm_container_registry_credential_set.test.container_registry_id login_server = azurerm_container_registry_credential_set.test.login_server - auth_credentials = azurerm_container_registry_credential_set.test.auth_credentials + auth_credentials { + username_secret_identifier = azurerm_container_registry_credential_set.test.auth_credentials[0].username_secret_identifier + password_secret_identifier = azurerm_container_registry_credential_set.test.auth_credentials[0].password_secret_identifier + } } `, r.basic(data)) } From e54020781d67871f5072e07fbad90528926bf478 Mon Sep 17 00:00:00 2001 From: jan-mrm <67435696+jan-mrm@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:01:20 +0100 Subject: [PATCH 03/12] refactor: implement review feedback --- ...tainer_registry_credential_set_resource.go | 160 ++++++------------ ...r_registry_credential_set_resource_test.go | 18 +- ...iner_registry_credential_set.html.markdown | 33 ++-- 3 files changed, 82 insertions(+), 129 deletions(-) diff --git a/internal/services/containers/container_registry_credential_set_resource.go b/internal/services/containers/container_registry_credential_set_resource.go index 985fed84d418..5150bf117ed5 100644 --- a/internal/services/containers/container_registry_credential_set_resource.go +++ b/internal/services/containers/container_registry_credential_set_resource.go @@ -8,11 +8,13 @@ import ( "github.com/hashicorp/go-azure-helpers/lang/pointer" "github.com/hashicorp/go-azure-helpers/lang/response" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" "github.com/hashicorp/go-azure-helpers/resourcemanager/identity" "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2023-06-01-preview/registries" "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2023-07-01/credentialsets" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" ) @@ -39,19 +41,21 @@ func (ContainerRegistryCredentialSetResource) Arguments() map[string]*pluginsdk. Required: true, ForceNew: true, }, - "auth_credentials": { + "authentication_credentials": { Type: pluginsdk.TypeList, Required: true, MaxItems: 1, Elem: &pluginsdk.Resource{ Schema: map[string]*schema.Schema{ - "username_secret_identifier": { - Type: pluginsdk.TypeString, - Required: true, + "username_secret_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: keyVaultValidate.VersionlessNestedItemId, }, - "password_secret_identifier": { - Type: pluginsdk.TypeString, - Required: true, + "password_secret_id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: keyVaultValidate.VersionlessNestedItemId, }, }, }, @@ -61,46 +65,21 @@ func (ContainerRegistryCredentialSetResource) Arguments() map[string]*pluginsdk. func (ContainerRegistryCredentialSetResource) Attributes() map[string]*pluginsdk.Schema { return map[string]*pluginsdk.Schema{ - "identity": { - Type: pluginsdk.TypeList, - Computed: true, - Elem: &pluginsdk.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: pluginsdk.TypeString, - Computed: true, - }, - "tenant_id": { - Type: pluginsdk.TypeString, - Computed: true, - }, - "principal_id": { - Type: pluginsdk.TypeString, - Computed: true, - }, - }, - }, - }, + "identity": commonschema.SystemOrUserAssignedIdentityComputed(), } } -type Identity struct { - Type string `tfschema:"type"` - TenantId string `tfschema:"tenant_id"` - PrincipalId string `tfschema:"principal_id"` -} - -type AuthCredential struct { - UsernameSecretIdentifier string `tfschema:"username_secret_identifier"` - PasswordSecretIdentifier string `tfschema:"password_secret_identifier"` +type AuthenticationCredential struct { + UsernameSecretId string `tfschema:"username_secret_id"` + PasswordSecretId string `tfschema:"password_secret_id"` } type ContainerRegistryCredentialSetModel struct { - Name string `tfschema:"name"` - ContainerRegistryId string `tfschema:"container_registry_id"` - LoginServer string `tfschema:"login_server"` - AuthCredentials []AuthCredential `tfschema:"auth_credentials"` - Identity []Identity `tfschema:"identity"` + Name string `tfschema:"name"` + ContainerRegistryId string `tfschema:"container_registry_id"` + LoginServer string `tfschema:"login_server"` + AuthenticationCredential []AuthenticationCredential `tfschema:"authentication_credentials"` + Identity []identity.ModelSystemAssignedUserAssigned `tfschema:"identity"` } func (ContainerRegistryCredentialSetResource) ModelObject() interface{} { @@ -125,7 +104,7 @@ func (r ContainerRegistryCredentialSetResource) Create() sdk.ResourceFunc { log.Printf("[INFO] preparing arguments for Container Registry Credential Set creation.") - registryId, err := registries.ParseRegistryID(metadata.ResourceData.Get("container_registry_id").(string)) + registryId, err := registries.ParseRegistryID(config.ContainerRegistryId) if err != nil { return err } @@ -133,7 +112,7 @@ func (r ContainerRegistryCredentialSetResource) Create() sdk.ResourceFunc { id := credentialsets.NewCredentialSetID(subscriptionId, registryId.ResourceGroupName, registryId.RegistryName, - metadata.ResourceData.Get("name").(string), + config.Name, ) existing, err := client.Get(ctx, id) @@ -145,10 +124,10 @@ func (r ContainerRegistryCredentialSetResource) Create() sdk.ResourceFunc { } param := credentialsets.CredentialSet{ - Name: &id.CredentialSetName, + Name: pointer.To(id.CredentialSetName), Properties: &credentialsets.CredentialSetProperties{ LoginServer: pointer.To(config.LoginServer), - AuthCredentials: expandAuthCredentials(config.AuthCredentials), + AuthCredentials: expandAuthCredentials(config.AuthenticationCredential), }, Identity: &identity.SystemAndUserAssignedMap{ Type: identity.TypeSystemAssigned, @@ -175,39 +154,24 @@ func (r ContainerRegistryCredentialSetResource) Update() sdk.ResourceFunc { return err } - existing, err := client.Get(ctx, *id) - if err != nil { - return fmt.Errorf("retrieving %s: %+v", id, err) - } - if existing.Model == nil { - return fmt.Errorf("retrieving %s: `model` was nil", id) - } - if existing.Model.Properties == nil { - return fmt.Errorf("retrieving %s: `properties` was nil", id) - } + param := credentialsets.CredentialSetUpdateParameters{} var model ContainerRegistryCredentialSetModel if err := metadata.Decode(&model); err != nil { return err } - if metadata.ResourceData.HasChange("login_server") { - existing.Model.Properties.LoginServer = pointer.To(model.LoginServer) - } - if metadata.ResourceData.HasChange("auth_credentials") { - existing.Model.Properties.AuthCredentials = expandAuthCredentials(model.AuthCredentials) - } + properties := credentialsets.CredentialSetUpdateProperties{} - param := credentialsets.CredentialSetUpdateParameters{ - Identity: expandIdentity(model.Identity), - Properties: &credentialsets.CredentialSetUpdateProperties{ - AuthCredentials: existing.Model.Properties.AuthCredentials, - }, + if metadata.ResourceData.HasChange("authentication_credentials") { + properties.AuthCredentials = expandAuthCredentials(model.AuthenticationCredential) } + + param.Properties = &properties + if err := client.UpdateThenPoll(ctx, *id, param); err != nil { return fmt.Errorf("updating %s: %+v", id, err) } - return nil }, } @@ -242,16 +206,17 @@ func (ContainerRegistryCredentialSetResource) Read() sdk.ResourceFunc { config.ContainerRegistryId = registryId.ID() if model := resp.Model; model != nil { - config.Identity = flattenIdentity(model.Identity) + flattenedIdentity, err := identity.FlattenSystemAndUserAssignedMapToModel(model.Identity) + if err != nil { + return fmt.Errorf("flattening `identity`: %+v", err) + } + config.Identity = pointer.From(flattenedIdentity) if props := model.Properties; props != nil { config.LoginServer = pointer.From(props.LoginServer) - config.AuthCredentials = flattenAuthCredentials(props.AuthCredentials) + config.AuthenticationCredential = flattenAuthCredentials(props.AuthCredentials) } } - if err := metadata.Encode(&config); err != nil { - return fmt.Errorf("encoding: %+v", err) - } - return nil + return metadata.Encode(&config) }, } } @@ -278,54 +243,31 @@ func (ContainerRegistryCredentialSetResource) IDValidationFunc() pluginsdk.Schem return credentialsets.ValidateCredentialSetID } -func expandAuthCredentials(input []AuthCredential) *[]credentialsets.AuthCredential { +func expandAuthCredentials(input []AuthenticationCredential) *[]credentialsets.AuthCredential { + output := make([]credentialsets.AuthCredential, 0) if len(input) == 0 { - return nil + return &output } - output := make([]credentialsets.AuthCredential, 0, len(input)) for _, v := range input { output = append(output, credentialsets.AuthCredential{ Name: pointer.To(credentialsets.CredentialNameCredentialOne), - UsernameSecretIdentifier: pointer.To(v.UsernameSecretIdentifier), - PasswordSecretIdentifier: pointer.To(v.PasswordSecretIdentifier), + UsernameSecretIdentifier: pointer.To(v.UsernameSecretId), + PasswordSecretIdentifier: pointer.To(v.PasswordSecretId), }) } return &output } -func flattenAuthCredentials(input *[]credentialsets.AuthCredential) []AuthCredential { +func flattenAuthCredentials(input *[]credentialsets.AuthCredential) []AuthenticationCredential { + output := make([]AuthenticationCredential, 0) if input == nil { - return nil - } - output := make([]AuthCredential, len(*input)) - for i, v := range *input { - output[i] = AuthCredential{ - UsernameSecretIdentifier: pointer.From(v.UsernameSecretIdentifier), - PasswordSecretIdentifier: pointer.From(v.PasswordSecretIdentifier), - } + return output } - return output -} - -func flattenIdentity(input *identity.SystemAndUserAssignedMap) []Identity { - if input == nil { - return nil - } - output := make([]Identity, 1) - output[0] = Identity{ - Type: string(input.Type), - TenantId: input.TenantId, - PrincipalId: input.PrincipalId, + for _, v := range *input { + output = append(output, AuthenticationCredential{ + UsernameSecretId: pointer.From(v.UsernameSecretIdentifier), + PasswordSecretId: pointer.From(v.PasswordSecretIdentifier), + }) } return output } - -func expandIdentity(input []Identity) *identity.SystemAndUserAssignedMap { - if len(input) == 0 { - return nil - } - output := identity.SystemAndUserAssignedMap{ - Type: identity.TypeSystemAssigned, - } - return &output -} diff --git a/internal/services/containers/container_registry_credential_set_resource_test.go b/internal/services/containers/container_registry_credential_set_resource_test.go index 537c6fbf6596..e950ca7c0dc4 100644 --- a/internal/services/containers/container_registry_credential_set_resource_test.go +++ b/internal/services/containers/container_registry_credential_set_resource_test.go @@ -109,9 +109,9 @@ resource "azurerm_container_registry_credential_set" "test" { name = "testacc-acr-credential-set-%d" container_registry_id = azurerm_container_registry.test.id login_server = "docker.io" - auth_credentials { - username_secret_identifier = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-name" - password_secret_identifier = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-password" + authentication_credentials { + username_secret_id = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-name" + password_secret_id = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-password" } } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) @@ -125,9 +125,9 @@ resource "azurerm_container_registry_credential_set" "import" { name = azurerm_container_registry_credential_set.test.name container_registry_id = azurerm_container_registry_credential_set.test.container_registry_id login_server = azurerm_container_registry_credential_set.test.login_server - auth_credentials { - username_secret_identifier = azurerm_container_registry_credential_set.test.auth_credentials[0].username_secret_identifier - password_secret_identifier = azurerm_container_registry_credential_set.test.auth_credentials[0].password_secret_identifier + authentication_credentials { + username_secret_id = azurerm_container_registry_credential_set.test.authentication_credentials[0].username_secret_id + password_secret_id = azurerm_container_registry_credential_set.test.authentication_credentials[0].password_secret_id } } `, r.basic(data)) @@ -155,9 +155,9 @@ resource "azurerm_container_registry_credential_set" "test" { name = "testacc-acr-credential-set-%d" container_registry_id = azurerm_container_registry.test.id login_server = "docker.io" - auth_credentials { - username_secret_identifier = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-name-changed" - password_secret_identifier = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-password" + authentication_credentials { + username_secret_id = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-name-changed" + password_secret_id = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-password" } } `, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) diff --git a/website/docs/r/container_registry_credential_set.html.markdown b/website/docs/r/container_registry_credential_set.html.markdown index 43774e13e5e7..286db61c383d 100644 --- a/website/docs/r/container_registry_credential_set.html.markdown +++ b/website/docs/r/container_registry_credential_set.html.markdown @@ -31,8 +31,10 @@ resource "azurerm_container_registry_credential_set" "example" { name = "exampleCredentialSet" container_registry_id = "azurerm_container_registry.example.id" login_server = "docker.io" - username_secret_identifier = "https://example-keyvault.vault.azure.net/secrets/example-user-name" - password_secret_identifier = "https://example-keyvault.vault.azure.net/secrets/example-user-password" + authentication_credentials { + username_secret_id = "https://example-keyvault.vault.azure.net/secrets/example-user-name" + password_secret_id = "https://example-keyvault.vault.azure.net/secrets/example-user-password" + } } ``` @@ -76,11 +78,13 @@ resource "azurerm_container_registry" "example" { } resource "azurerm_container_registry_credential_set" "example" { - name = "exampleCredentialSet" - container_registry_id = "azurerm_container_registry.example.id" - login_server = "docker.io" - username_secret_identifier = azurerm_key_vault_secret.example_user.resource_versionless_id - password_secret_identifier = azurerm_key_vault_secret.example_password.resource_versionless_id + name = "exampleCredentialSet" + container_registry_id = "azurerm_container_registry.example.id" + login_server = "docker.io" + authentication_credentials { + username_secret_id = azurerm_key_vault_secret.example_user.resource_versionless_id + password_secret_id = azurerm_key_vault_secret.example_password.resource_versionless_id + } } resource "azurerm_key_vault_access_policy" "read_secrets" { @@ -101,15 +105,17 @@ The following arguments are supported: * `login_server` - (Required) The login server for the Credential Set. Changing this forces a new Container Registry Credential Set to be created. -* `auth_credentials` - (Required) A `auth_credentials` block as defined below. +* `authentication_credentials` - (Required) A `authentication_credentials` block as defined below. --- -A `auth_credentials` block supports the following: +A `authentication_credentials` block supports the following: + +* `username_secret_id` - (Required) The URI of the secret containing the username in a Key Vault. -* `username_secret_identifier` - (Required) The URI of the secret containing the user's name in a Key Vault. +* `password_secret_id` - (Required) The URI of the secret containing the password in a Key Vault. -* `password_secret_identifier` - (Required) The URI of the secret containing the user's password in a Key Vault. +~> NOTE: Be aware that you will need to permit the Identity that is created for the Container Registry to have `get` on secrets to the Key Vault, e.g. using the `azurerm_key_vault_access_policy` resource. ## Attributes Reference @@ -130,6 +136,11 @@ A `identity` block exports the following: * `type` - The type of the Identity. +* `identity_ids` - A list of User Managed Identity IDs. + +~> **Note:** Currently only SystemAssigned Identities are supported. + + ## Timeouts The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: From 642b66a1d4cf910a8fd6fcefd3ae4b62de90e61c Mon Sep 17 00:00:00 2001 From: jan-mrm <67435696+jan-mrm@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:13:27 +0100 Subject: [PATCH 04/12] refactor: format docs --- .../docs/r/container_registry_credential_set.html.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/r/container_registry_credential_set.html.markdown b/website/docs/r/container_registry_credential_set.html.markdown index 286db61c383d..8654676962b4 100644 --- a/website/docs/r/container_registry_credential_set.html.markdown +++ b/website/docs/r/container_registry_credential_set.html.markdown @@ -28,9 +28,9 @@ resource "azurerm_container_registry" "example" { } resource "azurerm_container_registry_credential_set" "example" { - name = "exampleCredentialSet" - container_registry_id = "azurerm_container_registry.example.id" - login_server = "docker.io" + name = "exampleCredentialSet" + container_registry_id = "azurerm_container_registry.example.id" + login_server = "docker.io" authentication_credentials { username_secret_id = "https://example-keyvault.vault.azure.net/secrets/example-user-name" password_secret_id = "https://example-keyvault.vault.azure.net/secrets/example-user-password" From 593cf9f9d082788067ac8d597be3e10d9de8d6b0 Mon Sep 17 00:00:00 2001 From: jan-mrm <67435696+jan-mrm@users.noreply.github.com> Date: Fri, 6 Dec 2024 09:15:12 +0100 Subject: [PATCH 05/12] chore: update api version in new credential set resource after it has been updated in the main --- .../containers/container_registry_credential_set_resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/containers/container_registry_credential_set_resource.go b/internal/services/containers/container_registry_credential_set_resource.go index 5150bf117ed5..dc035c74cd36 100644 --- a/internal/services/containers/container_registry_credential_set_resource.go +++ b/internal/services/containers/container_registry_credential_set_resource.go @@ -10,8 +10,8 @@ import ( "github.com/hashicorp/go-azure-helpers/lang/response" "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" "github.com/hashicorp/go-azure-helpers/resourcemanager/identity" - "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2023-06-01-preview/registries" "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2023-07-01/credentialsets" + "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2023-11-01-preview/registries" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" keyVaultValidate "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" From 530676bd75944719c8148627a60db15562854cbf Mon Sep 17 00:00:00 2001 From: jan-mrm <67435696+jan-mrm@users.noreply.github.com> Date: Fri, 6 Dec 2024 09:32:12 +0100 Subject: [PATCH 06/12] chore: add changes from 'make generate' --- .github/labeler-issue-triage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/labeler-issue-triage.yml b/.github/labeler-issue-triage.yml index ba97f5f93250..fe158b051d1f 100644 --- a/.github/labeler-issue-triage.yml +++ b/.github/labeler-issue-triage.yml @@ -351,4 +351,4 @@ service/vmware: - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(vmware_cluster\W+|vmware_express_route_authorization\W+|vmware_netapp_volume_attachment\W+|vmware_private_cloud\W+|voice_services_communications_gateway\W+|voice_services_communications_gateway_test_line\W+)((.|\n)*)###' service/workloads: - - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(chaos_studio_|container_connected_registry\W+|container_registry_cache_rule\W+|container_registry_cache_rule\W+|container_registry_task\W+|container_registry_task_schedule_run_now\W+|container_registry_token_password\W+|kubernetes_cluster_extension\W+|kubernetes_cluster_trusted_access_role_binding\W+|kubernetes_fleet_manager\W+|kubernetes_fleet_member\W+|kubernetes_fleet_update_run\W+|kubernetes_fleet_update_strategy\W+|kubernetes_flux_configuration\W+|kubernetes_node_pool_snapshot\W+|workloads_sap_)((.|\n)*)###' + - '### (|New or )Affected Resource\(s\)\/Data Source\(s\)((.|\n)*)azurerm_(chaos_studio_|container_connected_registry\W+|container_registry_cache_rule\W+|container_registry_cache_rule\W+|container_registry_credential_set\W+|container_registry_task\W+|container_registry_task_schedule_run_now\W+|container_registry_token_password\W+|kubernetes_cluster_extension\W+|kubernetes_cluster_trusted_access_role_binding\W+|kubernetes_fleet_manager\W+|kubernetes_fleet_member\W+|kubernetes_fleet_update_run\W+|kubernetes_fleet_update_strategy\W+|kubernetes_flux_configuration\W+|kubernetes_node_pool_snapshot\W+|workloads_sap_)((.|\n)*)###' From 1170f1bf42364e3fb624fe565789a508f5f06508 Mon Sep 17 00:00:00 2001 From: jan-mrm <67435696+jan-mrm@users.noreply.github.com> Date: Fri, 10 Jan 2025 18:10:00 +0100 Subject: [PATCH 07/12] refactor: change implementation to SystemAssignedUserAssignedIdentityRequired as discussed in the pull request (but the api will only accept type SystemAssigned) --- ...tainer_registry_credential_set_resource.go | 28 +++++++++++++++---- ...r_registry_credential_set_resource_test.go | 9 ++++++ ...iner_registry_credential_set.html.markdown | 26 +++++++++++------ 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/internal/services/containers/container_registry_credential_set_resource.go b/internal/services/containers/container_registry_credential_set_resource.go index dc035c74cd36..0b299947b30a 100644 --- a/internal/services/containers/container_registry_credential_set_resource.go +++ b/internal/services/containers/container_registry_credential_set_resource.go @@ -60,13 +60,19 @@ func (ContainerRegistryCredentialSetResource) Arguments() map[string]*pluginsdk. }, }, }, + // At point in time of the implementation of this resource the API only accept SystemAssigned even though API Spec defines all three identity modes are possible + // Error when trying with type UserAssigned: + // code: "CannotSetResourceIdentity" + // message: "The resource identity 'UserAssigned' cannot be set on the resource of type 'Microsoft.ContainerRegistry/registries/credentialSets'." + // or with type empty: + // code: "OnlySystemManagedIdentityAllowed" + // message: "Only System Managed Identities are allowed for resources of type 'Microsoft.ContainerRegistry/registries/credentialSets'. For more information, please visit https://aka.ms/acr/cache." + "identity": commonschema.SystemAssignedUserAssignedIdentityRequired(), } } func (ContainerRegistryCredentialSetResource) Attributes() map[string]*pluginsdk.Schema { - return map[string]*pluginsdk.Schema{ - "identity": commonschema.SystemOrUserAssignedIdentityComputed(), - } + return map[string]*pluginsdk.Schema{} } type AuthenticationCredential struct { @@ -123,15 +129,18 @@ func (r ContainerRegistryCredentialSetResource) Create() sdk.ResourceFunc { return metadata.ResourceRequiresImport(r.ResourceType(), id) } + expandedIdentity, err := identity.ExpandSystemAndUserAssignedMapFromModel(config.Identity) + if err != nil { + return err + } + param := credentialsets.CredentialSet{ Name: pointer.To(id.CredentialSetName), Properties: &credentialsets.CredentialSetProperties{ LoginServer: pointer.To(config.LoginServer), AuthCredentials: expandAuthCredentials(config.AuthenticationCredential), }, - Identity: &identity.SystemAndUserAssignedMap{ - Type: identity.TypeSystemAssigned, - }, + Identity: expandedIdentity, } if err := client.CreateThenPoll(ctx, id, param); err != nil { @@ -169,6 +178,13 @@ func (r ContainerRegistryCredentialSetResource) Update() sdk.ResourceFunc { param.Properties = &properties + if metadata.ResourceData.HasChange("identity") { + param.Identity, err = identity.ExpandSystemAndUserAssignedMapFromModel(model.Identity) + if err != nil { + return err + } + } + if err := client.UpdateThenPoll(ctx, *id, param); err != nil { return fmt.Errorf("updating %s: %+v", id, err) } diff --git a/internal/services/containers/container_registry_credential_set_resource_test.go b/internal/services/containers/container_registry_credential_set_resource_test.go index e950ca7c0dc4..6d38f51281f0 100644 --- a/internal/services/containers/container_registry_credential_set_resource_test.go +++ b/internal/services/containers/container_registry_credential_set_resource_test.go @@ -109,6 +109,9 @@ resource "azurerm_container_registry_credential_set" "test" { name = "testacc-acr-credential-set-%d" container_registry_id = azurerm_container_registry.test.id login_server = "docker.io" + identity { + type = "SystemAssigned" + } authentication_credentials { username_secret_id = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-name" password_secret_id = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-password" @@ -125,6 +128,9 @@ resource "azurerm_container_registry_credential_set" "import" { name = azurerm_container_registry_credential_set.test.name container_registry_id = azurerm_container_registry_credential_set.test.container_registry_id login_server = azurerm_container_registry_credential_set.test.login_server + identity { + type = "SystemAssigned" + } authentication_credentials { username_secret_id = azurerm_container_registry_credential_set.test.authentication_credentials[0].username_secret_id password_secret_id = azurerm_container_registry_credential_set.test.authentication_credentials[0].password_secret_id @@ -155,6 +161,9 @@ resource "azurerm_container_registry_credential_set" "test" { name = "testacc-acr-credential-set-%d" container_registry_id = azurerm_container_registry.test.id login_server = "docker.io" + identity { + type = "SystemAssigned" + } authentication_credentials { username_secret_id = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-name-changed" password_secret_id = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-password" diff --git a/website/docs/r/container_registry_credential_set.html.markdown b/website/docs/r/container_registry_credential_set.html.markdown index 8654676962b4..e0a1a7533ac4 100644 --- a/website/docs/r/container_registry_credential_set.html.markdown +++ b/website/docs/r/container_registry_credential_set.html.markdown @@ -31,6 +31,9 @@ resource "azurerm_container_registry_credential_set" "example" { name = "exampleCredentialSet" container_registry_id = "azurerm_container_registry.example.id" login_server = "docker.io" + identity { + type = "SystemAssigned" + } authentication_credentials { username_secret_id = "https://example-keyvault.vault.azure.net/secrets/example-user-name" password_secret_id = "https://example-keyvault.vault.azure.net/secrets/example-user-password" @@ -81,6 +84,9 @@ resource "azurerm_container_registry_credential_set" "example" { name = "exampleCredentialSet" container_registry_id = "azurerm_container_registry.example.id" login_server = "docker.io" + identity { + type = "SystemAssigned" + } authentication_credentials { username_secret_id = azurerm_key_vault_secret.example_user.resource_versionless_id password_secret_id = azurerm_key_vault_secret.example_password.resource_versionless_id @@ -107,6 +113,8 @@ The following arguments are supported: * `authentication_credentials` - (Required) A `authentication_credentials` block as defined below. +* `identity` - (Required) An `identity` block as defined below. + --- A `authentication_credentials` block supports the following: @@ -117,6 +125,15 @@ A `authentication_credentials` block supports the following: ~> NOTE: Be aware that you will need to permit the Identity that is created for the Container Registry to have `get` on secrets to the Key Vault, e.g. using the `azurerm_key_vault_access_policy` resource. +An `identity` block supports the following: + +* `type` - (Required) The type of Managed Service Identity that is configured on for the Container Registry Credential Set. Possible values are `SystemAssigned`, `UserAssigned`, `SystemAssigned, UserAssigned` (to enable both). + +~> **NOTE:** The Azure Resource currently only supports `SystemAssigned` Identities. + +* `identity_ids` - (Optional) A list of User Assigned Managed Identity IDs to be assigned to this Container Registry Credential Set. + +~> **NOTE:** This is required when `type` is set to `UserAssigned` or `SystemAssigned, UserAssigned`. ## Attributes Reference @@ -124,8 +141,6 @@ In addition to the Arguments listed above - the following Attributes are exporte * `id` - The ID of the Container Registry Credential Set. -* `identity` - A `identity` block as defined below. - --- A `identity` block exports the following: @@ -134,13 +149,6 @@ A `identity` block exports the following: * `tenant_id` - The tenant ID of the Identity. -* `type` - The type of the Identity. - -* `identity_ids` - A list of User Managed Identity IDs. - -~> **Note:** Currently only SystemAssigned Identities are supported. - - ## Timeouts The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: From e729aa979dafaea23c896446708ddcb9456f0677 Mon Sep 17 00:00:00 2001 From: jan-mrm <67435696+jan-mrm@users.noreply.github.com> Date: Mon, 13 Jan 2025 12:58:33 +0100 Subject: [PATCH 08/12] refactor: change implementation from SystemAssignedUserAssignedIdentityRequired to SystemAssignedIdentityRequired --- ...tainer_registry_credential_set_resource.go | 59 ++++++++++++------- ...iner_registry_credential_set.html.markdown | 7 +-- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/internal/services/containers/container_registry_credential_set_resource.go b/internal/services/containers/container_registry_credential_set_resource.go index 0b299947b30a..3e243eff9938 100644 --- a/internal/services/containers/container_registry_credential_set_resource.go +++ b/internal/services/containers/container_registry_credential_set_resource.go @@ -60,14 +60,14 @@ func (ContainerRegistryCredentialSetResource) Arguments() map[string]*pluginsdk. }, }, }, - // At point in time of the implementation of this resource the API only accept SystemAssigned even though API Spec defines all three identity modes are possible + // Note [1]: At point in time of the implementation of this resource the API only accept SystemAssigned even though API Spec defines all three identity modes are possible // Error when trying with type UserAssigned: // code: "CannotSetResourceIdentity" // message: "The resource identity 'UserAssigned' cannot be set on the resource of type 'Microsoft.ContainerRegistry/registries/credentialSets'." // or with type empty: // code: "OnlySystemManagedIdentityAllowed" // message: "Only System Managed Identities are allowed for resources of type 'Microsoft.ContainerRegistry/registries/credentialSets'. For more information, please visit https://aka.ms/acr/cache." - "identity": commonschema.SystemAssignedUserAssignedIdentityRequired(), + "identity": commonschema.SystemAssignedIdentityRequired(), } } @@ -81,11 +81,11 @@ type AuthenticationCredential struct { } type ContainerRegistryCredentialSetModel struct { - Name string `tfschema:"name"` - ContainerRegistryId string `tfschema:"container_registry_id"` - LoginServer string `tfschema:"login_server"` - AuthenticationCredential []AuthenticationCredential `tfschema:"authentication_credentials"` - Identity []identity.ModelSystemAssignedUserAssigned `tfschema:"identity"` + Name string `tfschema:"name"` + ContainerRegistryId string `tfschema:"container_registry_id"` + LoginServer string `tfschema:"login_server"` + AuthenticationCredential []AuthenticationCredential `tfschema:"authentication_credentials"` + Identity []identity.ModelSystemAssigned `tfschema:"identity"` } func (ContainerRegistryCredentialSetResource) ModelObject() interface{} { @@ -129,18 +129,13 @@ func (r ContainerRegistryCredentialSetResource) Create() sdk.ResourceFunc { return metadata.ResourceRequiresImport(r.ResourceType(), id) } - expandedIdentity, err := identity.ExpandSystemAndUserAssignedMapFromModel(config.Identity) - if err != nil { - return err - } - param := credentialsets.CredentialSet{ Name: pointer.To(id.CredentialSetName), Properties: &credentialsets.CredentialSetProperties{ LoginServer: pointer.To(config.LoginServer), AuthCredentials: expandAuthCredentials(config.AuthenticationCredential), }, - Identity: expandedIdentity, + Identity: expandIdentity(config.Identity), } if err := client.CreateThenPoll(ctx, id, param); err != nil { @@ -179,10 +174,7 @@ func (r ContainerRegistryCredentialSetResource) Update() sdk.ResourceFunc { param.Properties = &properties if metadata.ResourceData.HasChange("identity") { - param.Identity, err = identity.ExpandSystemAndUserAssignedMapFromModel(model.Identity) - if err != nil { - return err - } + param.Identity = expandIdentity(model.Identity) } if err := client.UpdateThenPoll(ctx, *id, param); err != nil { @@ -222,11 +214,7 @@ func (ContainerRegistryCredentialSetResource) Read() sdk.ResourceFunc { config.ContainerRegistryId = registryId.ID() if model := resp.Model; model != nil { - flattenedIdentity, err := identity.FlattenSystemAndUserAssignedMapToModel(model.Identity) - if err != nil { - return fmt.Errorf("flattening `identity`: %+v", err) - } - config.Identity = pointer.From(flattenedIdentity) + config.Identity = flattenIdentity(model.Identity) if props := model.Properties; props != nil { config.LoginServer = pointer.From(props.LoginServer) config.AuthenticationCredential = flattenAuthCredentials(props.AuthCredentials) @@ -287,3 +275,30 @@ func flattenAuthCredentials(input *[]credentialsets.AuthCredential) []Authentica } return output } + +// read the note [1] above why we transform the identity here like that +func flattenIdentity(input *identity.SystemAndUserAssignedMap) []identity.ModelSystemAssigned { + if input == nil { + return nil + } + output := make([]identity.ModelSystemAssigned, 1) + output[0] = identity.ModelSystemAssigned{ + Type: input.Type, + TenantId: input.TenantId, + PrincipalId: input.PrincipalId, + } + return output +} + +// read the note [1] above why we transform the identity here like that +func expandIdentity(input []identity.ModelSystemAssigned) *identity.SystemAndUserAssignedMap { + if len(input) == 0 { + return nil + } + output := identity.SystemAndUserAssignedMap{ + Type: input[0].Type, + TenantId: input[0].TenantId, + PrincipalId: input[0].PrincipalId, + } + return &output +} diff --git a/website/docs/r/container_registry_credential_set.html.markdown b/website/docs/r/container_registry_credential_set.html.markdown index e0a1a7533ac4..fb847ac69c3e 100644 --- a/website/docs/r/container_registry_credential_set.html.markdown +++ b/website/docs/r/container_registry_credential_set.html.markdown @@ -127,13 +127,8 @@ A `authentication_credentials` block supports the following: An `identity` block supports the following: -* `type` - (Required) The type of Managed Service Identity that is configured on for the Container Registry Credential Set. Possible values are `SystemAssigned`, `UserAssigned`, `SystemAssigned, UserAssigned` (to enable both). +* `type` - (Required) The type of Managed Service Identity that is configured on for the Container Registry Credential Set. Currently the only possible value is `SystemAssigned`. -~> **NOTE:** The Azure Resource currently only supports `SystemAssigned` Identities. - -* `identity_ids` - (Optional) A list of User Assigned Managed Identity IDs to be assigned to this Container Registry Credential Set. - -~> **NOTE:** This is required when `type` is set to `UserAssigned` or `SystemAssigned, UserAssigned`. ## Attributes Reference From b56f65afe334b77e881fcc66085e8d71ad17809c Mon Sep 17 00:00:00 2001 From: jan-mrm <67435696+jan-mrm@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:27:01 +0100 Subject: [PATCH 09/12] refactor: implement review feedback - import step and key vault in tests. update markdown. update flattenIdentity --- ...tainer_registry_credential_set_resource.go | 6 +- ...r_registry_credential_set_resource_test.go | 89 ++++++++++++++++--- ...iner_registry_credential_set.html.markdown | 22 +++-- 3 files changed, 98 insertions(+), 19 deletions(-) diff --git a/internal/services/containers/container_registry_credential_set_resource.go b/internal/services/containers/container_registry_credential_set_resource.go index 3e243eff9938..894bbf08c990 100644 --- a/internal/services/containers/container_registry_credential_set_resource.go +++ b/internal/services/containers/container_registry_credential_set_resource.go @@ -281,12 +281,14 @@ func flattenIdentity(input *identity.SystemAndUserAssignedMap) []identity.ModelS if input == nil { return nil } - output := make([]identity.ModelSystemAssigned, 1) - output[0] = identity.ModelSystemAssigned{ + // the api returns 'systemAssigned' as the type instead of 'SystemAssigned'... + // in the identity package a private function to normalize the type is used (normalizeType) + systemAssignedIdentity := &identity.SystemAssigned{ Type: input.Type, TenantId: input.TenantId, PrincipalId: input.PrincipalId, } + output := identity.FlattenSystemAssignedToModel(systemAssignedIdentity) return output } diff --git a/internal/services/containers/container_registry_credential_set_resource_test.go b/internal/services/containers/container_registry_credential_set_resource_test.go index 6d38f51281f0..dab9f7f85047 100644 --- a/internal/services/containers/container_registry_credential_set_resource_test.go +++ b/internal/services/containers/container_registry_credential_set_resource_test.go @@ -44,10 +44,7 @@ func TestAccContainerRegistryCredentialSet_requiresImport(t *testing.T) { check.That(data.ResourceName).ExistsInAzure(r), ), }, - { - Config: r.requiresImport(data), - ExpectError: acceptance.RequiresImportError("azurerm_container_registry_credential_set"), - }, + data.RequiresImportErrorStep(r.requiresImport), }) } @@ -98,6 +95,39 @@ resource "azurerm_resource_group" "test" { location = "%s" } +data "azurerm_client_config" "current" {} + +resource "azurerm_key_vault" "test" { + name = "vault%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "standard" + 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 + certificate_permissions = [] + key_permissions = [] + secret_permissions = [ + "Get", "Set", "Delete", "Purge" + ] + } +} + +resource "azurerm_key_vault_secret" "test-user-name" { + key_vault_id = azurerm_key_vault.test.id + name = "acr-cs-user-name" + value = "name" +} + +resource "azurerm_key_vault_secret" "test-user-password" { + key_vault_id = azurerm_key_vault.test.id + name = "acr-cs-user-password" + value = "password" +} + resource "azurerm_container_registry" "test" { name = "testacccr%d" location = azurerm_resource_group.test.location @@ -113,11 +143,11 @@ resource "azurerm_container_registry_credential_set" "test" { type = "SystemAssigned" } authentication_credentials { - username_secret_id = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-name" - password_secret_id = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-password" + username_secret_id = azurerm_key_vault_secret.test-user-name.versionless_id + password_secret_id = azurerm_key_vault_secret.test-user-password.versionless_id } } -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) } func (r ContainerRegistryCredentialSetResource) requiresImport(data acceptance.TestData) string { @@ -150,6 +180,45 @@ resource "azurerm_resource_group" "test" { location = "%s" } +data "azurerm_client_config" "current" {} + +resource "azurerm_key_vault" "test" { + name = "vault%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "standard" + 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 + certificate_permissions = [] + key_permissions = [] + secret_permissions = [ + "Get", "Set", "Delete", "Purge" + ] + } +} + +resource "azurerm_key_vault_secret" "test-user-name" { + key_vault_id = azurerm_key_vault.test.id + name = "acr-cs-user-name" + value = "name" +} + +resource "azurerm_key_vault_secret" "test-user-password" { + key_vault_id = azurerm_key_vault.test.id + name = "acr-cs-user-password" + value = "password" +} + +resource "azurerm_key_vault_secret" "test-other-user-password" { + key_vault_id = azurerm_key_vault.test.id + name = "acr-cs-other-user-password" + value = "otherpassword" +} + resource "azurerm_container_registry" "test" { name = "testacccr%d" location = azurerm_resource_group.test.location @@ -165,9 +234,9 @@ resource "azurerm_container_registry_credential_set" "test" { type = "SystemAssigned" } authentication_credentials { - username_secret_id = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-name-changed" - password_secret_id = "https://example-keyvault.vault.azure.net/secrets/acr-cs-user-password" + username_secret_id = azurerm_key_vault_secret.test-user-name.versionless_id + password_secret_id = azurerm_key_vault_secret.test-other-user-password.versionless_id } } -`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger, data.RandomInteger) } diff --git a/website/docs/r/container_registry_credential_set.html.markdown b/website/docs/r/container_registry_credential_set.html.markdown index fb847ac69c3e..72311bf0e8c2 100644 --- a/website/docs/r/container_registry_credential_set.html.markdown +++ b/website/docs/r/container_registry_credential_set.html.markdown @@ -54,11 +54,19 @@ resource "azurerm_resource_group" "example" { data "azurerm_client_config" "current" {} resource "azurerm_key_vault" "example" { - name = "examplekeyvault" - location = azurerm_resource_group.example.location - resource_group_name = azurerm_resource_group.example.name - tenant_id = data.azurerm_client_config.current.tenant_id - sku_name = "standard" + name = "examplekeyvault" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "standard" + 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 + certificate_permissions = [] + key_permissions = [] + secret_permissions = ["Get", "Set", "Delete", "Purge"] + } } resource "azurerm_key_vault_secret" "example_user" { @@ -88,8 +96,8 @@ resource "azurerm_container_registry_credential_set" "example" { type = "SystemAssigned" } authentication_credentials { - username_secret_id = azurerm_key_vault_secret.example_user.resource_versionless_id - password_secret_id = azurerm_key_vault_secret.example_password.resource_versionless_id + username_secret_id = azurerm_key_vault_secret.example_user.versionless_id + password_secret_id = azurerm_key_vault_secret.example_password.versionless_id } } From c1a73eb9bad475840cfa2e3a5c8b08900861ef1c Mon Sep 17 00:00:00 2001 From: jan-mrm <67435696+jan-mrm@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:40:41 +0100 Subject: [PATCH 10/12] refactor: tf fmt --- ...ontainer_registry_credential_set_resource_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/services/containers/container_registry_credential_set_resource_test.go b/internal/services/containers/container_registry_credential_set_resource_test.go index dab9f7f85047..eb590c0118aa 100644 --- a/internal/services/containers/container_registry_credential_set_resource_test.go +++ b/internal/services/containers/container_registry_credential_set_resource_test.go @@ -106,11 +106,11 @@ resource "azurerm_key_vault" "test" { 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 certificate_permissions = [] key_permissions = [] - secret_permissions = [ + secret_permissions = [ "Get", "Set", "Delete", "Purge" ] } @@ -191,11 +191,11 @@ resource "azurerm_key_vault" "test" { 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 certificate_permissions = [] key_permissions = [] - secret_permissions = [ + secret_permissions = [ "Get", "Set", "Delete", "Purge" ] } From f1a6e5fdd54558eab29b77ab571f1a1dd03a14bf Mon Sep 17 00:00:00 2001 From: jan-mrm <67435696+jan-mrm@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:29:29 +0100 Subject: [PATCH 11/12] refactor: update to now returned ModelSystemAssigned which is now returned because of the implemented workaround in the go-azure-sdk --- ...tainer_registry_credential_set_resource.go | 44 +++++-------------- 1 file changed, 12 insertions(+), 32 deletions(-) diff --git a/internal/services/containers/container_registry_credential_set_resource.go b/internal/services/containers/container_registry_credential_set_resource.go index 894bbf08c990..23a4dac4ab15 100644 --- a/internal/services/containers/container_registry_credential_set_resource.go +++ b/internal/services/containers/container_registry_credential_set_resource.go @@ -129,13 +129,18 @@ func (r ContainerRegistryCredentialSetResource) Create() sdk.ResourceFunc { return metadata.ResourceRequiresImport(r.ResourceType(), id) } + identityExpanded, err := identity.ExpandSystemAssignedFromModel(config.Identity) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) + } + param := credentialsets.CredentialSet{ Name: pointer.To(id.CredentialSetName), Properties: &credentialsets.CredentialSetProperties{ LoginServer: pointer.To(config.LoginServer), AuthCredentials: expandAuthCredentials(config.AuthenticationCredential), }, - Identity: expandIdentity(config.Identity), + Identity: identityExpanded, } if err := client.CreateThenPoll(ctx, id, param); err != nil { @@ -174,7 +179,11 @@ func (r ContainerRegistryCredentialSetResource) Update() sdk.ResourceFunc { param.Properties = &properties if metadata.ResourceData.HasChange("identity") { - param.Identity = expandIdentity(model.Identity) + identityExpanded, err := identity.ExpandSystemAssignedFromModel(model.Identity) + if err != nil { + return fmt.Errorf("expanding `identity`: %+v", err) + } + param.Identity = identityExpanded } if err := client.UpdateThenPoll(ctx, *id, param); err != nil { @@ -214,7 +223,7 @@ func (ContainerRegistryCredentialSetResource) Read() sdk.ResourceFunc { config.ContainerRegistryId = registryId.ID() if model := resp.Model; model != nil { - config.Identity = flattenIdentity(model.Identity) + config.Identity = identity.FlattenSystemAssignedToModel(model.Identity) if props := model.Properties; props != nil { config.LoginServer = pointer.From(props.LoginServer) config.AuthenticationCredential = flattenAuthCredentials(props.AuthCredentials) @@ -275,32 +284,3 @@ func flattenAuthCredentials(input *[]credentialsets.AuthCredential) []Authentica } return output } - -// read the note [1] above why we transform the identity here like that -func flattenIdentity(input *identity.SystemAndUserAssignedMap) []identity.ModelSystemAssigned { - if input == nil { - return nil - } - // the api returns 'systemAssigned' as the type instead of 'SystemAssigned'... - // in the identity package a private function to normalize the type is used (normalizeType) - systemAssignedIdentity := &identity.SystemAssigned{ - Type: input.Type, - TenantId: input.TenantId, - PrincipalId: input.PrincipalId, - } - output := identity.FlattenSystemAssignedToModel(systemAssignedIdentity) - return output -} - -// read the note [1] above why we transform the identity here like that -func expandIdentity(input []identity.ModelSystemAssigned) *identity.SystemAndUserAssignedMap { - if len(input) == 0 { - return nil - } - output := identity.SystemAndUserAssignedMap{ - Type: input[0].Type, - TenantId: input[0].TenantId, - PrincipalId: input[0].PrincipalId, - } - return &output -} From ce63b3a683e054950698d873cbd5dc1bc3d43870 Mon Sep 17 00:00:00 2001 From: jan-mrm <67435696+jan-mrm@users.noreply.github.com> Date: Thu, 16 Jan 2025 10:52:19 +0100 Subject: [PATCH 12/12] Update internal/services/containers/container_registry_credential_set_resource.go Co-authored-by: stephybun --- .../container_registry_credential_set_resource.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/internal/services/containers/container_registry_credential_set_resource.go b/internal/services/containers/container_registry_credential_set_resource.go index 23a4dac4ab15..01ce86b6f6f6 100644 --- a/internal/services/containers/container_registry_credential_set_resource.go +++ b/internal/services/containers/container_registry_credential_set_resource.go @@ -60,13 +60,8 @@ func (ContainerRegistryCredentialSetResource) Arguments() map[string]*pluginsdk. }, }, }, - // Note [1]: At point in time of the implementation of this resource the API only accept SystemAssigned even though API Spec defines all three identity modes are possible - // Error when trying with type UserAssigned: - // code: "CannotSetResourceIdentity" - // message: "The resource identity 'UserAssigned' cannot be set on the resource of type 'Microsoft.ContainerRegistry/registries/credentialSets'." - // or with type empty: - // code: "OnlySystemManagedIdentityAllowed" - // message: "Only System Managed Identities are allowed for resources of type 'Microsoft.ContainerRegistry/registries/credentialSets'. For more information, please visit https://aka.ms/acr/cache." + // This property relies on a pandora workaround due to a swagger issue + // https://github.com/Azure/azure-rest-api-specs/issues/32154 "identity": commonschema.SystemAssignedIdentityRequired(), } }