Skip to content

Commit

Permalink
New Resource: azurerm_container_registry_credential_set (#27528)
Browse files Browse the repository at this point in the history
* feat: add new resource azurerm_container_registry_credential_set

* fix: set auth_credentials correctly in import test

* refactor: implement review feedback

* refactor: format docs

* chore: update api version in new credential set resource after it has been updated in the main

* chore: add changes from 'make generate'

* refactor: change implementation to SystemAssignedUserAssignedIdentityRequired as discussed in the pull request (but the api will only accept type SystemAssigned)

* refactor: change implementation from SystemAssignedUserAssignedIdentityRequired to SystemAssignedIdentityRequired

* refactor: implement review feedback - import step and key vault in tests. update markdown. update flattenIdentity

* refactor: tf fmt

* refactor: update to now returned ModelSystemAssigned which is now returned because of the implemented workaround in the go-azure-sdk

* Update internal/services/containers/container_registry_credential_set_resource.go

Co-authored-by: stephybun <steph@hashicorp.com>

---------

Co-authored-by: stephybun <steph@hashicorp.com>
  • Loading branch information
jan-mrm and stephybun authored Jan 16, 2025
1 parent 5a1f433 commit 28dae03
Show file tree
Hide file tree
Showing 6 changed files with 704 additions and 1 deletion.
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,281 @@
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,
},
},
},
},
// 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(),
}
}

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

0 comments on commit 28dae03

Please sign in to comment.