Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: DBTP-1503 - Terraform codebase pipelines #276

Merged
merged 42 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
3f34fbf
initial ecr import terraform
james-francis-MT Nov 6, 2024
a59d34b
wip: use mapping when creating ecr repos
james-francis-MT Nov 6, 2024
b997edd
Refactor importing ecr repo
JohnStainsby Nov 6, 2024
f57a2a3
Move for loop out of ecr resource; Add codebuild project and supporti…
JohnStainsby Nov 6, 2024
4c2f12f
Change config variable to pipelines and add codebase name; Add IAM po…
JohnStainsby Nov 7, 2024
d9646bd
Initial codebuild unit test
JohnStainsby Nov 7, 2024
135863f
Add correct image build buildspec
JohnStainsby Nov 7, 2024
83b3c17
Codebuild tests
JohnStainsby Nov 7, 2024
cecc294
Tests for webhook filters
JohnStainsby Nov 7, 2024
9bea12e
ECR tests
JohnStainsby Nov 7, 2024
c4a6d34
Iam policy tests
JohnStainsby Nov 7, 2024
bc6fd58
Permissions fixes
JohnStainsby Nov 7, 2024
1a1d75f
Merge branch 'main' into DBTP-1502-terraform-image-build-codebuild-pr…
JohnStainsby Nov 7, 2024
ec908ed
Fix checkov warnings
JohnStainsby Nov 7, 2024
fc44383
Remove unnecessary codebuild test
JohnStainsby Nov 7, 2024
8cfcfff
Remove commented line
JohnStainsby Nov 8, 2024
7ae1fd6
Initial pipeline config
JohnStainsby Nov 8, 2024
8d4d04b
Work out service deploy stage run order
JohnStainsby Nov 8, 2024
5e723ce
Add ECS deploy configuration
JohnStainsby Nov 8, 2024
43af432
Add codebuild project for creating deploy manifest files
JohnStainsby Nov 8, 2024
3f49bd2
Update buildspec for deploy manifests
JohnStainsby Nov 11, 2024
f177e2d
Pass variables to buildspec template file
JohnStainsby Nov 11, 2024
3e82991
Add source stage; Fix run order list sorting;
JohnStainsby Nov 11, 2024
23776cf
Add manifest codebuild permissions
JohnStainsby Nov 11, 2024
20b6dbf
Tighten ecs permissions
JohnStainsby Nov 12, 2024
e12e9a7
IAM permissions for ECS deploy
JohnStainsby Nov 12, 2024
9f5d0ae
Event bridge trigger for pipelines
JohnStainsby Nov 12, 2024
1028573
Add approval stages
JohnStainsby Nov 12, 2024
e1dc70c
Merge branch 'main' into DBTP-1503-terraform-codebase-pipeline
JohnStainsby Nov 12, 2024
b121509
Refactor codebuild jobs; Formatting
JohnStainsby Nov 12, 2024
33f23a2
Tighten ECS deploy permissions
JohnStainsby Nov 12, 2024
548a022
Test manifest codebuild; Deploy pipeline; Event bridge trigger
JohnStainsby Nov 13, 2024
9e913a0
Test run groups
JohnStainsby Nov 13, 2024
8a17010
Fix checkov comments
JohnStainsby Nov 13, 2024
f5e101f
Add lifecycle policy to pipelines
JohnStainsby Nov 15, 2024
0e2fc2c
Formatting
JohnStainsby Nov 15, 2024
7812c9c
Merge branch 'main' into DBTP-1503-terraform-codebase-pipeline
JohnStainsby Nov 15, 2024
c35d2e7
Add policy document tests
JohnStainsby Nov 20, 2024
50f17d2
Merge branch 'main' into DBTP-1503-terraform-codebase-pipeline
JohnStainsby Nov 21, 2024
e572e50
Add role policy document tests
JohnStainsby Nov 21, 2024
0162671
Fix test error message
JohnStainsby Nov 21, 2024
08d03a6
Merge branch 'main' into DBTP-1503-terraform-codebase-pipeline
JohnStainsby Nov 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = ["*"]
WillGibson marked this conversation as resolved.
Show resolved Hide resolved
}

actions = [
"s3:*",
WillGibson marked this conversation as resolved.
Show resolved Hide resolved
]

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
}
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
Loading