From 5cecedde353622c026236fb4a0644f1fbf55cb91 Mon Sep 17 00:00:00 2001 From: wata_mac Date: Fri, 31 Dec 2021 23:09:58 +0900 Subject: [PATCH] Add support for gRPC server for the new plugin system --- cmd/inspect.go | 24 ++- cmd/internal_aws_plugin.go | 2 - cmd/option_test.go | 1 + go.mod | 14 +- go.sum | 42 +++-- langserver/handler.go | 18 ++- plugin/discovery.go | 8 +- plugin/plugin.go | 4 +- plugin/server.go | 148 ++++++++++++++++++ plugin/stub-generator/sources/example/main.go | 33 +++- .../rules/aws_instance_example_type.go | 33 +++- .../sources/testing/rules/required_config.go | 4 +- tflint/config.go | 40 +++++ tflint/config_test.go | 3 + tflint/runner.go | 4 + tflint/runner_eval.go | 10 ++ 16 files changed, 340 insertions(+), 48 deletions(-) diff --git a/cmd/inspect.go b/cmd/inspect.go index ada283eee..ebb30d7c5 100644 --- a/cmd/inspect.go +++ b/cmd/inspect.go @@ -70,11 +70,31 @@ func (cli *CLI) inspect(opts Options, dir string, filterFiles []string) int { rulesets := []tflint.RuleSet{&rules.RuleSet{}} for name, ruleset := range plugin.RuleSets { - err = ruleset.ApplyConfig(cfg.ToPluginConfig(name)) + config, err := cfg.ToPluginConfig(name).Unmarshal() + if err != nil { + cli.formatter.Print(tflint.Issues{}, tflint.NewContextError("Failed to fetch config schema from plugins", err), cli.loader.Sources()) + return ExitCodeError + } + if err := ruleset.ApplyGlobalConfig(config); err != nil { + cli.formatter.Print(tflint.Issues{}, tflint.NewContextError("Failed to fetch config schema from plugins", err), cli.loader.Sources()) + return ExitCodeError + } + configSchema, err := ruleset.ConfigSchema() + if err != nil { + cli.formatter.Print(tflint.Issues{}, tflint.NewContextError("Failed to fetch config schema from plugins", err), cli.loader.Sources()) + return ExitCodeError + } + content, diags := cfg.PluginContent(name, configSchema) + if diags.HasErrors() { + cli.formatter.Print(tflint.Issues{}, tflint.NewContextError("Failed to parse plugin config schema", diags), cli.loader.Sources()) + return ExitCodeError + } + err = ruleset.ApplyConfig(content, cfg.Sources()) if err != nil { cli.formatter.Print(tflint.Issues{}, tflint.NewContextError("Failed to apply config to plugins", err), cli.loader.Sources()) return ExitCodeError } + rulesets = append(rulesets, ruleset) } if err := cfg.ValidateRules(rulesets...); err != nil { @@ -95,7 +115,7 @@ func (cli *CLI) inspect(opts Options, dir string, filterFiles []string) int { for _, ruleset := range plugin.RuleSets { for _, runner := range runners { - err = ruleset.Check(tfplugin.NewServer(runner, rootRunner, cli.loader.Sources())) + err = ruleset.Check(tfplugin.NewGRPCServer(runner, rootRunner, cli.loader.Sources())) if err != nil { cli.formatter.Print(tflint.Issues{}, tflint.NewContextError("Failed to check ruleset", err), cli.loader.Sources()) return ExitCodeError diff --git a/cmd/internal_aws_plugin.go b/cmd/internal_aws_plugin.go index b79686181..557e31606 100644 --- a/cmd/internal_aws_plugin.go +++ b/cmd/internal_aws_plugin.go @@ -8,7 +8,6 @@ import ( "github.com/terraform-linters/tflint-ruleset-aws/aws" "github.com/terraform-linters/tflint-ruleset-aws/project" "github.com/terraform-linters/tflint-ruleset-aws/rules" - "github.com/terraform-linters/tflint-ruleset-aws/rules/api" ) func (cli *CLI) actAsAwsPlugin() int { @@ -19,7 +18,6 @@ func (cli *CLI) actAsAwsPlugin() int { Version: fmt.Sprintf("%s-bundled", project.Version), Rules: rules.Rules, }, - APIRules: api.Rules, }, }) diff --git a/cmd/option_test.go b/cmd/option_test.go index b582d8167..9882a5e0b 100644 --- a/cmd/option_test.go +++ b/cmd/option_test.go @@ -230,6 +230,7 @@ func Test_toConfig(t *testing.T) { eqlopts := []cmp.Option{ cmpopts.IgnoreUnexported(tflint.RuleConfig{}), cmpopts.IgnoreUnexported(tflint.PluginConfig{}), + cmpopts.IgnoreUnexported(tflint.Config{}), } if !cmp.Equal(tc.Expected, ret, eqlopts...) { t.Fatalf("Failed `%s` test: diff=%s", tc.Name, cmp.Diff(tc.Expected, ret, eqlopts...)) diff --git a/go.mod b/go.mod index 4ef9fdd18..33bb91e40 100644 --- a/go.mod +++ b/go.mod @@ -31,14 +31,14 @@ require ( github.com/sourcegraph/jsonrpc2 v0.1.0 github.com/spf13/afero v1.7.0 github.com/stretchr/testify v1.7.0 - github.com/terraform-linters/tflint-plugin-sdk v0.9.1 - github.com/terraform-linters/tflint-ruleset-aws v0.10.1 + github.com/terraform-linters/tflint-plugin-sdk v0.9.2-0.20220110085511-23d9a44a772d + github.com/terraform-linters/tflint-ruleset-aws v0.11.1-0.20220110095527-2965dc3212ef github.com/xeipuuv/gojsonschema v1.2.0 github.com/zclconf/go-cty v1.10.0 github.com/zclconf/go-cty-yaml v1.0.2 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 - golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f + golang.org/x/net v0.0.0-20211216030914-fe4d6282115f golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f golang.org/x/text v0.3.7 ) @@ -47,11 +47,11 @@ require ( cloud.google.com/go v0.75.0 // indirect cloud.google.com/go/storage v1.14.0 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect - github.com/aws/aws-sdk-go v1.42.19 // indirect + github.com/aws/aws-sdk-go v1.42.30 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect - github.com/golang/protobuf v1.4.3 // indirect + github.com/golang/protobuf v1.5.0 // indirect github.com/google/go-querystring v1.0.0 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 // indirect @@ -80,7 +80,7 @@ require ( google.golang.org/api v0.40.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20210226172003-ab064af71705 // indirect - google.golang.org/grpc v1.35.0 // indirect - google.golang.org/protobuf v1.25.0 // indirect + google.golang.org/grpc v1.41.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect ) diff --git a/go.sum b/go.sum index 755f1913a..43c69214a 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,7 @@ github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3 h1:ZSTrOEhiM5J5RFxEaFvMZVEAM1KvT1YzbEOwB2EAGjA= @@ -58,13 +59,14 @@ github.com/apparentlymart/go-versions v1.0.1 h1:ECIpSn0adcYNsBfSRwdDdz9fWlL+S/6E github.com/apparentlymart/go-versions v1.0.1/go.mod h1:YF5j7IQtrOAOnsGkniupEA5bfCjzd7i14yu0shZavyM= github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go v1.42.19 h1:L/aM1QwsqVia9qIqexTHwYN+lgLYuOtf11VDgz0YIyw= -github.com/aws/aws-sdk-go v1.42.19/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/aws/aws-sdk-go v1.42.30 h1:GvzWHwAdE5ZQ9UOcq0lX+PTzVJ4+sm1DjYrk6nUSTgA= +github.com/aws/aws-sdk-go v1.42.30/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0= github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -73,6 +75,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dave/dst v0.26.2/go.mod h1:UMDJuIRPfyUCC78eFuB+SV/WI8oDeyFDvM/JR6NI3IU= github.com/dave/gopackages v0.0.0-20170318123100-46e7023ec56e/go.mod h1:i00+b/gKdIDIxuLDFob7ustLAVqhsZRk2qVZrArELGQ= @@ -87,6 +90,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= @@ -94,6 +98,7 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -129,8 +134,9 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -142,6 +148,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github/v35 v35.3.0 h1:fU+WBzuukn0VssbayTT+Zo3/ESKX9JYWjbZTLOTEyho= @@ -175,6 +182,7 @@ github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/aws-sdk-go-base v1.0.0 h1:J7MMLOfSoDWkusy+cSzKYG1/aFyCzYJmdE0mod3/WLw= github.com/hashicorp/aws-sdk-go-base v1.0.0/go.mod h1:2fRjWDv3jJBeN6mVWFHV6hFTNeFBx2gpDLQaZNxUVAY= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -285,6 +293,7 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -304,10 +313,11 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/terraform-linters/tflint-plugin-sdk v0.9.1 h1:Q7+QmvkxrINjsxQ9iOR9GDIppS7kT2hXQBHNaZ+01ug= -github.com/terraform-linters/tflint-plugin-sdk v0.9.1/go.mod h1:2pu+KHPrxfV/Y0inO9c5w4ptL6dNIHu8Em7ZxXBNP4E= -github.com/terraform-linters/tflint-ruleset-aws v0.10.1 h1:TNb6TDktpnc/n98mfYsrssYrWlPTEAtRZaZInnVrX+8= -github.com/terraform-linters/tflint-ruleset-aws v0.10.1/go.mod h1:cNzNvNBmQs2C7GH8Z9cUFuMo4fAiJxHNScO8jcI9HM4= +github.com/terraform-linters/tflint-plugin-sdk v0.9.2-0.20220104090459-1ef364be3016/go.mod h1:7jNzNi2Mx74jqLILDCdcXyA1MrsdvBghd8U2y8bTF20= +github.com/terraform-linters/tflint-plugin-sdk v0.9.2-0.20220110085511-23d9a44a772d h1:WspuloQ3Vwomcx77NQkzO2BcUIjaThBE1TuQO/VDpKw= +github.com/terraform-linters/tflint-plugin-sdk v0.9.2-0.20220110085511-23d9a44a772d/go.mod h1:7jNzNi2Mx74jqLILDCdcXyA1MrsdvBghd8U2y8bTF20= +github.com/terraform-linters/tflint-ruleset-aws v0.11.1-0.20220110095527-2965dc3212ef h1:zeCzZ3R8z2auhcoC8986PK0DhN5r331RK4DC9jq1uhg= +github.com/terraform-linters/tflint-ruleset-aws v0.11.1-0.20220110095527-2965dc3212ef/go.mod h1:DDIrgBuCev9oqQrNuMWmSC2NWG4KgL253VJYUoOu6Ss= github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vmihailenco/msgpack v3.3.3+incompatible h1:wapg9xDUZDzGCNFlwc5SqI1rvcciqcxEHac4CYj89xI= @@ -344,6 +354,7 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/arch v0.0.0-20180920145803-b19384d3c130/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -429,9 +440,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f h1:w6wWR0H+nyVpbSAQbzVEIACVyr/h8l/BEkY6Sokc7Eg= -golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -628,6 +638,7 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -657,11 +668,13 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -671,8 +684,10 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -684,6 +699,7 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/langserver/handler.go b/langserver/handler.go index 94928c9fc..6a0ddba0b 100644 --- a/langserver/handler.go +++ b/langserver/handler.go @@ -186,12 +186,24 @@ func (h *handler) inspect() (map[string][]lsp.Diagnostic, error) { } for name, ruleset := range h.plugin.RuleSets { - err = ruleset.ApplyConfig(h.config.ToPluginConfig(name)) + config, err := h.config.ToPluginConfig(name).Unmarshal() if err != nil { - return ret, fmt.Errorf("Failed to apply config to plugins: %s", err) + return ret, fmt.Errorf("Failed to fetch config schema from plugins") } + if err := ruleset.ApplyGlobalConfig(config); err != nil { + return ret, fmt.Errorf("Failed to fetch config schema from plugins") + } + configSchema, err := ruleset.ConfigSchema() + if err != nil { + return ret, fmt.Errorf("Failed to fetch config schema from plugins") + } + content, diags := h.config.PluginContent(name, configSchema) + if diags.HasErrors() { + return ret, fmt.Errorf("Failed to parse plugin config schema") + } + err = ruleset.ApplyConfig(content, h.config.Sources()) for _, runner := range runners { - err = ruleset.Check(tfplugin.NewServer(runner, runners[len(runners)-1], loader.Sources())) + err = ruleset.Check(tfplugin.NewGRPCServer(runner, runners[len(runners)-1], loader.Sources())) if err != nil { return ret, fmt.Errorf("Failed to check ruleset: %s", err) } diff --git a/plugin/discovery.go b/plugin/discovery.go index 1d7273302..75658f15a 100644 --- a/plugin/discovery.go +++ b/plugin/discovery.go @@ -12,7 +12,7 @@ import ( plugin "github.com/hashicorp/go-plugin" "github.com/mitchellh/go-homedir" - tfplugin "github.com/terraform-linters/tflint-plugin-sdk/plugin" + "github.com/terraform-linters/tflint-plugin-sdk/plugin/host2plugin" "github.com/terraform-linters/tflint/tflint" ) @@ -22,7 +22,7 @@ import ( // instead of returning an error. This is a process for backward compatibility. func Discovery(config *tflint.Config) (*Plugin, error) { clients := map[string]*plugin.Client{} - rulesets := map[string]*tfplugin.Client{} + rulesets := map[string]*host2plugin.GRPCClient{} for _, pluginCfg := range config.Plugins { installCfg := NewInstallConfig(config, pluginCfg) @@ -53,7 +53,7 @@ func Discovery(config *tflint.Config) (*Plugin, error) { if pluginCfg.Enabled { log.Printf("[INFO] Plugin `%s` found", pluginCfg.Name) - client := tfplugin.NewClient(&tfplugin.ClientOpts{ + client := host2plugin.NewClient(&host2plugin.ClientOpts{ Cmd: cmd, }) rpcClient, err := client.Client() @@ -64,7 +64,7 @@ func Discovery(config *tflint.Config) (*Plugin, error) { if err != nil { return nil, err } - ruleset := raw.(*tfplugin.Client) + ruleset := raw.(*host2plugin.GRPCClient) clients[pluginCfg.Name] = client rulesets[pluginCfg.Name] = ruleset diff --git a/plugin/plugin.go b/plugin/plugin.go index 41fa81884..d613da5aa 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -2,7 +2,7 @@ package plugin import ( plugin "github.com/hashicorp/go-plugin" - tfplugin "github.com/terraform-linters/tflint-plugin-sdk/plugin" + "github.com/terraform-linters/tflint-plugin-sdk/plugin/host2plugin" ) // PluginRoot is the root directory of the plugins @@ -15,7 +15,7 @@ var ( // 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 map[string]*tfplugin.Client + RuleSets map[string]*host2plugin.GRPCClient clients map[string]*plugin.Client } diff --git a/plugin/server.go b/plugin/server.go index c9cbae957..cad654bc2 100644 --- a/plugin/server.go +++ b/plugin/server.go @@ -1,12 +1,19 @@ package plugin import ( + "errors" + "fmt" + "sort" + "strings" + hcl "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/terraform-linters/tflint-plugin-sdk/hclext" client "github.com/terraform-linters/tflint-plugin-sdk/tflint" tfplugin "github.com/terraform-linters/tflint-plugin-sdk/tflint/client" "github.com/terraform-linters/tflint/terraform/configs" "github.com/terraform-linters/tflint/tflint" + "github.com/zclconf/go-cty/cty" ) // Server is a RPC server for responding to requests from plugins @@ -16,6 +23,147 @@ type Server struct { sources map[string][]byte } +type GRPCServer struct { + runner *tflint.Runner + rootRunner *tflint.Runner + sources map[string][]byte +} + +func NewGRPCServer(runner *tflint.Runner, rootRunner *tflint.Runner, sources map[string][]byte) *GRPCServer { + return &GRPCServer{runner: runner, rootRunner: rootRunner, sources: sources} +} + +func (s *GRPCServer) GetModuleContent(bodyS *hclext.BodySchema, opts client.GetModuleContentOption) (*hclext.BodyContent, hcl.Diagnostics) { + content := &hclext.BodyContent{} + diags := hcl.Diagnostics{} + + var files map[string]*hcl.File + switch opts.ModuleCtx { + case client.SelfModuleCtxType: + files = s.runner.Files() + case client.RootModuleCtxType: + files = s.rootRunner.Files() + } + + overrides := []*hcl.File{} + for name, f := range files { + if name == "override.tf" || name == "override.tf.json" || strings.HasSuffix(name, "_override.tf") || strings.HasSuffix(name, "_override.tf.json") { + overrides = append(overrides, f) + delete(files, name) + } + } + sort.Slice(overrides, func(i, j int) bool { + return overrides[i].Body.MissingItemRange().Filename < overrides[j].Body.MissingItemRange().Filename + }) + + for _, f := range files { + c, d := hclext.PartialContent(f.Body, bodyS) + diags = diags.Extend(d) + for name, attr := range c.Attributes { + content.Attributes[name] = attr + } + content.Blocks = append(content.Blocks, c.Blocks...) + } + + for _, f := range overrides { + c, d := hclext.PartialContent(f.Body, bodyS) + diags = diags.Extend(d) + for name, attr := range c.Attributes { + content.Attributes[name] = attr + } + content.Blocks = overrideBlocks(content.Blocks, c.Blocks) + } + + return content, diags +} + +func overrideBlocks(primaries, overrides hclext.Blocks) hclext.Blocks { + dict := map[string]*hclext.Block{} + for _, primary := range primaries { + key := fmt.Sprintf("%s[%s]", primary.Type, strings.Join(primary.Labels, ",")) + dict[key] = primary + } + + for _, override := range overrides { + key := fmt.Sprintf("%s[%s]", override.Type, strings.Join(override.Labels, ",")) + if primary, exists := dict[key]; exists { + for name, attr := range override.Body.Attributes { + primary.Body.Attributes[name] = attr + } + + for _, block := range override.Body.Blocks { + overrideBlocks(primary.Body.Blocks, block.Body.Blocks) + } + } + } + + return primaries +} + +func (s *GRPCServer) GetFile(name string) (*hcl.File, error) { + return s.runner.File(name), nil +} + +func (s *GRPCServer) GetFiles(ty client.ModuleCtxType) map[string]*hcl.File { + switch ty { + case client.SelfModuleCtxType: + return s.runner.Files() + case client.RootModuleCtxType: + return s.rootRunner.Files() + default: + panic(fmt.Sprintf("invalid ModuleCtxType: %s", ty)) + } +} + +func (s *GRPCServer) GetRuleConfigContent(name string, bodyS *hclext.BodySchema) (*hclext.BodyContent, *hcl.File, error) { + file := s.runner.ConfigFile() + config := s.runner.RuleConfig(name) + if config == nil { + return nil, file, fmt.Errorf("rule `%s` is not found in config", name) + } + // HACK: If you enable the rule through the CLI instead of the file, its hcl.Body will not contain valid range. + // @see https://github.com/hashicorp/hcl/blob/v2.8.0/merged.go#L132-L135 + if config.Body.MissingItemRange().Filename == "" { + return nil, file, errors.New("This rule cannot be enabled with the `--enable-rule` option because it lacks the required configuration") + } + + body, diags := hclext.Content(config.Body, bodyS) + if diags.HasErrors() { + return body, file, diags + } + return body, file, nil +} + +func (s *GRPCServer) EvaluateExpr(expr hcl.Expression, opts client.EvaluateExprOption) (cty.Value, error) { + var runner *tflint.Runner + switch opts.ModuleCtx { + case client.SelfModuleCtxType: + runner = s.runner + case client.RootModuleCtxType: + runner = s.rootRunner + } + val, err := runner.EvalExpr(expr, nil, *opts.WantType) + return val, wrapError(err) +} + +// TODO: Why rule needs to implement Check()? +func (s *GRPCServer) EmitIssue(rule client.Rule, message string, location hcl.Range) error { + file := s.runner.File(location.Filename) + if file == nil { + s.runner.EmitIssue(rule, message, location) + return nil + } + expr, diags := hclext.ParseExpression(location.SliceBytes(file.Bytes), location.Filename, location.Start) + if diags.HasErrors() { + s.runner.EmitIssue(rule, message, location) + return nil + } + return s.runner.WithExpressionContext(expr, func() error { + s.runner.EmitIssue(rule, message, location) + return nil + }) +} + // NewServer initializes a RPC server for plugins func NewServer(runner *tflint.Runner, rootRunner *tflint.Runner, sources map[string][]byte) *Server { return &Server{runner: runner, rootRunner: rootRunner, sources: sources} diff --git a/plugin/stub-generator/sources/example/main.go b/plugin/stub-generator/sources/example/main.go index 25b05cb91..646de0c0f 100644 --- a/plugin/stub-generator/sources/example/main.go +++ b/plugin/stub-generator/sources/example/main.go @@ -3,7 +3,7 @@ package main import ( "fmt" - hcl "github.com/hashicorp/hcl/v2" + "github.com/terraform-linters/tflint-plugin-sdk/hclext" "github.com/terraform-linters/tflint-plugin-sdk/plugin" "github.com/terraform-linters/tflint-plugin-sdk/tflint" ) @@ -21,7 +21,9 @@ func main() { } // AwsInstanceExampleTypeRule checks whether ... -type AwsInstanceExampleTypeRule struct{} +type AwsInstanceExampleTypeRule struct { + tflint.DefaultRule +} // NewAwsInstanceExampleTypeRule returns a new rule func NewAwsInstanceExampleTypeRule() *AwsInstanceExampleTypeRule { @@ -50,16 +52,33 @@ func (r *AwsInstanceExampleTypeRule) Link() string { // Check checks whether ... func (r *AwsInstanceExampleTypeRule) Check(runner tflint.Runner) error { - return runner.WalkResourceAttributes("aws_instance", "instance_type", func(attribute *hcl.Attribute) error { + resources, err := runner.GetResourceContent("aws_instance", &hclext.BodySchema{ + Attributes: []hclext.AttributeSchema{{Name: "instance_type"}}, + }, nil) + if err != nil { + return err + } + + for _, resource := range resources.Blocks { + attribute, exists := resource.Body.Attributes["instance_type"] + if !exists { + continue + } + var instanceType string err := runner.EvaluateExpr(attribute.Expr, &instanceType, nil) - return runner.EnsureNoError(err, func() error { - return runner.EmitIssueOnExpr( + err = runner.EnsureNoError(err, func() error { + return runner.EmitIssue( r, fmt.Sprintf("instance type is %s", instanceType), - attribute.Expr, + attribute.Expr.Range(), ) }) - }) + if err != nil { + return err + } + } + + return nil } diff --git a/plugin/stub-generator/sources/testing/rules/aws_instance_example_type.go b/plugin/stub-generator/sources/testing/rules/aws_instance_example_type.go index 716484c61..d98f917ba 100644 --- a/plugin/stub-generator/sources/testing/rules/aws_instance_example_type.go +++ b/plugin/stub-generator/sources/testing/rules/aws_instance_example_type.go @@ -3,12 +3,14 @@ package rules import ( "fmt" - hcl "github.com/hashicorp/hcl/v2" + "github.com/terraform-linters/tflint-plugin-sdk/hclext" "github.com/terraform-linters/tflint-plugin-sdk/tflint" ) // AwsInstanceExampleTypeRule checks whether ... -type AwsInstanceExampleTypeRule struct{} +type AwsInstanceExampleTypeRule struct { + tflint.DefaultRule +} // NewAwsInstanceExampleTypeRule returns a new rule func NewAwsInstanceExampleTypeRule() *AwsInstanceExampleTypeRule { @@ -37,16 +39,33 @@ func (r *AwsInstanceExampleTypeRule) Link() string { // Check checks whether ... func (r *AwsInstanceExampleTypeRule) Check(runner tflint.Runner) error { - return runner.WalkResourceAttributes("aws_instance", "instance_type", func(attribute *hcl.Attribute) error { + resources, err := runner.GetResourceContent("aws_instance", &hclext.BodySchema{ + Attributes: []hclext.AttributeSchema{{Name: "instance_type"}}, + }, nil) + if err != nil { + return err + } + + for _, resource := range resources.Blocks { + attribute, exists := resource.Body.Attributes["instance_type"] + if !exists { + continue + } + var instanceType string err := runner.EvaluateExpr(attribute.Expr, &instanceType, nil) - return runner.EnsureNoError(err, func() error { - return runner.EmitIssueOnExpr( + err = runner.EnsureNoError(err, func() error { + return runner.EmitIssue( r, fmt.Sprintf("instance type is %s", instanceType), - attribute.Expr, + attribute.Expr.Range(), ) }) - }) + if err != nil { + return err + } + } + + return nil } diff --git a/plugin/stub-generator/sources/testing/rules/required_config.go b/plugin/stub-generator/sources/testing/rules/required_config.go index c3bca3bb9..d1c5c67b7 100644 --- a/plugin/stub-generator/sources/testing/rules/required_config.go +++ b/plugin/stub-generator/sources/testing/rules/required_config.go @@ -5,7 +5,9 @@ import ( ) // RequredConfigRule checks whether ... -type RequredConfigRule struct{} +type RequredConfigRule struct { + tflint.DefaultRule +} type ruleConfig struct { Options []string `hcl:"options"` diff --git a/tflint/config.go b/tflint/config.go index b5bc0d499..3e1c9bb2a 100644 --- a/tflint/config.go +++ b/tflint/config.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/hcl/v2/hclparse" "github.com/hashicorp/hcl/v2/hclsyntax" homedir "github.com/mitchellh/go-homedir" + "github.com/terraform-linters/tflint-plugin-sdk/hclext" tfplugin "github.com/terraform-linters/tflint-plugin-sdk/tflint" ) @@ -55,6 +56,9 @@ type Config struct { PluginDir string Rules map[string]*RuleConfig Plugins map[string]*PluginConfig + + file *hcl.File + sources map[string][]byte } // RuleConfig is a TFLint's rule config @@ -193,6 +197,37 @@ func (c *Config) ToPluginConfig(name string) *tfplugin.MarshalledConfig { return cfg } +func (c *Config) PluginContent(name string, schema *hclext.BodySchema) (*hclext.BodyContent, hcl.Diagnostics) { + if schema == nil { + schema = &hclext.BodySchema{} + } + if c.file == nil { + return &hclext.BodyContent{}, nil + } + + plugins, _, diags := c.file.Body.PartialContent(&hcl.BodySchema{ + Blocks: []hcl.BlockHeaderSchema{ + {Type: "plugin", LabelNames: []string{"name"}}, + }, + }) + if diags.HasErrors() { + return nil, diags + } + + schema.Attributes = append(schema.Attributes, hclext.AttributeSchema{Name: "enabled"}) + for _, plugin := range plugins.Blocks { + if plugin.Labels[0] == name { + return hclext.Content(plugin.Body, schema) + } + } + + return &hclext.BodyContent{}, nil +} + +func (c *Config) Sources() map[string][]byte { + return c.sources +} + // RuleSet is an interface to handle plugin's RuleSet and core RuleSet both // In the future, when all RuleSets are cut out into plugins, it will no longer be needed. type RuleSet interface { @@ -269,6 +304,9 @@ func (c *Config) copy() *Config { PluginDir: c.PluginDir, Rules: rules, Plugins: plugins, + + file: c.file, + sources: c.sources, } } @@ -316,6 +354,8 @@ plugin "aws" { } cfg := raw.toConfig() + cfg.file = f + cfg.sources = parser.Sources() for _, rule := range cfg.Rules { rule.file = f } diff --git a/tflint/config_test.go b/tflint/config_test.go index de68dd847..5e69862df 100644 --- a/tflint/config_test.go +++ b/tflint/config_test.go @@ -108,6 +108,7 @@ func Test_LoadConfig(t *testing.T) { opts := []cmp.Option{ cmpopts.IgnoreUnexported(RuleConfig{}), cmpopts.IgnoreUnexported(PluginConfig{}), + cmpopts.IgnoreUnexported(Config{}), cmpopts.IgnoreFields(PluginConfig{}, "Body"), cmpopts.IgnoreFields(RuleConfig{}, "Body"), } @@ -626,6 +627,7 @@ func Test_Merge(t *testing.T) { opts := []cmp.Option{ cmpopts.IgnoreUnexported(RuleConfig{}), cmpopts.IgnoreUnexported(PluginConfig{}), + cmpopts.IgnoreUnexported(Config{}), cmpopts.IgnoreUnexported(hclsyntax.Body{}), cmpopts.IgnoreFields(hclsyntax.Body{}, "Attributes", "Blocks"), } @@ -881,6 +883,7 @@ func Test_copy(t *testing.T) { opts := []cmp.Option{ cmpopts.IgnoreUnexported(RuleConfig{}), cmpopts.IgnoreUnexported(PluginConfig{}), + cmpopts.IgnoreUnexported(Config{}), } for _, tc := range cases { ret := cfg.copy() diff --git a/tflint/runner.go b/tflint/runner.go index c495e5125..d955f711f 100644 --- a/tflint/runner.go +++ b/tflint/runner.go @@ -334,6 +334,10 @@ func (r *Runner) RuleConfig(ruleName string) *RuleConfig { return r.config.Rules[ruleName] } +func (r *Runner) ConfigFile() *hcl.File { + return r.config.file +} + func (r *Runner) emitIssue(issue *Issue) { if annotations, ok := r.annotations[issue.Range.Filename]; ok { for _, annotation := range annotations { diff --git a/tflint/runner_eval.go b/tflint/runner_eval.go index 2ce324cfc..642d0d027 100644 --- a/tflint/runner_eval.go +++ b/tflint/runner_eval.go @@ -109,6 +109,10 @@ func (r *Runner) EvalExpr(expr hcl.Expression, ret interface{}, wantType cty.Typ return cty.NullVal(cty.NilType), err } + if wantType == cty.DynamicPseudoType { + return val, nil + } + err = cty.Walk(val, func(path cty.Path, v cty.Value) (bool, error) { if !v.IsKnown() { err := &Error{ @@ -325,6 +329,12 @@ func (r *Runner) willEvaluateResource(resource *configs.Resource) (bool, error) var forEach cty.Value forEach, err = r.EvalExpr(resource.ForEach, nil, cty.DynamicPseudoType) if err == nil { + if forEach.IsNull() { + return true, nil + } + if !forEach.IsKnown() { + return false, nil + } if !forEach.CanIterateElements() { return false, fmt.Errorf("The `for_each` value is not iterable in %s:%d", resource.ForEach.Range().Filename, resource.ForEach.Range().Start.Line) }