From 20a14646ec7402659b997ee70858e59968af25cf Mon Sep 17 00:00:00 2001 From: James Ray Date: Thu, 12 Aug 2021 11:55:49 -0400 Subject: [PATCH] aws_lambda_end_of_life (#154) * aws_lambda_end_of_life - Added lambda End of Life rules * aws_lambda_end_of_life - Added end of support * Updated to have one rule called deprecated_runtime --- .../aws_lambda_function_deprecated_runtime.md | 33 ++++++ .../aws_lambda_function_deprecated_runtime.go | 91 ++++++++++++++++ ...lambda_function_deprecated_runtime_test.go | 103 ++++++++++++++++++ rules/provider.go | 1 + 4 files changed, 228 insertions(+) create mode 100644 docs/rules/aws_lambda_function_deprecated_runtime.md create mode 100644 rules/aws_lambda_function_deprecated_runtime.go create mode 100644 rules/aws_lambda_function_deprecated_runtime_test.go diff --git a/docs/rules/aws_lambda_function_deprecated_runtime.md b/docs/rules/aws_lambda_function_deprecated_runtime.md new file mode 100644 index 00000000..da787b96 --- /dev/null +++ b/docs/rules/aws_lambda_function_deprecated_runtime.md @@ -0,0 +1,33 @@ +# aws_lambda_function_deprecated_runtime + +Checks to see if a lambda function has been set with a runtime that is deprecated. This can show up as either "end of support" or "end of life" depending on the phase of deprecation it is currently in. + +## Example + +```hcl +resource "aws_lambda_function" "function" { + function_name = "test_function" + role = "test_role" + runtime = "python2.7" +} +``` + + +``` +$ tflint +1 issue(s) found: + +Error: The "python2.7" runtime has reached the end of support. (aws_lambda_function_deprecated_runtime) + + on template.tf line 4: + 4: runtime = "python2.7" // end of support reached! + +``` + +## Why + +AWS no longer supports these runtimes. + +## How To Fix + +Update to a newer runtime. Supported runtimes can be found [here](https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html) diff --git a/rules/aws_lambda_function_deprecated_runtime.go b/rules/aws_lambda_function_deprecated_runtime.go new file mode 100644 index 00000000..e7480b4c --- /dev/null +++ b/rules/aws_lambda_function_deprecated_runtime.go @@ -0,0 +1,91 @@ +package rules + +import ( + "fmt" + hcl "github.com/hashicorp/hcl/v2" + "github.com/terraform-linters/tflint-plugin-sdk/tflint" + "github.com/terraform-linters/tflint-ruleset-aws/project" + "time" +) + +// AwsLambdaFunctionDeprecatedRuntimeRule checks to see if the lambda runtime has reached End Of Support +type AwsLambdaFunctionDeprecatedRuntimeRule struct { + resourceType string + attributeName string + eosRuntimes map[string]time.Time + eolRuntimes map[string]time.Time +} + +// NewAwsLambdaFunctionDeprecatedRuntimeRule returns new rule with default attributes +func NewAwsLambdaFunctionDeprecatedRuntimeRule() *AwsLambdaFunctionDeprecatedRuntimeRule { + return &AwsLambdaFunctionDeprecatedRuntimeRule{ + resourceType: "aws_lambda_function", + attributeName: "runtime", + eosRuntimes: map[string]time.Time{ + "nodejs10.x": time.Date(2021, time.July, 30, 0, 0, 0, 0, time.UTC), + "ruby2.5": time.Date(2021, time.July, 30, 0, 0, 0, 0, time.UTC), + "python2.7": time.Date(2021, time.July, 15, 0, 0, 0, 0, time.UTC), + "dotnetcore2.1": time.Date(2021, time.September, 20, 0, 0, 0, 0, time.UTC), + }, + eolRuntimes: map[string]time.Time{ + "dotnetcore1.0": time.Date(2019, time.July, 30, 0, 0, 0, 0, time.UTC), + "dotnetcore2.0": time.Date(2019, time.May, 30, 0, 0, 0, 0, time.UTC), + "nodejs": time.Date(2016, time.October, 31, 0, 0, 0, 0, time.UTC), + "nodejs4.3": time.Date(2020, time.March, 06, 0, 0, 0, 0, time.UTC), + "nodejs4.3-edge": time.Date(2019, time.April, 30, 0, 0, 0, 0, time.UTC), + "nodejs6.10": time.Date(2019, time.August, 12, 0, 0, 0, 0, time.UTC), + "nodejs8.10": time.Date(2020, time.March, 06, 0, 0, 0, 0, time.UTC), + "nodejs10.x": time.Date(2021, time.August, 30, 0, 0, 0, 0, time.UTC), + "ruby2.5": time.Date(2021, time.August, 30, 0, 0, 0, 0, time.UTC), + "python2.7": time.Date(2021, time.September, 30, 0, 0, 0, 0, time.UTC), + "dotnetcore2.1": time.Date(2021, time.October, 30, 0, 0, 0, 0, time.UTC), + }, + } +} + +// Name returns the rule name +func (r *AwsLambdaFunctionDeprecatedRuntimeRule) Name() string { + return "aws_lambda_function_deprecated_runtime" +} + +// Enabled returns whether the rule is enabled by default +func (r *AwsLambdaFunctionDeprecatedRuntimeRule) Enabled() bool { + return true +} + +// Severity returns the rule severity +func (r *AwsLambdaFunctionDeprecatedRuntimeRule) Severity() string { + return tflint.WARNING +} + +// Link returns the rule reference link +func (r *AwsLambdaFunctionDeprecatedRuntimeRule) Link() string { + return project.ReferenceLink(r.Name()) +} + +// Check checks if the chosen runtime has reached EOS. Date check allows future values to be created as well. +func (r *AwsLambdaFunctionDeprecatedRuntimeRule) Check(runner tflint.Runner) error { + + return runner.WalkResourceAttributes(r.resourceType, r.attributeName, func(attribute *hcl.Attribute) error { + var val string + err := runner.EvaluateExpr(attribute.Expr, &val, nil) + now := time.Now().UTC() + + return runner.EnsureNoError(err, func() error { + if _, ok := r.eolRuntimes[val]; ok && now.After(r.eolRuntimes[val]) { + runner.EmitIssueOnExpr( + r, + fmt.Sprintf("The \"%s\" runtime has reached the end of life", val), + attribute.Expr, + ) + } else if _, ok := r.eosRuntimes[val]; ok && now.After(r.eosRuntimes[val]) { + runner.EmitIssueOnExpr( + r, + fmt.Sprintf("The \"%s\" runtime has reached the end of support", val), + attribute.Expr, + ) + } + return nil + }) + }) +} diff --git a/rules/aws_lambda_function_deprecated_runtime_test.go b/rules/aws_lambda_function_deprecated_runtime_test.go new file mode 100644 index 00000000..3bbbc03d --- /dev/null +++ b/rules/aws_lambda_function_deprecated_runtime_test.go @@ -0,0 +1,103 @@ +package rules + +import ( + hcl "github.com/hashicorp/hcl/v2" + "github.com/terraform-linters/tflint-plugin-sdk/helper" + "testing" + "time" +) + +func Test_AwsLambdaFunctionEndOfSupport(t *testing.T) { + type caseStudy struct { + Name string + Content string + Expected helper.Issues + } + eosRuntimes := map[string]time.Time{ + "nodejs10.x": time.Date(2021, time.July, 30, 0, 0, 0, 0, time.UTC), + "ruby2.5": time.Date(2021, time.July, 30, 0, 0, 0, 0, time.UTC), + "python2.7": time.Date(2021, time.July, 15, 0, 0, 0, 0, time.UTC), + "dotnetcore2.1": time.Date(2021, time.September, 20, 0, 0, 0, 0, time.UTC), + } + var cases []caseStudy + now := time.Now().UTC() + for runtime, eosDate := range eosRuntimes { + if now.Before(eosDate) { + continue + } + study := caseStudy{ + Name: runtime + " end of support", + Content: ` +resource "aws_lambda_function" "function" { + function_name = "test_function" + role = "test_role" + runtime = "` + runtime + `" +} +`, + Expected: helper.Issues{ + { + Rule: NewAwsLambdaFunctionDeprecatedRuntimeRule(), + Message: "The \"" + runtime + "\" runtime has reached the end of support", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 5, Column: 12}, + End: hcl.Pos{Line: 5, Column: len(runtime) + 14}, + }, + }, + }, + } + cases = append(cases, study) + } + eolRuntimes := map[string]time.Time{ + "dotnetcore1.0": time.Date(2019, time.July, 30, 0, 0, 0, 0, time.UTC), + "dotnetcore2.0": time.Date(2019, time.May, 30, 0, 0, 0, 0, time.UTC), + "nodejs": time.Date(2016, time.October, 31, 0, 0, 0, 0, time.UTC), + "nodejs4.3": time.Date(2020, time.March, 06, 0, 0, 0, 0, time.UTC), + "nodejs4.3-edge": time.Date(2019, time.April, 30, 0, 0, 0, 0, time.UTC), + "nodejs6.10": time.Date(2019, time.August, 12, 0, 0, 0, 0, time.UTC), + "nodejs8.10": time.Date(2020, time.March, 06, 0, 0, 0, 0, time.UTC), + "nodejs10.x": time.Date(2021, time.August, 30, 0, 0, 0, 0, time.UTC), + "ruby2.5": time.Date(2021, time.August, 30, 0, 0, 0, 0, time.UTC), + "python2.7": time.Date(2021, time.September, 30, 0, 0, 0, 0, time.UTC), + "dotnetcore2.1": time.Date(2021, time.October, 30, 0, 0, 0, 0, time.UTC), + } + for runtime, eolDate := range eolRuntimes { + if now.Before(eolDate) { + continue + } + study := caseStudy{ + Name: runtime + " end of life", + Content: ` +resource "aws_lambda_function" "function" { + function_name = "test_function" + role = "test_role" + runtime = "` + runtime + `" +} +`, + Expected: helper.Issues{ + { + Rule: NewAwsLambdaFunctionDeprecatedRuntimeRule(), + Message: "The \"" + runtime + "\" runtime has reached the end of life", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 5, Column: 12}, + End: hcl.Pos{Line: 5, Column: len(runtime) + 14}, + }, + }, + }, + } + cases = append(cases, study) + } + + rule := NewAwsLambdaFunctionDeprecatedRuntimeRule() + + for _, tc := range cases { + runner := helper.TestRunner(t, map[string]string{"resource.tf": tc.Content}) + + if err := rule.Check(runner); err != nil { + t.Fatalf("Unexpected error occurred: %s", err) + } + + helper.AssertIssues(t, tc.Expected, runner.Issues) + } +} diff --git a/rules/provider.go b/rules/provider.go index 65b78a02..6c9dd550 100644 --- a/rules/provider.go +++ b/rules/provider.go @@ -34,4 +34,5 @@ var Rules = append([]tflint.Rule{ NewAwsElastiCacheReplicationGroupPreviousTypeRule(), NewAwsIAMPolicySidInvalidCharactersRule(), NewAwsIAMPolicyTooLongPolicyRule(), + NewAwsLambdaFunctionDeprecatedRuntimeRule(), }, models.Rules...)