Skip to content

Commit

Permalink
Merge pull request #236 from mergenci/f-mq-user
Browse files Browse the repository at this point in the history
Add MQ User resource.
  • Loading branch information
mergenci authored May 23, 2024
2 parents f2f0fdd + ff919c1 commit 6c8577b
Show file tree
Hide file tree
Showing 8 changed files with 432 additions and 3 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/medialive v1.43.3
github.com/aws/aws-sdk-go-v2/service/mediapackage v1.28.5
github.com/aws/aws-sdk-go-v2/service/mediapackagev2 v1.7.5
github.com/aws/aws-sdk-go-v2/service/mq v1.20.7
github.com/aws/aws-sdk-go-v2/service/oam v1.7.5
github.com/aws/aws-sdk-go-v2/service/opensearchserverless v1.9.5
github.com/aws/aws-sdk-go-v2/service/osis v1.6.5
Expand Down Expand Up @@ -100,6 +101,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/vpclattice v1.5.5
github.com/aws/aws-sdk-go-v2/service/workspaces v1.35.5
github.com/aws/aws-sdk-go-v2/service/xray v1.23.5
github.com/aws/smithy-go v1.19.0
github.com/beevik/etree v1.2.0
github.com/davecgh/go-spew v1.1.1
github.com/gertd/go-pluralize v0.2.1
Expand Down Expand Up @@ -162,7 +164,6 @@ require (
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.9 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect
github.com/aws/smithy-go v1.19.0 // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/boombuler/barcode v1.0.1 // indirect
github.com/bufbuild/protocompile v0.6.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ github.com/aws/aws-sdk-go-v2/service/mediapackage v1.28.5 h1:z+b1lClMC3rSxlUQqRb
github.com/aws/aws-sdk-go-v2/service/mediapackage v1.28.5/go.mod h1:wGaElJ8kmGJ08nnirzZ/6iWKqBPErlHqtpkbx9go82Q=
github.com/aws/aws-sdk-go-v2/service/mediapackagev2 v1.7.5 h1:tkFfqFu8yx0AmRZAlwcF6hdDf7E7J+0P4tRAtfVB2bA=
github.com/aws/aws-sdk-go-v2/service/mediapackagev2 v1.7.5/go.mod h1:pPsl4jKNPkhp2unuSQ3upeQ+9U8onSOPA2B++m5bD8o=
github.com/aws/aws-sdk-go-v2/service/mq v1.20.7 h1:d4hynfHB+JsY5zcek4ITxVnVEkkxa7ZZDJW6OOPwEeQ=
github.com/aws/aws-sdk-go-v2/service/mq v1.20.7/go.mod h1:PHzqJZbmPmkFXCZiaOdEY9KoGFYWD2lVO2Rv8Om1hSg=
github.com/aws/aws-sdk-go-v2/service/oam v1.7.5 h1:Z5qjasrNlticGJVwZahvPiv7cnGeuEFGQ5AdCeTgf/0=
github.com/aws/aws-sdk-go-v2/service/oam v1.7.5/go.mod h1:qwJgNmAMUGFkLgAgTtkZZpGf9Qe1L0PwMD4oXMeS9Ic=
github.com/aws/aws-sdk-go-v2/service/opensearchserverless v1.9.5 h1:V+zBQiUAATdwx3rLbc4Em+G0IeqPtY1281lHMrTvIK4=
Expand Down
5 changes: 5 additions & 0 deletions internal/conns/awsclient_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 19 additions & 1 deletion internal/service/mq/service_package_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

277 changes: 277 additions & 0 deletions internal/service/mq/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package mq

import (
"context"
"fmt"
"strings"

"github.com/aws/aws-sdk-go-v2/service/mq"
awstypes "github.com/aws/aws-sdk-go-v2/service/mq/types"
"github.com/aws/aws-sdk-go/aws"

"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/hashicorp/terraform-provider-aws/internal/create"
"github.com/hashicorp/terraform-provider-aws/internal/errs"
"github.com/hashicorp/terraform-provider-aws/internal/framework"
"github.com/hashicorp/terraform-provider-aws/internal/framework/flex"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/names"
)

// @FrameworkResource(name="User")
func newResourceUser(_ context.Context) (resource.ResourceWithConfigure, error) {
return &resourceUser{}, nil
}

const (
ResourceNameUser = "User"
)

type resourceUser struct {
framework.ResourceWithConfigure
}

func (r *resourceUser) Metadata(_ context.Context, _ resource.MetadataRequest, response *resource.MetadataResponse) {
response.TypeName = "aws_mq_user"
}

// Schema returns the schema for this resource.
func (r *resourceUser) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) {
response.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"broker_id": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"console_access": schema.BoolAttribute{
Optional: true,
},
"groups": schema.ListAttribute{
ElementType: types.StringType,
Optional: true,
},
"id": framework.IDAttribute(),
"password": schema.StringAttribute{
Required: true,
Sensitive: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(12),
},
},
"replication_user": schema.BoolAttribute{
Optional: true,
},
"username": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
},
}
}

func (r *resourceUser) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
var plan resourceUserData
response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...)
if response.Diagnostics.HasError() {
return
}

conn := r.Meta().MQClient(ctx)

input := &mq.CreateUserInput{
BrokerId: flex.StringFromFramework(ctx, plan.BrokerID),
Password: flex.StringFromFramework(ctx, plan.Password),
Username: flex.StringFromFramework(ctx, plan.Username),
ConsoleAccess: flex.BoolFromFramework(ctx, plan.ConsoleAccess),
Groups: flex.ExpandFrameworkStringValueList(ctx, plan.Groups),
ReplicationUser: flex.BoolFromFramework(ctx, plan.ReplicationUser),
}
_, err := conn.CreateUser(ctx, input)
if err != nil {
response.Diagnostics.Append(create.DiagErrorFramework(names.MQ, create.ErrActionCreating, ResourceNameUser, fmt.Sprintf("%s/%s", plan.BrokerID.ValueString(), plan.Username.ValueString()), err))
return
}

// Create API call returns no data. Get resource details.
userDetails, err := findUserByID(ctx, conn, plan.BrokerID.ValueString(), plan.Username.ValueString())
if err != nil {
response.Diagnostics.Append(create.DiagErrorFramework(names.MQ, create.ErrActionCreating, ResourceNameUser, fmt.Sprintf("%s/%s", plan.BrokerID.ValueString(), plan.Username.ValueString()), err))
return
}

state := plan
state.refreshFromOutput(ctx, userDetails)

response.Diagnostics.Append(response.State.Set(ctx, state)...)
}

func (r *resourceUser) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
var state resourceUserData
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

conn := r.Meta().MQClient(ctx)

userDetails, err := findUserByID(ctx, conn, state.BrokerID.ValueString(), state.ID.ValueString())
if tfresource.NotFound(err) {
create.LogNotFoundRemoveState(names.MQ, create.ErrActionReading, ResourceNameUser, fmt.Sprintf("%s/%s", state.BrokerID.ValueString(), state.ID.ValueString()))
response.State.RemoveResource(ctx)
return
}
if err != nil {
response.Diagnostics.Append(create.DiagErrorFramework(names.MQ, create.ErrActionReading, ResourceNameUser, fmt.Sprintf("%s/%s", state.BrokerID.ValueString(), state.ID.ValueString()), err))
return
}

state.refreshFromOutput(ctx, userDetails)
response.Diagnostics.Append(response.State.Set(ctx, &state)...)
}

func (r *resourceUser) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
var state, plan resourceUserData

response.Diagnostics.Append(request.State.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...)
if response.Diagnostics.HasError() {
return
}

if userHasChanges(plan, state) {
conn := r.Meta().MQClient(ctx)

input := &mq.UpdateUserInput{
BrokerId: flex.StringFromFramework(ctx, plan.BrokerID),
Password: flex.StringFromFramework(ctx, plan.Password),
Username: flex.StringFromFramework(ctx, plan.ID),
ConsoleAccess: flex.BoolFromFramework(ctx, plan.ConsoleAccess),
Groups: flex.ExpandFrameworkStringValueList(ctx, plan.Groups),
ReplicationUser: flex.BoolFromFramework(ctx, plan.ReplicationUser),
}
_, err := conn.UpdateUser(ctx, input)
if err != nil {
response.Diagnostics.Append(create.DiagErrorFramework(names.MQ, create.ErrActionUpdating, ResourceNameUser, fmt.Sprintf("%s/%s", state.BrokerID.ValueString(), state.ID.ValueString()), err))
return
}
}

response.Diagnostics.Append(response.State.Set(ctx, &plan)...)
}

func (r *resourceUser) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
var state resourceUserData
response.Diagnostics.Append(request.State.Get(ctx, &state)...)
if response.Diagnostics.HasError() {
return
}

conn := r.Meta().MQClient(ctx)

input := &mq.DeleteUserInput{
BrokerId: flex.StringFromFramework(ctx, state.BrokerID),
Username: flex.StringFromFramework(ctx, state.ID),
}
_, err := conn.DeleteUser(ctx, input)
if err != nil {
response.Diagnostics.Append(create.DiagErrorFramework(names.MQ, create.ErrActionDeleting, ResourceNameUser, fmt.Sprintf("%s/%s", state.BrokerID.ValueString(), state.ID.ValueString()), err))
return
}
}

func (r *resourceUser) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
parts := strings.Split(request.ID, "/")
if len(parts) != 2 {
response.Diagnostics.AddError("Resource Import Invalid ID", fmt.Sprintf("wrong format of import ID (%s), use: broker-id/username'", request.ID))
return
}

brokerID := parts[0]
username := parts[1]
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("id"), username)...)
response.Diagnostics.Append(response.State.SetAttribute(ctx, path.Root("broker_id"), brokerID)...)
}

func findUserByID(ctx context.Context, conn *mq.Client, brokerID string, id string) (*mq.DescribeUserOutput, error) {
if brokerID == "" {
return nil, &retry.NotFoundError{
Message: "cannot find User with an empty broker ID.",
}
}
if id == "" {
return nil, &retry.NotFoundError{
Message: "cannot find User with an empty username.",
}
}

input := &mq.DescribeUserInput{
BrokerId: aws.String(brokerID),
Username: aws.String(id),
}

output, err := conn.DescribeUser(ctx, input)
if errs.IsA[*awstypes.NotFoundException](err) {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: input,
}
}
if err != nil {
return nil, err
}
if output == nil {
return nil, tfresource.NewEmptyResultError(input)
}

return output, nil
}

type resourceUserData struct {
BrokerID types.String `tfsdk:"broker_id"`
ConsoleAccess types.Bool `tfsdk:"console_access"`
Groups types.List `tfsdk:"groups"`
ID types.String `tfsdk:"id"`
Password types.String `tfsdk:"password"`
// Pending types.Object `tfsdk:"pending"`
ReplicationUser types.Bool `tfsdk:"replication_user"`
Username types.String `tfsdk:"username"`
}

func (rd *resourceUserData) refreshFromOutput(ctx context.Context, out *mq.DescribeUserOutput) {
if out == nil {
return
}

rd.BrokerID = flex.StringToFramework(ctx, out.BrokerId)
rd.ConsoleAccess = flex.BoolToFramework(ctx, out.ConsoleAccess)
rd.Groups = flex.FlattenFrameworkStringValueList(ctx, out.Groups)
rd.ReplicationUser = flex.BoolToFramework(ctx, out.ReplicationUser)
rd.Username = flex.StringToFramework(ctx, out.Username)
rd.ID = rd.Username
}

func userHasChanges(plan, state resourceUserData) bool {
return !plan.ConsoleAccess.Equal(state.ConsoleAccess) ||
!plan.Groups.Equal(state.Groups) ||
!plan.Password.Equal(state.Password) ||
!plan.ReplicationUser.Equal(state.ReplicationUser)
}
Loading

0 comments on commit 6c8577b

Please sign in to comment.