Skip to content

Commit 4cbd8ed

Browse files
authored
Merge pull request #44256 from bonclay7/f-amp-resource-policy
r/amp_resource_policy: Add support for resource policy in AMP
2 parents 1c8a2d5 + ca20ade commit 4cbd8ed

File tree

6 files changed

+772
-0
lines changed

6 files changed

+772
-0
lines changed

.changelog/44256.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-resource
2+
aws_prometheus_resource_policy
3+
```

internal/service/amp/exports_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ var (
1010
ResourceRuleGroupNamespace = resourceRuleGroupNamespace
1111
ResourceScraper = newScraperResource
1212
ResourceWorkspace = resourceWorkspace
13+
ResourceResourcePolicy = newResourcePolicyResource
1314

1415
FindAlertManagerDefinitionByID = findAlertManagerDefinitionByID
1516
FindQueryLoggingConfigurationByID = findQueryLoggingConfigurationByID
17+
FindResourcePolicyByWorkspaceID = findResourcePolicyByWorkspaceID
1618
FindRuleGroupNamespaceByARN = findRuleGroupNamespaceByARN
1719
FindScraperByID = findScraperByID
1820
FindWorkspaceByID = findWorkspaceByID
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package amp
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"time"
10+
11+
"github.com/aws/aws-sdk-go-v2/aws"
12+
"github.com/aws/aws-sdk-go-v2/service/amp"
13+
awstypes "github.com/aws/aws-sdk-go-v2/service/amp/types"
14+
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
15+
"github.com/hashicorp/terraform-plugin-framework/path"
16+
"github.com/hashicorp/terraform-plugin-framework/resource"
17+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
18+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
19+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
20+
"github.com/hashicorp/terraform-plugin-framework/types"
21+
sdkid "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id"
22+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
23+
"github.com/hashicorp/terraform-provider-aws/internal/enum"
24+
"github.com/hashicorp/terraform-provider-aws/internal/errs"
25+
"github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag"
26+
"github.com/hashicorp/terraform-provider-aws/internal/framework"
27+
fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex"
28+
fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types"
29+
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
30+
"github.com/hashicorp/terraform-provider-aws/names"
31+
)
32+
33+
// @FrameworkResource("aws_prometheus_resource_policy", name="Resource Policy")
34+
// @Testing(existsType="github.com/aws/aws-sdk-go-v2/service/amp;amp.DescribeResourcePolicyOutput")
35+
func newResourcePolicyResource(_ context.Context) (resource.ResourceWithConfigure, error) {
36+
r := &resourcePolicyResource{}
37+
38+
r.SetDefaultCreateTimeout(5 * time.Minute)
39+
r.SetDefaultDeleteTimeout(5 * time.Minute)
40+
r.SetDefaultUpdateTimeout(5 * time.Minute)
41+
42+
return r, nil
43+
}
44+
45+
type resourcePolicyResource struct {
46+
framework.ResourceWithModel[resourcePolicyResourceModel]
47+
framework.WithTimeouts
48+
}
49+
50+
func (r *resourcePolicyResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) {
51+
response.Schema = schema.Schema{
52+
Attributes: map[string]schema.Attribute{
53+
"policy_document": schema.StringAttribute{
54+
CustomType: fwtypes.IAMPolicyType,
55+
Required: true,
56+
},
57+
"revision_id": schema.StringAttribute{
58+
Computed: true,
59+
Optional: true,
60+
},
61+
"workspace_id": schema.StringAttribute{
62+
Required: true,
63+
PlanModifiers: []planmodifier.String{
64+
stringplanmodifier.RequiresReplace(),
65+
},
66+
},
67+
},
68+
Blocks: map[string]schema.Block{
69+
names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{
70+
Create: true,
71+
Delete: true,
72+
Update: true,
73+
}),
74+
},
75+
}
76+
}
77+
78+
func (r *resourcePolicyResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
79+
var data resourcePolicyResourceModel
80+
response.Diagnostics.Append(request.Plan.Get(ctx, &data)...)
81+
if response.Diagnostics.HasError() {
82+
return
83+
}
84+
85+
conn := r.Meta().AMPClient(ctx)
86+
87+
workspaceID := fwflex.StringValueFromFramework(ctx, data.WorkspaceID)
88+
input := amp.PutResourcePolicyInput{
89+
ClientToken: aws.String(sdkid.UniqueId()),
90+
PolicyDocument: fwflex.StringFromFramework(ctx, data.PolicyDocument),
91+
WorkspaceId: aws.String(workspaceID),
92+
}
93+
94+
if !data.RevisionID.IsNull() {
95+
input.RevisionId = fwflex.StringFromFramework(ctx, data.RevisionID)
96+
}
97+
98+
output, err := conn.PutResourcePolicy(ctx, &input)
99+
100+
if err != nil {
101+
response.Diagnostics.AddError(fmt.Sprintf("creating Prometheus Workspace (%s) Resource Policy", workspaceID), err.Error())
102+
return
103+
}
104+
105+
// Set values for unknowns.
106+
data.RevisionID = fwflex.StringToFramework(ctx, output.RevisionId)
107+
108+
if _, err := waitResourcePolicyCreated(ctx, conn, workspaceID, r.CreateTimeout(ctx, data.Timeouts)); err != nil {
109+
response.Diagnostics.AddError(fmt.Sprintf("waiting for Prometheus Workspace (%s) Resource Policy create", workspaceID), err.Error())
110+
return
111+
}
112+
113+
response.Diagnostics.Append(response.State.Set(ctx, &data)...)
114+
}
115+
116+
func (r *resourcePolicyResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
117+
var data resourcePolicyResourceModel
118+
response.Diagnostics.Append(request.State.Get(ctx, &data)...)
119+
if response.Diagnostics.HasError() {
120+
return
121+
}
122+
123+
conn := r.Meta().AMPClient(ctx)
124+
125+
workspaceID := fwflex.StringValueFromFramework(ctx, data.WorkspaceID)
126+
output, err := findResourcePolicyByWorkspaceID(ctx, conn, workspaceID)
127+
128+
if tfresource.NotFound(err) {
129+
response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err))
130+
response.State.RemoveResource(ctx)
131+
return
132+
}
133+
134+
if err != nil {
135+
response.Diagnostics.AddError(fmt.Sprintf("reading Prometheus Workspace (%s) Resource Policy", workspaceID), err.Error())
136+
return
137+
}
138+
139+
// Set attributes for import.
140+
response.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...)
141+
if response.Diagnostics.HasError() {
142+
return
143+
}
144+
145+
response.Diagnostics.Append(response.State.Set(ctx, &data)...)
146+
}
147+
148+
func (r *resourcePolicyResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
149+
var new, old resourcePolicyResourceModel
150+
response.Diagnostics.Append(request.Plan.Get(ctx, &new)...)
151+
if response.Diagnostics.HasError() {
152+
return
153+
}
154+
response.Diagnostics.Append(request.State.Get(ctx, &old)...)
155+
if response.Diagnostics.HasError() {
156+
return
157+
}
158+
159+
conn := r.Meta().AMPClient(ctx)
160+
161+
if !new.PolicyDocument.Equal(old.PolicyDocument) || !new.RevisionID.Equal(old.RevisionID) {
162+
workspaceID := fwflex.StringValueFromFramework(ctx, new.WorkspaceID)
163+
input := amp.PutResourcePolicyInput{
164+
ClientToken: aws.String(sdkid.UniqueId()),
165+
PolicyDocument: fwflex.StringFromFramework(ctx, new.PolicyDocument),
166+
WorkspaceId: aws.String(workspaceID),
167+
}
168+
169+
if !new.RevisionID.IsNull() {
170+
input.RevisionId = fwflex.StringFromFramework(ctx, new.RevisionID)
171+
}
172+
173+
output, err := conn.PutResourcePolicy(ctx, &input)
174+
175+
if err != nil {
176+
response.Diagnostics.AddError(fmt.Sprintf("updating Prometheus Workspace (%s) Resource Policy", workspaceID), err.Error())
177+
return
178+
}
179+
180+
new.RevisionID = fwflex.StringToFramework(ctx, output.RevisionId)
181+
182+
if _, err := waitResourcePolicyUpdated(ctx, conn, workspaceID, r.UpdateTimeout(ctx, new.Timeouts)); err != nil {
183+
response.Diagnostics.AddError(fmt.Sprintf("waiting for Prometheus Workspace (%s) Resource Policy update", workspaceID), err.Error())
184+
return
185+
}
186+
}
187+
188+
response.Diagnostics.Append(response.State.Set(ctx, &new)...)
189+
}
190+
191+
func (r *resourcePolicyResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
192+
var data resourcePolicyResourceModel
193+
response.Diagnostics.Append(request.State.Get(ctx, &data)...)
194+
if response.Diagnostics.HasError() {
195+
return
196+
}
197+
198+
conn := r.Meta().AMPClient(ctx)
199+
200+
workspaceID := fwflex.StringValueFromFramework(ctx, data.WorkspaceID)
201+
input := amp.DeleteResourcePolicyInput{
202+
ClientToken: aws.String(sdkid.UniqueId()),
203+
WorkspaceId: aws.String(workspaceID),
204+
}
205+
206+
if !data.RevisionID.IsNull() {
207+
input.RevisionId = fwflex.StringFromFramework(ctx, data.RevisionID)
208+
}
209+
210+
_, err := conn.DeleteResourcePolicy(ctx, &input)
211+
212+
if errs.IsA[*awstypes.ResourceNotFoundException](err) {
213+
return
214+
}
215+
216+
if err != nil {
217+
response.Diagnostics.AddError(fmt.Sprintf("deleting Prometheus Workspace (%s) Resource Policy", workspaceID), err.Error())
218+
return
219+
}
220+
221+
if _, err := waitResourcePolicyDeleted(ctx, conn, workspaceID, r.DeleteTimeout(ctx, data.Timeouts)); err != nil {
222+
response.Diagnostics.AddError(fmt.Sprintf("waiting for Prometheus Workspace (%s) Resource Policy delete", workspaceID), err.Error())
223+
return
224+
}
225+
}
226+
227+
func (r *resourcePolicyResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
228+
resource.ImportStatePassthroughID(ctx, path.Root("workspace_id"), request, response)
229+
}
230+
231+
type resourcePolicyResourceModel struct {
232+
framework.WithRegionModel
233+
PolicyDocument fwtypes.IAMPolicy `tfsdk:"policy_document"`
234+
RevisionID types.String `tfsdk:"revision_id"`
235+
Timeouts timeouts.Value `tfsdk:"timeouts"`
236+
WorkspaceID types.String `tfsdk:"workspace_id"`
237+
}
238+
239+
func findResourcePolicyByWorkspaceID(ctx context.Context, conn *amp.Client, workspaceID string) (*amp.DescribeResourcePolicyOutput, error) {
240+
input := amp.DescribeResourcePolicyInput{
241+
WorkspaceId: aws.String(workspaceID),
242+
}
243+
244+
return findResourcePolicy(ctx, conn, &input)
245+
}
246+
247+
func findResourcePolicy(ctx context.Context, conn *amp.Client, input *amp.DescribeResourcePolicyInput) (*amp.DescribeResourcePolicyOutput, error) {
248+
output, err := conn.DescribeResourcePolicy(ctx, input)
249+
250+
if errs.IsA[*awstypes.ResourceNotFoundException](err) {
251+
return nil, &retry.NotFoundError{
252+
LastError: err,
253+
LastRequest: input,
254+
}
255+
}
256+
257+
if err != nil {
258+
return nil, err
259+
}
260+
261+
if output == nil || output.PolicyDocument == nil {
262+
return nil, tfresource.NewEmptyResultError(input)
263+
}
264+
265+
return output, nil
266+
}
267+
268+
func statusResourcePolicy(ctx context.Context, conn *amp.Client, workspaceID string) retry.StateRefreshFunc {
269+
return func() (any, string, error) {
270+
output, err := findResourcePolicyByWorkspaceID(ctx, conn, workspaceID)
271+
272+
if tfresource.NotFound(err) {
273+
return nil, "", nil
274+
}
275+
276+
if err != nil {
277+
return nil, "", err
278+
}
279+
280+
return output, string(output.PolicyStatus), nil
281+
}
282+
}
283+
284+
func waitResourcePolicyCreated(ctx context.Context, conn *amp.Client, workspaceID string, timeout time.Duration) (*amp.DescribeResourcePolicyOutput, error) {
285+
stateConf := &retry.StateChangeConf{
286+
Pending: enum.Slice(awstypes.WorkspacePolicyStatusCodeCreating),
287+
Target: enum.Slice(awstypes.WorkspacePolicyStatusCodeActive),
288+
Refresh: statusResourcePolicy(ctx, conn, workspaceID),
289+
Timeout: timeout,
290+
}
291+
292+
outputRaw, err := stateConf.WaitForStateContext(ctx)
293+
294+
if output, ok := outputRaw.(*amp.DescribeResourcePolicyOutput); ok {
295+
return output, err
296+
}
297+
298+
return nil, err
299+
}
300+
301+
func waitResourcePolicyUpdated(ctx context.Context, conn *amp.Client, workspaceID string, timeout time.Duration) (*amp.DescribeResourcePolicyOutput, error) {
302+
stateConf := &retry.StateChangeConf{
303+
Pending: enum.Slice(awstypes.WorkspacePolicyStatusCodeUpdating),
304+
Target: enum.Slice(awstypes.WorkspacePolicyStatusCodeActive),
305+
Refresh: statusResourcePolicy(ctx, conn, workspaceID),
306+
Timeout: timeout,
307+
}
308+
309+
outputRaw, err := stateConf.WaitForStateContext(ctx)
310+
311+
if output, ok := outputRaw.(*amp.DescribeResourcePolicyOutput); ok {
312+
return output, err
313+
}
314+
315+
return nil, err
316+
}
317+
318+
func waitResourcePolicyDeleted(ctx context.Context, conn *amp.Client, workspaceID string, timeout time.Duration) (*amp.DescribeResourcePolicyOutput, error) {
319+
stateConf := &retry.StateChangeConf{
320+
Pending: enum.Slice(awstypes.WorkspacePolicyStatusCodeDeleting, awstypes.WorkspacePolicyStatusCodeActive),
321+
Target: []string{},
322+
Refresh: statusResourcePolicy(ctx, conn, workspaceID),
323+
Timeout: timeout,
324+
}
325+
326+
outputRaw, err := stateConf.WaitForStateContext(ctx)
327+
328+
if output, ok := outputRaw.(*amp.DescribeResourcePolicyOutput); ok {
329+
return output, err
330+
}
331+
332+
return nil, err
333+
}

0 commit comments

Comments
 (0)