From 9c0fc3815088b41134cc633ba17796345c044f74 Mon Sep 17 00:00:00 2001 From: wata_mac Date: Tue, 3 Nov 2020 17:21:54 +0900 Subject: [PATCH] plugin: Allow to declare custom attributes in config files --- cmd/inspect.go | 5 +- go.mod | 2 +- go.sum | 4 +- langserver/handler.go | 4 +- plugin/discovery.go | 8 ++-- plugin/plugin.go | 4 +- plugin/stub-generator/sources/bar/main.go | 2 +- plugin/stub-generator/sources/example/main.go | 2 +- plugin/stub-generator/sources/foo/main.go | 2 +- tflint/config.go | 44 +++++++++++++++-- tflint/config_test.go | 47 +++++++++++-------- tflint/test-fixtures/config/plugin.hcl | 21 +++++++++ 12 files changed, 105 insertions(+), 40 deletions(-) create mode 100644 tflint/test-fixtures/config/plugin.hcl diff --git a/cmd/inspect.go b/cmd/inspect.go index 9149a39f2..52657dd85 100644 --- a/cmd/inspect.go +++ b/cmd/inspect.go @@ -62,10 +62,11 @@ func (cli *CLI) inspect(opts Options, dir string, filterFiles []string) int { } } - for _, ruleset := range plugin.RuleSets { - err = ruleset.ApplyConfig(cfg.ToPluginConfig()) + for name, ruleset := range plugin.RuleSets { + err = ruleset.ApplyConfig(cfg.ToPluginConfig(name)) if err != nil { cli.formatter.Print(tflint.Issues{}, tflint.NewContextError("Failed to apply config to plugins", err), cli.loader.Sources()) + return ExitCodeError } for _, runner := range runners { err = ruleset.Check(tfplugin.NewServer(runner, cli.loader.Sources())) diff --git a/go.mod b/go.mod index 629910ccc..2f8766b9b 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/sourcegraph/go-lsp v0.0.0-20181119182933-0c7d621186c1 github.com/sourcegraph/jsonrpc2 v0.0.0-20190106185902-35a74f039c6a github.com/spf13/afero v1.4.1 - github.com/terraform-linters/tflint-plugin-sdk v0.5.1-0.20201024120523-69843955cc45 + github.com/terraform-linters/tflint-plugin-sdk v0.5.1-0.20201031131801-2f5c943768d5 github.com/terraform-providers/terraform-provider-aws v1.60.1-0.20201015205411-546f68d4a935 github.com/zclconf/go-cty v1.7.0 golang.org/x/lint v0.0.0-20200302205851-738671d3881b diff --git a/go.sum b/go.sum index 38ba67744..adba6ed0b 100644 --- a/go.sum +++ b/go.sum @@ -595,8 +595,8 @@ github.com/tencentcloud/tencentcloud-sdk-go v3.0.82+incompatible h1:5Td2b0yfaOvw github.com/tencentcloud/tencentcloud-sdk-go v3.0.82+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4= github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20190808065407-f07404cefc8c h1:iRD1CqtWUjgEVEmjwTMbP1DMzz1HRytOsgx/rlw/vNs= github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20190808065407-f07404cefc8c/go.mod h1:wk2XFUg6egk4tSDNZtXeKfe2G6690UVyt163PuUxBZk= -github.com/terraform-linters/tflint-plugin-sdk v0.5.1-0.20201024120523-69843955cc45 h1:FHFTMNt+RyONj0NoDznEuMwxaE1ZKp+KjwgZiW9+6JA= -github.com/terraform-linters/tflint-plugin-sdk v0.5.1-0.20201024120523-69843955cc45/go.mod h1:Ho5IxOfE18lhc4KeXSfSxnoZWev34G2617ke+GA+Frc= +github.com/terraform-linters/tflint-plugin-sdk v0.5.1-0.20201031131801-2f5c943768d5 h1:vmcJ+Ie9avZ1gTrV5i7ikFp8l4Y43id0KWBUybELK6w= +github.com/terraform-linters/tflint-plugin-sdk v0.5.1-0.20201031131801-2f5c943768d5/go.mod h1:Ho5IxOfE18lhc4KeXSfSxnoZWev34G2617ke+GA+Frc= github.com/terraform-providers/terraform-provider-aws v1.60.1-0.20201015205411-546f68d4a935 h1:PbobnAeVvdzE1/qqTYxaB9h/YIpHCZXbCRBaXNIi0qA= github.com/terraform-providers/terraform-provider-aws v1.60.1-0.20201015205411-546f68d4a935/go.mod h1:DdjydHaAmjsZl+uZ4QLwfx9iP+trTBMjEqLeAV9/OFE= github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= diff --git a/langserver/handler.go b/langserver/handler.go index 834f19f66..4104aad09 100644 --- a/langserver/handler.go +++ b/langserver/handler.go @@ -172,8 +172,8 @@ func (h *handler) inspect() (map[string][]lsp.Diagnostic, error) { } } - for _, ruleset := range h.plugin.RuleSets { - err = ruleset.ApplyConfig(h.config.ToPluginConfig()) + for name, ruleset := range h.plugin.RuleSets { + err = ruleset.ApplyConfig(h.config.ToPluginConfig(name)) if err != nil { return ret, fmt.Errorf("Failed to apply config to plugins: %s", err) } diff --git a/plugin/discovery.go b/plugin/discovery.go index bfbf3397a..8f26b48af 100644 --- a/plugin/discovery.go +++ b/plugin/discovery.go @@ -43,8 +43,8 @@ func Discovery(config *tflint.Config) (*Plugin, error) { } func findPlugins(config *tflint.Config, dir string) (*Plugin, error) { - clients := []*plugin.Client{} - rulesets := []*tfplugin.Client{} + clients := map[string]*plugin.Client{} + rulesets := map[string]*tfplugin.Client{} for _, cfg := range config.Plugins { pluginPath, err := getPluginPath(dir, cfg.Name) @@ -68,8 +68,8 @@ func findPlugins(config *tflint.Config, dir string) (*Plugin, error) { } ruleset := raw.(*tfplugin.Client) - clients = append(clients, client) - rulesets = append(rulesets, ruleset) + clients[cfg.Name] = client + rulesets[cfg.Name] = ruleset } else { log.Printf("[INFO] Plugin `%s` found, but the plugin is disabled", cfg.Name) } diff --git a/plugin/plugin.go b/plugin/plugin.go index 8dd4c9192..309ddf78f 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -13,9 +13,9 @@ var localPluginRoot = "./.tflint.d/plugins" // Plugin is an object handling plugins // Basically, it is a wrapper for go-plugin and provides an API to handle them collectively. type Plugin struct { - RuleSets []*tfplugin.Client + RuleSets map[string]*tfplugin.Client - clients []*plugin.Client + clients map[string]*plugin.Client } // Clean is a helper for ending plugin processes diff --git a/plugin/stub-generator/sources/bar/main.go b/plugin/stub-generator/sources/bar/main.go index 2c6eaf415..6eda257fc 100644 --- a/plugin/stub-generator/sources/bar/main.go +++ b/plugin/stub-generator/sources/bar/main.go @@ -7,7 +7,7 @@ import ( func main() { plugin.Serve(&plugin.ServeOpts{ - RuleSet: tflint.RuleSet{ + RuleSet: &tflint.BuiltinRuleSet{ Name: "bar", Version: "0.1.0", Rules: []tflint.Rule{}, diff --git a/plugin/stub-generator/sources/example/main.go b/plugin/stub-generator/sources/example/main.go index dd54bc51e..1f2ad29f3 100644 --- a/plugin/stub-generator/sources/example/main.go +++ b/plugin/stub-generator/sources/example/main.go @@ -10,7 +10,7 @@ import ( func main() { plugin.Serve(&plugin.ServeOpts{ - RuleSet: tflint.RuleSet{ + RuleSet: &tflint.BuiltinRuleSet{ Name: "example", Version: "0.1.0", Rules: []tflint.Rule{ diff --git a/plugin/stub-generator/sources/foo/main.go b/plugin/stub-generator/sources/foo/main.go index 8ef024a03..d58112cce 100644 --- a/plugin/stub-generator/sources/foo/main.go +++ b/plugin/stub-generator/sources/foo/main.go @@ -7,7 +7,7 @@ import ( func main() { plugin.Serve(&plugin.ServeOpts{ - RuleSet: tflint.RuleSet{ + RuleSet: &tflint.BuiltinRuleSet{ Name: "foo", Version: "0.1.0", Rules: []tflint.Rule{}, diff --git a/tflint/config.go b/tflint/config.go index b05ccbdbe..5a1d7ce31 100644 --- a/tflint/config.go +++ b/tflint/config.go @@ -9,6 +9,7 @@ import ( hcl "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/gohcl" "github.com/hashicorp/hcl/v2/hclparse" + "github.com/hashicorp/hcl/v2/hclsyntax" homedir "github.com/mitchellh/go-homedir" tfplugin "github.com/terraform-linters/tflint-plugin-sdk/tflint" "github.com/terraform-linters/tflint/client" @@ -65,8 +66,12 @@ type RuleConfig struct { // PluginConfig is a TFLint's plugin config type PluginConfig struct { - Name string `hcl:"name,label"` - Enabled bool `hcl:"enabled"` + Name string `hcl:"name,label"` + Enabled bool `hcl:"enabled"` + Body hcl.Body `hcl:",remain"` + + // file is the result of parsing the HCL file that declares the plugin configuration. + file *hcl.File } // EmptyConfig returns default config @@ -155,10 +160,15 @@ func (c *Config) Merge(other *Config) *Config { } // ToPluginConfig converts self into the plugin configuration format -func (c *Config) ToPluginConfig() *tfplugin.Config { - cfg := &tfplugin.Config{ +func (c *Config) ToPluginConfig(name string) *tfplugin.MarshalledConfig { + pluginCfg := c.Plugins[name] + cfgRange := configBodyRange(pluginCfg.Body) + + cfg := &tfplugin.MarshalledConfig{ Rules: map[string]*tfplugin.RuleConfig{}, DisabledByDefault: c.DisabledByDefault, + BodyBytes: cfgRange.SliceBytes(pluginCfg.file.Bytes), + BodyRange: cfgRange, } for _, rule := range c.Rules { cfg.Rules[rule.Name] = &tfplugin.RuleConfig{ @@ -274,6 +284,10 @@ func loadConfigFromFile(file string) (*Config, error) { } cfg := raw.toConfig() + for _, plugin := range cfg.Plugins { + plugin.file = f + } + log.Printf("[DEBUG] Config loaded") log.Printf("[DEBUG] Module: %t", cfg.Module) log.Printf("[DEBUG] DeepCheck: %t", cfg.DeepCheck) @@ -396,3 +410,25 @@ func (raw *rawConfig) toConfig() *Config { return ret } + +func configBodyRange(body hcl.Body) hcl.Range { + var bodyRange hcl.Range + + // Estimate the range of the body from the range of all attributes and blocks. + hclBody := body.(*hclsyntax.Body) + for _, attr := range hclBody.Attributes { + if bodyRange.Empty() { + bodyRange = attr.Range() + } else { + bodyRange = hcl.RangeOver(bodyRange, attr.Range()) + } + } + for _, block := range hclBody.Blocks { + if bodyRange.Empty() { + bodyRange = block.Range() + } else { + bodyRange = hcl.RangeOver(bodyRange, block.Range()) + } + } + return bodyRange +} diff --git a/tflint/config_test.go b/tflint/config_test.go index 948716dac..82ae249e3 100644 --- a/tflint/config_test.go +++ b/tflint/config_test.go @@ -115,10 +115,12 @@ func Test_LoadConfig(t *testing.T) { } opts := []cmp.Option{ + cmpopts.IgnoreUnexported(PluginConfig{}), + cmpopts.IgnoreFields(PluginConfig{}, "Body"), cmpopts.IgnoreFields(RuleConfig{}, "Body"), } if !cmp.Equal(tc.Expected, ret, opts...) { - t.Fatalf("Failed `%s` test: diff=%s", tc.Name, cmp.Diff(tc.Expected, ret)) + t.Fatalf("Failed `%s` test: diff=%s", tc.Name, cmp.Diff(tc.Expected, ret, opts...)) } defaultConfigFile = originalDefault @@ -576,6 +578,7 @@ func Test_Merge(t *testing.T) { ret := tc.Base.Merge(tc.Other) opts := []cmp.Option{ + cmpopts.IgnoreUnexported(PluginConfig{}), cmpopts.IgnoreUnexported(hclsyntax.Body{}), cmpopts.IgnoreFields(hclsyntax.Body{}, "Attributes", "Blocks"), } @@ -586,22 +589,18 @@ func Test_Merge(t *testing.T) { } func Test_ToPluginConfig(t *testing.T) { - config := &Config{ - Rules: map[string]*RuleConfig{ - "aws_instance_invalid_type": { - Name: "aws_instance_invalid_type", - Enabled: false, - }, - "aws_instance_invalid_ami": { - Name: "aws_instance_invalid_ami", - Enabled: true, - }, - }, - DisabledByDefault: true, + currentDir, err := os.Getwd() + if err != nil { + t.Fatal(err) } - ret := config.ToPluginConfig() - expected := &tfplugin.Config{ + config, err := LoadConfig(filepath.Join(currentDir, "test-fixtures", "config", "plugin.hcl")) + if err != nil { + t.Fatalf("Failed: Unexpected error occurred: %s", err) + } + + ret := config.ToPluginConfig("foo") + expected := &tfplugin.MarshalledConfig{ Rules: map[string]*tfplugin.RuleConfig{ "aws_instance_invalid_type": { Name: "aws_instance_invalid_type", @@ -613,9 +612,16 @@ func Test_ToPluginConfig(t *testing.T) { }, }, DisabledByDefault: true, + BodyRange: hcl.Range{Start: hcl.Pos{Line: 14, Column: 3}, End: hcl.Pos{Line: 16, Column: 17}}, + } + opts := cmp.Options{ + cmpopts.IgnoreUnexported(PluginConfig{}), + cmpopts.IgnoreFields(hcl.Range{}, "Filename"), + cmpopts.IgnoreFields(hcl.Pos{}, "Byte"), + cmpopts.IgnoreFields(tfplugin.MarshalledConfig{}, "BodyBytes"), } - if !cmp.Equal(expected, ret) { - t.Fatalf("Failed to match: %s", cmp.Diff(expected, ret)) + if !cmp.Equal(expected, ret, opts...) { + t.Fatalf("Failed to match: %s", cmp.Diff(expected, ret, opts...)) } } @@ -822,14 +828,15 @@ func Test_copy(t *testing.T) { }, } + opt := cmpopts.IgnoreUnexported(PluginConfig{}) for _, tc := range cases { ret := cfg.copy() - if !cmp.Equal(cfg, ret) { - t.Fatalf("The copied config doesn't match original: Diff=%s", cmp.Diff(cfg, ret)) + if !cmp.Equal(cfg, ret, opt) { + t.Fatalf("The copied config doesn't match original: Diff=%s", cmp.Diff(cfg, ret, opt)) } tc.SideEffect(ret) - if cmp.Equal(cfg, ret) { + if cmp.Equal(cfg, ret, opt) { t.Fatalf("The original was changed when updating `%s`", tc.Name) } } diff --git a/tflint/test-fixtures/config/plugin.hcl b/tflint/test-fixtures/config/plugin.hcl new file mode 100644 index 000000000..8f5c096a1 --- /dev/null +++ b/tflint/test-fixtures/config/plugin.hcl @@ -0,0 +1,21 @@ +config { + disabled_by_default = true +} + +rule "aws_instance_invalid_type" { + enabled = false +} + +rule "aws_instance_invalid_ami" { + enabled = true +} + +plugin "foo" { + enabled = true + + custom = "foo" +} + +plugin "bar" { + enabled = false +}