Skip to content

Commit 6395872

Browse files
authored
Initial implementation (#1)
* Init * Add S3 and DynamoDB config * Update `README` * Hardcode versioning and lifecycle rules for S3 bucket * Update DynamoDB autoscaling settings * Add `encrypt` to terraform backend * Hardcode DynamoDB `hash_key` to `LockID`, required by Terraform * Update DynamoDB autoscaling settings * Restructure modules * Restructure modules * Change `name` and `attributes` * Add bootstrapping note to `README` * Update `README` * Update `README` * Update `README` * Update `README` * Update `README` * Update `README` * Update `README` * Update `README` * Update `README` * Update `README`
1 parent 50cc5f2 commit 6395872

9 files changed

+340
-2
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@
44

55
# Module directory
66
.terraform/
7+
.idea
8+
terraform-aws-tfstate-backend.iml

.travis.yml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
addons:
2+
apt:
3+
packages:
4+
- git
5+
- make
6+
- curl
7+
8+
install:
9+
- make init
10+
11+
script:
12+
- make terraform/install
13+
- make terraform/get-plugins
14+
- make terraform/get-modules
15+
- make terraform/lint
16+
- make terraform/validate

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186
same "printed page" as the copyright notice for easier
187187
identification within third-party archives.
188188

189-
Copyright [yyyy] [name of copyright owner]
189+
Copyright 2018 Cloud Posse, LLC
190190

191191
Licensed under the Apache License, Version 2.0 (the "License");
192192
you may not use this file except in compliance with the License.

Makefile

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
SHELL := /bin/bash
2+
3+
-include $(shell curl -sSL -o .build-harness "https://git.io/build-harness"; echo .build-harness)
4+
5+
lint:
6+
$(SELF) terraform/install terraform/get-modules terraform/get-plugins terraform/lint terraform/validate

README.md

+175-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,175 @@
1-
# terraform-aws-state-backend
1+
# terraform-aws-tfstate-backend [![Build Status](https://travis-ci.org/cloudposse/terraform-aws-tfstate-backend.svg?branch=master)](https://travis-ci.org/cloudposse/terraform-aws-tfstate-backend)
2+
3+
Terraform module to provision an S3 bucket to store `terraform.tfstate` file and a DynamoDB table to lock the state file
4+
to prevent concurrent modifications and state corruption.
5+
6+
The module supports the following:
7+
8+
1. Forced server-side encryption at rest for the S3 bucket
9+
2. S3 bucket versioning to allow for Terraform state recovery in the case of accidental deletions and human errors
10+
3. State locking and consistency checking via DynamoDB table to prevent concurrent operations
11+
4. DynamoDB server-side encryption
12+
13+
https://www.terraform.io/docs/backends/types/s3.html
14+
15+
16+
__NOTE:__ The operators of the module (IAM Users) must have permissions to create S3 buckets and DynamoDB tables when performing `terraform plan` and `terraform apply`
17+
18+
19+
## Usage
20+
21+
```hcl
22+
terraform {
23+
required_version = ">= 0.11.3"
24+
}
25+
26+
module "terraform_state_backend" {
27+
source = "git::https://github.com/cloudposse/terraform-aws-tfstate-backend.git?ref=master"
28+
namespace = "cp"
29+
stage = "dev"
30+
name = "app"
31+
region = "us-east-1"
32+
}
33+
```
34+
35+
__NOTE:__ First create the bucket and table without any state enabled (Terraform will use the local file system to store state).
36+
You can then import the bucket and table by using [`terraform import`](https://www.terraform.io/docs/import/index.html) and store the state file into the bucket.
37+
38+
Once the bucket and table have been created, configure the [backend](https://www.terraform.io/docs/backends/types/s3.html)
39+
40+
```hcl
41+
terraform {
42+
required_version = ">= 0.11.3"
43+
44+
backend "s3" {
45+
region = "us-east-1"
46+
bucket = "< the name of the S3 bucket >"
47+
key = "terraform.tfstate"
48+
dynamodb_table = "< the name of the DynamoDB table >"
49+
encrypt = true
50+
}
51+
}
52+
53+
module "another_module" {
54+
source = "....."
55+
}
56+
```
57+
58+
Initialize the backend with `terraform init`.
59+
60+
After `terraform apply`, `terraform.tfstate` file will be stored in the bucket,
61+
and the DynamoDB table will be used to lock the state to prevent concurrent modifications.
62+
63+
<br/>
64+
65+
![s3-bucket-with-terraform-state](images/s3-bucket-with-terraform-state.png)
66+
67+
68+
## Variables
69+
70+
| Name | Default | Description | Required |
71+
|:-------------------------|:-------------|:----------------------------------------------------------------------------------|:--------:|
72+
| `namespace` | `` | Namespace (_e.g._ `cp` or `cloudposse`) | Yes |
73+
| `stage` | `` | Stage (_e.g._ `prod`, `dev`, `staging`) | Yes |
74+
| `region` | `us-east-1` | AWS Region the S3 bucket should reside in | Yes |
75+
| `name` | `terraform` | Name (_e.g._ `app`, `cluster`, or `terraform`) | No |
76+
| `attributes` | `["state"]` | Additional attributes (_e.g._ `policy` or `role`) | No |
77+
| `tags` | `{}` | Additional tags (_e.g._ `map("BusinessUnit","XYZ")` | No |
78+
| `delimiter` | `-` | Delimiter to be used between `namespace`, `stage`, `name`, and `attributes` | No |
79+
| `acl` | `private` | The canned ACL to apply to the S3 bucket | No |
80+
| `read_capacity` | `5` | DynamoDB read capacity units | No |
81+
| `write_capacity` | `5` | DynamoDB write capacity units | No |
82+
83+
84+
## Outputs
85+
86+
| Name | Description |
87+
|:-------------------------|:-----------------------------|
88+
| `s3_bucket_domain_name` | S3 bucket domain name |
89+
| `s3_bucket_id` | S3 bucket ID |
90+
| `s3_bucket_arn` | S3 bucket ARN |
91+
| `dynamodb_table_id` | DynamoDB table ID |
92+
| `dynamodb_table_arn` | DynamoDB table ARN |
93+
| `dynamodb_table_name` | DynamoDB table name |
94+
95+
96+
## Help
97+
98+
**Got a question?**
99+
100+
File a GitHub [issue](https://github.com/cloudposse/terraform-aws-tfstate-backend/issues), send us an [email](mailto:[email protected]) or reach out to us on [Gitter](https://gitter.im/cloudposse/).
101+
102+
103+
## Contributing
104+
105+
### Bug Reports & Feature Requests
106+
107+
Please use the [issue tracker](https://github.com/cloudposse/terraform-aws-tfstate-backend/issues) to report any bugs or file feature requests.
108+
109+
### Developing
110+
111+
If you are interested in being a contributor and want to get involved in developing `terraform-aws-tfstate-backend`, we would love to hear from you! Shoot us an [email](mailto:[email protected]).
112+
113+
In general, PRs are welcome. We follow the typical "fork-and-pull" Git workflow.
114+
115+
1. **Fork** the repo on GitHub
116+
2. **Clone** the project to your own machine
117+
3. **Commit** changes to your own branch
118+
4. **Push** your work back up to your fork
119+
5. Submit a **Pull request** so that we can review your changes
120+
121+
**NOTE:** Be sure to merge the latest from "upstream" before making a pull request!
122+
123+
124+
## License
125+
126+
[APACHE 2.0](LICENSE) © 2018 [Cloud Posse, LLC](https://cloudposse.com)
127+
128+
See [LICENSE](LICENSE) for full details.
129+
130+
Licensed to the Apache Software Foundation (ASF) under one
131+
or more contributor license agreements. See the NOTICE file
132+
distributed with this work for additional information
133+
regarding copyright ownership. The ASF licenses this file
134+
to you under the Apache License, Version 2.0 (the
135+
"License"); you may not use this file except in compliance
136+
with the License. You may obtain a copy of the License at
137+
138+
http://www.apache.org/licenses/LICENSE-2.0
139+
140+
Unless required by applicable law or agreed to in writing,
141+
software distributed under the License is distributed on an
142+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
143+
KIND, either express or implied. See the License for the
144+
specific language governing permissions and limitations
145+
under the License.
146+
147+
148+
## About
149+
150+
`terraform-aws-tfstate-backend` is maintained and funded by [Cloud Posse, LLC][website].
151+
152+
![Cloud Posse](https://cloudposse.com/logo-300x69.png)
153+
154+
155+
Like it? Please let us know at <[email protected]>
156+
157+
We love [Open Source Software](https://github.com/cloudposse/)!
158+
159+
See [our other projects][community]
160+
or [hire us][hire] to help build your next cloud platform.
161+
162+
[website]: https://cloudposse.com/
163+
[community]: https://github.com/cloudposse/
164+
[hire]: https://cloudposse.com/contact/
165+
166+
167+
### Contributors
168+
169+
| [![Erik Osterman][erik_img]][erik_web]<br/>[Erik Osterman][erik_web] | [![Andriy Knysh][andriy_img]][andriy_web]<br/>[Andriy Knysh][andriy_web] |
170+
|-------------------------------------------------------|------------------------------------------------------------------|
171+
172+
[erik_img]: http://s.gravatar.com/avatar/88c480d4f73b813904e00a5695a454cb?s=144
173+
[erik_web]: https://github.com/osterman/
174+
[andriy_img]: https://avatars0.githubusercontent.com/u/7356997?v=4&u=ed9ce1c9151d552d985bdf5546772e14ef7ab617&s=144
175+
[andriy_web]: https://github.com/aknysh/
147 KB
Loading

main.tf

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
module "s3_bucket_label" {
2+
source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.3.3"
3+
namespace = "${var.namespace}"
4+
stage = "${var.stage}"
5+
name = "${var.name}"
6+
delimiter = "${var.delimiter}"
7+
attributes = "${var.attributes}"
8+
tags = "${var.tags}"
9+
}
10+
11+
resource "aws_s3_bucket" "default" {
12+
bucket = "${module.s3_bucket_label.id}"
13+
acl = "${var.acl}"
14+
region = "${var.region}"
15+
force_destroy = false
16+
17+
versioning {
18+
enabled = true
19+
}
20+
21+
server_side_encryption_configuration {
22+
rule {
23+
apply_server_side_encryption_by_default {
24+
sse_algorithm = "AES256"
25+
}
26+
}
27+
}
28+
29+
tags = "${module.s3_bucket_label.tags}"
30+
}
31+
32+
module "dynamodb_table_label" {
33+
source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.3.3"
34+
namespace = "${var.namespace}"
35+
stage = "${var.stage}"
36+
name = "${var.name}"
37+
delimiter = "${var.delimiter}"
38+
attributes = ["${compact(concat(var.attributes, list("lock")))}"]
39+
tags = "${var.tags}"
40+
}
41+
42+
resource "aws_dynamodb_table" "default" {
43+
name = "${module.dynamodb_table_label.id}"
44+
read_capacity = "${var.read_capacity}"
45+
write_capacity = "${var.write_capacity}"
46+
hash_key = "LockID" # https://www.terraform.io/docs/backends/types/s3.html#dynamodb_table
47+
48+
server_side_encryption {
49+
enabled = true
50+
}
51+
52+
lifecycle {
53+
ignore_changes = ["read_capacity", "write_capacity"]
54+
}
55+
56+
attribute {
57+
name = "LockID"
58+
type = "S"
59+
}
60+
61+
tags = "${module.dynamodb_table_label.tags}"
62+
}

output.tf

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
output "s3_bucket_domain_name" {
2+
value = "${aws_s3_bucket.default.bucket_domain_name}"
3+
}
4+
5+
output "s3_bucket_id" {
6+
value = "${aws_s3_bucket.default.id}"
7+
}
8+
9+
output "s3_bucket_arn" {
10+
value = "${aws_s3_bucket.default.arn}"
11+
}
12+
13+
output "dynamodb_table_name" {
14+
value = "${aws_dynamodb_table.default.name}"
15+
}
16+
17+
output "dynamodb_table_id" {
18+
value = "${aws_dynamodb_table.default.id}"
19+
}
20+
21+
output "dynamodb_table_arn" {
22+
value = "${aws_dynamodb_table.default.arn}"
23+
}

variables.tf

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
variable "namespace" {
2+
type = "string"
3+
description = "Namespace (e.g. `cp` or `cloudposse`)"
4+
}
5+
6+
variable "stage" {
7+
type = "string"
8+
description = "Stage (e.g. `prod`, `dev`, `staging`, `infra`)"
9+
}
10+
11+
variable "name" {
12+
type = "string"
13+
default = "terraform"
14+
description = "Name (e.g. `app` or `cluster`)"
15+
}
16+
17+
variable "delimiter" {
18+
type = "string"
19+
default = "-"
20+
description = "Delimiter to be used between `namespace`, `stage`, `name`, and `attributes`"
21+
}
22+
23+
variable "attributes" {
24+
type = "list"
25+
default = ["state"]
26+
description = "Additional attributes (e.g. `policy` or `role`)"
27+
}
28+
29+
variable "tags" {
30+
type = "map"
31+
default = {}
32+
description = "Additional tags (e.g. map('BusinessUnit`,`XYZ`)"
33+
}
34+
35+
variable "region" {
36+
type = "string"
37+
description = "AWS Region the S3 bucket should reside in"
38+
default = "us-east-1"
39+
}
40+
41+
variable "acl" {
42+
type = "string"
43+
description = "The canned ACL to apply to the S3 bucket"
44+
default = "private"
45+
}
46+
47+
variable "read_capacity" {
48+
default = 5
49+
description = "DynamoDB read capacity units"
50+
}
51+
52+
variable "write_capacity" {
53+
default = 5
54+
description = "DynamoDB write capacity units"
55+
}

0 commit comments

Comments
 (0)