Skip to content

Commit

Permalink
configs: Accept and minimally validate a "language" argument
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
apparentlymart committed Feb 26, 2021
1 parent 54cc4da commit b5adc33
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 5 deletions.
44 changes: 44 additions & 0 deletions configs/experiments.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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
Expand Down
10 changes: 5 additions & 5 deletions configs/parser_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -244,6 +244,7 @@ var terraformBlockSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{Name: "required_version"},
{Name: "experiments"},
{Name: "language"},
},
Blocks: []hcl.BlockHeaderSchema{
{
Expand Down Expand Up @@ -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"},
},
}
4 changes: 4 additions & 0 deletions configs/testdata/error-files/invalid_language_edition.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
terraform {
# The language argument expects a bare keyword, not a string.
language = "TF2021" # ERROR: Invalid language edition
}
6 changes: 6 additions & 0 deletions configs/testdata/error-files/unsupported_language_edition.tf
Original file line number Diff line number Diff line change
@@ -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
}
8 changes: 8 additions & 0 deletions configs/testdata/valid-files/valid-language-edition.tf
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit b5adc33

Please sign in to comment.