diff --git a/command/test-fixtures/validate-invalid/missing_defined_var/main.tf b/command/test-fixtures/validate-invalid/missing_defined_var/main.tf new file mode 100644 index 000000000..b3e122172 --- /dev/null +++ b/command/test-fixtures/validate-invalid/missing_defined_var/main.tf @@ -0,0 +1,10 @@ +resource "test_instance" "foo" { + ami = "bar" + + network_interface { + device_index = 0 + description = "Main network interface ${var.name}" + } +} + +variable "name" {} diff --git a/command/validate.go b/command/validate.go index f693da244..c4be168af 100644 --- a/command/validate.go +++ b/command/validate.go @@ -1,12 +1,12 @@ package command import ( - "flag" "fmt" "path/filepath" "strings" "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/terraform" ) // ValidateCommand is a Command implementation that validates the terraform files @@ -17,15 +17,21 @@ type ValidateCommand struct { const defaultPath = "." func (c *ValidateCommand) Run(args []string) int { - args = c.Meta.process(args, false) - var dirPath string + args = c.Meta.process(args, true) + var checkVars bool - cmdFlags := flag.NewFlagSet("validate", flag.ContinueOnError) - cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } + cmdFlags := c.Meta.flagSet("validate") + cmdFlags.BoolVar(&checkVars, "check-variables", true, "check-variables") + cmdFlags.Usage = func() { + c.Ui.Error(c.Help()) + } if err := cmdFlags.Parse(args); err != nil { return 1 } + args = cmdFlags.Args() + + var dirPath string if len(args) == 1 { dirPath = args[0] } else { @@ -37,7 +43,7 @@ func (c *ValidateCommand) Run(args []string) int { "Unable to locate directory %v\n", err.Error())) } - rtnCode := c.validate(dir) + rtnCode := c.validate(dir, checkVars) return rtnCode } @@ -48,24 +54,38 @@ func (c *ValidateCommand) Synopsis() string { func (c *ValidateCommand) Help() string { helpText := ` -Usage: terraform validate [options] [path] +Usage: terraform validate [options] [dir] - Reads the Terraform files in the given path (directory) and - validates their syntax and basic semantics. + Validate the terraform files in a directory. Validation includes a + basic check of syntax as well as checking that all variables declared + in the configuration are specified in one of the possible ways: - This is not a full validation that is normally done with - a plan or apply operation, but can be used to verify the basic - syntax and usage of Terraform configurations is correct. + -var foo=... + -var-file=foo.vars + TF_VAR_foo environment variable + terraform.tfvars + default value + + If dir is not specified, then the current directory will be used. Options: - -no-color If specified, output won't contain any color. + -check-variables=true If set to true (default), the command will check + whether all required variables have been specified. + -no-color If specified, output won't contain any color. + + -var 'foo=bar' Set a variable in the Terraform configuration. This + flag can be set multiple times. + + -var-file=foo Set variables in the Terraform configuration from + a file. If "terraform.tfvars" is present, it will be + automatically loaded if this flag is not specified. ` return strings.TrimSpace(helpText) } -func (c *ValidateCommand) validate(dir string) int { +func (c *ValidateCommand) validate(dir string, checkVars bool) int { cfg, err := config.LoadDir(dir) if err != nil { c.Ui.Error(fmt.Sprintf( @@ -78,5 +98,27 @@ func (c *ValidateCommand) validate(dir string) int { "Error validating: %v\n", err.Error())) return 1 } + + if checkVars { + mod, err := c.Module(dir) + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err)) + return 1 + } + + opts := c.contextOpts() + opts.Module = mod + + tfCtx, err := terraform.NewContext(opts) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error: %v\n", err.Error())) + return 1 + } + + if !validateContext(tfCtx, c.Ui) { + return 1 + } + } + return 0 } diff --git a/command/validate_test.go b/command/validate_test.go index de293eed9..77bdfed97 100644 --- a/command/validate_test.go +++ b/command/validate_test.go @@ -7,21 +7,21 @@ import ( "github.com/mitchellh/cli" ) -func setupTest(fixturepath string) (*cli.MockUi, int) { +func setupTest(fixturepath string, args ...string) (*cli.MockUi, int) { ui := new(cli.MockUi) c := &ValidateCommand{ Meta: Meta{ - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } - args := []string{ - testFixturePath(fixturepath), - } + args = append(args, testFixturePath(fixturepath)) code := c.Run(args) return ui, code } + func TestValidateCommand(t *testing.T) { if ui, code := setupTest("validate-valid"); code != 0 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) @@ -122,3 +122,21 @@ func TestWronglyUsedInterpolationShouldFail(t *testing.T) { t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String()) } } + +func TestMissingDefinedVar(t *testing.T) { + ui, code := setupTest("validate-invalid/missing_defined_var") + if code != 1 { + t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + if !strings.Contains(ui.ErrorWriter.String(), "Required variable not set:") { + t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String()) + } +} + +func TestMissingDefinedVarConfigOnly(t *testing.T) { + ui, code := setupTest("validate-invalid/missing_defined_var", "-check-variables=false") + if code != 0 { + t.Fatalf("Should have passed: %d\n\n%s", code, ui.ErrorWriter.String()) + } +} diff --git a/website/docs/commands/validate.html.markdown b/website/docs/commands/validate.html.markdown index a13cc7e30..3a17c79af 100644 --- a/website/docs/commands/validate.html.markdown +++ b/website/docs/commands/validate.html.markdown @@ -24,10 +24,31 @@ The following can be reported: * invalid `module` name * interpolation used in places where it's unsupported (e.g. `variable`, `depends_on`, `module.source`, `provider`) + * missing value for a variable (none of `-var foo=...` flag, + `-var-file=foo.vars` flag, `TF_VAR_foo` environment variable, + `terraform.tfvars`, or default value in the configuration) ## Usage -Usage: `terraform validate [dir]` +Usage: `terraform validate [options] [dir]` By default, `validate` requires no flags and looks in the current directory -for the configurations. \ No newline at end of file +for the configurations. + +The command-line flags are all optional. The available flags are: + +* `-check-variables=true` - If set to true (default), the command will check + whether all required variables have been specified. + +* `-no-color` - Disables output with coloring. + +* `-var 'foo=bar'` - Set a variable in the Terraform configuration. This flag + can be set multiple times. Variable values are interpreted as + [HCL](/docs/configuration/syntax.html#HCL), so list and map values can be + specified via this flag. + +* `-var-file=foo` - Set variables in the Terraform configuration from + a [variable file](/docs/configuration/variables.html#variable-files). If + "terraform.tfvars" is present, it will be automatically loaded first. Any + files specified by `-var-file` override any values in a "terraform.tfvars". + This flag can be used multiple times.