Skip to content

Commit 4855ce2

Browse files
authored
Merge pull request #42544 from marcinbelczewski/f-notification-notification-hub
feat: implement notification hub resource
2 parents 82531c2 + 022385d commit 4855ce2

File tree

7 files changed

+506
-1
lines changed

7 files changed

+506
-1
lines changed

.changelog/42544.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_notifications_notification_hub
3+
```
Lines changed: 11 additions & 0 deletions
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+
ResourceNotificationHub = newNotificationHubResource
9+
10+
FindNotificationHubByRegion = findNotificationHubByRegion
11+
)
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package notifications
5+
6+
import (
7+
"context"
8+
"errors"
9+
"fmt"
10+
"time"
11+
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-timeouts/resource/timeouts"
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/stringplanmodifier"
21+
"github.com/hashicorp/terraform-plugin-framework/types"
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+
tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices"
29+
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
30+
"github.com/hashicorp/terraform-provider-aws/names"
31+
)
32+
33+
// @FrameworkResource("aws_notifications_notification_hub", name="Notification Hub")
34+
func newNotificationHubResource(context.Context) (resource.ResourceWithConfigure, error) {
35+
r := &notificationHubResource{}
36+
37+
r.SetDefaultCreateTimeout(20 * time.Minute)
38+
r.SetDefaultDeleteTimeout(20 * time.Minute)
39+
40+
return r, nil
41+
}
42+
43+
type notificationHubResource struct {
44+
framework.ResourceWithConfigure
45+
framework.WithTimeouts
46+
framework.WithNoUpdate
47+
}
48+
49+
func (r *notificationHubResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) {
50+
response.Schema = schema.Schema{
51+
Attributes: map[string]schema.Attribute{
52+
"notification_hub_region": schema.StringAttribute{
53+
Required: true,
54+
PlanModifiers: []planmodifier.String{
55+
stringplanmodifier.RequiresReplace(),
56+
},
57+
},
58+
},
59+
Blocks: map[string]schema.Block{
60+
names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{
61+
Create: true,
62+
Delete: true,
63+
}),
64+
},
65+
}
66+
}
67+
68+
func (r *notificationHubResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
69+
var data notificationHubResourceModel
70+
response.Diagnostics.Append(request.Plan.Get(ctx, &data)...)
71+
if response.Diagnostics.HasError() {
72+
return
73+
}
74+
75+
conn := r.Meta().NotificationsClient(ctx)
76+
77+
region := fwflex.StringValueFromFramework(ctx, data.NotificationHubRegion)
78+
input := notifications.RegisterNotificationHubInput{
79+
NotificationHubRegion: aws.String(region),
80+
}
81+
_, err := conn.RegisterNotificationHub(ctx, &input)
82+
83+
if err != nil {
84+
response.Diagnostics.AddError(fmt.Sprintf("registering User Notifications Notification Hub (%s)", region), err.Error())
85+
86+
return
87+
}
88+
89+
if _, err := waitNotificationHubCreated(ctx, conn, region, r.CreateTimeout(ctx, data.Timeouts)); err != nil {
90+
response.Diagnostics.AddError(fmt.Sprintf("waiting for User Notifications Notification Hub (%s) create", region), err.Error())
91+
92+
return
93+
}
94+
95+
response.Diagnostics.Append(response.State.Set(ctx, data)...)
96+
}
97+
98+
func (r *notificationHubResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
99+
var data notificationHubResourceModel
100+
response.Diagnostics.Append(request.State.Get(ctx, &data)...)
101+
if response.Diagnostics.HasError() {
102+
return
103+
}
104+
105+
conn := r.Meta().NotificationsClient(ctx)
106+
107+
_, err := findNotificationHubByRegion(ctx, conn, data.NotificationHubRegion.ValueString())
108+
109+
if tfresource.NotFound(err) {
110+
response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err))
111+
response.State.RemoveResource(ctx)
112+
113+
return
114+
}
115+
116+
if err != nil {
117+
response.Diagnostics.AddError(fmt.Sprintf("reading User Notifications Notification Hub (%s)", data.NotificationHubRegion.ValueString()), err.Error())
118+
119+
return
120+
}
121+
122+
response.Diagnostics.Append(response.State.Set(ctx, &data)...)
123+
}
124+
125+
func (r *notificationHubResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
126+
var data notificationHubResourceModel
127+
response.Diagnostics.Append(request.State.Get(ctx, &data)...)
128+
if response.Diagnostics.HasError() {
129+
return
130+
}
131+
132+
conn := r.Meta().NotificationsClient(ctx)
133+
134+
region := fwflex.StringValueFromFramework(ctx, data.NotificationHubRegion)
135+
input := notifications.DeregisterNotificationHubInput{
136+
NotificationHubRegion: aws.String(region),
137+
}
138+
_, err := conn.DeregisterNotificationHub(ctx, &input)
139+
140+
if errs.IsA[*awstypes.ResourceNotFoundException](err) {
141+
return
142+
}
143+
144+
if errs.IsAErrorMessageContains[*awstypes.ConflictException](err, "Cannot deregister last ACTIVE notification hub") {
145+
return
146+
}
147+
148+
if err != nil {
149+
response.Diagnostics.AddError(fmt.Sprintf("deregistering User Notifications Notification Hub (%s)", region), err.Error())
150+
151+
return
152+
}
153+
154+
if _, err := waitNotificationHubDeleted(ctx, conn, region, r.DeleteTimeout(ctx, data.Timeouts)); err != nil {
155+
response.Diagnostics.AddError(fmt.Sprintf("waiting for User Notifications Notification Hub (%s) delete", region), err.Error())
156+
157+
return
158+
}
159+
}
160+
161+
func (r *notificationHubResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) {
162+
resource.ImportStatePassthroughID(ctx, path.Root("notification_hub_region"), request, response)
163+
}
164+
165+
func findNotificationHubByRegion(ctx context.Context, conn *notifications.Client, region string) (*awstypes.NotificationHubOverview, error) {
166+
var input notifications.ListNotificationHubsInput
167+
output, err := findNotificationHub(ctx, conn, &input, func(v *awstypes.NotificationHubOverview) bool {
168+
return aws.ToString(v.NotificationHubRegion) == region
169+
})
170+
171+
if err != nil {
172+
return nil, err
173+
}
174+
175+
if output.StatusSummary == nil {
176+
return nil, tfresource.NewEmptyResultError(input)
177+
}
178+
179+
return output, nil
180+
}
181+
182+
func findNotificationHub(ctx context.Context, conn *notifications.Client, input *notifications.ListNotificationHubsInput, filter tfslices.Predicate[*awstypes.NotificationHubOverview]) (*awstypes.NotificationHubOverview, error) {
183+
output, err := findNotificationHubs(ctx, conn, input, filter)
184+
185+
if err != nil {
186+
return nil, err
187+
}
188+
189+
return tfresource.AssertSingleValueResult(output)
190+
}
191+
192+
func findNotificationHubs(ctx context.Context, conn *notifications.Client, input *notifications.ListNotificationHubsInput, filter tfslices.Predicate[*awstypes.NotificationHubOverview]) ([]awstypes.NotificationHubOverview, error) {
193+
var output []awstypes.NotificationHubOverview
194+
195+
pages := notifications.NewListNotificationHubsPaginator(conn, input)
196+
for pages.HasMorePages() {
197+
page, err := pages.NextPage(ctx)
198+
199+
if errs.IsA[*awstypes.ResourceNotFoundException](err) {
200+
return nil, &retry.NotFoundError{
201+
LastError: err,
202+
LastRequest: input,
203+
}
204+
}
205+
206+
if err != nil {
207+
return nil, err
208+
}
209+
210+
for _, v := range page.NotificationHubs {
211+
if filter(&v) {
212+
output = append(output, v)
213+
}
214+
}
215+
}
216+
217+
return output, nil
218+
}
219+
220+
func statusNotificationHub(ctx context.Context, conn *notifications.Client, region string) retry.StateRefreshFunc {
221+
return func() (any, string, error) {
222+
output, err := findNotificationHubByRegion(ctx, conn, region)
223+
224+
if tfresource.NotFound(err) {
225+
return nil, "", nil
226+
}
227+
228+
if err != nil {
229+
return nil, "", err
230+
}
231+
232+
return output, string(output.StatusSummary.Status), nil
233+
}
234+
}
235+
236+
func waitNotificationHubCreated(ctx context.Context, conn *notifications.Client, region string, timeout time.Duration) (*awstypes.NotificationHubOverview, error) {
237+
stateConf := &retry.StateChangeConf{
238+
Pending: enum.Slice(awstypes.NotificationHubStatusRegistering),
239+
Target: enum.Slice(awstypes.NotificationHubStatusActive),
240+
Refresh: statusNotificationHub(ctx, conn, region),
241+
Timeout: timeout,
242+
ContinuousTargetOccurence: 2,
243+
}
244+
245+
outputRaw, err := stateConf.WaitForStateContext(ctx)
246+
247+
if output, ok := outputRaw.(*awstypes.NotificationHubOverview); ok {
248+
tfresource.SetLastError(err, errors.New(aws.ToString(output.StatusSummary.Reason)))
249+
250+
return output, err
251+
}
252+
253+
return nil, err
254+
}
255+
256+
func waitNotificationHubDeleted(ctx context.Context, conn *notifications.Client, region string, timeout time.Duration) (*awstypes.NotificationHubOverview, error) {
257+
stateConf := &retry.StateChangeConf{
258+
Pending: enum.Slice(awstypes.NotificationHubStatusDeregistering),
259+
Target: []string{},
260+
Refresh: statusNotificationHub(ctx, conn, region),
261+
Timeout: timeout,
262+
}
263+
264+
outputRaw, err := stateConf.WaitForStateContext(ctx)
265+
266+
if output, ok := outputRaw.(*awstypes.NotificationHubOverview); ok {
267+
tfresource.SetLastError(err, errors.New(aws.ToString(output.StatusSummary.Reason)))
268+
269+
return output, err
270+
}
271+
272+
return nil, err
273+
}
274+
275+
type notificationHubResourceModel struct {
276+
NotificationHubRegion types.String `tfsdk:"notification_hub_region"`
277+
Timeouts timeouts.Value `tfsdk:"timeouts"`
278+
}

0 commit comments

Comments
 (0)