-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
plugin: gRPC-based new plugin system
- Loading branch information
Showing
26 changed files
with
3,531 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
proto: | ||
cd plugin/proto; \ | ||
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative tflint.proto |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package hclext | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/hashicorp/hcl/v2" | ||
"github.com/hashicorp/hcl/v2/hclsyntax" | ||
"github.com/hashicorp/hcl/v2/json" | ||
) | ||
|
||
// ParseExpression is a wrapper that calls ParseExpression of hclsyntax and json based on the file extension. | ||
// This function specializes in parsing intermediate expressions in the file, | ||
// so it takes into account the hack on trailing newlines in heredoc. | ||
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)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package fromproto | ||
|
||
import ( | ||
"github.com/hashicorp/hcl/v2" | ||
"github.com/terraform-linters/tflint-plugin-sdk/hclext" | ||
"github.com/terraform-linters/tflint-plugin-sdk/plugin/proto" | ||
"github.com/terraform-linters/tflint-plugin-sdk/schema" | ||
) | ||
|
||
// BodySchema converts proto.BodySchema to schema.BodySchema | ||
func BodySchema(body *proto.BodySchema) *schema.BodySchema { | ||
if body == nil { | ||
return nil | ||
} | ||
|
||
attributes := make([]schema.AttributeSchema, len(body.Attributes)) | ||
for idx, attr := range body.Attributes { | ||
attributes[idx] = schema.AttributeSchema{Name: attr.Name} | ||
} | ||
|
||
blocks := make([]schema.BlockSchema, len(body.Blocks)) | ||
for idx, block := range body.Blocks { | ||
blocks[idx] = schema.BlockSchema{ | ||
Type: block.Type, | ||
LabelNames: block.LabelNames, | ||
Body: BodySchema(block.Body), | ||
} | ||
} | ||
|
||
return &schema.BodySchema{ | ||
Attributes: attributes, | ||
Blocks: blocks, | ||
} | ||
} | ||
|
||
// BodyContent converts proto.BodyContent to schema.BodyContent | ||
func BodyContent(body *proto.BodyContent) (*schema.BodyContent, hcl.Diagnostics) { | ||
if body == nil { | ||
return nil, nil | ||
} | ||
diags := hcl.Diagnostics{} | ||
|
||
attributes := schema.Attributes{} | ||
for key, attr := range body.Attributes { | ||
expr, exprDiags := hclext.ParseExpression(attr.Expr, attr.ExprRange.Filename, Pos(attr.ExprRange.Start)) | ||
diags = diags.Extend(exprDiags) | ||
|
||
attributes[key] = &schema.Attribute{ | ||
Name: attr.Name, | ||
Expr: expr, | ||
Range: Range(attr.Range), | ||
NameRange: Range(attr.NameRange), | ||
} | ||
} | ||
|
||
blocks := make(schema.Blocks, len(body.Blocks)) | ||
for idx, block := range body.Blocks { | ||
blockBody, contentDiags := BodyContent(block.Body) | ||
diags = diags.Extend(contentDiags) | ||
|
||
labelRanges := make([]hcl.Range, len(block.LabelRanges)) | ||
for idx, labelRange := range block.LabelRanges { | ||
labelRanges[idx] = Range(labelRange) | ||
} | ||
|
||
blocks[idx] = &schema.Block{ | ||
Type: block.Type, | ||
Labels: block.Labels, | ||
Body: blockBody, | ||
DefRange: Range(block.DefRange), | ||
TypeRange: Range(block.TypeRange), | ||
LabelRanges: labelRanges, | ||
} | ||
} | ||
|
||
return &schema.BodyContent{ | ||
Attributes: attributes, | ||
Blocks: blocks, | ||
}, diags | ||
} | ||
|
||
// Range converts proto.Range to hcl.Range | ||
func Range(rng *proto.Range) hcl.Range { | ||
if rng == nil { | ||
return hcl.Range{} | ||
} | ||
|
||
return hcl.Range{ | ||
Filename: rng.Filename, | ||
Start: Pos(rng.Start), | ||
End: Pos(rng.End), | ||
} | ||
} | ||
|
||
// Pos converts proto.Range_Pos to hcl.Pos | ||
func Pos(pos *proto.Range_Pos) hcl.Pos { | ||
if pos == nil { | ||
return hcl.Pos{} | ||
} | ||
|
||
return hcl.Pos{ | ||
Line: int(pos.Line), | ||
Column: int(pos.Column), | ||
Byte: int(pos.Byte), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package host2plugin | ||
|
||
import ( | ||
"context" | ||
"os" | ||
"os/exec" | ||
|
||
"github.com/hashicorp/go-hclog" | ||
"github.com/hashicorp/go-plugin" | ||
"github.com/terraform-linters/tflint-plugin-sdk/plugin/plugin2host" | ||
"github.com/terraform-linters/tflint-plugin-sdk/plugin/proto" | ||
"google.golang.org/grpc" | ||
) | ||
|
||
// GRPCClient is a host-side implementation. Host can send requests through the client to plugin's gRPC server. | ||
type GRPCClient struct { | ||
broker *plugin.GRPCBroker | ||
client proto.RuleSetClient | ||
} | ||
|
||
// 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, | ||
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, | ||
// TODO: Should use structured logging? | ||
// https://github.com/hashicorp/terraform-plugin-sdk/blob/v2.8.0/plugin/serve.go#L64-L75 | ||
Logger: hclog.New(&hclog.LoggerOptions{ | ||
Name: "plugin", | ||
Output: os.Stderr, | ||
Level: hclog.LevelFromString(os.Getenv("TFLINT_LOG")), | ||
}), | ||
}) | ||
} | ||
|
||
// TODO: Improve error handling | ||
|
||
// RuleSetName returns the name of the plugin. | ||
func (c *GRPCClient) RuleSetName() (string, error) { | ||
resp, err := c.client.RuleSetName(context.Background(), &proto.RuleSetName_Request{}) | ||
if err != nil { | ||
return "", err | ||
} | ||
return resp.Name, nil | ||
} | ||
|
||
// RuleSetVersion returns the version of the plugin. | ||
func (c *GRPCClient) RuleSetVersion() (string, error) { | ||
resp, err := c.client.RuleSetVersion(context.Background(), &proto.RuleSetVersion_Request{}) | ||
if err != nil { | ||
return "", err | ||
} | ||
return resp.Version, nil | ||
} | ||
|
||
// RuleNames returns the list of rule names provided by the plugin. | ||
func (c *GRPCClient) RuleNames() ([]string, error) { | ||
resp, err := c.client.RuleNames(context.Background(), &proto.RuleNames_Request{}) | ||
if err != nil { | ||
return []string{}, err | ||
} | ||
return resp.Names, nil | ||
} | ||
|
||
// Check calls its own plugin implementation with an gRPC client that can send | ||
// requests to the host process. | ||
func (c *GRPCClient) Check(runner plugin2host.Server) error { | ||
brokerID := c.broker.NextId() | ||
go c.broker.AcceptAndServe(brokerID, func(opts []grpc.ServerOption) *grpc.Server { | ||
server := grpc.NewServer(opts...) | ||
proto.RegisterRunnerServer(server, &plugin2host.GRPCServer{Impl: runner}) | ||
return server | ||
}) | ||
|
||
_, err := c.client.Check(context.Background(), &proto.Check_Request{Runner: brokerID}) | ||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package host2plugin | ||
|
||
import ( | ||
"context" | ||
|
||
plugin "github.com/hashicorp/go-plugin" | ||
"github.com/terraform-linters/tflint-plugin-sdk/plugin/proto" | ||
"github.com/terraform-linters/tflint-plugin-sdk/tflint" | ||
"google.golang.org/grpc" | ||
) | ||
|
||
// handShakeConfig is used for UX. ProcotolVersion will be updated by incompatible changes. | ||
var handshakeConfig = plugin.HandshakeConfig{ | ||
ProtocolVersion: 10, | ||
MagicCookieKey: "TFLINT_RULESET_PLUGIN", | ||
MagicCookieValue: "5adSn1bX8nrDfgBqiAqqEkC6OE1h3iD8SqbMc5UUONx8x3xCF0KlPDsBRNDjoYDP", | ||
} | ||
|
||
// RuleSetPlugin is a wrapper to satisfy the interface of go-plugin. | ||
type RuleSetPlugin struct { | ||
plugin.NetRPCUnsupportedPlugin | ||
|
||
impl tflint.RuleSet | ||
} | ||
|
||
var _ plugin.GRPCPlugin = &RuleSetPlugin{} | ||
|
||
// GRPCServer returns an gRPC server acting as a plugin. | ||
func (p *RuleSetPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { | ||
proto.RegisterRuleSetServer(s, &GRPCServer{ | ||
impl: p.impl, | ||
broker: broker, | ||
}) | ||
return nil | ||
} | ||
|
||
// GRPCClient returns an RPC client for the host. | ||
func (*RuleSetPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { | ||
return &GRPCClient{ | ||
client: proto.NewRuleSetClient(c), | ||
broker: broker, | ||
}, nil | ||
} |
Oops, something went wrong.