From 232f11d798ecf5cc8bf0d7d42b1fd17652e4d8cf Mon Sep 17 00:00:00 2001 From: wata_mac Date: Mon, 21 Mar 2022 22:40:54 +0900 Subject: [PATCH] Remove unused net/rpc based plugin implementations --- README.md | 6 +- go.mod | 2 - go.sum | 9 - helper/doc.go | 2 +- helper/runner.go | 30 +- helper/runner_test.go | 2 +- plugin/client.go | 76 ---- plugin/doc.go | 7 +- plugin/plugin.go | 35 +- plugin/server.go | 59 ---- terraform/addrs/module.go | 28 -- terraform/configs/backend.go | 13 - terraform/configs/config.go | 21 -- terraform/configs/module.go | 31 -- terraform/configs/module_call.go | 34 -- terraform/configs/named_values.go | 79 ----- terraform/configs/provider.go | 48 --- terraform/configs/provisioner.go | 50 --- terraform/configs/resource.go | 53 --- terraform/configs/version_constraint.go | 13 - terraform/doc.go | 8 - terraform/experiments/experiment.go | 9 - tflint/client/client.go | 413 ---------------------- tflint/client/client_test.go | 445 ------------------------ tflint/client/decode.go | 142 -------- tflint/client/decode_backend.go | 33 -- tflint/client/decode_config.go | 49 --- tflint/client/decode_module.go | 149 -------- tflint/client/decode_module_call.go | 91 ----- tflint/client/decode_named_values.go | 155 --------- tflint/client/decode_provider.go | 124 ------- tflint/client/decode_provisioner.go | 66 ---- tflint/client/decode_resource.go | 118 ------- tflint/client/doc.go | 12 - tflint/client/encode.go | 46 --- tflint/client/rpc.go | 147 -------- tflint/config.go | 32 -- tflint/doc.go | 2 +- tflint/errors.go | 57 +-- tflint/interface.go | 253 +++++++------- tflint/server/doc.go | 2 - tflint/server/server.go | 15 - 42 files changed, 163 insertions(+), 2803 deletions(-) delete mode 100644 plugin/client.go delete mode 100644 plugin/server.go delete mode 100644 terraform/addrs/module.go delete mode 100644 terraform/configs/backend.go delete mode 100644 terraform/configs/config.go delete mode 100644 terraform/configs/module.go delete mode 100644 terraform/configs/module_call.go delete mode 100644 terraform/configs/named_values.go delete mode 100644 terraform/configs/provider.go delete mode 100644 terraform/configs/provisioner.go delete mode 100644 terraform/configs/resource.go delete mode 100644 terraform/configs/version_constraint.go delete mode 100644 terraform/doc.go delete mode 100644 terraform/experiments/experiment.go delete mode 100644 tflint/client/client.go delete mode 100644 tflint/client/client_test.go delete mode 100644 tflint/client/decode.go delete mode 100644 tflint/client/decode_backend.go delete mode 100644 tflint/client/decode_config.go delete mode 100644 tflint/client/decode_module.go delete mode 100644 tflint/client/decode_module_call.go delete mode 100644 tflint/client/decode_named_values.go delete mode 100644 tflint/client/decode_provider.go delete mode 100644 tflint/client/decode_provisioner.go delete mode 100644 tflint/client/decode_resource.go delete mode 100644 tflint/client/doc.go delete mode 100644 tflint/client/encode.go delete mode 100644 tflint/client/rpc.go delete mode 100644 tflint/server/doc.go delete mode 100644 tflint/server/server.go diff --git a/README.md b/README.md index 4fb53fc..fdc2e33 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ NOTE: This plugin system is experimental. This means that API compatibility is f ## Requirements -- TFLint v0.30+ +- TFLint v0.35+ - Go v1.18 ## Usage @@ -23,6 +23,8 @@ For more details on the API, see [tflint](https://pkg.go.dev/github.com/terrafor ![architecture](architecture.png) -This plugin system uses [go-plugin](https://github.com/hashicorp/go-plugin). TFLint launches the plugin as a sub-process and communicates with the plugin over RPC. The plugin acts as a server, while TFLint acts as a client that sends inspection requests to the plugin. +This plugin system uses [go-plugin](https://github.com/hashicorp/go-plugin). TFLint launches the plugin as a sub-process and communicates with the plugin over gRPC. The plugin acts as a server, while TFLint acts as a client that sends inspection requests to the plugin. On the other hand, the plugin sends various requests to a server (TFLint) to get detailed runtime contexts (e.g. variables and expressions). This means that TFLint and plugins can act as both a server and a client. + +These implementations are included in the [plugin/host2plugin](https://pkg.go.dev/github.com/terraform-linters/tflint-plugin-sdk/plugin/host2plugin) and [plugin/plugin2host]((https://pkg.go.dev/github.com/terraform-linters/tflint-plugin-sdk/plugin/plugin2host)) packages. diff --git a/go.mod b/go.mod index 4e54d51..20c7b9a 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,7 @@ require ( github.com/google/go-cmp v0.5.7 github.com/hashicorp/go-hclog v1.2.0 github.com/hashicorp/go-plugin v1.4.3 - github.com/hashicorp/go-version v1.4.0 github.com/hashicorp/hcl/v2 v2.11.1 - github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 github.com/zclconf/go-cty v1.10.0 google.golang.org/grpc v1.45.0 google.golang.org/protobuf v1.27.1 diff --git a/go.sum b/go.sum index 5cafb13..ffa5e8a 100644 --- a/go.sum +++ b/go.sum @@ -60,19 +60,13 @@ github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= -github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hcl/v2 v2.11.1 h1:yTyWcXcm9XB0TEkyU/JCRU6rYy4K+mgLtzn2wlrJbcc= github.com/hashicorp/hcl/v2 v2.11.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= -github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= -github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= @@ -113,7 +107,6 @@ github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvC github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= @@ -136,12 +129,10 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/helper/doc.go b/helper/doc.go index 2662998..bb2fe37 100644 --- a/helper/doc.go +++ b/helper/doc.go @@ -1,7 +1,7 @@ // Package helper contains implementations for plugin testing. // // You can test the implemented rules using the mock Runner that is not -// an RPC client. It is similar to TFLint's Runner, but is implemented +// an gRPC client. It is similar to TFLint's Runner, but is implemented // from scratch to avoid Terraform dependencies. // // Some implementations of the mock Runner have been simplified. As a result, diff --git a/helper/runner.go b/helper/runner.go index 6d0cbfb..fb7a18a 100644 --- a/helper/runner.go +++ b/helper/runner.go @@ -6,7 +6,6 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/terraform-linters/tflint-plugin-sdk/hclext" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/configs" "github.com/terraform-linters/tflint-plugin-sdk/tflint" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" @@ -15,11 +14,18 @@ import ( // Runner is a mock that satisfies the Runner interface for plugin testing. type Runner struct { - files map[string]*hcl.File Issues Issues - tfconfig *configs.Config - config Config + files map[string]*hcl.File + config Config + variables map[string]*Variable +} + +// Variable is an implementation of variables in Terraform language +type Variable struct { + Name string + Default cty.Value + DeclRange hcl.Range } // Config is a pseudo TFLint config file object for testing from plugins. @@ -141,7 +147,7 @@ func (r *Runner) EvaluateExpr(expr hcl.Expression, ret interface{}, opts *tflint } variables := map[string]cty.Value{} - for _, variable := range r.tfconfig.Module.Variables { + for _, variable := range r.variables { variables[variable.Name] = variable.Default } workspace, success := os.LookupEnv("TF_WORKSPACE") @@ -203,12 +209,6 @@ func (r *Runner) AddLocalFile(name string, file *hcl.File) bool { } func (r *Runner) initFromFiles() error { - r.tfconfig = &configs.Config{ - Module: &configs.Module{ - Variables: map[string]*configs.Variable{}, - }, - } - for _, file := range r.files { content, _, diags := file.Body.PartialContent(configFileSchema) if diags.HasErrors() { @@ -218,11 +218,11 @@ func (r *Runner) initFromFiles() error { for _, block := range content.Blocks { switch block.Type { case "variable": - variable, diags := simpleDecodeVariableBlock(block) + variable, diags := decodeVariableBlock(block) if diags.HasErrors() { return diags } - r.tfconfig.Module.Variables[variable.Name] = variable + r.variables[variable.Name] = variable default: continue } @@ -232,8 +232,8 @@ func (r *Runner) initFromFiles() error { return nil } -func simpleDecodeVariableBlock(block *hcl.Block) (*configs.Variable, hcl.Diagnostics) { - v := &configs.Variable{ +func decodeVariableBlock(block *hcl.Block) (*Variable, hcl.Diagnostics) { + v := &Variable{ Name: block.Labels[0], DeclRange: block.DefRange, } diff --git a/helper/runner_test.go b/helper/runner_test.go index 2ace8a1..b463f4e 100644 --- a/helper/runner_test.go +++ b/helper/runner_test.go @@ -283,7 +283,7 @@ func (r *dummyRule) Enabled() bool { return true } func (r *dummyRule) Severity() tflint.Severity { return tflint.ERROR } func (r *dummyRule) Check(tflint.Runner) error { return nil } -func Test_EmitIssueOnExpr(t *testing.T) { +func Test_EmitIssue(t *testing.T) { src := ` resource "aws_instance" "foo" { instance_type = "t2.micro" diff --git a/plugin/client.go b/plugin/client.go deleted file mode 100644 index 74d0eaa..0000000 --- a/plugin/client.go +++ /dev/null @@ -1,76 +0,0 @@ -package plugin - -import ( - "net/rpc" - "os" - "os/exec" - - "github.com/hashicorp/go-hclog" - plugin "github.com/hashicorp/go-plugin" - "github.com/terraform-linters/tflint-plugin-sdk/tflint" - tfserver "github.com/terraform-linters/tflint-plugin-sdk/tflint/server" -) - -// Client is an RPC client for the host. -type Client struct { - rpcClient *rpc.Client - broker *plugin.MuxBroker -} - -// ClientOpts is an option for initializing a Client. -type ClientOpts struct { - Cmd *exec.Cmd -} - -// NewClient is a wrapper of plugin.NewClient. -func NewClient(opts *ClientOpts) *plugin.Client { - return plugin.NewClient(&plugin.ClientConfig{ - HandshakeConfig: handshakeConfig, - Plugins: map[string]plugin.Plugin{ - "ruleset": &RuleSetPlugin{}, - }, - Cmd: opts.Cmd, - Logger: hclog.New(&hclog.LoggerOptions{ - Name: "plugin", - Output: os.Stderr, - Level: hclog.LevelFromString(os.Getenv("TFLINT_LOG")), - }), - }) -} - -// RuleSetName calls the server-side RuleSetName method and returns its name. -func (c *Client) RuleSetName() (string, error) { - var resp string - err := c.rpcClient.Call("Plugin.RuleSetName", new(interface{}), &resp) - return resp, err -} - -// RuleSetVersion calls the server-side RuleSetVersion method and returns its version. -func (c *Client) RuleSetVersion() (string, error) { - var resp string - err := c.rpcClient.Call("Plugin.RuleSetVersion", new(interface{}), &resp) - return resp, err -} - -// RuleNames calls the server-side RuleNames method and returns the list of names. -func (c *Client) RuleNames() ([]string, error) { - var resp []string - err := c.rpcClient.Call("Plugin.RuleNames", new(interface{}), &resp) - return resp, err -} - -// ApplyConfig calls the server-side ApplyConfig method. -func (c *Client) ApplyConfig(config *tflint.MarshalledConfig) error { - return c.rpcClient.Call("Plugin.ApplyConfig", config, new(interface{})) -} - -// Check calls the server-side Check method. -// At the same time, it starts the server to respond to requests from the plugin side. -// Note that this server (tfserver.Server) serves clients that satisfy the Runner interface -// and is different from the server (plugin.Server) that provides the plugin system. -func (c *Client) Check(server tfserver.Server) error { - brokerID := c.broker.NextId() - go c.broker.AcceptAndServe(brokerID, server) - - return c.rpcClient.Call("Plugin.Check", brokerID, new(interface{})) -} diff --git a/plugin/doc.go b/plugin/doc.go index a71d9e4..2eee968 100644 --- a/plugin/doc.go +++ b/plugin/doc.go @@ -1,11 +1,8 @@ // Package plugin contains the implementations needed to make // the built binary act as a plugin. // -// A plugin is implemented as an RPC server and the host acts +// A plugin is implemented as an gRPC server and the host acts // as the client, sending analysis requests to the plugin. -// Note that the server-client relationship here is the opposite of -// the communication that takes place during the checking phase. // -// Implementation details are hidden in go-plugin. This package is -// essentially a wrapper for go-plugin. +// See host2plugin for implementation details. package plugin diff --git a/plugin/plugin.go b/plugin/plugin.go index c493a6a..d93c6b1 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -1,38 +1,15 @@ package plugin import ( - "encoding/gob" - "net/rpc" - - plugin "github.com/hashicorp/go-plugin" - "github.com/terraform-linters/tflint-plugin-sdk/tflint" + "github.com/terraform-linters/tflint-plugin-sdk/plugin/host2plugin" // Import this package to initialize the global logger _ "github.com/terraform-linters/tflint-plugin-sdk/logger" ) -// handShakeConfig is used for UX. ProcotolVersion will be updated by incompatible changes. -var handshakeConfig = plugin.HandshakeConfig{ - ProtocolVersion: 9, - MagicCookieKey: "TFLINT_RULESET_PLUGIN", - MagicCookieValue: "5adSn1bX8nrDfgBqiAqqEkC6OE1h3iD8SqbMc5UUONx8x3xCF0KlPDsBRNDjoYDP", -} - -// RuleSetPlugin is a wrapper to satisfy the interface of go-plugin. -type RuleSetPlugin struct { - impl tflint.RPCRuleSet -} - -// Server returns an RPC server acting as a plugin. -func (p *RuleSetPlugin) Server(b *plugin.MuxBroker) (interface{}, error) { - return &Server{impl: p.impl, broker: b}, nil -} - -// Client returns an RPC client for the host. -func (RuleSetPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { - return &Client{rpcClient: c, broker: b}, nil -} +// ServeOpts is an option for serving a plugin. +// Each plugin can pass a RuleSet that represents its own functionality. +type ServeOpts = host2plugin.ServeOpts -func init() { - gob.Register(tflint.Error{}) -} +// Serve is a wrapper of plugin.Serve. This is entrypoint of all plugins. +var Serve = host2plugin.Serve diff --git a/plugin/server.go b/plugin/server.go deleted file mode 100644 index c05456f..0000000 --- a/plugin/server.go +++ /dev/null @@ -1,59 +0,0 @@ -package plugin - -import ( - plugin "github.com/hashicorp/go-plugin" - "github.com/terraform-linters/tflint-plugin-sdk/plugin/host2plugin" - "github.com/terraform-linters/tflint-plugin-sdk/tflint" - tfclient "github.com/terraform-linters/tflint-plugin-sdk/tflint/client" -) - -// ServeOpts is an option for serving a plugin. -// Each plugin can pass a RuleSet that represents its own functionality. -type ServeOpts = host2plugin.ServeOpts - -// Serve is a wrapper of plugin.Serve. This is entrypoint of all plugins. -var Serve = host2plugin.Serve - -// Server is an RPC server acting as a plugin. -type Server struct { - impl tflint.RPCRuleSet - broker *plugin.MuxBroker -} - -// RuleSetName returns the name of the plugin. -func (s *Server) RuleSetName(args interface{}, resp *string) error { - *resp = s.impl.RuleSetName() - return nil -} - -// RuleSetVersion returns the version of the plugin. -func (s *Server) RuleSetVersion(args interface{}, resp *string) error { - *resp = s.impl.RuleSetVersion() - return nil -} - -// RuleNames returns the list of rule names provided by the plugin. -func (s *Server) RuleNames(args interface{}, resp *[]string) error { - *resp = s.impl.RuleNames() - return nil -} - -// ApplyConfig applies the passed config to its own plugin implementation. -func (s *Server) ApplyConfig(config *tflint.MarshalledConfig, resp *interface{}) error { - cfg, err := config.Unmarshal() - if err != nil { - return err - } - return s.impl.ApplyConfig(cfg) -} - -// Check calls its own plugin implementation with an RPC client that can send -// requests to the host process. -func (s *Server) Check(brokerID uint32, resp *interface{}) error { - conn, err := s.broker.Dial(brokerID) - if err != nil { - return err - } - - return s.impl.Check(tfclient.NewClient(conn)) -} diff --git a/terraform/addrs/module.go b/terraform/addrs/module.go deleted file mode 100644 index 81d097c..0000000 --- a/terraform/addrs/module.go +++ /dev/null @@ -1,28 +0,0 @@ -package addrs - -import svchost "github.com/hashicorp/terraform-svchost" - -// Module is an alternative representation of addrs.Module. -// https://github.com/hashicorp/terraform/blob/v0.14.3/addrs/module.go#L17 -type Module []string - -// Provider is an alternative representation of addrs.Provider. -// https://github.com/hashicorp/terraform/blob/v0.14.3/addrs/provider.go#L16-L20 -type Provider struct { - Type string - Namespace string - Hostname svchost.Hostname -} - -// ResourceMode is an alternative representation of addrs.ResourceMode. -// https://github.com/hashicorp/terraform/blob/v0.14.3/addrs/resource.go#L326-L344 -type ResourceMode rune - -const ( - // InvalidResourceMode is the zero value of ResourceMode. - InvalidResourceMode ResourceMode = 0 - // ManagedResourceMode indicates a managed resource. - ManagedResourceMode ResourceMode = 'M' - // DataResourceMode indicates a data resource. - DataResourceMode ResourceMode = 'D' -) diff --git a/terraform/configs/backend.go b/terraform/configs/backend.go deleted file mode 100644 index 984e051..0000000 --- a/terraform/configs/backend.go +++ /dev/null @@ -1,13 +0,0 @@ -package configs - -import "github.com/hashicorp/hcl/v2" - -// Backend is an alternative representation of configs.Backend. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/backend.go#L12-L18 -type Backend struct { - Type string - Config hcl.Body - - TypeRange hcl.Range - DeclRange hcl.Range -} diff --git a/terraform/configs/config.go b/terraform/configs/config.go deleted file mode 100644 index cc8af8b..0000000 --- a/terraform/configs/config.go +++ /dev/null @@ -1,21 +0,0 @@ -package configs - -import ( - "github.com/hashicorp/go-version" - "github.com/hashicorp/hcl/v2" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/addrs" -) - -// Config is an alternative representation of configs.Config. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/config.go#L22-L78 -type Config struct { - // Root *Config - // Parent *Config - Path addrs.Module - // Children map[string]*Config - Module *Module - CallRange hcl.Range - SourceAddr string - SourceAddrRange hcl.Range - Version *version.Version -} diff --git a/terraform/configs/module.go b/terraform/configs/module.go deleted file mode 100644 index a46df01..0000000 --- a/terraform/configs/module.go +++ /dev/null @@ -1,31 +0,0 @@ -package configs - -import ( - "github.com/terraform-linters/tflint-plugin-sdk/terraform/addrs" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/experiments" -) - -// Module is an alternative representation of configs.Module. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/module.go#L14-L45 -type Module struct { - SourceDir string - - CoreVersionConstraints []VersionConstraint - - ActiveExperiments experiments.Set - - Backend *Backend - ProviderConfigs map[string]*Provider - ProviderRequirements *RequiredProviders - ProviderLocalNames map[addrs.Provider]string - ProviderMetas map[addrs.Provider]*ProviderMeta - - Variables map[string]*Variable - Locals map[string]*Local - Outputs map[string]*Output - - ModuleCalls map[string]*ModuleCall - - ManagedResources map[string]*Resource - DataResources map[string]*Resource -} diff --git a/terraform/configs/module_call.go b/terraform/configs/module_call.go deleted file mode 100644 index 6a9472c..0000000 --- a/terraform/configs/module_call.go +++ /dev/null @@ -1,34 +0,0 @@ -package configs - -import "github.com/hashicorp/hcl/v2" - -// ModuleCall is an alternative representation of configs.ModuleCall. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/module_call.go#L12-L31 -// DependsOn is not supported due to the difficulty of intermediate representation. -type ModuleCall struct { - Name string - - SourceAddr string - SourceAddrRange hcl.Range - SourceSet bool - - Config hcl.Body - - Version VersionConstraint - - Count hcl.Expression - ForEach hcl.Expression - - Providers []PassedProviderConfig - - // DependsOn []hcl.Traversal - - DeclRange hcl.Range -} - -// PassedProviderConfig is an alternative representation of configs.PassedProviderConfig. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/module_call.go#L148-L151 -type PassedProviderConfig struct { - InChild *ProviderConfigRef - InParent *ProviderConfigRef -} diff --git a/terraform/configs/named_values.go b/terraform/configs/named_values.go deleted file mode 100644 index c6627e4..0000000 --- a/terraform/configs/named_values.go +++ /dev/null @@ -1,79 +0,0 @@ -package configs - -import ( - "github.com/hashicorp/hcl/v2" - "github.com/zclconf/go-cty/cty" -) - -// Variable is an alternative representation of configs.Variable. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/named_values.go#L21-L34 -type Variable struct { - Name string - Description string - Default cty.Value - Type cty.Type - ParsingMode VariableParsingMode - Validations []*VariableValidation - Sensitive bool - - DescriptionSet bool - SensitiveSet bool - - DeclRange hcl.Range -} - -// VariableParsingMode is an alternative representation of configs.VariableParsingMode. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/named_values.go#L234-L242 -type VariableParsingMode rune - -// VariableParseLiteral is a variable parsing mode that just takes the given -// string directly as a cty.String value. -const VariableParseLiteral VariableParsingMode = 'L' - -// VariableParseHCL is a variable parsing mode that attempts to parse the given -// string as an HCL expression and returns the result. -const VariableParseHCL VariableParsingMode = 'H' - -// VariableValidation is an alternative representation of configs.VariableValidation. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/named_values.go#L281-L297 -type VariableValidation struct { - // Condition is an expression that refers to the variable being tested - // and contains no other references. The expression must return true - // to indicate that the value is valid or false to indicate that it is - // invalid. If the expression produces an error, that's considered a bug - // in the module defining the validation rule, not an error in the caller. - Condition hcl.Expression - - // ErrorMessage is one or more full sentences, which would need to be in - // English for consistency with the rest of the error message output but - // can in practice be in any language as long as it ends with a period. - // The message should describe what is required for the condition to return - // true in a way that would make sense to a caller of the module. - ErrorMessage string - - DeclRange hcl.Range -} - -// Local is an alternative representation of configs.Local. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/named_values.go#L501-L506 -type Local struct { - Name string - Expr hcl.Expression - - DeclRange hcl.Range -} - -// Output is an alternative representation of configs.Output. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/named_values.go#L430-L441 -type Output struct { - Name string - Description string - Expr hcl.Expression - // DependsOn []hcl.Traversal - Sensitive bool - - DescriptionSet bool - SensitiveSet bool - - DeclRange hcl.Range -} diff --git a/terraform/configs/provider.go b/terraform/configs/provider.go deleted file mode 100644 index f642d11..0000000 --- a/terraform/configs/provider.go +++ /dev/null @@ -1,48 +0,0 @@ -package configs - -import ( - "github.com/hashicorp/hcl/v2" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/addrs" -) - -// Provider is an alternative representation of configs.Provider. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/provider.go#L17-L28 -type Provider struct { - Name string - NameRange hcl.Range - Alias string - AliasRange *hcl.Range // nil if no alias set - - Version VersionConstraint - - Config hcl.Body - - DeclRange hcl.Range -} - -// ProviderMeta is an alternative representation of configs.ProviderMeta. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/provider_meta.go#L7-L13 -type ProviderMeta struct { - Provider string - Config hcl.Body - - ProviderRange hcl.Range - DeclRange hcl.Range -} - -// RequiredProvider is an alternative representation of configs.RequiredProvider. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/provider_requirements.go#L14-L20 -type RequiredProvider struct { - Name string - Source string - Type addrs.Provider - Requirement VersionConstraint - DeclRange hcl.Range -} - -// RequiredProviders is an alternative representation of configs.RequiredProviders. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/provider_requirements.go#L22-L25 -type RequiredProviders struct { - RequiredProviders map[string]*RequiredProvider - DeclRange hcl.Range -} diff --git a/terraform/configs/provisioner.go b/terraform/configs/provisioner.go deleted file mode 100644 index 96f593a..0000000 --- a/terraform/configs/provisioner.go +++ /dev/null @@ -1,50 +0,0 @@ -package configs - -import "github.com/hashicorp/hcl/v2" - -// Provisioner is an alternative representation of configs.Provisioner. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/provisioner.go#L11-L20 -type Provisioner struct { - Type string - Config hcl.Body - Connection *Connection - When ProvisionerWhen - OnFailure ProvisionerOnFailure - - DeclRange hcl.Range - TypeRange hcl.Range -} - -// Connection is an alternative representation of configs.Connection. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/provisioner.go#L176-L180 -type Connection struct { - Config hcl.Body - - DeclRange hcl.Range -} - -// ProvisionerWhen is an alternative representation of configs.ProvisionerWhen. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/provisioner.go#L182-L191 -type ProvisionerWhen int - -const ( - // ProvisionerWhenInvalid is the zero value of ProvisionerWhen. - ProvisionerWhenInvalid ProvisionerWhen = iota - // ProvisionerWhenCreate indicates the time of creation. - ProvisionerWhenCreate - // ProvisionerWhenDestroy indicates the time of deletion. - ProvisionerWhenDestroy -) - -// ProvisionerOnFailure is an alternative representation of configs.ProvisionerOnFailure. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/provisioner.go#L193-L203 -type ProvisionerOnFailure int - -const ( - // ProvisionerOnFailureInvalid is the zero value of ProvisionerOnFailure. - ProvisionerOnFailureInvalid ProvisionerOnFailure = iota - // ProvisionerOnFailureContinue indicates continuation on failure. - ProvisionerOnFailureContinue - // ProvisionerOnFailureFail indicates failure on failure. - ProvisionerOnFailureFail -) diff --git a/terraform/configs/resource.go b/terraform/configs/resource.go deleted file mode 100644 index b8e22ba..0000000 --- a/terraform/configs/resource.go +++ /dev/null @@ -1,53 +0,0 @@ -package configs - -import ( - "github.com/hashicorp/hcl/v2" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/addrs" -) - -// Resource is an alternative representation of configs.Resource. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/resource.go#L14-L34 -// DependsOn is not supported due to the difficulty of intermediate representation. -type Resource struct { - Mode addrs.ResourceMode - Name string - Type string - Config hcl.Body - Count hcl.Expression - ForEach hcl.Expression - - ProviderConfigRef *ProviderConfigRef - Provider addrs.Provider - - // DependsOn []hcl.Traversal - - Managed *ManagedResource - - DeclRange hcl.Range - TypeRange hcl.Range -} - -// ManagedResource is an alternative representation of configs.ManagedResource. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/resource.go#L37-L48 -// IgnoreChanges is not supported due to the difficulty of intermediate representation. -type ManagedResource struct { - Connection *Connection - Provisioners []*Provisioner - - CreateBeforeDestroy bool - PreventDestroy bool - // IgnoreChanges []hcl.Traversal - IgnoreAllChanges bool - - CreateBeforeDestroySet bool - PreventDestroySet bool -} - -// ProviderConfigRef is an alternative representation of configs.ProviderConfigRef. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/resource.go#L384-L389 -type ProviderConfigRef struct { - Name string - NameRange hcl.Range - Alias string - AliasRange *hcl.Range -} diff --git a/terraform/configs/version_constraint.go b/terraform/configs/version_constraint.go deleted file mode 100644 index d475562..0000000 --- a/terraform/configs/version_constraint.go +++ /dev/null @@ -1,13 +0,0 @@ -package configs - -import ( - "github.com/hashicorp/go-version" - "github.com/hashicorp/hcl/v2" -) - -// VersionConstraint is an alternative representation of configs.VersionConstraint. -// https://github.com/hashicorp/terraform/blob/v0.14.3/configs/version_constraint.go#L16-L19 -type VersionConstraint struct { - Required version.Constraints - DeclRange hcl.Range -} diff --git a/terraform/doc.go b/terraform/doc.go deleted file mode 100644 index cb2e68f..0000000 --- a/terraform/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Package terraform contains structures for Terraform's alternative -// representations. The reason for providing these is to avoid depending -// on Terraform for this plugin. -// -// The structures provided by this package are minimal used for static analysis. -// Also, this is often not transferred directly from the host process, but through -// an intermediate representation. See the package tflint for details. -package terraform diff --git a/terraform/experiments/experiment.go b/terraform/experiments/experiment.go deleted file mode 100644 index 10332db..0000000 --- a/terraform/experiments/experiment.go +++ /dev/null @@ -1,9 +0,0 @@ -package experiments - -// Experiment is an alternative representation of experiments.Experiment. -// https://github.com/hashicorp/terraform/blob/v0.14.3/experiments/experiment.go#L5 -type Experiment string - -// Set is an alternative representation of experiments.Set. -// https://github.com/hashicorp/terraform/blob/v0.14.3/experiments/set.go#L5 -type Set map[Experiment]struct{} diff --git a/tflint/client/client.go b/tflint/client/client.go deleted file mode 100644 index 1c6d9c1..0000000 --- a/tflint/client/client.go +++ /dev/null @@ -1,413 +0,0 @@ -package client - -import ( - "fmt" - "log" - "net" - "net/rpc" - - hcl "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/gohcl" - "github.com/hashicorp/hcl/v2/hclsyntax" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/configs" - "github.com/terraform-linters/tflint-plugin-sdk/tflint" - "github.com/zclconf/go-cty/cty" - "github.com/zclconf/go-cty/cty/gocty" -) - -// Client is an RPC client for plugins. -type Client struct { - rpcClient *rpc.Client -} - -// NewClient returns a new Client. -func NewClient(conn net.Conn) *Client { - return &Client{rpcClient: rpc.NewClient(conn)} -} - -// WalkResourceAttributes calls the server-side Attributes method and passes the decoded response -// to the passed function. -func (c *Client) WalkResourceAttributes(resource, attributeName string, walker func(*hcl.Attribute) error) error { - log.Printf("[DEBUG] Walk `%s.*.%s` attribute", resource, attributeName) - - var response AttributesResponse - if err := c.rpcClient.Call("Plugin.Attributes", AttributesRequest{Resource: resource, AttributeName: attributeName}, &response); err != nil { - return err - } - if response.Err != nil { - return response.Err - } - - for _, attr := range response.Attributes { - attribute, diags := decodeAttribute(attr) - if diags.HasErrors() { - return diags - } - - if err := walker(attribute); err != nil { - return err - } - } - - return nil -} - -// WalkResourceBlocks calls the server-side Blocks method and passes the decoded response -// to the passed function. -func (c *Client) WalkResourceBlocks(resource, blockType string, walker func(*hcl.Block) error) error { - log.Printf("[DEBUG] Walk `%s.*.%s` block", resource, blockType) - - var response BlocksResponse - if err := c.rpcClient.Call("Plugin.Blocks", BlocksRequest{Resource: resource, BlockType: blockType}, &response); err != nil { - return err - } - if response.Err != nil { - return response.Err - } - - for _, b := range response.Blocks { - block, diags := decodeBlock(b) - if diags.HasErrors() { - return diags - } - - if err := walker(block); err != nil { - return err - } - } - - return nil -} - -// WalkResources calls the server-side Resources method and passes the decoded response -// to the passed function. -func (c *Client) WalkResources(resource string, walker func(*configs.Resource) error) error { - log.Printf("[DEBUG] Walk `%s` resource", resource) - - var response ResourcesResponse - if err := c.rpcClient.Call("Plugin.Resources", ResourcesRequest{Name: resource}, &response); err != nil { - return err - } - if response.Err != nil { - return response.Err - } - - for _, r := range response.Resources { - resource, diags := decodeResource(r) - if diags.HasErrors() { - return diags - } - - if err := walker(resource); err != nil { - return err - } - } - return nil -} - -// WalkModuleCalls calls the server-side ModuleCalls method and passed the decode response -// to the passed function. -func (c *Client) WalkModuleCalls(walker func(*configs.ModuleCall) error) error { - log.Printf("[DEBUG] WalkModuleCalls") - - var response ModuleCallsResponse - if err := c.rpcClient.Call("Plugin.ModuleCalls", ModuleCallsRequest{}, &response); err != nil { - return err - } - if response.Err != nil { - return response.Err - } - - for _, c := range response.ModuleCalls { - call, diags := decodeModuleCall(c) - if diags.HasErrors() { - return diags - } - - if err := walker(call); err != nil { - return err - } - } - - return nil -} - -// Backend calls the server-side Backend method and returns the backend configuration. -func (c *Client) Backend() (*configs.Backend, error) { - log.Printf("[DEBUG] Backend") - - var response BackendResponse - if err := c.rpcClient.Call("Plugin.Backend", BackendRequest{}, &response); err != nil { - return nil, err - } - if response.Err != nil { - return nil, response.Err - } - - backend, diags := decodeBackend(response.Backend) - if diags.HasErrors() { - return nil, diags - } - - return backend, nil -} - -// Config calls the server-side Config method and returns the Terraform configuration. -func (c *Client) Config() (*configs.Config, error) { - log.Print("[DEBUG] Accessing to Config") - - var response ConfigResponse - if err := c.rpcClient.Call("Plugin.Config", ConfigRequest{}, &response); err != nil { - return nil, err - } - if response.Err != nil { - return nil, response.Err - } - - config, diags := decodeConfig(response.Config) - if diags.HasErrors() { - return nil, diags - } - - return config, nil -} - -// File calls the server-side File method and returns the hcl.File object. -func (c *Client) File(filename string) (*hcl.File, error) { - var response FileResponse - if err := c.rpcClient.Call("Plugin.File", FileRequest{Filename: filename}, &response); err != nil { - return nil, err - } - - file, diags := parseConfig(response.Bytes, filename, response.Range.Start) - if diags.HasErrors() { - return nil, diags - } - return file, nil -} - -// Files calls the server-side Files method and returns a collection of hcl.File object. -func (c *Client) Files() (map[string]*hcl.File, error) { - var response FilesResponse - if err := c.rpcClient.Call("Plugin.Files", FilesRequest{}, &response); err != nil { - return nil, err - } - - files := make(map[string]*hcl.File) - for filename, content := range response.Files { - file, diags := parseConfig(content, filename, hcl.InitialPos) - - if diags.HasErrors() { - return nil, diags - } - - files[filename] = file - } - return files, nil -} - -// RootProvider calls the server-side RootProvider method and returns the provider configuration. -func (c *Client) RootProvider(name string) (*configs.Provider, error) { - log.Printf("[DEBUG] Accessing to the `%s` provider config in the root module", name) - - var response RootProviderResponse - if err := c.rpcClient.Call("Plugin.RootProvider", RootProviderRequest{Name: name}, &response); err != nil { - return nil, err - } - if response.Err != nil { - return nil, response.Err - } - - provider, diags := decodeProvider(response.Provider) - if diags.HasErrors() { - return nil, diags - } - - return provider, nil -} - -// DecodeRuleConfig calls the server-side RuleConfig method and reflects the response -// in the passed argument. -func (c *Client) DecodeRuleConfig(name string, ret interface{}) error { - var response RuleConfigResponse - - req := RuleConfigRequest{Name: name} - if err := c.rpcClient.Call("Plugin.RuleConfig", req, &response); err != nil { - return err - } - if response.Err != nil { - return response.Err - } - - if !response.Exists { - return nil - } - file, diags := hclsyntax.ParseConfig(response.Config, response.Range.Filename, response.Range.Start) - if diags.HasErrors() { - return diags - } - if diags = gohcl.DecodeBody(file.Body, nil, ret); diags.HasErrors() { - return diags - } - - return nil -} - -// EvaluateExpr calls the server-side EvalExpr method and reflects the response -// in the passed argument. -func (c *Client) EvaluateExpr(expr hcl.Expression, ret interface{}, wantType *cty.Type) error { - if wantType == nil { - wantType = &cty.Type{} - } - - file, err := c.File(expr.Range().Filename) - if err != nil { - return err - } - - var response EvalExprResponse - req := EvalExprRequest{Type: *wantType} - if req.Type == (cty.Type{}) { - req.Ret = ret - } - req.Expr, req.ExprRange = encodeExpr(file.Bytes, expr) - if err := c.rpcClient.Call("Plugin.EvalExpr", req, &response); err != nil { - return err - } - if response.Err != nil { - return response.Err - } - - err = gocty.FromCtyValue(response.Val, ret) - if err != nil { - err := &tflint.Error{ - Code: tflint.TypeMismatchError, - Level: tflint.ErrorLevel, - Message: fmt.Sprintf( - "Invalid type expression in %s:%d", - expr.Range().Filename, - expr.Range().Start.Line, - ), - Cause: err, - } - log.Printf("[ERROR] %s", err) - return err - } - return nil -} - -// EvaluateExprOnRootCtx calls the server-side EvalExprOnRootCtx method and reflects the response -// in the passed argument. -func (c *Client) EvaluateExprOnRootCtx(expr hcl.Expression, ret interface{}, wantType *cty.Type) error { - if wantType == nil { - wantType = &cty.Type{} - } - file, err := c.File(expr.Range().Filename) - if err != nil { - return err - } - - var response EvalExprResponse - req := EvalExprRequest{Type: *wantType} - if req.Type == (cty.Type{}) { - req.Ret = ret - } - req.Expr, req.ExprRange = encodeExpr(file.Bytes, expr) - if err := c.rpcClient.Call("Plugin.EvalExprOnRootCtx", req, &response); err != nil { - return err - } - if response.Err != nil { - return response.Err - } - - err = gocty.FromCtyValue(response.Val, ret) - if err != nil { - err := &tflint.Error{ - Code: tflint.TypeMismatchError, - Level: tflint.ErrorLevel, - Message: fmt.Sprintf( - "Invalid type expression in %s:%d", - expr.Range().Filename, - expr.Range().Start.Line, - ), - Cause: err, - } - log.Printf("[ERROR] %s", err) - return err - } - return nil -} - -// IsNullExpr calls the server-side IsNullExpr method with the passed expression. -func (c *Client) IsNullExpr(expr hcl.Expression) (bool, error) { - file, err := c.File(expr.Range().Filename) - if err != nil { - return false, err - } - - var response IsNullExprResponse - req := &IsNullExprRequest{} - req.Expr, req.Range = encodeExpr(file.Bytes, expr) - if err := c.rpcClient.Call("Plugin.IsNullExpr", req, &response); err != nil { - return false, err - } - - return response.Ret, response.Err -} - -// EmitIssueOnExpr calls the server-side EmitIssue method with the passed expression. -func (c *Client) EmitIssueOnExpr(rule tflint.RPCRule, message string, expr hcl.Expression) error { - file, err := c.File(expr.Range().Filename) - if err != nil { - return err - } - - req := &EmitIssueRequest{ - Rule: encodeRule(rule), - Message: message, - Location: expr.Range(), - } - req.Expr, req.ExprRange = encodeExpr(file.Bytes, expr) - - if err := c.rpcClient.Call("Plugin.EmitIssue", &req, new(interface{})); err != nil { - return err - } - return nil -} - -// EmitIssue calls the server-side EmitIssue method with the passed rule and range. -// You should use EmitIssueOnExpr if you want to emit an issue for an expression. -// This API provides a lower level interface. -func (c *Client) EmitIssue(rule tflint.RPCRule, message string, location hcl.Range) error { - req := &EmitIssueRequest{ - Rule: encodeRule(rule), - Message: message, - Location: location, - } - - if err := c.rpcClient.Call("Plugin.EmitIssue", &req, new(interface{})); err != nil { - return err - } - return nil -} - -// EnsureNoError is a helper for error handling. Depending on the type of error generated by EvaluateExpr, -// determine whether to exit, skip, or continue. If it is continued, the passed function will be executed. -func (*Client) EnsureNoError(err error, proc func() error) error { - if err == nil { - return proc() - } - - if appErr, ok := err.(tflint.Error); ok { - switch appErr.Level { - case tflint.WarningLevel: - return nil - case tflint.ErrorLevel: - return appErr - default: - panic(appErr) - } - } else { - return err - } -} diff --git a/tflint/client/client_test.go b/tflint/client/client_test.go deleted file mode 100644 index fc159df..0000000 --- a/tflint/client/client_test.go +++ /dev/null @@ -1,445 +0,0 @@ -package client - -import ( - "errors" - "net" - "net/rpc" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - hcl "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/hclsyntax" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/addrs" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/configs" - "github.com/terraform-linters/tflint-plugin-sdk/tflint" - "github.com/zclconf/go-cty/cty" -) - -type mockServer struct { - Listener *net.TCPListener -} - -func (*mockServer) Attributes(req *AttributesRequest, resp *AttributesResponse) error { - *resp = AttributesResponse{Attributes: []*Attribute{ - { - Name: req.AttributeName, - Expr: []byte("1"), - ExprRange: hcl.Range{ - Filename: "example.tf", - Start: hcl.Pos{Line: 1, Column: 1}, - End: hcl.Pos{Line: 2, Column: 2}, - }, - Range: hcl.Range{ - Start: hcl.Pos{Line: 1, Column: 1}, - End: hcl.Pos{Line: 2, Column: 2}, - }, - }, - }, Err: nil} - return nil -} - -func (*mockServer) Blocks(req *BlocksRequest, resp *BlocksResponse) error { - *resp = BlocksResponse{Blocks: []*Block{ - { - Type: "resource", - Labels: []string{"aws_instance", "foo"}, - Body: []byte(`instance_type = "t2.micro"`), - BodyRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 28}}, - DefRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 29}}, - TypeRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 8}}, - LabelRanges: []hcl.Range{ - {Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 10}, End: hcl.Pos{Line: 3, Column: 23}}, - {Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 25}, End: hcl.Pos{Line: 3, Column: 29}}, - }, - }, - }, Err: nil} - return nil -} - -func (*mockServer) Resources(req *ResourcesRequest, resp *ResourcesResponse) error { - *resp = ResourcesResponse{Resources: []*Resource{ - { - Mode: addrs.ManagedResourceMode, - Name: "example", - Type: "resource", - Config: []byte(`instance_type = "t2.micro"`), - ConfigRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 28}}, - Count: nil, - ForEach: nil, - ProviderConfigRef: nil, - Provider: addrs.Provider{Type: "aws", Namespace: "hashicorp", Hostname: "registry.terraform.io"}, - Managed: &ManagedResource{ - Connection: nil, - Provisioners: []*Provisioner{}, - CreateBeforeDestroy: false, - PreventDestroy: false, - IgnoreAllChanges: false, - CreateBeforeDestroySet: false, - PreventDestroySet: false, - }, - DeclRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 29}}, - TypeRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 8}}, - }, - }, Err: nil} - return nil -} - -func (*mockServer) Backend(req *BackendRequest, resp *BackendResponse) error { - *resp = BackendResponse{Backend: &Backend{ - Type: "example", - Config: []byte(`storage = "cloud"`), - ConfigRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 5}, End: hcl.Pos{Line: 3, Column: 22}}, - DeclRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 22}}, - TypeRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 11}, End: hcl.Pos{Line: 2, Column: 19}}, - }, Err: nil} - return nil -} - -func (*mockServer) File(req *FileRequest, resp *FileResponse) error { - *resp = FileResponse{Bytes: []byte("foo = 1"), Range: hcl.Range{Filename: req.Filename, Start: hcl.InitialPos}} - return nil -} - -func (*mockServer) EvalExpr(req *EvalExprRequest, resp *EvalExprResponse) error { - *resp = EvalExprResponse{Val: cty.StringVal("1"), Err: nil} - return nil -} - -func (s *mockServer) EmitIssue(req *EmitIssueRequest, resp *interface{}) error { - return nil -} - -func startMockServer(t *testing.T) (*Client, *mockServer) { - addy, err := net.ResolveTCPAddr("tcp", "0.0.0.0:42586") - if err != nil { - t.Fatal(err) - } - inbound, err := net.ListenTCP("tcp", addy) - if err != nil { - t.Fatal(err) - } - - server := &mockServer{Listener: inbound} - rpc.RegisterName("Plugin", server) - go rpc.Accept(inbound) - - conn, err := net.Dial("tcp", "0.0.0.0:42586") - if err != nil { - t.Fatal(err) - } - return NewClient(conn), server -} - -func Test_WalkResourceAttributes(t *testing.T) { - client, server := startMockServer(t) - defer server.Listener.Close() - - walked := []*hcl.Attribute{} - walker := func(attribute *hcl.Attribute) error { - walked = append(walked, attribute) - return nil - } - - if err := client.WalkResourceAttributes("foo", "bar", walker); err != nil { - t.Fatal(err) - } - - expr, diags := hclsyntax.ParseExpression([]byte("1"), "example.tf", hcl.Pos{Line: 1, Column: 1}) - if diags.HasErrors() { - t.Fatal(diags) - } - expected := []*hcl.Attribute{ - { - Name: "bar", - Expr: expr, - Range: hcl.Range{ - Start: hcl.Pos{Line: 1, Column: 1}, - End: hcl.Pos{Line: 2, Column: 2}, - }, - }, - } - - opt := cmpopts.IgnoreFields(hclsyntax.LiteralValueExpr{}, "Val") - if !cmp.Equal(expected, walked, opt) { - t.Fatalf("Diff: %s", cmp.Diff(expected, walked, opt)) - } -} - -func Test_Backend(t *testing.T) { - client, server := startMockServer(t) - defer server.Listener.Close() - - expected := &configs.Backend{ - Type: "example", - Config: &hclsyntax.Body{ - Attributes: hclsyntax.Attributes{ - "storage": { - Name: "storage", - Expr: &hclsyntax.TemplateExpr{ - Parts: []hclsyntax.Expression{ - &hclsyntax.LiteralValueExpr{ - SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 16}, End: hcl.Pos{Line: 3, Column: 21}}, - }, - }, - SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 15}, End: hcl.Pos{Line: 3, Column: 22}}, - }, - SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 5}, End: hcl.Pos{Line: 3, Column: 22}}, - NameRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 5}, End: hcl.Pos{Line: 3, Column: 12}}, - EqualsRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 13}, End: hcl.Pos{Line: 3, Column: 14}}, - }, - }, - Blocks: hclsyntax.Blocks{}, - SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 5}, End: hcl.Pos{Line: 3, Column: 22}}, - EndRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 3, Column: 22}, End: hcl.Pos{Line: 3, Column: 22}}, - }, - DeclRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 22}}, - TypeRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 11}, End: hcl.Pos{Line: 2, Column: 19}}, - } - - backend, err := client.Backend() - if err != nil { - t.Fatal(err) - } - - opts := []cmp.Option{ - cmpopts.IgnoreUnexported(hclsyntax.Body{}), - cmpopts.IgnoreFields(hclsyntax.LiteralValueExpr{}, "Val"), - cmpopts.IgnoreFields(hcl.Pos{}, "Byte"), - } - if !cmp.Equal(expected, backend, opts...) { - t.Fatalf("Diff: %s", cmp.Diff(expected, backend, opts...)) - } -} - -func Test_WalkResourceBlocks(t *testing.T) { - client, server := startMockServer(t) - defer server.Listener.Close() - - walked := []*hcl.Block{} - walker := func(block *hcl.Block) error { - walked = append(walked, block) - return nil - } - - if err := client.WalkResourceBlocks("foo", "bar", walker); err != nil { - t.Fatal(err) - } - - expected := []*hcl.Block{ - { - Type: "resource", - Labels: []string{"aws_instance", "foo"}, - Body: &hclsyntax.Body{ - Attributes: hclsyntax.Attributes{ - "instance_type": { - Name: "instance_type", - Expr: &hclsyntax.TemplateExpr{ - Parts: []hclsyntax.Expression{ - &hclsyntax.LiteralValueExpr{ - SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 20}, End: hcl.Pos{Line: 2, Column: 28}}, - }, - }, - SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 19}, End: hcl.Pos{Line: 2, Column: 29}}, - }, - SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 29}}, - NameRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 16}}, - EqualsRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 17}, End: hcl.Pos{Line: 2, Column: 18}}, - }, - }, - Blocks: hclsyntax.Blocks{}, - SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 29}}, - EndRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 29}, End: hcl.Pos{Line: 2, Column: 29}}, - }, - DefRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 29}}, - TypeRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 8}}, - LabelRanges: []hcl.Range{ - {Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 10}, End: hcl.Pos{Line: 3, Column: 23}}, - {Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 25}, End: hcl.Pos{Line: 3, Column: 29}}, - }, - }, - } - - opts := []cmp.Option{ - cmpopts.IgnoreUnexported(hclsyntax.Body{}), - cmpopts.IgnoreFields(hclsyntax.LiteralValueExpr{}, "Val"), - cmpopts.IgnoreFields(hcl.Pos{}, "Byte"), - } - if !cmp.Equal(expected, walked, opts...) { - t.Fatalf("Diff: %s", cmp.Diff(expected, walked, opts...)) - } -} - -func Test_WalkResources(t *testing.T) { - client, server := startMockServer(t) - defer server.Listener.Close() - - walked := []*configs.Resource{} - walker := func(block *configs.Resource) error { - walked = append(walked, block) - return nil - } - - if err := client.WalkResources("example", walker); err != nil { - t.Fatal(err) - } - - expected := []*configs.Resource{ - { - Mode: addrs.ManagedResourceMode, - Name: "example", - Type: "resource", - Config: &hclsyntax.Body{ - Attributes: hclsyntax.Attributes{ - "instance_type": { - Name: "instance_type", - Expr: &hclsyntax.TemplateExpr{ - Parts: []hclsyntax.Expression{ - &hclsyntax.LiteralValueExpr{ - SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 20}, End: hcl.Pos{Line: 2, Column: 28}}, - }, - }, - SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 19}, End: hcl.Pos{Line: 2, Column: 29}}, - }, - SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 29}}, - NameRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 16}}, - EqualsRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 17}, End: hcl.Pos{Line: 2, Column: 18}}, - }, - }, - Blocks: hclsyntax.Blocks{}, - SrcRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 3}, End: hcl.Pos{Line: 2, Column: 29}}, - EndRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 2, Column: 29}, End: hcl.Pos{Line: 2, Column: 29}}, - }, - Count: nil, - ForEach: nil, - ProviderConfigRef: nil, - Provider: addrs.Provider{Type: "aws", Namespace: "hashicorp", Hostname: "registry.terraform.io"}, - Managed: &configs.ManagedResource{ - Connection: nil, - Provisioners: []*configs.Provisioner{}, - CreateBeforeDestroy: false, - PreventDestroy: false, - IgnoreAllChanges: false, - CreateBeforeDestroySet: false, - PreventDestroySet: false, - }, - DeclRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 29}}, - TypeRange: hcl.Range{Filename: "example.tf", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 1, Column: 8}}, - }, - } - - opts := []cmp.Option{ - cmpopts.IgnoreUnexported(hclsyntax.Body{}), - cmpopts.IgnoreFields(hclsyntax.LiteralValueExpr{}, "Val"), - cmpopts.IgnoreFields(hcl.Pos{}, "Byte"), - } - if !cmp.Equal(expected, walked, opts...) { - t.Fatalf("Diff: %s", cmp.Diff(expected, walked, opts...)) - } -} - -func Test_EvaluateExpr(t *testing.T) { - client, server := startMockServer(t) - defer server.Listener.Close() - - expr, diags := hclsyntax.ParseExpression([]byte("1"), "example.tf", hcl.Pos{Line: 1, Column: 7}) - if diags.HasErrors() { - t.Fatal(diags) - } - - var ret string - if err := client.EvaluateExpr(expr, &ret, nil); err != nil { - t.Fatal(err) - } - - if ret != "1" { - t.Fatalf("Expected: 1, but got %s", ret) - } -} - -type testRule struct{} - -func (*testRule) Name() string { return "test" } -func (*testRule) Enabled() bool { return true } -func (*testRule) Severity() string { return "Error" } -func (*testRule) Link() string { return "" } -func (*testRule) Check(tflint.RPCRunner) error { return nil } - -func Test_EmitIssueOnExpr(t *testing.T) { - client, server := startMockServer(t) - defer server.Listener.Close() - - expr, diags := hclsyntax.ParseExpression([]byte("1"), "example.tf", hcl.Pos{Line: 1, Column: 7}) - if diags.HasErrors() { - t.Fatal(diags) - } - - if err := client.EmitIssueOnExpr(&testRule{}, "example.tf", expr); err != nil { - t.Fatal(err) - } -} - -func Test_EmitIssue(t *testing.T) { - client, server := startMockServer(t) - defer server.Listener.Close() - - expr, diags := hclsyntax.ParseExpression([]byte("1"), "example.tf", hcl.Pos{Line: 1, Column: 7}) - if diags.HasErrors() { - t.Fatal(diags) - } - - if err := client.EmitIssue(&testRule{}, "example.tf", expr.Range()); err != nil { - t.Fatal(err) - } -} - -func Test_EnsureNoError(t *testing.T) { - cases := []struct { - Name string - Error error - ErrorText string - }{ - { - Name: "no error", - Error: nil, - ErrorText: "function called", - }, - { - Name: "native error", - Error: errors.New("Error occurred"), - ErrorText: "Error occurred", - }, - { - Name: "warning error", - Error: tflint.Error{ - Code: tflint.UnknownValueError, - Level: tflint.WarningLevel, - Message: "Warning error", - }, - }, - { - Name: "app error", - Error: tflint.Error{ - Code: tflint.TypeMismatchError, - Level: tflint.ErrorLevel, - Message: "App error", - }, - ErrorText: "App error", - }, - } - - client, _ := startMockServer(t) - - for _, tc := range cases { - err := client.EnsureNoError(tc.Error, func() error { - return errors.New("function called") - }) - if err == nil { - if tc.ErrorText != "" { - t.Fatalf("Failed `%s` test: expected error is not occurred `%s`", tc.Name, tc.ErrorText) - } - } else if err.Error() != tc.ErrorText { - t.Fatalf("Failed `%s` test: expected error is %s, but get %s", tc.Name, tc.ErrorText, err) - } - } -} diff --git a/tflint/client/decode.go b/tflint/client/decode.go deleted file mode 100644 index 1a2826c..0000000 --- a/tflint/client/decode.go +++ /dev/null @@ -1,142 +0,0 @@ -package client - -import ( - "fmt" - "strings" - - "github.com/hashicorp/go-version" - hcl "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/hclsyntax" - "github.com/hashicorp/hcl/v2/json" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/configs" -) - -// Attribute is an intermediate representation of hcl.Attribute. -type Attribute struct { - Name string - Expr []byte - ExprRange hcl.Range - Range hcl.Range - NameRange hcl.Range -} - -func decodeAttribute(attribute *Attribute) (*hcl.Attribute, hcl.Diagnostics) { - expr, diags := parseExpression(attribute.Expr, attribute.ExprRange.Filename, attribute.ExprRange.Start) - if diags.HasErrors() { - return nil, diags - } - - return &hcl.Attribute{ - Name: attribute.Name, - Expr: expr, - Range: attribute.Range, - NameRange: attribute.NameRange, - }, nil -} - -// Block is an intermediate representation of hcl.Block. -type Block struct { - Type string - Labels []string - Body []byte - BodyRange hcl.Range - - DefRange hcl.Range - TypeRange hcl.Range - LabelRanges []hcl.Range -} - -func decodeBlock(block *Block) (*hcl.Block, hcl.Diagnostics) { - file, diags := parseConfig(block.Body, block.BodyRange.Filename, block.BodyRange.Start) - if diags.HasErrors() { - return nil, diags - } - - return &hcl.Block{ - Type: block.Type, - Labels: block.Labels, - Body: file.Body, - DefRange: block.DefRange, - TypeRange: block.TypeRange, - LabelRanges: block.LabelRanges, - }, nil -} - -func parseExpression(src []byte, filename string, start hcl.Pos) (hcl.Expression, hcl.Diagnostics) { - if strings.HasSuffix(filename, ".tf") { - // HACK: Always add a newline to avoid heredoc parse errors. - // @see https://github.com/hashicorp/hcl/issues/441 - src = []byte(string(src) + "\n") - return hclsyntax.ParseExpression(src, filename, start) - } - - if strings.HasSuffix(filename, ".tf.json") { - return json.ParseExpressionWithStartPos(src, filename, start) - } - - panic(fmt.Sprintf("Unexpected file: %s", filename)) -} - -func hasUnterminatedTemplateString(diags []*hcl.Diagnostic) bool { - for _, diag := range diags { - if diag.Summary == "Unterminated template string" && - diag.Detail == "No closing marker was found for the string." { - return true - } - } - - return false -} - -func parseConfig(src []byte, filename string, start hcl.Pos) (*hcl.File, hcl.Diagnostics) { - if strings.HasSuffix(filename, ".tf") { - file, diags := hclsyntax.ParseConfig(src, filename, start) - if hasUnterminatedTemplateString(diags) { - // HACK: Add a newline to avoid heredoc parse errors. - // @see https://github.com/hashicorp/hcl/issues/441 - src = []byte(string(src) + "\n") - fixedFile, fixedDiags := hclsyntax.ParseConfig(src, filename, start) - - // Still has error? Return first result - if hasUnterminatedTemplateString(fixedDiags) { - return file, diags - } - - return fixedFile, fixedDiags - } - - return file, diags - } - - if strings.HasSuffix(filename, ".tf.json") { - return json.ParseWithStartPos(src, filename, start) - } - - panic(fmt.Sprintf("Unexpected file: %s", filename)) -} - -func parseVersionConstraint(versionStr string, versionRange hcl.Range) (configs.VersionConstraint, hcl.Diagnostics) { - versionConstraint := configs.VersionConstraint{DeclRange: versionRange} - if !versionRange.Empty() { - required, err := version.NewConstraint(versionStr) - if err != nil { - detail := fmt.Sprintf( - "Version constraint '%s' parse error: %s", - versionStr, - err, - ) - - return versionConstraint, hcl.Diagnostics{ - &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Failed to reparse version constraint", - Detail: detail, - Subject: &versionRange, - }, - } - } - - versionConstraint.Required = required - } - return versionConstraint, nil -} diff --git a/tflint/client/decode_backend.go b/tflint/client/decode_backend.go deleted file mode 100644 index 316b500..0000000 --- a/tflint/client/decode_backend.go +++ /dev/null @@ -1,33 +0,0 @@ -package client - -import ( - hcl "github.com/hashicorp/hcl/v2" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/configs" -) - -// Backend is an intermediate representation of terraform.Backend. -type Backend struct { - Type string - Config []byte - ConfigRange hcl.Range - TypeRange hcl.Range - DeclRange hcl.Range -} - -func decodeBackend(backend *Backend) (*configs.Backend, hcl.Diagnostics) { - if backend == nil { - return nil, nil - } - - file, diags := parseConfig(backend.Config, backend.ConfigRange.Filename, backend.ConfigRange.Start) - if diags.HasErrors() { - return nil, diags - } - - return &configs.Backend{ - Type: backend.Type, - Config: file.Body, - TypeRange: backend.TypeRange, - DeclRange: backend.DeclRange, - }, nil -} diff --git a/tflint/client/decode_config.go b/tflint/client/decode_config.go deleted file mode 100644 index a95dda1..0000000 --- a/tflint/client/decode_config.go +++ /dev/null @@ -1,49 +0,0 @@ -package client - -import ( - "github.com/hashicorp/go-version" - hcl "github.com/hashicorp/hcl/v2" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/addrs" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/configs" -) - -// Config is an intermediate representation of configs.Config. -type Config struct { - Path addrs.Module - Module *Module - CallRange hcl.Range - SourceAddr string - SourceAddrRange hcl.Range - Version string -} - -func decodeConfig(config *Config) (*configs.Config, hcl.Diagnostics) { - module, diags := decodeModule(config.Module) - if diags.HasErrors() { - return nil, diags - } - - var ver *version.Version - var err error - if config.Version != "" { - ver, err = version.NewVersion(config.Version) - if err != nil { - return nil, hcl.Diagnostics{ - &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Failed to reparse version", - Detail: err.Error(), - }, - } - } - } - - return &configs.Config{ - Path: config.Path, - Module: module, - CallRange: config.CallRange, - SourceAddr: config.SourceAddr, - SourceAddrRange: config.SourceAddrRange, - Version: ver, - }, nil -} diff --git a/tflint/client/decode_module.go b/tflint/client/decode_module.go deleted file mode 100644 index 4667f80..0000000 --- a/tflint/client/decode_module.go +++ /dev/null @@ -1,149 +0,0 @@ -package client - -import ( - hcl "github.com/hashicorp/hcl/v2" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/addrs" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/configs" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/experiments" -) - -// Module is an intermediate representation of configs.Module. -type Module struct { - SourceDir string - - CoreVersionConstraints []string - CoreVersionConstraintRanges []hcl.Range - - ActiveExperiments experiments.Set - - Backend *Backend - ProviderConfigs map[string]*Provider - ProviderRequirements *RequiredProviders - ProviderLocalNames map[addrs.Provider]string - ProviderMetas map[addrs.Provider]*ProviderMeta - - Variables map[string]*Variable - Locals map[string]*Local - Outputs map[string]*Output - - ModuleCalls map[string]*ModuleCall - - ManagedResources map[string]*Resource - DataResources map[string]*Resource -} - -func decodeModule(module *Module) (*configs.Module, hcl.Diagnostics) { - versionConstraints := make([]configs.VersionConstraint, len(module.CoreVersionConstraints)) - for i, v := range module.CoreVersionConstraints { - constraint, diags := parseVersionConstraint(v, module.CoreVersionConstraintRanges[i]) - if diags.HasErrors() { - return nil, diags - } - versionConstraints[i] = constraint - } - - backend, diags := decodeBackend(module.Backend) - if diags.HasErrors() { - return nil, diags - } - - providers := map[string]*configs.Provider{} - for k, v := range module.ProviderConfigs { - p, diags := decodeProvider(v) - if diags.HasErrors() { - return nil, diags - } - providers[k] = p - } - - requirements, diags := decodeRequiredProviders(module.ProviderRequirements) - if diags.HasErrors() { - return nil, diags - } - - metas := map[addrs.Provider]*configs.ProviderMeta{} - for k, v := range module.ProviderMetas { - m, diags := decodeProviderMeta(v) - if diags.HasErrors() { - return nil, diags - } - metas[k] = m - } - - variables := map[string]*configs.Variable{} - for k, v := range module.Variables { - variable, diags := decodeVariable(v) - if diags.HasErrors() { - return nil, diags - } - variables[k] = variable - } - - locals := map[string]*configs.Local{} - for k, v := range module.Locals { - l, diags := decodeLocal(v) - if diags.HasErrors() { - return nil, diags - } - locals[k] = l - } - - outputs := map[string]*configs.Output{} - for k, v := range module.Outputs { - o, diags := decodeOutput(v) - if diags.HasErrors() { - return nil, diags - } - outputs[k] = o - } - - calls := map[string]*configs.ModuleCall{} - for k, v := range module.ModuleCalls { - c, diags := decodeModuleCall(v) - if diags.HasErrors() { - return nil, diags - } - calls[k] = c - } - - managed := map[string]*configs.Resource{} - for k, v := range module.ManagedResources { - r, diags := decodeResource(v) - if diags.HasErrors() { - return nil, diags - } - managed[k] = r - } - - data := map[string]*configs.Resource{} - for k, v := range module.DataResources { - d, diags := decodeResource(v) - if diags.HasErrors() { - return nil, diags - } - data[k] = d - } - - return &configs.Module{ - SourceDir: module.SourceDir, - - CoreVersionConstraints: versionConstraints, - - ActiveExperiments: module.ActiveExperiments, - - Backend: backend, - ProviderConfigs: providers, - ProviderRequirements: requirements, - ProviderLocalNames: module.ProviderLocalNames, - ProviderMetas: metas, - - Variables: variables, - Locals: locals, - Outputs: outputs, - - ModuleCalls: calls, - - ManagedResources: managed, - DataResources: data, - }, nil -} diff --git a/tflint/client/decode_module_call.go b/tflint/client/decode_module_call.go deleted file mode 100644 index aa5aec0..0000000 --- a/tflint/client/decode_module_call.go +++ /dev/null @@ -1,91 +0,0 @@ -package client - -import ( - hcl "github.com/hashicorp/hcl/v2" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/configs" -) - -// ModuleCall is an intermediate representation of terraform.ModuleCall. -type ModuleCall struct { - Name string - - SourceAddr string - SourceAddrRange hcl.Range - SourceSet bool - - Version string - VersionRange hcl.Range - - Config []byte - ConfigRange hcl.Range - - Count []byte - CountRange hcl.Range - ForEach []byte - ForEachRange hcl.Range - - Providers []PassedProviderConfig - DeclRange hcl.Range - - // DependsOn []hcl.Traversal -} - -// PassedProviderConfig is an intermediate representation of terraform.PassedProviderConfig. -type PassedProviderConfig struct { - InChild *configs.ProviderConfigRef - InParent *configs.ProviderConfigRef -} - -func decodeModuleCall(call *ModuleCall) (*configs.ModuleCall, hcl.Diagnostics) { - file, diags := parseConfig(call.Config, call.ConfigRange.Filename, call.ConfigRange.Start) - if diags.HasErrors() { - return nil, diags - } - - var count hcl.Expression - if call.Count != nil { - count, diags = parseExpression(call.Count, call.CountRange.Filename, call.CountRange.Start) - if diags.HasErrors() { - return nil, diags - } - } - - var forEach hcl.Expression - if call.ForEach != nil { - forEach, diags = parseExpression(call.ForEach, call.ForEachRange.Filename, call.ForEachRange.Start) - if diags.HasErrors() { - return nil, diags - } - } - - providers := []configs.PassedProviderConfig{} - for _, provider := range call.Providers { - providers = append(providers, configs.PassedProviderConfig{ - InChild: provider.InChild, - InParent: provider.InParent, - }) - } - - versionConstraint, diags := parseVersionConstraint(call.Version, call.VersionRange) - if diags.HasErrors() { - return nil, diags - } - - return &configs.ModuleCall{ - Name: call.Name, - - SourceAddr: call.SourceAddr, - SourceAddrRange: call.SourceAddrRange, - SourceSet: call.SourceSet, - - Config: file.Body, - - Version: versionConstraint, - - Count: count, - ForEach: forEach, - - Providers: providers, - DeclRange: call.DeclRange, - }, nil -} diff --git a/tflint/client/decode_named_values.go b/tflint/client/decode_named_values.go deleted file mode 100644 index 52c95ab..0000000 --- a/tflint/client/decode_named_values.go +++ /dev/null @@ -1,155 +0,0 @@ -package client - -import ( - "fmt" - - hcl "github.com/hashicorp/hcl/v2" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/configs" - "github.com/zclconf/go-cty/cty/json" - "github.com/zclconf/go-cty/cty/msgpack" -) - -// Variable is an intermediate representation of configs.Variable. -type Variable struct { - Name string - Description string - Default []byte - Type []byte - ParsingMode configs.VariableParsingMode - Validations []*VariableValidation - Sensitive bool - - DescriptionSet bool - SensitiveSet bool - - DeclRange hcl.Range -} - -func decodeVariable(variable *Variable) (*configs.Variable, hcl.Diagnostics) { - ret := make([]*configs.VariableValidation, len(variable.Validations)) - for i, v := range variable.Validations { - validation, diags := decodeVariableValidation(v) - if diags.HasErrors() { - return nil, diags - } - ret[i] = validation - } - - typeVal, err := json.UnmarshalType(variable.Type) - if err != nil { - return nil, hcl.Diagnostics{ - &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "cannot unmarshal type for variable", - Detail: fmt.Sprint(err), - Subject: &variable.DeclRange, - }, - } - } - defaultVal, err := msgpack.Unmarshal(variable.Default, typeVal) - if err != nil { - return nil, hcl.Diagnostics{ - &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "cannot unmarshal variable default value", - Detail: fmt.Sprint(err), - Subject: &variable.DeclRange, - }, - } - } - - return &configs.Variable{ - Name: variable.Name, - Description: variable.Description, - Default: defaultVal, - Type: typeVal, - ParsingMode: variable.ParsingMode, - Validations: ret, - Sensitive: variable.Sensitive, - - DescriptionSet: variable.DescriptionSet, - SensitiveSet: variable.SensitiveSet, - - DeclRange: variable.DeclRange, - }, nil -} - -// VariableValidation is an intermediate representation of configs.VariableValidation. -type VariableValidation struct { - Condition []byte - ConditionRange hcl.Range - - ErrorMessage string - - DeclRange hcl.Range -} - -func decodeVariableValidation(validation *VariableValidation) (*configs.VariableValidation, hcl.Diagnostics) { - expr, diags := parseExpression(validation.Condition, validation.ConditionRange.Filename, validation.ConditionRange.Start) - if diags.HasErrors() { - return nil, diags - } - - return &configs.VariableValidation{ - Condition: expr, - ErrorMessage: validation.ErrorMessage, - DeclRange: validation.DeclRange, - }, nil -} - -// Local is an intermediate representation of configs.Local. -type Local struct { - Name string - Expr []byte - ExprRange hcl.Range - - DeclRange hcl.Range -} - -func decodeLocal(local *Local) (*configs.Local, hcl.Diagnostics) { - expr, diags := parseExpression(local.Expr, local.ExprRange.Filename, local.ExprRange.Start) - if diags.HasErrors() { - return nil, diags - } - - return &configs.Local{ - Name: local.Name, - Expr: expr, - - DeclRange: local.DeclRange, - }, nil -} - -// Output is an intermediate representation of configs.Output. -type Output struct { - Name string - Description string - Expr []byte - ExprRange hcl.Range - // DependsOn []hcl.Traversal - Sensitive bool - - DescriptionSet bool - SensitiveSet bool - - DeclRange hcl.Range -} - -func decodeOutput(output *Output) (*configs.Output, hcl.Diagnostics) { - expr, diags := parseExpression(output.Expr, output.ExprRange.Filename, output.ExprRange.Start) - if diags.HasErrors() { - return nil, diags - } - - return &configs.Output{ - Name: output.Name, - Description: output.Description, - Expr: expr, - Sensitive: output.Sensitive, - - DescriptionSet: output.DescriptionSet, - SensitiveSet: output.Sensitive, - - DeclRange: output.DeclRange, - }, nil -} diff --git a/tflint/client/decode_provider.go b/tflint/client/decode_provider.go deleted file mode 100644 index 86d36ed..0000000 --- a/tflint/client/decode_provider.go +++ /dev/null @@ -1,124 +0,0 @@ -package client - -import ( - hcl "github.com/hashicorp/hcl/v2" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/addrs" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/configs" -) - -// Provider is an intermediate representation of configs.Provider. -type Provider struct { - Name string - NameRange hcl.Range - Alias string - AliasRange *hcl.Range // nil if no alias set - - Version string - VersionRange hcl.Range - - Config []byte - ConfigRange hcl.Range - - DeclRange hcl.Range -} - -func decodeProvider(provider *Provider) (*configs.Provider, hcl.Diagnostics) { - if provider == nil { - return nil, nil - } - - versionConstraint, diags := parseVersionConstraint(provider.Version, provider.VersionRange) - if diags.HasErrors() { - return nil, diags - } - - file, diags := parseConfig(provider.Config, provider.ConfigRange.Filename, provider.ConfigRange.Start) - if diags.HasErrors() { - return nil, diags - } - - return &configs.Provider{ - Name: provider.Name, - NameRange: provider.NameRange, - Alias: provider.Alias, - AliasRange: provider.AliasRange, - - Version: versionConstraint, - - Config: file.Body, - - DeclRange: provider.DeclRange, - }, nil -} - -// ProviderMeta is an intermediate representation of configs.ProviderMeta. -type ProviderMeta struct { - Provider string - Config []byte - ConfigRange hcl.Range - - ProviderRange hcl.Range - DeclRange hcl.Range -} - -func decodeProviderMeta(meta *ProviderMeta) (*configs.ProviderMeta, hcl.Diagnostics) { - file, diags := parseConfig(meta.Config, meta.ConfigRange.Filename, meta.ConfigRange.Start) - if diags.HasErrors() { - return nil, diags - } - - return &configs.ProviderMeta{ - Provider: meta.Provider, - Config: file.Body, - - ProviderRange: meta.ProviderRange, - DeclRange: meta.DeclRange, - }, nil -} - -// RequiredProvider is an intermediate representation of configs.RequiredProvider. -type RequiredProvider struct { - Name string - Source string - Type addrs.Provider - Requirement string - RequirementRange hcl.Range - DeclRange hcl.Range -} - -func decodeRequiredProvider(provider *RequiredProvider) (*configs.RequiredProvider, hcl.Diagnostics) { - versionConstraint, diags := parseVersionConstraint(provider.Requirement, provider.RequirementRange) - if diags.HasErrors() { - return nil, diags - } - - return &configs.RequiredProvider{ - Name: provider.Name, - Source: provider.Source, - Type: provider.Type, - Requirement: versionConstraint, - DeclRange: provider.DeclRange, - }, nil -} - -// RequiredProviders is an intermediate representation of configs.RequiredProviders. -type RequiredProviders struct { - RequiredProviders map[string]*RequiredProvider - DeclRange hcl.Range -} - -func decodeRequiredProviders(providers *RequiredProviders) (*configs.RequiredProviders, hcl.Diagnostics) { - ret := map[string]*configs.RequiredProvider{} - for k, v := range providers.RequiredProviders { - p, diags := decodeRequiredProvider(v) - if diags.HasErrors() { - return nil, diags - } - ret[k] = p - } - - return &configs.RequiredProviders{ - RequiredProviders: ret, - DeclRange: providers.DeclRange, - }, nil -} diff --git a/tflint/client/decode_provisioner.go b/tflint/client/decode_provisioner.go deleted file mode 100644 index fc633c6..0000000 --- a/tflint/client/decode_provisioner.go +++ /dev/null @@ -1,66 +0,0 @@ -package client - -import ( - hcl "github.com/hashicorp/hcl/v2" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/configs" -) - -// Provisioner is an intermediate representation of terraform.Provisioner. -type Provisioner struct { - Type string - Config []byte - ConfigRange hcl.Range - Connection *Connection - When configs.ProvisionerWhen - OnFailure configs.ProvisionerOnFailure - - DeclRange hcl.Range - TypeRange hcl.Range -} - -func decodeProvisioner(provisioner *Provisioner) (*configs.Provisioner, hcl.Diagnostics) { - file, diags := parseConfig(provisioner.Config, provisioner.ConfigRange.Filename, provisioner.ConfigRange.Start) - if diags.HasErrors() { - return nil, diags - } - - connection, diags := decodeConnection(provisioner.Connection) - if diags.HasErrors() { - return nil, diags - } - - return &configs.Provisioner{ - Type: provisioner.Type, - Config: file.Body, - Connection: connection, - When: provisioner.When, - OnFailure: provisioner.OnFailure, - - DeclRange: provisioner.DeclRange, - TypeRange: provisioner.TypeRange, - }, nil -} - -// Connection is an intermediate representation of terraform.Connection. -type Connection struct { - Config []byte - ConfigRange hcl.Range - - DeclRange hcl.Range -} - -func decodeConnection(connection *Connection) (*configs.Connection, hcl.Diagnostics) { - if connection == nil { - return nil, hcl.Diagnostics{} - } - - file, diags := parseConfig(connection.Config, connection.ConfigRange.Filename, connection.ConfigRange.Start) - if diags.HasErrors() { - return nil, diags - } - - return &configs.Connection{ - Config: file.Body, - DeclRange: connection.DeclRange, - }, nil -} diff --git a/tflint/client/decode_resource.go b/tflint/client/decode_resource.go deleted file mode 100644 index 63d1b6d..0000000 --- a/tflint/client/decode_resource.go +++ /dev/null @@ -1,118 +0,0 @@ -package client - -import ( - hcl "github.com/hashicorp/hcl/v2" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/addrs" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/configs" -) - -// Resource is an intermediate representation of terraform.Resource. -type Resource struct { - Mode addrs.ResourceMode - Name string - Type string - Config []byte - ConfigRange hcl.Range - Count []byte - CountRange hcl.Range - ForEach []byte - ForEachRange hcl.Range - - ProviderConfigRef *configs.ProviderConfigRef - Provider addrs.Provider - - Managed *ManagedResource - - DeclRange hcl.Range - TypeRange hcl.Range -} - -func decodeResource(resource *Resource) (*configs.Resource, hcl.Diagnostics) { - file, diags := parseConfig(resource.Config, resource.ConfigRange.Filename, resource.ConfigRange.Start) - if diags.HasErrors() { - return nil, diags - } - - var count hcl.Expression - if resource.Count != nil { - count, diags = parseExpression(resource.Count, resource.CountRange.Filename, resource.CountRange.Start) - if diags.HasErrors() { - return nil, diags - } - } - - var forEach hcl.Expression - if resource.ForEach != nil { - forEach, diags = parseExpression(resource.ForEach, resource.ForEachRange.Filename, resource.ForEachRange.Start) - if diags.HasErrors() { - return nil, diags - } - } - - managed, diags := decodeManagedResource(resource.Managed) - if diags.HasErrors() { - return nil, diags - } - - return &configs.Resource{ - Mode: resource.Mode, - Name: resource.Name, - Type: resource.Type, - Config: file.Body, - Count: count, - ForEach: forEach, - - ProviderConfigRef: resource.ProviderConfigRef, - Provider: resource.Provider, - - Managed: managed, - - DeclRange: resource.DeclRange, - TypeRange: resource.TypeRange, - }, nil -} - -// ManagedResource is an intermediate representation of terraform.ManagedResource. -type ManagedResource struct { - Connection *Connection - Provisioners []*Provisioner - - CreateBeforeDestroy bool - PreventDestroy bool - IgnoreAllChanges bool - - CreateBeforeDestroySet bool - PreventDestroySet bool -} - -func decodeManagedResource(resource *ManagedResource) (*configs.ManagedResource, hcl.Diagnostics) { - if resource == nil { - return nil, nil - } - - connection, diags := decodeConnection(resource.Connection) - if diags.HasErrors() { - return nil, diags - } - - provisioners := make([]*configs.Provisioner, len(resource.Provisioners)) - for i, p := range resource.Provisioners { - provisioner, diags := decodeProvisioner(p) - if diags.HasErrors() { - return nil, diags - } - provisioners[i] = provisioner - } - - return &configs.ManagedResource{ - Connection: connection, - Provisioners: provisioners, - - CreateBeforeDestroy: resource.CreateBeforeDestroy, - PreventDestroy: resource.PreventDestroy, - IgnoreAllChanges: resource.IgnoreAllChanges, - - CreateBeforeDestroySet: resource.CreateBeforeDestroySet, - PreventDestroySet: resource.PreventDestroySet, - }, nil -} diff --git a/tflint/client/doc.go b/tflint/client/doc.go deleted file mode 100644 index 9cb6c90..0000000 --- a/tflint/client/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// Package client contains the implementations required for plugins -// to act as a client. Developers typically query via the Runner interface, -// so they don't need to be aware of this layer. -// -// The client is the actual entity that satisfies the interface. -// It sends a request to the host via RPC, decodes the response, -// and provides APIs for plugins. -// -// Complex structures such as hcl.Expression and hcl.Body are sent/received -// as byte slices and range. Plugins and host parse the byte slice to get the -// original object. -package client diff --git a/tflint/client/encode.go b/tflint/client/encode.go deleted file mode 100644 index 9d7a87f..0000000 --- a/tflint/client/encode.go +++ /dev/null @@ -1,46 +0,0 @@ -package client - -import ( - "github.com/hashicorp/hcl/v2" - "github.com/terraform-linters/tflint-plugin-sdk/tflint" -) - -func encodeExpr(src []byte, expr hcl.Expression) ([]byte, hcl.Range) { - return expr.Range().SliceBytes(src), expr.Range() -} - -// Rule is an intermediate representation of tflint.Rule. -type Rule struct { - Data *RuleObject -} - -// RuleObject holds the data that Rule needs to satisfy the Rule interface. -type RuleObject struct { - Name string - Enabled bool - Severity string - Link string -} - -// Name is a reference method to internal data. -func (r *Rule) Name() string { return r.Data.Name } - -// Enabled is a reference method to internal data. -func (r *Rule) Enabled() bool { return r.Data.Enabled } - -// Severity is a reference method to internal data. -func (r *Rule) Severity() string { return r.Data.Severity } - -// Link is a reference method to internal data. -func (r *Rule) Link() string { return r.Data.Link } - -func encodeRule(rule tflint.RPCRule) *Rule { - return &Rule{ - Data: &RuleObject{ - Name: rule.Name(), - Enabled: rule.Enabled(), - Severity: rule.Severity(), - Link: rule.Link(), - }, - } -} diff --git a/tflint/client/rpc.go b/tflint/client/rpc.go deleted file mode 100644 index 1264f98..0000000 --- a/tflint/client/rpc.go +++ /dev/null @@ -1,147 +0,0 @@ -package client - -import ( - hcl "github.com/hashicorp/hcl/v2" - "github.com/zclconf/go-cty/cty" -) - -// AttributesRequest is a request to the server-side Attributes method. -type AttributesRequest struct { - Resource string - AttributeName string -} - -// AttributesResponse is a response to the server-side Attributes method. -type AttributesResponse struct { - Attributes []*Attribute - Err error -} - -// BackendRequest is a request to the server-side Backend method. -type BackendRequest struct{} - -// BackendResponse is a response to the server-side Backend method. -type BackendResponse struct { - Backend *Backend - Err error -} - -// BlocksRequest is a request to the server-side Blocks method. -type BlocksRequest struct { - Resource string - BlockType string -} - -// BlocksResponse is a response to the server-side Blocks method. -type BlocksResponse struct { - Blocks []*Block - Err error -} - -// ModuleCallsRequest is a request to the server-side ModuleCalls method. -type ModuleCallsRequest struct{} - -// ModuleCallsResponse is a response to the server-side ModuleCalls method. -type ModuleCallsResponse struct { - ModuleCalls []*ModuleCall - Err error -} - -// ResourcesRequest is a request to the server-side Resources method. -type ResourcesRequest struct { - Name string -} - -// ResourcesResponse is a response to the server-side Resources method. -type ResourcesResponse struct { - Resources []*Resource - Err error -} - -// ConfigRequest is a request to the server-side Config method. -type ConfigRequest struct{} - -// ConfigResponse is a response to the server-side Config method. -type ConfigResponse struct { - Config *Config - Err error -} - -// FileRequest is a request to the server-side File method. -type FileRequest struct { - Filename string -} - -// FileResponse is a response to the server-side File method. -type FileResponse struct { - Bytes []byte - Range hcl.Range -} - -// FilesRequest is a request to the server-side Files method. -type FilesRequest struct{} - -// FilesResponse is a response to the server-side Files method. -type FilesResponse struct { - Files map[string][]byte - Err error -} - -// RootProviderRequest is a request to the server-side RootProvider method. -type RootProviderRequest struct { - Name string -} - -// RootProviderResponse is a response to the server-side RootProvider method. -type RootProviderResponse struct { - Provider *Provider - Err error -} - -// RuleConfigRequest is a request to the server-side RuleConfig method. -type RuleConfigRequest struct { - Name string -} - -// RuleConfigResponse is a response to the server-side RuleConfig method. -type RuleConfigResponse struct { - Exists bool - Config []byte - Range hcl.Range - Err error -} - -// EvalExprRequest is a request to the server-side EvalExpr method. -type EvalExprRequest struct { - Expr []byte - ExprRange hcl.Range - Type cty.Type - Ret interface{} -} - -// EvalExprResponse is a response to the server-side EvalExpr method. -type EvalExprResponse struct { - Val cty.Value - Err error -} - -// IsNullExprRequest is a request to the server-side IsNullExpr method. -type IsNullExprRequest struct { - Expr []byte - Range hcl.Range -} - -// IsNullExprResponse is a response to the server-side IsNullExpr method. -type IsNullExprResponse struct { - Ret bool - Err error -} - -// EmitIssueRequest is a request to the server-side EmitIssue method. -type EmitIssueRequest struct { - Rule *Rule - Message string - Location hcl.Range - Expr []byte - ExprRange hcl.Range -} diff --git a/tflint/config.go b/tflint/config.go index 1ecc370..b601182 100644 --- a/tflint/config.go +++ b/tflint/config.go @@ -1,16 +1,9 @@ package tflint -import ( - "github.com/hashicorp/hcl/v2" - "github.com/hashicorp/hcl/v2/hclsyntax" -) - // Config is a TFLint configuration applied to the plugin. -// The Body contains the contents declared in the "plugin" block. type Config struct { Rules map[string]*RuleConfig DisabledByDefault bool - Body hcl.Body } // RuleConfig is a TFLint's rule configuration. @@ -18,28 +11,3 @@ type RuleConfig struct { Name string Enabled bool } - -// MarshalledConfig is an intermediate representation of Config for communicating over RPC -type MarshalledConfig struct { - Rules map[string]*RuleConfig - DisabledByDefault bool - BodyBytes []byte - BodyRange hcl.Range -} - -// Unmarshal converts intermediate representations into the Config object. -func (c *MarshalledConfig) Unmarshal() (*Config, error) { - // HACK: Always add a newline to avoid heredoc parse errors. - // @see https://github.com/hashicorp/hcl/issues/441 - src := []byte(string(c.BodyBytes) + "\n") - file, diags := hclsyntax.ParseConfig(src, c.BodyRange.Filename, c.BodyRange.Start) - if diags.HasErrors() { - return nil, diags - } - - return &Config{ - Rules: c.Rules, - DisabledByDefault: c.DisabledByDefault, - Body: file.Body, - }, nil -} diff --git a/tflint/doc.go b/tflint/doc.go index e91d359..215f3c4 100644 --- a/tflint/doc.go +++ b/tflint/doc.go @@ -1,7 +1,7 @@ // Package tflint contains implementations and interfaces for // plugin developers. // -// Each rule can use the RPC client that satisfies the Runner +// Each rule can use the gRPC client that satisfies the Runner // interface as an argument. Through this client, developers // can get attributes, blocks, and resources to be analyzed // and send issues to TFLint. diff --git a/tflint/errors.go b/tflint/errors.go index 21c70e2..911298f 100644 --- a/tflint/errors.go +++ b/tflint/errors.go @@ -2,40 +2,11 @@ package tflint import ( "errors" - "fmt" -) - -// List of error types and levels in an application error. -// It's possible to get this error from a plugin, but the basic error handling -// is hidden inside the plugin system, so you usually don't have to worry about it. -const ( - // EvaluationError is an error when interpolation failed (unexpected) - EvaluationError string = "E:Evaluation" - // UnknownValueError is an error when an unknown value is referenced - UnknownValueError string = "W:UnknownValue" - // NullValueError is an error when null value is referenced - NullValueError string = "W:NullValue" - // TypeConversionError is an error when type conversion of cty.Value failed - TypeConversionError string = "E:TypeConversion" - // TypeMismatchError is an error when a type of cty.Value is not as expected - TypeMismatchError string = "E:TypeMismatch" - // UnevaluableError is an error when a received expression has unevaluable references. - UnevaluableError string = "W:Unevaluable" - // UnexpectedAttributeError is an error when handle unexpected attributes (e.g. block) - UnexpectedAttributeError string = "E:UnexpectedAttribute" - // ExternalAPIError is an error when calling the external API (e.g. AWS SDK) - ExternalAPIError string = "E:ExternalAPI" - // ContextError is pseudo error code for propagating runtime context. - ContextError string = "I:Context" - - // FatalLevel is a recorverable error, it cause panic - FatalLevel string = "Fatal" - // ErrorLevel is a user-level error, it display and feedback error information - ErrorLevel string = "Error" - // WarningLevel is a user-level warning. Although it is an error, it has no effect on execution. - WarningLevel string = "Warning" ) +// List of errors returned by TFLint. +// It's possible to get this error from a plugin, but the error handling is hidden +// inside the plugin system, so you usually don't have to worry about it. var ( // ErrUnknownValue is an error when an unknown value is referenced ErrUnknownValue = errors.New("") @@ -44,25 +15,3 @@ var ( // ErrUnevaluable is an error when a received expression has unevaluable references. ErrUnevaluable = errors.New("") ) - -// Error is an application error object. It has own error code -// for processing according to the type of error. -type Error struct { - Code string - Level string - Message string - Cause error -} - -// Error shows error message. This must be implemented for error interface. -func (e Error) Error() string { - if e.Message != "" && e.Cause != nil { - return fmt.Sprintf("%s; %s", e.Message, e.Cause) - } - - if e.Message == "" && e.Cause != nil { - return e.Cause.Error() - } - - return e.Message -} diff --git a/tflint/interface.go b/tflint/interface.go index a943839..e66852b 100644 --- a/tflint/interface.go +++ b/tflint/interface.go @@ -3,13 +3,12 @@ package tflint import ( "github.com/hashicorp/hcl/v2" "github.com/terraform-linters/tflint-plugin-sdk/hclext" - "github.com/terraform-linters/tflint-plugin-sdk/terraform/configs" - "github.com/zclconf/go-cty/cty" ) // RuleSet is a list of rules that a plugin should provide. // Normally, plugins can use BuiltinRuleSet directly, // but you can also use custom rulesets that satisfy this interface. +// The actual implementation can be found in plugin/host2plugin.GRPCServer. type RuleSet interface { // RuleSetName is the name of the ruleset. This method is not expected to be overridden. RuleSetName() string @@ -21,16 +20,44 @@ type RuleSet interface { RuleNames() []string // ConfigSchema returns the ruleset plugin config schema. - // This schema should be a schema inside of "plugin" block. + // If you return a schema, TFLint will extract the config from .tflint.hcl based on that schema + // and pass it to ApplyConfig. This schema should be a schema inside of "plugin" block. + // If you don't need a config that controls the entire plugin, you don't need to override this method. + // + // It is recommended to use hclext.ImpliedBodySchema to generate the schema from the structure: + // + // ``` + // type myPluginConfig struct { + // Style string `hclext:"style"` + // Description string `hclext:"description,optional"` + // Detail Detail `hclext:"detail,block"` + // } + // + // config := &myPluginConfig{} + // hclext.ImpliedBodySchema(config) + // ``` ConfigSchema() *hclext.BodySchema // ApplyGlobalConfig applies the common config to the ruleset. // This is not supposed to be overridden from custom rulesets. - // Override the ApplyConfig if you want to apply the plugin's own configuration. + // Override the ApplyConfig if you want to apply the plugin's custom configuration. ApplyGlobalConfig(*Config) error // ApplyConfig applies the configuration to the ruleset. - // Custom rulesets can override this method to reflect the plugin's own configuration. + // Custom rulesets can override this method to reflect the plugin's custom configuration. + // + // You can reflect the body in the structure by using hclext.DecodeBody: + // + // ``` + // type myPluginConfig struct { + // Style string `hclext:"style"` + // Description string `hclext:"description,optional"` + // Detail Detail `hclext:"detail,block"` + // } + // + // config := &myPluginConfig{} + // hclext.DecodeBody(body, nil, config) + // ``` ApplyConfig(*hclext.BodyContent) error // Check runs inspection for each rule by applying Runner. @@ -42,14 +69,106 @@ type RuleSet interface { } // Runner acts as a client for each plugin to query the host process about the Terraform configurations. +// The actual implementation can be found in plugin/plugin2host.GRPCClient. type Runner interface { - GetResourceContent(string, *hclext.BodySchema, *GetModuleContentOption) (*hclext.BodyContent, error) - GetModuleContent(*hclext.BodySchema, *GetModuleContentOption) (*hclext.BodyContent, error) - GetFile(string) (*hcl.File, error) + // GetResourceContent retrieves the content of resources based on the passed schema. + // The schema allows you to specify attributes and blocks that describe the structure needed for the inspection: + // + // ``` + // runner.GetResourceContent("aws_instance", &hclext.BodySchema{ + // Attributes: []hclext.AttributeSchema{{Name: "instance_type"}}, + // Blocks: []hclext.BlockSchema{ + // { + // Type: "ebs_block_device", + // Body: &hclext.BodySchema{Attributes: []hclext.AttributeSchema{{Name: "volume_size"}}}, + // }, + // }, + // }, nil) + // ``` + GetResourceContent(resourceName string, schema *hclext.BodySchema, option *GetModuleContentOption) (*hclext.BodyContent, error) + + // GetModuleContent retrieves the content of the module based on the passed schema. + // GetResourceContent is syntactic sugar for GetModuleContent, which you can use to access structures other than resources. + GetModuleContent(schema *hclext.BodySchema, option *GetModuleContentOption) (*hclext.BodyContent, error) + + // GetFile returns the hcl.File object. + // This is low level API for accessing information such as comments and syntax. + // When accessing resources, expressions, etc, it is recommended to use high-level APIs. + GetFile(filename string) (*hcl.File, error) + + // GetFiles returns a map[string]hcl.File object, where the key is the file name. + // This is low level API for accessing information such as comments and syntax. GetFiles() (map[string]*hcl.File, error) - DecodeRuleConfig(name string, ret interface{}) error - EvaluateExpr(hcl.Expression, interface{}, *EvaluateExprOption) error - EmitIssue(Rule, string, hcl.Range) error + + // DecodeRuleConfig fetches the rule's configuration and reflects the result in the 2nd argument. + // The argument is expected to be a pointer to a structure tagged with hclext: + // + // ``` + // type myRuleConfig struct { + // Style string `hclext:"style"` + // Description string `hclext:"description,optional"` + // Detail Detail `hclext:"detail,block"` + // } + // + // config := &myRuleConfig{} + // runner.DecodeRuleConfig("my_rule", config) + // ``` + // + // See the hclext.DecodeBody documentation and examples for more details. + DecodeRuleConfig(ruleName string, ret interface{}) error + + // EvaluateExpr evaluates the passed expression and reflects the result in the 2nd argument. + // In addition to the obvious errors, this function returns an error if: + // - The expression contains unknown variables (e.g. variables without defaults) + // - The expression contains null variables + // - The expression contains unevaluable references (e.g. `aws_instance.arn`) + // + // To ignore these, use EnsureNoError for the returned error: + // + // ``` + // var val string + // err := runner.EvaluateExpr(expr, &val, nil) + // err = runner.EnsureNoError(err, func () error { + // // Only when no error occurs + // }) + // if err != nil { + // // Only for obvious errors, excluding the above errors + // } + // ``` + // + // The types that can be passed to the 2nd argument are assumed to be as follows: + // - string + // - int + // - []string + // - []int + // - map[string]string + // - map[string]int + // - cty.Value + // + // Besides this, you can pass a structure. In that case, you need to explicitly pass + // the type in the option of the 3rd argument: + // + // ``` + // type complexVal struct { + // Key string `cty:"key"` + // Enabled bool `cty:"enabled"` + // } + // + // wantType := cty.List(cty.Object(map[string]cty.Type{ + // "key": cty.String, + // "enabled": cty.Bool, + // })) + // + // var complexVals []complexVal + // runner.EvaluateExpr(expr, &compleVals, &tflint.EvaluateExprOption{WantType: &wantType}) + // ``` + EvaluateExpr(expr hcl.Expression, ret interface{}, option *EvaluateExprOption) error + + // EmitIssue sends an issue to TFLint. You need to pass the message of the issue and the range. + EmitIssue(rule Rule, message string, issueRange hcl.Range) error + + // EnsureNoError is a helper for error handling. Depending on the type of error generated by EvaluateExpr, + // determine whether to exit, skip, or continue. If it is continued, the passed function will be executed. EnsureNoError(error, func() error) error } @@ -79,115 +198,3 @@ type Rule interface { // All rules must embed the default rule. mustEmbedDefaultRule() } - -// RPCRuleSet is a list of rules that a plugin should provide. -// Normally, plugins can use BuiltinRuleSet directly, -// but you can also use custom rulesets that satisfy this interface. -type RPCRuleSet interface { - // RuleSetName is the name of the ruleset. This method is not expected to be overridden. - RuleSetName() string - - // RuleSetVersion is the version of the plugin. This method is not expected to be overridden. - RuleSetVersion() string - - // RuleNames is a list of rule names provided by the plugin. This method is not expected to be overridden. - RuleNames() []string - - // ApplyConfig reflects the configuration to the ruleset. - // Custom rulesets can override this method to reflect the plugin's own configuration. - // In that case, don't forget to call ApplyCommonConfig. - ApplyConfig(*Config) error - - // Check runs inspection for each rule by applying Runner. - // This is a entrypoint for all inspections and can be used as a hook to inject a custom runner. - Check(RPCRunner) error -} - -// RPCRunner acts as a client for each plugin to query the host process about the Terraform configurations. -type RPCRunner interface { - // WalkResourceAttributes visits attributes with the passed function. - // You must pass a resource type as the first argument and an attribute name as the second argument. - WalkResourceAttributes(string, string, func(*hcl.Attribute) error) error - - // WalkResourceBlocks visits blocks with the passed function. - // You must pass a resource type as the first argument and a block type as the second argument. - // This API currently does not support labeled blocks. - WalkResourceBlocks(string, string, func(*hcl.Block) error) error - - // WalkResources visits resources with the passed function. - // You must pass a resource type as the first argument. - WalkResources(string, func(*configs.Resource) error) error - - // WalkModuleCalls visits module calls with the passed function. - WalkModuleCalls(func(*configs.ModuleCall) error) error - - // Backend returns the backend configuration, if any. - Backend() (*configs.Backend, error) - - // Config returns the Terraform configuration. - // This object contains almost all accessible data structures from plugins. - Config() (*configs.Config, error) - - // File returns the hcl.File object. - // This is low level API for accessing information such as comments and syntax. - // When accessing resources, expressions, etc, it is recommended to use high-level APIs. - File(string) (*hcl.File, error) - - // Files returns a map[string]hcl.File object, where the key is the file name. - // This is low level API for accessing information such as comments and syntax. - Files() (map[string]*hcl.File, error) - - // RootProvider returns the provider configuration in the root module. - // It can be used by child modules to access the credentials defined in the root module. - RootProvider(name string) (*configs.Provider, error) - - // DecodeRuleConfig fetches the rule's configuration and reflects the result in ret. - DecodeRuleConfig(name string, ret interface{}) error - - // EvaluateExpr evaluates the passed expression and reflects the result in ret. - // If you want to ensure the type of ret, you can pass the type as the 3rd argument. - // If you pass nil as the type, it will be inferred from the type of ret. - // Since this function returns an application error, it is expected to use the EnsureNoError - // to determine whether to continue processing. - EvaluateExpr(expr hcl.Expression, ret interface{}, wantType *cty.Type) error - - // EvaluateExprOnRootCtx is the equivalent of EvaluateExpr method in the context of the root module. - // Its main use is to evaluate the provider block obtained by the RootProvider method. - EvaluateExprOnRootCtx(expr hcl.Expression, ret interface{}, wantType *cty.Type) error - - // IsNullExpr checks whether the passed expression is null or not. - // This returns an error when the passed expression is invalid, occurs evaluation errors, etc. - IsNullExpr(expr hcl.Expression) (bool, error) - - // EmitIssue sends an issue with an expression to TFLint. You need to pass the message of the issue and the expression. - EmitIssueOnExpr(rule RPCRule, message string, expr hcl.Expression) error - - // EmitIssue sends an issue to TFLint. You need to pass the message of the issue and the range. - // You should use EmitIssueOnExpr if you want to emit an issue for an expression. - // This API provides a lower level interface. - EmitIssue(rule RPCRule, message string, location hcl.Range) error - - // EnsureNoError is a helper for error handling. Depending on the type of error generated by EvaluateExpr, - // determine whether to exit, skip, or continue. If it is continued, the passed function will be executed. - EnsureNoError(error, func() error) error -} - -// RPCRule is the interface that the plugin's rules should satisfy. -type RPCRule interface { - // Name will be displayed with a message of an issue and will be the identifier used to control - // the behavior of this rule in the configuration file etc. - // Therefore, it is expected that this will not duplicate the rule names provided by other plugins. - Name() string - - // Enabled indicates whether the rule is enabled by default. - Enabled() bool - - // Severity indicates the severity of the rule. - Severity() string - - // Link allows you to add a reference link to the rule. - Link() string - - // Check is the entrypoint of the rule. You can fetch Terraform configurations and send issues via Runner. - Check(RPCRunner) error -} diff --git a/tflint/server/doc.go b/tflint/server/doc.go deleted file mode 100644 index 351902b..0000000 --- a/tflint/server/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package server contains the interfaces that the host process should satisfy. -package server diff --git a/tflint/server/server.go b/tflint/server/server.go deleted file mode 100644 index 67c7e85..0000000 --- a/tflint/server/server.go +++ /dev/null @@ -1,15 +0,0 @@ -package server - -import "github.com/terraform-linters/tflint-plugin-sdk/tflint/client" - -// Server is the interface that hosts that provide the plugin mechanism must meet in order to respond to queries from the plugin. -type Server interface { - Attributes(*client.AttributesRequest, *client.AttributesResponse) error - Blocks(*client.BlocksRequest, *client.BlocksResponse) error - Resources(*client.ResourcesRequest, *client.ResourcesResponse) error - ModuleCalls(*client.ModuleCallsRequest, *client.ModuleCallsResponse) error - Backend(*client.BackendRequest, *client.BackendResponse) error - EvalExpr(*client.EvalExprRequest, *client.EvalExprResponse) error - EmitIssue(*client.EmitIssueRequest, *interface{}) error - Files(*client.FilesRequest, *client.FilesResponse) error -}