Skip to content

Commit

Permalink
plugin: gRPC-based new plugin system
Browse files Browse the repository at this point in the history
  • Loading branch information
wata727 committed Dec 31, 2021
1 parent 11205ad commit ed02c1e
Show file tree
Hide file tree
Showing 32 changed files with 6,433 additions and 63 deletions.
3 changes: 3 additions & 0 deletions Makefile
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
13 changes: 7 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ require (
github.com/hashicorp/hcl/v2 v2.10.0
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734
github.com/zclconf/go-cty v1.9.0
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
google.golang.org/grpc v1.41.0
google.golang.org/protobuf v1.27.1
)

require (
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/golang/protobuf v1.3.4 // indirect
github.com/golang/protobuf v1.5.0 // indirect
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.10 // indirect
Expand All @@ -25,11 +28,9 @@ require (
github.com/oklog/run v1.0.0 // indirect
github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect
github.com/vmihailenco/tagparser v0.1.1 // indirect
golang.org/x/net v0.0.0-20200301022130-244492dfa37a // indirect
golang.org/x/sys v0.0.0-20191008105621-543471e840be // indirect
golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
golang.org/x/text v0.3.5 // indirect
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/appengine v1.6.5 // indirect
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect
google.golang.org/grpc v1.27.1 // indirect
)
74 changes: 66 additions & 8 deletions go.sum

Large diffs are not rendered by default.

164 changes: 164 additions & 0 deletions hclext/decode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package hclext

import (
"fmt"
"reflect"

"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
)

// DecodeBody is a derivative of gohcl.DecodeBody the receives hclext.BodyContent instead of hcl.Body.
// Since hcl.Body and interfaces are hard to send over a wire protocol, it is needed to support BodyContent.
// This method differs from gohcl.DecodeBody in several ways:
//
// - Does not support decoding to map.
// - Does not support decoding to hcl.Body, hcl.Expression, etc.
// - Does not support `body` and `remain` tag.
func DecodeBody(body *BodyContent, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics {
rv := reflect.ValueOf(val)
if rv.Kind() != reflect.Ptr {
panic(fmt.Sprintf("target value must be a pointer, not %s", rv.Type().String()))
}

return decodeBody(body, ctx, rv.Elem())
}

func decodeBody(body *BodyContent, ctx *hcl.EvalContext, val reflect.Value) hcl.Diagnostics {
et := val.Type()
switch et.Kind() {
case reflect.Struct:
return decodeBodyToStruct(body, ctx, val)
default:
panic(fmt.Sprintf("target value must be pointer to struct, not %s", et.String()))
}
}

func decodeBodyToStruct(body *BodyContent, ctx *hcl.EvalContext, val reflect.Value) hcl.Diagnostics {
var diags hcl.Diagnostics

tags := getFieldTags(val.Type())

for name, fieldIdx := range tags.Attributes {
attr, exists := body.Attributes[name]
if !exists {
continue
}
diags = diags.Extend(gohcl.DecodeExpression(attr.Expr, ctx, val.Field(fieldIdx).Addr().Interface()))
}

blocksByType := body.Blocks.ByType()

for typeName, fieldIdx := range tags.Blocks {
blocks := blocksByType[typeName]
field := val.Type().Field((fieldIdx))

ty := field.Type
isSlice := false
isPtr := false
if ty.Kind() == reflect.Slice {
isSlice = true
ty = ty.Elem()
}
if ty.Kind() == reflect.Ptr {
isPtr = true
ty = ty.Elem()
}

if len(blocks) > 1 && !isSlice {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Duplicate %s block", typeName),
Detail: fmt.Sprintf(
"Only one %s block is allowed. Another was defined at %s.",
typeName, blocks[0].DefRange.String(),
),
Subject: &blocks[1].DefRange,
})
continue
}

if len(blocks) == 0 {
if isSlice || isPtr {
if val.Field(fieldIdx).IsNil() {
val.Field(fieldIdx).Set(reflect.Zero(field.Type))
}
} else {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: fmt.Sprintf("Missing %s block", typeName),
Detail: fmt.Sprintf("A %s block is required.", typeName),
})
}
continue
}

switch {

case isSlice:
elemType := ty
if isPtr {
elemType = reflect.PtrTo(ty)
}
sli := val.Field(fieldIdx)
if sli.IsNil() {
sli = reflect.MakeSlice(reflect.SliceOf(elemType), len(blocks), len(blocks))
}

for i, block := range blocks {
if isPtr {
if i >= sli.Len() {
sli = reflect.Append(sli, reflect.New(ty))
}
v := sli.Index(i)
if v.IsNil() {
v = reflect.New(ty)
}
diags = append(diags, decodeBlockToValue(block, ctx, v.Elem())...)
sli.Index(i).Set(v)
} else {
if i >= sli.Len() {
sli = reflect.Append(sli, reflect.Indirect(reflect.New(ty)))
}
diags = append(diags, decodeBlockToValue(block, ctx, sli.Index(i))...)
}
}

if sli.Len() > len(blocks) {
sli.SetLen(len(blocks))
}

val.Field(fieldIdx).Set(sli)

default:
block := blocks[0]
if isPtr {
v := val.Field(fieldIdx)
if v.IsNil() {
v = reflect.New(ty)
}
diags = append(diags, decodeBlockToValue(block, ctx, v.Elem())...)
val.Field(fieldIdx).Set(v)
} else {
diags = append(diags, decodeBlockToValue(block, ctx, val.Field(fieldIdx))...)
}

}
}

return diags
}

func decodeBlockToValue(block *Block, ctx *hcl.EvalContext, v reflect.Value) hcl.Diagnostics {
diags := decodeBody(block.Body, ctx, v)

if len(block.Labels) > 0 {
blockTags := getFieldTags(v.Type())
for li, lv := range block.Labels {
lfieldIdx := blockTags.Labels[li].FieldIndex
v.Field(lfieldIdx).Set(reflect.ValueOf(lv))
}
}

return diags
}
34 changes: 34 additions & 0 deletions hclext/parse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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") || strings.HasSuffix(filename, ".hcl") {
// 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)
}

return nil, hcl.Diagnostics{
{
Severity: hcl.DiagError,
Summary: "Unexpected file extension",
Detail: fmt.Sprintf("The file name `%s` is a file with an unexpected extension. Valid extensions are `.tf` and `.tf.json`.", filename),
},
}
}
Loading

0 comments on commit ed02c1e

Please sign in to comment.