Skip to content

Commit 7193646

Browse files
smangelsplejd-sebmanmilldr
authored
add support for S3 object based TF state lock (#192)
* add support for S3 object based TF state lock * atmos docs generate readme * atmos docs generate readme * resolved tflint warnings * resolved tflint warnings --------- Co-authored-by: Sebastian Mangelsen <[email protected]> Co-authored-by: milldr <[email protected]>
1 parent 66c57b5 commit 7193646

File tree

6 files changed

+59
-161
lines changed

6 files changed

+59
-161
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ terraform-aws-tfstate-backend.iml
1010

1111
.build-harness
1212
build-harness
13+
14+
.atmos/cache.yaml

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ module "terraform_state_backend" {
233233
| [aws_s3_bucket.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource |
234234
| [aws_s3_bucket_acl.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource |
235235
| [aws_s3_bucket_logging.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource |
236+
| [aws_s3_bucket_object_lock_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_object_lock_configuration) | resource |
236237
| [aws_s3_bucket_ownership_controls.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource |
237238
| [aws_s3_bucket_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource |
238239
| [aws_s3_bucket_public_access_block.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource |
@@ -292,6 +293,7 @@ module "terraform_state_backend" {
292293
| <a name="input_s3_bucket_name"></a> [s3\_bucket\_name](#input\_s3\_bucket\_name) | S3 bucket name. If not provided, the name will be generated from the context by the label module. | `string` | `""` | no |
293294
| <a name="input_s3_replica_bucket_arn"></a> [s3\_replica\_bucket\_arn](#input\_s3\_replica\_bucket\_arn) | The ARN of the S3 replica bucket (destination) | `string` | `""` | no |
294295
| <a name="input_s3_replication_enabled"></a> [s3\_replication\_enabled](#input\_s3\_replication\_enabled) | Set this to true and specify `s3_replica_bucket_arn` to enable replication | `bool` | `false` | no |
296+
| <a name="input_s3_state_lock_enabled"></a> [s3\_state\_lock\_enabled](#input\_s3\_state\_lock\_enabled) | Whether to create the S3 bucket. | `bool` | `false` | no |
295297
| <a name="input_source_policy_documents"></a> [source\_policy\_documents](#input\_source\_policy\_documents) | List of IAM policy documents (in JSON format) that are merged together into the generated S3 bucket policy.<br/>Statements must have unique SIDs.<br/>Statement having SIDs that match policy SIDs generated by this module will override them. | `list(string)` | `[]` | no |
296298
| <a name="input_sse_encryption"></a> [sse\_encryption](#input\_sse\_encryption) | The server-side encryption algorithm to use.<br/>Valid values are `AES256`, `aws:kms`, and `aws:kms:dsse`. | `string` | `"AES256"` | no |
297299
| <a name="input_stage"></a> [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no |
@@ -301,7 +303,7 @@ module "terraform_state_backend" {
301303
| <a name="input_terraform_backend_config_file_path"></a> [terraform\_backend\_config\_file\_path](#input\_terraform\_backend\_config\_file\_path) | (Deprecated) Directory for the terraform backend config file, usually `.`. The default is to create no file. | `string` | `""` | no |
302304
| <a name="input_terraform_backend_config_template_file"></a> [terraform\_backend\_config\_template\_file](#input\_terraform\_backend\_config\_template\_file) | (Deprecated) The path to the template used to generate the config file | `string` | `""` | no |
303305
| <a name="input_terraform_state_file"></a> [terraform\_state\_file](#input\_terraform\_state\_file) | The path to the state file inside the bucket | `string` | `"terraform.tfstate"` | no |
304-
| <a name="input_terraform_version"></a> [terraform\_version](#input\_terraform\_version) | The minimum required terraform version | `string` | `"1.0.0"` | no |
306+
| <a name="input_terraform_version"></a> [terraform\_version](#input\_terraform\_version) | The minimum required terraform version | `string` | `null` | no |
305307
| <a name="input_write_capacity"></a> [write\_capacity](#input\_write\_capacity) | DynamoDB write capacity units when using provisioned mode | `number` | `5` | no |
306308

307309
## Outputs

examples/complete/variables.tf

Lines changed: 1 addition & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,160 +1,6 @@
11
variable "region" {
2-
type = string
3-
}
4-
5-
variable "arn_format" {
6-
type = string
7-
default = "arn:aws"
8-
description = "ARN format to be used. May be changed to support deployment in GovCloud/China regions."
9-
}
10-
11-
variable "acl" {
12-
type = string
13-
description = "The canned ACL to apply to the S3 bucket"
14-
default = "private"
15-
}
16-
17-
variable "billing_mode" {
18-
default = "PROVISIONED"
19-
description = "DynamoDB billing mode"
20-
}
21-
22-
variable "read_capacity" {
23-
default = 5
24-
description = "DynamoDB read capacity units"
25-
}
26-
27-
variable "write_capacity" {
28-
default = 5
29-
description = "DynamoDB write capacity units"
30-
}
31-
32-
variable "force_destroy" {
33-
type = bool
34-
description = "A boolean that indicates the S3 bucket can be destroyed even if it contains objects. These objects are not recoverable"
35-
default = false
36-
}
37-
38-
variable "mfa_delete" {
39-
type = bool
40-
description = "A boolean that indicates that versions of S3 objects can only be deleted with MFA. ( Terraform cannot apply changes of this value; https://github.com/terraform-providers/terraform-provider-aws/issues/629 )"
41-
default = false
42-
}
43-
44-
variable "enable_point_in_time_recovery" {
45-
type = bool
46-
description = "Enable DynamoDB point-in-time recovery"
47-
default = true
48-
}
49-
50-
variable "enable_server_side_encryption" {
51-
type = bool
52-
description = "Enable DynamoDB server-side encryption"
53-
default = true
54-
}
55-
56-
variable "enable_public_access_block" {
57-
type = bool
58-
description = "Enable Bucket Public Access Block"
59-
default = true
60-
}
61-
62-
variable "block_public_acls" {
63-
type = bool
64-
description = "Whether Amazon S3 should block public ACLs for this bucket"
65-
default = true
66-
}
67-
68-
variable "ignore_public_acls" {
69-
type = bool
70-
description = "Whether Amazon S3 should ignore public ACLs for this bucket"
71-
default = true
72-
}
73-
74-
variable "block_public_policy" {
75-
description = "Whether Amazon S3 should block public bucket policies for this bucket"
76-
default = true
77-
}
78-
79-
variable "restrict_public_buckets" {
80-
type = bool
81-
description = "Whether Amazon S3 should restrict public bucket policies for this bucket"
82-
default = true
83-
}
84-
85-
variable "prevent_unencrypted_uploads" {
86-
type = bool
87-
default = true
88-
description = "Prevent uploads of unencrypted objects to S3"
89-
}
90-
91-
variable "profile" {
92-
type = string
93-
default = ""
94-
description = "AWS profile name as set in the shared credentials file"
95-
}
96-
97-
variable "role_arn" {
98-
type = string
99-
default = ""
100-
description = "The role to be assumed"
101-
}
102-
103-
variable "terraform_backend_config_file_name" {
104-
type = string
105-
default = "terraform.tf"
106-
description = "Name of terraform backend config file"
107-
}
108-
109-
variable "terraform_backend_config_file_path" {
1102
type = string
111-
default = ""
112-
description = "Directory for the terraform backend config file, usually `.`. The default is to create no file."
113-
}
114-
115-
variable "terraform_backend_config_template_file" {
116-
type = string
117-
default = ""
118-
description = "The path to the template used to generate the config file"
119-
}
120-
121-
variable "terraform_version" {
122-
type = string
123-
default = "0.12.2"
124-
description = "The minimum required terraform version"
125-
}
126-
127-
variable "terraform_state_file" {
128-
type = string
129-
default = "terraform.tfstate"
130-
description = "The path to the state file inside the bucket"
131-
}
132-
133-
variable "s3_bucket_name" {
134-
type = string
135-
default = ""
136-
description = "S3 bucket name. If not provided, the name will be generated by the label module in the format namespace-stage-name"
137-
}
138-
139-
variable "s3_replication_enabled" {
140-
type = bool
141-
default = false
142-
description = "Set this to true and specify `s3_replica_bucket_arn` to enable replication"
143-
}
144-
145-
variable "s3_replica_bucket_arn" {
146-
type = string
147-
default = ""
148-
description = "The ARN of the S3 replica bucket (destination)"
149-
}
150-
151-
variable "logging" {
152-
type = object({
153-
bucket_name = string
154-
prefix = string
155-
})
156-
default = null
157-
description = "Bucket access logging configuration."
3+
description = "AWS region"
1584
}
1595

1606
variable "bucket_enabled" {

main.tf

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
locals {
22
enabled = module.this.enabled
33

4-
bucket_enabled = local.enabled && var.bucket_enabled
5-
dynamodb_enabled = local.enabled && var.dynamodb_enabled
4+
bucket_enabled = local.enabled && var.bucket_enabled
5+
s3_state_lock_enabled = local.enabled && var.s3_state_lock_enabled
6+
dynamodb_enabled = local.enabled && var.dynamodb_enabled
67

78
dynamodb_table_name = local.dynamodb_enabled ? coalesce(var.dynamodb_table_name, module.dynamodb_table_label.id) : ""
89

@@ -16,19 +17,23 @@ locals {
1617
var.terraform_backend_config_file_name
1718
)
1819

19-
terraform_backend_config_template_file = var.terraform_backend_config_template_file != "" ? var.terraform_backend_config_template_file : "${path.module}/templates/terraform.tf.tpl"
20+
terraform_backend_default_template_file = local.s3_state_lock_enabled ? "${path.module}/templates/terraform-s3-lock.tf.tpl" : "${path.module}/templates/terraform.tf.tpl"
21+
terraform_version_minimum = local.s3_state_lock_enabled ? "1.11.0" : "1.0.0"
22+
terraform_version_requested = var.terraform_version != null ? var.terraform_version : local.terraform_version_minimum
23+
terraform_backend_config_template_file = var.terraform_backend_config_template_file != "" ? var.terraform_backend_config_template_file : local.terraform_backend_default_template_file
2024

2125
terraform_backend_config_content = templatefile(local.terraform_backend_config_template_file, {
2226
region = data.aws_region.current.name
2327
# Template file inputs cannot be null, so we use empty string if the variable is null
2428
bucket = try(aws_s3_bucket.default[0].id, "")
2529

2630
dynamodb_table = try(aws_dynamodb_table.with_server_side_encryption[0].name, "")
31+
use_lockfile = local.s3_state_lock_enabled
2732

2833
encrypt = "true"
2934
role_arn = var.role_arn == null ? "" : var.role_arn
3035
profile = var.profile == null ? "" : var.profile
31-
terraform_version = var.terraform_version == null ? "" : var.terraform_version
36+
terraform_version = local.terraform_version_requested
3237
terraform_state_file = var.terraform_state_file == null ? "" : var.terraform_state_file
3338
namespace = var.namespace == null ? "" : var.namespace
3439
stage = var.stage == null ? "" : var.stage
@@ -166,6 +171,19 @@ resource "aws_s3_bucket" "default" {
166171
tags = module.this.tags
167172
}
168173

174+
resource "aws_s3_bucket_object_lock_configuration" "default" {
175+
count = local.s3_state_lock_enabled ? 1 : 0
176+
177+
bucket = one(aws_s3_bucket.default[*].id)
178+
179+
rule {
180+
default_retention {
181+
mode = "GOVERNANCE"
182+
days = 7
183+
}
184+
}
185+
}
186+
169187
resource "aws_s3_bucket_policy" "default" {
170188
count = local.bucket_enabled ? 1 : 0
171189

templates/terraform-s3-lock.tf.tpl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
terraform {
2+
required_version = ">= ${terraform_version}"
3+
4+
backend "s3" {
5+
region = "${region}"
6+
bucket = "${bucket}"
7+
key = "${terraform_state_file}"
8+
profile = "${profile}"
9+
encrypt = "${encrypt}"
10+
%{~ if role_arn != "" ~}
11+
12+
assume_role {
13+
role_arn = "${role_arn}"
14+
}
15+
%{~ endif ~}
16+
%{~ if ! use_lockfile ~}
17+
18+
dynamodb_table = "${dynamodb_table}"
19+
%{~ else ~}
20+
21+
use_lockfile = "${use_lockfile}"
22+
%{~ endif ~}
23+
}
24+
}

variables.tf

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ variable "terraform_backend_config_template_file" {
127127

128128
variable "terraform_version" {
129129
type = string
130-
default = "1.0.0"
130+
default = null
131131
description = "The minimum required terraform version"
132132
}
133133

@@ -179,6 +179,12 @@ variable "bucket_enabled" {
179179
description = "Whether to create the S3 bucket."
180180
}
181181

182+
variable "s3_state_lock_enabled" {
183+
type = bool
184+
default = false
185+
description = "Whether to create the S3 bucket."
186+
}
187+
182188
variable "dynamodb_enabled" {
183189
type = bool
184190
default = true

0 commit comments

Comments
 (0)