diff --git a/.gitignore b/.gitignore
index 41859c8..c5a6503 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@
# Module directory
.terraform/
+.idea
+terraform-aws-tfstate-backend.iml
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..241026e
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,16 @@
+addons:
+ apt:
+ packages:
+ - git
+ - make
+ - curl
+
+install:
+ - make init
+
+script:
+ - make terraform/install
+ - make terraform/get-plugins
+ - make terraform/get-modules
+ - make terraform/lint
+ - make terraform/validate
diff --git a/LICENSE b/LICENSE
index 261eeb9..c37833f 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright [yyyy] [name of copyright owner]
+ Copyright 2018 Cloud Posse, LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..d002c7d
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,6 @@
+SHELL := /bin/bash
+
+-include $(shell curl -sSL -o .build-harness "https://git.io/build-harness"; echo .build-harness)
+
+lint:
+ $(SELF) terraform/install terraform/get-modules terraform/get-plugins terraform/lint terraform/validate
diff --git a/README.md b/README.md
index 9e11c15..e069b99 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,175 @@
-# terraform-aws-state-backend
\ No newline at end of file
+# 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)
+
+Terraform module to provision an S3 bucket to store `terraform.tfstate` file and a DynamoDB table to lock the state file
+to prevent concurrent modifications and state corruption.
+
+The module supports the following:
+
+1. Forced server-side encryption at rest for the S3 bucket
+2. S3 bucket versioning to allow for Terraform state recovery in the case of accidental deletions and human errors
+3. State locking and consistency checking via DynamoDB table to prevent concurrent operations
+4. DynamoDB server-side encryption
+
+https://www.terraform.io/docs/backends/types/s3.html
+
+
+__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`
+
+
+## Usage
+
+```hcl
+terraform {
+ required_version = ">= 0.11.3"
+}
+
+module "terraform_state_backend" {
+ source = "git::https://github.com/cloudposse/terraform-aws-tfstate-backend.git?ref=master"
+ namespace = "cp"
+ stage = "dev"
+ name = "app"
+ region = "us-east-1"
+}
+```
+
+__NOTE:__ First create the bucket and table without any state enabled (Terraform will use the local file system to store state).
+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.
+
+Once the bucket and table have been created, configure the [backend](https://www.terraform.io/docs/backends/types/s3.html)
+
+```hcl
+terraform {
+ required_version = ">= 0.11.3"
+
+ backend "s3" {
+ region = "us-east-1"
+ bucket = "< the name of the S3 bucket >"
+ key = "terraform.tfstate"
+ dynamodb_table = "< the name of the DynamoDB table >"
+ encrypt = true
+ }
+}
+
+module "another_module" {
+ source = "....."
+}
+```
+
+Initialize the backend with `terraform init`.
+
+After `terraform apply`, `terraform.tfstate` file will be stored in the bucket,
+and the DynamoDB table will be used to lock the state to prevent concurrent modifications.
+
+
+
+![s3-bucket-with-terraform-state](images/s3-bucket-with-terraform-state.png)
+
+
+## Variables
+
+| Name | Default | Description | Required |
+|:-------------------------|:-------------|:----------------------------------------------------------------------------------|:--------:|
+| `namespace` | `` | Namespace (_e.g._ `cp` or `cloudposse`) | Yes |
+| `stage` | `` | Stage (_e.g._ `prod`, `dev`, `staging`) | Yes |
+| `region` | `us-east-1` | AWS Region the S3 bucket should reside in | Yes |
+| `name` | `terraform` | Name (_e.g._ `app`, `cluster`, or `terraform`) | No |
+| `attributes` | `["state"]` | Additional attributes (_e.g._ `policy` or `role`) | No |
+| `tags` | `{}` | Additional tags (_e.g._ `map("BusinessUnit","XYZ")` | No |
+| `delimiter` | `-` | Delimiter to be used between `namespace`, `stage`, `name`, and `attributes` | No |
+| `acl` | `private` | The canned ACL to apply to the S3 bucket | No |
+| `read_capacity` | `5` | DynamoDB read capacity units | No |
+| `write_capacity` | `5` | DynamoDB write capacity units | No |
+
+
+## Outputs
+
+| Name | Description |
+|:-------------------------|:-----------------------------|
+| `s3_bucket_domain_name` | S3 bucket domain name |
+| `s3_bucket_id` | S3 bucket ID |
+| `s3_bucket_arn` | S3 bucket ARN |
+| `dynamodb_table_id` | DynamoDB table ID |
+| `dynamodb_table_arn` | DynamoDB table ARN |
+| `dynamodb_table_name` | DynamoDB table name |
+
+
+## Help
+
+**Got a question?**
+
+File a GitHub [issue](https://github.com/cloudposse/terraform-aws-tfstate-backend/issues), send us an [email](mailto:hello@cloudposse.com) or reach out to us on [Gitter](https://gitter.im/cloudposse/).
+
+
+## Contributing
+
+### Bug Reports & Feature Requests
+
+Please use the [issue tracker](https://github.com/cloudposse/terraform-aws-tfstate-backend/issues) to report any bugs or file feature requests.
+
+### Developing
+
+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:hello@cloudposse.com).
+
+In general, PRs are welcome. We follow the typical "fork-and-pull" Git workflow.
+
+ 1. **Fork** the repo on GitHub
+ 2. **Clone** the project to your own machine
+ 3. **Commit** changes to your own branch
+ 4. **Push** your work back up to your fork
+ 5. Submit a **Pull request** so that we can review your changes
+
+**NOTE:** Be sure to merge the latest from "upstream" before making a pull request!
+
+
+## License
+
+[APACHE 2.0](LICENSE) © 2018 [Cloud Posse, LLC](https://cloudposse.com)
+
+See [LICENSE](LICENSE) for full details.
+
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+
+
+## About
+
+`terraform-aws-tfstate-backend` is maintained and funded by [Cloud Posse, LLC][website].
+
+![Cloud Posse](https://cloudposse.com/logo-300x69.png)
+
+
+Like it? Please let us know at
+
+We love [Open Source Software](https://github.com/cloudposse/)!
+
+See [our other projects][community]
+or [hire us][hire] to help build your next cloud platform.
+
+ [website]: https://cloudposse.com/
+ [community]: https://github.com/cloudposse/
+ [hire]: https://cloudposse.com/contact/
+
+
+### Contributors
+
+| [![Erik Osterman][erik_img]][erik_web]
[Erik Osterman][erik_web] | [![Andriy Knysh][andriy_img]][andriy_web]
[Andriy Knysh][andriy_web] |
+|-------------------------------------------------------|------------------------------------------------------------------|
+
+ [erik_img]: http://s.gravatar.com/avatar/88c480d4f73b813904e00a5695a454cb?s=144
+ [erik_web]: https://github.com/osterman/
+ [andriy_img]: https://avatars0.githubusercontent.com/u/7356997?v=4&u=ed9ce1c9151d552d985bdf5546772e14ef7ab617&s=144
+ [andriy_web]: https://github.com/aknysh/
diff --git a/images/s3-bucket-with-terraform-state.png b/images/s3-bucket-with-terraform-state.png
new file mode 100644
index 0000000..09624a5
Binary files /dev/null and b/images/s3-bucket-with-terraform-state.png differ
diff --git a/main.tf b/main.tf
new file mode 100644
index 0000000..2e54bcf
--- /dev/null
+++ b/main.tf
@@ -0,0 +1,62 @@
+module "s3_bucket_label" {
+ source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.3.3"
+ namespace = "${var.namespace}"
+ stage = "${var.stage}"
+ name = "${var.name}"
+ delimiter = "${var.delimiter}"
+ attributes = "${var.attributes}"
+ tags = "${var.tags}"
+}
+
+resource "aws_s3_bucket" "default" {
+ bucket = "${module.s3_bucket_label.id}"
+ acl = "${var.acl}"
+ region = "${var.region}"
+ force_destroy = false
+
+ versioning {
+ enabled = true
+ }
+
+ server_side_encryption_configuration {
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = "AES256"
+ }
+ }
+ }
+
+ tags = "${module.s3_bucket_label.tags}"
+}
+
+module "dynamodb_table_label" {
+ source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.3.3"
+ namespace = "${var.namespace}"
+ stage = "${var.stage}"
+ name = "${var.name}"
+ delimiter = "${var.delimiter}"
+ attributes = ["${compact(concat(var.attributes, list("lock")))}"]
+ tags = "${var.tags}"
+}
+
+resource "aws_dynamodb_table" "default" {
+ name = "${module.dynamodb_table_label.id}"
+ read_capacity = "${var.read_capacity}"
+ write_capacity = "${var.write_capacity}"
+ hash_key = "LockID" # https://www.terraform.io/docs/backends/types/s3.html#dynamodb_table
+
+ server_side_encryption {
+ enabled = true
+ }
+
+ lifecycle {
+ ignore_changes = ["read_capacity", "write_capacity"]
+ }
+
+ attribute {
+ name = "LockID"
+ type = "S"
+ }
+
+ tags = "${module.dynamodb_table_label.tags}"
+}
diff --git a/output.tf b/output.tf
new file mode 100644
index 0000000..40f52c4
--- /dev/null
+++ b/output.tf
@@ -0,0 +1,23 @@
+output "s3_bucket_domain_name" {
+ value = "${aws_s3_bucket.default.bucket_domain_name}"
+}
+
+output "s3_bucket_id" {
+ value = "${aws_s3_bucket.default.id}"
+}
+
+output "s3_bucket_arn" {
+ value = "${aws_s3_bucket.default.arn}"
+}
+
+output "dynamodb_table_name" {
+ value = "${aws_dynamodb_table.default.name}"
+}
+
+output "dynamodb_table_id" {
+ value = "${aws_dynamodb_table.default.id}"
+}
+
+output "dynamodb_table_arn" {
+ value = "${aws_dynamodb_table.default.arn}"
+}
diff --git a/variables.tf b/variables.tf
new file mode 100644
index 0000000..c02e4e7
--- /dev/null
+++ b/variables.tf
@@ -0,0 +1,55 @@
+variable "namespace" {
+ type = "string"
+ description = "Namespace (e.g. `cp` or `cloudposse`)"
+}
+
+variable "stage" {
+ type = "string"
+ description = "Stage (e.g. `prod`, `dev`, `staging`, `infra`)"
+}
+
+variable "name" {
+ type = "string"
+ default = "terraform"
+ description = "Name (e.g. `app` or `cluster`)"
+}
+
+variable "delimiter" {
+ type = "string"
+ default = "-"
+ description = "Delimiter to be used between `namespace`, `stage`, `name`, and `attributes`"
+}
+
+variable "attributes" {
+ type = "list"
+ default = ["state"]
+ description = "Additional attributes (e.g. `policy` or `role`)"
+}
+
+variable "tags" {
+ type = "map"
+ default = {}
+ description = "Additional tags (e.g. map('BusinessUnit`,`XYZ`)"
+}
+
+variable "region" {
+ type = "string"
+ description = "AWS Region the S3 bucket should reside in"
+ default = "us-east-1"
+}
+
+variable "acl" {
+ type = "string"
+ description = "The canned ACL to apply to the S3 bucket"
+ default = "private"
+}
+
+variable "read_capacity" {
+ default = 5
+ description = "DynamoDB read capacity units"
+}
+
+variable "write_capacity" {
+ default = 5
+ description = "DynamoDB write capacity units"
+}