command: Specialized error message for var decls in tfvars

A common new-user mistake is to place variable _declarations_ into .tfvars
files instead of variable _values_. To guide towards the correct approach
here, we add a specialized error message for that situation that includes
guidance on the distinction between declaring and setting values for
variables, and an example of what setting a value should look like.
This commit is contained in:
Martin Atkins 2019-02-22 17:51:48 -08:00
parent 9c0e3cc819
commit 552dddfb4c
2 changed files with 70 additions and 0 deletions

View File

@ -165,6 +165,37 @@ func (m *Meta) addVarsFromFile(filename string, sourceType terraform.ValueSource
} }
} }
// Before we do our real decode, we'll probe to see if there are any blocks
// of type "variable" in this body, since it's a common mistake for new
// users to put variable declarations in tfvars rather than variable value
// definitions, and otherwise our error message for that case is not so
// helpful.
{
content, _, _ := f.Body.PartialContent(&hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{
Type: "variable",
LabelNames: []string{"name"},
},
},
})
for _, block := range content.Blocks {
name := block.Labels[0]
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Variable declaration in .tfvars file",
Detail: fmt.Sprintf("A .tfvars file is used to assign values to variables that have already been declared in .tf files, not to declare new variables. To declare variable %q, place this block in one of your .tf files, such as variables.tf.\n\nTo set a value for this variable in %s, use the definition syntax instead:\n %s = <value>", name, block.TypeRange.Filename, name),
Subject: &block.TypeRange,
})
}
if diags.HasErrors() {
// If we already found problems then JustAttributes below will find
// the same problems with less-helpful messages, so we'll bail for
// now to let the user focus on the immediate problem.
return diags
}
}
attrs, hclDiags := f.Body.JustAttributes() attrs, hclDiags := f.Body.JustAttributes()
diags = diags.Append(hclDiags) diags = diags.Append(hclDiags)

View File

@ -686,6 +686,38 @@ func TestPlan_varFileDefault(t *testing.T) {
} }
} }
func TestPlan_varFileWithDecls(t *testing.T) {
tmp, cwd := testCwd(t)
defer testFixCwd(t, tmp, cwd)
varFilePath := testTempFile(t)
if err := ioutil.WriteFile(varFilePath, []byte(planVarFileWithDecl), 0644); err != nil {
t.Fatalf("err: %s", err)
}
p := planVarsFixtureProvider()
ui := cli.NewMockUi()
c := &PlanCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{
"-var-file", varFilePath,
testFixturePath("plan-vars"),
}
if code := c.Run(args); code == 0 {
t.Fatalf("succeeded; want failure\n\n%s", ui.OutputWriter.String())
}
msg := ui.ErrorWriter.String()
if got, want := msg, "Variable declaration in .tfvars file"; !strings.Contains(got, want) {
t.Fatalf("missing expected error message\nwant message containing %q\ngot:\n%s", want, got)
}
}
func TestPlan_detailedExitcode(t *testing.T) { func TestPlan_detailedExitcode(t *testing.T) {
cwd, err := os.Getwd() cwd, err := os.Getwd()
if err != nil { if err != nil {
@ -892,6 +924,13 @@ const planVarFile = `
foo = "bar" foo = "bar"
` `
const planVarFileWithDecl = `
foo = "bar"
variable "nope" {
}
`
const testPlanNoStateStr = ` const testPlanNoStateStr = `
<not created> <not created>
` `