github.com/tetrafolium/tflint@v0.8.0/cmd/cli.go (about)

     1  package cmd
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  
     9  	"github.com/fatih/color"
    10  	flags "github.com/jessevdk/go-flags"
    11  
    12  	"github.com/wata727/tflint/issue"
    13  	"github.com/wata727/tflint/printer"
    14  	"github.com/wata727/tflint/project"
    15  	"github.com/wata727/tflint/rules"
    16  	"github.com/wata727/tflint/tflint"
    17  )
    18  
    19  // Exit codes are int values that represent an exit code for a particular error.
    20  const (
    21  	ExitCodeOK    int = 0
    22  	ExitCodeError int = 1 + iota
    23  	ExitCodeIssuesFound
    24  )
    25  
    26  // CLI is the command line object
    27  type CLI struct {
    28  	// outStream and errStream are the stdout and stderr
    29  	// to write message from the CLI.
    30  	outStream, errStream io.Writer
    31  	loader               tflint.AbstractLoader
    32  	testMode             bool
    33  }
    34  
    35  // NewCLI returns new CLI initialized by input streams
    36  func NewCLI(outStream io.Writer, errStream io.Writer) *CLI {
    37  	return &CLI{
    38  		outStream: outStream,
    39  		errStream: errStream,
    40  	}
    41  }
    42  
    43  // Run invokes the CLI with the given arguments.
    44  func (cli *CLI) Run(args []string) int {
    45  	var opts Options
    46  	parser := flags.NewParser(&opts, flags.HelpFlag)
    47  	parser.Usage = "[OPTIONS]"
    48  	parser.UnknownOptionHandler = func(option string, arg flags.SplitArgument, args []string) ([]string, error) {
    49  		if option == "debug" {
    50  			return []string{}, errors.New("`debug` option was removed in v0.8.0. Please set `TFLINT_LOG` environment variables instead")
    51  		}
    52  		return []string{}, fmt.Errorf("`%s` is unknown option. Please run `tflint --help`", option)
    53  	}
    54  	// Parse commandline flag
    55  	args, err := parser.ParseArgs(args)
    56  	if err != nil {
    57  		if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp {
    58  			fmt.Fprintln(cli.outStream, err)
    59  			return ExitCodeOK
    60  		}
    61  		cli.printError(err)
    62  		return ExitCodeError
    63  	}
    64  	argFiles := args[1:]
    65  
    66  	// Show version
    67  	if opts.Version {
    68  		fmt.Fprintf(cli.outStream, "TFLint version %s\n", project.Version)
    69  		return ExitCodeOK
    70  	}
    71  
    72  	// Setup config
    73  	cfg, err := tflint.LoadConfig(opts.Config)
    74  	if err != nil {
    75  		cli.printError(fmt.Errorf("Failed to load TFLint config: %s", err))
    76  		return ExitCodeError
    77  	}
    78  	cfg = cfg.Merge(opts.toConfig())
    79  
    80  	// Load Terraform's configurations
    81  	if !cli.testMode {
    82  		cli.loader, err = tflint.NewLoader()
    83  		if err != nil {
    84  			cli.printError(fmt.Errorf("Failed to prepare loading: %s", err))
    85  			return ExitCodeError
    86  		}
    87  	}
    88  	for _, file := range argFiles {
    89  		if fileInfo, err := os.Stat(file); os.IsNotExist(err) {
    90  			cli.printError(fmt.Errorf("Failed to load `%s`: File not found", file))
    91  			return ExitCodeError
    92  		} else if fileInfo.IsDir() {
    93  			cli.printError(fmt.Errorf("Failed to load `%s`: TFLint doesn't accept directories as arguments", file))
    94  			return ExitCodeError
    95  		}
    96  
    97  		if !cli.loader.IsConfigFile(file) {
    98  			cli.printError(fmt.Errorf("Failed to load `%s`: File is not a target of Terraform", file))
    99  			return ExitCodeError
   100  		}
   101  	}
   102  	configs, err := cli.loader.LoadConfig()
   103  	if err != nil {
   104  		cli.printError(fmt.Errorf("Failed to load configurations: %s", err))
   105  		return ExitCodeError
   106  	}
   107  	valuesFiles, err := cli.loader.LoadValuesFiles(cfg.Varfile...)
   108  	if err != nil {
   109  		cli.printError(fmt.Errorf("Failed to load values files: %s", err))
   110  		return ExitCodeError
   111  	}
   112  
   113  	// Check configurations via Runner
   114  	runner := tflint.NewRunner(cfg, configs, valuesFiles...)
   115  	runners, err := tflint.NewModuleRunners(runner)
   116  	if err != nil {
   117  		cli.printError(fmt.Errorf("Failed to prepare rule checking: %s", err))
   118  		return ExitCodeError
   119  	}
   120  	runners = append(runners, runner)
   121  
   122  	for _, rule := range rules.NewRules(cfg) {
   123  		for _, runner := range runners {
   124  			err := rule.Check(runner)
   125  			if err != nil {
   126  				cli.printError(fmt.Errorf("Failed to check `%s` rule: %s", rule.Name(), err))
   127  				return ExitCodeError
   128  			}
   129  		}
   130  	}
   131  
   132  	issues := []*issue.Issue{}
   133  	for _, runner := range runners {
   134  		issues = append(issues, runner.LookupIssues(argFiles...)...)
   135  	}
   136  
   137  	// Print issues
   138  	printer.NewPrinter(cli.outStream, cli.errStream).Print(issues, opts.Format, opts.Quiet)
   139  
   140  	if opts.ErrorWithIssues && len(issues) > 0 {
   141  		return ExitCodeIssuesFound
   142  	}
   143  
   144  	return ExitCodeOK
   145  }
   146  
   147  func (cli *CLI) printError(err error) {
   148  	fmt.Fprintln(cli.errStream, color.New(color.FgRed).Sprintf("Error: ")+err.Error())
   149  }