add nested config block to list block (#37229)

stack-cli-download-in-plugin-cache
Samsondeen 11 months ago committed by GitHub
parent 49a4ad3b26
commit cacd7861e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -161,7 +161,7 @@ func TestParserLoadConfigDirWithQueries(t *testing.T) {
{
name: "simple",
directory: "testdata/query-files/valid/simple",
listResources: 2,
listResources: 3,
allowExperiments: true,
},
{

@ -150,6 +150,36 @@ func decodeQueryListBlock(block *hcl.Block) (*Resource, hcl.Diagnostics) {
r.List.IncludeResource = attr.Expr
}
// verify that the list block has a config block
content, contentDiags = block.Body.Content(&hcl.BodySchema{
Attributes: QueryListResourceBlockSchema.Attributes,
Blocks: []hcl.BlockHeaderSchema{
{Type: "config"},
},
})
diags = append(diags, contentDiags...)
var configBlock hcl.Body
for _, block := range content.Blocks {
switch block.Type {
case "config":
if configBlock != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Duplicate config block",
Detail: "A list block must contain only one nested \"config\" block.",
Subject: block.DefRange.Ptr(),
})
continue
}
configBlock = block.Body
default:
// Should not get here because the above should cover all
// block types declared in the schema.
panic(fmt.Sprintf("unhandled block type %q", block.Type))
}
}
return &r, diags
}

@ -1,7 +1,10 @@
list "test_resource" "test" {
provider = azurerm
count = 1
tags = {
Name = "test"
config {
tags = {
Name = "test"
}
}
}

@ -1,6 +1,8 @@
list "aws_instance" "test" {
count = 1
tags = {
Name = "test"
config {
tags = {
Name = "test"
}
}
}

@ -1,14 +1,18 @@
list "aws_instance" "test" {
provider = aws
count = 1
tags = {
Name = "test"
config {
tags = {
Name = "test"
}
}
}
list "aws_instance" "test2" {
provider = aws
count = 1
tags = {
Name = join("-", ["test2", list.aws_instance.test.data[0]])
config {
tags = {
Name = join("-", ["test2", list.aws_instance.test.data[0]])
}
}
}

@ -2,14 +2,21 @@ list "aws_instance" "test" {
provider = aws
count = 1
include_resource = true
tags = {
Name = "test"
config {
tags = {
Name = "test"
}
}
}
list "aws_instance" "test2" {
provider = aws
count = 1
tags = {
Name = join("-", ["test2", list.aws_instance.test.data[0]])
config {
tags = {
Name = join("-", ["test2", list.aws_instance.test.data[0]])
}
}
}
}
list "aws_instance" "test3" {
provider = aws
}

@ -376,7 +376,11 @@ func (p *GRPCProvider) ValidateListResourceConfig(r providers.ValidateListResour
}
configSchema := listResourceSchema.Body.BlockTypes["config"]
mp, err := msgpack.Marshal(r.Config, configSchema.ImpliedType())
config := cty.NullVal(configSchema.ImpliedType())
if r.Config.Type().HasAttribute("config") {
config = r.Config.GetAttr("config")
}
mp, err := msgpack.Marshal(config, configSchema.ImpliedType())
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp

@ -451,7 +451,43 @@ func TestGRPCProvider_ValidateListResourceConfig(t *testing.T) {
gomock.Any(),
).Return(&proto.ValidateListResourceConfig_Response{}, nil)
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"filter_attr": "value"})
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"config": map[string]interface{}{"filter_attr": "value"}})
resp := p.ValidateListResourceConfig(providers.ValidateListResourceConfigRequest{
TypeName: "list",
Config: cfg,
})
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_ValidateListResourceConfig_OptionalCfg(t *testing.T) {
ctrl := gomock.NewController(t)
client := mockproto.NewMockProviderClient(ctrl)
sch := providerProtoSchema()
sch.ListResourceSchemas["list"].Block.Attributes[0].Optional = true
sch.ListResourceSchemas["list"].Block.Attributes[0].Required = false
// we always need a GetSchema method
client.EXPECT().GetSchema(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(sch, nil)
// GetResourceIdentitySchemas is called as part of GetSchema
client.EXPECT().GetResourceIdentitySchemas(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(providerResourceIdentitySchemas(), nil)
p := &GRPCProvider{
client: client,
}
client.EXPECT().ValidateListResourceConfig(
gomock.Any(),
gomock.Any(),
).Return(&proto.ValidateListResourceConfig_Response{}, nil)
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{})
resp := p.ValidateListResourceConfig(providers.ValidateListResourceConfigRequest{
TypeName: "list",
Config: cfg,

@ -373,7 +373,11 @@ func (p *GRPCProvider) ValidateListResourceConfig(r providers.ValidateListResour
return resp
}
configSchema := listResourceSchema.Body.BlockTypes["config"]
mp, err := msgpack.Marshal(r.Config, configSchema.ImpliedType())
config := cty.NullVal(configSchema.ImpliedType())
if r.Config.Type().HasAttribute("config") {
config = r.Config.GetAttr("config")
}
mp, err := msgpack.Marshal(config, configSchema.ImpliedType())
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp

@ -458,7 +458,43 @@ func TestGRPCProvider_ValidateListResourceConfig(t *testing.T) {
gomock.Any(),
).Return(&proto.ValidateListResourceConfig_Response{}, nil)
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"filter_attr": "value"})
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{"config": map[string]interface{}{"filter_attr": "value"}})
resp := p.ValidateListResourceConfig(providers.ValidateListResourceConfigRequest{
TypeName: "list",
Config: cfg,
})
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_ValidateListResourceConfig_OptionalCfg(t *testing.T) {
ctrl := gomock.NewController(t)
client := mockproto.NewMockProviderClient(ctrl)
sch := providerProtoSchema()
sch.ListResourceSchemas["list"].Block.Attributes[0].Optional = true
sch.ListResourceSchemas["list"].Block.Attributes[0].Required = false
// we always need a GetSchema method
client.EXPECT().GetProviderSchema(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(sch, nil)
// GetResourceIdentitySchemas is called as part of GetSchema
client.EXPECT().GetResourceIdentitySchemas(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(providerResourceIdentitySchemas(), nil)
p := &GRPCProvider{
client: client,
}
client.EXPECT().ValidateListResourceConfig(
gomock.Any(),
gomock.Any(),
).Return(&proto.ValidateListResourceConfig_Response{}, nil)
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{})
resp := p.ValidateListResourceConfig(providers.ValidateListResourceConfigRequest{
TypeName: "list",
Config: cfg,

@ -104,16 +104,20 @@ func TestContext2Plan_queryList(t *testing.T) {
list "test_resource" "test" {
provider = test
filter = {
attr = var.input
config {
filter = {
attr = var.input
}
}
}
list "test_resource" "test2" {
provider = test
filter = {
attr = list.test_resource.test.data[0].state.instance_type
config {
filter = {
attr = list.test_resource.test.data[0].state.instance_type
}
}
}
`,
@ -218,16 +222,20 @@ func TestContext2Plan_queryList(t *testing.T) {
count = 1
provider = test
filter = {
attr = var.input
config {
filter = {
attr = var.input
}
}
}
list "test_resource" "test2" {
provider = test
filter = {
attr = list.test_resource.test[0].data[0].state.instance_type
config {
filter = {
attr = list.test_resource.test[0].data[0].state.instance_type
}
}
}
`,
@ -330,16 +338,20 @@ func TestContext2Plan_queryList(t *testing.T) {
list "test_resource" "test" {
provider = test
filter = {
attr = var.input
config {
filter = {
attr = var.input
}
}
}
list "test_resource" "test2" {
provider = test
filter = {
attr = list.test_resource.test.state.instance_type
config {
filter = {
attr = list.test_resource.test.state.instance_type
}
}
}
`,
@ -366,8 +378,10 @@ func TestContext2Plan_queryList(t *testing.T) {
list "test_resource" "test" {
provider = test
filter = {
attr = list.non_existent.attr
config {
filter = {
attr = list.non_existent.attr
}
}
}
`,
@ -393,16 +407,20 @@ func TestContext2Plan_queryList(t *testing.T) {
list "test_resource" "test" {
provider = test
filter = {
attr = "valid"
config {
filter = {
attr = "valid"
}
}
}
list "test_resource" "another" {
provider = test
filter = {
attr = list.test_resource.test.data[0].state.invalid_attr
config {
filter = {
attr = list.test_resource.test.data[0].state.invalid_attr
}
}
}
`,
@ -458,16 +476,20 @@ func TestContext2Plan_queryList(t *testing.T) {
list "test_resource" "test1" {
provider = test
filter = {
attr = list.test_resource.test2.data[0].state.id
config {
filter = {
attr = list.test_resource.test2.data[0].state.id
}
}
}
list "test_resource" "test2" {
provider = test
filter = {
attr = list.test_resource.test1.data[0].state.id
config {
filter = {
attr = list.test_resource.test1.data[0].state.id
}
}
}
`,
@ -498,16 +520,20 @@ func TestContext2Plan_queryList(t *testing.T) {
list "test_resource" "test1" {
provider = test
filter = {
attr = var.test_var
config {
filter = {
attr = var.test_var
}
}
}
list "test_resource" "test2" {
provider = test
filter = {
attr = length(list.test_resource.test1.data) > 0 ? list.test_resource.test1.data[0].state.instance_type : var.test_var
config {
filter = {
attr = length(list.test_resource.test1.data) > 0 ? list.test_resource.test1.data[0].state.instance_type : var.test_var
}
}
}
`,
@ -606,8 +632,10 @@ func TestContext2Plan_queryList(t *testing.T) {
for_each = toset(["foo", "bar"])
provider = test
filter = {
attr = each.value
config {
filter = {
attr = each.value
}
}
}
@ -615,8 +643,10 @@ func TestContext2Plan_queryList(t *testing.T) {
provider = test
for_each = list.test_resource.test1
filter = {
attr = each.value.data[0].state.instance_type
config {
filter = {
attr = each.value.data[0].state.instance_type
}
}
}
`,

@ -3105,6 +3105,29 @@ func TestContext2Validate_queryList(t *testing.T) {
diagCount int
expectedErrMsg []string
}{
{
name: "valid simple block",
mainConfig: `
terraform {
required_providers {
test = {
source = "hashicorp/test"
version = "1.0.0"
}
}
}
`,
queryConfig: `
variable "input" {
type = string
default = "foo"
}
list "test_resource" "test" {
provider = test
}
`,
},
{
name: "valid list reference",
mainConfig: `
@ -3126,16 +3149,20 @@ func TestContext2Validate_queryList(t *testing.T) {
list "test_resource" "test" {
provider = test
filter = {
attr = var.input
config {
filter = {
attr = var.input
}
}
}
list "test_resource" "test2" {
provider = test
filter = {
attr = list.test_resource.test.data[0].state.instance_type
config {
filter = {
attr = list.test_resource.test.data[0].state.instance_type
}
}
}
`,
@ -3162,16 +3189,20 @@ func TestContext2Validate_queryList(t *testing.T) {
count = 1
provider = test
filter = {
attr = var.input
config {
filter = {
attr = var.input
}
}
}
list "test_resource" "test2" {
provider = test
filter = {
attr = list.test_resource.test[0].data[0].state.instance_type
config {
filter = {
attr = list.test_resource.test[0].data[0].state.instance_type
}
}
}
`,
@ -3197,16 +3228,20 @@ func TestContext2Validate_queryList(t *testing.T) {
list "test_resource" "test" {
provider = test
filter = {
attr = var.input
config {
filter = {
attr = var.input
}
}
}
list "test_resource" "test2" {
provider = test
filter = {
attr = list.test_resource.test.state.instance_type
config {
filter = {
attr = list.test_resource.test.state.instance_type
}
}
}
`,
@ -3248,8 +3283,10 @@ func TestContext2Validate_queryList(t *testing.T) {
list "test_resource" "test" {
provider = test
filter = {
attr = var.input
config {
filter = {
attr = var.input
}
}
}
`,
@ -3288,8 +3325,10 @@ func TestContext2Validate_queryList(t *testing.T) {
list "test_resource" "test" {
provider = test
filter = {
attr = resource.list.test_resource.attr
config {
filter = {
attr = resource.list.test_resource.attr
}
}
}
`,
@ -3311,8 +3350,10 @@ func TestContext2Validate_queryList(t *testing.T) {
list "test_resource" "test" {
provider = test
filter = {
attr = list.non_existent.attr
config {
filter = {
attr = list.non_existent.attr
}
}
}
`,
@ -3338,16 +3379,20 @@ func TestContext2Validate_queryList(t *testing.T) {
list "test_resource" "test" {
provider = test
filter = {
attr = "valid"
config {
filter = {
attr = "valid"
}
}
}
list "test_resource" "another" {
provider = test
filter = {
attr = list.test_resource.test.data[0].state.invalid_attr
config {
filter = {
attr = list.test_resource.test.data[0].state.invalid_attr
}
}
}
`,
@ -3372,16 +3417,20 @@ func TestContext2Validate_queryList(t *testing.T) {
list "test_resource" "test1" {
provider = test
filter = {
attr = list.test_resource.test2.data[0].state.id
config {
filter = {
attr = list.test_resource.test2.data[0].state.id
}
}
}
list "test_resource" "test2" {
provider = test
filter = {
attr = list.test_resource.test1.data[0].state.id
config {
filter = {
attr = list.test_resource.test1.data[0].state.id
}
}
}
`,
@ -3412,16 +3461,20 @@ func TestContext2Validate_queryList(t *testing.T) {
list "test_resource" "test1" {
provider = test
filter = {
attr = var.test_var
config {
filter = {
attr = var.test_var
}
}
}
list "test_resource" "test2" {
provider = test
filter = {
attr = length(list.test_resource.test1.data) > 0 ? list.test_resource.test1.data[0].state.instance_type : var.test_var
config {
filter = {
attr = length(list.test_resource.test1.data) > 0 ? list.test_resource.test1.data[0].state.instance_type : var.test_var
}
}
}
`,
@ -3444,8 +3497,10 @@ func TestContext2Validate_queryList(t *testing.T) {
for_each = toset(["foo", "bar"])
provider = test
filter = {
attr = each.value
config {
filter = {
attr = each.value
}
}
}
@ -3453,8 +3508,10 @@ func TestContext2Validate_queryList(t *testing.T) {
provider = test
for_each = list.test_resource.test1
filter = {
attr = each.value.data[0].instance_type
config {
filter = {
attr = each.value.data[0].instance_type
}
}
}
`,
@ -3480,6 +3537,7 @@ func TestContext2Validate_queryList(t *testing.T) {
PreloadedProviderSchemas: map[addrs.Provider]providers.ProviderSchema{
providerAddr: *provider.GetProviderSchemaResponse,
},
Parallelism: 1,
})
tfdiags.AssertNoDiagnostics(t, diags)
@ -3540,24 +3598,34 @@ func getTestProvider() *testing_provider.MockProvider {
// getQueryTestSchema returns a schema for query tests with a filter attribute
func getQueryTestSchema() *configschema.Block {
return &configschema.Block{
body := &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"filter": {
Required: true,
NestedType: &configschema.Object{
Nesting: configschema.NestingSingle,
"data": {
Type: cty.DynamicPseudoType,
Computed: true,
},
},
BlockTypes: map[string]*configschema.NestedBlock{
"config": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"attr": {
Type: cty.String,
"filter": {
Required: true,
NestedType: &configschema.Object{
Nesting: configschema.NestingSingle,
Attributes: map[string]*configschema.Attribute{
"attr": {
Type: cty.String,
Required: true,
},
},
},
},
},
},
},
"data": {
Computed: true,
Type: cty.DynamicPseudoType,
Nesting: configschema.NestingSingle,
},
},
}
return body
}

@ -38,15 +38,15 @@ func (n *NodePlannableResourceInstance) listResourceExecute(ctx EvalContext) (di
// evaluate the list config block
var configDiags tfdiags.Diagnostics
configVal, _, configDiags := ctx.EvaluateBlock(config.Config, n.Schema.Body, nil, keyData)
blockVal, _, configDiags := ctx.EvaluateBlock(config.Config, n.Schema.Body, nil, keyData)
diags = diags.Append(configDiags)
if diags.HasErrors() {
return diags
}
// Unmark before sending to provider
unmarkedConfigVal, _ := configVal.UnmarkDeepWithPaths()
configKnown := configVal.IsWhollyKnown()
unmarkedBlockVal, _ := blockVal.UnmarkDeepWithPaths()
configKnown := blockVal.IsWhollyKnown()
if !configKnown {
diags = diags.Append(fmt.Errorf("config is not known"))
return diags
@ -56,7 +56,7 @@ func (n *NodePlannableResourceInstance) listResourceExecute(ctx EvalContext) (di
validateResp := provider.ValidateListResourceConfig(
providers.ValidateListResourceConfigRequest{
TypeName: n.Config.Type,
Config: unmarkedConfigVal,
Config: unmarkedBlockVal,
},
)
diags = diags.Append(validateResp.Diagnostics.InConfigBody(config.Config, n.Addr.String()))
@ -68,7 +68,7 @@ func (n *NodePlannableResourceInstance) listResourceExecute(ctx EvalContext) (di
// to actually call the provider to list the data.
resp := provider.ListResource(providers.ListResourceRequest{
TypeName: n.Config.Type,
Config: unmarkedConfigVal,
Config: unmarkedBlockVal,
})
if resp.Diagnostics != nil {
return diags.Append(resp.Diagnostics.InConfigBody(config.Config, n.Addr.String()))

@ -482,16 +482,16 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
return diags
}
configVal, _, valDiags := ctx.EvaluateBlock(n.Config.Config, schema.Body, nil, keyData)
blockVal, _, valDiags := ctx.EvaluateBlock(n.Config.Config, schema.Body, nil, keyData)
diags = diags.Append(valDiags)
if valDiags.HasErrors() {
return diags
}
// Use unmarked value for validate request
unmarkedConfigVal, _ := configVal.UnmarkDeep()
unmarkedBlockVal, _ := blockVal.UnmarkDeep()
req := providers.ValidateListResourceConfigRequest{
TypeName: n.Config.Type,
Config: unmarkedConfigVal,
Config: unmarkedBlockVal,
}
resp := provider.ValidateListResourceConfig(req)

Loading…
Cancel
Save