Skip to content
This repository has been archived by the owner on Jun 19, 2024. It is now read-only.

Commit

Permalink
feat: adds initial cut at code for tutorial #3 (#4)
Browse files Browse the repository at this point in the history
* feat: adds initial cut at cloudposse/tutorials Docker image

* chore: updates gitignore to ignore terraform.lock.hcl files

* chore: adds backend.tf.json to gitignore

* feat: adds initial cut at code for tutorial #3

* chore: updates to rootfs/ pattern for motd file

* feat: updates tutorial #3 stacks to correct catalog/ conventions

* feat: updates stacks to include TODOs for names + adds bin/uniquify.sh

* chore: adds vendir to tutorials image

* chore: adds --name arg to `make run`

* feat: updates our tfstate-backend component to latest

* chore: updates tfstate-backend to be in us-east-2

* chore: applies suggestions from Erik's code review

Co-authored-by: Erik Osterman (CEO @ Cloud Posse) <[email protected]>

* chore: applies suggestions from Erik's code review

Co-authored-by: Erik Osterman (CEO @ Cloud Posse) <[email protected]>

* feat: updates to use golang-petname > random string

* fix: issue with dynamo lock attributes being out of order

* chore: updates tutorial #3 README to link to docs

Co-authored-by: Erik Osterman (CEO @ Cloud Posse) <[email protected]>
  • Loading branch information
Gowiem and osterman authored Apr 21, 2021
1 parent 346a399 commit 845dbd4
Show file tree
Hide file tree
Showing 25 changed files with 833 additions and 4 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ aws-assumed-role/
**/*.terraform.auto.tfvars.json
**/*.helmfile.vars.yaml
**/vendir.lock.yml
**/.terraform.lock.hcl
**/backend.tf.json

# Module directory
# Note that the leading "**/" appears necessary for Docker even if not for Git
**/.terraform/
**/.module/
**/.helmfile/


# Draft or auto-saved version
# Note that the leading "**/" appears necessary for Docker even if not for Git
**/*.draft.*
Expand Down
3 changes: 3 additions & 0 deletions 03-first-aws-environment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Tutorial #3: You first AWS Environment with Atmos

The code in this directory is an accompaniment to the [Your first environment on AWS](https://docs.cloudposse.com/tutorials/first-aws-environment/) tutorial in our SweetOps documentation.
33 changes: 33 additions & 0 deletions 03-first-aws-environment/bin/random-pet.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
#
# Since we're creating an S3 bucket as part of this tutorial, we need the names of those
# buckets to be unique, otherwise we'll run into issues with AWS S3 API errors blocking
# our terraform applies. This scripts generates random pet names to append to attributes
# which ensures all bucket names in the project are unique.

# Build random value
# We install golang-petname as part of our tutorials toolbox.
RANDOM_PET=$(golang-petname)

# Updates our tfstate-backend's attributes to be unique
yq eval --inplace \
".components.terraform.tfstate-backend.vars.attributes.[0] = \"$RANDOM_PET\"" \
stacks/ue2-root.yaml

# Updates our static backend config with unique names
yq eval --inplace \
".terraform.backend.s3.bucket = \"acme-uw2-tfstate-$RANDOM_PET\"" \
stacks/catalog/globals.yaml

yq eval --inplace \
".terraform.backend.s3.dynamodb_table = \"acme-uw2-tfstate-lock-$RANDOM_PET\"" \
stacks/catalog/globals.yaml

# Updates our dev + prod static-site component vars to ensure we're creating unique names
yq eval --inplace \
".components.terraform.static-site.vars.attributes.[0] = \"$RANDOM_PET\"" \
stacks/uw2-dev.yaml

yq eval --inplace \
".components.terraform.static-site.vars.attributes.[0] = \"$RANDOM_PET\"" \
stacks/uw2-prod.yaml
202 changes: 202 additions & 0 deletions 03-first-aws-environment/components/terraform/static-site/context.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
#
# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label
# All other instances of this file should be a copy of that one
#
#
# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf
# and then place it in your Terraform module to automatically get
# Cloud Posse's standard configuration inputs suitable for passing
# to Cloud Posse modules.
#
# Modules should access the whole context as `module.this.context`
# to get the input variables with nulls for defaults,
# for example `context = module.this.context`,
# and access individual variables as `module.this.<var>`,
# with final values filled in.
#
# For example, when using defaults, `module.this.context.delimiter`
# will be null, and `module.this.delimiter` will be `-` (hyphen).
#

module "this" {
source = "cloudposse/label/null"
version = "0.24.1" # requires Terraform >= 0.13.0

enabled = var.enabled
namespace = var.namespace
environment = var.environment
stage = var.stage
name = var.name
delimiter = var.delimiter
attributes = var.attributes
tags = var.tags
additional_tag_map = var.additional_tag_map
label_order = var.label_order
regex_replace_chars = var.regex_replace_chars
id_length_limit = var.id_length_limit
label_key_case = var.label_key_case
label_value_case = var.label_value_case

context = var.context
}

# Copy contents of cloudposse/terraform-null-label/variables.tf here

variable "context" {
type = any
default = {
enabled = true
namespace = null
environment = null
stage = null
name = null
delimiter = null
attributes = []
tags = {}
additional_tag_map = {}
regex_replace_chars = null
label_order = []
id_length_limit = null
label_key_case = null
label_value_case = null
}
description = <<-EOT
Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional_tag_map, which are merged.
EOT

validation {
condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
error_message = "Allowed values: `lower`, `title`, `upper`."
}

validation {
condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
}
}

variable "enabled" {
type = bool
default = null
description = "Set to false to prevent the module from creating any resources"
}

variable "namespace" {
type = string
default = null
description = "Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp'"
}

variable "environment" {
type = string
default = null
description = "Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT'"
}

variable "stage" {
type = string
default = null
description = "Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release'"
}

variable "name" {
type = string
default = null
description = "Solution name, e.g. 'app' or 'jenkins'"
}

variable "delimiter" {
type = string
default = null
description = <<-EOT
Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all.
EOT
}

variable "attributes" {
type = list(string)
default = []
description = "Additional attributes (e.g. `1`)"
}

variable "tags" {
type = map(string)
default = {}
description = "Additional tags (e.g. `map('BusinessUnit','XYZ')`"
}

variable "additional_tag_map" {
type = map(string)
default = {}
description = "Additional tags for appending to tags_as_list_of_maps. Not added to `tags`."
}

variable "label_order" {
type = list(string)
default = null
description = <<-EOT
The naming order of the id output and Name tag.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 5 elements, but at least one must be present.
EOT
}

variable "regex_replace_chars" {
type = string
default = null
description = <<-EOT
Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits.
EOT
}

variable "id_length_limit" {
type = number
default = null
description = <<-EOT
Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for default, which is `0`.
Does not affect `id_full`.
EOT
validation {
condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0
error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length."
}
}

variable "label_key_case" {
type = string
default = null
description = <<-EOT
The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
Possible values: `lower`, `title`, `upper`.
Default value: `title`.
EOT

validation {
condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case)
error_message = "Allowed values: `lower`, `title`, `upper`."
}
}

variable "label_value_case" {
type = string
default = null
description = <<-EOT
The letter case of output label values (also used in `tags` and `id`).
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Default value: `lower`.
EOT

validation {
condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case)
error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
}
}
#### End of copy of cloudposse/terraform-null-label/variables.tf
20 changes: 20 additions & 0 deletions 03-first-aws-environment/components/terraform/static-site/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module "cdn" {
source = "cloudposse/cloudfront-s3-cdn/aws"
version = "0.59.0"

name = "static-site"
website_enabled = true

context = module.this.context
}

resource "aws_s3_bucket_object" "site_index" {
bucket = module.cdn.s3_bucket
key = "index.html"
content_type = "text/html"

content = <<-EOT
<h1>Hello World!</h1>
<p>This is ${var.stage}!</p>
EOT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
output "domain_name" {
value = module.cdn.cf_domain_name
description = "The FQDN of the created static site."
}

output "s3_bucket_name" {
value = module.cdn.s3_bucket
description = "The FQDN of the created static site."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
provider "aws" {
region = var.region
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
variable "region" {
type = string
description = "AWS Region"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Component: `tfstate-backend`

This component is responsible for provisioning an S3 Bucket and DynamoDB table that follow security best practices for usage as a Terraform backend.

## Usage

**Stack Level**: Regional

Here's an example snippet for how to use this component.

```yaml
components:
terraform:
tfstate-backend:
vars:
name: tfstate
force_destroy: false
prevent_unencrypted_uploads: true
enable_server_side_encryption: true
```
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements
| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | ~> 0.14.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 3.32 |
| <a name="requirement_local"></a> [local](#requirement\_local) | ~> 2.1 |
| <a name="requirement_template"></a> [template](#requirement\_template) | ~> 2.2 |
## Providers
No providers.
## Modules
| Name | Source | Version |
|------|--------|---------|
| <a name="module_tfstate_backend"></a> [tfstate\_backend](#module\_tfstate\_backend) | cloudposse/tfstate-backend/aws | 0.32.0 |
| <a name="module_this"></a> [this](#module\_this) | cloudposse/label/null | 0.24.1 |
## Resources
No resources.
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_additional_tag_map"></a> [additional\_tag\_map](#input\_additional\_tag\_map) | Additional tags for appending to tags\_as\_list\_of\_maps. Not added to `tags`. | `map(string)` | `{}` | no |
| <a name="input_attributes"></a> [attributes](#input\_attributes) | Additional attributes (e.g. `1`) | `list(string)` | `[]` | no |
| <a name="input_context"></a> [context](#input\_context) | Single object for setting entire context at once.<br>See description of individual variables for details.<br>Leave string and numeric variables as `null` to use default value.<br>Individual variable settings (non-null) override settings in context object,<br>except for attributes, tags, and additional\_tag\_map, which are merged. | <pre>object({<br> enabled = bool<br> namespace = string<br> environment = string<br> stage = string<br> name = string<br> delimiter = string<br> attributes = list(string)<br> tags = map(string)<br> additional_tag_map = map(string)<br> regex_replace_chars = string<br> label_order = list(string)<br> id_length_limit = number<br> })</pre> | <pre>{<br> "additional_tag_map": {},<br> "attributes": [],<br> "delimiter": null,<br> "enabled": true,<br> "environment": null,<br> "id_length_limit": null,<br> "label_order": [],<br> "name": null,<br> "namespace": null,<br> "regex_replace_chars": null,<br> "stage": null,<br> "tags": {}<br>}</pre> | no |
| <a name="input_delimiter"></a> [delimiter](#input\_delimiter) | Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`.<br>Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no |
| <a name="input_enable_server_side_encryption"></a> [enable\_server\_side\_encryption](#input\_enable\_server\_side\_encryption) | Enable DynamoDB and S3 server-side encryption | `bool` | `true` | no |
| <a name="input_enabled"></a> [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no |
| <a name="input_environment"></a> [environment](#input\_environment) | Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no |
| <a name="input_force_destroy"></a> [force\_destroy](#input\_force\_destroy) | A boolean that indicates the terraform state S3 bucket can be destroyed even if it contains objects. These objects are not recoverable. | `bool` | `false` | no |
| <a name="input_id_length_limit"></a> [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters.<br>Set to `0` for unlimited length.<br>Set to `null` for default, which is `0`.<br>Does not affect `id_full`. | `number` | `null` | no |
| <a name="input_label_order"></a> [label\_order](#input\_label\_order) | The naming order of the id output and Name tag.<br>Defaults to ["namespace", "environment", "stage", "name", "attributes"].<br>You can omit any of the 5 elements, but at least one must be present. | `list(string)` | `null` | no |
| <a name="input_name"></a> [name](#input\_name) | Solution name, e.g. 'app' or 'jenkins' | `string` | `null` | no |
| <a name="input_namespace"></a> [namespace](#input\_namespace) | Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp' | `string` | `null` | no |
| <a name="input_prevent_unencrypted_uploads"></a> [prevent\_unencrypted\_uploads](#input\_prevent\_unencrypted\_uploads) | Prevent uploads of unencrypted objects to S3 | `bool` | `false` | no |
| <a name="input_regex_replace_chars"></a> [regex\_replace\_chars](#input\_regex\_replace\_chars) | Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.<br>If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
| <a name="input_region"></a> [region](#input\_region) | AWS Region | `string` | n/a | yes |
| <a name="input_stage"></a> [stage](#input\_stage) | Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no |
| <a name="input_tags"></a> [tags](#input\_tags) | Additional tags (e.g. `map('BusinessUnit','XYZ')` | `map(string)` | `{}` | no |
| <a name="input_tfstate_account_id"></a> [tfstate\_account\_id](#input\_tfstate\_account\_id) | The ID of the account where the Terraform remote state backend is provisioned | `string` | `""` | no |
| <a name="input_tfstate_assume_role"></a> [tfstate\_assume\_role](#input\_tfstate\_assume\_role) | Set to false to use the caller's role to access the Terraform remote state | `bool` | `true` | no |
| <a name="input_tfstate_bucket_environment_name"></a> [tfstate\_bucket\_environment\_name](#input\_tfstate\_bucket\_environment\_name) | The name of the environment for Terraform state bucket | `string` | `""` | no |
| <a name="input_tfstate_bucket_stage_name"></a> [tfstate\_bucket\_stage\_name](#input\_tfstate\_bucket\_stage\_name) | The name of the stage for Terraform state bucket | `string` | `"root"` | no |
| <a name="input_tfstate_existing_role_arn"></a> [tfstate\_existing\_role\_arn](#input\_tfstate\_existing\_role\_arn) | The ARN of the existing IAM Role to access the Terraform remote state. If not provided and `remote_state_assume_role` is `true`, a role will be constructed from `remote_state_role_arn_template` | `string` | `""` | no |
| <a name="input_tfstate_role_arn_template"></a> [tfstate\_role\_arn\_template](#input\_tfstate\_role\_arn\_template) | IAM Role ARN template for accessing the Terraform remote state | `string` | `"arn:aws:iam::%s:role/%s-%s-%s-%s"` | no |
| <a name="input_tfstate_role_environment_name"></a> [tfstate\_role\_environment\_name](#input\_tfstate\_role\_environment\_name) | The name of the environment for Terraform state IAM role | `string` | `"gbl"` | no |
| <a name="input_tfstate_role_name"></a> [tfstate\_role\_name](#input\_tfstate\_role\_name) | IAM Role name for accessing the Terraform remote state | `string` | `"terraform"` | no |
| <a name="input_tfstate_role_stage_name"></a> [tfstate\_role\_stage\_name](#input\_tfstate\_role\_stage\_name) | The name of the stage for Terraform state IAM role | `string` | `"root"` | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_tfstate_backend_dynamodb_table_arn"></a> [tfstate\_backend\_dynamodb\_table\_arn](#output\_tfstate\_backend\_dynamodb\_table\_arn) | Terraform state DynamoDB table ARN |
| <a name="output_tfstate_backend_dynamodb_table_id"></a> [tfstate\_backend\_dynamodb\_table\_id](#output\_tfstate\_backend\_dynamodb\_table\_id) | Terraform state DynamoDB table ID |
| <a name="output_tfstate_backend_dynamodb_table_name"></a> [tfstate\_backend\_dynamodb\_table\_name](#output\_tfstate\_backend\_dynamodb\_table\_name) | Terraform state DynamoDB table name |
| <a name="output_tfstate_backend_s3_bucket_arn"></a> [tfstate\_backend\_s3\_bucket\_arn](#output\_tfstate\_backend\_s3\_bucket\_arn) | Terraform state S3 bucket ARN |
| <a name="output_tfstate_backend_s3_bucket_domain_name"></a> [tfstate\_backend\_s3\_bucket\_domain\_name](#output\_tfstate\_backend\_s3\_bucket\_domain\_name) | Terraform state S3 bucket domain name |
| <a name="output_tfstate_backend_s3_bucket_id"></a> [tfstate\_backend\_s3\_bucket\_id](#output\_tfstate\_backend\_s3\_bucket\_id) | Terraform state S3 bucket ID |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->

## References

- [cloudposse/terraform-aws-components](https://github.com/cloudposse/terraform-aws-components/tree/master/modules/tfstate-backend) - Cloud Posse's upstream component

[<img src="https://cloudposse.com/logo-300x69.svg" height="32" align="right"/>](https://cpco.io/component)
Loading

0 comments on commit 845dbd4

Please sign in to comment.