From b5adc330751a4e2a7f5873ecb745349361cda5ac Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 26 Feb 2021 09:48:42 -0800 Subject: [PATCH] configs: Accept and minimally validate a "language" argument We expect that in order to continue to evolve the language without breaking existing modules we will at some point need to have a way to mark when a particular module is expecting a newer interpretation of the language. Although it's too early to do any deep preparation for that, this commit aims to proactively reserve an argument named "language" inside "terraform" blocks, which currently only accepts the keyword TF2021 that is intended to represent "the edition of the Terraform language as defined in 2021". That argument also defaults to TF2021 if not set, so in practice there's no real reason to set this today, but this minimal validation today is intended to give better feedback to users of older Terraform versions in the event that we introduce a new language edition later and they try to use an module incompatible with their Terraform version. --- configs/experiments.go | 44 +++++++++++++++++++ configs/parser_config.go | 10 ++--- .../error-files/invalid_language_edition.tf | 4 ++ .../unsupported_language_edition.tf | 6 +++ .../valid-files/valid-language-edition.tf | 8 ++++ 5 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 configs/testdata/error-files/invalid_language_edition.tf create mode 100644 configs/testdata/error-files/unsupported_language_edition.tf create mode 100644 configs/testdata/valid-files/valid-language-edition.tf diff --git a/configs/experiments.go b/configs/experiments.go index 82ff3bd91..83557a85e 100644 --- a/configs/experiments.go +++ b/configs/experiments.go @@ -5,6 +5,7 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/terraform/experiments" + "github.com/hashicorp/terraform/version" "github.com/zclconf/go-cty/cty" ) @@ -25,6 +26,49 @@ func sniffActiveExperiments(body hcl.Body) (experiments.Set, hcl.Diagnostics) { content, _, blockDiags := block.Body.PartialContent(configFileExperimentsSniffBlockSchema) diags = append(diags, blockDiags...) + if attr, exists := content.Attributes["language"]; exists { + // We don't yet have a sense of selecting an edition of the + // language, but we're reserving this syntax for now so that + // if and when we do this later older versions of Terraform + // will emit a more helpful error message than just saying + // this attribute doesn't exist. Handling this as part of + // experiments is a bit odd for now but justified by the + // fact that a future fuller implementation of switchable + // languages would be likely use a similar implementation + // strategy as experiments, and thus would lead to this + // function being refactored to deal with both concerns at + // once. We'll see, though! + kw := hcl.ExprAsKeyword(attr.Expr) + currentVersion := version.SemVer.String() + const firstEdition = "TF2021" + switch { + case kw == "": // (the expression wasn't a keyword at all) + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid language edition", + Detail: fmt.Sprintf( + "The language argument expects a bare language edition keyword. Terraform %s supports only language edition %s, which is the default.", + currentVersion, firstEdition, + ), + Subject: attr.Expr.Range().Ptr(), + }) + case kw != firstEdition: + rel := "different" + if kw > firstEdition { // would be weird for this not to be true, but it's user input so anything goes + rel = "newer" + } + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Unsupported language edition", + Detail: fmt.Sprintf( + "Terraform v%s only supports language edition %s. This module requires a %s version of Terraform CLI.", + currentVersion, firstEdition, rel, + ), + Subject: attr.Expr.Range().Ptr(), + }) + } + } + attr, exists := content.Attributes["experiments"] if !exists { continue diff --git a/configs/parser_config.go b/configs/parser_config.go index 354b96a72..ec66b4cc5 100644 --- a/configs/parser_config.go +++ b/configs/parser_config.go @@ -58,8 +58,8 @@ func (p *Parser) loadConfigFile(path string, override bool) (*File, hcl.Diagnost content, contentDiags := block.Body.Content(terraformBlockSchema) diags = append(diags, contentDiags...) - // We ignore the "terraform_version" and "experiments" attributes - // here because sniffCoreVersionRequirements and + // We ignore the "terraform_version", "language" and "experiments" + // attributes here because sniffCoreVersionRequirements and // sniffActiveExperiments already dealt with those above. for _, innerBlock := range content.Blocks { @@ -244,6 +244,7 @@ var terraformBlockSchema = &hcl.BodySchema{ Attributes: []hcl.AttributeSchema{ {Name: "required_version"}, {Name: "experiments"}, + {Name: "language"}, }, Blocks: []hcl.BlockHeaderSchema{ { @@ -283,8 +284,7 @@ var configFileVersionSniffBlockSchema = &hcl.BodySchema{ // to decode a single attribute from inside a "terraform" block. var configFileExperimentsSniffBlockSchema = &hcl.BodySchema{ Attributes: []hcl.AttributeSchema{ - { - Name: "experiments", - }, + {Name: "experiments"}, + {Name: "language"}, }, } diff --git a/configs/testdata/error-files/invalid_language_edition.tf b/configs/testdata/error-files/invalid_language_edition.tf new file mode 100644 index 000000000..255aefe0a --- /dev/null +++ b/configs/testdata/error-files/invalid_language_edition.tf @@ -0,0 +1,4 @@ +terraform { + # The language argument expects a bare keyword, not a string. + language = "TF2021" # ERROR: Invalid language edition +} diff --git a/configs/testdata/error-files/unsupported_language_edition.tf b/configs/testdata/error-files/unsupported_language_edition.tf new file mode 100644 index 000000000..0ee616f73 --- /dev/null +++ b/configs/testdata/error-files/unsupported_language_edition.tf @@ -0,0 +1,6 @@ +terraform { + # If a future change in this repository happens to make TF2038 a valid + # edition then this will start failing; in that case, change this file to + # select a different edition that isn't supported. + language = TF2038 # ERROR: Unsupported language edition +} diff --git a/configs/testdata/valid-files/valid-language-edition.tf b/configs/testdata/valid-files/valid-language-edition.tf new file mode 100644 index 000000000..60f2df3eb --- /dev/null +++ b/configs/testdata/valid-files/valid-language-edition.tf @@ -0,0 +1,8 @@ +terraform { + # If we drop support for TF2021 in a future Terraform release then this + # test will fail. In that case, update this to a newer edition that is + # still supported, because the purpose of this test is to verify that + # we can successfully decode the language argument, not specifically + # that we support TF2021. + language = TF2021 +}