Skip to content

Commit 035197f

Browse files
committed
r/aws_pinpoint_app: gate settings API calls in preparation for Pinpoint EOL
Amazon Pinpoint engagement features are being discontinued on October 30, 2026. The 'campaign_hook', 'limits', and 'quiet_time' blocks correspond to those features and rely on GetApplicationSettings and UpdateApplicationSettings, which will be removed at EOL. Skip those API calls when the user's HCL config does not declare any of the three blocks, so that 'aws_pinpoint_app' continues to work after EOL for users who only manage the application itself. - Add configHasSettings helper that reads GetRawConfig() and reports whether any of the three blocks are present in the user's config. - Add shouldFetchSettings helper that defers to configHasSettings during normal CRUD/refresh, and detects the first Read after import (rawConfig is null and rawState.arn is null) to preserve import behavior for configs that declare these blocks. - Gate UpdateApplicationSettings on configHasSettings. - Gate GetApplicationSettings on shouldFetchSettings. Tests: - Add TestAccPinpointApp_noSettingsNoImport asserting the no-settings path makes no settings API calls (verified via debug instrumentation during development). - Add TestAccPinpointApp_settingsAdded covering the transition from no settings to settings configured. - Update TestAccPinpointApp_basic and TestAccPinpointApp_tags state expectations and add ImportStateVerifyIgnore for the three blocks, since import populates them but post-create Read does not when config has none.
1 parent 5642185 commit 035197f

2 files changed

Lines changed: 149 additions & 48 deletions

File tree

internal/service/pinpoint/app.go

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,6 @@ func resourceApp() *schema.Resource {
7272
},
7373
},
7474
},
75-
//"cloudwatch_metrics_enabled": {
76-
// Type: schema.TypeBool,
77-
// Optional: true,
78-
// Default: false,
79-
//},
8075
"limits": {
8176
Type: schema.TypeList,
8277
Optional: true,
@@ -170,6 +165,48 @@ func resourceAppCreate(ctx context.Context, d *schema.ResourceData, meta any) di
170165
return append(diags, resourceAppUpdate(ctx, d, meta)...)
171166
}
172167

168+
// configHasSettings returns true if any of the deprecated settings attributes
169+
// are present in the user's raw config. Used to gate UpdateApplicationSettings
170+
// calls so they happen only when the user actively manages these blocks.
171+
func configHasSettings(d *schema.ResourceData) bool {
172+
rawConfig := d.GetRawConfig()
173+
if !rawConfig.IsKnown() || rawConfig.IsNull() {
174+
return false
175+
}
176+
for _, attr := range []string{"campaign_hook", "limits", "quiet_time"} {
177+
if v := rawConfig.GetAttr(attr); v.IsKnown() && !v.IsNull() && v.LengthInt() > 0 {
178+
return true
179+
}
180+
}
181+
return false
182+
}
183+
184+
// shouldFetchSettings returns true if GetApplicationSettings should be called
185+
// during Read. Returns true when:
186+
// - The raw config has settings blocks (normal CRUD case), OR
187+
// - This is the first Read after an import.
188+
//
189+
// Import detection: ImportStatePassthroughContext sets only the ID, so during
190+
// the first Read after import, rawConfig is null and rawState has all computed
191+
// attributes (e.g., arn) as null. Any other Read with rawConfig=null (such as
192+
// internal SDK refreshes) has arn populated from the prior Read. This lets us
193+
// fetch settings on import — preserving the user's ability to import an app
194+
// that has settings configured in their HCL — without fetching during refresh
195+
// reads when the user has no settings in config.
196+
func shouldFetchSettings(d *schema.ResourceData) bool {
197+
rawConfig := d.GetRawConfig()
198+
if rawConfig.IsKnown() && !rawConfig.IsNull() {
199+
return configHasSettings(d)
200+
}
201+
202+
rawState := d.GetRawState()
203+
if !rawState.IsKnown() || rawState.IsNull() {
204+
return false
205+
}
206+
arn := rawState.GetAttr(names.AttrARN)
207+
return arn.IsKnown() && arn.IsNull()
208+
}
209+
173210
func resourceAppRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
174211
var diags diag.Diagnostics
175212
conn := meta.(*conns.AWSClient).PinpointClient(ctx)
@@ -186,24 +223,27 @@ func resourceAppRead(ctx context.Context, d *schema.ResourceData, meta any) diag
186223
return sdkdiag.AppendErrorf(diags, "reading Pinpoint App (%s): %s", d.Id(), err)
187224
}
188225

189-
settings, err := findAppSettingsByID(ctx, conn, d.Id())
190-
191-
if err != nil {
192-
return sdkdiag.AppendErrorf(diags, "reading Pinpoint App (%s) settings: %s", d.Id(), err)
193-
}
194-
195226
d.Set(names.AttrApplicationID, app.Id)
196227
d.Set(names.AttrARN, app.Arn)
197-
if err := d.Set("campaign_hook", flattenCampaignHook(settings.CampaignHook)); err != nil {
198-
return sdkdiag.AppendErrorf(diags, "setting campaign_hook: %s", err)
199-
}
200-
if err := d.Set("limits", flattenCampaignLimits(settings.Limits)); err != nil {
201-
return sdkdiag.AppendErrorf(diags, "setting limits: %s", err)
202-
}
203228
d.Set(names.AttrName, app.Name)
204229
d.Set(names.AttrNamePrefix, create.NamePrefixFromName(aws.ToString(app.Name)))
205-
if err := d.Set("quiet_time", flattenQuietTime(settings.QuietTime)); err != nil {
206-
return sdkdiag.AppendErrorf(diags, "setting quiet_time: %s", err)
230+
231+
if shouldFetchSettings(d) {
232+
settings, err := findAppSettingsByID(ctx, conn, d.Id())
233+
234+
if err != nil {
235+
return sdkdiag.AppendErrorf(diags, "reading Pinpoint App (%s) settings: %s", d.Id(), err)
236+
}
237+
238+
if err := d.Set("campaign_hook", flattenCampaignHook(settings.CampaignHook)); err != nil {
239+
return sdkdiag.AppendErrorf(diags, "setting campaign_hook: %s", err)
240+
}
241+
if err := d.Set("limits", flattenCampaignLimits(settings.Limits)); err != nil {
242+
return sdkdiag.AppendErrorf(diags, "setting limits: %s", err)
243+
}
244+
if err := d.Set("quiet_time", flattenQuietTime(settings.QuietTime)); err != nil {
245+
return sdkdiag.AppendErrorf(diags, "setting quiet_time: %s", err)
246+
}
207247
}
208248

209249
return diags
@@ -213,7 +253,7 @@ func resourceAppUpdate(ctx context.Context, d *schema.ResourceData, meta any) di
213253
var diags diag.Diagnostics
214254
conn := meta.(*conns.AWSClient).PinpointClient(ctx)
215255

216-
if d.HasChangesExcept(names.AttrTags, names.AttrTagsAll) {
256+
if d.HasChangesExcept(names.AttrTags, names.AttrTagsAll) && configHasSettings(d) {
217257
appSettings := &awstypes.WriteApplicationSettingsRequest{}
218258

219259
if d.HasChange("campaign_hook") {
@@ -238,7 +278,6 @@ func resourceAppUpdate(ctx context.Context, d *schema.ResourceData, meta any) di
238278
}
239279

240280
_, err := conn.UpdateApplicationSettings(ctx, input)
241-
242281
if err != nil {
243282
return sdkdiag.AppendErrorf(diags, "updating Pinpoint App (%s) settings: %s", d.Id(), err)
244283
}

internal/service/pinpoint/app_test.go

Lines changed: 89 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -44,35 +44,19 @@ func TestAccPinpointApp_basic(t *testing.T) {
4444
resource.TestCheckResourceAttr(resourceName, names.AttrNamePrefix, ""),
4545
),
4646
ConfigStateChecks: []statecheck.StateCheck{
47-
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("campaign_hook"), knownvalue.ListExact([]knownvalue.Check{
48-
knownvalue.ObjectExact(map[string]knownvalue.Check{
49-
"lambda_function_name": knownvalue.StringExact(""),
50-
names.AttrMode: knownvalue.StringExact(""),
51-
"web_url": knownvalue.StringExact(""),
52-
}),
53-
})),
54-
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("limits"), knownvalue.ListExact([]knownvalue.Check{
55-
knownvalue.ObjectExact(map[string]knownvalue.Check{
56-
"daily": knownvalue.Int64Exact(0),
57-
"maximum_duration": knownvalue.Int64Exact(0),
58-
"messages_per_second": knownvalue.Int64Exact(0),
59-
"total": knownvalue.Int64Exact(0),
60-
}),
61-
})),
62-
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("quiet_time"), knownvalue.ListExact([]knownvalue.Check{
63-
knownvalue.ObjectExact(map[string]knownvalue.Check{
64-
"end": knownvalue.StringExact(""),
65-
"start": knownvalue.StringExact(""),
66-
}),
67-
})),
47+
// Settings blocks are not configured, so they are not populated in state.
48+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("campaign_hook"), knownvalue.ListExact([]knownvalue.Check{})),
49+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("limits"), knownvalue.ListExact([]knownvalue.Check{})),
50+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("quiet_time"), knownvalue.ListExact([]knownvalue.Check{})),
6851
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.Null()),
6952
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTagsAll), knownvalue.MapExact(map[string]knownvalue.Check{})),
7053
},
7154
},
7255
{
73-
ResourceName: resourceName,
74-
ImportState: true,
75-
ImportStateVerify: true,
56+
ResourceName: resourceName,
57+
ImportState: true,
58+
ImportStateVerify: true,
59+
ImportStateVerifyIgnore: []string{"campaign_hook", "limits", "quiet_time"},
7660
},
7761
},
7862
})
@@ -102,6 +86,41 @@ func TestAccPinpointApp_disappears(t *testing.T) {
10286
})
10387
}
10488

89+
func TestAccPinpointApp_noSettingsNoImport(t *testing.T) {
90+
ctx := acctest.Context(t)
91+
var application awstypes.ApplicationResponse
92+
rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix)
93+
resourceName := "aws_pinpoint_app.test"
94+
95+
acctest.ParallelTest(ctx, t, resource.TestCase{
96+
PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheckApp(ctx, t) },
97+
ErrorCheck: acctest.ErrorCheck(t, names.PinpointServiceID),
98+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
99+
CheckDestroy: testAccCheckAppDestroy(ctx, t),
100+
Steps: []resource.TestStep{
101+
{
102+
Config: testAccAppConfig_basic(rName),
103+
Check: resource.ComposeAggregateTestCheckFunc(
104+
testAccCheckAppExists(ctx, t, resourceName, &application),
105+
resource.TestCheckResourceAttrSet(resourceName, names.AttrApplicationID),
106+
acctest.CheckResourceAttrRegionalARNFormat(ctx, resourceName, names.AttrARN, "mobiletargeting", "apps/{id}"),
107+
resource.TestCheckResourceAttrPair(resourceName, names.AttrID, resourceName, names.AttrApplicationID),
108+
resource.TestCheckResourceAttr(resourceName, names.AttrName, rName),
109+
resource.TestCheckResourceAttr(resourceName, names.AttrNamePrefix, ""),
110+
),
111+
ConfigStateChecks: []statecheck.StateCheck{
112+
// Settings blocks are not configured, so they are not populated in state.
113+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("campaign_hook"), knownvalue.ListExact([]knownvalue.Check{})),
114+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("limits"), knownvalue.ListExact([]knownvalue.Check{})),
115+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("quiet_time"), knownvalue.ListExact([]knownvalue.Check{})),
116+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.Null()),
117+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTagsAll), knownvalue.MapExact(map[string]knownvalue.Check{})),
118+
},
119+
},
120+
},
121+
})
122+
}
123+
105124
func TestAccPinpointApp_nameGenerated(t *testing.T) {
106125
ctx := acctest.Context(t)
107126
var application awstypes.ApplicationResponse
@@ -169,9 +188,10 @@ func TestAccPinpointApp_tags(t *testing.T) {
169188
),
170189
},
171190
{
172-
ResourceName: resourceName,
173-
ImportState: true,
174-
ImportStateVerify: true,
191+
ResourceName: resourceName,
192+
ImportState: true,
193+
ImportStateVerify: true,
194+
ImportStateVerifyIgnore: []string{"campaign_hook", "limits", "quiet_time"},
175195
},
176196
{
177197
Config: testAccAppConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2),
@@ -411,6 +431,48 @@ func TestAccPinpointApp_quietTimeEmpty(t *testing.T) {
411431
})
412432
}
413433

434+
// TestAccPinpointApp_settingsAdded verifies that adding settings blocks to an
435+
// existing app (that was created without them) correctly calls the settings API
436+
// and populates state.
437+
func TestAccPinpointApp_settingsAdded(t *testing.T) {
438+
ctx := acctest.Context(t)
439+
var application awstypes.ApplicationResponse
440+
rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix)
441+
resourceName := "aws_pinpoint_app.test"
442+
443+
acctest.ParallelTest(ctx, t, resource.TestCase{
444+
PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheckApp(ctx, t) },
445+
ErrorCheck: acctest.ErrorCheck(t, names.PinpointServiceID),
446+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
447+
CheckDestroy: testAccCheckAppDestroy(ctx, t),
448+
Steps: []resource.TestStep{
449+
{
450+
// Create without any settings blocks.
451+
Config: testAccAppConfig_basic(rName),
452+
Check: testAccCheckAppExists(ctx, t, resourceName, &application),
453+
ConfigStateChecks: []statecheck.StateCheck{
454+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("limits"), knownvalue.ListExact([]knownvalue.Check{})),
455+
},
456+
},
457+
{
458+
// Add limits — settings API should be called.
459+
Config: testAccAppConfig_limits(rName),
460+
Check: testAccCheckAppExists(ctx, t, resourceName, &application),
461+
ConfigStateChecks: []statecheck.StateCheck{
462+
statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("limits"), knownvalue.ListExact([]knownvalue.Check{
463+
knownvalue.ObjectExact(map[string]knownvalue.Check{
464+
"daily": knownvalue.Int64Exact(3),
465+
"maximum_duration": knownvalue.Int64Exact(600),
466+
"messages_per_second": knownvalue.Int64Exact(1),
467+
"total": knownvalue.Int64Exact(100),
468+
}),
469+
})),
470+
},
471+
},
472+
},
473+
})
474+
}
475+
414476
func testAccPreCheckApp(ctx context.Context, t *testing.T) {
415477
t.Helper()
416478
acctest.PreCheckPinpointApp(ctx, t)

0 commit comments

Comments
 (0)