Skip to content

Commit

Permalink
Accept a directory as an argument
Browse files Browse the repository at this point in the history
  • Loading branch information
wata727 committed Jun 2, 2019
1 parent 7aef739 commit ae96a8f
Show file tree
Hide file tree
Showing 56 changed files with 765 additions and 227 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ TFLint inspects all configurations under the current directory by default. You c
```
$ tflint --help
Usage:
tflint [OPTIONS]
tflint [OPTIONS] [FILE or DIR...]
Application Options:
-v, --version Print TFLint version
Expand Down
72 changes: 54 additions & 18 deletions cmd/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"strings"

"github.com/fatih/color"
flags "github.com/jessevdk/go-flags"
Expand Down Expand Up @@ -44,7 +46,7 @@ func NewCLI(outStream io.Writer, errStream io.Writer) *CLI {
func (cli *CLI) Run(args []string) int {
var opts Options
parser := flags.NewParser(&opts, flags.HelpFlag)
parser.Usage = "[OPTIONS]"
parser.Usage = "[OPTIONS] [FILE or DIR...]"
parser.UnknownOptionHandler = func(option string, arg flags.SplitArgument, args []string) ([]string, error) {
if option == "debug" {
return []string{}, errors.New("`debug` option was removed in v0.8.0. Please set `TFLINT_LOG` environment variables instead")
Expand All @@ -61,7 +63,11 @@ func (cli *CLI) Run(args []string) int {
cli.printError(err)
return ExitCodeError
}
argFiles := args[1:]
dir, filterFiles, err := processArgs(args[1:])
if err != nil {
cli.printError(err)
return ExitCodeError
}

// Show version
if opts.Version {
Expand All @@ -85,26 +91,13 @@ func (cli *CLI) Run(args []string) int {
return ExitCodeError
}
}
for _, file := range argFiles {
if fileInfo, err := os.Stat(file); os.IsNotExist(err) {
cli.printError(fmt.Errorf("Failed to load `%s`: File not found", file))
return ExitCodeError
} else if fileInfo.IsDir() {
cli.printError(fmt.Errorf("Failed to load `%s`: TFLint doesn't accept directories as arguments", file))
return ExitCodeError
}

if !cli.loader.IsConfigFile(file) {
cli.printError(fmt.Errorf("Failed to load `%s`: File is not a target of Terraform", file))
return ExitCodeError
}
}
configs, err := cli.loader.LoadConfig()
configs, err := cli.loader.LoadConfig(dir)
if err != nil {
cli.printError(fmt.Errorf("Failed to load configurations: %s", err))
return ExitCodeError
}
annotations, err := cli.loader.LoadAnnotations()
annotations, err := cli.loader.LoadAnnotations(dir)
if err != nil {
cli.printError(fmt.Errorf("Failed to load configuration tokens: %s", err))
return ExitCodeError
Expand Down Expand Up @@ -136,7 +129,7 @@ func (cli *CLI) Run(args []string) int {

issues := []*issue.Issue{}
for _, runner := range runners {
issues = append(issues, runner.LookupIssues(argFiles...)...)
issues = append(issues, runner.LookupIssues(filterFiles...)...)
}

// Print issues
Expand All @@ -152,3 +145,46 @@ func (cli *CLI) Run(args []string) int {
func (cli *CLI) printError(err error) {
fmt.Fprintln(cli.errStream, color.New(color.FgRed).Sprintf("Error: ")+err.Error())
}

func processArgs(args []string) (string, []string, error) {
if len(args) == 0 {
return ".", []string{}, nil
}

var dir string
filterFiles := []string{}

for _, file := range args {
fileInfo, err := os.Stat(file)
if err != nil {
if os.IsNotExist(err) {
return dir, filterFiles, fmt.Errorf("Failed to load `%s`: File not found", file)
}
return dir, filterFiles, fmt.Errorf("Failed to load `%s`: %s", file, err)
}

if fileInfo.IsDir() {
dir = file
if len(args) != 1 {
return dir, filterFiles, fmt.Errorf("Failed to load `%s`: Multiple arguments are not allowed when passing a directory", file)
}
return dir, filterFiles, nil
}

if !strings.HasSuffix(file, ".tf") && !strings.HasSuffix(file, ".tf.json") {
return dir, filterFiles, fmt.Errorf("Failed to load `%s`: File is not a target of Terraform", file)
}

fileDir := filepath.Dir(file)
if dir == "" {
dir = fileDir
filterFiles = append(filterFiles, file)
} else if fileDir == dir {
filterFiles = append(filterFiles, file)
} else {
return dir, filterFiles, fmt.Errorf("Failed to load `%s`: Multiple files in different directories are not allowed", file)
}
}

return dir, filterFiles, nil
}
114 changes: 78 additions & 36 deletions cmd/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ func TestCLIRun__noIssuesFound(t *testing.T) {
}

loader := tflint.NewMockAbstractLoader(ctrl)
loader.EXPECT().LoadConfig().Return(configs.NewEmptyConfig(), tc.LoadErr).AnyTimes()
loader.EXPECT().LoadAnnotations().Return(map[string]tflint.Annotations{}, tc.LoadErr).AnyTimes()
loader.EXPECT().LoadConfig(".").Return(configs.NewEmptyConfig(), tc.LoadErr).AnyTimes()
loader.EXPECT().LoadAnnotations(".").Return(map[string]tflint.Annotations{}, tc.LoadErr).AnyTimes()
loader.EXPECT().LoadValuesFiles().Return([]terraform.InputValues{}, tc.LoadErr).AnyTimes()
cli.loader = loader

Expand All @@ -122,7 +122,9 @@ func TestCLIRun__noIssuesFound(t *testing.T) {
}
}

type testRule struct{}
type testRule struct {
dir string
}
type errorRule struct{}

func (r *testRule) Name() string {
Expand Down Expand Up @@ -154,11 +156,16 @@ func (r *errorRule) Link() string {
}

func (r *testRule) Check(runner *tflint.Runner) error {
filename := "test.tf"
if r.dir != "" {
filename = filepath.Join(r.dir, filename)
}

runner.EmitIssue(
r,
"This is test error",
hcl.Range{
Filename: "test.tf",
Filename: filename,
Start: hcl.Pos{Line: 1},
},
)
Expand Down Expand Up @@ -226,8 +233,8 @@ func TestCLIRun__issuesFound(t *testing.T) {
}

loader := tflint.NewMockAbstractLoader(ctrl)
loader.EXPECT().LoadConfig().Return(configs.NewEmptyConfig(), nil).AnyTimes()
loader.EXPECT().LoadAnnotations().Return(map[string]tflint.Annotations{}, nil).AnyTimes()
loader.EXPECT().LoadConfig(".").Return(configs.NewEmptyConfig(), nil).AnyTimes()
loader.EXPECT().LoadAnnotations(".").Return(map[string]tflint.Annotations{}, nil).AnyTimes()
loader.EXPECT().LoadValuesFiles().Return([]terraform.InputValues{}, nil).AnyTimes()
cli.loader = loader

Expand All @@ -247,46 +254,82 @@ func TestCLIRun__issuesFound(t *testing.T) {

func TestCLIRun__withArguments(t *testing.T) {
cases := []struct {
Name string
Command string
IsConfigFile bool
Status int
Stdout string
Stderr string
Name string
Command string
Dir string
Status int
Stdout string
Stderr string
}{
{
Name: "no arguments",
Command: "./tflint",
Dir: ".",
Status: ExitCodeOK,
Stdout: "This is test error (test_rule)",
},
{
Name: "files arguments",
Command: "./tflint template.tf",
IsConfigFile: true,
Status: ExitCodeOK,
Stdout: "Awesome! Your code is following the best practices :)",
Name: "files arguments",
Command: "./tflint template.tf",
Dir: ".",
Status: ExitCodeOK,
Stdout: "Awesome! Your code is following the best practices :)",
},
{
Name: "file not found",
Command: "./tflint not_found.tf",
Dir: ".",
Status: ExitCodeError,
Stderr: "Failed to load `not_found.tf`: File not found",
},
{
Name: "directories arguments",
Command: "./tflint ./",
IsConfigFile: true,
Status: ExitCodeError,
Stderr: "Failed to load `./`: TFLint doesn't accept directories as arguments",
Name: "not Terraform configuration",
Command: "./tflint README",
Dir: ".",
Status: ExitCodeError,
Stderr: "Failed to load `README`: File is not a target of Terraform",
},
{
Name: "file not found",
Command: "./tflint not_found.tf",
IsConfigFile: true,
Status: ExitCodeError,
Stderr: "Failed to load `not_found.tf`: File not found",
Name: "multiple files",
Command: "./tflint template.tf test.tf",
Dir: ".",
Status: ExitCodeOK,
Stdout: "This is test error (test_rule)",
},
{
Name: "not Terraform configuration",
Command: "./tflint README",
IsConfigFile: false,
Status: ExitCodeError,
Stderr: "Failed to load `README`: File is not a target of Terraform",
Name: "directory argument",
Command: "./tflint example",
Dir: "example",
Status: ExitCodeOK,
Stdout: "This is test error (test_rule)",
},
{
Name: "file under the directory",
Command: fmt.Sprintf("./tflint %s", filepath.Join("example", "test.tf")),
Dir: "example",
Status: ExitCodeOK,
Stdout: "This is test error (test_rule)",
},
{
Name: "multiple directories",
Command: "./tflint example ./",
Dir: "example",
Status: ExitCodeError,
Stderr: "Failed to load `example`: Multiple arguments are not allowed when passing a directory",
},
{
Name: "file and directory",
Command: "./tflint template.tf example",
Dir: "example",
Status: ExitCodeError,
Stderr: "Failed to load `example`: Multiple arguments are not allowed when passing a directory",
},
{
Name: "multiple files in different directories",
Command: fmt.Sprintf("./tflint test.tf %s", filepath.Join("example", "test.tf")),
Dir: "example",
Status: ExitCodeError,
Stderr: fmt.Sprintf("Failed to load `%s`: Multiple files in different directories are not allowed", filepath.Join("example", "test.tf")),
},
}

Expand All @@ -310,7 +353,7 @@ func TestCLIRun__withArguments(t *testing.T) {

for _, tc := range cases {
// Mock rules
rules.DefaultRules = []rules.Rule{&testRule{}}
rules.DefaultRules = []rules.Rule{&testRule{dir: tc.Dir}}

outStream, errStream := new(bytes.Buffer), new(bytes.Buffer)
cli := &CLI{
Expand All @@ -320,9 +363,8 @@ func TestCLIRun__withArguments(t *testing.T) {
}

loader := tflint.NewMockAbstractLoader(ctrl)
loader.EXPECT().IsConfigFile(gomock.Any()).Return(tc.IsConfigFile).AnyTimes()
loader.EXPECT().LoadConfig().Return(configs.NewEmptyConfig(), nil).AnyTimes()
loader.EXPECT().LoadAnnotations().Return(map[string]tflint.Annotations{}, nil).AnyTimes()
loader.EXPECT().LoadConfig(tc.Dir).Return(configs.NewEmptyConfig(), nil).AnyTimes()
loader.EXPECT().LoadAnnotations(tc.Dir).Return(map[string]tflint.Annotations{}, nil).AnyTimes()
loader.EXPECT().LoadValuesFiles().Return([]terraform.InputValues{}, nil).AnyTimes()
cli.loader = loader

Expand Down
8 changes: 8 additions & 0 deletions cmd/test-fixtures/arguments/example/test.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
resource "aws_instance" "backend" {
ami = "ami-b73b63a0"
instance_type = "t1.2xlarge"

tags {
Name = "HelloWorld"
}
}
1 change: 1 addition & 0 deletions integration/arguments/dir/.terraform/modules/modules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"Modules":[{"Key":"","Source":"","Dir":"."},{"Key":"instances","Source":"./module","Dir":"module"}]}
3 changes: 3 additions & 0 deletions integration/arguments/dir/example.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
resource "aws_instance" "example" {
instance_type = "t1.2xlarge"
}
3 changes: 3 additions & 0 deletions integration/arguments/dir/module/template.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
resource "aws_instance" "example" {
instance_type = "t1.2xlarge"
}
8 changes: 8 additions & 0 deletions integration/arguments/dir/template.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
resource "aws_instance" "template" {
instance_type = "t1.2xlarge"
}

// `terraform get` in Terraform v0.12
module "instances" {
source = "./module"
}
10 changes: 10 additions & 0 deletions integration/arguments/result.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"detector": "aws_instance_invalid_type",
"type": "ERROR",
"message": "\"t1.2xlarge\" is invalid instance type.",
"line": 2,
"file": "dir/template.tf",
"link": ""
}
]
10 changes: 10 additions & 0 deletions integration/arguments/result_windows.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"detector": "aws_instance_invalid_type",
"type": "ERROR",
"message": "\"t1.2xlarge\" is invalid instance type.",
"line": 2,
"file": "dir\\template.tf",
"link": ""
}
]
3 changes: 3 additions & 0 deletions integration/arguments/template.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
resource "aws_instance" "default" {
instance_type = "t1.2xlarge"
}
Loading

0 comments on commit ae96a8f

Please sign in to comment.