Skip to content

Commit c1940e8

Browse files
authored
Merge pull request #43659 from marcinbelczewski/b-dont-set-region-on-null-state
fix: setting the region after resource has been removed from state due resource disappearance
2 parents 01f7e04 + 180f711 commit c1940e8

8 files changed

Lines changed: 132 additions & 0 deletions

File tree

.changelog/43659.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
provider: Fix failure to detect resources deleted outside of Terraform as missing for numerous resource types
3+
```

internal/provider/framework/region.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,11 @@ func (r resourceSetRegionInStateInterceptor) read(ctx context.Context, opts inte
332332

333333
switch response, when := opts.response, opts.when; when {
334334
case After:
335+
// Will occur on a refresh when the resource does not exist in AWS and needs to be recreated, e.g. "_disappears" tests.
336+
if response.State.Raw.IsNull() {
337+
return diags
338+
}
339+
335340
// Set region in state after R.
336341
diags.Append(response.State.SetAttribute(ctx, path.Root(names.AttrRegion), c.Region(ctx))...)
337342
if diags.HasError() {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package framework
5+
6+
import (
7+
"context"
8+
"os"
9+
"testing"
10+
11+
"github.com/hashicorp/terraform-plugin-framework/path"
12+
"github.com/hashicorp/terraform-plugin-framework/resource"
13+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
14+
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
15+
"github.com/hashicorp/terraform-plugin-framework/types"
16+
"github.com/hashicorp/terraform-plugin-go/tftypes"
17+
"github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag"
18+
"github.com/hashicorp/terraform-provider-aws/internal/provider/framework/resourceattribute"
19+
"github.com/hashicorp/terraform-provider-aws/names"
20+
)
21+
22+
func TestResourceSetRegionInStateInterceptor_Read(t *testing.T) {
23+
t.Parallel()
24+
25+
const name = "example"
26+
27+
region := os.Getenv("AWS_DEFAULT_REGION")
28+
if region == "" {
29+
t.Skip("AWS_DEFAULT_REGION env var must be set for ResourceSetRegionInStateInterceptor test.")
30+
}
31+
32+
ctx := context.Background()
33+
client := mockClient{region: region}
34+
icpt := resourceSetRegionInStateInterceptor{}
35+
36+
s := schema.Schema{
37+
Attributes: map[string]schema.Attribute{
38+
names.AttrName: schema.StringAttribute{Required: true},
39+
names.AttrRegion: resourceattribute.Region(),
40+
},
41+
}
42+
43+
tests := map[string]struct {
44+
startState tfsdk.State
45+
expectSet bool
46+
}{
47+
"when state is present then region is set": {
48+
startState: stateFromSchema(ctx, s, map[string]string{"name": name}),
49+
expectSet: true,
50+
},
51+
"when state is null then it remains null": {
52+
startState: tfsdk.State{
53+
Raw: tftypes.NewValue(s.Type().TerraformType(ctx), nil),
54+
Schema: s,
55+
},
56+
expectSet: false,
57+
},
58+
}
59+
60+
for tn, tc := range tests {
61+
t.Run(tn, func(t *testing.T) {
62+
t.Parallel()
63+
64+
req := resource.ReadRequest{State: tc.startState}
65+
resp := resource.ReadResponse{State: tc.startState}
66+
67+
if diags := icpt.read(ctx, interceptorOptions[resource.ReadRequest, resource.ReadResponse]{
68+
c: client,
69+
request: &req,
70+
response: &resp,
71+
when: After,
72+
}); diags.HasError() {
73+
t.Fatalf("unexpected diags: %s", diags)
74+
}
75+
76+
if tc.expectSet {
77+
got := getStateAttributeValue(ctx, t, resp.State, path.Root("region"))
78+
if got != region {
79+
t.Errorf("expected region %q, got %q", region, got)
80+
}
81+
} else {
82+
if !resp.State.Raw.IsNull() {
83+
t.Errorf("expected State.Raw to stay null, got %#v", resp.State.Raw)
84+
}
85+
}
86+
})
87+
}
88+
}
89+
90+
func getStateAttributeValue(ctx context.Context, t *testing.T, st tfsdk.State, p path.Path) string {
91+
t.Helper()
92+
93+
var v types.String
94+
if diags := st.GetAttribute(ctx, p, &v); diags.HasError() {
95+
t.Fatalf("unexpected error getting State attribute %q: %s", p, fwdiag.DiagnosticsError(diags))
96+
}
97+
return v.ValueString()
98+
}

internal/provider/sdkv2/region.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ func setRegionInState() crudInterceptor {
7878
// Set region in state after R.
7979
switch why {
8080
case Read:
81+
// Will occur on a refresh when the resource does not exist in AWS and needs to be recreated, e.g. "_disappears" tests.
82+
if d.Id() == "" {
83+
return diags
84+
}
85+
8186
if err := d.Set(names.AttrRegion, c.Region(ctx)); err != nil {
8287
return sdkdiag.AppendErrorf(diags, "setting %s: %s", names.AttrRegion, err)
8388
}

internal/service/appfabric/app_bundle_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ func testAccAppBundle_disappears(t *testing.T) {
8989
acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfappfabric.ResourceAppBundle, resourceName),
9090
),
9191
ExpectNonEmptyPlan: true,
92+
ConfigPlanChecks: resource.ConfigPlanChecks{
93+
PostApplyPostRefresh: []plancheck.PlanCheck{
94+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate),
95+
},
96+
},
9297
},
9398
},
9499
})

internal/service/ec2/vpc_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ func TestAccVPC_disappears(t *testing.T) {
9494
acctest.CheckResourceDisappears(ctx, acctest.Provider, tfec2.ResourceVPC(), resourceName),
9595
),
9696
ExpectNonEmptyPlan: true,
97+
ConfigPlanChecks: resource.ConfigPlanChecks{
98+
PostApplyPostRefresh: []plancheck.PlanCheck{
99+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate),
100+
},
101+
},
97102
},
98103
},
99104
})

internal/service/s3control/access_point_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
1515
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
1616
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
17+
"github.com/hashicorp/terraform-plugin-testing/plancheck"
1718
"github.com/hashicorp/terraform-plugin-testing/statecheck"
1819
"github.com/hashicorp/terraform-plugin-testing/terraform"
1920
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
@@ -92,6 +93,11 @@ func TestAccS3ControlAccessPoint_disappears(t *testing.T) {
9293
acctest.CheckResourceDisappears(ctx, acctest.Provider, tfs3control.ResourceAccessPoint(), resourceName),
9394
),
9495
ExpectNonEmptyPlan: true,
96+
ConfigPlanChecks: resource.ConfigPlanChecks{
97+
PostApplyPostRefresh: []plancheck.PlanCheck{
98+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate),
99+
},
100+
},
95101
},
96102
},
97103
})

internal/service/s3tables/table_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ func TestAccS3TablesTable_disappears(t *testing.T) {
129129
acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfs3tables.NewResourceTable, resourceName),
130130
),
131131
ExpectNonEmptyPlan: true,
132+
ConfigPlanChecks: resource.ConfigPlanChecks{
133+
PostApplyPostRefresh: []plancheck.PlanCheck{
134+
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate),
135+
},
136+
},
132137
},
133138
},
134139
})

0 commit comments

Comments
 (0)