Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for recursive init and print versions #1629

Merged
merged 1 commit into from
Dec 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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