Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ArnauLlamas committed Nov 22, 2023
0 parents commit 0188b44
Show file tree
Hide file tree
Showing 50 changed files with 1,034 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.terragrunt-cache
2 changes: 2 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
terraform 1.5.7
terragrunt 0.47.0
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Terragrunt Learning

A demo repository with an opinionated structure to manage multi-env and multi-region
applications with Terragrunt.

The demo application is a simple Lambda that is triggered when an object iscreated
created in an S3 bucket and copies the object into another bucket.

Lambda code is heavily inspired by the one in [this](https://repost.aws/knowledge-center/lambda-copy-s3-files)
AWS repost.

Note: Most of the times you would want to use [S3 Replication](https://docs.aws.amazon.com/AmazonS3/latest/userguide/replication.html)
feature to perform what the lambda in this example does, it is just a demo! :)

### Additional tools
Along with Terraform, this repository assumes that you have all extra tools to ensure Terraforms workflow, these are:
* A Terminal emulator
* AWS CLI or a way to set up AWS credentials into your environment
* If you are using Zsh and/or [Oh My Zsh](https://github.com/ohmyzsh/ohmyzsh) we strongly recommend its [AWS plugin](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/aws)
* Terraform and Terragrunt binaries
* This repo includes [ASDF](https://asdf-vm.com/) Tool Versions files


## Official Links
List of official links:
* [Terraform download](https://developer.hashicorp.com/terraform/downloads)
* [Terragrunt download](https://terragrunt.gruntwork.io/docs/getting-started/install/)
* [Terragrunt docs](https://terragrunt.gruntwork.io/docs/getting-started/quick-start/)
* [S3 Terraform module](https://github.com/terraform-aws-modules/terraform-aws-s3-bucket)
* [Lambda Terraform module](https://github.com/terraform-aws-modules/terraform-aws-lambda)
10 changes: 10 additions & 0 deletions _common/app.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
locals {
team = "a-team"
project = "learning-terragrunt"
service = "remote-modules"

# This is used for the highest S3 key to store the Terraform state
# Is is usually safer to use a "hardcoded/specified" string to ensure
# it survives both project and team names (these can actually change!)
project_key = "learning-terragrunt-remote-modules"
}
80 changes: 80 additions & 0 deletions _common/lambda.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
terraform {
source = "github.com/terraform-aws-modules/terraform-aws-lambda?ref=v6.4.0"
}

dependency "origin_bucket" {
config_path = "../01-origin-bucket"

mock_outputs = {
s3_bucket_arn = "arn:aws:s3:::mocked-origin-s3-bucket"
}
mock_outputs_allowed_terraform_commands = ["init", "plan", "validate"]
mock_outputs_merge_strategy_with_state = "shallow"
}

dependency "target_bucket" {
config_path = "../02-target-bucket"

mock_outputs = {
s3_bucket_arn = "arn:aws:s3:::mocked-destination-s3-bucket"
s3_bucket_id = "mocked-destination-s3-bucket"
}
mock_outputs_allowed_terraform_commands = ["init", "plan", "validate"]
mock_outputs_merge_strategy_with_state = "shallow"
}

dependency "lambda_random_name" {
config_path = "../03-lambda-random-name"

mock_outputs = {
random_id = "abcde1452/"
random_pet = "friendly-random-name"
}
}

inputs = {
function_name = dependency.lambda_random_name.outputs.random_pet

handler = "lambda_function.lambda_handler"
runtime = "python3.11"
publish = true

source_path = {
path = "${get_repo_root()}/_common/lambda/src",
commands = [ ":zip" ]
}

environment_variables = {
TARGET_BUCKET = dependency.target_bucket.outputs.s3_bucket_id
}

allowed_triggers = {
OriginS3 = {
principal = "s3.amazonaws.com"
source_arn = dependency.origin_bucket.outputs.s3_bucket_arn
}
}

attach_policy_json = true
policy_json = <<-EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "${dependency.origin_bucket.outputs.s3_bucket_arn}/*"
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject"
],
"Resource": "${dependency.target_bucket.outputs.s3_bucket_arn}/*"
}
]
}
EOF
}
40 changes: 40 additions & 0 deletions _common/lambda/src/lambda_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import boto3
import botocore
import logging
import os

logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.info("Loading function")

s3 = boto3.resource("s3")


def lambda_handler(event, _):
logger.info("New files uploaded to the source bucket.")

key = event["Records"][0]["s3"]["object"]["key"]
source_bucket = event["Records"][0]["s3"]["bucket"]["name"]

source = {"Bucket": source_bucket, "Key": key}

try:
target_bucket = os.environ["TARGET_BUCKET"]
except Exception as e:
logger.error("Error fetching environment variable 'TARGET_BUCKET'")
print(e)
raise (e)

try:
s3.meta.client.copy(source, target_bucket, key)
logger.info("File copied to the destination bucket successfully!")

except botocore.exceptions.ClientError as e:
logger.error("There was an error copying the file to the destination bucket")
print(e)
raise (e)

except botocore.exceptions.ParamValidationError as e:
logger.error("Missing required parameters while calling the API.")
print(e)
raise (e)
12 changes: 12 additions & 0 deletions _common/modules/random_name_generator/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
resource "random_pet" "this" {}
resource "random_id" "this" {
byte_length = 8
}

output "random_id" {
value = random_id.this.id
}

output "random_pet" {
value = random_pet.this.id
}
8 changes: 8 additions & 0 deletions _common/modules/random_name_generator/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
terraform {
required_providers {
random = {
source = "hashicorp/random"
version = ">= 3.0"
}
}
}
17 changes: 17 additions & 0 deletions _common/modules/s3_notification/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
variable "bucket" {
type = string
description = "S3 bucket name that will emit notifications on objects created"
}

variable "lambda_function_arn" {
type = string
description = "Lambda function ARN that will receive the notifications"
}

resource "aws_s3_bucket_notification" "this" {
bucket = var.bucket
lambda_function {
lambda_function_arn = var.lambda_function_arn
events = ["s3:ObjectCreated:*"]
}
}
8 changes: 8 additions & 0 deletions _common/modules/s3_notification/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}
3 changes: 3 additions & 0 deletions _common/random_name_generator.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
terraform {
source = "${get_repo_root()}/_common/modules//random_name_generator"
}
51 changes: 51 additions & 0 deletions _common/root.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
locals {
app_vars = read_terragrunt_config("${get_repo_root()}/_common/app.hcl")
env_vars = read_terragrunt_config(find_in_parent_folders("env.hcl"))
region_vars = read_terragrunt_config(find_in_parent_folders("region.hcl"))

aws_default_tags = jsonencode({
environment = local.env_vars.locals.environment
team = local.app_vars.locals.team
project = local.app_vars.locals.project
service = local.app_vars.locals.service
iac = "terragrunt"
repo = trimsuffix(run_cmd("--terragrunt-quiet", "git", "config", "--get", "remote.origin.url"), ".git")
})
}

# Generate an AWS provider block for the current Region and AWS Account
generate "provider" {
path = "provider.tf"
if_exists = "overwrite_terragrunt"
contents = <<-EOF
provider "aws" {
region = "${local.region_vars.locals.aws_region}"
# Allow Terraform code to only be ran on these AWS Account IDs
# allowed_account_ids = ["${local.env_vars.locals.aws_account_id}"]
default_tags {
tags = jsondecode(
<<-INNEREOF
${local.aws_default_tags}
INNEREOF
)
}
}
EOF
}

# remote_state {
# backend = "s3"
# config = {
# disable_bucket_update = true
# encrypt = true
# bucket = "${local.env_vars.locals.tf_bucket_name}"
# key = "${local.app_vars.locals.project_key}/ ${local.region_vars.locals.aws_region}/ ${trim(basename(path_relative_to_include()), "-0123456789")}/terraform.tfstate"
# region = "eu-west-1"
# }
# generate = {
# path = "backend.tf"
# if_exists = "overwrite_terragrunt"
# }
# }
3 changes: 3 additions & 0 deletions _common/s3.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
terraform {
source = "github.com/terraform-aws-modules/terraform-aws-s3-bucket?ref=v3.15.1"
}
28 changes: 28 additions & 0 deletions _common/s3_notifications.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
terraform {
source = "${get_repo_root()}/_common/modules//s3_notification"
}

dependency "origin_bucket" {
config_path = "../01-origin-bucket"

mock_outputs = {
s3_bucket_id = "mocked-origin-s3-bucket"
}
mock_outputs_allowed_terraform_commands = ["init", "plan", "validate"]
mock_outputs_merge_strategy_with_state = "shallow"
}

dependency "lambda" {
config_path = "../04-lambda"

mock_outputs = {
lambda_function_arn = "arn:aws:lambda:eu-west-1:123456789012:function:mocked-lambda"
}
mock_outputs_allowed_terraform_commands = ["init", "plan", "validate"]
mock_outputs_merge_strategy_with_state = "shallow"
}

inputs = {
bucket = dependency.origin_bucket.outputs.s3_bucket_id
lambda_function_arn = dependency.lambda.outputs.lambda_function_arn
}
5 changes: 5 additions & 0 deletions pro/env.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
locals {
environment = "pro"
tf_bucket_name = "fake-bucket-name-01" # FAKE!
aws_account_id = "123456789012" # FAKE!
}
24 changes: 24 additions & 0 deletions pro/eu-west-1/01-origin-bucket/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions pro/eu-west-1/01-origin-bucket/terragrunt.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
include "root" {
path = "${get_repo_root()}/_common/root.hcl"
}

include "s3" {
path = "${get_repo_root()}/_common/s3.hcl"
}
24 changes: 24 additions & 0 deletions pro/eu-west-1/02-target-bucket/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions pro/eu-west-1/02-target-bucket/terragrunt.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
include "root" {
path = "${get_repo_root()}/_common/root.hcl"
}

include "s3" {
path = "${get_repo_root()}/_common/s3.hcl"
}
Loading

0 comments on commit 0188b44

Please sign in to comment.