diff --git a/cmd/inspect.go b/cmd/inspect.go index 928b57f0f..0f1b180a7 100644 --- a/cmd/inspect.go +++ b/cmd/inspect.go @@ -83,7 +83,9 @@ func (cli *CLI) inspect(opts Options, args []string) int { if opts.Recursive { // Respect "--format" and "--force" flags in recursive mode cli.formatter.Format = opts.Format - force = opts.Force + if opts.Force != nil { + force = *opts.Force + } } else { cli.formatter.Format = cli.config.Format force = cli.config.Force diff --git a/cmd/option.go b/cmd/option.go index 46494c809..b28279ff8 100644 --- a/cmd/option.go +++ b/cmd/option.go @@ -21,11 +21,11 @@ type Options struct { EnablePlugins []string `long:"enable-plugin" description:"Enable plugins from the command line" value-name:"PLUGIN_NAME"` 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"` + Module *bool `long:"module" description:"Inspect modules"` 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"` Filter []string `long:"filter" description:"Filter issues by file names or globs" value-name:"FILE"` - Force bool `long:"force" description:"Return zero exit status even if issues found"` + Force *bool `long:"force" description:"Return zero exit status even if issues found"` MinimumFailureSeverity string `long:"minimum-failure-severity" description:"Sets minimum severity level for exiting with a non-zero error code" choice:"error" choice:"warning" choice:"notice"` Color bool `long:"color" description:"Enable colorized output"` NoColor bool `long:"no-color" description:"Disable colorized output"` @@ -50,20 +50,38 @@ func (opts *Options) toConfig() *tflint.Config { opts.Variables = []string{} } - log.Printf("[DEBUG] CLI Options") - log.Printf("[DEBUG] Module: %t", opts.Module) - log.Printf("[DEBUG] Force: %t", opts.Force) - log.Printf("[DEBUG] IgnoreModules:") - for name, ignore := range ignoreModules { - log.Printf("[DEBUG] %s: %t", name, ignore) + var module, moduleSet bool + if opts.Module == nil { + module = false + moduleSet = false + } else { + module = *opts.Module + moduleSet = true + } + + var force, forceSet bool + if opts.Force == nil { + force = false + forceSet = false + } else { + force = *opts.Force + forceSet = true } + + log.Printf("[DEBUG] CLI Options") + log.Printf("[DEBUG] Module: %t", module) + log.Printf("[DEBUG] Force: %t", force) + log.Printf("[DEBUG] Format: %s", opts.Format) + log.Printf("[DEBUG] Varfiles: %s", strings.Join(opts.Varfiles, ", ")) + log.Printf("[DEBUG] Variables: %s", strings.Join(opts.Variables, ", ")) log.Printf("[DEBUG] EnableRules: %s", strings.Join(opts.EnableRules, ", ")) log.Printf("[DEBUG] DisableRules: %s", strings.Join(opts.DisableRules, ", ")) log.Printf("[DEBUG] Only: %s", strings.Join(opts.Only, ", ")) log.Printf("[DEBUG] EnablePlugins: %s", strings.Join(opts.EnablePlugins, ", ")) - log.Printf("[DEBUG] Varfiles: %s", strings.Join(opts.Varfiles, ", ")) - log.Printf("[DEBUG] Variables: %s", strings.Join(opts.Variables, ", ")) - log.Printf("[DEBUG] Format: %s", opts.Format) + log.Printf("[DEBUG] IgnoreModules:") + for name, ignore := range ignoreModules { + log.Printf("[DEBUG] %s: %t", name, ignore) + } rules := map[string]*tflint.RuleConfig{} if len(opts.Only) > 0 { @@ -107,15 +125,23 @@ func (opts *Options) toConfig() *tflint.Config { } return &tflint.Config{ - Module: opts.Module, - Force: opts.Force, - IgnoreModules: ignoreModules, - Varfiles: varfiles, - Variables: opts.Variables, - DisabledByDefault: len(opts.Only) > 0, - Only: opts.Only, - Format: opts.Format, - Rules: rules, - Plugins: plugins, + Module: module, + ModuleSet: moduleSet, + + Force: force, + ForceSet: forceSet, + + Format: opts.Format, + FormatSet: opts.Format != "", + + DisabledByDefault: len(opts.Only) > 0, + DisabledByDefaultSet: len(opts.Only) > 0, + + Varfiles: varfiles, + Variables: opts.Variables, + Only: opts.Only, + IgnoreModules: ignoreModules, + Rules: rules, + Plugins: plugins, } } diff --git a/cmd/option_test.go b/cmd/option_test.go index fafd34332..b0b7de9b6 100644 --- a/cmd/option_test.go +++ b/cmd/option_test.go @@ -26,6 +26,7 @@ func Test_toConfig(t *testing.T) { Command: "./tflint --module", Expected: &tflint.Config{ Module: true, + ModuleSet: true, Force: false, IgnoreModules: map[string]bool{}, Varfiles: []string{}, @@ -41,6 +42,7 @@ func Test_toConfig(t *testing.T) { Expected: &tflint.Config{ Module: false, Force: true, + ForceSet: true, IgnoreModules: map[string]bool{}, Varfiles: []string{}, Variables: []string{}, @@ -173,13 +175,14 @@ func Test_toConfig(t *testing.T) { Name: "--only", Command: "./tflint --only aws_instance_invalid_type", Expected: &tflint.Config{ - Module: false, - Force: false, - IgnoreModules: map[string]bool{}, - Varfiles: []string{}, - Variables: []string{}, - DisabledByDefault: true, - Only: []string{"aws_instance_invalid_type"}, + Module: false, + Force: false, + IgnoreModules: map[string]bool{}, + Varfiles: []string{}, + Variables: []string{}, + DisabledByDefault: true, + DisabledByDefaultSet: true, + Only: []string{"aws_instance_invalid_type"}, Rules: map[string]*tflint.RuleConfig{ "aws_instance_invalid_type": { Name: "aws_instance_invalid_type", @@ -226,6 +229,7 @@ func Test_toConfig(t *testing.T) { Variables: []string{}, DisabledByDefault: false, Format: "compact", + FormatSet: true, Rules: map[string]*tflint.RuleConfig{}, Plugins: map[string]*tflint.PluginConfig{}, }, diff --git a/tflint/config.go b/tflint/config.go index 1325288ef..34edf3821 100644 --- a/tflint/config.go +++ b/tflint/config.go @@ -58,17 +58,27 @@ var validFormats = []string{ // Config describes the behavior of TFLint type Config struct { - Module bool - Force bool - IgnoreModules map[string]bool - Varfiles []string - Variables []string - DisabledByDefault bool - Only []string - PluginDir string - Format string - Rules map[string]*RuleConfig - Plugins map[string]*PluginConfig + Module bool + ModuleSet bool + + Force bool + ForceSet bool + + DisabledByDefault bool + DisabledByDefaultSet bool + + PluginDir string + PluginDirSet bool + + Format string + FormatSet bool + + Varfiles []string + Variables []string + Only []string + IgnoreModules map[string]bool + Rules map[string]*RuleConfig + Plugins map[string]*PluginConfig sources map[string][]byte } @@ -185,10 +195,12 @@ func loadConfig(file afero.File) (*Config, error) { for name, attr := range inner.Attributes { switch name { case "module": + config.ModuleSet = true if err := gohcl.DecodeExpression(attr.Expr, nil, &config.Module); err != nil { return config, err } case "force": + config.ForceSet = true if err := gohcl.DecodeExpression(attr.Expr, nil, &config.Force); err != nil { return config, err } @@ -205,14 +217,17 @@ func loadConfig(file afero.File) (*Config, error) { return config, err } case "disabled_by_default": + config.DisabledByDefaultSet = true if err := gohcl.DecodeExpression(attr.Expr, nil, &config.DisabledByDefault); err != nil { return config, err } case "plugin_dir": + config.PluginDirSet = true if err := gohcl.DecodeExpression(attr.Expr, nil, &config.PluginDir); err != nil { return config, err } case "format": + config.FormatSet = true if err := gohcl.DecodeExpression(attr.Expr, nil, &config.Format); err != nil { return config, err } @@ -252,16 +267,22 @@ func loadConfig(file afero.File) (*Config, error) { log.Printf("[DEBUG] Config loaded") log.Printf("[DEBUG] Module: %t", config.Module) + log.Printf("[DEBUG] ModuleSet: %t", config.ModuleSet) log.Printf("[DEBUG] Force: %t", config.Force) + log.Printf("[DEBUG] ForceSet: %t", config.ForceSet) + log.Printf("[DEBUG] DisabledByDefault: %t", config.DisabledByDefault) + log.Printf("[DEBUG] DisabledByDefaultSet: %t", config.DisabledByDefaultSet) + log.Printf("[DEBUG] PluginDir: %s", config.PluginDir) + log.Printf("[DEBUG] PluginDirSet: %t", config.PluginDirSet) + log.Printf("[DEBUG] Format: %s", config.Format) + log.Printf("[DEBUG] FormatSet: %t", config.FormatSet) + log.Printf("[DEBUG] Varfiles: %s", strings.Join(config.Varfiles, ", ")) + log.Printf("[DEBUG] Variables: %s", strings.Join(config.Variables, ", ")) + log.Printf("[DEBUG] Only: %s", strings.Join(config.Only, ", ")) log.Printf("[DEBUG] IgnoreModules:") for name, ignore := range config.IgnoreModules { log.Printf("[DEBUG] %s: %t", name, ignore) } - log.Printf("[DEBUG] Varfiles: %s", strings.Join(config.Varfiles, ", ")) - log.Printf("[DEBUG] Variables: %s", strings.Join(config.Variables, ", ")) - log.Printf("[DEBUG] DisabledByDefault: %t", config.DisabledByDefault) - log.Printf("[DEBUG] PluginDir: %s", config.PluginDir) - log.Printf("[DEBUG] Format: %s", config.Format) log.Printf("[DEBUG] Rules:") for name, rule := range config.Rules { log.Printf("[DEBUG] %s: %t", name, rule.Enabled) @@ -328,29 +349,35 @@ func (c *Config) Sources() map[string][]byte { // Merge merges the two configs and applies to itself. // Since the argument takes precedence, it can be used as overwriting of the config. func (c *Config) Merge(other *Config) { - if other.Module { - c.Module = true + if other.ModuleSet { + c.ModuleSet = true + c.Module = other.Module } - if other.Force { - c.Force = true + if other.ForceSet { + c.ForceSet = true + c.Force = other.Force } - if other.DisabledByDefault { - c.DisabledByDefault = true + if other.DisabledByDefaultSet { + c.DisabledByDefaultSet = true + c.DisabledByDefault = other.DisabledByDefault } - if other.PluginDir != "" { + if other.PluginDirSet { + c.PluginDirSet = true c.PluginDir = other.PluginDir } - if other.Format != "" { + if other.FormatSet { + c.FormatSet = true c.Format = other.Format } - for name, ignore := range other.IgnoreModules { - c.IgnoreModules[name] = ignore - } c.Varfiles = append(c.Varfiles, other.Varfiles...) c.Variables = append(c.Variables, other.Variables...) c.Only = append(c.Only, other.Only...) + for name, ignore := range other.IgnoreModules { + c.IgnoreModules[name] = ignore + } + for name, rule := range other.Rules { // HACK: If you enable the rule through the CLI instead of the file, its hcl.Body will be nil. // In this case, only override Enabled flag diff --git a/tflint/config_test.go b/tflint/config_test.go index 47c2ca20e..33372d561 100644 --- a/tflint/config_test.go +++ b/tflint/config_test.go @@ -72,8 +72,10 @@ plugin "baz" { }`, }, want: &Config{ - Module: true, - Force: true, + Module: true, + ModuleSet: true, + Force: true, + ForceSet: true, IgnoreModules: map[string]bool{ "github.com/terraform-linters/example-module": true, }, @@ -81,7 +83,9 @@ plugin "baz" { Variables: []string{"foo=bar", "bar=['foo']"}, DisabledByDefault: false, PluginDir: "~/.tflint.d/plugins", + PluginDirSet: true, Format: "compact", + FormatSet: true, Rules: map[string]*RuleConfig{ "aws_instance_invalid_type": { Name: "aws_instance_invalid_type", @@ -136,13 +140,15 @@ config { }`, }, want: &Config{ - Module: false, - Force: true, - IgnoreModules: map[string]bool{}, - Varfiles: []string{}, - Variables: []string{}, - DisabledByDefault: true, - Rules: map[string]*RuleConfig{}, + Module: false, + Force: true, + ForceSet: true, + IgnoreModules: map[string]bool{}, + Varfiles: []string{}, + Variables: []string{}, + DisabledByDefault: true, + DisabledByDefaultSet: true, + Rules: map[string]*RuleConfig{}, Plugins: map[string]*PluginConfig{ "terraform": { Name: "terraform", @@ -329,8 +335,10 @@ func TestMerge(t *testing.T) { } config := &Config{ - Module: true, - Force: true, + Module: true, + ModuleSet: true, + Force: true, + ForceSet: true, IgnoreModules: map[string]bool{ "github.com/terraform-linters/example-1": true, "github.com/terraform-linters/example-2": false, @@ -339,7 +347,9 @@ func TestMerge(t *testing.T) { Variables: []string{"foo=bar"}, DisabledByDefault: false, PluginDir: "./.tflint.d/plugins", + PluginDirSet: true, Format: "compact", + FormatSet: true, Rules: map[string]*RuleConfig{ "aws_instance_invalid_type": { Name: "aws_instance_invalid_type", @@ -382,17 +392,21 @@ func TestMerge(t *testing.T) { { name: "override and merge", base: &Config{ - Module: true, - Force: false, + Module: true, + ModuleSet: true, + Force: false, IgnoreModules: map[string]bool{ "github.com/terraform-linters/example-1": true, "github.com/terraform-linters/example-2": false, }, - Varfiles: []string{"example1.tfvars", "example2.tfvars"}, - Variables: []string{"foo=bar"}, - DisabledByDefault: false, - PluginDir: "./.tflint.d/plugins", - Format: "compact", + Varfiles: []string{"example1.tfvars", "example2.tfvars"}, + Variables: []string{"foo=bar"}, + DisabledByDefault: true, + DisabledByDefaultSet: true, + PluginDir: "./.tflint.d/plugins", + PluginDirSet: true, + Format: "compact", + FormatSet: true, Rules: map[string]*RuleConfig{ "aws_instance_invalid_type": { Name: "aws_instance_invalid_type", @@ -417,17 +431,21 @@ func TestMerge(t *testing.T) { }, }, other: &Config{ - Module: false, - Force: true, + Module: false, + Force: true, + ForceSet: true, IgnoreModules: map[string]bool{ "github.com/terraform-linters/example-2": true, "github.com/terraform-linters/example-3": false, }, - Varfiles: []string{"example3.tfvars"}, - Variables: []string{"bar=baz"}, - DisabledByDefault: false, - PluginDir: "~/.tflint.d/plugins", - Format: "json", + Varfiles: []string{"example3.tfvars"}, + Variables: []string{"bar=baz"}, + DisabledByDefault: false, + DisabledByDefaultSet: true, + PluginDir: "~/.tflint.d/plugins", + PluginDirSet: true, + Format: "json", + FormatSet: true, Rules: map[string]*RuleConfig{ "aws_instance_invalid_ami": { Name: "aws_instance_invalid_ami", @@ -452,18 +470,23 @@ func TestMerge(t *testing.T) { }, }, want: &Config{ - Module: true, - Force: true, + Module: true, + ModuleSet: true, + Force: true, + ForceSet: true, IgnoreModules: map[string]bool{ "github.com/terraform-linters/example-1": true, "github.com/terraform-linters/example-2": true, "github.com/terraform-linters/example-3": false, }, - Varfiles: []string{"example1.tfvars", "example2.tfvars", "example3.tfvars"}, - Variables: []string{"foo=bar", "bar=baz"}, - DisabledByDefault: false, - PluginDir: "~/.tflint.d/plugins", - Format: "json", + Varfiles: []string{"example1.tfvars", "example2.tfvars", "example3.tfvars"}, + Variables: []string{"foo=bar", "bar=baz"}, + DisabledByDefault: false, + DisabledByDefaultSet: true, + PluginDir: "~/.tflint.d/plugins", + PluginDirSet: true, + Format: "json", + FormatSet: true, Rules: map[string]*RuleConfig{ "aws_instance_invalid_type": { Name: "aws_instance_invalid_type", @@ -500,8 +523,9 @@ func TestMerge(t *testing.T) { { name: "CLI --only argument and merge", base: &Config{ - Module: true, - Force: false, + Module: true, + ModuleSet: true, + Force: false, IgnoreModules: map[string]bool{ "github.com/terraform-linters/example-1": true, "github.com/terraform-linters/example-2": false, @@ -533,16 +557,18 @@ func TestMerge(t *testing.T) { }, }, other: &Config{ - Module: false, - Force: true, + Module: false, + Force: true, + ForceSet: true, IgnoreModules: map[string]bool{ "github.com/terraform-linters/example-2": true, "github.com/terraform-linters/example-3": false, }, - Varfiles: []string{"example3.tfvars"}, - Variables: []string{"bar=baz"}, - DisabledByDefault: true, - Only: []string{"aws_instance_invalid_type", "aws_instance_previous_type"}, + Varfiles: []string{"example3.tfvars"}, + Variables: []string{"bar=baz"}, + DisabledByDefault: true, + DisabledByDefaultSet: true, + Only: []string{"aws_instance_invalid_type", "aws_instance_previous_type"}, Rules: map[string]*RuleConfig{ "aws_instance_invalid_type": { Name: "aws_instance_invalid_type", @@ -567,17 +593,20 @@ func TestMerge(t *testing.T) { }, }, want: &Config{ - Module: true, - Force: true, + Module: true, + ModuleSet: true, + Force: true, + ForceSet: true, IgnoreModules: map[string]bool{ "github.com/terraform-linters/example-1": true, "github.com/terraform-linters/example-2": true, "github.com/terraform-linters/example-3": false, }, - Varfiles: []string{"example1.tfvars", "example2.tfvars", "example3.tfvars"}, - Variables: []string{"foo=bar", "bar=baz"}, - DisabledByDefault: true, - Only: []string{"aws_instance_invalid_type", "aws_instance_previous_type"}, + Varfiles: []string{"example1.tfvars", "example2.tfvars", "example3.tfvars"}, + Variables: []string{"foo=bar", "bar=baz"}, + DisabledByDefault: true, + DisabledByDefaultSet: true, + Only: []string{"aws_instance_invalid_type", "aws_instance_previous_type"}, Rules: map[string]*RuleConfig{ "aws_instance_invalid_type": { Name: "aws_instance_invalid_type",