diff --git a/.changelog/30653.txt b/.changelog/30653.txt new file mode 100644 index 000000000000..0d439b5d25c2 --- /dev/null +++ b/.changelog/30653.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_quicksight_iam_policy_assignment +``` diff --git a/internal/service/quicksight/exports_test.go b/internal/service/quicksight/exports_test.go index 8454411badf4..d4f8c49de062 100644 --- a/internal/service/quicksight/exports_test.go +++ b/internal/service/quicksight/exports_test.go @@ -2,5 +2,6 @@ package quicksight // Exports for use in tests only. var ( - ResourceIngestion = newResourceIngestion + ResourceIngestion = newResourceIngestion + ResourceIAMPolicyAssignment = newResourceIAMPolicyAssignment ) diff --git a/internal/service/quicksight/iam_policy_assignment.go b/internal/service/quicksight/iam_policy_assignment.go new file mode 100644 index 000000000000..45313b68d490 --- /dev/null +++ b/internal/service/quicksight/iam_policy_assignment.go @@ -0,0 +1,422 @@ +package quicksight + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/quicksight" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "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" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "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/flex" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource +func newResourceIAMPolicyAssignment(_ context.Context) (resource.ResourceWithConfigure, error) { + return &resourceIAMPolicyAssignment{}, nil +} + +const ( + ResNameIAMPolicyAssignment = "IAMPolicyAssignment" + + DefaultIAMPolicyAssignmentNamespace = "default" + iamPropagationTimeout = 2 * time.Minute + identitiesUserKey = "user" + identitiesGroupKey = "group" +) + +type resourceIAMPolicyAssignment struct { + framework.ResourceWithConfigure +} + +func (r *resourceIAMPolicyAssignment) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_quicksight_iam_policy_assignment" +} + +func (r *resourceIAMPolicyAssignment) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "assignment_id": schema.StringAttribute{ + Computed: true, + }, + "assignment_name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "assignment_status": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf(quicksight.AssignmentStatus_Values()...), + }, + }, + "aws_account_id": schema.StringAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + stringplanmodifier.RequiresReplace(), + }, + }, + "id": framework.IDAttribute(), + "namespace": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString(DefaultIAMPolicyAssignmentNamespace), + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "policy_arn": schema.StringAttribute{ + Optional: true, + }, + }, + Blocks: map[string]schema.Block{ + "identities": schema.ListNestedBlock{ + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "user": schema.SetAttribute{ + Optional: true, + ElementType: types.StringType, + }, + "group": schema.SetAttribute{ + Optional: true, + ElementType: types.StringType, + }, + }, + }, + }, + }, + } +} + +func (r *resourceIAMPolicyAssignment) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().QuickSightConn() + + var plan resourceIAMPolicyAssignmentData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + if plan.AWSAccountID.IsUnknown() || plan.AWSAccountID.IsNull() { + plan.AWSAccountID = types.StringValue(r.Meta().AccountID) + } + plan.ID = types.StringValue(createIAMPolicyAssignmentID(plan.AWSAccountID.ValueString(), plan.Namespace.ValueString(), plan.AssignmentName.ValueString())) + + in := quicksight.CreateIAMPolicyAssignmentInput{ + AwsAccountId: aws.String(plan.AWSAccountID.ValueString()), + Namespace: aws.String(plan.Namespace.ValueString()), + AssignmentName: aws.String(plan.AssignmentName.ValueString()), + AssignmentStatus: aws.String(plan.AssignmentStatus.ValueString()), + } + + if !plan.Identities.IsNull() { + var identities []identitiesData + resp.Diagnostics.Append(plan.Identities.ElementsAs(ctx, &identities, false)...) + if resp.Diagnostics.HasError() { + return + } + in.Identities = expandIdentities(ctx, identities) + } + if !plan.PolicyARN.IsNull() { + in.PolicyArn = aws.String(plan.PolicyARN.ValueString()) + } + + out, err := conn.CreateIAMPolicyAssignmentWithContext(ctx, &in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.QuickSight, create.ErrActionCreating, ResNameIAMPolicyAssignment, plan.AssignmentName.String(), nil), + err.Error(), + ) + return + } + if out == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.QuickSight, create.ErrActionCreating, ResNameIAMPolicyAssignment, plan.AssignmentName.String(), nil), + errors.New("empty output").Error(), + ) + return + } + plan.AssignmentID = flex.StringToFramework(ctx, out.AssignmentId) + + // wait for IAM to propagate before returning + _, err = tfresource.RetryWhenNotFound(ctx, iamPropagationTimeout, func() (interface{}, error) { + return FindIAMPolicyAssignmentByID(ctx, conn, plan.ID.ValueString()) + }) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.QuickSight, create.ErrActionCreating, ResNameIAMPolicyAssignment, plan.AssignmentName.String(), nil), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceIAMPolicyAssignment) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().QuickSightConn() + + var state resourceIAMPolicyAssignmentData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := FindIAMPolicyAssignmentByID(ctx, conn, state.ID.ValueString()) + if tfresource.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.QuickSight, create.ErrActionSetting, ResNameIAMPolicyAssignment, state.ID.String(), nil), + err.Error(), + ) + return + } + + state.AssignmentID = flex.StringToFramework(ctx, out.AssignmentId) + state.AssignmentName = flex.StringToFramework(ctx, out.AssignmentName) + state.AssignmentStatus = flex.StringToFramework(ctx, out.AssignmentStatus) + state.AWSAccountID = flex.StringToFramework(ctx, out.AwsAccountId) + identities, d := flattenIdentities(ctx, out.Identities) + resp.Diagnostics.Append(d...) + state.Identities = identities + state.PolicyARN = flex.StringToFramework(ctx, out.PolicyArn) + + // To support import, parse the ID for the component keys and set + // individual values in state + _, namespace, _, err := ParseIAMPolicyAssignmentID(state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.QuickSight, create.ErrActionSetting, ResNameIAMPolicyAssignment, state.ID.String(), nil), + err.Error(), + ) + return + } + state.Namespace = flex.StringValueToFramework(ctx, namespace) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceIAMPolicyAssignment) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().QuickSightConn() + + var plan, state resourceIAMPolicyAssignmentData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + if !plan.AssignmentStatus.Equal(state.AssignmentStatus) || + !plan.Identities.Equal(state.Identities) || + !plan.PolicyARN.Equal(state.PolicyARN) { + in := quicksight.UpdateIAMPolicyAssignmentInput{ + AwsAccountId: aws.String(plan.AWSAccountID.ValueString()), + Namespace: aws.String(plan.Namespace.ValueString()), + AssignmentName: aws.String(plan.AssignmentName.ValueString()), + AssignmentStatus: aws.String(plan.AssignmentStatus.ValueString()), + } + + if !plan.Identities.IsNull() { + var identities []identitiesData + resp.Diagnostics.Append(plan.Identities.ElementsAs(ctx, &identities, false)...) + if resp.Diagnostics.HasError() { + return + } + in.Identities = expandIdentities(ctx, identities) + } + if !plan.PolicyARN.IsNull() { + in.PolicyArn = aws.String(plan.PolicyARN.ValueString()) + } + + out, err := conn.UpdateIAMPolicyAssignmentWithContext(ctx, &in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.QuickSight, create.ErrActionUpdating, ResNameIAMPolicyAssignment, plan.ID.String(), nil), + err.Error(), + ) + return + } + if out == nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.QuickSight, create.ErrActionUpdating, ResNameIAMPolicyAssignment, plan.ID.String(), nil), + errors.New("empty output").Error(), + ) + return + } + plan.AssignmentID = flex.StringToFramework(ctx, out.AssignmentId) + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + } +} + +func (r *resourceIAMPolicyAssignment) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().QuickSightConn() + + var state resourceIAMPolicyAssignmentData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + _, err := conn.DeleteIAMPolicyAssignmentWithContext(ctx, &quicksight.DeleteIAMPolicyAssignmentInput{ + AwsAccountId: aws.String(state.AWSAccountID.ValueString()), + Namespace: aws.String(state.Namespace.ValueString()), + AssignmentName: aws.String(state.AssignmentName.ValueString()), + }) + if err != nil { + if tfawserr.ErrCodeEquals(err, quicksight.ErrCodeResourceNotFoundException) { + return + } + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.QuickSight, create.ErrActionDeleting, ResNameIAMPolicyAssignment, state.ID.String(), nil), + err.Error(), + ) + } + + // wait for IAM to propagate before returning + _, err = tfresource.RetryUntilNotFound(ctx, iamPropagationTimeout, func() (interface{}, error) { + return FindIAMPolicyAssignmentByID(ctx, conn, state.ID.ValueString()) + }) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.QuickSight, create.ErrActionDeleting, ResNameIAMPolicyAssignment, state.ID.String(), nil), + err.Error(), + ) + return + } +} + +func (r *resourceIAMPolicyAssignment) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +func FindIAMPolicyAssignmentByID(ctx context.Context, conn *quicksight.QuickSight, id string) (*quicksight.IAMPolicyAssignment, error) { + awsAccountID, namespace, assignmentName, err := ParseIAMPolicyAssignmentID(id) + if err != nil { + return nil, err + } + + in := &quicksight.DescribeIAMPolicyAssignmentInput{ + AwsAccountId: aws.String(awsAccountID), + Namespace: aws.String(namespace), + AssignmentName: aws.String(assignmentName), + } + + out, err := conn.DescribeIAMPolicyAssignmentWithContext(ctx, in) + if err != nil { + if tfawserr.ErrCodeEquals(err, quicksight.ErrCodeResourceNotFoundException) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + return nil, err + } + + if out == nil || out.IAMPolicyAssignment == nil { + return nil, tfresource.NewEmptyResultError(in) + } + + return out.IAMPolicyAssignment, nil +} + +func ParseIAMPolicyAssignmentID(id string) (string, string, string, error) { + parts := strings.SplitN(id, ",", 3) + if len(parts) != 3 || parts[0] == "" || parts[1] == "" || parts[2] == "" { + return "", "", "", fmt.Errorf("unexpected format of ID (%s), expected AWS_ACCOUNT_ID,NAMESPACE,ASSIGNMENT_NAME", id) + } + return parts[0], parts[1], parts[2], nil +} + +func createIAMPolicyAssignmentID(awsAccountID, namespace, assignmentName string) string { + return fmt.Sprintf("%s,%s,%s", awsAccountID, namespace, assignmentName) +} + +var ( + identitiesAttrTypes = map[string]attr.Type{ + "user": types.SetType{ElemType: types.StringType}, + "group": types.SetType{ElemType: types.StringType}, + } +) + +type resourceIAMPolicyAssignmentData struct { + AssignmentID types.String `tfsdk:"assignment_id"` + AssignmentName types.String `tfsdk:"assignment_name"` + AssignmentStatus types.String `tfsdk:"assignment_status"` + AWSAccountID types.String `tfsdk:"aws_account_id"` + ID types.String `tfsdk:"id"` + Identities types.List `tfsdk:"identities"` + Namespace types.String `tfsdk:"namespace"` + PolicyARN types.String `tfsdk:"policy_arn"` +} + +type identitiesData struct { + User types.Set `tfsdk:"user"` + Group types.Set `tfsdk:"group"` +} + +func expandIdentities(ctx context.Context, tfList []identitiesData) map[string][]*string { + if len(tfList) == 0 { + return nil + } + tfObj := tfList[0] + + apiObject := map[string][]*string{} + if !tfObj.User.IsNull() { + apiObject[identitiesUserKey] = flex.ExpandFrameworkStringSet(ctx, tfObj.User) + } + if !tfObj.Group.IsNull() { + apiObject[identitiesGroupKey] = flex.ExpandFrameworkStringSet(ctx, tfObj.Group) + } + return apiObject +} + +func flattenIdentities(ctx context.Context, apiObject map[string][]*string) (types.List, diag.Diagnostics) { + var diags diag.Diagnostics + elemType := types.ObjectType{AttrTypes: identitiesAttrTypes} + + if len(apiObject) == 0 { + return types.ListNull(elemType), diags + } + + obj := map[string]attr.Value{ + "user": flex.FlattenFrameworkStringSet(ctx, apiObject[identitiesUserKey]), + "group": flex.FlattenFrameworkStringSet(ctx, apiObject[identitiesGroupKey]), + } + + objVal, d := types.ObjectValue(identitiesAttrTypes, obj) + diags.Append(d...) + listVal, d := types.ListValue(elemType, []attr.Value{objVal}) + diags.Append(d...) + + return listVal, diags +} diff --git a/internal/service/quicksight/iam_policy_assignment_test.go b/internal/service/quicksight/iam_policy_assignment_test.go new file mode 100644 index 000000000000..015c67bfffaf --- /dev/null +++ b/internal/service/quicksight/iam_policy_assignment_test.go @@ -0,0 +1,240 @@ +package quicksight_test + +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/quicksight" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/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" + tfquicksight "github.com/hashicorp/terraform-provider-aws/internal/service/quicksight" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccQuickSightIAMPolicyAssignment_basic(t *testing.T) { + ctx := acctest.Context(t) + var assignment quicksight.IAMPolicyAssignment + resourceName := "aws_quicksight_iam_policy_assignment.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, quicksight.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckIAMPolicyAssignmentDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccIAMPolicyAssignmentConfig_basic(rName, quicksight.AssignmentStatusEnabled), + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMPolicyAssignmentExists(ctx, resourceName, &assignment), + resource.TestCheckResourceAttr(resourceName, "assignment_name", rName), + resource.TestCheckResourceAttr(resourceName, "assignment_status", quicksight.AssignmentStatusEnabled), + resource.TestCheckResourceAttr(resourceName, "namespace", tfquicksight.DefaultIAMPolicyAssignmentNamespace), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccQuickSightIAMPolicyAssignment_disappears(t *testing.T) { + ctx := acctest.Context(t) + var assignment quicksight.IAMPolicyAssignment + resourceName := "aws_quicksight_iam_policy_assignment.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, quicksight.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckIAMPolicyAssignmentDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccIAMPolicyAssignmentConfig_basic(rName, quicksight.AssignmentStatusEnabled), + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMPolicyAssignmentExists(ctx, resourceName, &assignment), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfquicksight.ResourceIAMPolicyAssignment, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccQuickSightIAMPolicyAssignment_assignmentStatus(t *testing.T) { + ctx := acctest.Context(t) + var assignment quicksight.IAMPolicyAssignment + resourceName := "aws_quicksight_iam_policy_assignment.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, quicksight.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckIAMPolicyAssignmentDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccIAMPolicyAssignmentConfig_basic(rName, quicksight.AssignmentStatusDraft), + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMPolicyAssignmentExists(ctx, resourceName, &assignment), + resource.TestCheckResourceAttr(resourceName, "assignment_name", rName), + resource.TestCheckResourceAttr(resourceName, "assignment_status", quicksight.AssignmentStatusDraft), + resource.TestCheckResourceAttr(resourceName, "namespace", tfquicksight.DefaultIAMPolicyAssignmentNamespace), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccIAMPolicyAssignmentConfig_basic(rName, quicksight.AssignmentStatusEnabled), + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMPolicyAssignmentExists(ctx, resourceName, &assignment), + resource.TestCheckResourceAttr(resourceName, "assignment_name", rName), + resource.TestCheckResourceAttr(resourceName, "assignment_status", quicksight.AssignmentStatusEnabled), + resource.TestCheckResourceAttr(resourceName, "namespace", tfquicksight.DefaultIAMPolicyAssignmentNamespace), + ), + }, + { + Config: testAccIAMPolicyAssignmentConfig_basic(rName, quicksight.AssignmentStatusDisabled), + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMPolicyAssignmentExists(ctx, resourceName, &assignment), + resource.TestCheckResourceAttr(resourceName, "assignment_name", rName), + resource.TestCheckResourceAttr(resourceName, "assignment_status", quicksight.AssignmentStatusDisabled), + resource.TestCheckResourceAttr(resourceName, "namespace", tfquicksight.DefaultIAMPolicyAssignmentNamespace), + ), + }, + }, + }) +} + +func TestAccQuickSightIAMPolicyAssignment_identities(t *testing.T) { + ctx := acctest.Context(t) + var assignment quicksight.IAMPolicyAssignment + resourceName := "aws_quicksight_iam_policy_assignment.test" + policyResourceName := "aws_iam_policy.test" + userResourceName := "aws_quicksight_user.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, quicksight.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckIAMPolicyAssignmentDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccIAMPolicyAssignmentConfig_identities(rName, quicksight.AssignmentStatusEnabled), + Check: resource.ComposeTestCheckFunc( + testAccCheckIAMPolicyAssignmentExists(ctx, resourceName, &assignment), + resource.TestCheckResourceAttr(resourceName, "assignment_name", rName), + resource.TestCheckResourceAttr(resourceName, "assignment_status", quicksight.AssignmentStatusEnabled), + resource.TestCheckResourceAttr(resourceName, "namespace", tfquicksight.DefaultIAMPolicyAssignmentNamespace), + resource.TestCheckResourceAttr(resourceName, "identities.#", "1"), + resource.TestCheckResourceAttr(resourceName, "identities.0.user.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "identities.0.user.0", userResourceName, "user_name"), + resource.TestCheckResourceAttrPair(resourceName, "policy_arn", policyResourceName, "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckIAMPolicyAssignmentExists(ctx context.Context, resourceName string, assignment *quicksight.IAMPolicyAssignment) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).QuickSightConn() + output, err := tfquicksight.FindIAMPolicyAssignmentByID(ctx, conn, rs.Primary.ID) + if err != nil { + return create.Error(names.QuickSight, create.ErrActionCheckingExistence, tfquicksight.ResNameIAMPolicyAssignment, rs.Primary.ID, err) + } + + *assignment = *output + + return nil + } +} + +func testAccCheckIAMPolicyAssignmentDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).QuickSightConn() + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_quicksight_iam_policy_assignment" { + continue + } + + output, err := tfquicksight.FindIAMPolicyAssignmentByID(ctx, conn, rs.Primary.ID) + if err != nil { + if tfawserr.ErrCodeEquals(err, quicksight.ErrCodeResourceNotFoundException) { + return nil + } + return err + } + + if output != nil { + return create.Error(names.QuickSight, create.ErrActionCheckingDestroyed, tfquicksight.ResNameIAMPolicyAssignment, rs.Primary.ID, err) + } + } + + return nil + } +} + +func testAccIAMPolicyAssignmentConfig_basic(rName, assignmentStatus string) string { + return fmt.Sprintf(` +resource "aws_quicksight_iam_policy_assignment" "test" { + assignment_name = %[1]q + assignment_status = %[2]q +} +`, rName, assignmentStatus) +} + +func testAccIAMPolicyAssignmentConfig_identities(rName, assignmentStatus string) string { + return fmt.Sprintf(` +resource "aws_quicksight_user" "test" { + user_name = %[1]q + email = %[3]q + identity_type = "QUICKSIGHT" + user_role = "READER" +} + +data "aws_iam_policy_document" "test" { + statement { + actions = ["quicksight:ListUsers"] + resources = ["*"] + } +} + +resource "aws_iam_policy" "test" { + policy = data.aws_iam_policy_document.test.json +} + +resource "aws_quicksight_iam_policy_assignment" "test" { + assignment_name = %[1]q + assignment_status = %[2]q + policy_arn = aws_iam_policy.test.arn + identities { + user = [aws_quicksight_user.test.user_name] + } +} +`, rName, assignmentStatus, acctest.DefaultEmailAddress) +} diff --git a/internal/service/quicksight/service_package_gen.go b/internal/service/quicksight/service_package_gen.go index c18add397ffb..0a439bec9f13 100644 --- a/internal/service/quicksight/service_package_gen.go +++ b/internal/service/quicksight/service_package_gen.go @@ -17,6 +17,9 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { return []*types.ServicePackageFrameworkResource{ + { + Factory: newResourceIAMPolicyAssignment, + }, { Factory: newResourceIngestion, }, diff --git a/website/docs/r/quicksight_iam_policy_assignment.html.markdown b/website/docs/r/quicksight_iam_policy_assignment.html.markdown new file mode 100644 index 000000000000..3ff83af2432a --- /dev/null +++ b/website/docs/r/quicksight_iam_policy_assignment.html.markdown @@ -0,0 +1,60 @@ +--- +subcategory: "QuickSight" +layout: "aws" +page_title: "AWS: aws_quicksight_iam_policy_assignment" +description: |- + Terraform resource for managing an AWS QuickSight IAM Policy Assignment. +--- + +# Resource: aws_quicksight_iam_policy_assignment + +Terraform resource for managing an AWS QuickSight IAM Policy Assignment. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_quicksight_iam_policy_assignment" "example" { + assignment_name = "example" + assignment_status = "ENABLED" + policy_arn = aws_iam_policy.example.arn + identities { + user = [aws_quicksight_user.example.user_name] + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `assignment_name` - (Required) Name of the assignment. +* `assignment_status` - (Required) Status of the assignment. Valid values are `ENABLED`, `DISABLED`, and `DRAFT`. + +The following arguments are optional: + +* `aws_account_id` - (Optional) AWS account ID. +* `identities` - (Optional) Amazon QuickSight users, groups, or both to assign the policy to. See [`identities`](#identities). +* `namespace` - (Optional) Namespace that contains the assignment. Defaults to `default`. +* `policy_arn` - (Optional) ARN of the IAM policy to apply to the Amazon QuickSight users and groups specified in this assignment. + +### identities + +* `groups` - (Optional) Array of Quicksight group names to assign the policy to. +* `user` - (Optional) Array of Quicksight user names to assign the policy to. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `assignment_id` - Assignment ID. +* `id` - A comma-delimited string joining AWS account ID, namespace, and assignment name. + +## Import + +QuickSight IAM Policy Assignment can be imported using the AWS account ID, namespace, and assignment name separated by commas (`,`) e.g., + +``` +$ terraform import aws_quicksight_iam_policy_assignment.example 123456789012,default,example +```