From a29f9131f835bb2984e09f2342a2d63467dbf46c Mon Sep 17 00:00:00 2001 From: Alper Rifat Ulucinar Date: Tue, 21 May 2024 17:02:34 +0300 Subject: [PATCH 1/6] Copy xpprovider/xpprovider.go & internal/conns/awsclient_xp.go to expose some provider internals. Signed-off-by: Alper Rifat Ulucinar --- internal/conns/awsclient_xp.go | 10 +++++ xpprovider/xpprovider.go | 69 ++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 internal/conns/awsclient_xp.go create mode 100644 xpprovider/xpprovider.go diff --git a/internal/conns/awsclient_xp.go b/internal/conns/awsclient_xp.go new file mode 100644 index 00000000000..478298e2430 --- /dev/null +++ b/internal/conns/awsclient_xp.go @@ -0,0 +1,10 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package conns + +import "github.com/aws/smithy-go/middleware" + +func (c *AWSClient) AppendAPIOptions(options ...func(stack *middleware.Stack) error) { + c.awsConfig.APIOptions = append(c.awsConfig.APIOptions, options...) +} diff --git a/xpprovider/xpprovider.go b/xpprovider/xpprovider.go new file mode 100644 index 00000000000..38c41083b57 --- /dev/null +++ b/xpprovider/xpprovider.go @@ -0,0 +1,69 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package xpprovider exports needed internal types and functions used by Crossplane for instantiating, interacting and +// configuring the underlying Terraform AWS providers. +package xpprovider + +import ( + "context" + + fwprovider "github.com/hashicorp/terraform-plugin-framework/provider" + fwschema "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/provider" + internalfwprovider "github.com/hashicorp/terraform-provider-aws/internal/provider/fwprovider" +) + +// AWSConfig exports the internal type conns.Config of the Terraform provider +type AWSConfig conns.Config + +// AWSClient exports the internal type conns.AWSClient of the Terraform provider +type AWSClient = conns.AWSClient + +// GetProvider returns new provider instances for both Terraform Plugin Framework provider of type provider.Provider +// and Terraform Plugin SDKv2 provider of type *schema.Provider +// provider +func GetProvider(ctx context.Context) (fwprovider.Provider, *schema.Provider, error) { + p, err := provider.New(ctx) + if err != nil { + return nil, nil, err + } + fwProvider := internalfwprovider.New(p) + return fwProvider, p, err +} + +// GetProviderSchema returns the Terraform Plugin SDKv2 provider schema of the provider +func GetProviderSchema(ctx context.Context) (*schema.Provider, error) { + return provider.New(ctx) +} + +// GetFrameworkProviderSchema returns the Terraform Plugin Framework provider schema of the provider +func GetFrameworkProviderSchema(ctx context.Context) (fwschema.Schema, error) { + fwProvider, _, err := GetProvider(ctx) + if err != nil { + return fwschema.Schema{}, err + } + schemaReq := fwprovider.SchemaRequest{} + schemaResp := fwprovider.SchemaResponse{} + fwProvider.Schema(ctx, schemaReq, &schemaResp) + return schemaResp.Schema, nil +} + +// GetFrameworkProviderWithMeta returns a new Terraform Plugin Framework-style provider instance with the given +// provider meta (AWS client). Supplied meta can be any type implementing Meta(), that returns a configured AWS Client +// of type *conns.AWSClient +// Can be used to create provider instances with arbitrary AWS clients. +func GetFrameworkProviderWithMeta(primary interface{ Meta() interface{} }) fwprovider.Provider { + return internalfwprovider.New(primary) +} + +// GetClient configures the supplied provider meta (in the *AWSClient). It is a wrapper function that exports +// the internal type conns.Config's ConfigureProvider() func, over the exported type AWSConfig +// supplied *AWSClient in the arguments, is an in-out argument and is returned back as internal type *conns.AWSClient +func (ac *AWSConfig) GetClient(ctx context.Context, client *AWSClient) (*conns.AWSClient, diag.Diagnostics) { + return (*conns.Config)(ac).ConfigureProvider(ctx, (*conns.AWSClient)(client)) +} From a17f1924c2c28c32545986fc192e89e3e3fada24 Mon Sep 17 00:00:00 2001 From: Alper Rifat Ulucinar Date: Tue, 21 May 2024 20:23:42 +0300 Subject: [PATCH 2/6] Expose AWSClient.session via the AWSClient.Session getter function Signed-off-by: Alper Rifat Ulucinar --- internal/conns/awsclient_xp.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/internal/conns/awsclient_xp.go b/internal/conns/awsclient_xp.go index 478298e2430..b7968496316 100644 --- a/internal/conns/awsclient_xp.go +++ b/internal/conns/awsclient_xp.go @@ -3,8 +3,18 @@ package conns -import "github.com/aws/smithy-go/middleware" +import ( + session_sdkv1 "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/smithy-go/middleware" +) +// AppendAPIOptions appends the specified AWS client APIOptions to +// the client c. func (c *AWSClient) AppendAPIOptions(options ...func(stack *middleware.Stack) error) { c.awsConfig.APIOptions = append(c.awsConfig.APIOptions, options...) } + +// Session returns the associated session with this client. +func (c *AWSClient) Session() *session_sdkv1.Session { + return c.session +} From aa9b57539204a0b6498ba67e7e9c6d08ddc5b6a2 Mon Sep 17 00:00:00 2001 From: Cem Mergenci Date: Tue, 21 May 2024 21:07:57 +0300 Subject: [PATCH 3/6] Add MQ User resource. Signed-off-by: Cem Mergenci --- internal/service/mq/service_package_gen.go | 7 +- internal/service/mq/user.go | 277 +++++++++++++++++++++ internal/service/mq/user_test.go | 125 ++++++++++ 3 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 internal/service/mq/user.go create mode 100644 internal/service/mq/user_test.go diff --git a/internal/service/mq/service_package_gen.go b/internal/service/mq/service_package_gen.go index b39f6eb3ae8..d8578cb9d79 100644 --- a/internal/service/mq/service_package_gen.go +++ b/internal/service/mq/service_package_gen.go @@ -19,7 +19,12 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv } func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { - return []*types.ServicePackageFrameworkResource{} + return []*types.ServicePackageFrameworkResource{ + { + Factory: newResourceUser, + Name: "User", + }, + } } func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { diff --git a/internal/service/mq/user.go b/internal/service/mq/user.go new file mode 100644 index 00000000000..c90551e6a37 --- /dev/null +++ b/internal/service/mq/user.go @@ -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) +} diff --git a/internal/service/mq/user_test.go b/internal/service/mq/user_test.go new file mode 100644 index 00000000000..2b3b2a11ba5 --- /dev/null +++ b/internal/service/mq/user_test.go @@ -0,0 +1,125 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package mq_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/mq" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + tfmq "github.com/hashicorp/terraform-provider-aws/internal/service/mq" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func init() { + acctest.RegisterServiceErrorCheckFunc(names.MQEndpointID, testAccErrorCheckSkip) +} + +func testAccErrorCheckSkip(t *testing.T) resource.ErrorCheckFunc { + return acctest.ErrorCheckSkipMessagesContaining(t, + "To be determined...", + ) +} + +func TestAccUser_serial(t *testing.T) { + testCases := map[string]func(t *testing.T){ + "basic": testAccUser_basic, + // "disappears": testAccAccountRegistration_disappears, + // "kms key": testAccAccountRegistration_optionalKMSKey, + } + + acctest.RunSerialTests1Level(t, testCases, 0) +} + +func testAccUser_basic(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_mq_user.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.MQEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + // CheckDestroy: testAccCheckAccountRegistrationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccConfigUser_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserExists(ctx, resourceName), + ), + }, + { + ResourceName: resourceName, + ImportStateIdFunc: testAccUserImportStateIDFunc(ctx, resourceName), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccUserImportStateIDFunc(ctx context.Context, resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + err := testAccCheckUserExists(ctx, resourceName)(s) + if err != nil { + return "", fmt.Errorf("cannot generate import ID because resource doesn't exist: %v", err) + } + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", create.Error(names.MQ, create.ErrActionCheckingExistence, tfmq.ResourceNameUser, resourceName, errors.New("not found")) + } + + brokerID := rs.Primary.Attributes["broker_id"] + username := rs.Primary.ID + return fmt.Sprintf("%s/%s", brokerID, username), nil + } +} + +func testAccCheckUserExists(ctx context.Context, resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return create.Error(names.MQ, create.ErrActionCheckingExistence, tfmq.ResourceNameUser, resourceName, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.MQ, create.ErrActionCheckingExistence, tfmq.ResourceNameUser, resourceName, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).MQClient(ctx) + brokerID := rs.Primary.Attributes["broker_id"] + out, err := conn.DescribeUser(ctx, &mq.DescribeUserInput{ + BrokerId: &brokerID, + Username: &rs.Primary.ID, + }) + if err != nil { + return create.Error(names.MQ, create.ErrActionCheckingExistence, tfmq.ResourceNameUser, rs.Primary.ID, err) + } + + if out == nil { + return create.Error(names.MQ, create.ErrActionCheckingExistence, tfmq.ResourceNameUser, rs.Primary.ID, errors.New("mq user not active")) + } + + return nil + } +} + +func testAccConfigUser_basic() string { + return ` +resource "aws_mq_user" "test" { + broker_id = "" + username = "testuser" + password = "v98jxV3U0288" +} +` +} From 3818260acaa812a7973d189d1d1d2e3ea4d989d4 Mon Sep 17 00:00:00 2001 From: Erhan Cagirici Date: Fri, 18 Oct 2024 14:25:54 +0300 Subject: [PATCH 4/6] add null check for lb_listener forward action config Signed-off-by: Erhan Cagirici --- internal/service/elbv2/listener.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/elbv2/listener.go b/internal/service/elbv2/listener.go index 4c8abea486a..ad1cf8892aa 100644 --- a/internal/service/elbv2/listener.go +++ b/internal/service/elbv2/listener.go @@ -1253,7 +1253,7 @@ func flattenForwardActionOneOf(actions cty.Value, i int, awsAction awstypes.Acti action := actions.Index(index) if action.IsKnown() && !action.IsNull() { forward := action.GetAttr("forward") - if forward.IsKnown() && forward.LengthInt() > 0 { + if forward.IsKnown() && !forward.IsNull() && forward.LengthInt() > 0 { actionMap["forward"] = flattenListenerActionForwardConfig(awsAction.ForwardConfig) } else { actionMap["target_group_arn"] = aws.ToString(awsAction.TargetGroupArn) From ff257c3d8ca76b2e0a9180e9d860c209fb308a30 Mon Sep 17 00:00:00 2001 From: Cem Mergenci Date: Mon, 7 Oct 2024 21:09:52 +0300 Subject: [PATCH 5/6] Implement deprecated diagnostic helper function locally. Signed-off-by: Cem Mergenci --- internal/service/mq/user.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/internal/service/mq/user.go b/internal/service/mq/user.go index c90551e6a37..7482851d1bf 100644 --- a/internal/service/mq/user.go +++ b/internal/service/mq/user.go @@ -13,6 +13,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -29,6 +30,15 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) +// diagErrorFramework is a helper method that creates an error diagnostic. +// This method was formerly at internal/create/errors.go +func diagErrorFramework(service, action, resource, id string, gotError error) diag.Diagnostic { + return diag.NewErrorDiagnostic( + create.ProblemStandardMessage(service, action, resource, id, nil), + gotError.Error(), + ) +} + // @FrameworkResource(name="User") func newResourceUser(_ context.Context) (resource.ResourceWithConfigure, error) { return &resourceUser{}, nil @@ -103,14 +113,14 @@ func (r *resourceUser) Create(ctx context.Context, request resource.CreateReques } _, 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)) + response.Diagnostics.Append(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)) + response.Diagnostics.Append(diagErrorFramework(names.MQ, create.ErrActionCreating, ResourceNameUser, fmt.Sprintf("%s/%s", plan.BrokerID.ValueString(), plan.Username.ValueString()), err)) return } @@ -136,7 +146,7 @@ func (r *resourceUser) Read(ctx context.Context, request resource.ReadRequest, r 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)) + response.Diagnostics.Append(diagErrorFramework(names.MQ, create.ErrActionReading, ResourceNameUser, fmt.Sprintf("%s/%s", state.BrokerID.ValueString(), state.ID.ValueString()), err)) return } @@ -170,7 +180,7 @@ func (r *resourceUser) Update(ctx context.Context, request resource.UpdateReques } _, 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)) + response.Diagnostics.Append(diagErrorFramework(names.MQ, create.ErrActionUpdating, ResourceNameUser, fmt.Sprintf("%s/%s", state.BrokerID.ValueString(), state.ID.ValueString()), err)) return } } @@ -193,7 +203,7 @@ func (r *resourceUser) Delete(ctx context.Context, request resource.DeleteReques } _, 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)) + response.Diagnostics.Append(diagErrorFramework(names.MQ, create.ErrActionDeleting, ResourceNameUser, fmt.Sprintf("%s/%s", state.BrokerID.ValueString(), state.ID.ValueString()), err)) return } } From bed7ae32a1da475c51b2d6e3c5f2b5205bed10ca Mon Sep 17 00:00:00 2001 From: Cem Mergenci Date: Mon, 23 Dec 2024 22:36:57 +0300 Subject: [PATCH 6/6] Guard against panic in cty.Value.LengthInt(). Signed-off-by: Cem Mergenci --- internal/tags/key_value_tags.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/tags/key_value_tags.go b/internal/tags/key_value_tags.go index 725eae7aeec..81e26b8a32d 100644 --- a/internal/tags/key_value_tags.go +++ b/internal/tags/key_value_tags.go @@ -800,6 +800,10 @@ func GetAnyAttr(value cty.Value, attr string, shouldReturnSetElement func(string return cty.NilVal, fmt.Errorf("invalid index: %s", indexStr) } + if value.Type().IsListType() && (value.IsNull() || !value.IsKnown()) { + return cty.NilVal, fmt.Errorf("list attribute %s is unknown or null", attrName) + } + if index >= value.LengthInt() { return cty.NilVal, fmt.Errorf("index %d out of range for attribute %s", index, attrName) }