-
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
4,359 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,243 @@ | ||
package fromproto | ||
|
||
import ( | ||
"errors" | ||
|
||
"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" | ||
"github.com/terraform-linters/tflint-plugin-sdk/tflint" | ||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/status" | ||
) | ||
|
||
// 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 | ||
} | ||
|
||
// RuleObject is an intermediate representation that satisfies the Rule interface. | ||
type RuleObject struct { | ||
Data struct { | ||
Name string | ||
Enabled bool | ||
Severity string | ||
Link string | ||
} | ||
} | ||
|
||
// Name returns the rule name | ||
func (r *RuleObject) Name() string { return r.Data.Name } | ||
|
||
// Enabled returns whether the rule is enabled | ||
func (r *RuleObject) Enabled() bool { return r.Data.Enabled } | ||
|
||
// Severity returns the severify of the rule | ||
func (r *RuleObject) Severity() string { return r.Data.Severity } | ||
|
||
// Link returns the link of the rule documentation if exists | ||
func (r *RuleObject) Link() string { return r.Data.Link } | ||
|
||
// Check does nothing. This is just a method to satisfy the interface | ||
func (r *RuleObject) Check(tflint.Runner) error { return nil } | ||
|
||
// Rule converts proto.EmitIssue_Rule to RuleObject | ||
func Rule(rule *proto.EmitIssue_Rule) *RuleObject { | ||
if rule == nil { | ||
return nil | ||
} | ||
|
||
return &RuleObject{ | ||
Data: struct { | ||
Name string | ||
Enabled bool | ||
Severity string | ||
Link string | ||
}{ | ||
Name: rule.Name, | ||
Enabled: rule.Enabled, | ||
Severity: Severity(rule.Severity), | ||
Link: rule.Link, | ||
}, | ||
} | ||
} | ||
|
||
// Severity converts proto.EmitIssue_Severity to severity | ||
func Severity(severity proto.EmitIssue_Severity) string { | ||
switch severity { | ||
case proto.EmitIssue_SEVERITY_ERROR: | ||
return tflint.ERROR | ||
case proto.EmitIssue_SEVERITY_WARNING: | ||
return tflint.WARNING | ||
case proto.EmitIssue_SEVERITY_NOTICE: | ||
return tflint.NOTICE | ||
} | ||
|
||
return tflint.ERROR | ||
} | ||
|
||
// 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), | ||
} | ||
} | ||
|
||
// Error converts gRPC error status to tflint.Error | ||
func Error(err error) error { | ||
st, ok := status.FromError(err) | ||
if !ok { | ||
return err | ||
} | ||
|
||
// If the error status has no details, retrieve an error from the gRPC error status. | ||
// Remove the status code because some statuses are expected and should not be shown to users. | ||
if len(st.Details()) == 0 { | ||
switch st.Code() { | ||
case codes.InvalidArgument: | ||
fallthrough | ||
case codes.Aborted: | ||
return errors.New(st.Message()) | ||
default: | ||
return err | ||
} | ||
} | ||
|
||
// It is not supposed to have multiple details. The detail have an error code and are converted to tflint.Error | ||
switch t := st.Details()[0].(type) { | ||
case *proto.ErrorDetail: | ||
switch t.Code { | ||
case proto.ErrorCode_FAILED_TO_EVAL: | ||
return &tflint.Error{ | ||
Code: tflint.EvaluationError, | ||
Level: tflint.ErrorLevel, | ||
Message: st.Message(), | ||
} | ||
case proto.ErrorCode_UNKNOWN_VALUE: | ||
return &tflint.Error{ | ||
Code: tflint.UnknownValueError, | ||
Level: tflint.WarningLevel, | ||
Message: st.Message(), | ||
} | ||
case proto.ErrorCode_NULL_VALUE: | ||
return &tflint.Error{ | ||
Code: tflint.NullValueError, | ||
Level: tflint.WarningLevel, | ||
Message: st.Message(), | ||
} | ||
case proto.ErrorCode_TYPE_CONVERSION: | ||
return &tflint.Error{ | ||
Code: tflint.TypeConversionError, | ||
Level: tflint.ErrorLevel, | ||
Message: st.Message(), | ||
} | ||
case proto.ErrorCode_TYPE_MISMATCH: | ||
return &tflint.Error{ | ||
Code: tflint.TypeMismatchError, | ||
Level: tflint.ErrorLevel, | ||
Message: st.Message(), | ||
} | ||
case proto.ErrorCode_UNEVALUABLE: | ||
return &tflint.Error{ | ||
Code: tflint.UnevaluableError, | ||
Level: tflint.WarningLevel, | ||
Message: st.Message(), | ||
} | ||
case proto.ErrorCode_UNEXPECTED_ATTRIBUTE: | ||
return &tflint.Error{ | ||
Code: tflint.UnexpectedAttributeError, | ||
Level: tflint.ErrorLevel, | ||
Message: st.Message(), | ||
} | ||
} | ||
} | ||
|
||
return err | ||
} |
Oops, something went wrong.