Skip to content

Commit

Permalink
Added verify command
Browse files Browse the repository at this point in the history
  • Loading branch information
Soren Mathiasen committed Feb 8, 2016
1 parent 819db34 commit db69a29
Show file tree
Hide file tree
Showing 17 changed files with 297 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ website/node_modules
.*.swp
.idea
*.test
*.iml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module "super#module" {
}

module "super" {
source = "${var.modulename}"
}
11 changes: 11 additions & 0 deletions command/test-fixtures/validate-invalid/interpolation/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
variable "otherresourcename" {
default = "aws_instance.web1"
}

variable "vairable_with_interpolation" {
default = "${var.otherresourcename}"
}

resource "aws_instance" "web" {
depends_on = ["${var.otherresourcename}}"]
}
8 changes: 8 additions & 0 deletions command/test-fixtures/validate-invalid/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
resource "test_instance" "foo" {
ami = "bar"

network_interface {
device_index = 0
description = "Main network interface ${var.this_is_an_error}"
}
}
9 changes: 9 additions & 0 deletions command/test-fixtures/validate-invalid/missing_quote/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
resource "test_instance" "foo" {
ami = "bar"

network_interface {
device_index = 0
name = test
description = "Main network interface"
}
}
8 changes: 8 additions & 0 deletions command/test-fixtures/validate-invalid/missing_var/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
resource "test_instance" "foo" {
ami = "bar"

network_interface {
device_index = 0
description = "${var.description}"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module "multi_module" {
}

module "multi_module" {
}
11 changes: 11 additions & 0 deletions command/test-fixtures/validate-invalid/multiple_providers/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
provider "aws" {
access_key = "123"
secret_key = "233"
region = "us-east-1"
}

provider "aws" {
access_key = "123"
secret_key = "233"
region = "us-east-1"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
resource "aws_instance" "web" {
}

resource "aws_instance" "web" {
}
3 changes: 3 additions & 0 deletions command/test-fixtures/validate-invalid/outputs/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
output "myvalue" {
values = "Some value"
}
9 changes: 9 additions & 0 deletions command/test-fixtures/validate-valid/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
resource "test_instance" "foo" {
ami = "bar"

# This is here because at some point it caused a test failure
network_interface {
device_index = 0
description = "Main network interface"
}
}
58 changes: 58 additions & 0 deletions command/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package command

import (
"fmt"
"github.com/hashicorp/terraform/config"
"path/filepath"
)

// ValidateCommand is a Command implementation that validates the terraform files
type ValidateCommand struct {
Meta
}

const defaultPath = "."

func (c *ValidateCommand) Help() string {
return ""
}

func (c *ValidateCommand) Run(args []string) int {
args = c.Meta.process(args, false)
var dirPath string

if len(args) == 1 {
dirPath = args[0]
} else {
dirPath = "."
}
dir, err := filepath.Abs(dirPath)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Unable to locate directory %v\n", err.Error()))
}

rtnCode := c.validate(dir)

return rtnCode
}

func (c *ValidateCommand) Synopsis() string {
return "Validates the Terraform files"
}

func (c *ValidateCommand) validate(dir string) int {
cfg, err := config.LoadDir(dir)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error loading files %v\n", err.Error()))
return 1
}
err = cfg.Validate()
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error validating: %v\n", err.Error()))
return 1
}
return 0
}
123 changes: 123 additions & 0 deletions command/validate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package command

import (
"github.com/mitchellh/cli"
"strings"
"testing"
)

func setupTest(fixturepath string) (*cli.MockUi, int) {
ui := new(cli.MockUi)
c := &ValidateCommand{
Meta: Meta{
Ui: ui,
},
}

args := []string{
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())
}
}

func TestValidateFailingCommand(t *testing.T) {
if ui, code := setupTest("validate-invalid"); code != 1 {
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
}
}

func TestValidateFailingCommandMissingQuote(t *testing.T) {
ui, code := setupTest("validate-invalid/missing_quote")

if code != 1 {
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
}
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "IDENT test") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
}
}

func TestValidateFailingCommandMissingVariable(t *testing.T) {
ui, code := setupTest("validate-invalid/missing_var")
if code != 1 {
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
}
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "config: unknown variable referenced: 'description'. define it with 'variable' blocks") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
}
}

func TestSameProviderMutipleTimesShouldFail(t *testing.T) {
ui, code := setupTest("validate-invalid/multiple_providers")
if code != 1 {
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
}
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "provider.aws: declared multiple times, you can only declare a provider once") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
}
}

func TestSameModuleMultipleTimesShouldFail(t *testing.T) {
ui, code := setupTest("validate-invalid/multiple_modules")
if code != 1 {
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
}
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "multi_module: module repeated multiple times") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
}
}

func TestSameResourceMultipleTimesShouldFail(t *testing.T) {
ui, code := setupTest("validate-invalid/multiple_resources")
if code != 1 {
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
}
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "aws_instance.web: resource repeated multiple times") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
}
}

func TestOutputWithoutValueShouldFail(t *testing.T) {
ui, code := setupTest("validate-invalid/outputs")
if code != 1 {
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
}
if !strings.HasSuffix(strings.TrimSpace(ui.ErrorWriter.String()), "output is missing required 'value' key") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
}
}

func TestModuleWithIncorrectNameShouldFail(t *testing.T) {
ui, code := setupTest("validate-invalid/incorrectmodulename")
if code != 1 {
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
}

if !strings.Contains(ui.ErrorWriter.String(), "module name can only contain letters, numbers, dashes, and underscores") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
}
if !strings.Contains(ui.ErrorWriter.String(), "module source cannot contain interpolations") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
}
}

func TestWronglyUsedInterpolationShouldFail(t *testing.T) {
ui, code := setupTest("validate-invalid/interpolation")
if code != 1 {
t.Fatalf("Should have failed: %d\n\n%s", code, ui.ErrorWriter.String())
}

if !strings.Contains(ui.ErrorWriter.String(), "depends on value cannot contain interpolations") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
}
if !strings.Contains(ui.ErrorWriter.String(), "Variable 'vairable_with_interpolation': cannot contain interpolations") {
t.Fatalf("Should have failed: %d\n\n'%s'", code, ui.ErrorWriter.String())
}
}
6 changes: 6 additions & 0 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ func init() {
}, nil
},

"validate": func() (cli.Command, error) {
return &command.ValidateCommand{
Meta: meta,
}, nil
},

"version": func() (cli.Command, error) {
return &command.VersionCommand{
Meta: meta,
Expand Down
1 change: 1 addition & 0 deletions website/source/docs/commands/index.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Available commands are:
remote Configure remote state storage
show Inspect Terraform state or plan
taint Manually mark a resource for recreation
validate Validates the Terraform files
version Prints the Terraform version
```

Expand Down
28 changes: 28 additions & 0 deletions website/source/docs/commands/validate.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
layout: "docs"
page_title: "Command: validate"
sidebar_current: "docs-commands-validate"
description: |-
The `terraform validate` command is used to validate the format and structure of the terraform files.
---

# Command: verify

The `terraform validate` command is used to validate the syntax of the terraform files.
Terraform performs a syntax check on all the terraform files in the directory, and will display an error if the file(s)
doesn't validate.

These errors include:

* Interpolation in variable values, depends_on, module source etc.

* Duplicate names in resource, modules and providers.

* Missing variable values.

## Usage

Usage: `terraform validate [dir]`

By default, `validate` requires no flags and looks in the current directory
for the configurations.
5 changes: 5 additions & 0 deletions website/source/layouts/docs.erb
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@
<li<%= sidebar_current("docs-commands-taint") %>>
<a href="/docs/commands/taint.html">taint</a>
</li>

<li<%= sidebar_current("docs-commands-validate") %>>
<a href="/docs/commands/validate.html">validate</a>
</li>

</ul>
</li>

Expand Down

0 comments on commit db69a29

Please sign in to comment.