github.com/jpreese/tflint@v0.19.2-0.20200908152133-b01686250fb6/cmd/cli.go (about) 1 package cmd 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "log" 8 "os" 9 "path/filepath" 10 "strings" 11 12 "github.com/fatih/color" 13 "github.com/hashicorp/logutils" 14 flags "github.com/jessevdk/go-flags" 15 16 "github.com/terraform-linters/tflint/formatter" 17 "github.com/terraform-linters/tflint/tflint" 18 ) 19 20 // Exit codes are int values that represent an exit code for a particular error. 21 const ( 22 ExitCodeOK int = 0 23 ExitCodeError int = 1 + iota 24 ExitCodeIssuesFound 25 ) 26 27 // CLI is the command line object 28 type CLI struct { 29 // outStream and errStream are the stdout and stderr 30 // to write message from the CLI. 31 outStream, errStream io.Writer 32 loader tflint.AbstractLoader 33 formatter *formatter.Formatter 34 testMode bool 35 } 36 37 // NewCLI returns new CLI initialized by input streams 38 func NewCLI(outStream io.Writer, errStream io.Writer) *CLI { 39 return &CLI{ 40 outStream: outStream, 41 errStream: errStream, 42 } 43 } 44 45 // Run invokes the CLI with the given arguments. 46 func (cli *CLI) Run(args []string) int { 47 var opts Options 48 parser := flags.NewParser(&opts, flags.HelpFlag) 49 parser.Usage = "[OPTIONS] [FILE or DIR...]" 50 parser.UnknownOptionHandler = unknownOptionHandler 51 // Parse commandline flag 52 args, err := parser.ParseArgs(args) 53 // Set up output formatter 54 cli.formatter = &formatter.Formatter{ 55 Stdout: cli.outStream, 56 Stderr: cli.errStream, 57 Format: opts.Format, 58 } 59 if opts.NoColor { 60 color.NoColor = true 61 cli.formatter.NoColor = true 62 } 63 level := os.Getenv("TFLINT_LOG") 64 if opts.LogLevel != "" { 65 level = opts.LogLevel 66 } 67 log.SetOutput(&logutils.LevelFilter{ 68 Levels: []logutils.LogLevel{"TRACE", "DEBUG", "INFO", "WARN", "ERROR"}, 69 MinLevel: logutils.LogLevel(strings.ToUpper(level)), 70 Writer: os.Stderr, 71 }) 72 log.SetFlags(log.Ltime | log.Lshortfile) 73 74 if err != nil { 75 if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp { 76 fmt.Fprintln(cli.outStream, err) 77 return ExitCodeOK 78 } 79 cli.formatter.Print(tflint.Issues{}, tflint.NewContextError("Failed to parse CLI options", err), map[string][]byte{}) 80 return ExitCodeError 81 } 82 dir, filterFiles, err := processArgs(args[1:]) 83 if err != nil { 84 cli.formatter.Print(tflint.Issues{}, tflint.NewContextError("Failed to parse CLI arguments", err), map[string][]byte{}) 85 return ExitCodeError 86 } 87 88 switch { 89 case opts.Version: 90 return cli.printVersion(opts) 91 case opts.Langserver: 92 return cli.startLanguageServer(opts.Config, opts.toConfig()) 93 default: 94 return cli.inspect(opts, dir, filterFiles) 95 } 96 } 97 98 func processArgs(args []string) (string, []string, error) { 99 if len(args) == 0 { 100 return ".", []string{}, nil 101 } 102 103 var dir string 104 filterFiles := []string{} 105 106 for _, file := range args { 107 fileInfo, err := os.Stat(file) 108 if err != nil { 109 if os.IsNotExist(err) { 110 return dir, filterFiles, fmt.Errorf("Failed to load `%s`: File not found", file) 111 } 112 return dir, filterFiles, fmt.Errorf("Failed to load `%s`: %s", file, err) 113 } 114 115 if fileInfo.IsDir() { 116 dir = file 117 if len(args) != 1 { 118 return dir, filterFiles, fmt.Errorf("Failed to load `%s`: Multiple arguments are not allowed when passing a directory", file) 119 } 120 return dir, filterFiles, nil 121 } 122 123 if !strings.HasSuffix(file, ".tf") && !strings.HasSuffix(file, ".tf.json") { 124 return dir, filterFiles, fmt.Errorf("Failed to load `%s`: File is not a target of Terraform", file) 125 } 126 127 fileDir := filepath.Dir(file) 128 if dir == "" { 129 dir = fileDir 130 filterFiles = append(filterFiles, file) 131 } else if fileDir == dir { 132 filterFiles = append(filterFiles, file) 133 } else { 134 return dir, filterFiles, fmt.Errorf("Failed to load `%s`: Multiple files in different directories are not allowed", file) 135 } 136 } 137 138 return dir, filterFiles, nil 139 } 140 141 func unknownOptionHandler(option string, arg flags.SplitArgument, args []string) ([]string, error) { 142 if option == "debug" { 143 return []string{}, errors.New("`debug` option was removed in v0.8.0. Please set `TFLINT_LOG` environment variables instead") 144 } 145 if option == "fast" { 146 return []string{}, errors.New("`fast` option was removed in v0.9.0. The `aws_instance_invalid_ami` rule is already fast enough") 147 } 148 if option == "error-with-issues" { 149 return []string{}, errors.New("`error-with-issues` option was removed in v0.9.0. The behavior is now default") 150 } 151 if option == "quiet" || option == "q" { 152 return []string{}, errors.New("`quiet` option was removed in v0.11.0. The behavior is now default") 153 } 154 if option == "ignore-rule" { 155 return []string{}, errors.New("`ignore-rule` option was removed in v0.12.0. Please use `--disable-rule` instead") 156 } 157 return []string{}, fmt.Errorf("`%s` is unknown option. Please run `tflint --help`", option) 158 }