Skip to content

Commit f38d744

Browse files
committed
internal/provider: fix required tag validation crash
Previously the validate required tags interceptor would crash when the planned value for the `tags` attribute included unknown tag values. Validation now skips when planned values are not wholly known. Without the patch: ```console % go test -count=1 ./internal/provider/framework/... -run Test_resourceValidateRequiredTagsInterceptor --- FAIL: Test_resourceValidateRequiredTagsInterceptor (0.00s) --- FAIL: Test_resourceValidateRequiredTagsInterceptor/create,_unknown_tag_values (0.00s) --- FAIL: Test_resourceValidateRequiredTagsInterceptor (0.00s) --- FAIL: Test_resourceValidateRequiredTagsInterceptor/update,_unknown_tag_values (0.00s) panic: Value Conversion Error An unexpected error was encountered trying to build a value. This is always an error in the provider. Please report the following to the provider developer: Received unknown value, however the target type cannot handle unknown values. Use the corresponding `types` package type or a custom type that handles unknown values. Path: ["foo"] Target Type: *string Suggested Type: basetypes.StringValue ["foo"] [recovered] panic: Value Conversion Error An unexpected error was encountered trying to build a value. This is always an error in the provider. Please report the following to the provider developer: Received unknown value, however the target type cannot handle unknown values. Use the corresponding `types` package type or a custom type that handles unknown values. Path: ["foo"] Target Type: *string Suggested Type: basetypes.StringValue ["foo"] goroutine 28 [running]: testing.tRunner.func1.2({0x101701aa0, 0x14001282150}) /Users/jaredbaker/sdk/go1.24.10/src/testing/testing.go:1734 +0x1ac testing.tRunner.func1() /Users/jaredbaker/sdk/go1.24.10/src/testing/testing.go:1737 +0x334 panic({0x101701aa0?, 0x14001282150?}) /Users/jaredbaker/sdk/go1.24.10/src/runtime/panic.go:792 +0x124 github.com/hashicorp/terraform-provider-aws/internal/errs.Must[...](...) /Users/jaredbaker/development/_worktrees/b-tag-policy-interceptor/internal/errs/must.go:13 github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag.Must[...]({0x0?, 0x0}, {0x14001280920, 0x1016b17a0?, 0x1400009a298?}) /Users/jaredbaker/development/_worktrees/b-tag-policy-interceptor/internal/errs/fwdiag/must.go:17 +0x58 github.com/hashicorp/terraform-provider-aws/internal/framework/flex.must(...) /Users/jaredbaker/development/_worktrees/b-tag-policy-interceptor/internal/framework/flex/errs.go:13 github.com/hashicorp/terraform-provider-aws/internal/framework/flex.ExpandFrameworkStringMap({0x1017c9e08, 0x140011020c0}, {0x1017cea10, 0x1400138c1c0}) /Users/jaredbaker/development/_worktrees/b-tag-policy-interceptor/internal/framework/flex/map.go:17 +0x94 github.com/hashicorp/terraform-provider-aws/internal/tags.New({0x1017c9e08, 0x140011020c0}, {0x101795120?, 0x1400110dd68}) /Users/jaredbaker/development/_worktrees/b-tag-policy-interceptor/internal/tags/key_value_tags.go:650 +0x2fc github.com/hashicorp/terraform-provider-aws/internal/provider/framework.resourceValidateRequiredTagsInterceptor.modifyPlan({}, {0x1017c9e08, 0x140011020c0}, {{0x1017cfb58, 0x14000aec160}, 0x140000fc700, 0x14001049580, 0x1}) /Users/jaredbaker/development/_worktrees/b-tag-policy-interceptor/internal/provider/framework/tags_interceptor.go:308 +0x4d8 github.com/hashicorp/terraform-provider-aws/internal/provider/framework.Test_resourceValidateRequiredTagsInterceptor.func2(0x14000003c00) /Users/jaredbaker/development/_worktrees/b-tag-policy-interceptor/internal/provider/framework/tags_interceptor_test.go:404 +0x16c testing.tRunner(0x14000003c00, 0x14001063570) /Users/jaredbaker/sdk/go1.24.10/src/testing/testing.go:1792 +0xe4 created by testing.(*T).Run in goroutine 24 /Users/jaredbaker/sdk/go1.24.10/src/testing/testing.go:1851 +0x374 FAIL github.com/hashicorp/terraform-provider-aws/internal/provider/framework 0.850s ``` ```console % TF_ACC_REQUIRED_TAG_KEY=Owner make t K=iot T=TestAccIoTBillingGroup_requiredTags && TF_ACC_REQUIRED_TAG_KEY=Owner make t K=logs T=TestAccLogsLogGroup_requiredTags make: Verifying source code with gofmt... ==> Checking that code complies with gofmt requirements... make: Running acceptance tests on branch: 🌿 b-tag-policy-interceptor 🌿... TF_ACC=1 go1.24.10 test ./internal/service/iot/... -v -count 1 -parallel 20 -run='TestAccIoTBillingGroup_requiredTags' -timeout 360m -vet=off 2025/11/21 11:54:39 Creating Terraform AWS Provider (SDKv2-style)... 2025/11/21 11:54:39 Initializing Terraform AWS Provider (SDKv2-style)... --- PASS: TestAccIoTBillingGroup_requiredTags_defaultTags (19.31s) --- PASS: TestAccIoTBillingGroup_requiredTags (19.33s) --- PASS: TestAccIoTBillingGroup_requiredTags_disabled (34.01s) --- PASS: TestAccIoTBillingGroup_requiredTags_warning (37.18s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/iot 43.681s make: Verifying source code with gofmt... ==> Checking that code complies with gofmt requirements... make: Running acceptance tests on branch: 🌿 b-tag-policy-interceptor 🌿... TF_ACC=1 go1.24.10 test ./internal/service/logs/... -v -count 1 -parallel 20 -run='TestAccLogsLogGroup_requiredTags' -timeout 360m -vet=off 2025/11/21 11:55:37 Creating Terraform AWS Provider (SDKv2-style)... 2025/11/21 11:55:37 Initializing Terraform AWS Provider (SDKv2-style)... --- PASS: TestAccLogsLogGroup_requiredTags (19.59s) --- PASS: TestAccLogsLogGroup_requiredTags_defaultTags (19.67s) --- PASS: TestAccLogsLogGroup_requiredTags_disabled (35.34s) --- PASS: TestAccLogsLogGroup_requiredTags_warning (38.32s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/logs 44.717s ```
1 parent 2c53338 commit f38d744

File tree

4 files changed

+215
-0
lines changed

4 files changed

+215
-0
lines changed

.changelog/45201.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
```release-note:bug
22
provider: Fix early return logic in the required tag validation interceptor. This addresses a performance regression introduced in [v6.22.0](https://github.com/hashicorp/terraform-provider-aws/blob/main/CHANGELOG.md#6220-november-20-2025).
33
```
4+
```release-note:bug
5+
provider: Fix crash in required tag validation interceptor when tag values are unknown. This addresses a regression introduced in [v6.22.0](https://github.com/hashicorp/terraform-provider-aws/blob/main/CHANGELOG.md#6220-november-20-2025).
6+
```

internal/provider/framework/tags_interceptor.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,10 @@ func (r resourceValidateRequiredTagsInterceptor) modifyPlan(ctx context.Context,
301301
return
302302
}
303303

304+
if !planTags.IsWhollyKnown() {
305+
return
306+
}
307+
304308
allPlanTags := c.DefaultTagsConfig(ctx).MergeTags(tftags.New(ctx, planTags))
305309
allStateTags := c.DefaultTagsConfig(ctx).MergeTags(tftags.New(ctx, stateTags))
306310

internal/provider/framework/tags_interceptor_test.go

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,42 @@ func Test_resourceValidateRequiredTagsInterceptor(t *testing.T) {
9191
},
9292
}
9393

94+
// Null tags
9495
attrs := map[string]tftypes.Value{
9596
"name": tftypes.NewValue(tftypes.String, "test"),
9697
"tags": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, nil),
9798
}
9899
rawVal := tftypes.NewValue(resourceSchema.Type().TerraformType(ctx), attrs)
99100

101+
// Partial required tags
102+
attrsPartial := map[string]tftypes.Value{
103+
"name": tftypes.NewValue(tftypes.String, "test"),
104+
"tags": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, map[string]tftypes.Value{
105+
"bar": tftypes.NewValue(tftypes.String, nil),
106+
}),
107+
}
108+
rawValPartial := tftypes.NewValue(resourceSchema.Type().TerraformType(ctx), attrsPartial)
109+
110+
// All required tags
111+
attrsRequired := map[string]tftypes.Value{
112+
"name": tftypes.NewValue(tftypes.String, "test"),
113+
"tags": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, map[string]tftypes.Value{
114+
"foo": tftypes.NewValue(tftypes.String, nil),
115+
"bar": tftypes.NewValue(tftypes.String, nil),
116+
}),
117+
}
118+
rawValRequired := tftypes.NewValue(resourceSchema.Type().TerraformType(ctx), attrsRequired)
119+
120+
// Unknown tag values
121+
attrsUnknown := map[string]tftypes.Value{
122+
"name": tftypes.NewValue(tftypes.String, "test"),
123+
"tags": tftypes.NewValue(tftypes.Map{ElementType: tftypes.String}, map[string]tftypes.Value{
124+
"foo": tftypes.NewValue(tftypes.String, tftypes.UnknownValue),
125+
"bar": tftypes.NewValue(tftypes.String, tftypes.UnknownValue),
126+
}),
127+
}
128+
rawValUnknown := tftypes.NewValue(resourceSchema.Type().TerraformType(ctx), attrsUnknown)
129+
100130
tests := []struct {
101131
name string
102132
opts interceptorOptions[resource.ModifyPlanRequest, resource.ModifyPlanResponse]
@@ -135,6 +165,93 @@ func Test_resourceValidateRequiredTagsInterceptor(t *testing.T) {
135165
),
136166
},
137167
},
168+
{
169+
name: "create, partial tags",
170+
opts: interceptorOptions[resource.ModifyPlanRequest, resource.ModifyPlanResponse]{
171+
c: mockRequiredTagsClient{},
172+
request: &resource.ModifyPlanRequest{
173+
Config: tfsdk.Config{
174+
Raw: rawValPartial,
175+
Schema: resourceSchema,
176+
},
177+
State: tfsdk.State{
178+
Raw: tftypes.NewValue(resourceSchema.Type().TerraformType(ctx), nil), // Raw state is null on creation
179+
Schema: resourceSchema,
180+
},
181+
Plan: tfsdk.Plan{
182+
Raw: rawValPartial,
183+
Schema: resourceSchema,
184+
},
185+
},
186+
response: &resource.ModifyPlanResponse{
187+
Plan: tfsdk.Plan{
188+
Raw: rawValPartial,
189+
Schema: resourceSchema,
190+
},
191+
},
192+
when: Before,
193+
},
194+
wantDiags: diag.Diagnostics{diag.NewAttributeErrorDiagnostic(
195+
path.Root(names.AttrTags),
196+
"Missing Required Tags",
197+
"An organizational tag policy requires the following tags for aws_test: [foo]",
198+
),
199+
},
200+
},
201+
{
202+
name: "create, required tags",
203+
opts: interceptorOptions[resource.ModifyPlanRequest, resource.ModifyPlanResponse]{
204+
c: mockRequiredTagsClient{},
205+
request: &resource.ModifyPlanRequest{
206+
Config: tfsdk.Config{
207+
Raw: rawValRequired,
208+
Schema: resourceSchema,
209+
},
210+
State: tfsdk.State{
211+
Raw: tftypes.NewValue(resourceSchema.Type().TerraformType(ctx), nil), // Raw state is null on creation
212+
Schema: resourceSchema,
213+
},
214+
Plan: tfsdk.Plan{
215+
Raw: rawValRequired,
216+
Schema: resourceSchema,
217+
},
218+
},
219+
response: &resource.ModifyPlanResponse{
220+
Plan: tfsdk.Plan{
221+
Raw: rawValRequired,
222+
Schema: resourceSchema,
223+
},
224+
},
225+
when: Before,
226+
},
227+
},
228+
{
229+
name: "create, unknown tag values",
230+
opts: interceptorOptions[resource.ModifyPlanRequest, resource.ModifyPlanResponse]{
231+
c: mockRequiredTagsClient{},
232+
request: &resource.ModifyPlanRequest{
233+
Config: tfsdk.Config{
234+
Raw: rawValUnknown,
235+
Schema: resourceSchema,
236+
},
237+
State: tfsdk.State{
238+
Raw: tftypes.NewValue(resourceSchema.Type().TerraformType(ctx), nil), // Raw state is null on creation
239+
Schema: resourceSchema,
240+
},
241+
Plan: tfsdk.Plan{
242+
Raw: rawValUnknown,
243+
Schema: resourceSchema,
244+
},
245+
},
246+
response: &resource.ModifyPlanResponse{
247+
Plan: tfsdk.Plan{
248+
Raw: rawValUnknown,
249+
Schema: resourceSchema,
250+
},
251+
},
252+
when: Before,
253+
},
254+
},
138255
{
139256
name: "update, no tags change",
140257
opts: interceptorOptions[resource.ModifyPlanRequest, resource.ModifyPlanResponse]{
@@ -162,6 +279,93 @@ func Test_resourceValidateRequiredTagsInterceptor(t *testing.T) {
162279
when: Before,
163280
},
164281
},
282+
{
283+
name: "update, add required",
284+
opts: interceptorOptions[resource.ModifyPlanRequest, resource.ModifyPlanResponse]{
285+
c: mockRequiredTagsClient{},
286+
request: &resource.ModifyPlanRequest{
287+
Config: tfsdk.Config{
288+
Raw: rawValRequired,
289+
Schema: resourceSchema,
290+
},
291+
State: tfsdk.State{
292+
Raw: rawValPartial,
293+
Schema: resourceSchema,
294+
},
295+
Plan: tfsdk.Plan{
296+
Raw: rawValRequired,
297+
Schema: resourceSchema,
298+
},
299+
},
300+
response: &resource.ModifyPlanResponse{
301+
Plan: tfsdk.Plan{
302+
Raw: rawValRequired,
303+
Schema: resourceSchema,
304+
},
305+
},
306+
when: Before,
307+
},
308+
},
309+
{
310+
name: "update, remove required",
311+
opts: interceptorOptions[resource.ModifyPlanRequest, resource.ModifyPlanResponse]{
312+
c: mockRequiredTagsClient{},
313+
request: &resource.ModifyPlanRequest{
314+
Config: tfsdk.Config{
315+
Raw: rawValPartial,
316+
Schema: resourceSchema,
317+
},
318+
State: tfsdk.State{
319+
Raw: rawValRequired,
320+
Schema: resourceSchema,
321+
},
322+
Plan: tfsdk.Plan{
323+
Raw: rawValPartial,
324+
Schema: resourceSchema,
325+
},
326+
},
327+
response: &resource.ModifyPlanResponse{
328+
Plan: tfsdk.Plan{
329+
Raw: rawValRequired,
330+
Schema: resourceSchema,
331+
},
332+
},
333+
when: Before,
334+
},
335+
wantDiags: diag.Diagnostics{diag.NewAttributeErrorDiagnostic(
336+
path.Root(names.AttrTags),
337+
"Missing Required Tags",
338+
"An organizational tag policy requires the following tags for aws_test: [foo]",
339+
),
340+
},
341+
},
342+
{
343+
name: "update, unknown tag values",
344+
opts: interceptorOptions[resource.ModifyPlanRequest, resource.ModifyPlanResponse]{
345+
c: mockRequiredTagsClient{},
346+
request: &resource.ModifyPlanRequest{
347+
Config: tfsdk.Config{
348+
Raw: rawValUnknown,
349+
Schema: resourceSchema,
350+
},
351+
State: tfsdk.State{
352+
Raw: rawValPartial,
353+
Schema: resourceSchema,
354+
},
355+
Plan: tfsdk.Plan{
356+
Raw: rawValUnknown,
357+
Schema: resourceSchema,
358+
},
359+
},
360+
response: &resource.ModifyPlanResponse{
361+
Plan: tfsdk.Plan{
362+
Raw: rawValUnknown,
363+
Schema: resourceSchema,
364+
},
365+
},
366+
when: Before,
367+
},
368+
},
165369
{
166370
name: "destroy",
167371
opts: interceptorOptions[resource.ModifyPlanRequest, resource.ModifyPlanResponse]{

internal/provider/sdkv2/tags_interceptor.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,10 @@ func validateRequiredTags() customizeDiffInterceptor {
329329
return nil
330330
}
331331

332+
if !d.GetRawPlan().GetAttr(names.AttrTags).IsWhollyKnown() {
333+
return nil
334+
}
335+
332336
cfgTags := tftags.New(ctx, d.Get(names.AttrTags).(map[string]any))
333337
allTags := c.DefaultTagsConfig(ctx).MergeTags(cfgTags)
334338
if allTags.ContainsAllKeys(reqTags) {

0 commit comments

Comments
 (0)