Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Resource: azurerm_container_registry_credential_set #27528

Merged
merged 15 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/labeler-issue-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -357,4 +357,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_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_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)*)###'
9 changes: 9 additions & 0 deletions internal/services/containers/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/hashicorp/go-azure-sdk/resource-manager/containerinstance/2023-05-01/containerinstance"
containerregistry_v2019_06_01_preview "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2019-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"
containerregistry "github.com/hashicorp/go-azure-sdk/resource-manager/containerregistry/2023-11-01-preview"
"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"
Expand All @@ -28,6 +29,7 @@ type Client struct {
AgentPoolsClient *agentpools.AgentPoolsClient
ContainerInstanceClient *containerinstance.ContainerInstanceClient
CacheRulesClient *cacherules.CacheRulesClient
CredentialSetsClient *credentialsets.CredentialSetsClient
ContainerRegistryClient *containerregistry.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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -128,6 +136,7 @@ func NewContainersClient(o *common.ClientOptions) (*Client, error) {
AgentPoolsClient: agentPoolsClient,
ContainerInstanceClient: containerInstanceClient,
CacheRulesClient: cacheRulesClient,
CredentialSetsClient: credentialSetsClient,
ContainerRegistryClient: containerRegistryClient,
ContainerRegistryClient_v2019_06_01_preview: containerRegistryClient_v2019_06_01_preview,
FleetUpdateRunsClient: fleetUpdateRunsClient,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
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/commonschema"
"github.com/hashicorp/go-azure-helpers/resourcemanager/identity"
"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"
"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,
},
"authentication_credentials": {
Type: pluginsdk.TypeList,
Required: true,
MaxItems: 1,
Elem: &pluginsdk.Resource{
Schema: map[string]*schema.Schema{
"username_secret_id": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: keyVaultValidate.VersionlessNestedItemId,
},
"password_secret_id": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: keyVaultValidate.VersionlessNestedItemId,
},
},
},
},
// 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."
jan-mrm marked this conversation as resolved.
Show resolved Hide resolved
"identity": commonschema.SystemAssignedIdentityRequired(),
}
}

func (ContainerRegistryCredentialSetResource) Attributes() map[string]*pluginsdk.Schema {
return map[string]*pluginsdk.Schema{}
}

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"`
AuthenticationCredential []AuthenticationCredential `tfschema:"authentication_credentials"`
Identity []identity.ModelSystemAssigned `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(config.ContainerRegistryId)
if err != nil {
return err
}

id := credentialsets.NewCredentialSetID(subscriptionId,
registryId.ResourceGroupName,
registryId.RegistryName,
config.Name,
)

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)
}

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: identityExpanded,
}

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
}

param := credentialsets.CredentialSetUpdateParameters{}

var model ContainerRegistryCredentialSetModel
if err := metadata.Decode(&model); err != nil {
return err
}

properties := credentialsets.CredentialSetUpdateProperties{}

if metadata.ResourceData.HasChange("authentication_credentials") {
properties.AuthCredentials = expandAuthCredentials(model.AuthenticationCredential)
}

param.Properties = &properties

if metadata.ResourceData.HasChange("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 {
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 = identity.FlattenSystemAssignedToModel(model.Identity)
if props := model.Properties; props != nil {
config.LoginServer = pointer.From(props.LoginServer)
config.AuthenticationCredential = flattenAuthCredentials(props.AuthCredentials)
}
}
return metadata.Encode(&config)
},
}
}

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 []AuthenticationCredential) *[]credentialsets.AuthCredential {
output := make([]credentialsets.AuthCredential, 0)
if len(input) == 0 {
return &output
}
for _, v := range input {
output = append(output, credentialsets.AuthCredential{
Name: pointer.To(credentialsets.CredentialNameCredentialOne),
UsernameSecretIdentifier: pointer.To(v.UsernameSecretId),
PasswordSecretIdentifier: pointer.To(v.PasswordSecretId),
})
}
return &output
}

func flattenAuthCredentials(input *[]credentialsets.AuthCredential) []AuthenticationCredential {
output := make([]AuthenticationCredential, 0)
if input == nil {
return output
}
for _, v := range *input {
output = append(output, AuthenticationCredential{
UsernameSecretId: pointer.From(v.UsernameSecretIdentifier),
PasswordSecretId: pointer.From(v.PasswordSecretIdentifier),
})
}
return output
}
Loading
Loading