diff --git a/configs/configupgrade/test-fixtures/valid/noop/input/resources.tf b/configs/configupgrade/test-fixtures/valid/noop/input/resources.tf index f738f6971..29ceef046 100644 --- a/configs/configupgrade/test-fixtures/valid/noop/input/resources.tf +++ b/configs/configupgrade/test-fixtures/valid/noop/input/resources.tf @@ -3,7 +3,7 @@ resource "test_instance" "example" { connection { host = "127.0.0.1" } - provisioner "local-exec" { + provisioner "test" { connection { host = "127.0.0.2" } diff --git a/configs/configupgrade/test-fixtures/valid/noop/want/resources.tf b/configs/configupgrade/test-fixtures/valid/noop/want/resources.tf index f738f6971..29ceef046 100644 --- a/configs/configupgrade/test-fixtures/valid/noop/want/resources.tf +++ b/configs/configupgrade/test-fixtures/valid/noop/want/resources.tf @@ -3,7 +3,7 @@ resource "test_instance" "example" { connection { host = "127.0.0.1" } - provisioner "local-exec" { + provisioner "test" { connection { host = "127.0.0.2" } diff --git a/configs/configupgrade/test-fixtures/valid/provisioner/input/provisioner.tf b/configs/configupgrade/test-fixtures/valid/provisioner/input/provisioner.tf new file mode 100644 index 000000000..815dd31f4 --- /dev/null +++ b/configs/configupgrade/test-fixtures/valid/provisioner/input/provisioner.tf @@ -0,0 +1,8 @@ +resource "test_instance" "foo" { + provisioner "test" { + commands = "${list("a", "b", "c")}" + + when = "create" + on_failure = "fail" + } +} diff --git a/configs/configupgrade/test-fixtures/valid/provisioner/want/provisioner.tf b/configs/configupgrade/test-fixtures/valid/provisioner/want/provisioner.tf new file mode 100644 index 000000000..55fbbfbe8 --- /dev/null +++ b/configs/configupgrade/test-fixtures/valid/provisioner/want/provisioner.tf @@ -0,0 +1,8 @@ +resource "test_instance" "foo" { + provisioner "test" { + commands = ["a", "b", "c"] + + when = create + on_failure = fail + } +} diff --git a/configs/configupgrade/test-fixtures/valid/provisioner/want/versions.tf b/configs/configupgrade/test-fixtures/valid/provisioner/want/versions.tf new file mode 100644 index 000000000..d9b6f790b --- /dev/null +++ b/configs/configupgrade/test-fixtures/valid/provisioner/want/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.12" +} diff --git a/configs/configupgrade/upgrade_body.go b/configs/configupgrade/upgrade_body.go index d091916ac..5baa2108d 100644 --- a/configs/configupgrade/upgrade_body.go +++ b/configs/configupgrade/upgrade_body.go @@ -7,6 +7,7 @@ import ( "strings" hcl1ast "github.com/hashicorp/hcl/hcl/ast" + hcl1printer "github.com/hashicorp/hcl/hcl/printer" hcl1token "github.com/hashicorp/hcl/hcl/token" hcl2 "github.com/hashicorp/hcl2/hcl" hcl2syntax "github.com/hashicorp/hcl2/hcl/hclsyntax" @@ -554,3 +555,69 @@ func lifecycleBlockBodyRules(filename string, an *analysis) bodyContentRules { }, } } + +func provisionerBlockRule(filename string, an *analysis, adhocComments *commentQueue) bodyItemRule { + // Unlike some other examples above, this is a rule for the entire + // provisioner block, rather than just for its contents. Therefore it must + // also produce the block header and body delimiters. + return func(buf *bytes.Buffer, blockAddr string, item *hcl1ast.ObjectItem) tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + body := item.Val.(*hcl1ast.ObjectType) + declRange := hcl1PosRange(filename, item.Keys[0].Pos()) + + if len(item.Keys) < 2 { + diags = diags.Append(&hcl2.Diagnostic{ + Severity: hcl2.DiagError, + Summary: "Invalid provisioner block", + Detail: "A provisioner block must have one label: the provisioner type.", + Subject: &declRange, + }) + return diags + } + + typeName := item.Keys[1].Token.Value().(string) + schema := an.ProvisionerSchemas[typeName] + if schema == nil { + // This message is assuming that if the user _is_ using a third-party + // provisioner plugin they already know how to install it for normal + // use and so we don't need to spell out those instructions in detail + // here. + diags = diags.Append(&hcl2.Diagnostic{ + Severity: hcl2.DiagError, + Summary: "Unknown provisioner type", + Detail: fmt.Sprintf("The provisioner type %q is not supported. If this is a third-party plugin, make sure its plugin executable is available in one of the usual plugin search paths.", typeName), + Subject: &declRange, + }) + return diags + } + + rules := schemaDefaultBodyRules(filename, schema, an, adhocComments) + rules["when"] = maybeBareTraversalAttributeRule(filename, an) + rules["on_failure"] = maybeBareTraversalAttributeRule(filename, an) + rules["connection"] = connectionBlockRule(filename, an, adhocComments) + + printComments(buf, item.LeadComment) + printBlockOpen(buf, "provisioner", []string{typeName}, item.LineComment) + bodyDiags := upgradeBlockBody(filename, fmt.Sprintf("%s.provisioner[%q]", blockAddr, typeName), buf, body.List.Items, body.Rbrace, rules, adhocComments) + diags = diags.Append(bodyDiags) + buf.WriteString("}\n") + + return diags + } +} + +func connectionBlockRule(filename string, an *analysis, adhocComments *commentQueue) bodyItemRule { + // Unlike some other examples above, this is a rule for the entire + // connection block, rather than just for its contents. Therefore it must + // also produce the block header and body delimiters. + return func(buf *bytes.Buffer, blockAddr string, item *hcl1ast.ObjectItem) tfdiags.Diagnostics { + // TODO: For the few resource types that were setting ConnInfo in + // state after create/update in prior versions, generate the additional + // explicit connection settings that are now required if and only if + // there's at least one provisioner block. + // For now, we just pass this through as-is. + hcl1printer.Fprint(buf, item) + buf.WriteByte('\n') + return nil + } +} diff --git a/configs/configupgrade/upgrade_native.go b/configs/configupgrade/upgrade_native.go index b2090ed94..4807b246c 100644 --- a/configs/configupgrade/upgrade_native.go +++ b/configs/configupgrade/upgrade_native.go @@ -329,24 +329,8 @@ func (u *Upgrader) upgradeNativeSyntaxResource(filename string, buf *bytes.Buffe rules["depends_on"] = dependsOnAttributeRule(filename, an) rules["provider"] = maybeBareTraversalAttributeRule(filename, an) rules["lifecycle"] = nestedBlockRule(filename, lifecycleBlockBodyRules(filename, an), an, adhocComments) - rules["connection"] = func(buf *bytes.Buffer, blockAddr string, item *hcl1ast.ObjectItem) tfdiags.Diagnostics { - // TODO: For the few resource types that were setting ConnInfo in - // state after create/update in prior versions, generate the additional - // explicit connection settings that are now required if and only if - // there's at least one provisioner block. - // For now, we just pass this through as-is. - hcl1printer.Fprint(buf, item) - buf.WriteByte('\n') - return nil - } - rules["provisioner"] = func(buf *bytes.Buffer, blockAddr string, item *hcl1ast.ObjectItem) tfdiags.Diagnostics { - // TODO: Look up the provisioner schema and map this properly to ensure - // any references get properly updated. - // For now, we just pass this through as-is. - hcl1printer.Fprint(buf, item) - buf.WriteByte('\n') - return nil - } + rules["connection"] = connectionBlockRule(filename, an, adhocComments) + rules["provisioner"] = provisionerBlockRule(filename, an, adhocComments) printComments(buf, item.LeadComment) printBlockOpen(buf, blockType, labels, item.LineComment) diff --git a/configs/configupgrade/upgrade_test.go b/configs/configupgrade/upgrade_test.go index a0c90b21f..cc5f2de53 100644 --- a/configs/configupgrade/upgrade_test.go +++ b/configs/configupgrade/upgrade_test.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/helper/logging" "github.com/hashicorp/terraform/providers" + "github.com/hashicorp/terraform/provisioners" "github.com/hashicorp/terraform/terraform" ) @@ -39,7 +40,8 @@ func TestUpgradeValid(t *testing.T) { inputDir := filepath.Join(fixtureDir, entry.Name(), "input") wantDir := filepath.Join(fixtureDir, entry.Name(), "want") u := &Upgrader{ - Providers: providers.ResolverFixed(testProviders), + Providers: providers.ResolverFixed(testProviders), + Provisioners: testProvisioners, } inputSrc, err := LoadModule(inputDir) @@ -250,6 +252,21 @@ var testProviders = map[string]providers.Factory{ }), } +var testProvisioners = map[string]provisioners.Factory{ + "test": provisioners.Factory(func() (provisioners.Interface, error) { + p := &terraform.MockProvisioner{} + p.GetSchemaResponse = provisioners.GetSchemaResponse{ + Provisioner: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "commands": {Type: cty.List(cty.String), Optional: true}, + "interpreter": {Type: cty.String, Optional: true}, + }, + }, + } + return p, nil + }), +} + func init() { // Initialize the backends backendinit.Init(nil)