Skip to content

Commit fade75a

Browse files
committed
send full object when config is null
1 parent ffc40fa commit fade75a

File tree

7 files changed

+206
-46
lines changed

7 files changed

+206
-46
lines changed

internal/command/query_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,9 @@ func queryFixtureProvider() *testing_provider.MockProvider {
285285
},
286286
},
287287
},
288-
Nesting: configschema.NestingSingle,
288+
Nesting: configschema.NestingSingle,
289+
MinItems: 1,
290+
MaxItems: 1,
289291
},
290292
},
291293
}
@@ -306,7 +308,9 @@ func queryFixtureProvider() *testing_provider.MockProvider {
306308
},
307309
},
308310
},
309-
Nesting: configschema.NestingSingle,
311+
Nesting: configschema.NestingSingle,
312+
MinItems: 1,
313+
MaxItems: 1,
310314
},
311315
},
312316
}

internal/plugin/grpc_provider.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,16 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
172172

173173
for name, list := range protoResp.ListResourceSchemas {
174174
ret := convert.ProtoToProviderSchema(list, nil)
175+
minItem := 0
176+
// check if the provider has set some attributes as required.
177+
// When yes, then we set minItem = 1, which
178+
// validates that the configuration contains a "config" block.
179+
for _, attrS := range ret.Body.Attributes {
180+
if attrS.Required {
181+
minItem = 1
182+
break
183+
}
184+
}
175185
resp.ListResourceTypes[name] = providers.Schema{
176186
Version: ret.Version,
177187
Body: &configschema.Block{
@@ -183,8 +193,10 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
183193
},
184194
BlockTypes: map[string]*configschema.NestedBlock{
185195
"config": {
186-
Block: *ret.Body,
187-
Nesting: configschema.NestingSingle,
196+
Block: *ret.Body,
197+
Nesting: configschema.NestingSingle,
198+
MinItems: minItem,
199+
MaxItems: minItem,
188200
},
189201
},
190202
},

internal/plugin6/grpc_provider.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,16 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
173173

174174
for name, list := range protoResp.ListResourceSchemas {
175175
ret := convert.ProtoToProviderSchema(list, nil)
176+
minItem := 0
177+
// check if the provider has set some attributes as required.
178+
// When yes, then we set minItem = 1, which
179+
// validates that the configuration contains a "config" block.
180+
for _, attrS := range ret.Body.Attributes {
181+
if attrS.Required {
182+
minItem = 1
183+
break
184+
}
185+
}
176186
resp.ListResourceTypes[name] = providers.Schema{
177187
Version: ret.Version,
178188
Body: &configschema.Block{
@@ -184,8 +194,10 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
184194
},
185195
BlockTypes: map[string]*configschema.NestedBlock{
186196
"config": {
187-
Block: *ret.Body,
188-
Nesting: configschema.NestingSingle,
197+
Block: *ret.Body,
198+
Nesting: configschema.NestingSingle,
199+
MinItems: minItem,
200+
MaxItems: minItem,
189201
},
190202
},
191203
},

internal/terraform/context_plan_query_test.go

Lines changed: 152 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/google/go-cmp/cmp"
1414
"github.com/google/go-cmp/cmp/cmpopts"
15+
"github.com/hashicorp/hcl/v2"
1516
"github.com/hashicorp/terraform/internal/addrs"
1617
"github.com/hashicorp/terraform/internal/configs/configschema"
1718
"github.com/hashicorp/terraform/internal/plans"
@@ -24,15 +25,16 @@ import (
2425

2526
func TestContext2Plan_queryList(t *testing.T) {
2627
cases := []struct {
27-
name string
28-
mainConfig string
29-
queryConfig string
30-
generatedPath string
31-
diagCount int
32-
expectedErrMsg []string
33-
assertState func(*states.State)
34-
assertChanges func(providers.ProviderSchema, *plans.ChangesSrc)
35-
listResourceFn func(request providers.ListResourceRequest) providers.ListResourceResponse
28+
name string
29+
mainConfig string
30+
queryConfig string
31+
generatedPath string
32+
expectedErrMsg []string
33+
transformSchema func(*providers.GetProviderSchemaResponse)
34+
assertState func(*states.State)
35+
assertValidateDiags func(t *testing.T, diags tfdiags.Diagnostics)
36+
assertChanges func(providers.ProviderSchema, *plans.ChangesSrc)
37+
listResourceFn func(request providers.ListResourceRequest) providers.ListResourceResponse
3638
}{
3739
{
3840
name: "valid list reference - generates config",
@@ -262,7 +264,68 @@ func TestContext2Plan_queryList(t *testing.T) {
262264
},
263265
},
264266
{
265-
name: "valid list with empty config",
267+
// want this to fail
268+
name: "with empty config when it is required",
269+
mainConfig: `
270+
terraform {
271+
required_providers {
272+
test = {
273+
source = "hashicorp/test"
274+
version = "1.0.0"
275+
}
276+
}
277+
}
278+
`,
279+
queryConfig: `
280+
variable "input" {
281+
type = string
282+
default = "foo"
283+
}
284+
285+
list "test_resource" "test" {
286+
provider = test
287+
}
288+
`,
289+
290+
transformSchema: func(schema *providers.GetProviderSchemaResponse) {
291+
schema.ListResourceTypes["test_resource"].Body.BlockTypes = map[string]*configschema.NestedBlock{
292+
"config": {
293+
Block: configschema.Block{
294+
Attributes: map[string]*configschema.Attribute{
295+
"filter": {
296+
Required: true,
297+
NestedType: &configschema.Object{
298+
Nesting: configschema.NestingSingle,
299+
Attributes: map[string]*configschema.Attribute{
300+
"attr": {
301+
Type: cty.String,
302+
Optional: true,
303+
},
304+
},
305+
},
306+
},
307+
},
308+
},
309+
Nesting: configschema.NestingSingle,
310+
MinItems: 1,
311+
MaxItems: 1,
312+
},
313+
}
314+
315+
},
316+
assertValidateDiags: func(t *testing.T, diags tfdiags.Diagnostics) {
317+
tfdiags.AssertDiagnosticCount(t, diags, 1)
318+
var exp tfdiags.Diagnostics
319+
exp = exp.Append(&hcl.Diagnostic{
320+
Summary: "Missing config block",
321+
Detail: "A block of type \"config\" is required here.",
322+
Subject: diags[0].Source().Subject.ToHCL().Ptr(),
323+
})
324+
tfdiags.AssertDiagnosticsMatch(t, diags, exp)
325+
},
326+
},
327+
{
328+
name: "with empty optional config",
266329
mainConfig: `
267330
terraform {
268331
required_providers {
@@ -283,6 +346,30 @@ func TestContext2Plan_queryList(t *testing.T) {
283346
provider = test
284347
}
285348
`,
349+
transformSchema: func(schema *providers.GetProviderSchemaResponse) {
350+
schema.ListResourceTypes["test_resource"].Body.BlockTypes = map[string]*configschema.NestedBlock{
351+
"config": {
352+
Block: configschema.Block{
353+
Attributes: map[string]*configschema.Attribute{
354+
"filter": {
355+
Optional: true,
356+
NestedType: &configschema.Object{
357+
Nesting: configschema.NestingSingle,
358+
Attributes: map[string]*configschema.Attribute{
359+
"attr": {
360+
Type: cty.String,
361+
Optional: true,
362+
},
363+
},
364+
},
365+
},
366+
},
367+
},
368+
Nesting: configschema.NestingSingle,
369+
},
370+
}
371+
372+
},
286373
listResourceFn: func(request providers.ListResourceRequest) providers.ListResourceResponse {
287374
madeUp := []cty.Value{
288375
cty.ObjectVal(map[string]cty.Value{"instance_type": cty.StringVal("ami-123456")}),
@@ -397,10 +484,17 @@ func TestContext2Plan_queryList(t *testing.T) {
397484
}
398485
}
399486
`,
400-
diagCount: 1,
401-
expectedErrMsg: []string{
402-
"Invalid list resource traversal",
403-
"The first step in the traversal for a list resource must be an attribute \"data\"",
487+
assertValidateDiags: func(t *testing.T, diags tfdiags.Diagnostics) {
488+
tfdiags.AssertDiagnosticCount(t, diags, 1)
489+
var exp tfdiags.Diagnostics
490+
exp = exp.Append(&hcl.Diagnostic{
491+
Severity: hcl.DiagError,
492+
Summary: "Invalid list resource traversal",
493+
Detail: "The first step in the traversal for a list resource must be an attribute \"data\".",
494+
Subject: diags[0].Source().Subject.ToHCL().Ptr(),
495+
})
496+
497+
tfdiags.AssertDiagnosticsMatch(t, diags, exp)
404498
},
405499
},
406500
{
@@ -428,9 +522,18 @@ func TestContext2Plan_queryList(t *testing.T) {
428522
}
429523
}
430524
`,
431-
diagCount: 1,
432-
expectedErrMsg: []string{
433-
"A list resource \"non_existent\" \"attr\" has not been declared in the root module.",
525+
assertValidateDiags: func(t *testing.T, diags tfdiags.Diagnostics) {
526+
tfdiags.AssertDiagnosticCount(t, diags, 1)
527+
var exp tfdiags.Diagnostics
528+
529+
exp = exp.Append(&hcl.Diagnostic{
530+
Severity: hcl.DiagError,
531+
Summary: "Reference to undeclared resource",
532+
Detail: "A list resource \"non_existent\" \"attr\" has not been declared in the root module.",
533+
Subject: diags[0].Source().Subject.ToHCL().Ptr(),
534+
})
535+
536+
tfdiags.AssertDiagnosticsMatch(t, diags, exp)
434537
},
435538
},
436539
{
@@ -469,9 +572,18 @@ func TestContext2Plan_queryList(t *testing.T) {
469572
}
470573
}
471574
`,
472-
diagCount: 1,
473-
expectedErrMsg: []string{
474-
"Unsupported attribute: This object has no argument, nested block, or exported attribute named \"invalid_attr\".",
575+
assertValidateDiags: func(t *testing.T, diags tfdiags.Diagnostics) {
576+
tfdiags.AssertDiagnosticCount(t, diags, 1)
577+
var exp tfdiags.Diagnostics
578+
579+
exp = exp.Append(&hcl.Diagnostic{
580+
Severity: hcl.DiagError,
581+
Summary: "Unsupported attribute",
582+
Detail: "This object has no argument, nested block, or exported attribute named \"invalid_attr\".",
583+
Subject: diags[0].Source().Subject.ToHCL().Ptr(),
584+
})
585+
586+
tfdiags.AssertDiagnosticsMatch(t, diags, exp)
475587
},
476588
listResourceFn: func(request providers.ListResourceRequest) providers.ListResourceResponse {
477589
madeUp := []cty.Value{
@@ -540,9 +652,14 @@ func TestContext2Plan_queryList(t *testing.T) {
540652
}
541653
}
542654
`,
543-
diagCount: 1,
544-
expectedErrMsg: []string{
545-
"Cycle: list.test_resource",
655+
assertValidateDiags: func(t *testing.T, diags tfdiags.Diagnostics) {
656+
tfdiags.AssertDiagnosticCount(t, diags, 1)
657+
if !strings.Contains(diags[0].Description().Summary, "Cycle: list.test_resource") {
658+
t.Errorf("Expected error message to contain 'Cycle: list.test_resource', got %q", diags[0].Description().Summary)
659+
}
660+
if diags[0].Severity() != tfdiags.Error {
661+
t.Errorf("Expected error severity to be Error, got %s", diags[0].Severity())
662+
}
546663
},
547664
},
548665
{
@@ -661,7 +778,7 @@ func TestContext2Plan_queryList(t *testing.T) {
661778
},
662779
{
663780
// Test list reference with index but without data field
664-
name: "list reference with index but without data field",
781+
name: "list reference as for_each",
665782
mainConfig: `
666783
terraform {
667784
required_providers {
@@ -784,6 +901,9 @@ func TestContext2Plan_queryList(t *testing.T) {
784901
provider := testProvider("test")
785902
provider.ConfigureProvider(providers.ConfigureProviderRequest{})
786903
provider.GetProviderSchemaResponse = getListProviderSchemaResp()
904+
if tc.transformSchema != nil {
905+
tc.transformSchema(provider.GetProviderSchemaResponse)
906+
}
787907
var requestConfigs = make(map[string]cty.Value)
788908
provider.ListResourceFn = func(request providers.ListResourceRequest) providers.ListResourceResponse {
789909
if request.Config.IsNull() || request.Config.GetAttr("config").IsNull() {
@@ -804,15 +924,20 @@ func TestContext2Plan_queryList(t *testing.T) {
804924
})
805925
tfdiags.AssertNoDiagnostics(t, diags)
806926

927+
diags = ctx.Validate(mod, &ValidateOpts{})
928+
if tc.assertValidateDiags != nil {
929+
tc.assertValidateDiags(t, diags)
930+
return
931+
} else {
932+
tfdiags.AssertNoDiagnostics(t, diags)
933+
}
934+
807935
plan, diags := ctx.Plan(mod, states.NewState(), &PlanOpts{
808936
Mode: plans.NormalMode,
809937
SetVariables: testInputValuesUnset(mod.Module.Variables),
810938
Query: true,
811939
GenerateConfigPath: tc.generatedPath,
812940
})
813-
if len(diags) != tc.diagCount {
814-
t.Fatalf("expected %d diagnostics, got %d \n -diags: %s", tc.diagCount, len(diags), diags)
815-
}
816941

817942
if tc.assertChanges != nil {
818943
sch, err := ctx.Schemas(mod, states.NewState())
@@ -821,15 +946,6 @@ func TestContext2Plan_queryList(t *testing.T) {
821946
}
822947
tc.assertChanges(sch.Providers[providerAddr], plan.Changes)
823948
}
824-
825-
if tc.diagCount > 0 {
826-
for _, err := range tc.expectedErrMsg {
827-
if !strings.Contains(diags.Err().Error(), err) {
828-
t.Fatalf("expected error message %q, but got %q", err, diags.Err().Error())
829-
}
830-
}
831-
}
832-
833949
})
834950
}
835951
}

internal/terraform/evaluate_valid.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ func staticValidateResourceReference(modCfg *configs.Config, addr addrs.Resource
303303
diags = diags.Append(&hcl.Diagnostic{
304304
Severity: hcl.DiagError,
305305
Summary: `Invalid list resource traversal`,
306-
Detail: fmt.Sprintf(`The first step in the traversal for a %s resource must be an attribute "data", but got %q instead.`, modeAdjective, remain[0]),
306+
Detail: fmt.Sprintf(`The first step in the traversal for a %s resource must be an attribute "data".`, modeAdjective),
307307
Subject: rng.ToHCL().Ptr(),
308308
})
309309
return diags
@@ -315,7 +315,7 @@ func staticValidateResourceReference(modCfg *configs.Config, addr addrs.Resource
315315
diags = diags.Append(&hcl.Diagnostic{
316316
Severity: hcl.DiagError,
317317
Summary: `Invalid list resource traversal`,
318-
Detail: fmt.Sprintf(`The second step in the traversal for a %s resource must be an index, but got %q instead.`, modeAdjective, remain[0]),
318+
Detail: fmt.Sprintf(`The second step in the traversal for a %s resource must be an index.`, modeAdjective),
319319
Subject: rng.ToHCL().Ptr(),
320320
})
321321
return diags
@@ -331,7 +331,7 @@ func staticValidateResourceReference(modCfg *configs.Config, addr addrs.Resource
331331
diags = diags.Append(&hcl.Diagnostic{
332332
Severity: hcl.DiagError,
333333
Summary: `Invalid list resource traversal`,
334-
Detail: fmt.Sprintf(`The third step in the traversal for a %s resource must be an attribute "state" or "identity", but got %q instead.`, modeAdjective, remain[0]),
334+
Detail: fmt.Sprintf(`The third step in the traversal for a %s resource must be an attribute "state" or "identity".`, modeAdjective),
335335
Subject: rng.ToHCL().Ptr(),
336336
})
337337
return diags
@@ -340,7 +340,7 @@ func staticValidateResourceReference(modCfg *configs.Config, addr addrs.Resource
340340
diags = diags.Append(&hcl.Diagnostic{
341341
Severity: hcl.DiagError,
342342
Summary: `Invalid list resource traversal`,
343-
Detail: fmt.Sprintf(`The third step in the traversal for a %s resource must be an attribute "state" or "identity", but got %q instead.`, modeAdjective, stateOrIdent.Name),
343+
Detail: fmt.Sprintf(`The third step in the traversal for a %s resource must be an attribute "state" or "identity".`, modeAdjective),
344344
Subject: rng.ToHCL().Ptr(),
345345
})
346346
return diags

0 commit comments

Comments
 (0)