Skip to content
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
7 changes: 1 addition & 6 deletions docs/developer-guide/api_compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,9 @@
This is an internal documentation summarizing the currently supported SDK and TFLint versions and any compatibility caveats.

Protocol version: 11
SDK version: v0.14.0+
SDK version: v0.16.0+
TFLint version: v0.42.0+

- Client-side value handling is introduced in SDK v0.16.0 and TFLint v0.46.0. TFLint v0.45.0 returns an error instead of a value.
- https://github.com/terraform-linters/tflint/pull/1700
- https://github.com/terraform-linters/tflint/pull/1722
- https://github.com/terraform-linters/tflint-plugin-sdk/pull/235
- https://github.com/terraform-linters/tflint-plugin-sdk/pull/239
- Ephemeral value is introduced in SDK v0.22.0 and TFLint v0.55.0. TFLint returns ErrSensitive instead of ephemeral values to plugins built with SDK v0.21.0.
- https://github.com/terraform-linters/tflint/pull/2178
- https://github.com/terraform-linters/tflint-plugin-sdk/pull/358
2 changes: 1 addition & 1 deletion plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var (
)

// SDKVersionConstraints is the version constraint of the supported SDK version.
var SDKVersionConstraints = version.MustConstraints(version.NewConstraint(">= 0.14.0"))
var SDKVersionConstraints = version.MustConstraints(version.NewConstraint(">= 0.16.0"))

// Plugin is an object handling plugins
// Basically, it is a wrapper for go-plugin and provides an API to handle them collectively.
Expand Down
63 changes: 13 additions & 50 deletions plugin/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,59 +144,22 @@ func (s *GRPCServer) EvaluateExpr(expr hcl.Expression, opts sdk.EvaluateExprOpti
return val, diags
}

// SDK v0.16+ introduces client-side handling of unknown/NULL/sensitive values.
if s.clientSDKVersion != nil && s.clientSDKVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.16.0"))) {
// Before SDK v0.22, ephemeral marks are ignored, so retrun ErrSensitive to prevent secrets ​​from being leaked.
if !marks.Contains(val, marks.Ephemeral) || s.clientSDKVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.22.0"))) {
return val, nil
}
}

if val.ContainsMarked() {
err := fmt.Errorf(
"sensitive value found in %s:%d%w",
expr.Range().Filename,
expr.Range().Start.Line,
sdk.ErrSensitive,
)
log.Printf("[INFO] %s. TFLint ignores expressions with sensitive values.", err)
return cty.NullVal(cty.NilType), err
}

if *opts.WantType == cty.DynamicPseudoType {
// If an ephemeral mark is contained, cty.Value will not be returned
// unless the plugin is built with SDK 0.22+ which supports ephemeral marks.
if !marks.Contains(val, marks.Ephemeral) || s.clientSDKVersion.GreaterThanOrEqual(version.Must(version.NewVersion("0.22.0"))) {
return val, nil
}

err := cty.Walk(val, func(path cty.Path, v cty.Value) (bool, error) {
if !v.IsKnown() {
err := fmt.Errorf(
"unknown value found in %s:%d%w",
expr.Range().Filename,
expr.Range().Start.Line,
sdk.ErrUnknownValue,
)
log.Printf("[INFO] %s. TFLint can only evaluate provided variables and skips dynamic values.", err)
return false, err
}

if v.IsNull() {
err := fmt.Errorf(
"null value found in %s:%d%w",
expr.Range().Filename,
expr.Range().Start.Line,
sdk.ErrNullValue,
)
log.Printf("[INFO] %s. TFLint ignores expressions with null values.", err)
return false, err
}

return true, nil
})
if err != nil {
return cty.NullVal(cty.NilType), err
}

return val, nil
// Plugins that do not support ephemeral marks will return ErrSensitive to prevent secrets from being exposed.
// Do not return ErrEphemeral as it is not supported by plugins.
err := fmt.Errorf(
"ephemeral value found in %s:%d%w",
expr.Range().Filename,
expr.Range().Start.Line,
sdk.ErrSensitive,
)
log.Printf("[INFO] %s. TFLint ignores ephemeral values for plugins built with SDK versions earlier than v0.22.", err)
return cty.NullVal(cty.NilType), err
}

// EmitIssue stores an issue in the server based on passed rule, message, and location.
Expand Down
88 changes: 0 additions & 88 deletions plugin/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,6 @@ variable "foo" {

server := NewGRPCServer(runner, rootRunner, runner.Files(), SDKVersion)

sdkv15 := version.Must(version.NewVersion("0.15.0"))
sdkv21 := version.Must(version.NewVersion("0.21.0"))

// test util functions
Expand Down Expand Up @@ -525,29 +524,6 @@ variable "foo" {
Want: cty.StringVal("foo").Mark(marks.Sensitive),
ErrCheck: neverHappend,
},
{
Name: "sensitive value (SDK v0.15)",
Args: func() (hcl.Expression, sdk.EvaluateExprOption) {
return hclExpr(`var.sensitive`), sdk.EvaluateExprOption{WantType: &cty.String, ModuleCtx: sdk.SelfModuleCtxType}
},
Want: cty.NullVal(cty.NilType),
SDKVersion: sdkv15,
ErrCheck: func(err error) bool {
return err == nil || !errors.Is(err, sdk.ErrSensitive)
},
},
{
Name: "sensitive value in object (SDK v0.15)",
Args: func() (hcl.Expression, sdk.EvaluateExprOption) {
ty := cty.Object(map[string]cty.Type{"value": cty.String})
return hclExpr(`{ value = var.sensitive }`), sdk.EvaluateExprOption{WantType: &ty, ModuleCtx: sdk.SelfModuleCtxType}
},
Want: cty.NullVal(cty.NilType),
SDKVersion: sdkv15,
ErrCheck: func(err error) bool {
return err == nil || !errors.Is(err, sdk.ErrSensitive)
},
},
{
Name: "sensitive value (SDK v0.21)",
Args: func() (hcl.Expression, sdk.EvaluateExprOption) {
Expand All @@ -565,38 +541,6 @@ variable "foo" {
Want: cty.UnknownVal(cty.String),
ErrCheck: neverHappend,
},
{
Name: "no default (SDK v0.15)",
Args: func() (hcl.Expression, sdk.EvaluateExprOption) {
return hclExpr(`var.no_default`), sdk.EvaluateExprOption{WantType: &cty.String, ModuleCtx: sdk.SelfModuleCtxType}
},
SDKVersion: sdkv15,
Want: cty.NullVal(cty.NilType),
ErrCheck: func(err error) bool {
return err == nil || !errors.Is(err, sdk.ErrUnknownValue)
},
},
{
Name: "no default as cty.Value (SDK v0.15)",
Args: func() (hcl.Expression, sdk.EvaluateExprOption) {
return hclExpr(`var.no_default`), sdk.EvaluateExprOption{WantType: &cty.DynamicPseudoType, ModuleCtx: sdk.SelfModuleCtxType}
},
SDKVersion: sdkv15,
Want: cty.DynamicVal,
ErrCheck: neverHappend,
},
{
Name: "no default value in object (SDK v0.15)",
Args: func() (hcl.Expression, sdk.EvaluateExprOption) {
ty := cty.Object(map[string]cty.Type{"value": cty.String})
return hclExpr(`{ value = var.no_default }`), sdk.EvaluateExprOption{WantType: &ty, ModuleCtx: sdk.SelfModuleCtxType}
},
SDKVersion: sdkv15,
Want: cty.NullVal(cty.NilType),
ErrCheck: func(err error) bool {
return err == nil || !errors.Is(err, sdk.ErrUnknownValue)
},
},
{
Name: "null",
Args: func() (hcl.Expression, sdk.EvaluateExprOption) {
Expand All @@ -605,38 +549,6 @@ variable "foo" {
Want: cty.NullVal(cty.String),
ErrCheck: neverHappend,
},
{
Name: "null (SDK v0.15)",
Args: func() (hcl.Expression, sdk.EvaluateExprOption) {
return hclExpr(`var.null`), sdk.EvaluateExprOption{WantType: &cty.String, ModuleCtx: sdk.SelfModuleCtxType}
},
SDKVersion: sdkv15,
Want: cty.NullVal(cty.NilType),
ErrCheck: func(err error) bool {
return err == nil || !errors.Is(err, sdk.ErrNullValue)
},
},
{
Name: "null as cty.Value (SDK v0.15)",
Args: func() (hcl.Expression, sdk.EvaluateExprOption) {
return hclExpr(`var.null`), sdk.EvaluateExprOption{WantType: &cty.DynamicPseudoType, ModuleCtx: sdk.SelfModuleCtxType}
},
SDKVersion: sdkv15,
Want: cty.NullVal(cty.String),
ErrCheck: neverHappend,
},
{
Name: "null value in object (SDK v0.15)",
Args: func() (hcl.Expression, sdk.EvaluateExprOption) {
ty := cty.Object(map[string]cty.Type{"value": cty.String})
return hclExpr(`{ value = var.null }`), sdk.EvaluateExprOption{WantType: &ty, ModuleCtx: sdk.SelfModuleCtxType}
},
Want: cty.NullVal(cty.NilType),
SDKVersion: sdkv15,
ErrCheck: func(err error) bool {
return err == nil || !errors.Is(err, sdk.ErrNullValue)
},
},
{
Name: "ephemeral value",
Args: func() (hcl.Expression, sdk.EvaluateExprOption) {
Expand Down
3 changes: 2 additions & 1 deletion terraform/tfhcl/expand_body.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ func (b *expandBody) prepareAttributes(rawAttrs hcl.Attributes) (hcl.Attributes,
diags = append(diags, evalDiags...)
continue
}
// Marked values (e.g. sensitive values) are unbound for serialization.
// Marked values (e.g. ephemeral values) are unbound for serialization.
// TODO: Update the minimum supported SDK version to v0.22+ and then remove this condition.
if !val.ContainsMarked() {
attr.Expr = hclext.BindValue(val, expr)
}
Expand Down