Skip to content

Commit

Permalink
feat: DBTP-1502 - Terraform image build codebuild project (#274)
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 12, 2024
1 parent 254d606 commit 4f44598
Show file tree
Hide file tree
Showing 8 changed files with 636 additions and 0 deletions.
48 changes: 48 additions & 0 deletions codebase-pipelines/buildspec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
version: 0.2

env:
parameter-store:
SLACK_CHANNEL_ID: /codebuild/slack_oauth_channel
SLACK_TOKEN: /codebuild/slack_oauth_token

phases:
install:
commands:
- >
if [ -f .copilot/phases/install.sh ]; then
bash .copilot/phases/install.sh;
fi
pre_build:
commands:
- >
if [ -f .copilot/phases/pre_build.sh ]; then
bash .copilot/phases/pre_build.sh;
fi
build:
commands:
- >
if [ -f .copilot/phases/build.sh ]; then
bash .copilot/phases/build.sh;
fi
- /work/cli build --publish --send-notifications

post_build:
commands:
- >
if [ -f .copilot/phases/post_build.sh ]; then
bash .copilot/phases/post_build.sh;
fi
if [ "${CODEBUILD_BUILD_SUCCEEDING}" != "1" ]; then
BUILD_ID_PREFIX=$(echo $CODEBUILD_BUILD_ID | cut -d':' -f1)
echo "BUILD_ID_PREFIX - ${BUILD_ID_PREFIX}"
echo -e "\nInstalling dependencies"
pip install dbt-platform-helper
MESSAGE=":no_entry::building_construction: Image build failure in codebuild project: <https://eu-west-2.console.aws.amazon.com/codesuite/codebuild/${AWS_ACCOUNT_ID}/projects/${BUILD_ID_PREFIX}/build/${CODEBUILD_BUILD_ID}/?region=eu-west-2|${BUILD_ID_PREFIX} - build ${CODEBUILD_BUILD_NUMBER}>"
platform-helper notify add-comment "${SLACK_CHANNEL_ID}" "${SLACK_TOKEN}" "" "${MESSAGE}"
fi
115 changes: 115 additions & 0 deletions codebase-pipelines/codebuild.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
data "aws_codestarconnections_connection" "github_codestar_connection" {
name = var.application
}

resource "aws_codebuild_project" "codebase_image_build" {
name = "${var.application}-${var.codebase}-codebase-image-build"
description = "Publish images on push to ${var.repository}"
build_timeout = 30
service_role = aws_iam_role.codebase_image_build.arn
badge_enabled = true

artifacts {
type = "NO_ARTIFACTS"
}

cache {
type = "LOCAL"
modes = ["LOCAL_DOCKER_LAYER_CACHE"]
}

environment {
compute_type = "BUILD_GENERAL1_SMALL"
image = "public.ecr.aws/uktrade/ci-image-builder:tag-latest"
type = "LINUX_CONTAINER"

environment_variable {
name = "AWS_ACCOUNT_ID"
value = data.aws_caller_identity.current.account_id
}

environment_variable {
name = "ECR_REPOSITORY"
value = local.ecr_name
}

environment_variable {
name = "CODESTAR_CONNECTION_ARN"
value = data.aws_codestarconnections_connection.github_codestar_connection.arn
}

dynamic "environment_variable" {
for_each = var.additional_ecr_repository != null ? [1] : []
content {
name = "ADDITIONAL_ECR_REPOSITORY"
value = var.additional_ecr_repository
}
}
}

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

source {
type = "GITHUB"
buildspec = file("${path.module}/buildspec.yml")
location = "https://github.com/${var.repository}.git"
git_clone_depth = 0
git_submodules_config {
fetch_submodules = false
}
}

tags = local.tags
}

resource "aws_cloudwatch_log_group" "codebase_image_build" {
# 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-image-build/log-group"
retention_in_days = 90
}

resource "aws_cloudwatch_log_stream" "codebase_image_build" {
name = "codebuild/${var.application}-${var.codebase}-codebase-image-build/log-stream"
log_group_name = aws_cloudwatch_log_group.codebase_image_build.name
}

resource "aws_codebuild_webhook" "codebuild_webhook" {
project_name = aws_codebuild_project.codebase_image_build.name
build_type = "BUILD"

dynamic "filter_group" {
for_each = local.pipeline_branches
content {
filter {
type = "EVENT"
pattern = "PUSH"
}

filter {
type = "HEAD_REF"
pattern = "^refs/heads/${filter_group.value}$"
}
}
}

dynamic "filter_group" {
for_each = local.tagged_pipeline ? [1] : []
content {
filter {
type = "EVENT"
pattern = "PUSH"
}

filter {
type = "HEAD_REF"
pattern = "^refs/tags/.*"
}
}
}
}
14 changes: 14 additions & 0 deletions codebase-pipelines/ecr.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
resource "aws_ecr_repository" "this" {
# checkov:skip=CKV_AWS_136:Not using KMS to encrypt repositories
# checkov:skip=CKV_AWS_51:ECR image tags can't be immutable
name = local.ecr_name

tags = {
copilot-pipeline = var.codebase
copilot-application = var.application
}

image_scanning_configuration {
scan_on_push = true
}
}
165 changes: 165 additions & 0 deletions codebase-pipelines/iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

resource "aws_iam_role" "codebase_image_build" {
name = "${var.application}-${var.codebase}-codebase-image-build"
assume_role_policy = data.aws_iam_policy_document.assume_codebuild_role.json
tags = local.tags
}

data "aws_iam_policy_document" "assume_codebuild_role" {
statement {
effect = "Allow"

principals {
type = "Service"
identifiers = ["codebuild.amazonaws.com"]
}

actions = ["sts:AssumeRole"]
}
}

resource "aws_iam_role_policy_attachment" "ssm_access" {
role = aws_iam_role.codebase_image_build.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess"
}

resource "aws_iam_role_policy" "codebuild_logs" {
name = "log-policy"
role = aws_iam_role.codebase_image_build.name
policy = data.aws_iam_policy_document.codebuild_logs.json
}

data "aws_iam_policy_document" "codebuild_logs" {
statement {
effect = "Allow"
actions = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:TagLogGroup"
]
resources = [
aws_cloudwatch_log_group.codebase_image_build.arn,
"${aws_cloudwatch_log_group.codebase_image_build.arn}:*",
"arn:aws:logs:${local.account_region}:log-group:*"
]
}

statement {
effect = "Allow"
actions = [
"codebuild:CreateReportGroup",
"codebuild:CreateReport",
"codebuild:UpdateReport",
"codebuild:BatchPutTestCases",
"codebuild:BatchPutCodeCoverages"
]
resources = [
"arn:aws:codebuild:${local.account_region}:report-group/${aws_codebuild_project.codebase_image_build.name}-*",
"arn:aws:codebuild:${local.account_region}:report-group/pipeline-${var.application}-*"
]
}
}

resource "aws_iam_role_policy" "ecr_access" {
name = "ecr-policy"
role = aws_iam_role.codebase_image_build.name
policy = data.aws_iam_policy_document.ecr_access.json
}

data "aws_iam_policy_document" "ecr_access" {
statement {
effect = "Allow"
actions = [
"ecr:GetAuthorizationToken"
]
resources = [
"arn:aws:codebuild:${local.account_region}:report-group/pipeline-${var.application}-*"
]
}

statement {
# checkov:skip=CKV_AWS_107:GetAuthorizationToken required for ci-image-builder
effect = "Allow"
actions = [
"ecr:GetAuthorizationToken",
"ecr-public:GetAuthorizationToken",
"sts:GetServiceBearerToken"
]
resources = [
"*"
]
}

statement {
effect = "Allow"
actions = [
"ecr-public:DescribeImageScanFindings",
"ecr-public:GetLifecyclePolicyPreview",
"ecr-public:GetDownloadUrlForLayer",
"ecr-public:BatchGetImage",
"ecr-public:DescribeImages",
"ecr-public:ListTagsForResource",
"ecr-public:BatchCheckLayerAvailability",
"ecr-public:GetLifecyclePolicy",
"ecr-public:GetRepositoryPolicy",
"ecr-public:PutImage",
"ecr-public:InitiateLayerUpload",
"ecr-public:UploadLayerPart",
"ecr-public:CompleteLayerUpload",
"ecr-public:BatchDeleteImage",
"ecr-public:DescribeRepositories",
"ecr-public:ListImages"
]
resources = [
# We have to wildcard the repository name because we currently expect the repository URL and it's not possible to get the ARN from that
"arn:aws:ecr-public::${data.aws_caller_identity.current.account_id}:repository/*"
]
}

statement {
effect = "Allow"
actions = [
"ecr:DescribeImageScanFindings",
"ecr:GetLifecyclePolicyPreview",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:DescribeImages",
"ecr:ListTagsForResource",
"ecr:BatchCheckLayerAvailability",
"ecr:GetLifecyclePolicy",
"ecr:GetRepositoryPolicy",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:BatchDeleteImage",
"ecr:DescribeRepositories",
"ecr:ListImages"
]
resources = [
aws_ecr_repository.this.arn
]
}
}

resource "aws_iam_role_policy" "codestar_connection_access" {
name = "codestar-connection-policy"
role = aws_iam_role.codebase_image_build.name
policy = data.aws_iam_policy_document.codestar_connection_access.json
}

data "aws_iam_policy_document" "codestar_connection_access" {
statement {
effect = "Allow"
actions = [
"codestar-connections:GetConnectionToken",
"codestar-connections:UseConnection"
]
resources = [
data.aws_codestarconnections_connection.github_codestar_connection.arn
]
}
}
13 changes: 13 additions & 0 deletions codebase-pipelines/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
locals {
tags = {
application = var.application
copilot-application = var.application
managed-by = "DBT Platform - Terraform"
}

account_region = "${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}"

ecr_name = "${var.application}/${var.codebase}"
pipeline_branches = distinct([for pipeline in var.pipelines : pipeline.branch if lookup(pipeline, "branch", null) != null])
tagged_pipeline = length([for pipeline in var.pipelines : true if lookup(pipeline, "tag", null) == true]) > 0
}
9 changes: 9 additions & 0 deletions codebase-pipelines/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
terraform {
required_version = "~> 1.7"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5"
}
}
}
Loading

0 comments on commit 4f44598

Please sign in to comment.