Skip to content

Commit

Permalink
feat: DBTP-1503 - Terraform codebase pipelines (#276)
Browse files Browse the repository at this point in the history
Co-authored-by: James Francis <[email protected]>
  • Loading branch information
JohnStainsby and james-francis-MT authored Nov 26, 2024
1 parent 3937e53 commit 96b5935
Show file tree
Hide file tree
Showing 11 changed files with 2,234 additions and 31 deletions.
107 changes: 107 additions & 0 deletions codebase-pipelines/artifactstore.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
resource "aws_s3_bucket" "artifact_store" {
# checkov:skip=CKV_AWS_144: It's just a pipeline artifacts bucket, cross-region replication is not needed.
# checkov:skip=CKV2_AWS_62: It's just a pipeline artifacts bucket, event notifications are not needed.
# checkov:skip=CKV_AWS_21: It's just a pipeline artifacts bucket, versioning is not needed.
# checkov:skip=CKV_AWS_18: It's just a pipeline artifacts bucket, access logging is not needed.
bucket = "${var.application}-${var.codebase}-codebase-pipeline-artifact-store"

tags = local.tags
}

resource "aws_s3_bucket_lifecycle_configuration" "lifecycle_rule" {
bucket = aws_s3_bucket.artifact_store.id

rule {
id = "delete-after-7-days"
status = "Enabled"

abort_incomplete_multipart_upload {
days_after_initiation = 1
}

expiration {
days = 7
}
}
}

data "aws_iam_policy_document" "artifact_store_bucket_policy" {
statement {
principals {
type = "*"
identifiers = ["*"]
}

actions = [
"s3:*",
]

effect = "Deny"

condition {
test = "Bool"
variable = "aws:SecureTransport"

values = [
"false",
]
}

resources = [
aws_s3_bucket.artifact_store.arn,
"${aws_s3_bucket.artifact_store.arn}/*",
]
}
}

resource "aws_s3_bucket_policy" "artifact_store_bucket_policy" {
bucket = aws_s3_bucket.artifact_store.id
policy = data.aws_iam_policy_document.artifact_store_bucket_policy.json
}

resource "aws_kms_key" "artifact_store_kms_key" {
# checkov:skip=CKV_AWS_7:We are not currently rotating the keys
description = "KMS Key for S3 encryption"
tags = local.tags

policy = jsonencode({
Statement = [
{
"Sid" : "Enable IAM User Permissions",
"Effect" : "Allow",
"Principal" : {
"AWS" : "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
},
"Action" : "kms:*",
"Resource" : "*"
}
]
Version = "2012-10-17"
})
}

resource "aws_kms_alias" "artifact_store_kms_alias" {
depends_on = [aws_kms_key.artifact_store_kms_key]
name = "alias/${var.application}-${var.codebase}-codebase-pipeline-artifact-store-key"
target_key_id = aws_kms_key.artifact_store_kms_key.id
}

resource "aws_s3_bucket_server_side_encryption_configuration" "encryption-config" {
# checkov:skip=CKV2_AWS_67:We are not currently rotating the keys
bucket = aws_s3_bucket.artifact_store.id

rule {
apply_server_side_encryption_by_default {
kms_master_key_id = aws_kms_key.artifact_store_kms_key.arn
sse_algorithm = "aws:kms"
}
}
}

resource "aws_s3_bucket_public_access_block" "public_access_block" {
bucket = aws_s3_bucket.artifact_store.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
File renamed without changes.
33 changes: 33 additions & 0 deletions codebase-pipelines/buildspec-manifests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
version: 0.2

env:
${jsonencode({
exported-variables: flatten([
for env in environments : [
"CLUSTER_NAME_${env}",
[ for svc in services: "SERVICE_NAME_${env}_${svc}" ]
]
])
})}

phases:
build:
commands:
- set -e
- for env in $(echo $ENVIRONMENTS | jq -c -r '.[]');
do
EXPORT_ENV=$(echo $env | tr '[:lower:]' '[:upper:]');
export CLUSTER_NAME_$EXPORT_ENV="$APPLICATION-$env";
for svc in $(echo $SERVICES | jq -c -r '.[]');
do
echo '[{"name":"'$svc'","imageUri":"'$REPOSITORY_URL':'$IMAGE_TAG'"}]' > image-definitions-$svc.json;
SERVICE_NAME=$(aws ecs list-services --cluster $APPLICATION-$env | jq -r '.serviceArns[] | select(contains("'$APPLICATION-$env'-'$svc'-Service"))');
EXPORT_SVC="$(echo $EXPORT_ENV'_'$svc | tr - _ | tr '[:lower:]' '[:upper:]')";
export SERVICE_NAME_$EXPORT_SVC=$(echo $SERVICE_NAME | cut -d '/' -f3);
cat image-definitions-$svc.json;
done
done

artifacts:
files:
- "**/*"
82 changes: 81 additions & 1 deletion codebase-pipelines/codebuild.tf
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ resource "aws_codebuild_project" "codebase_image_build" {

source {
type = "GITHUB"
buildspec = file("${path.module}/buildspec.yml")
buildspec = file("${path.module}/buildspec-images.yml")
location = "https://github.com/${var.repository}.git"
git_clone_depth = 0
git_submodules_config {
Expand Down Expand Up @@ -113,3 +113,83 @@ resource "aws_codebuild_webhook" "codebuild_webhook" {
}
}
}


resource "aws_codebuild_project" "codebase_deploy_manifests" {
for_each = local.pipeline_map
name = "${var.application}-${var.codebase}-${each.value.name}-codebase-deploy-manifests"
description = "Create image deploy manifests to deploy services"
build_timeout = 5
service_role = aws_iam_role.codebuild_manifests.arn
encryption_key = aws_kms_key.artifact_store_kms_key.arn

artifacts {
type = "CODEPIPELINE"
}

cache {
type = "S3"
location = aws_s3_bucket.artifact_store.bucket
}

environment {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/amazonlinux2-x86_64-standard:5.0"
type = "LINUX_CONTAINER"
image_pull_credentials_type = "CODEBUILD"
}

logs_config {
cloudwatch_logs {
group_name = aws_cloudwatch_log_group.codebase_deploy_manifests.name
stream_name = aws_cloudwatch_log_stream.codebase_deploy_manifests.name
}
}

source {
type = "CODEPIPELINE"
buildspec = templatefile("${path.module}/buildspec-manifests.yml", {
application = var.application,
environments = [
for env in each.value.environments : upper(env.name)
],
services = local.service_export_names
})
}

tags = local.tags
}

resource "aws_kms_key" "codebuild_kms_key" {
description = "KMS Key for ${var.application} ${var.codebase} CodeBuild encryption"
enable_key_rotation = true

policy = jsonencode({
Statement = [
{
"Sid" : "Enable IAM User Permissions",
"Effect" : "Allow",
"Principal" : {
"AWS" : "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
},
"Action" : "kms:*",
"Resource" : "*"
}
]
Version = "2012-10-17"
})

tags = local.tags
}

resource "aws_cloudwatch_log_group" "codebase_deploy_manifests" {
# checkov:skip=CKV_AWS_338:Retains logs for 3 months instead of 1 year
# checkov:skip=CKV_AWS_158:Log groups encrypted using default encryption key instead of KMS CMK
name = "codebuild/${var.application}-${var.codebase}-codebase-deploy-manifests/log-group"
retention_in_days = 90
}

resource "aws_cloudwatch_log_stream" "codebase_deploy_manifests" {
name = "codebuild/${var.application}-${var.codebase}-codebase-deploy-manifests/log-stream"
log_group_name = aws_cloudwatch_log_group.codebase_deploy_manifests.name
}
108 changes: 108 additions & 0 deletions codebase-pipelines/codepipeline.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
resource "aws_codepipeline" "codebase_pipeline" {
for_each = local.pipeline_map
name = "${var.application}-${var.codebase}-${each.value.name}-codebase-pipeline"
role_arn = aws_iam_role.codebase_deploy_pipeline.arn
depends_on = [aws_iam_role_policy.artifact_store_access_for_codebase_pipeline]
pipeline_type = "V2"
execution_mode = "QUEUED"

variable {
name = "IMAGE_TAG"
default_value = coalesce(each.value.tag, false) ? "tag-latest" : "branch-${each.value.branch}"
description = "Tagged image in ECR to deploy"
}

artifact_store {
location = aws_s3_bucket.artifact_store.bucket
type = "S3"

encryption_key {
id = aws_kms_key.artifact_store_kms_key.arn
type = "KMS"
}
}

stage {
name = "Source"

action {
name = "Source"
category = "Source"
owner = "AWS"
provider = "ECR"
version = "1"
namespace = "source_ecr"
output_artifacts = ["source_output"]

configuration = {
RepositoryName = local.ecr_name
ImageTag = coalesce(each.value.tag, false) ? "tag-latest" : "branch-${each.value.branch}"
}
}
}

stage {
name = "Create-Deploy-Manifests"

action {
name = "CreateManifests"
category = "Build"
owner = "AWS"
provider = "CodeBuild"
input_artifacts = ["source_output"]
output_artifacts = ["manifest_output"]
version = "1"
namespace = "build_manifest"

configuration = {
ProjectName = "${var.application}-${var.codebase}-${each.value.name}-codebase-deploy-manifests"
EnvironmentVariables : jsonencode([
{ name : "APPLICATION", value : var.application },
{ name : "ENVIRONMENTS", value : jsonencode([for env in each.value.environments : env.name]) },
{ name : "SERVICES", value : jsonencode(local.services) },
{ name : "REPOSITORY_URL", value : "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/${local.ecr_name}" },
{ name : "IMAGE_TAG", value : "#{variables.IMAGE_TAG}" }
])
}
}
}

dynamic "stage" {
for_each = each.value.environments
content {
name = "Deploy-${stage.value.name}"

dynamic "action" {
for_each = coalesce(stage.value.requires_approval, false) ? [1] : []
content {
name = "Approve-${stage.value.name}"
category = "Approval"
owner = "AWS"
provider = "Manual"
version = "1"
run_order = 1
}
}

dynamic "action" {
for_each = local.service_order_list
content {
name = action.value.name
category = "Deploy"
owner = "AWS"
provider = "ECS"
version = "1"
input_artifacts = ["manifest_output"]
run_order = action.value.order + 1
configuration = {
ClusterName = "#{build_manifest.CLUSTER_NAME_${upper(stage.value.name)}}"
ServiceName = "#{build_manifest.SERVICE_NAME_${upper(stage.value.name)}_${upper(replace(action.value.name, "-", "_"))}}"
FileName = "image-definitions-${action.value.name}.json"
}
}
}
}
}

tags = local.tags
}
Loading

0 comments on commit 96b5935

Please sign in to comment.