Loading repository data…
Loading repository data…
camposvinicius / repository
This is an ETL application on AWS with general open sales and customer data that you can find here: https://github.com/camposvinicius/data/blob/main/AdventureWorks.zip, it's a zipped file with some .csvs inside that we will apply transformations.
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
This is an ETL application on AWS with general open sales and customer data that you can find here: https://github.com/camposvinicius/data/blob/main/AdventureWorks.zip, it's a zipped file with some .csvs inside that we will apply transformations.
List of tools we will be using:
verify.yml for testing and validation of resource constructiondeploy.yml for building resourcesdestroy.yml for resource destructionFirst of all, we need to create a bucket in AWS that will hold the state of our infrastructure.

Now let's talk about the codes for building our resources with Terraform.
As mentioned earlier, it is being created to store the state of our infrastructure in our bucket.
terraform {
backend "s3" {
bucket = "tfstate-vini-campos-etl-aws-poc"
key = "terraform/tfstate"
region = "us-east-1"
}
}
Since we're going to be ingesting data in redshift, we're going to need to create a few things. Here we are basically creating a VPC, a gateway, a security group, two subnets, a security group, a policy with full permissions on s3, a role and the redshift cluster, of the single-node type.
resource "aws_vpc" "redshift_vpc" {
cidr_block = "10.0.0.0/16"
instance_tenancy = "default"
tags = {
Name = "redshift-vpc"
}
}
resource "aws_internet_gateway" "redshift_vpc_gw" {
vpc_id = aws_vpc.redshift_vpc.id
depends_on = [
aws_vpc.redshift_vpc
]
}
resource "aws_default_security_group" "redshift_security_group" {
vpc_id = aws_vpc.redshift_vpc.id
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "redshift-sg"
}
depends_on = [
aws_vpc.redshift_vpc
]
}
resource "aws_subnet" "redshift_subnet_1" {
vpc_id = aws_vpc.redshift_vpc.id
cidr_block = "10.0.1.0/28"
availability_zone = "us-east-1a"
map_public_ip_on_launch = "true"
tags = {
Name = "redshift-subnet-1"
}
depends_on = [
aws_vpc.redshift_vpc
]
}
resource "aws_subnet" "redshift_subnet_2" {
vpc_id = aws_vpc.redshift_vpc.id
cidr_block = "10.0.32.0/20"
availability_zone = "us-east-1a"
map_public_ip_on_launch = "true"
tags = {
Name = "redshift-subnet-2"
}
depends_on = [
aws_vpc.redshift_vpc
]
}
resource "aws_redshift_subnet_group" "redshift_subnet_group" {
name = "redshift-subnet-group"
subnet_ids = [
aws_subnet.redshift_subnet_1.id,
aws_subnet.redshift_subnet_2.id
]
tags = {
environment = "vini-etl-aws"
Name = "redshift-subnet-group"
}
}
resource "aws_iam_role_policy" "s3_full_access_policy" {
name = "redshift_s3_policy"
role = aws_iam_role.redshift_role.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
]
}
EOF
}
resource "aws_iam_role" "redshift_role" {
name = "redshift_role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "redshift.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
tags = {
tag-key = "redshift-role"
}
}
resource "aws_redshift_cluster" "default" {
cluster_identifier = "redshift-cluster-etl-vini"
database_name = var.redshift_db
master_username = var.redshift_user
master_password = var.redshift_pass
node_type = "dc2.large"
cluster_type = "single-node"
skip_final_snapshot = true
publicly_accessible = true
iam_roles = ["${aws_iam_role.redshift_role.arn}"]
tags = {
tag-key = "vini-cluster-redshift-etl-aws"
}
depends_on = [
aws_vpc.redshift_vpc,
aws_default_security_group.redshift_security_group,
aws_redshift_subnet_group.redshift_subnet_group,
aws_iam_role.redshift_role
]
}
Here we are creating an eks cluster through a module, with two 16gb ram machines, one focused on memory and the other on processing.
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "17.24.0"
cluster_name = var.cluster_name
cluster_version = "1.21"
subnets = module.vpc.private_subnets
tags = {
Vini = "ETL-AWS"
}
vpc_id = module.vpc.vpc_id
workers_group_defaults = {
root_volume_type = "gp2"
}
worker_groups = [
{
name = "worker-group-1"
instance_type = "r5.xlarge"
asg_desired_capacity = 1
additional_security_group_ids = [aws_security_group.worker_group_mgmt_one.id]
},
{
name = "worker-group-2"
instance_type = "c5.2xlarge"
additional_security_group_ids = [aws_security_group.worker_group_mgmt_two.id]
asg_desired_capacity = 1
}
]
}
data "aws_eks_cluster" "cluster" {
name = module.eks.cluster_id
}
data "aws_eks_cluster_auth" "cluster" {
name = module.eks.cluster_id
}
Basically here is creating a bucket that will store all our code and other dependency files of our spark job as pyfiles, and after this creation, it will upload these files.
resource "aws_s3_bucket" "emr_codes_bucket" {
bucket = "emr-code-zone-vini-etl-aws"
force_destroy = true
}
resource "aws_s3_bucket" "athena-results" {
bucket = "athena-results-vini-etl-aws"
force_destroy = true
}
resource "aws_s3_bucket_object" "codes_object" {
for_each = fileset("../codes/", "*")
bucket = aws_s3_bucket.emr_codes_bucket.id
key = each.key
source = "../codes/${each.key}"
force_destroy = true
depends_on = [aws_s3_bucket.emr_codes_bucket]
}
Here we basically have the creation of a database and a crawler in glue to use in our pipeline, in addition to some policies and roles so we don't have problems with permission levels.
resource "aws_glue_catalog_database" "aws_glue_catalog_database" {
name = "vini-database-etl-aws"
}
resource "aws_iam_role" "glue_role" {
name = "glue_role"
assume_role_policy = data.aws_iam_policy_document.glue-assume-role-policy.json
}
resource "aws_glue_crawler" "glue_crawler" {
database_name = aws_glue_catalog_database.aws_glue_catalog_database.name
name = "CrawlerETLAWSVini"
role = aws_iam_role.glue_role.arn
s3_target {
path = "s3://curated-zone-vini-etl-aws/curated/"
}
depends_on = [
aws_glue_catalog_database.aws_glue_catalog_database,
aws_iam_role.glue_role
]
}
data "aws_iam_policy_document" "glue-assume-role-policy" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["glue.amazonaws.com"]
}
}
}
resource "aws_iam_policy" "extra-policy" {
name = "extra-policy"
policy = data.aws_iam_policy_document.extra-policy-document.json
}
data "aws_iam_policy_document" "extra-policy-document" {
statement {
actions = [
"s3:GetBucketLocation", "s3:ListBucket", "s3:ListAllMyBuckets", "s3:GetBucketAcl", "s3:GetObject"]
resources = [
"arn:aws:s3:::curated-zone-vini-etl-aws",
"arn:aws:s3:::curated-zone-vini-etl-aws/*"
]
}
}
resource "aws_iam_role_policy_attachment" "extra-policy-attachment" {
role = aws_iam_role.glue_role.name
policy_arn = aws_iam_policy.extra-policy.arn
}
resource "aws_iam_role_policy_attachment" "glue-service-role-attachment" {
role = aws_iam_role.glue_role.name
policy_arn = data.aws_iam_policy.AWSGlueServiceRole.arn
}
data "aws_iam_policy" "AWSGlueServiceRole" {
arn = "arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole"
}
This is optional, only if you need to connect to a cluster for debugging at some point in the pipeline.
resource "aws_key_pair" "my-key" {
key_name = "my-key"
public_key = "YOUR-PUBLIC-KEY"
}
Here we are basically creating a role and assuming a policy with permission for all resources, in addition to our lambda function, with its characteristics.
resource "aws_iam_role" "iam_for_lambda" {
name = "iam_for_lambda"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_policy" "policy" {
name = "iam_for_lambda_policy"
policy = <<-EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "policy-attach" {
role = aws_iam_role.iam_for_lambda.name
policy_arn = aws_iam_policy.policy.arn
}
resource "aws_lambda_function" "lambda_function" {
function_name = "myfunction"
filename = "lambda_function.zip"
role = aws_iam_role.iam_for_lambda.arn
handler = "lambda_function.lambda_handler"
memory_size = 1000
timeout = 120
source_code_hash = filebase64sha256("lambda_function.zip")
runtime = "python3.9"
}
First, let's understand what our lambda function is doing. In a simple and summarized way, the function makes a request for a web link (in the case of github from my repository that I made available), for a zipped file, then uploads it to a bucket and then unzips the files in the bucket itself.
import requests, io, tempfile, os, boto3
from zipfile import ZipFile
file_name = 'AdventureWorks.zip'
bucket = "landing-zone-vini-poc-etl-aws"
folder_temp_name = 'temp'
url = 'https://github.com/camposvinicius/data/raw/main/AdventureWorks.zip'
def lambda_handler(event, context):
with tempfile.TemporaryDirectory() as temp_path:
temp_dir = os.path.join(temp_path, folder_temp_name)
with open(temp_dir, 'wb') as f:
req = requests.get(url)
f.write(req.content)
s3 = boto3.resource('s3')
s3.Bucket(bucket).upload_file(temp_dir, file_name)
zip_obj = s3.Object(bucket_name=bucket, key=file_name)
buffer = io.BytesIO(zip_obj.get()["Body"].read())
z = ZipFile(buffer)
for filename in z.namelist():
file_info = z.getinfo(filename)
s3.meta.client.upload_fileobj(
z.open(filename),
Bucket=bucket,
Key='data/' + f'{filename}')
for file in s3.Bucket(bucket).objects.all():
print(file.key)
Here we only have the external library that we have as a dependency of our function to pack in our function.
requests>=2.26