configs/configupgrade: Upgrading of simple nested blocks

This commit is contained in:
Martin Atkins 2018-11-30 22:49:48 -08:00
parent 39c3e7112f
commit f96d702d4f
8 changed files with 145 additions and 21 deletions

View File

@ -0,0 +1,10 @@
resource "test_instance" "foo" {
network = [
{
cidr_block = "10.1.0.0/16"
},
{
cidr_block = "10.2.0.0/16"
},
]
}

View File

@ -0,0 +1,8 @@
resource "test_instance" "foo" {
network {
cidr_block = "10.1.0.0/16"
}
network {
cidr_block = "10.2.0.0/16"
}
}

View File

@ -0,0 +1,3 @@
terraform {
required_version = ">= 0.12"
}

View File

@ -0,0 +1,5 @@
resource "test_instance" "foo" {
network = {
cidr_block = "10.1.0.0/16"
}
}

View File

@ -0,0 +1,5 @@
resource "test_instance" "foo" {
network {
cidr_block = "10.1.0.0/16"
}
}

View File

@ -0,0 +1,3 @@
terraform {
required_version = ">= 0.12"
}

View File

@ -123,12 +123,102 @@ func attributeRule(filename string, wantTy cty.Type, an *analysis, upgradeExpr f
}
}
func nestedBlockRule(filename string, nestedRules bodyContentRules, an *analysis) bodyItemRule {
func nestedBlockRule(filename string, nestedRules bodyContentRules, an *analysis, adhocComments *commentQueue) bodyItemRule {
return func(buf *bytes.Buffer, blockAddr string, item *hcl1ast.ObjectItem) tfdiags.Diagnostics {
// TODO: Deal with this.
// In particular we need to handle the tricky case where
// a user attempts to treat a block type name like it's
// an attribute, by producing a "dynamic" block.
// This simpler nestedBlockRule is for contexts where the special
// "dynamic" block type is not accepted and so only HCL1 object
// constructs can be accepted. Attempts to assign arbitrary HIL
// expressions will be rejected as errors.
var diags tfdiags.Diagnostics
declRange := hcl1PosRange(filename, item.Keys[0].Pos())
blockType := item.Keys[0].Token.Value().(string)
labels := make([]string, len(item.Keys)-1)
for i, key := range item.Keys[1:] {
labels[i] = key.Token.Value().(string)
}
var blockItems []*hcl1ast.ObjectType
switch val := item.Val.(type) {
case *hcl1ast.ObjectType:
blockItems = []*hcl1ast.ObjectType{val}
case *hcl1ast.ListType:
for _, node := range val.List {
switch listItem := node.(type) {
case *hcl1ast.ObjectType:
blockItems = append(blockItems, listItem)
default:
diags = diags.Append(&hcl2.Diagnostic{
Severity: hcl2.DiagError,
Summary: "Invalid value for nested block",
Detail: fmt.Sprintf("In %s the name %q is a nested block type, so any value assigned to it must be an object.", blockAddr, blockType),
Subject: hcl1PosRange(filename, node.Pos()).Ptr(),
})
}
}
default:
diags = diags.Append(&hcl2.Diagnostic{
Severity: hcl2.DiagError,
Summary: "Invalid value for nested block",
Detail: fmt.Sprintf("In %s the name %q is a nested block type, so any value assigned to it must be an object.", blockAddr, blockType),
Subject: &declRange,
})
return diags
}
for _, blockItem := range blockItems {
printBlockOpen(buf, blockType, labels, item.LineComment)
bodyDiags := upgradeBlockBody(
filename, fmt.Sprintf("%s.%s", blockAddr, blockType), buf,
blockItem.List.Items, nestedRules, adhocComments,
)
diags = diags.Append(bodyDiags)
buf.WriteString("}\n")
}
return diags
}
}
func nestedBlockRuleWithDynamic(filename string, nestedRules bodyContentRules, an *analysis, adhocComments *commentQueue) bodyItemRule {
return func(buf *bytes.Buffer, blockAddr string, item *hcl1ast.ObjectItem) tfdiags.Diagnostics {
// In Terraform v0.11 it was possible in some cases to trick Terraform
// and providers into accepting HCL's attribute syntax and some HIL
// expressions in places where blocks or sequences of blocks were
// expected, since the information about the heritage of the values
// was lost during decoding and interpolation.
//
// In order to avoid all of the weird rough edges that resulted from
// those misinterpretations, Terraform v0.12 is stricter and requires
// the use of block syntax for blocks in all cases. However, because
// various abuses of attribute syntax _did_ work (with some caveats)
// in v0.11 we will upgrade them as best we can to use proper block
// syntax.
//
// There are a few different permutations supported by this code:
//
// - Assigning a single HCL1 "object" using attribute syntax. This is
// straightforward to migrate just by dropping the equals sign.
//
// - Assigning a HCL1 list of objects using attribute syntax. Each
// object in that list can be translated to a separate block.
//
// - Assigning a HCL1 list containing HIL expressions that evaluate
// to maps. This is a hard case because we can't know the internal
// structure of those maps during static analysis, and so we must
// generate a worst-case dynamic block structure for it.
//
// - Assigning a single HIL expression that evaluates to a list of
// maps. This is just like the previous case except additionally
// we cannot even predict the number of generated blocks, so we must
// generate a single "dynamic" block to iterate over the list at
// runtime.
// TODO: Implement this
hcl1printer.Fprint(buf, item)
buf.WriteByte('\n')
return nil
@ -140,7 +230,7 @@ func nestedBlockRule(filename string, nestedRules bodyContentRules, an *analysis
// callers can safely mutate the result in order to impose custom rules
// in addition to or instead of those created by default, for situations
// where schema-based and predefined items mix in a single body.
func schemaDefaultBodyRules(filename string, schema *configschema.Block, an *analysis) bodyContentRules {
func schemaDefaultBodyRules(filename string, schema *configschema.Block, an *analysis, adhocComments *commentQueue) bodyContentRules {
ret := make(bodyContentRules)
if schema == nil {
// Shouldn't happen in any real case, but often crops up in tests
@ -152,8 +242,8 @@ func schemaDefaultBodyRules(filename string, schema *configschema.Block, an *ana
ret[name] = normalAttributeRule(filename, attrS.Type, an)
}
for name, blockS := range schema.BlockTypes {
nestedRules := schemaDefaultBodyRules(filename, &blockS.Block, an)
ret[name] = nestedBlockRule(filename, nestedRules, an)
nestedRules := schemaDefaultBodyRules(filename, &blockS.Block, an, adhocComments)
ret[name] = nestedBlockRule(filename, nestedRules, an, adhocComments)
}
return ret
@ -164,7 +254,7 @@ func schemaDefaultBodyRules(filename string, schema *configschema.Block, an *ana
// callers can safely mutate the result in order to impose custom rules
// in addition to or instead of those created by default, for situations
// where schema-based and predefined items mix in a single body.
func schemaNoInterpBodyRules(filename string, schema *configschema.Block, an *analysis) bodyContentRules {
func schemaNoInterpBodyRules(filename string, schema *configschema.Block, an *analysis, adhocComments *commentQueue) bodyContentRules {
ret := make(bodyContentRules)
if schema == nil {
// Shouldn't happen in any real case, but often crops up in tests
@ -176,8 +266,8 @@ func schemaNoInterpBodyRules(filename string, schema *configschema.Block, an *an
ret[name] = noInterpAttributeRule(filename, attrS.Type, an)
}
for name, blockS := range schema.BlockTypes {
nestedRules := schemaDefaultBodyRules(filename, &blockS.Block, an)
ret[name] = nestedBlockRule(filename, nestedRules, an)
nestedRules := schemaDefaultBodyRules(filename, &blockS.Block, an, adhocComments)
ret[name] = nestedBlockRule(filename, nestedRules, an, adhocComments)
}
return ret

View File

@ -161,7 +161,7 @@ func (u *Upgrader) upgradeNativeSyntaxFile(filename string, src []byte, an *anal
}),
}
log.Printf("[TRACE] configupgrade: Upgrading var.%s at %s", labels[0], declRange)
bodyDiags := u.upgradeBlockBody(filename, fmt.Sprintf("var.%s", labels[0]), &buf, body.List.Items, rules, adhocComments)
bodyDiags := upgradeBlockBody(filename, fmt.Sprintf("var.%s", labels[0]), &buf, body.List.Items, rules, adhocComments)
diags = diags.Append(bodyDiags)
buf.WriteString("}\n\n")
@ -186,7 +186,7 @@ func (u *Upgrader) upgradeNativeSyntaxFile(filename string, src []byte, an *anal
"depends_on": dependsOnAttributeRule(filename, an),
}
log.Printf("[TRACE] configupgrade: Upgrading output.%s at %s", labels[0], declRange)
bodyDiags := u.upgradeBlockBody(filename, fmt.Sprintf("output.%s", labels[0]), &buf, body.List.Items, rules, adhocComments)
bodyDiags := upgradeBlockBody(filename, fmt.Sprintf("output.%s", labels[0]), &buf, body.List.Items, rules, adhocComments)
diags = diags.Append(bodyDiags)
buf.WriteString("}\n\n")
@ -307,11 +307,11 @@ func (u *Upgrader) upgradeNativeSyntaxResource(filename string, buf *bytes.Buffe
}
labels := []string{addr.Type, addr.Name}
rules := schemaDefaultBodyRules(filename, schema, an)
rules := schemaDefaultBodyRules(filename, schema, an, adhocComments)
printComments(buf, item.LeadComment)
printBlockOpen(buf, blockType, labels, item.LineComment)
bodyDiags := u.upgradeBlockBody(filename, addr.String(), buf, body.List.Items, rules, adhocComments)
bodyDiags := upgradeBlockBody(filename, addr.String(), buf, body.List.Items, rules, adhocComments)
diags = diags.Append(bodyDiags)
buf.WriteString("}\n\n")
@ -330,11 +330,11 @@ func (u *Upgrader) upgradeNativeSyntaxProvider(filename string, buf *bytes.Buffe
panic(fmt.Sprintf("missing schema for provider type %q", typeName))
}
schema := providerSchema.Provider
rules := schemaDefaultBodyRules(filename, schema, an)
rules := schemaDefaultBodyRules(filename, schema, an, adhocComments)
printComments(buf, item.LeadComment)
printBlockOpen(buf, "provider", []string{typeName}, item.LineComment)
bodyDiags := u.upgradeBlockBody(filename, fmt.Sprintf("provider.%s", typeName), buf, body.List.Items, rules, adhocComments)
bodyDiags := upgradeBlockBody(filename, fmt.Sprintf("provider.%s", typeName), buf, body.List.Items, rules, adhocComments)
diags = diags.Append(bodyDiags)
buf.WriteString("}\n\n")
@ -375,13 +375,13 @@ func (u *Upgrader) upgradeNativeSyntaxTerraformBlock(filename string, buf *bytes
}
be := beFn()
schema := be.ConfigSchema()
rules := schemaNoInterpBodyRules(filename, schema, an)
rules := schemaNoInterpBodyRules(filename, schema, an, adhocComments)
body := item.Val.(*hcl1ast.ObjectType)
printComments(buf, item.LeadComment)
printBlockOpen(buf, "backend", []string{typeName}, item.LineComment)
bodyDiags := u.upgradeBlockBody(filename, fmt.Sprintf("terraform.backend.%s", typeName), buf, body.List.Items, rules, adhocComments)
bodyDiags := upgradeBlockBody(filename, fmt.Sprintf("terraform.backend.%s", typeName), buf, body.List.Items, rules, adhocComments)
diags = diags.Append(bodyDiags)
buf.WriteString("}\n")
@ -391,14 +391,14 @@ func (u *Upgrader) upgradeNativeSyntaxTerraformBlock(filename string, buf *bytes
printComments(buf, item.LeadComment)
printBlockOpen(buf, "terraform", nil, item.LineComment)
bodyDiags := u.upgradeBlockBody(filename, "terraform", buf, body.List.Items, rules, adhocComments)
bodyDiags := upgradeBlockBody(filename, "terraform", buf, body.List.Items, rules, adhocComments)
diags = diags.Append(bodyDiags)
buf.WriteString("}\n\n")
return diags
}
func (u *Upgrader) upgradeBlockBody(filename string, blockAddr string, buf *bytes.Buffer, args []*hcl1ast.ObjectItem, rules bodyContentRules, adhocComments *commentQueue) tfdiags.Diagnostics {
func upgradeBlockBody(filename string, blockAddr string, buf *bytes.Buffer, args []*hcl1ast.ObjectItem, rules bodyContentRules, adhocComments *commentQueue) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
for i, arg := range args {