Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hclext.BoundExpr #205

Merged
merged 1 commit into from
Oct 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions hclext/expression.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package hclext

import (
"github.com/hashicorp/hcl/v2"
"github.com/zclconf/go-cty/cty"
)

// BoundExpr represents an expression whose a value is bound.
// This is a wrapper for any expression, typically satisfying
// an interface to behave like the wrapped expression.
//
// The difference is that when resolving a value with `Value()`,
// instead of resolving the variables with EvalContext,
// the bound value is returned directly.
type BoundExpr struct {
Val cty.Value

original hcl.Expression
}

var _ hcl.Expression = (*BoundExpr)(nil)

// BindValue binds the passed value to an expression.
// This returns the bound expression.
func BindValue(val cty.Value, expr hcl.Expression) hcl.Expression {
return &BoundExpr{original: expr, Val: val}
}

// Value returns the bound value.
func (e BoundExpr) Value(*hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
return e.Val, nil
}

// Variables delegates to the wrapped expression.
func (e BoundExpr) Variables() []hcl.Traversal {
return e.original.Variables()
}

// Range delegates to the wrapped expression.
func (e BoundExpr) Range() hcl.Range {
return e.original.Range()
}

// StartRange delegates to the wrapped expression.
func (e BoundExpr) StartRange() hcl.Range {
return e.original.StartRange()
}
26 changes: 25 additions & 1 deletion plugin/fromproto/fromproto.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/terraform-linters/tflint-plugin-sdk/hclext"
"github.com/terraform-linters/tflint-plugin-sdk/plugin/proto"
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/msgpack"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
Expand Down Expand Up @@ -62,7 +64,13 @@ func BodyContent(body *proto.BodyContent) (*hclext.BodyContent, hcl.Diagnostics)

attributes := hclext.Attributes{}
for key, attr := range body.Attributes {
expr, exprDiags := hclext.ParseExpression(attr.Expr, attr.ExprRange.Filename, Pos(attr.ExprRange.Start))
var expr hcl.Expression
var exprDiags hcl.Diagnostics
if attr.Expression != nil {
expr, exprDiags = Expression(attr.Expression)
} else {
expr, exprDiags = hclext.ParseExpression(attr.Expr, attr.ExprRange.Filename, Pos(attr.ExprRange.Start))
}
diags = diags.Extend(exprDiags)

attributes[key] = &hclext.Attribute{
Expand Down Expand Up @@ -146,6 +154,22 @@ func Rule(rule *proto.EmitIssue_Rule) *RuleObject {
}
}

// Expression converts proto.Expression to hcl.Expression
func Expression(expr *proto.Expression) (hcl.Expression, hcl.Diagnostics) {
parsed, diags := hclext.ParseExpression(expr.Bytes, expr.Range.Filename, Pos(expr.Range.Start))
if diags.HasErrors() {
return nil, diags
}
if expr.Value != nil {
val, err := msgpack.Unmarshal(expr.Value, cty.DynamicPseudoType)
if err != nil {
panic(fmt.Errorf("cannot unmarshal the bound expr: %w", err))
}
parsed = hclext.BindValue(val, parsed)
}
return parsed, diags
}

// Severity converts proto.EmitIssue_Severity to severity
func Severity(severity proto.EmitIssue_Severity) tflint.Severity {
switch severity {
Expand Down
7 changes: 4 additions & 3 deletions plugin/plugin2host/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,10 @@ func (c *GRPCClient) EvaluateExpr(expr hcl.Expression, ret interface{}, opts *tf
resp, err := c.Client.EvaluateExpr(
context.Background(),
&proto.EvaluateExpr_Request{
Expr: expr.Range().SliceBytes(file.Bytes),
ExprRange: toproto.Range(expr.Range()),
Option: &proto.EvaluateExpr_Option{Type: tyby, ModuleCtx: toproto.ModuleCtxType(opts.ModuleCtx)},
Expr: expr.Range().SliceBytes(file.Bytes),
ExprRange: toproto.Range(expr.Range()),
Expression: toproto.Expression(expr, file.Bytes),
Option: &proto.EvaluateExpr_Option{Type: tyby, ModuleCtx: toproto.ModuleCtxType(opts.ModuleCtx)},
},
)
if err != nil {
Expand Down
60 changes: 60 additions & 0 deletions plugin/plugin2host/plugin2host_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,55 @@ volume_size = 10`)
},
ErrCheck: neverHappend,
},
{
Name: "get content with bound expr",
Args: func() (*hclext.BodySchema, *tflint.GetModuleContentOption) {
return &hclext.BodySchema{
Attributes: []hclext.AttributeSchema{{Name: "value"}},
}, nil
},
ServerImpl: func(schema *hclext.BodySchema, opts tflint.GetModuleContentOption) (*hclext.BodyContent, hcl.Diagnostics) {
file := hclFile("test.tf", "value = each.key")
attrs, diags := file.Body.JustAttributes()
if diags.HasErrors() {
return nil, diags
}
attr := attrs["value"]

return &hclext.BodyContent{
Attributes: hclext.Attributes{
"value": {
Name: attr.Name,
Expr: hclext.BindValue(cty.StringVal("bound value"), attr.Expr),
Range: attr.Range,
NameRange: attr.NameRange,
},
},
Blocks: hclext.Blocks{},
}, nil
},
Want: func(schema *hclext.BodySchema, opts *tflint.GetModuleContentOption) (*hclext.BodyContent, hcl.Diagnostics) {
file := hclFile("test.tf", "value = each.key")
attrs, diags := file.Body.JustAttributes()
if diags.HasErrors() {
return nil, diags
}
attr := attrs["value"]

return &hclext.BodyContent{
Attributes: hclext.Attributes{
"value": {
Name: attr.Name,
Expr: hclext.BindValue(cty.StringVal("bound value"), attr.Expr),
Range: attr.Range,
NameRange: attr.NameRange,
},
},
Blocks: hclext.Blocks{},
}, nil
},
ErrCheck: neverHappend,
},
{
Name: "get content with options",
Args: func() (*hclext.BodySchema, *tflint.GetModuleContentOption) {
Expand Down Expand Up @@ -1537,6 +1586,17 @@ func TestEvaluateExpr(t *testing.T) {
GetFileImpl: fileExists,
ErrCheck: neverHappend,
},
{
Name: "bound expr",
Expr: hclext.BindValue(cty.StringVal("bound value"), hclExpr(`var.foo`)),
TargetType: reflect.TypeOf(""),
ServerImpl: func(expr hcl.Expression, opts tflint.EvaluateExprOption) (cty.Value, error) {
return evalExpr(expr, &hcl.EvalContext{})
},
Want: "bound value",
GetFileImpl: fileExists,
ErrCheck: neverHappend,
},
{
Name: "eval with moduleCtx option",
Expr: hclExpr(`1`),
Expand Down
27 changes: 21 additions & 6 deletions plugin/plugin2host/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,17 +112,32 @@ func (s *GRPCServer) GetRuleConfigContent(ctx context.Context, req *proto.GetRul

// EvaluateExpr evals the passed expression based on the type.
func (s *GRPCServer) EvaluateExpr(ctx context.Context, req *proto.EvaluateExpr_Request) (*proto.EvaluateExpr_Response, error) {
if req.Expr == nil {
return nil, status.Error(codes.InvalidArgument, "expr should not be null")
}
if req.ExprRange == nil {
return nil, status.Error(codes.InvalidArgument, "expr_range should not be null")
if req.Expression == nil {
if req.Expr == nil {
return nil, status.Error(codes.InvalidArgument, "expr should not be null")
}
if req.ExprRange == nil {
return nil, status.Error(codes.InvalidArgument, "expr_range should not be null")
}
} else {
if req.Expression.Bytes == nil {
return nil, status.Error(codes.InvalidArgument, "expression.bytes should not be null")
}
if req.Expression.Range == nil {
return nil, status.Error(codes.InvalidArgument, "expression.range should not be null")
}
}
if req.Option == nil {
return nil, status.Error(codes.InvalidArgument, "option should not be null")
}

expr, diags := hclext.ParseExpression(req.Expr, req.ExprRange.Filename, fromproto.Pos(req.ExprRange.Start))
var expr hcl.Expression
var diags hcl.Diagnostics
if req.Expression != nil {
expr, diags = fromproto.Expression(req.Expression)
} else {
expr, diags = hclext.ParseExpression(req.Expr, req.ExprRange.Filename, fromproto.Pos(req.ExprRange.Start))
}
if diags.HasErrors() {
return nil, toproto.Error(codes.InvalidArgument, diags)
}
Expand Down
Loading