Skip to content

Commit b213cb8

Browse files
committed
r/aws_quicksight_folder_membership: initial resource implementation
1 parent 746e737 commit b213cb8

File tree

6 files changed

+453
-0
lines changed

6 files changed

+453
-0
lines changed

.changelog/30871.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-resource
2+
aws_quicksight_folder_membership
3+
```

internal/service/quicksight/exports_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package quicksight
22

33
// Exports for use in tests only.
44
var (
5+
ResourceFolderMembership = newResourceFolderMembership
56
ResourceIAMPolicyAssignment = newResourceIAMPolicyAssignment
67
ResourceIngestion = newResourceIngestion
78
ResourceNamespace = newResourceNamespace
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
package quicksight
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"strings"
8+
9+
"github.com/aws/aws-sdk-go/aws"
10+
"github.com/aws/aws-sdk-go/service/quicksight"
11+
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
12+
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
13+
"github.com/hashicorp/terraform-plugin-framework/path"
14+
"github.com/hashicorp/terraform-plugin-framework/resource"
15+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
16+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
17+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
18+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
19+
"github.com/hashicorp/terraform-plugin-framework/types"
20+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
21+
"github.com/hashicorp/terraform-provider-aws/internal/create"
22+
"github.com/hashicorp/terraform-provider-aws/internal/flex"
23+
"github.com/hashicorp/terraform-provider-aws/internal/framework"
24+
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
25+
"github.com/hashicorp/terraform-provider-aws/names"
26+
)
27+
28+
// Function annotations are used for resource registration to the Provider. DO NOT EDIT.
29+
// @FrameworkResource(name="Folder Membership")
30+
func newResourceFolderMembership(_ context.Context) (resource.ResourceWithConfigure, error) {
31+
return &resourceFolderMembership{}, nil
32+
}
33+
34+
const (
35+
ResNameFolderMembership = "Folder Membership"
36+
)
37+
38+
type resourceFolderMembership struct {
39+
framework.ResourceWithConfigure
40+
}
41+
42+
func (r *resourceFolderMembership) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
43+
resp.TypeName = "aws_quicksight_folder_membership"
44+
}
45+
46+
func (r *resourceFolderMembership) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
47+
resp.Schema = schema.Schema{
48+
Attributes: map[string]schema.Attribute{
49+
"aws_account_id": schema.StringAttribute{
50+
Optional: true,
51+
Computed: true,
52+
PlanModifiers: []planmodifier.String{
53+
stringplanmodifier.UseStateForUnknown(),
54+
stringplanmodifier.RequiresReplace(),
55+
},
56+
},
57+
"id": framework.IDAttribute(),
58+
"folder_id": schema.StringAttribute{
59+
Required: true,
60+
PlanModifiers: []planmodifier.String{
61+
stringplanmodifier.RequiresReplace(),
62+
},
63+
},
64+
"member_id": schema.StringAttribute{
65+
Required: true,
66+
PlanModifiers: []planmodifier.String{
67+
stringplanmodifier.RequiresReplace(),
68+
},
69+
},
70+
"member_type": schema.StringAttribute{
71+
Required: true,
72+
PlanModifiers: []planmodifier.String{
73+
stringplanmodifier.RequiresReplace(),
74+
},
75+
Validators: []validator.String{
76+
stringvalidator.OneOf(quicksight.MemberType_Values()...),
77+
},
78+
},
79+
},
80+
}
81+
}
82+
83+
func (r *resourceFolderMembership) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
84+
conn := r.Meta().QuickSightConn()
85+
86+
var plan resourceFolderMembershipData
87+
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
88+
if resp.Diagnostics.HasError() {
89+
return
90+
}
91+
92+
if plan.AWSAccountID.IsUnknown() || plan.AWSAccountID.IsNull() {
93+
plan.AWSAccountID = types.StringValue(r.Meta().AccountID)
94+
}
95+
plan.ID = types.StringValue(createFolderMembershipID(
96+
plan.AWSAccountID.ValueString(), plan.FolderID.ValueString(),
97+
plan.MemberType.ValueString(), plan.MemberID.ValueString(),
98+
))
99+
100+
in := &quicksight.CreateFolderMembershipInput{
101+
AwsAccountId: aws.String(plan.AWSAccountID.ValueString()),
102+
FolderId: aws.String(plan.FolderID.ValueString()),
103+
MemberId: aws.String(plan.MemberID.ValueString()),
104+
MemberType: aws.String(plan.MemberType.ValueString()),
105+
}
106+
107+
out, err := conn.CreateFolderMembershipWithContext(ctx, in)
108+
if err != nil {
109+
resp.Diagnostics.AddError(
110+
create.ProblemStandardMessage(names.QuickSight, create.ErrActionCreating, ResNameFolderMembership, plan.MemberID.String(), err),
111+
err.Error(),
112+
)
113+
return
114+
}
115+
if out == nil || out.FolderMember == nil {
116+
resp.Diagnostics.AddError(
117+
create.ProblemStandardMessage(names.QuickSight, create.ErrActionCreating, ResNameFolderMembership, plan.MemberID.String(), nil),
118+
errors.New("empty output").Error(),
119+
)
120+
return
121+
}
122+
123+
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
124+
}
125+
126+
func (r *resourceFolderMembership) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
127+
conn := r.Meta().QuickSightConn()
128+
129+
var state resourceFolderMembershipData
130+
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
131+
if resp.Diagnostics.HasError() {
132+
return
133+
}
134+
135+
out, err := FindFolderMembershipByID(ctx, conn, state.ID.ValueString())
136+
if tfresource.NotFound(err) {
137+
resp.State.RemoveResource(ctx)
138+
return
139+
}
140+
if err != nil {
141+
resp.Diagnostics.AddError(
142+
create.ProblemStandardMessage(names.QuickSight, create.ErrActionSetting, ResNameFolderMembership, state.ID.String(), err),
143+
err.Error(),
144+
)
145+
return
146+
}
147+
148+
state.MemberID = flex.StringToFramework(ctx, out.MemberId)
149+
150+
// To support import, parse the ID for the component keys and set
151+
// individual values in state
152+
awsAccountID, folderID, memberType, _, err := ParseFolderMembershipID(state.ID.ValueString())
153+
if err != nil {
154+
resp.Diagnostics.AddError(
155+
create.ProblemStandardMessage(names.QuickSight, create.ErrActionSetting, ResNameIngestion, state.ID.String(), nil),
156+
err.Error(),
157+
)
158+
return
159+
}
160+
state.AWSAccountID = flex.StringValueToFramework(ctx, awsAccountID)
161+
state.FolderID = flex.StringValueToFramework(ctx, folderID)
162+
state.MemberType = flex.StringValueToFramework(ctx, memberType)
163+
164+
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
165+
}
166+
167+
// There is no update API, so this method is a no-op
168+
func (r *resourceFolderMembership) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
169+
}
170+
171+
func (r *resourceFolderMembership) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
172+
conn := r.Meta().QuickSightConn()
173+
174+
var state resourceFolderMembershipData
175+
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
176+
if resp.Diagnostics.HasError() {
177+
return
178+
}
179+
180+
in := &quicksight.DeleteFolderMembershipInput{
181+
AwsAccountId: aws.String(state.AWSAccountID.ValueString()),
182+
FolderId: aws.String(state.FolderID.ValueString()),
183+
MemberId: aws.String(state.MemberID.ValueString()),
184+
MemberType: aws.String(state.MemberType.ValueString()),
185+
}
186+
187+
_, err := conn.DeleteFolderMembershipWithContext(ctx, in)
188+
if err != nil {
189+
if tfawserr.ErrCodeEquals(err, quicksight.ErrCodeResourceNotFoundException) {
190+
return
191+
}
192+
resp.Diagnostics.AddError(
193+
create.ProblemStandardMessage(names.QuickSight, create.ErrActionDeleting, ResNameFolderMembership, state.ID.String(), err),
194+
err.Error(),
195+
)
196+
return
197+
}
198+
}
199+
200+
func (r *resourceFolderMembership) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
201+
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
202+
}
203+
204+
func FindFolderMembershipByID(ctx context.Context, conn *quicksight.QuickSight, id string) (*quicksight.MemberIdArnPair, error) {
205+
awsAccountID, folderID, _, memberID, err := ParseFolderMembershipID(id)
206+
if err != nil {
207+
return nil, err
208+
}
209+
210+
in := &quicksight.ListFolderMembersInput{
211+
AwsAccountId: aws.String(awsAccountID),
212+
FolderId: aws.String(folderID),
213+
}
214+
215+
// There is no Get/Describe API for a single folder member, so utilize the
216+
// ListFolderMembers API to get all members and check for the presence of the
217+
// configured member ID
218+
out, err := conn.ListFolderMembersWithContext(ctx, in)
219+
if tfawserr.ErrCodeEquals(err, quicksight.ErrCodeResourceNotFoundException) {
220+
return nil, &retry.NotFoundError{
221+
LastError: err,
222+
LastRequest: in,
223+
}
224+
}
225+
226+
if err != nil {
227+
return nil, err
228+
}
229+
if out == nil {
230+
return nil, tfresource.NewEmptyResultError(in)
231+
}
232+
233+
for _, member := range out.FolderMemberList {
234+
if aws.StringValue(member.MemberId) == memberID {
235+
return member, nil
236+
}
237+
}
238+
239+
return nil, &retry.NotFoundError{
240+
LastError: errors.New("member ID not found in folder"),
241+
LastRequest: in,
242+
}
243+
}
244+
245+
func ParseFolderMembershipID(id string) (string, string, string, string, error) {
246+
parts := strings.SplitN(id, ",", 4)
247+
if len(parts) != 4 || parts[0] == "" || parts[1] == "" || parts[2] == "" || parts[3] == "" {
248+
return "", "", "", "", fmt.Errorf("unexpected format of ID (%s), expected AWS_ACCOUNT_ID,FOLDER_ID,MEMBER_TYPE,MEMBER_ID", id)
249+
}
250+
return parts[0], parts[1], parts[2], parts[3], nil
251+
}
252+
253+
func createFolderMembershipID(awsAccountID, folderID, memberType, memberID string) string {
254+
return strings.Join([]string{awsAccountID, folderID, memberType, memberID}, ",")
255+
}
256+
257+
type resourceFolderMembershipData struct {
258+
AWSAccountID types.String `tfsdk:"aws_account_id"`
259+
FolderID types.String `tfsdk:"folder_id"`
260+
ID types.String `tfsdk:"id"`
261+
MemberID types.String `tfsdk:"member_id"`
262+
MemberType types.String `tfsdk:"member_type"`
263+
}

0 commit comments

Comments
 (0)