Skip to content

Commit

Permalink
Add hclext.BoundExpr
Browse files Browse the repository at this point in the history
  • Loading branch information
wata727 committed Oct 15, 2022
1 parent cb843d9 commit 1371d67
Show file tree
Hide file tree
Showing 8 changed files with 789 additions and 498 deletions.
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

0 comments on commit 1371d67

Please sign in to comment.