Skip to content

Commit

Permalink
Merge #4955: "terraform fmt" command
Browse files Browse the repository at this point in the history
  • Loading branch information
apparentlymart committed Apr 4, 2016
2 parents ee54f1c + d883b76 commit fa703db
Show file tree
Hide file tree
Showing 10 changed files with 1,151 additions and 1 deletion.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ FEATURES:
* **New resource:** `aws_iam_user_ssh_key` [GH-5774]
* **New resource:** `triton_fabric` [GH-5920]
* **New resource:** `triton_vlan` [GH-5920]

* New `terraform fmt` command to automatically normalize config file style [GH-4955]

IMPROVEMENTS:

* provider/aws: Change `aws_elb` access_logs to list type [GH-5065]
Expand Down
8 changes: 8 additions & 0 deletions Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

94 changes: 94 additions & 0 deletions command/fmt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package command

import (
"flag"
"fmt"
"io"
"os"
"strings"

"github.com/hashicorp/hcl/hcl/fmtcmd"
"github.com/mitchellh/cli"
)

const (
stdinArg = "-"
fileExtension = "tf"
)

// FmtCommand is a Command implementation that rewrites Terraform config
// files to a canonical format and style.
type FmtCommand struct {
Meta
opts fmtcmd.Options
input io.Reader // STDIN if nil
}

func (c *FmtCommand) Run(args []string) int {
if c.input == nil {
c.input = os.Stdin
}

args = c.Meta.process(args, false)

cmdFlags := flag.NewFlagSet("fmt", flag.ContinueOnError)
cmdFlags.BoolVar(&c.opts.List, "list", true, "list")
cmdFlags.BoolVar(&c.opts.Write, "write", true, "write")
cmdFlags.BoolVar(&c.opts.Diff, "diff", false, "diff")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }

if err := cmdFlags.Parse(args); err != nil {
return 1
}

args = cmdFlags.Args()
if len(args) > 1 {
c.Ui.Error("The fmt command expects at most one argument.")
cmdFlags.Usage()
return 1
}

var dirs []string
if len(args) == 0 {
dirs = []string{"."}
} else if args[0] == stdinArg {
c.opts.List = false
c.opts.Write = false
} else {
dirs = []string{args[0]}
}

output := &cli.UiWriter{Ui: c.Ui}
err := fmtcmd.Run(dirs, []string{fileExtension}, c.input, output, c.opts)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error running fmt: %s", err))
return 2
}

return 0
}

func (c *FmtCommand) Help() string {
helpText := `
Usage: terraform fmt [options] [DIR]
Rewrites all Terraform configuration files to a canonical format.
If DIR is not specified then the current working directory will be used.
If DIR is "-" then content will be read from STDIN.
Options:
-list List files whose formatting differs (disabled if using STDIN)
-write Write result to source file instead of STDOUT (disabled if using STDIN)
-diff Display diffs instead of rewriting files
`
return strings.TrimSpace(helpText)
}

func (c *FmtCommand) Synopsis() string {
return "Rewrites config files to canonical format"
}
206 changes: 206 additions & 0 deletions command/fmt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package command

import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"

"github.com/mitchellh/cli"
)

func TestFmt_errorReporting(t *testing.T) {
tempDir, err := fmtFixtureWriteDir()
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(tempDir)

ui := new(cli.MockUi)
c := &FmtCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
},
}

dummy_file := filepath.Join(tempDir, "doesnotexist")
args := []string{dummy_file}
if code := c.Run(args); code != 2 {
t.Fatalf("wrong exit code. errors: \n%s", ui.ErrorWriter.String())
}

expected := fmt.Sprintf("Error running fmt: stat %s: no such file or directory", dummy_file)
if actual := ui.ErrorWriter.String(); !strings.Contains(actual, expected) {
t.Fatalf("expected:\n%s\n\nto include: %q", actual, expected)
}
}

func TestFmt_tooManyArgs(t *testing.T) {
ui := new(cli.MockUi)
c := &FmtCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
},
}

args := []string{
"one",
"two",
}
if code := c.Run(args); code != 1 {
t.Fatalf("wrong exit code. errors: \n%s", ui.ErrorWriter.String())
}

expected := "The fmt command expects at most one argument."
if actual := ui.ErrorWriter.String(); !strings.Contains(actual, expected) {
t.Fatalf("expected:\n%s\n\nto include: %q", actual, expected)
}
}

func TestFmt_workingDirectory(t *testing.T) {
tempDir, err := fmtFixtureWriteDir()
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(tempDir)

cwd, err := os.Getwd()
if err != nil {
t.Fatalf("err: %s", err)
}
err = os.Chdir(tempDir)
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Chdir(cwd)

ui := new(cli.MockUi)
c := &FmtCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
},
}

args := []string{}
if code := c.Run(args); code != 0 {
t.Fatalf("wrong exit code. errors: \n%s", ui.ErrorWriter.String())
}

expected := fmt.Sprintf("%s\n", fmtFixture.filename)
if actual := ui.OutputWriter.String(); actual != expected {
t.Fatalf("got: %q\nexpected: %q", actual, expected)
}
}

func TestFmt_directoryArg(t *testing.T) {
tempDir, err := fmtFixtureWriteDir()
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(tempDir)

ui := new(cli.MockUi)
c := &FmtCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
},
}

args := []string{tempDir}
if code := c.Run(args); code != 0 {
t.Fatalf("wrong exit code. errors: \n%s", ui.ErrorWriter.String())
}

expected := fmt.Sprintf("%s\n", filepath.Join(tempDir, fmtFixture.filename))
if actual := ui.OutputWriter.String(); actual != expected {
t.Fatalf("got: %q\nexpected: %q", actual, expected)
}
}

func TestFmt_stdinArg(t *testing.T) {
input := new(bytes.Buffer)
input.Write(fmtFixture.input)

ui := new(cli.MockUi)
c := &FmtCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
},
input: input,
}

args := []string{"-"}
if code := c.Run(args); code != 0 {
t.Fatalf("wrong exit code. errors: \n%s", ui.ErrorWriter.String())
}

expected := fmtFixture.golden
if actual := ui.OutputWriter.Bytes(); !bytes.Equal(actual, expected) {
t.Fatalf("got: %q\nexpected: %q", actual, expected)
}
}

func TestFmt_nonDefaultOptions(t *testing.T) {
tempDir, err := fmtFixtureWriteDir()
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.RemoveAll(tempDir)

ui := new(cli.MockUi)
c := &FmtCommand{
Meta: Meta{
ContextOpts: testCtxConfig(testProvider()),
Ui: ui,
},
}

args := []string{
"-list=false",
"-write=false",
"-diff",
tempDir,
}
if code := c.Run(args); code != 0 {
t.Fatalf("wrong exit code. errors: \n%s", ui.ErrorWriter.String())
}

expected := fmt.Sprintf("-%s+%s", fmtFixture.input, fmtFixture.golden)
if actual := ui.OutputWriter.String(); !strings.Contains(actual, expected) {
t.Fatalf("expected:\n%s\n\nto include: %q", actual, expected)
}
}

var fmtFixture = struct {
filename string
input, golden []byte
}{
"main.tf",
[]byte(` foo = "bar"
`),
[]byte(`foo = "bar"
`),
}

func fmtFixtureWriteDir() (string, error) {
dir, err := ioutil.TempDir("", "tf")
if err != nil {
return "", err
}

err = ioutil.WriteFile(filepath.Join(dir, fmtFixture.filename), fmtFixture.input, 0644)
if err != nil {
os.RemoveAll(dir)
return "", err
}

return dir, nil
}
6 changes: 6 additions & 0 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ func init() {
}, nil
},

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

"get": func() (cli.Command, error) {
return &command.GetCommand{
Meta: meta,
Expand Down
Loading

0 comments on commit fa703db

Please sign in to comment.