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 }