Skip to content

Commit

Permalink
Add support for recursive init and print versions (#1629)
Browse files Browse the repository at this point in the history
  • Loading branch information
wata727 authored Dec 25, 2022
1 parent 017026f commit f221b31
Show file tree
Hide file tree
Showing 13 changed files with 227 additions and 129 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,9 @@ Application Options:
--var-file=FILE Terraform variable file name
--var='foo=bar' Set a Terraform variable
--module Inspect modules
--chdir=DIR Switch to a different working directory before running inspection
--chdir=DIR Switch to a different working directory before executing the command
--recursive Run command in each directory recursively
--force Return zero exit status even if issues found
--recursive Inspect directories recursively
--color Enable colorized output
--no-color Disable colorized output
Expand Down
55 changes: 55 additions & 0 deletions cmd/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,3 +178,58 @@ func unknownOptionHandler(option string, arg flags.SplitArgument, args []string)
}
return []string{}, fmt.Errorf("`%s` is unknown option. Please run `tflint --help`", option)
}

func findWorkingDirs(opts Options) ([]string, error) {
if opts.Recursive && opts.Chdir != "" {
return []string{}, errors.New("cannot use --recursive and --chdir at the same time")
}

workingDirs := []string{}

if opts.Recursive {
// NOTE: The target directory is always the current directory in recursive mode
err := filepath.WalkDir(".", func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() {
return nil
}
// hidden directories are skipped
if path != "." && strings.HasPrefix(d.Name(), ".") {
return filepath.SkipDir
}

workingDirs = append(workingDirs, path)
return nil
})
if err != nil {
return []string{}, err
}
} else {
if opts.Chdir == "" {
workingDirs = []string{"."}
} else {
workingDirs = []string{opts.Chdir}
}
}

return workingDirs, nil
}

func (cli *CLI) withinChangedDir(dir string, proc func() error) (err error) {
if dir != "." {
chErr := os.Chdir(dir)
if chErr != nil {
return fmt.Errorf("Failed to switch to a different working directory; %w", chErr)
}
defer func() {
chErr := os.Chdir(cli.originalWorkingDir)
if chErr != nil {
err = fmt.Errorf("Failed to switch to the original working directory; %s; %w", chErr, err)
}
}()
}

return proc()
}
83 changes: 51 additions & 32 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,54 +11,73 @@ import (
)

func (cli *CLI) init(opts Options) int {
if opts.Chdir != "" {
fmt.Fprintf(cli.errStream, "Cannot use --chdir with --init\n")
workingDirs, err := findWorkingDirs(opts)
if err != nil {
cli.formatter.Print(tflint.Issues{}, fmt.Errorf("Failed to find workspaces; %w", err), map[string][]byte{})
return ExitCodeError
}

if opts.Recursive {
fmt.Fprintf(cli.errStream, "Cannot use --recursive with --init\n")
return ExitCodeError
fmt.Fprint(cli.outStream, "Installing plugins on each working directory...\n\n")
}

cfg, err := tflint.LoadConfig(afero.Afero{Fs: afero.NewOsFs()}, opts.Config)
if err != nil {
cli.formatter.Print(tflint.Issues{}, fmt.Errorf("Failed to load TFLint config; %w", err), map[string][]byte{})
return ExitCodeError
}
for _, wd := range workingDirs {
err := cli.withinChangedDir(wd, func() error {
if opts.Recursive {
fmt.Fprint(cli.outStream, "====================================================\n")
fmt.Fprintf(cli.outStream, "working directory: %s\n\n", wd)
}

for _, pluginCfg := range cfg.Plugins {
installCfg := plugin.NewInstallConfig(cfg, pluginCfg)
cfg, err := tflint.LoadConfig(afero.Afero{Fs: afero.NewOsFs()}, opts.Config)
if err != nil {
return fmt.Errorf("Failed to load TFLint config; %w", err)
}

// If version or source is not set, you need to install it manually
if installCfg.ManuallyInstalled() {
continue
}
found := false
for _, pluginCfg := range cfg.Plugins {
installCfg := plugin.NewInstallConfig(cfg, pluginCfg)

_, err := plugin.FindPluginPath(installCfg)
if os.IsNotExist(err) {
fmt.Fprintf(cli.outStream, "Installing `%s` plugin...\n", pluginCfg.Name)
// If version or source is not set, you need to install it manually
if installCfg.ManuallyInstalled() {
continue
}
found = true

sigchecker := plugin.NewSignatureChecker(installCfg)
if !sigchecker.HasSigningKey() {
_, _ = color.New(color.FgYellow).Fprintln(cli.outStream, "No signing key configured. Set `signing_key` to verify that the release is signed by the plugin developer")
}
_, err := plugin.FindPluginPath(installCfg)
if os.IsNotExist(err) {
fmt.Fprintf(cli.outStream, "Installing `%s` plugin...\n", pluginCfg.Name)

_, err = installCfg.Install()
if err != nil {
cli.formatter.Print(tflint.Issues{}, fmt.Errorf("Failed to install a plugin; %w", err), map[string][]byte{})
return ExitCodeError
sigchecker := plugin.NewSignatureChecker(installCfg)
if !sigchecker.HasSigningKey() {
_, _ = color.New(color.FgYellow).Fprintln(cli.outStream, "No signing key configured. Set `signing_key` to verify that the release is signed by the plugin developer")
}

_, err = installCfg.Install()
if err != nil {
return fmt.Errorf("Failed to install a plugin; %w", err)
}

fmt.Fprintf(cli.outStream, "Installed `%s` (source: %s, version: %s)\n", pluginCfg.Name, pluginCfg.Source, pluginCfg.Version)
continue
}

if err != nil {
return fmt.Errorf("Failed to find a plugin; %w", err)
}

fmt.Fprintf(cli.outStream, "Plugin `%s` is already installed\n", pluginCfg.Name)
}

fmt.Fprintf(cli.outStream, "Installed `%s` (source: %s, version: %s)\n", pluginCfg.Name, pluginCfg.Source, pluginCfg.Version)
continue
}
if opts.Recursive && !found {
fmt.Fprint(cli.outStream, "No plugins to install\n")
}

return nil
})
if err != nil {
cli.formatter.Print(tflint.Issues{}, fmt.Errorf("Failed to find a plugin; %w", err), map[string][]byte{})
cli.formatter.Print(tflint.Issues{}, err, map[string][]byte{})
return ExitCodeError
}

fmt.Fprintf(cli.outStream, "Plugin `%s` is already installed\n", pluginCfg.Name)
}

return ExitCodeOK
Expand Down
52 changes: 4 additions & 48 deletions cmd/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package cmd

import (
"fmt"
"os"
"path/filepath"
"strings"

"github.com/hashicorp/hcl/v2"
Expand All @@ -29,35 +27,10 @@ func (cli *CLI) inspect(opts Options, targetDir string, filterFiles []string) in
return ExitCodeError
}

workingDirs := []string{}

if opts.Recursive {
// NOTE: The target directory is always the current directory in recursive mode
err := filepath.WalkDir(".", func(path string, d os.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() {
return nil
}
// hidden directories are skipped
if path != "." && strings.HasPrefix(d.Name(), ".") {
return filepath.SkipDir
}

workingDirs = append(workingDirs, path)
return nil
})
if err != nil {
cli.formatter.Print(tflint.Issues{}, err, map[string][]byte{})
return ExitCodeError
}
} else {
if opts.Chdir == "" {
workingDirs = []string{"."}
} else {
workingDirs = []string{opts.Chdir}
}
workingDirs, err := findWorkingDirs(opts)
if err != nil {
cli.formatter.Print(tflint.Issues{}, fmt.Errorf("Failed to find workspaces; %w", err), map[string][]byte{})
return ExitCodeError
}

issues := tflint.Issues{}
Expand Down Expand Up @@ -165,23 +138,6 @@ func (cli *CLI) inspectModule(opts Options, dir string, filterFiles []string) (t
return issues, nil
}

func (cli *CLI) withinChangedDir(dir string, proc func() error) (err error) {
if dir != "." {
chErr := os.Chdir(dir)
if chErr != nil {
return fmt.Errorf("Failed to switch to a different working directory; %w", chErr)
}
defer func() {
chErr := os.Chdir(cli.originalWorkingDir)
if chErr != nil {
err = fmt.Errorf("Failed to switch to the original working directory; %s; %w", chErr, err)
}
}()
}

return proc()
}

func (cli *CLI) setupRunners(opts Options, dir string) ([]*tflint.Runner, error) {
configs, diags := cli.loader.LoadConfig(dir, cli.config.Module)
if diags.HasErrors() {
Expand Down
4 changes: 2 additions & 2 deletions cmd/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ type Options struct {
Varfiles []string `long:"var-file" description:"Terraform variable file name" value-name:"FILE"`
Variables []string `long:"var" description:"Set a Terraform variable" value-name:"'foo=bar'"`
Module bool `long:"module" description:"Inspect modules"`
Chdir string `long:"chdir" description:"Switch to a different working directory before running inspection" value-name:"DIR"`
Recursive bool `long:"recursive" description:"Inspect directories recursively"`
Chdir string `long:"chdir" description:"Switch to a different working directory before executing the command" value-name:"DIR"`
Recursive bool `long:"recursive" description:"Run command in each directory recursively"`
Force bool `long:"force" description:"Return zero exit status even if issues found"`
Color bool `long:"color" description:"Enable colorized output"`
NoColor bool `long:"no-color" description:"Disable colorized output"`
Expand Down
1 change: 0 additions & 1 deletion cmd/test-fixtures/arguments/README

This file was deleted.

8 changes: 0 additions & 8 deletions cmd/test-fixtures/arguments/example/test.tf

This file was deleted.

8 changes: 0 additions & 8 deletions cmd/test-fixtures/arguments/template.tf

This file was deleted.

8 changes: 0 additions & 8 deletions cmd/test-fixtures/arguments/test.tf

This file was deleted.

51 changes: 37 additions & 14 deletions cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,60 @@ import (
)

func (cli *CLI) printVersion(opts Options) int {
if opts.Chdir != "" {
fmt.Fprintf(cli.errStream, "Cannot use --chdir with --version\n")
fmt.Fprintf(cli.outStream, "TFLint version %s\n", tflint.Version)

workingDirs, err := findWorkingDirs(opts)
if err != nil {
cli.formatter.Print(tflint.Issues{}, fmt.Errorf("Failed to find workspaces; %w", err), map[string][]byte{})
return ExitCodeError
}

if opts.Recursive {
fmt.Fprintf(cli.errStream, "Cannot use --recursive with --version\n")
return ExitCodeError
fmt.Fprint(cli.outStream, "\n")
}

fmt.Fprintf(cli.outStream, "TFLint version %s\n", tflint.Version)
for _, wd := range workingDirs {
err := cli.withinChangedDir(wd, func() error {
if opts.Recursive {
fmt.Fprint(cli.outStream, "====================================================\n")
fmt.Fprintf(cli.outStream, "working directory: %s\n\n", wd)
}

versions := getPluginVersions(opts)

for _, version := range versions {
fmt.Fprint(cli.outStream, version)
}
if len(versions) == 0 && opts.Recursive {
fmt.Fprint(cli.outStream, "No plugins\n")
}
return nil
})
if err != nil {
cli.formatter.Print(tflint.Issues{}, err, map[string][]byte{})
}
}

return ExitCodeOK
}

func getPluginVersions(opts Options) []string {
// Load configuration files to print plugin versions
cfg, err := tflint.LoadConfig(afero.Afero{Fs: afero.NewOsFs()}, opts.Config)
if err != nil {
log.Printf("[ERROR] Failed to load TFLint config: %s", err)
return ExitCodeOK
}
if len(opts.Only) > 0 {
for _, rule := range cfg.Rules {
rule.Enabled = false
}
return []string{}
}
cfg.Merge(opts.toConfig())

rulesetPlugin, err := plugin.Discovery(cfg)
if err != nil {
log.Printf("[ERROR] Failed to initialize plugins: %s", err)
return ExitCodeOK
return []string{}
}
defer rulesetPlugin.Clean()

versions := []string{}
for _, ruleset := range rulesetPlugin.RuleSets {
name, err := ruleset.RuleSetName()
if err != nil {
Expand All @@ -53,8 +76,8 @@ func (cli *CLI) printVersion(opts Options) int {
continue
}

fmt.Fprintf(cli.outStream, "+ ruleset.%s (%s)\n", name, version)
versions = append(versions, fmt.Sprintf("+ ruleset.%s (%s)\n", name, version))
}

return ExitCodeOK
return versions
}
8 changes: 8 additions & 0 deletions docs/user-guide/working-directory.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,11 @@ $ tflint --recursive
```

It takes no arguments in recursive mode. Passing a directory or file name will result in an error.

These flags are also valid for `--init` and `--version`. Recursive init is required when installing required plugins all at once:

```console
$ tflint --recursive --init
$ tflint --recursive --version
$ tflint --recursive
```
2 changes: 1 addition & 1 deletion integrationtest/init/basic/.tflint.hcl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugin "aws" {
enabled = true

version = "0.5.0"
version = "0.21.1"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
Loading

0 comments on commit f221b31

Please sign in to comment.