Skip to content

Commit dd804b3

Browse files
authored
plugin2host: Handle eval errors on the client side (#235)
* Handle eval errors on the client side * Bump SDK version
1 parent de8b014 commit dd804b3

File tree

5 files changed

+138
-5
lines changed

5 files changed

+138
-5
lines changed

plugin/fromproto/fromproto.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,9 +307,9 @@ func Error(err error) error {
307307
case *proto.ErrorDetail:
308308
switch t.Code {
309309
case proto.ErrorCode_ERROR_CODE_UNKNOWN_VALUE:
310-
return fmt.Errorf("%s%w", st.Message(), tflint.ErrUnknownValue)
310+
return tflint.ErrUnknownValue
311311
case proto.ErrorCode_ERROR_CODE_NULL_VALUE:
312-
return fmt.Errorf("%s%w", st.Message(), tflint.ErrNullValue)
312+
return tflint.ErrNullValue
313313
case proto.ErrorCode_ERROR_CODE_UNEVALUABLE:
314314
return fmt.Errorf("%s%w", st.Message(), tflint.ErrUnevaluable)
315315
case proto.ErrorCode_ERROR_CODE_SENSITIVE:

plugin/host2plugin/plugin.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
)
1111

1212
// SDKVersion is the SDK version.
13-
const SDKVersion = "0.15.0"
13+
const SDKVersion = "0.16.0"
1414

1515
// handShakeConfig is used for UX. ProcotolVersion will be updated by incompatible changes.
1616
var handshakeConfig = plugin.HandshakeConfig{

plugin/plugin2host/client.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/hashicorp/hcl/v2/hclsyntax"
1212
hcljson "github.com/hashicorp/hcl/v2/json"
1313
"github.com/terraform-linters/tflint-plugin-sdk/hclext"
14+
"github.com/terraform-linters/tflint-plugin-sdk/logger"
1415
"github.com/terraform-linters/tflint-plugin-sdk/plugin/fromproto"
1516
"github.com/terraform-linters/tflint-plugin-sdk/plugin/proto"
1617
"github.com/terraform-linters/tflint-plugin-sdk/plugin/toproto"
@@ -331,6 +332,27 @@ func (c *GRPCClient) EvaluateExpr(expr hcl.Expression, ret interface{}, opts *tf
331332
return err
332333
}
333334

335+
if ty == cty.DynamicPseudoType {
336+
return gocty.FromCtyValue(val, ret)
337+
}
338+
339+
// Returns an error if the value cannot be decoded to a Go value (e.g. unknown value, null).
340+
// This allows the caller to handle the value by the errors package.
341+
err = cty.Walk(val, func(path cty.Path, v cty.Value) (bool, error) {
342+
if !v.IsKnown() {
343+
logger.Debug(fmt.Sprintf("unknown value found in %s", expr.Range()))
344+
return false, tflint.ErrUnknownValue
345+
}
346+
if v.IsNull() {
347+
logger.Debug(fmt.Sprintf("null value found in %s", expr.Range()))
348+
return false, tflint.ErrNullValue
349+
}
350+
return true, nil
351+
})
352+
if err != nil {
353+
return err
354+
}
355+
334356
return gocty.FromCtyValue(val, ret)
335357
}
336358

plugin/plugin2host/plugin2host_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1745,6 +1745,116 @@ func TestEvaluateExpr(t *testing.T) {
17451745
return !errors.Is(err, tflint.ErrSensitive)
17461746
},
17471747
},
1748+
{
1749+
Name: "unknown value",
1750+
Expr: hclExpr(`var.foo`),
1751+
TargetType: reflect.TypeOf(""),
1752+
ServerImpl: func(expr hcl.Expression, opts tflint.EvaluateExprOption) (cty.Value, error) {
1753+
return evalExpr(expr, &hcl.EvalContext{
1754+
Variables: map[string]cty.Value{
1755+
"var": cty.MapVal(map[string]cty.Value{
1756+
"foo": cty.UnknownVal(cty.String),
1757+
}),
1758+
},
1759+
})
1760+
},
1761+
Want: "",
1762+
GetFileImpl: fileExists,
1763+
ErrCheck: func(err error) bool {
1764+
return !errors.Is(err, tflint.ErrUnknownValue)
1765+
},
1766+
},
1767+
{
1768+
Name: "unknown value as cty.Value",
1769+
Expr: hclExpr(`var.foo`),
1770+
TargetType: reflect.TypeOf(cty.Value{}),
1771+
ServerImpl: func(expr hcl.Expression, opts tflint.EvaluateExprOption) (cty.Value, error) {
1772+
return evalExpr(expr, &hcl.EvalContext{
1773+
Variables: map[string]cty.Value{
1774+
"var": cty.MapVal(map[string]cty.Value{
1775+
"foo": cty.UnknownVal(cty.String),
1776+
}),
1777+
},
1778+
})
1779+
},
1780+
Want: cty.UnknownVal(cty.String),
1781+
GetFileImpl: fileExists,
1782+
ErrCheck: neverHappend,
1783+
},
1784+
{
1785+
Name: "unknown value in object",
1786+
Expr: hclExpr(`{ value = var.foo }`),
1787+
TargetType: reflect.TypeOf(map[string]string{}),
1788+
ServerImpl: func(expr hcl.Expression, opts tflint.EvaluateExprOption) (cty.Value, error) {
1789+
return evalExpr(expr, &hcl.EvalContext{
1790+
Variables: map[string]cty.Value{
1791+
"var": cty.MapVal(map[string]cty.Value{
1792+
"foo": cty.UnknownVal(cty.String),
1793+
}),
1794+
},
1795+
})
1796+
},
1797+
Want: (map[string]string)(nil),
1798+
GetFileImpl: fileExists,
1799+
ErrCheck: func(err error) bool {
1800+
return !errors.Is(err, tflint.ErrUnknownValue)
1801+
},
1802+
},
1803+
{
1804+
Name: "null",
1805+
Expr: hclExpr(`var.foo`),
1806+
TargetType: reflect.TypeOf(""),
1807+
ServerImpl: func(expr hcl.Expression, opts tflint.EvaluateExprOption) (cty.Value, error) {
1808+
return evalExpr(expr, &hcl.EvalContext{
1809+
Variables: map[string]cty.Value{
1810+
"var": cty.MapVal(map[string]cty.Value{
1811+
"foo": cty.NullVal(cty.String),
1812+
}),
1813+
},
1814+
})
1815+
},
1816+
Want: "",
1817+
GetFileImpl: fileExists,
1818+
ErrCheck: func(err error) bool {
1819+
return !errors.Is(err, tflint.ErrNullValue)
1820+
},
1821+
},
1822+
{
1823+
Name: "null as cty.Value",
1824+
Expr: hclExpr(`var.foo`),
1825+
TargetType: reflect.TypeOf(cty.Value{}),
1826+
ServerImpl: func(expr hcl.Expression, opts tflint.EvaluateExprOption) (cty.Value, error) {
1827+
return evalExpr(expr, &hcl.EvalContext{
1828+
Variables: map[string]cty.Value{
1829+
"var": cty.MapVal(map[string]cty.Value{
1830+
"foo": cty.NullVal(cty.String),
1831+
}),
1832+
},
1833+
})
1834+
},
1835+
Want: cty.NullVal(cty.String),
1836+
GetFileImpl: fileExists,
1837+
ErrCheck: neverHappend,
1838+
},
1839+
{
1840+
Name: "null value in object",
1841+
Expr: hclExpr(`{ value = var.foo }`),
1842+
TargetType: reflect.TypeOf(map[string]string{}),
1843+
ServerImpl: func(expr hcl.Expression, opts tflint.EvaluateExprOption) (cty.Value, error) {
1844+
return evalExpr(expr, &hcl.EvalContext{
1845+
Variables: map[string]cty.Value{
1846+
"var": cty.MapVal(map[string]cty.Value{
1847+
"foo": cty.NullVal(cty.String),
1848+
}),
1849+
},
1850+
})
1851+
},
1852+
Want: (map[string]string)(nil),
1853+
GetFileImpl: fileExists,
1854+
ErrCheck: func(err error) bool {
1855+
return !errors.Is(err, tflint.ErrNullValue)
1856+
},
1857+
},
17481858
}
17491859

17501860
for _, test := range tests {

tflint/errors.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import (
99
// inside the plugin system, so you usually don't have to worry about it.
1010
var (
1111
// ErrUnknownValue is an error when an unknown value is referenced
12-
ErrUnknownValue = errors.New("")
12+
ErrUnknownValue = errors.New("unknown value found")
1313
// ErrNullValue is an error when null value is referenced
14-
ErrNullValue = errors.New("")
14+
ErrNullValue = errors.New("null value found")
1515
// ErrUnevaluable is an error when a received expression has unevaluable references.
16+
// Deprecated: This error is no longer returned since TFLint v0.41.
1617
ErrUnevaluable = errors.New("")
1718
// ErrSensitive is an error when a received expression contains a sensitive value.
1819
ErrSensitive = errors.New("")

0 commit comments

Comments
 (0)