Skip to content

Commit 9c4f788

Browse files
author
Marcin Belczewski
committed
feat: implement notification configuration
1 parent bf29406 commit 9c4f788

14 files changed

+1081
-1
lines changed

.changelog/42575.txt

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
```release-note:new-resource
2+
aws_notifications_notification_configuration
3+
```
4+
5+
```release-note:new-resource
6+
aws_notifications_event_rule
7+
```
8+
9+
```release-note:new-resource
10+
aws_notifications_contacts_email
11+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package notifications
5+
6+
// Exports for use in tests only.
7+
var (
8+
ResourceNotificationConfiguration = newResourceNotificationConfiguration
9+
10+
FindNotificationConfigurationByARN = findNotificationConfigurationByARN
11+
)

internal/service/notifications/generate.go

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) HashiCorp, Inc.
22
// SPDX-License-Identifier: MPL-2.0
33

4+
//go:generate go run ../../generate/tags/main.go -ServiceTagsMap -KVTValues -UpdateTags -ListTags -ListTagsInIDElem=Arn -TagInIDElem=Arn
45
//go:generate go run ../../generate/servicepackage/main.go
56
// ONLY generate directives and package declaration! Do not add anything else to this file.
67

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package notifications
5+
6+
import (
7+
"context"
8+
"errors"
9+
"time"
10+
11+
"github.com/YakDriver/regexache"
12+
"github.com/aws/aws-sdk-go-v2/aws"
13+
"github.com/aws/aws-sdk-go-v2/service/notifications"
14+
awstypes "github.com/aws/aws-sdk-go-v2/service/notifications/types"
15+
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
16+
"github.com/hashicorp/terraform-plugin-framework/path"
17+
"github.com/hashicorp/terraform-plugin-framework/resource"
18+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
19+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
20+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
21+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
22+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
23+
"github.com/hashicorp/terraform-plugin-framework/types"
24+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
25+
"github.com/hashicorp/terraform-provider-aws/internal/conns"
26+
"github.com/hashicorp/terraform-provider-aws/internal/create"
27+
"github.com/hashicorp/terraform-provider-aws/internal/enum"
28+
"github.com/hashicorp/terraform-provider-aws/internal/errs"
29+
"github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag"
30+
"github.com/hashicorp/terraform-provider-aws/internal/framework"
31+
"github.com/hashicorp/terraform-provider-aws/internal/framework/flex"
32+
fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types"
33+
"github.com/hashicorp/terraform-provider-aws/internal/sweep"
34+
sweepfw "github.com/hashicorp/terraform-provider-aws/internal/sweep/framework"
35+
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
36+
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
37+
"github.com/hashicorp/terraform-provider-aws/names"
38+
)
39+
40+
// Function annotations are used for resource registration to the Provider. DO NOT EDIT.
41+
// @FrameworkResource("aws_notifications_notification_configuration", name="Notification Configuration")
42+
// @Tags(identifierAttribute="arn")
43+
func newResourceNotificationConfiguration(_ context.Context) (resource.ResourceWithConfigure, error) {
44+
r := &resourceNotificationConfiguration{}
45+
46+
return r, nil
47+
}
48+
49+
const (
50+
ResNameNotificationConfiguration = "Notification Configuration"
51+
)
52+
53+
type resourceNotificationConfiguration struct {
54+
framework.ResourceWithConfigure
55+
}
56+
57+
func (r *resourceNotificationConfiguration) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
58+
resp.Schema = schema.Schema{
59+
Attributes: map[string]schema.Attribute{
60+
names.AttrARN: framework.ARNAttributeComputedOnly(),
61+
"aggregation_duration": schema.StringAttribute{
62+
Optional: true,
63+
Computed: true,
64+
CustomType: fwtypes.StringEnumType[awstypes.AggregationDuration](),
65+
Default: stringdefault.StaticString(string(awstypes.AggregationDurationNone)),
66+
},
67+
names.AttrDescription: schema.StringAttribute{
68+
Required: true,
69+
Validators: []validator.String{
70+
stringvalidator.LengthBetween(0, 256),
71+
stringvalidator.RegexMatches(regexache.MustCompile(`[^\x01-\x1F\x7F-\x9F]*`), ""),
72+
},
73+
},
74+
names.AttrName: schema.StringAttribute{
75+
Required: true,
76+
Validators: []validator.String{
77+
stringvalidator.LengthBetween(1, 64),
78+
stringvalidator.RegexMatches(regexache.MustCompile(`[A-Za-z0-9_\-]+`), ""),
79+
},
80+
},
81+
names.AttrStatus: schema.StringAttribute{
82+
CustomType: fwtypes.StringEnumType[awstypes.NotificationConfigurationStatus](),
83+
Computed: true,
84+
PlanModifiers: []planmodifier.String{
85+
stringplanmodifier.UseStateForUnknown(),
86+
},
87+
}, names.AttrTags: tftags.TagsAttribute(),
88+
names.AttrTagsAll: tftags.TagsAttributeComputedOnly(),
89+
},
90+
}
91+
}
92+
93+
func (r *resourceNotificationConfiguration) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
94+
conn := r.Meta().NotificationsClient(ctx)
95+
96+
var plan resourceNotificationConfigurationModel
97+
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
98+
if resp.Diagnostics.HasError() {
99+
return
100+
}
101+
102+
var input notifications.CreateNotificationConfigurationInput
103+
resp.Diagnostics.Append(flex.Expand(ctx, plan, &input)...)
104+
if resp.Diagnostics.HasError() {
105+
return
106+
}
107+
input.Tags = getTagsIn(ctx)
108+
109+
out, err := conn.CreateNotificationConfiguration(ctx, &input)
110+
if err != nil {
111+
resp.Diagnostics.AddError(
112+
create.ProblemStandardMessage(names.Notifications, create.ErrActionCreating, ResNameNotificationConfiguration, plan.Name.String(), err),
113+
err.Error(),
114+
)
115+
return
116+
}
117+
if out == nil || out.Arn == nil {
118+
resp.Diagnostics.AddError(
119+
create.ProblemStandardMessage(names.Notifications, create.ErrActionCreating, ResNameNotificationConfiguration, plan.Name.String(), nil),
120+
errors.New("empty output").Error(),
121+
)
122+
return
123+
}
124+
125+
resp.Diagnostics.Append(flex.Flatten(ctx, out, &plan)...)
126+
if resp.Diagnostics.HasError() {
127+
return
128+
}
129+
130+
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
131+
}
132+
133+
func (r *resourceNotificationConfiguration) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
134+
conn := r.Meta().NotificationsClient(ctx)
135+
136+
var state resourceNotificationConfigurationModel
137+
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
138+
if resp.Diagnostics.HasError() {
139+
return
140+
}
141+
142+
out, err := findNotificationConfigurationByARN(ctx, conn, state.ARN.ValueString())
143+
if tfresource.NotFound(err) {
144+
resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err))
145+
resp.State.RemoveResource(ctx)
146+
return
147+
}
148+
if err != nil {
149+
resp.Diagnostics.AddError(
150+
create.ProblemStandardMessage(names.Notifications, create.ErrActionReading, ResNameNotificationConfiguration, state.ARN.String(), err),
151+
err.Error(),
152+
)
153+
return
154+
}
155+
156+
resp.Diagnostics.Append(flex.Flatten(ctx, out, &state)...)
157+
if resp.Diagnostics.HasError() {
158+
return
159+
}
160+
161+
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
162+
}
163+
164+
func (r *resourceNotificationConfiguration) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
165+
conn := r.Meta().NotificationsClient(ctx)
166+
167+
var plan, state resourceNotificationConfigurationModel
168+
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
169+
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
170+
if resp.Diagnostics.HasError() {
171+
return
172+
}
173+
174+
diff, d := flex.Diff(ctx, plan, state)
175+
resp.Diagnostics.Append(d...)
176+
if resp.Diagnostics.HasError() {
177+
return
178+
}
179+
180+
if diff.HasChanges() {
181+
var input notifications.UpdateNotificationConfigurationInput
182+
resp.Diagnostics.Append(flex.Expand(ctx, plan, &input)...)
183+
if resp.Diagnostics.HasError() {
184+
return
185+
}
186+
187+
out, err := conn.UpdateNotificationConfiguration(ctx, &input)
188+
if err != nil {
189+
resp.Diagnostics.AddError(
190+
create.ProblemStandardMessage(names.Notifications, create.ErrActionUpdating, ResNameNotificationConfiguration, plan.ARN.String(), err),
191+
err.Error(),
192+
)
193+
return
194+
}
195+
if out == nil {
196+
resp.Diagnostics.AddError(
197+
create.ProblemStandardMessage(names.Notifications, create.ErrActionUpdating, ResNameNotificationConfiguration, plan.ARN.String(), nil),
198+
errors.New("empty output").Error(),
199+
)
200+
return
201+
}
202+
203+
resp.Diagnostics.Append(flex.Flatten(ctx, out, &plan)...)
204+
if resp.Diagnostics.HasError() {
205+
return
206+
}
207+
}
208+
209+
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
210+
}
211+
212+
func (r *resourceNotificationConfiguration) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
213+
conn := r.Meta().NotificationsClient(ctx)
214+
215+
var state resourceNotificationConfigurationModel
216+
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
217+
if resp.Diagnostics.HasError() {
218+
return
219+
}
220+
221+
input := notifications.DeleteNotificationConfigurationInput{
222+
Arn: state.ARN.ValueStringPointer(),
223+
}
224+
225+
_, err := conn.DeleteNotificationConfiguration(ctx, &input)
226+
if err != nil {
227+
if errs.IsA[*awstypes.ResourceNotFoundException](err) {
228+
return
229+
}
230+
231+
resp.Diagnostics.AddError(
232+
create.ProblemStandardMessage(names.Notifications, create.ErrActionDeleting, ResNameNotificationConfiguration, state.ARN.String(), err),
233+
err.Error(),
234+
)
235+
return
236+
}
237+
238+
_, err = waitNotificationConfigurationDeleted(ctx, conn, state.ARN.ValueString())
239+
if err != nil {
240+
resp.Diagnostics.AddError(
241+
create.ProblemStandardMessage(names.Notifications, create.ErrActionWaitingForDeletion, ResNameNotificationConfiguration, state.ARN.String(), err),
242+
err.Error(),
243+
)
244+
return
245+
}
246+
}
247+
248+
func (r *resourceNotificationConfiguration) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
249+
resource.ImportStatePassthroughID(ctx, path.Root(names.AttrARN), req, resp)
250+
}
251+
252+
func waitNotificationConfigurationDeleted(ctx context.Context, conn *notifications.Client, id string) (*awstypes.NotificationConfigurationStructure, error) {
253+
stateConf := &retry.StateChangeConf{
254+
Pending: enum.Slice(awstypes.NotificationConfigurationStatusDeleting),
255+
Target: []string{},
256+
Refresh: statusNotificationConfiguration(ctx, conn, id),
257+
Timeout: 10 * time.Minute,
258+
}
259+
260+
outputRaw, err := stateConf.WaitForStateContext(ctx)
261+
if out, ok := outputRaw.(*awstypes.NotificationConfigurationStructure); ok {
262+
return out, err
263+
}
264+
265+
return nil, err
266+
}
267+
268+
func statusNotificationConfiguration(ctx context.Context, conn *notifications.Client, arn string) retry.StateRefreshFunc {
269+
return func() (any, string, error) {
270+
out, err := findNotificationConfigurationByARN(ctx, conn, arn)
271+
if tfresource.NotFound(err) {
272+
return nil, "", nil
273+
}
274+
275+
if err != nil {
276+
return nil, "", err
277+
}
278+
279+
return out, string(out.Status), nil
280+
}
281+
}
282+
283+
func findNotificationConfigurationByARN(ctx context.Context, conn *notifications.Client, id string) (*notifications.GetNotificationConfigurationOutput, error) {
284+
input := notifications.GetNotificationConfigurationInput{
285+
Arn: aws.String(id),
286+
}
287+
288+
out, err := conn.GetNotificationConfiguration(ctx, &input)
289+
if err != nil {
290+
if errs.IsA[*awstypes.ResourceNotFoundException](err) {
291+
return nil, &retry.NotFoundError{
292+
LastError: err,
293+
LastRequest: &input,
294+
}
295+
}
296+
297+
return nil, err
298+
}
299+
300+
if out == nil {
301+
return nil, tfresource.NewEmptyResultError(&input)
302+
}
303+
304+
return out, nil
305+
}
306+
307+
type resourceNotificationConfigurationModel struct {
308+
ARN types.String `tfsdk:"arn"`
309+
AggregationDuration fwtypes.StringEnum[awstypes.AggregationDuration] `tfsdk:"aggregation_duration"`
310+
Description types.String `tfsdk:"description"`
311+
Name types.String `tfsdk:"name"`
312+
Status fwtypes.StringEnum[awstypes.NotificationConfigurationStatus] `tfsdk:"status"`
313+
Tags tftags.Map `tfsdk:"tags"`
314+
TagsAll tftags.Map `tfsdk:"tags_all"`
315+
}
316+
317+
func sweepNotificationConfigurations(ctx context.Context, client *conns.AWSClient) ([]sweep.Sweepable, error) {
318+
input := notifications.ListNotificationConfigurationsInput{}
319+
conn := client.NotificationsClient(ctx)
320+
var sweepResources []sweep.Sweepable
321+
322+
pages := notifications.NewListNotificationConfigurationsPaginator(conn, &input)
323+
for pages.HasMorePages() {
324+
page, err := pages.NextPage(ctx)
325+
if err != nil {
326+
return nil, err
327+
}
328+
329+
for _, v := range page.NotificationConfigurations {
330+
sweepResources = append(sweepResources, sweepfw.NewSweepResource(newResourceNotificationConfiguration, client,
331+
sweepfw.NewAttribute(names.AttrID, aws.ToString(v.Arn))),
332+
)
333+
}
334+
}
335+
336+
return sweepResources, nil
337+
}

0 commit comments

Comments
 (0)