Skip to content

Commit 131fddd

Browse files
authored
Add acm certificate lifecycle create before destroy rule (#202)
* Add acm certificate lifecycle create before destroy rule * Fix broken links
1 parent c34979e commit 131fddd

File tree

6 files changed

+188
-4
lines changed

6 files changed

+188
-4
lines changed

docs/rules/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ These rules warn of possible errors that can occur at `terraform apply`. Rules m
2020
|aws_elasticache_cluster_invalid_parameter_group|Disallow using invalid parameter group|||
2121
|aws_elasticache_cluster_invalid_security_group|Disallow using invalid security groups|||
2222
|aws_elasticache_cluster_invalid_subnet_group|Disallow using invalid subnet group|||
23-
|[aws_elasticache_cluster_invalid_type](aws_elasticache_cluster_invalid_type)|Disallow using invalid node type|||
24-
|[aws_elasticache_replication_group_invalid_type](aws_elasticache_replication_group_invalid_type)|Disallow using invalid node type|||
23+
|[aws_elasticache_cluster_invalid_type](aws_elasticache_cluster_invalid_type.md)|Disallow using invalid node type|||
24+
|[aws_elasticache_replication_group_invalid_type](aws_elasticache_replication_group_invalid_type.md)|Disallow using invalid node type|||
2525
|aws_elb_invalid_instance|Disallow using invalid instances|||
2626
|aws_elb_invalid_security_group|Disallow using invalid security groups|||
2727
|aws_elb_invalid_subnet|Disallow using invalid subnets|||
@@ -48,6 +48,7 @@ These rules enforce best practices and naming conventions:
4848

4949
|Rule|Description|Enabled by default|
5050
| --- | --- | --- |
51+
|[aws_acm_certificate_lifecycle](aws_acm_certificate_lifecycle.md)|Disallow adding `aws_acm_certificate` resource without setting `create_before_destroy = true` in `lifecycle` block ||
5152
|[aws_db_instance_previous_type](aws_db_instance_previous_type.md)|Disallow using previous generation instance types||
5253
|[aws_db_instance_default_parameter_group](aws_db_instance_default_parameter_group.md)|Disallow using default DB parameter group||
5354
|[aws_elasticache_cluster_previous_type](aws_elasticache_cluster_previous_type.md)|Disallow using previous node types||

docs/rules/README.md.tmpl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ These rules warn of possible errors that can occur at `terraform apply`. Rules m
2020
|aws_elasticache_cluster_invalid_parameter_group|Disallow using invalid parameter group|✔|✔|
2121
|aws_elasticache_cluster_invalid_security_group|Disallow using invalid security groups|✔|✔|
2222
|aws_elasticache_cluster_invalid_subnet_group|Disallow using invalid subnet group|✔|✔|
23-
|[aws_elasticache_cluster_invalid_type](aws_elasticache_cluster_invalid_type)|Disallow using invalid node type||✔|
24-
|[aws_elasticache_replication_group_invalid_type](aws_elasticache_replication_group_invalid_type)|Disallow using invalid node type||✔|
23+
|[aws_elasticache_cluster_invalid_type](aws_elasticache_cluster_invalid_type.md)|Disallow using invalid node type||✔|
24+
|[aws_elasticache_replication_group_invalid_type](aws_elasticache_replication_group_invalid_type.md)|Disallow using invalid node type||✔|
2525
|aws_elb_invalid_instance|Disallow using invalid instances|✔|✔|
2626
|aws_elb_invalid_security_group|Disallow using invalid security groups|✔|✔|
2727
|aws_elb_invalid_subnet|Disallow using invalid subnets|✔|✔|
@@ -48,6 +48,7 @@ These rules enforce best practices and naming conventions:
4848

4949
|Rule|Description|Enabled by default|
5050
| --- | --- | --- |
51+
|[aws_acm_certificate_lifecycle](aws_acm_certificate_lifecycle.md)|Disallow adding `aws_acm_certificate` resource without setting `create_before_destroy = true` in `lifecycle` block |✔|
5152
|[aws_db_instance_previous_type](aws_db_instance_previous_type.md)|Disallow using previous generation instance types|✔|
5253
|[aws_db_instance_default_parameter_group](aws_db_instance_default_parameter_group.md)|Disallow using default DB parameter group|✔|
5354
|[aws_elasticache_cluster_previous_type](aws_elasticache_cluster_previous_type.md)|Disallow using previous node types|✔|
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# aws_acm_certificate_lifecycle
2+
3+
This rule ensures lifecycle [`create_before_destroy`](https://www.terraform.io/docs/language/meta-arguments/lifecycle.html#create_before_destroy)
4+
argument is set to `true` for acm certificates as per
5+
[`aws_acm_certificate`](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate)
6+
official docs:
7+
8+
> It's recommended to specify `create_before_destroy = true` in a
9+
> lifecycle block to replace a certificate which is currently in use
10+
11+
12+
## Example
13+
14+
```hcl
15+
resource "aws_acm_certificate" "test" {
16+
domain_name = var.domain_name
17+
validation_method = "DNS"
18+
}
19+
```
20+
21+
```console
22+
$ tflint
23+
1 issue(s) found:
24+
25+
Warning: resource `aws_acm_certificate` needs to contain `create_before_destroy = true` in `lifecycle` block (aws_acm_certificate_lifecycle)
26+
27+
on test.tf line 1:
28+
1: resource "aws_acm_certificate" "test" {
29+
```
30+
31+
## Why
32+
33+
If you don't add this, terraform will timeout when trying to replace a certificate
34+
that's in use at the moment of apply.
35+
36+
## How To Fix
37+
38+
```hcl
39+
resource "aws_acm_certificate" "test" {
40+
domain_name = local.assets_domain_name
41+
validation_method = "DNS"
42+
43+
lifecycle {
44+
create_before_destroy = true
45+
}
46+
}
47+
```
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package rules
2+
3+
import (
4+
"github.com/terraform-linters/tflint-plugin-sdk/terraform/configs"
5+
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
6+
"github.com/terraform-linters/tflint-ruleset-aws/project"
7+
)
8+
9+
// AwsAcmCertificateLifecycleRule checks whether `create_before_destroy = true` is set in a lifecycle block of
10+
// `aws_acm_certificate` resource
11+
type AwsAcmCertificateLifecycleRule struct {
12+
resourceType string
13+
attributeName string
14+
}
15+
16+
// NewAwsAcmCertificateLifecycleRule returns new rule with default attributes
17+
func NewAwsAcmCertificateLifecycleRule() *AwsAcmCertificateLifecycleRule {
18+
return &AwsAcmCertificateLifecycleRule{
19+
resourceType: "aws_acm_certificate",
20+
attributeName: "lifecycle",
21+
}
22+
}
23+
24+
// Name returns the rule name
25+
func (r *AwsAcmCertificateLifecycleRule) Name() string {
26+
return "aws_acm_certificate_lifecycle"
27+
}
28+
29+
// Enabled returns whether the rule is enabled by default
30+
func (r *AwsAcmCertificateLifecycleRule) Enabled() bool {
31+
return true
32+
}
33+
34+
// Severity returns the rule severity
35+
func (r *AwsAcmCertificateLifecycleRule) Severity() string {
36+
return tflint.WARNING
37+
}
38+
39+
// Link returns the rule reference link
40+
func (r *AwsAcmCertificateLifecycleRule) Link() string {
41+
return project.ReferenceLink(r.Name())
42+
}
43+
44+
// Check checks whether the aws_acm_certificate resource contains create_before_destroy = true in lifecycle block
45+
func (r *AwsAcmCertificateLifecycleRule) Check(runner tflint.Runner) error {
46+
return runner.WalkResources("aws_acm_certificate", func(resource *configs.Resource) error {
47+
if !resource.Managed.CreateBeforeDestroy {
48+
if err := runner.EmitIssue(r, "resource `aws_acm_certificate` needs to contain `create_before_destroy = true` in `lifecycle` block", resource.DeclRange); err != nil {
49+
return err
50+
}
51+
}
52+
return nil
53+
})
54+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package rules
2+
3+
import (
4+
"testing"
5+
6+
hcl "github.com/hashicorp/hcl/v2"
7+
"github.com/terraform-linters/tflint-plugin-sdk/helper"
8+
)
9+
10+
func Test_AwsAcmCertificateLifecycle(t *testing.T) {
11+
cases := []struct {
12+
Name string
13+
Content string
14+
Expected helper.Issues
15+
}{
16+
{
17+
Name: "no lifecycle block",
18+
Content: `
19+
resource "aws_acm_certificate" "test" {
20+
domain_name = var.domain_name
21+
validation_method = "DNS"
22+
}`,
23+
Expected: helper.Issues{
24+
{
25+
Rule: NewAwsAcmCertificateLifecycleRule(),
26+
Message: "resource `aws_acm_certificate` needs to contain `create_before_destroy = true` in `lifecycle` block",
27+
Range: hcl.Range{
28+
Filename: "resource.tf",
29+
Start: hcl.Pos{Line: 2, Column: 1},
30+
End: hcl.Pos{Line: 2, Column: 38},
31+
},
32+
},
33+
},
34+
},
35+
{
36+
Name: "no create_before_destroy attribute in lifecycle block",
37+
Content: `
38+
resource "aws_acm_certificate" "test" {
39+
domain_name = var.domain_name
40+
validation_method = "DNS"
41+
lifecycle {}
42+
}`,
43+
Expected: helper.Issues{
44+
{
45+
Rule: NewAwsAcmCertificateLifecycleRule(),
46+
Message: "resource `aws_acm_certificate` needs to contain `create_before_destroy = true` in `lifecycle` block",
47+
Range: hcl.Range{
48+
Filename: "resource.tf",
49+
Start: hcl.Pos{Line: 2, Column: 1},
50+
End: hcl.Pos{Line: 2, Column: 38},
51+
},
52+
},
53+
},
54+
},
55+
{
56+
Name: "create_before_destroy = false",
57+
Content: `
58+
resource "aws_acm_certificate" "test" {
59+
domain_name = var.domain_name
60+
validation_method = "DNS"
61+
lifecycle {
62+
create_before_destroy = true
63+
}
64+
}`,
65+
Expected: helper.Issues{},
66+
},
67+
}
68+
69+
rule := NewAwsAcmCertificateLifecycleRule()
70+
71+
for _, tc := range cases {
72+
runner := helper.TestRunner(t, map[string]string{"resource.tf": tc.Content})
73+
74+
if err := rule.Check(runner); err != nil {
75+
t.Fatalf("Unexpected error occurred: %s", err)
76+
}
77+
78+
helper.AssertIssues(t, tc.Expected, runner.Issues)
79+
}
80+
}

rules/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ var Rules = append([]tflint.Rule{
3636
NewAwsIAMPolicyTooLongPolicyRule(),
3737
NewAwsLambdaFunctionDeprecatedRuntimeRule(),
3838
NewAwsIAMGroupPolicyTooLongRule(),
39+
NewAwsAcmCertificateLifecycleRule(),
3940
}, models.Rules...)

0 commit comments

Comments
 (0)