diff --git a/cmd/generate_test.go b/cmd/generate_test.go index ddbac619..c9239f5b 100644 --- a/cmd/generate_test.go +++ b/cmd/generate_test.go @@ -211,3 +211,11 @@ func TestExtraArguments(t *testing.T) { "--ignore-parent-terragrunt", }) } + +func TestInfrastructureLive(t *testing.T) { + runTest(t, filepath.Join("golden", "infrastructureLive.yaml"), []string{ + "--root", + filepath.Join("..", "test_examples", "terragrunt-infrastructure-live-example"), + "--ignore-parent-terragrunt", + }) +} diff --git a/cmd/golden/infrastructureLive.yaml b/cmd/golden/infrastructureLive.yaml new file mode 100644 index 00000000..2f309d0b --- /dev/null +++ b/cmd/golden/infrastructureLive.yaml @@ -0,0 +1,59 @@ +automerge: false +parallel_apply: true +parallel_plan: true +projects: +- autoplan: + enabled: false + when_modified: + - '*.hcl' + - '*.tf*' + - ../../../account.hcl + - ../../region.hcl + - ../env.hcl + dir: non-prod/us-east-1/qa/mysql +- autoplan: + enabled: false + when_modified: + - '*.hcl' + - '*.tf*' + - ../../../account.hcl + - ../../region.hcl + - ../env.hcl + dir: non-prod/us-east-1/qa/webserver-cluster +- autoplan: + enabled: false + when_modified: + - '*.hcl' + - '*.tf*' + - ../../../account.hcl + - ../../region.hcl + - ../env.hcl + dir: non-prod/us-east-1/stage/mysql +- autoplan: + enabled: false + when_modified: + - '*.hcl' + - '*.tf*' + - ../../../account.hcl + - ../../region.hcl + - ../env.hcl + dir: non-prod/us-east-1/stage/webserver-cluster +- autoplan: + enabled: false + when_modified: + - '*.hcl' + - '*.tf*' + - ../../../account.hcl + - ../../region.hcl + - ../env.hcl + dir: prod/us-east-1/prod/mysql +- autoplan: + enabled: false + when_modified: + - '*.hcl' + - '*.tf*' + - ../../../account.hcl + - ../../region.hcl + - ../env.hcl + dir: prod/us-east-1/prod/webserver-cluster +version: 3 diff --git a/test_examples/terragrunt-infrastructure-live-example/README.md b/test_examples/terragrunt-infrastructure-live-example/README.md new file mode 100644 index 00000000..22686d34 --- /dev/null +++ b/test_examples/terragrunt-infrastructure-live-example/README.md @@ -0,0 +1,140 @@ +[![Maintained by Gruntwork.io](https://img.shields.io/badge/maintained%20by-gruntwork.io-%235849a6.svg)](https://gruntwork.io/?ref=repo_terragrunt-infra-live-example) + +# Example infrastructure-live for Terragrunt + +This repo, along with the [terragrunt-infrastructure-modules-example +repo](https://github.com/gruntwork-io/terragrunt-infrastructure-modules-example), show an example file/folder structure +you can use with [Terragrunt](https://github.com/gruntwork-io/terragrunt) to keep your +[Terraform](https://www.terraform.io) code DRY. For background information, check out the [Keep your Terraform code +DRY](https://github.com/gruntwork-io/terragrunt#keep-your-terraform-code-dry) section of the Terragrunt documentation. + +This repo shows an example of how to use the modules from the `terragrunt-infrastructure-modules-example` repo to +deploy an Auto Scaling Group (ASG) and a MySQL DB across three environments (qa, stage, prod) and two AWS accounts +(non-prod, prod), all without duplicating any of the Terraform code. That's because there is just a single copy of +the Terraform code, defined in the `terragrunt-infrastructure-modules-example` repo, and in this repo, we solely define +`terragrunt.hcl` files that reference that code (at a specific version, too!) and fill in variables specific to each +environment. + +Note: This code is solely for demonstration purposes. This is not production-ready code, so use at your own risk. If +you are interested in battle-tested, production-ready Terraform code, check out [Gruntwork](http://www.gruntwork.io/). + + + + +## How do you deploy the infrastructure in this repo? + + +### Pre-requisites + +1. Install [Terraform](https://www.terraform.io/) version `0.12.0` or newer and + [Terragrunt](https://github.com/gruntwork-io/terragrunt) version `v0.23.0` or newer. +1. Update the `bucket` parameter in `non-prod/terragrunt.hcl` and `prod/terragrunt.hcl` to unique names. We use S3 + [as a Terraform backend](https://www.terraform.io/docs/backends/types/s3.html) to store your Terraform state, and + S3 bucket names must be globally unique. The names currently in the file are already taken, so you'll have to + specify your own. Alternatives, you can set the environment variable `TG_BUCKET_PREFIX` to set a custom prefix. +1. Configure your AWS credentials using one of the supported [authentication + mechanisms](https://www.terraform.io/docs/providers/aws/#authentication). + + +### Deploying a single module + +1. `cd` into the module's folder (e.g. `cd non-prod/us-east-1/qa/mysql`). +1. Note: if you're deploying the MySQL DB, you'll need to configure your DB password as an environment variable: + `export TF_VAR_master_password=(...)`. +1. Run `terragrunt plan` to see the changes you're about to apply. +1. If the plan looks good, run `terragrunt apply`. + + +### Deploying all modules in a region + +1. `cd` into the region folder (e.g. `cd non-prod/us-east-1`). +1. Configure the password for the MySQL DB as an environment variable: `export TF_VAR_master_password=(...)`. +1. Run `terragrunt plan-all` to see all the changes you're about to apply. +1. If the plan looks good, run `terragrunt apply-all`. + + +### Testing the infrastructure after it's deployed + +After each module is finished deploying, it will write a bunch of outputs to the screen. For example, the ASG will +output something like the following: + +``` +Outputs: + +asg_name = tf-asg-00343cdb2415e9d5f20cda6620 +asg_security_group_id = sg-d27df1a3 +elb_dns_name = webserver-example-prod-1234567890.us-east-1.elb.amazonaws.com +elb_security_group_id = sg-fe62ee8f +url = http://webserver-example-prod-1234567890.us-east-1.elb.amazonaws.com:80 +``` + +A minute or two after the deployment finishes, and the servers in the ASG have passed their health checks, you should +be able to test the `url` output in your browser or with `curl`: + +``` +curl http://webserver-example-prod-1234567890.us-east-1.elb.amazonaws.com:80 + +Hello, World +``` + +Similarly, the MySQL module produces outputs that will look something like this: + +``` +Outputs: + +arn = arn:aws:rds:us-east-1:1234567890:db:terraform-00d7a11c1e02cf617f80bbe301 +db_name = mysql_prod +endpoint = terraform-1234567890.abcdefghijklmonp.us-east-1.rds.amazonaws.com:3306 +``` + +You can use the `endpoint` and `db_name` outputs with any MySQL client: + +``` +mysql --host=terraform-1234567890.abcdefghijklmonp.us-east-1.rds.amazonaws.com:3306 --user=admin --password mysql_prod +``` + + + + + + +## How is the code in this repo organized? + +The code in this repo uses the following folder hierarchy: + +``` +account + └ _global + └ region + └ _global + └ environment + └ resource +``` + +Where: + +* **Account**: At the top level are each of your AWS accounts, such as `stage-account`, `prod-account`, `mgmt-account`, + etc. If you have everything deployed in a single AWS account, there will just be a single folder at the root (e.g. + `main-account`). + +* **Region**: Within each account, there will be one or more [AWS + regions](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html), such as + `us-east-1`, `eu-west-1`, and `ap-southeast-2`, where you've deployed resources. There may also be a `_global` + folder that defines resources that are available across all the AWS regions in this account, such as IAM users, + Route 53 hosted zones, and CloudTrail. + +* **Environment**: Within each region, there will be one or more "environments", such as `qa`, `stage`, etc. Typically, + an environment will correspond to a single [AWS Virtual Private Cloud (VPC)](https://aws.amazon.com/vpc/), which + isolates that environment from everything else in that AWS account. There may also be a `_global` folder + that defines resources that are available across all the environments in this AWS region, such as Route 53 A records, + SNS topics, and ECR repos. + +* **Resource**: Within each environment, you deploy all the resources for that environment, such as EC2 Instances, Auto + Scaling Groups, ECS Clusters, Databases, Load Balancers, and so on. Note that the Terraform code for most of these + resources lives in the [terragrunt-infrastructure-modules-example repo](https://github.com/gruntwork-io/terragrunt-infrastructure-modules-example). + +## Creating and using root (account) level variables + +In the situation where you have multiple AWS accounts or regions, you often have to pass common variables down to each +of your modules. Rather than copy/pasting the same variables into each `terragrunt.hcl` file, in every region, and in +every environment, you can inherit them from the `inputs` defined in the root `terragrunt.hcl` file. diff --git a/test_examples/terragrunt-infrastructure-live-example/non-prod/account.hcl b/test_examples/terragrunt-infrastructure-live-example/non-prod/account.hcl new file mode 100644 index 00000000..9b12e199 --- /dev/null +++ b/test_examples/terragrunt-infrastructure-live-example/non-prod/account.hcl @@ -0,0 +1,7 @@ +# Set account-wide variables. These are automatically pulled in to configure the remote state bucket in the root +# terragrunt.hcl configuration. +locals { + account_name = "non-prod" + aws_account_id = "replaceme" # TODO: replace me with your AWS account ID! + aws_profile = "non-prod" +} diff --git a/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/qa/env.hcl b/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/qa/env.hcl new file mode 100644 index 00000000..3cfebf9d --- /dev/null +++ b/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/qa/env.hcl @@ -0,0 +1,5 @@ +# Set common variables for the environment. This is automatically pulled in in the root terragrunt.hcl configuration to +# feed forward to the child modules. +locals { + environment = "qa" +} diff --git a/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/qa/mysql/terragrunt.hcl b/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/qa/mysql/terragrunt.hcl new file mode 100644 index 00000000..29e2c4d6 --- /dev/null +++ b/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/qa/mysql/terragrunt.hcl @@ -0,0 +1,31 @@ +locals { + # Automatically load environment-level variables + environment_vars = read_terragrunt_config(find_in_parent_folders("env.hcl")) + + # Extract out common variables for reuse + env = local.environment_vars.locals.environment +} + +# Terragrunt will copy the Terraform configurations specified by the source parameter, along with any files in the +# working directory, into a temporary folder, and execute your Terraform commands in that folder. +terraform { + source = "git::git@github.com:gruntwork-io/terragrunt-infrastructure-modules-example.git//mysql?ref=v0.3.0" +} + +# Include all settings from the root terragrunt.hcl file +include { + path = find_in_parent_folders() +} + +# These are the variables we have to pass in to use the module specified in the terragrunt configuration above +inputs = { + name = "mysql_${local.env}" + instance_class = "db.t2.micro" + + allocated_storage = 20 + storage_type = "standard" + + master_username = "admin" + # TODO: To avoid storing your DB password in the code, set it as the environment variable TF_VAR_master_password +} + diff --git a/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/qa/webserver-cluster/terragrunt.hcl b/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/qa/webserver-cluster/terragrunt.hcl new file mode 100644 index 00000000..bcf01510 --- /dev/null +++ b/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/qa/webserver-cluster/terragrunt.hcl @@ -0,0 +1,30 @@ +locals { + # Automatically load environment-level variables + environment_vars = read_terragrunt_config(find_in_parent_folders("env.hcl")) + + # Extract out common variables for reuse + env = local.environment_vars.locals.environment +} + +# Terragrunt will copy the Terraform configurations specified by the source parameter, along with any files in the +# working directory, into a temporary folder, and execute your Terraform commands in that folder. +terraform { + source = "git::git@github.com:gruntwork-io/terragrunt-infrastructure-modules-example.git//asg-elb-service?ref=v0.3.0" +} + +# Include all settings from the root terragrunt.hcl file +include { + path = find_in_parent_folders() +} + +# These are the variables we have to pass in to use the module specified in the terragrunt configuration above +inputs = { + name = "webserver-example-${local.env}" + instance_type = "t2.micro" + + min_size = 2 + max_size = 2 + + server_port = 8080 + elb_port = 80 +} diff --git a/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/region.hcl b/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/region.hcl new file mode 100644 index 00000000..b35cde49 --- /dev/null +++ b/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/region.hcl @@ -0,0 +1,5 @@ +# Set common variables for the region. This is automatically pulled in in the root terragrunt.hcl configuration to +# configure the remote state bucket and pass forward to the child modules as inputs. +locals { + aws_region = "us-east-1" +} diff --git a/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/stage/env.hcl b/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/stage/env.hcl new file mode 100644 index 00000000..833b802d --- /dev/null +++ b/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/stage/env.hcl @@ -0,0 +1,5 @@ +# Set common variables for the environment. This is automatically pulled in in the root terragrunt.hcl configuration to +# feed forward to the child modules. +locals { + environment = "stage" +} diff --git a/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/stage/mysql/terragrunt.hcl b/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/stage/mysql/terragrunt.hcl new file mode 100644 index 00000000..053c2774 --- /dev/null +++ b/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/stage/mysql/terragrunt.hcl @@ -0,0 +1,30 @@ +locals { + # Automatically load environment-level variables + environment_vars = read_terragrunt_config(find_in_parent_folders("env.hcl")) + + # Extract out common variables for reuse + env = local.environment_vars.locals.environment +} + +# Terragrunt will copy the Terraform configurations specified by the source parameter, along with any files in the +# working directory, into a temporary folder, and execute your Terraform commands in that folder. +terraform { + source = "git::git@github.com:gruntwork-io/terragrunt-infrastructure-modules-example.git//mysql?ref=v0.3.0" +} + +# Include all settings from the root terragrunt.hcl file +include { + path = find_in_parent_folders() +} + +# These are the variables we have to pass in to use the module specified in the terragrunt configuration above +inputs = { + name = "mysql_${local.env}" + instance_class = "db.t2.micro" + + allocated_storage = 20 + storage_type = "standard" + + master_username = "admin" + # TODO: To avoid storing your DB password in the code, set it as the environment variable TF_VAR_master_password +} diff --git a/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/stage/webserver-cluster/terragrunt.hcl b/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/stage/webserver-cluster/terragrunt.hcl new file mode 100644 index 00000000..bcf01510 --- /dev/null +++ b/test_examples/terragrunt-infrastructure-live-example/non-prod/us-east-1/stage/webserver-cluster/terragrunt.hcl @@ -0,0 +1,30 @@ +locals { + # Automatically load environment-level variables + environment_vars = read_terragrunt_config(find_in_parent_folders("env.hcl")) + + # Extract out common variables for reuse + env = local.environment_vars.locals.environment +} + +# Terragrunt will copy the Terraform configurations specified by the source parameter, along with any files in the +# working directory, into a temporary folder, and execute your Terraform commands in that folder. +terraform { + source = "git::git@github.com:gruntwork-io/terragrunt-infrastructure-modules-example.git//asg-elb-service?ref=v0.3.0" +} + +# Include all settings from the root terragrunt.hcl file +include { + path = find_in_parent_folders() +} + +# These are the variables we have to pass in to use the module specified in the terragrunt configuration above +inputs = { + name = "webserver-example-${local.env}" + instance_type = "t2.micro" + + min_size = 2 + max_size = 2 + + server_port = 8080 + elb_port = 80 +} diff --git a/test_examples/terragrunt-infrastructure-live-example/prod/account.hcl b/test_examples/terragrunt-infrastructure-live-example/prod/account.hcl new file mode 100644 index 00000000..3cd40842 --- /dev/null +++ b/test_examples/terragrunt-infrastructure-live-example/prod/account.hcl @@ -0,0 +1,7 @@ +# Set account-wide variables. These are automatically pulled in to configure the remote state bucket in the root +# terragrunt.hcl configuration. +locals { + account_name = "prod" + aws_account_id = "replaceme" # TODO: replace me with your AWS account ID! + aws_profile = "prod" +} diff --git a/test_examples/terragrunt-infrastructure-live-example/prod/us-east-1/prod/env.hcl b/test_examples/terragrunt-infrastructure-live-example/prod/us-east-1/prod/env.hcl new file mode 100644 index 00000000..746645d1 --- /dev/null +++ b/test_examples/terragrunt-infrastructure-live-example/prod/us-east-1/prod/env.hcl @@ -0,0 +1,5 @@ +# Set common variables for the environment. This is automatically pulled in in the root terragrunt.hcl configuration to +# feed forward to the child modules. +locals { + environment = "prod" +} diff --git a/test_examples/terragrunt-infrastructure-live-example/prod/us-east-1/prod/mysql/terragrunt.hcl b/test_examples/terragrunt-infrastructure-live-example/prod/us-east-1/prod/mysql/terragrunt.hcl new file mode 100644 index 00000000..b1d55241 --- /dev/null +++ b/test_examples/terragrunt-infrastructure-live-example/prod/us-east-1/prod/mysql/terragrunt.hcl @@ -0,0 +1,30 @@ +locals { + # Automatically load environment-level variables + environment_vars = read_terragrunt_config(find_in_parent_folders("env.hcl")) + + # Extract out common variables for reuse + env = local.environment_vars.locals.environment +} + +# Terragrunt will copy the Terraform configurations specified by the source parameter, along with any files in the +# working directory, into a temporary folder, and execute your Terraform commands in that folder. +terraform { + source = "git::git@github.com:gruntwork-io/terragrunt-infrastructure-modules-example.git//mysql?ref=v0.3.0" +} + +# Include all settings from the root terragrunt.hcl file +include { + path = find_in_parent_folders() +} + +# These are the variables we have to pass in to use the module specified in the terragrunt configuration above +inputs = { + name = "mysql_${local.env}" + instance_class = "db.t2.medium" + + allocated_storage = 100 + storage_type = "standard" + + master_username = "admin" + # TODO: To avoid storing your DB password in the code, set it as the environment variable TF_VAR_master_password +} diff --git a/test_examples/terragrunt-infrastructure-live-example/prod/us-east-1/prod/webserver-cluster/terragrunt.hcl b/test_examples/terragrunt-infrastructure-live-example/prod/us-east-1/prod/webserver-cluster/terragrunt.hcl new file mode 100644 index 00000000..3ca2c46e --- /dev/null +++ b/test_examples/terragrunt-infrastructure-live-example/prod/us-east-1/prod/webserver-cluster/terragrunt.hcl @@ -0,0 +1,30 @@ +locals { + # Automatically load environment-level variables + environment_vars = read_terragrunt_config(find_in_parent_folders("env.hcl")) + + # Extract out common variables for reuse + env = local.environment_vars.locals.environment +} + +# Terragrunt will copy the Terraform configurations specified by the source parameter, along with any files in the +# working directory, into a temporary folder, and execute your Terraform commands in that folder. +terraform { + source = "git::git@github.com:gruntwork-io/terragrunt-infrastructure-modules-example.git//asg-elb-service?ref=v0.3.0" +} + +# Include all settings from the root terragrunt.hcl file +include { + path = find_in_parent_folders() +} + +# These are the variables we have to pass in to use the module specified in the terragrunt configuration above +inputs = { + name = "webserver-example-${local.env}" + instance_type = "t2.medium" + + min_size = 3 + max_size = 3 + + server_port = 8080 + elb_port = 80 +} diff --git a/test_examples/terragrunt-infrastructure-live-example/prod/us-east-1/region.hcl b/test_examples/terragrunt-infrastructure-live-example/prod/us-east-1/region.hcl new file mode 100644 index 00000000..b35cde49 --- /dev/null +++ b/test_examples/terragrunt-infrastructure-live-example/prod/us-east-1/region.hcl @@ -0,0 +1,5 @@ +# Set common variables for the region. This is automatically pulled in in the root terragrunt.hcl configuration to +# configure the remote state bucket and pass forward to the child modules as inputs. +locals { + aws_region = "us-east-1" +} diff --git a/test_examples/terragrunt-infrastructure-live-example/terragrunt.hcl b/test_examples/terragrunt-infrastructure-live-example/terragrunt.hcl new file mode 100644 index 00000000..f9744252 --- /dev/null +++ b/test_examples/terragrunt-infrastructure-live-example/terragrunt.hcl @@ -0,0 +1,72 @@ +# --------------------------------------------------------------------------------------------------------------------- +# TERRAGRUNT CONFIGURATION +# Terragrunt is a thin wrapper for Terraform that provides extra tools for working with multiple Terraform modules, +# remote state, and locking: https://github.com/gruntwork-io/terragrunt +# --------------------------------------------------------------------------------------------------------------------- + +locals { + extra_atlantis_dependencies = [ + find_in_parent_folders("account.hcl"), + find_in_parent_folders("region.hcl"), + find_in_parent_folders("env.hcl"), + ] + + # Automatically load account-level variables + account_vars = read_terragrunt_config(find_in_parent_folders("account.hcl")) + + # Automatically load region-level variables + region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl")) + + # Automatically load environment-level variables + environment_vars = read_terragrunt_config(find_in_parent_folders("env.hcl")) + + # Extract the variables we need for easy access + account_name = local.account_vars.locals.account_name + account_id = local.account_vars.locals.aws_account_id + aws_region = local.region_vars.locals.aws_region +} + +# Generate an AWS provider block +generate "provider" { + path = "provider.tf" + if_exists = "overwrite_terragrunt" + contents = <