From 00f98e59e9b589283ee5cfa1aeb20bce0d6a1276 Mon Sep 17 00:00:00 2001 From: wata_mac Date: Sun, 2 Feb 2020 20:56:21 +0900 Subject: [PATCH 1/2] Add new terraform_deprecated_interpolation rule --- docs/rules/README.md | 1 + .../terraform_deprecated_interpolation.md | 38 ++++++ rules/provider.go | 1 + .../terraform_deprecated_interpolation.go | 84 ++++++++++++++ ...terraform_deprecated_interpolation_test.go | 109 ++++++++++++++++++ 5 files changed, 233 insertions(+) create mode 100644 docs/rules/terraform_deprecated_interpolation.md create mode 100644 rules/terraformrules/terraform_deprecated_interpolation.go create mode 100644 rules/terraformrules/terraform_deprecated_interpolation_test.go diff --git a/docs/rules/README.md b/docs/rules/README.md index 9f4164932..4b5e8e849 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -69,6 +69,7 @@ These rules suggest to better ways. | --- | --- | |[terraform_dash_in_resource_name](terraform_dash_in_resource_name.md)|| |[terraform_dash_in_output_name](terraform_dash_in_output_name.md)|| +|[terraform_deprecated_interpolation](terraform_deprecated_interpolation.md)|✔| |[terraform_documented_outputs](terraform_documented_outputs.md)|| |[terraform_documented_variables](terraform_documented_variables.md)|| |[terraform_module_pinned_source](terraform_module_pinned_source.md)|✔| diff --git a/docs/rules/terraform_deprecated_interpolation.md b/docs/rules/terraform_deprecated_interpolation.md new file mode 100644 index 000000000..53a1cf3d8 --- /dev/null +++ b/docs/rules/terraform_deprecated_interpolation.md @@ -0,0 +1,38 @@ +# terraform_deprecated_interpolation + +Disallow deprecated (0.11-style) interpolation + +## Example + +```hcl +resource "aws_instance" "deprecated" { + instance_type = "${var.type}" +} + +resource "aws_instance" "new" { + instance_type = var.type +} +``` + +``` +$ tflint +1 issue(s) found: + +Warning: Interpolation-only expressions are deprecated in Terraform v0.12.14 (terraform_deprecated_interpolation) + + on example.tf line 2: + 2: instance_type = "${var.type}" + +Reference: https://github.com/terraform-linters/tflint/blob/v0.14.0/docs/rules/terraform_deprecated_interpolation.md + +``` + +## Why + +Terraform v0.12 introduces a new interpolation syntax, but continues to support the old 0.11-style interpolation syntax for compatibility. + +Terraform will now warn at planning/applying due to its policy to make old syntax errors in the next major release (v0.13). TFLint emits an issue instead of a warning with the same logic. + +## How To Fix + +Switch to the new interpolation syntax. See the release notes for Terraform 0.12.14 for details: https://github.com/hashicorp/terraform/releases/tag/v0.12.14 \ No newline at end of file diff --git a/rules/provider.go b/rules/provider.go index d7a3f8381..87a8c37f2 100644 --- a/rules/provider.go +++ b/rules/provider.go @@ -37,6 +37,7 @@ var manualDefaultRules = []Rule{ awsrules.NewAwsSpotFleetRequestInvalidExcessCapacityTerminationPolicyRule(), terraformrules.NewTerraformDashInResourceNameRule(), terraformrules.NewTerraformDashInOutputNameRule(), + terraformrules.NewTerraformDeprecatedInterpolationRule(), terraformrules.NewTerraformDocumentedOutputsRule(), terraformrules.NewTerraformDocumentedVariablesRule(), terraformrules.NewTerraformModulePinnedSourceRule(), diff --git a/rules/terraformrules/terraform_deprecated_interpolation.go b/rules/terraformrules/terraform_deprecated_interpolation.go new file mode 100644 index 000000000..8a3c5cc5c --- /dev/null +++ b/rules/terraformrules/terraform_deprecated_interpolation.go @@ -0,0 +1,84 @@ +package terraformrules + +import ( + "log" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/terraform-linters/tflint/tflint" +) + +// TerraformDeprecatedInterpolationRule warns of deprecated interpolation in Terraform v0.11 or earlier. +type TerraformDeprecatedInterpolationRule struct{} + +// NewTerraformDeprecatedInterpolationRule return a new rule +func NewTerraformDeprecatedInterpolationRule() *TerraformDeprecatedInterpolationRule { + return &TerraformDeprecatedInterpolationRule{} +} + +// Name returns the rule name +func (r *TerraformDeprecatedInterpolationRule) Name() string { + return "terraform_deprecated_interpolation" +} + +// Enabled returns whether the rule is enabled by default +func (r *TerraformDeprecatedInterpolationRule) Enabled() bool { + return true +} + +// Severity returns the rule severity +func (r *TerraformDeprecatedInterpolationRule) Severity() string { + return tflint.WARNING +} + +// Link returns the rule reference link +func (r *TerraformDeprecatedInterpolationRule) Link() string { + return tflint.ReferenceLink(r.Name()) +} + +// Check emits issues on the deprecated interpolation syntax. +// This logic is equivalent to the warning logic implemented in Terraform. +// See https://github.com/hashicorp/terraform/pull/23348 +func (r *TerraformDeprecatedInterpolationRule) Check(runner *tflint.Runner) error { + log.Printf("[TRACE] Check `%s` rule for `%s` runner", r.Name(), runner.TFConfigPath()) + + for _, resource := range runner.TFConfig.Module.ManagedResources { + r.checkForDeprecatedInterpolationsInBody(runner, resource.Config) + } + for _, provider := range runner.TFConfig.Module.ProviderConfigs { + r.checkForDeprecatedInterpolationsInBody(runner, provider.Config) + } + + return nil +} + +func (r *TerraformDeprecatedInterpolationRule) checkForDeprecatedInterpolationsInBody(runner *tflint.Runner, body hcl.Body) { + nativeBody, ok := body.(*hclsyntax.Body) + if !ok { + return + } + + for _, attr := range nativeBody.Attributes { + r.checkForDeprecatedInterpolationsInExpr(runner, attr.Expr) + } + + for _, block := range nativeBody.Blocks { + r.checkForDeprecatedInterpolationsInBody(runner, block.Body) + } + + return +} + +func (r *TerraformDeprecatedInterpolationRule) checkForDeprecatedInterpolationsInExpr(runner *tflint.Runner, expr hcl.Expression) { + if _, ok := expr.(*hclsyntax.TemplateWrapExpr); !ok { + return + } + + runner.EmitIssue( + r, + "Interpolation-only expressions are deprecated in Terraform v0.12.14", + expr.Range(), + ) + + return +} diff --git a/rules/terraformrules/terraform_deprecated_interpolation_test.go b/rules/terraformrules/terraform_deprecated_interpolation_test.go new file mode 100644 index 000000000..f01ae027c --- /dev/null +++ b/rules/terraformrules/terraform_deprecated_interpolation_test.go @@ -0,0 +1,109 @@ +package terraformrules + +import ( + "testing" + + "github.com/hashicorp/hcl/v2" + "github.com/terraform-linters/tflint/tflint" +) + +func Test_TerraformDeprecatedInterpolationRule(t *testing.T) { + cases := []struct { + Name string + Content string + Expected tflint.Issues + }{ + { + Name: "deprecated single interpolation", + Content: ` +resource "null_resource" "a" { + triggers = "${var.triggers}" +}`, + Expected: tflint.Issues{ + { + Rule: NewTerraformDeprecatedInterpolationRule(), + Message: "Interpolation-only expressions are deprecated in Terraform v0.12.14", + Range: hcl.Range{ + Filename: "config.tf", + Start: hcl.Pos{Line: 3, Column: 13}, + End: hcl.Pos{Line: 3, Column: 30}, + }, + }, + }, + }, + { + Name: "deprecated single interpolation in provider block", + Content: ` +provider "null" { + foo = "${var.triggers["foo"]}" +}`, + Expected: tflint.Issues{ + { + Rule: NewTerraformDeprecatedInterpolationRule(), + Message: "Interpolation-only expressions are deprecated in Terraform v0.12.14", + Range: hcl.Range{ + Filename: "config.tf", + Start: hcl.Pos{Line: 3, Column: 8}, + End: hcl.Pos{Line: 3, Column: 32}, + }, + }, + }, + }, + { + Name: "deprecated single interpolation in nested block", + Content: ` +resource "null_resource" "a" { + provisioner "local-exec" { + single = "${var.triggers["greeting"]}" + } +}`, + Expected: tflint.Issues{ + { + Rule: NewTerraformDeprecatedInterpolationRule(), + Message: "Interpolation-only expressions are deprecated in Terraform v0.12.14", + Range: hcl.Range{ + Filename: "config.tf", + Start: hcl.Pos{Line: 4, Column: 12}, + End: hcl.Pos{Line: 4, Column: 41}, + }, + }, + }, + }, + { + Name: "interpolation as template", + Content: ` +resource "null_resource" "a" { + triggers = "${var.triggers} " +}`, + Expected: tflint.Issues{}, + }, + { + Name: "interpolation in array", + Content: ` +resource "null_resource" "a" { + triggers = ["${var.triggers}"] +}`, + Expected: tflint.Issues{}, + }, + { + Name: "new interpolation syntax", + Content: ` +resource "null_resource" "a" { + triggers = var.triggers +}`, + Expected: tflint.Issues{}, + }, + } + + rule := NewTerraformDeprecatedInterpolationRule() + + for _, tc := range cases { + runner := tflint.TestRunner(t, map[string]string{"config.tf": tc.Content}) + + if err := rule.Check(runner); err != nil { + t.Fatalf("Unexpected error occurred: %s", err) + } + + tflint.AssertIssues(t, tc.Expected, runner.Issues) + } +} From 606a2fe8198579a09f474c65ef7130c097d18abc Mon Sep 17 00:00:00 2001 From: wata_mac Date: Sun, 2 Feb 2020 21:39:02 +0900 Subject: [PATCH 2/2] Tweak integration tests --- integration/variables/result.json | 10 +++++----- integration/variables/template.tf | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/integration/variables/result.json b/integration/variables/result.json index 84bfde5fd..295efc231 100644 --- a/integration/variables/result.json +++ b/integration/variables/result.json @@ -15,7 +15,7 @@ }, "end": { "line": 28, - "column": 35 + "column": 30 } }, "callers": [] @@ -35,7 +35,7 @@ }, "end": { "line": 32, - "column": 47 + "column": 42 } }, "callers": [] @@ -55,7 +55,7 @@ }, "end": { "line": 36, - "column": 44 + "column": 39 } }, "callers": [] @@ -75,7 +75,7 @@ }, "end": { "line": 40, - "column": 39 + "column": 34 } }, "callers": [] @@ -95,7 +95,7 @@ }, "end": { "line": 44, - "column": 31 + "column": 26 } }, "callers": [] diff --git a/integration/variables/template.tf b/integration/variables/template.tf index 3c09e9226..38c3dacfd 100644 --- a/integration/variables/template.tf +++ b/integration/variables/template.tf @@ -21,25 +21,25 @@ variable "var" { } resource "aws_instance" "unknown" { - instance_type = "${var.unknown}" + instance_type = var.unknown } resource "aws_instance" "default" { - instance_type = "${var.default}" + instance_type = var.default } resource "aws_instance" "default_values_file" { - instance_type = "${var.default_values_file}" + instance_type = var.default_values_file } resource "aws_instance" "auto_values_file" { - instance_type = "${var.auto_values_file}" + instance_type = var.auto_values_file } resource "aws_instance" "values_file" { - instance_type = "${var.values_file}" + instance_type = var.values_file } resource "aws_instance" "var" { - instance_type = "${var.var}" + instance_type = var.var }