diff --git a/docs/rules/README.md b/docs/rules/README.md index bbdc7d41..d522c1ca 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -18,6 +18,7 @@ These rules warn of possible errors that can occur at `terraform apply`. Rules m |[aws_db_instance_invalid_type](aws_db_instance_invalid_type.md)|Disallow using invalid instance class||✔| |aws_db_instance_invalid_vpc_security_group|Disallow using invalid VPC security groups|✔|✔| |aws_dynamodb_table_invalid_stream_view_type|Disallow using invalid stream view types for DynamoDB||✔| +|[aws_elastic_beanstalk_environment_invalid_name_format](aws_elastic_beanstalk_environment_invalid_name_format.md)|Disallow invalid environment name||✔| |aws_elasticache_cluster_invalid_parameter_group|Disallow using invalid parameter group|✔|✔| |aws_elasticache_cluster_invalid_security_group|Disallow using invalid security groups|✔|✔| |aws_elasticache_cluster_invalid_subnet_group|Disallow using invalid subnet group|✔|✔| diff --git a/docs/rules/README.md.tmpl b/docs/rules/README.md.tmpl index 6da3849e..ef48f163 100644 --- a/docs/rules/README.md.tmpl +++ b/docs/rules/README.md.tmpl @@ -18,6 +18,7 @@ These rules warn of possible errors that can occur at `terraform apply`. Rules m |[aws_db_instance_invalid_type](aws_db_instance_invalid_type.md)|Disallow using invalid instance class||✔| |aws_db_instance_invalid_vpc_security_group|Disallow using invalid VPC security groups|✔|✔| |aws_dynamodb_table_invalid_stream_view_type|Disallow using invalid stream view types for DynamoDB||✔| +|[aws_elastic_beanstalk_environment_invalid_name_format](aws_elastic_beanstalk_environment_invalid_name_format.md)|Disallow invalid environment name||✔| |aws_elasticache_cluster_invalid_parameter_group|Disallow using invalid parameter group|✔|✔| |aws_elasticache_cluster_invalid_security_group|Disallow using invalid security groups|✔|✔| |aws_elasticache_cluster_invalid_subnet_group|Disallow using invalid subnet group|✔|✔| diff --git a/docs/rules/aws_elastic_beanstalk_environment_invalid_name_format.md b/docs/rules/aws_elastic_beanstalk_environment_invalid_name_format.md new file mode 100644 index 00000000..47db0562 --- /dev/null +++ b/docs/rules/aws_elastic_beanstalk_environment_invalid_name_format.md @@ -0,0 +1,42 @@ +# aws_elastic_beanstalk_environment_invalid_name_format + +Disallow invalid Elastic Beanstalk environment name + +## Example + +```hcl +resource "aws_elastic_beanstalk_environment" "tfenvtest" { + name = "env_name_underscores" + application = "example-app" + solution_stack_name = "64bit Amazon Linux 2015.03 v2.0.3 running Go 1.4" +} +``` + +``` +$ tflint +1 issue(s) found: + +Error: env_name_underscores does not match constraint: must contain only letters, digits, and +the dash character and may not start or end with a dash +(^[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]$) (aws_elastic_beanstalk_environment_invalid_name_format) + + on example.tf line 2: + 2: name = "env_name_underscores" + +``` + +## Why + +When attempting to create the resource, Terraform will return the error: +``` +Error: InvalidParameterValue: Value env_name_underscores at 'EnvironmentName' failed to satisfy +constraint: Member must contain only letters, digits, and the dash character and may not start +or end with a dash +status code: 400 +``` + +## How To Fix + +Ensure your environment name consists only of letters, digits, and the dash character, and does +not start or end with a dash. +The regex used is `^[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]$` diff --git a/rules/aws_elastic_beanstalk_environment_invalid_name_format.go b/rules/aws_elastic_beanstalk_environment_invalid_name_format.go new file mode 100644 index 00000000..af8bd98e --- /dev/null +++ b/rules/aws_elastic_beanstalk_environment_invalid_name_format.go @@ -0,0 +1,85 @@ +package rules + +import ( + "fmt" + "regexp" + + "github.com/terraform-linters/tflint-plugin-sdk/hclext" + "github.com/terraform-linters/tflint-plugin-sdk/tflint" + "github.com/terraform-linters/tflint-ruleset-aws/project" +) + +// AwsElasticBeanstalkEnvironmentInvalidNameFormatRule checks EB environment name matches a pattern +type AwsElasticBeanstalkEnvironmentInvalidNameFormatRule struct { + tflint.DefaultRule + + resourceType string + attributeName string + pattern *regexp.Regexp +} + +// NewAwsElasticBeanstalkEnvironmentInvalidNameFormatRule returns new rule with default attributes +func NewAwsElasticBeanstalkEnvironmentInvalidNameFormatRule() *AwsElasticBeanstalkEnvironmentInvalidNameFormatRule { + return &AwsElasticBeanstalkEnvironmentInvalidNameFormatRule{ + resourceType: "aws_elastic_beanstalk_environment", + attributeName: "name", + pattern: regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]$"), + } +} + +// Name returns the rule name +func (r *AwsElasticBeanstalkEnvironmentInvalidNameFormatRule) Name() string { + return "aws_elastic_beanstalk_environment_invalid_name_format" +} + +// Enabled returns whether the rule is enabled by default +func (r *AwsElasticBeanstalkEnvironmentInvalidNameFormatRule) Enabled() bool { + return true +} + +// Severity returns the rule severity +func (r *AwsElasticBeanstalkEnvironmentInvalidNameFormatRule) Severity() tflint.Severity { + return tflint.ERROR +} + +// Link returns the rule reference link +func (r *AwsElasticBeanstalkEnvironmentInvalidNameFormatRule) Link() string { + return project.ReferenceLink(r.Name()) +} + +// Check checks the environment name matches the pattern provided +func (r *AwsElasticBeanstalkEnvironmentInvalidNameFormatRule) Check(runner tflint.Runner) error { + resources, err := runner.GetResourceContent(r.resourceType, &hclext.BodySchema{ + Attributes: []hclext.AttributeSchema{{Name: r.attributeName}}, + }, nil) + if err != nil { + return err + } + + for _, resource := range resources.Blocks { + attribute, exists := resource.Body.Attributes[r.attributeName] + if !exists { + continue + } + + var val string + err := runner.EvaluateExpr(attribute.Expr, &val, nil) + + err = runner.EnsureNoError(err, func() error { + if !r.pattern.MatchString(val) { + runner.EmitIssue( + r, + fmt.Sprintf(`%s does not match constraint: must contain only letters, digits, and the dash `+ + `character and may not start or end with a dash (^[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]$)`, val), + attribute.Expr.Range(), + ) + } + return nil + }) + if err != nil { + return err + } + } + + return nil +} diff --git a/rules/aws_elastic_beanstalk_environment_invalid_name_format_test.go b/rules/aws_elastic_beanstalk_environment_invalid_name_format_test.go new file mode 100644 index 00000000..f969d04e --- /dev/null +++ b/rules/aws_elastic_beanstalk_environment_invalid_name_format_test.go @@ -0,0 +1,84 @@ +package rules + +import ( + "testing" + + hcl "github.com/hashicorp/hcl/v2" + "github.com/terraform-linters/tflint-plugin-sdk/helper" +) + +func Test_AwsElasticBeanstalkEnvironmentInvalidNameFormat(t *testing.T) { + cases := []struct { + Name string + Content string + Expected helper.Issues + }{ + { + Name: "tf-test-name dash valid", + Content: ` +resource "aws_elastic_beanstalk_environment" "tfenvtest" { + name = "tf-test-name" + application = "tf-test-name" + solution_stack_name = "64bit Amazon Linux 2015.03 v2.0.3 running Go 1.4" +} +`, + Expected: helper.Issues{}, + }, + { + Name: "underscores invalid", + Content: ` +resource "aws_elastic_beanstalk_environment" "tfenvtest" { + name = "tf_test_name" + application = "tf-test-name" + solution_stack_name = "64bit Amazon Linux 2015.03 v2.0.3 running Go 1.4" +} +`, + Expected: helper.Issues{ + { + Rule: NewAwsElasticBeanstalkEnvironmentInvalidNameFormatRule(), + Message: "tf_test_name does not match constraint: must contain only letters, digits, and " + + "the dash character and may not start or end with a dash (^[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]$)", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 3, Column: 24}, + End: hcl.Pos{Line: 3, Column: 38}, + }, + }, + }, + }, + { + Name: "end with dash invalid", + Content: ` +resource "aws_elastic_beanstalk_environment" "tfenvtest" { + name = "tf-test-name-" + application = "tf-test-name" + solution_stack_name = "64bit Amazon Linux 2015.03 v2.0.3 running Go 1.4" +} +`, + Expected: helper.Issues{ + { + Rule: NewAwsElasticBeanstalkEnvironmentInvalidNameFormatRule(), + Message: "tf-test-name- does not match constraint: must contain only letters, digits, and " + + "the dash character and may not start or end with a dash (^[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]$)", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 3, Column: 24}, + End: hcl.Pos{Line: 3, Column: 39}, + }, + }, + }, + }, + } + + rule := NewAwsElasticBeanstalkEnvironmentInvalidNameFormatRule() + + 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 2e8884c8..2056d4cd 100644 --- a/rules/provider.go +++ b/rules/provider.go @@ -38,6 +38,7 @@ var rules = [][]tflint.Rule{ NewAwsLambdaFunctionDeprecatedRuntimeRule(), NewAwsIAMGroupPolicyTooLongRule(), NewAwsAcmCertificateLifecycleRule(), + NewAwsElasticBeanstalkEnvironmentInvalidNameFormatRule(), }, models.Rules, api.Rules,