Serverless code pipelines on AWS

Bo the dog in the winter sun. Photo by the author
Ferris Bueller’s Day Off
  • CodeBuild
  • CodePipeline
% git remote -vorigin ssh://git-codecommit.eu-west-2.amazonaws.com/v1/repos/photo-lambda (fetch)origin ssh://git-codecommit.eu-west-2.amazonaws.com/v1/repos/photo-lambda (push)
IAM Security Credentials
resource "aws_iam_group" "readonly" {
name = "readonly"
path = "/"
}
resource "aws_iam_group_policy_attachment" "readonly" {
for_each = toset([
"arn:aws:iam::aws:policy/ReadOnlyAccess",
"arn:aws:iam::aws:policy/IAMUserChangePassword",
"arn:aws:iam::aws:policy/IAMUserSSHKeys"
])
group = aws_iam_group.readonly.name
policy_arn = each.value
}
locals {
lambda_name = "photo-lambda"
}
resource "aws_codecommit_repository" "photo_lambda" {
repository_name = local.lambda_name
description = "Source for lambda for photo archive"
tags = merge({ "Name" = local.lambda_name }, var.tags)
}
resource "aws_s3_bucket" "build" {
bucket_prefix = "rahookbuild"
acl = "private"
versioning {
enabled = true
}
lifecycle {
prevent_destroy = true
}
tags = merge({ "Name" = "rahook-build" }, var.tags)
}
resource "aws_s3_bucket_public_access_block" "build" {
bucket = aws_s3_bucket.build.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# policy allowing CodeBuild to adopt the roledata "aws_iam_policy_document" "codebuild_assume" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["codebuild.amazonaws.com"]
}
}
}
# Role that CodeBuild will adoptresource "aws_iam_role" "codebuild" {
name = "${local.lambda_name}-codebuild"
description =
"role allowing codebuild access to build ${local.lambda_name}"
assume_role_policy =
data.aws_iam_policy_document.codebuild_assume.json
force_detach_policies = true
tags = merge({ "Name" = local.lambda_name }, var.tags)
}
# Policy controlling the permissions the role hasdata "aws_iam_policy_document" "codebuild" {
statement {
sid = "createlogs"
actions = ["logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"]
resources = [
"arn:aws:logs:${var.aws_region}:${var.aws_account_id}:log-group:/aws/codebuild/lambda",
"arn:aws:logs:${var.aws_region}:${var.aws_account_id}:log-group:/aws/codebuild/lambda:*"
]
}
statement {
sid = "s3"
resources = ["arn:aws:s3:::codepipeline-${var.aws_region}-*"]
actions = ["s3:PutObject",
"s3:GetObject",
"s3:GetObjectVersion",
"s3:GetBucketAcl",
"s3:GetBucketLocation"]
}
statement {
sid = "gitpull"
resources = [aws_codecommit_repository.photo_lambda.arn]
actions = ["codecommit:GitPull"]
}
statement {
sid = "push"
resources = [aws_s3_bucket.build.arn,
"${aws_s3_bucket.build.arn}/*"]
actions = ["s3:PutObject",
"s3:GetBucketAcl",
"s3:GetBucketLocation"]
}
statement {
sid = "read"
resources = [
"${aws_s3_bucket.build.arn}/${local.lambda_name}",
"${aws_s3_bucket.build.arn}/${local.lambda_name}/*"
]
actions = ["s3:GetObject",
"s3:GetObjectAcl"]
}
}
# Attaching the policy to the roleresource "aws_iam_role_policy" "codebuild" {
name = "${local.lambda_name}-codebuild"
role = aws_iam_role.codebuild.name
policy = data.aws_iam_policy_document.codebuild.json
}
  • use S3 for it’s internal purposes;
  • Pull the code from CodeCommit;
  • Push the built artefact into our build bucket;
  • Read the artefact from our build bucket.
resource "aws_cloudwatch_log_group" "lambda" {
name = "/aws/codebuild/lambda"
retention_in_days = 90
tags = merge({ "Name" = "lambda-codebuild" }, var.tags)
}
  • a definition of where the code is coming from;
  • a definition of where we put the generated artefact;
  • the type of ephemeral instance to build on;
  • instructions on where to put our logs.
resource "aws_codebuild_project" "photo_lambda" {
name = local.lambda_name
description =
"project to build the ${local.lambda_name} lambda functions"
service_role = aws_iam_role.codebuild.arn
build_timeout = 15
badge_enabled = true
source_version = "refs/heads/master"
source {
git_clone_depth = 1
insecure_ssl = false
location =
aws_codecommit_repository.photo_lambda.clone_url_http
report_build_status = false
type = "CODECOMMIT"
git_submodules_config {
fetch_submodules = false
}
}
artifacts {
encryption_disabled = false
location = aws_s3_bucket.build.id
name = local.lambda_name
namespace_type = "NONE"
override_artifact_name = true
packaging = "ZIP"
path = local.lambda_name
type = "S3"
}
environment {
compute_type = "BUILD_GENERAL1_SMALL"
image =
"aws/codebuild/amazonlinux2-x86_64-standard:3.0"
image_pull_credentials_type = "CODEBUILD"
privileged_mode = false
type = "LINUX_CONTAINER"
}
logs_config {
cloudwatch_logs {
status = "ENABLED"
group_name = aws_cloudwatch_log_group.lambda.name
}
s3_logs {
encryption_disabled = false
status = "DISABLED"
}
}
tags = merge({ "Name" = local.lambda_name }, var.tags)
}
version: 0.2phases:
install:
runtime-versions:
golang: latest
pre_build:
commands:
- echo Build started
- go test
build:
commands:
- go build
post_build:
commands:
- echo Build completed
artifacts:
files:
- photo-lambda
name: photo-lambda-$(date +%Y%m%d.%H%M%s).zip
# policy to allow CodePipeline to adopt a roledata "aws_iam_policy_document" "pipeline_assume" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["codepipeline.amazonaws.com"]
}
}
}
# the role for CodePipeline to adoptresource "aws_iam_role" "pipeline" {
name = "${local.lambda_name}-pipeline"
description =
"role allowing pipeline access to relevant resources"
assume_role_policy =
data.aws_iam_policy_document.pipeline_assume.json
force_detach_policies = true
tags = merge({ "Name" = local.lambda_name }, var.tags)
}
# The permissions CodePipeline has to act on our behalfresource "aws_iam_role_policy" "pipeline" {
name = "${local.lambda_name}-pipeline"
role = aws_iam_role.pipeline.name
policy = data.aws_iam_policy_document.pipeline.json
}
data "aws_iam_policy_document" "pipeline" {
statement {
actions = ["iam:PassRole"]
resources = ["*"]
condition {
test = "StringEqualsIfExists"
variable = "iam:PassedToService"
values = [
"cloudformation.amazonaws.com",
"elasticbeanstalk.amazonaws.com",
"ec2.amazonaws.com",
"ecs-tasks.amazonaws.com"
]
}
}
statement {
actions = [
"codecommit:CancelUploadArchive",
"codecommit:GetBranch",
"codecommit:GetCommit",
"codecommit:GetRepository",
"codecommit:GetUploadArchiveStatus",
"codecommit:UploadArchive"
]
resources = ["*"]
}
statement {
actions = [
"codedeploy:CreateDeployment",
"codedeploy:GetApplication",
"codedeploy:GetApplicationRevision",
"codedeploy:GetDeployment",
"codedeploy:GetDeploymentConfig",
"codedeploy:RegisterApplicationRevision"
]
resources = ["*"]
}
statement {
actions = ["codestar-connections:UseConnection"]
resources = ["*"]
}
statement {
actions = [
"elasticbeanstalk:*",
"ec2:*",
"elasticloadbalancing:*",
"autoscaling:*",
"cloudwatch:*",
"s3:*",
"sns:*",
"cloudformation:*",
"rds:*",
"sqs:*",
"ecs:*"
]
resources = ["*"]
}
statement {
actions = ["lambda:InvokeFunction", "lambda:ListFunctions"]
resources = ["*"]
}
statement {
actions = ["opsworks:CreateDeployment",
"opsworks:DescribeApps",
"opsworks:DescribeCommands",
"opsworks:DescribeDeployments",
"opsworks:DescribeInstances",
"opsworks:DescribeStacks",
"opsworks:UpdateApp",
"opsworks:UpdateStack"
]
resources = ["*"]
}
statement {
actions = ["cloudformation:CreateStack",
"cloudformation:DeleteStack",
"cloudformation:DescribeStacks",
"cloudformation:UpdateStack",
"cloudformation:CreateChangeSet",
"cloudformation:DeleteChangeSet",
"cloudformation:DescribeChangeSet",
"cloudformation:ExecuteChangeSet",
"cloudformation:SetStackPolicy",
"cloudformation:ValidateTemplate"
]
resources = ["*"]
}
statement {
actions = ["codebuild:BatchGetBuilds",
"codebuild:StartBuild",
"codebuild:BatchGetBuildBatches",
"codebuild:StartBuildBatch"
]
resources = ["*"]
}
statement {
actions = ["devicefarm:ListProjects",
"devicefarm:ListDevicePools",
"devicefarm:GetRun",
"devicefarm:GetUpload",
"devicefarm:CreateUpload",
"devicefarm:ScheduleRun"
]
resources = ["*"]
}
statement {
actions = ["servicecatalog:ListProvisioningArtifacts",
"servicecatalog:CreateProvisioningArtifact",
"servicecatalog:DescribeProvisioningArtifact",
"servicecatalog:DeleteProvisioningArtifact",
"servicecatalog:UpdateProduct"
]
resources = ["*"]
}
statement {
actions = ["cloudformation:ValidateTemplate"]
resources = ["*"]
}
statement {
actions = ["ecr:DescribeImages"]
resources = ["*"]
}
statement {
actions = ["states:DescribeExecution",
"states:DescribeStateMachine",
"states:StartExecution"
]
resources = ["*"]
}
statement {
actions = ["appconfig:StartDeployment",
"appconfig:StopDeployment",
"appconfig:GetDeployment"
]
resources = ["*"]
}
}
resource "aws_codepipeline" "photo_lambda" {
name = local.lambda_name
role_arn = aws_iam_role.pipeline.arn
tags = merge({ "Name" = local.lambda_name }, var.tags)
artifact_store {
location = aws_s3_bucket.build.id
type = "S3"
}
stage {
name = "Source"
action {
category = "Source"
owner = "AWS"
name = "Source"
provider = "CodeCommit"
version = "1"
configuration = {
"BranchName" = "master"
"OutputArtifactFormat" = "CODEBUILD_CLONE_REF"
"PollForSourceChanges" = "false"
"RepositoryName" =
aws_codecommit_repository.photo_lambda.repository_name
}
input_artifacts = []
output_artifacts = ["SourceArtifact"]
namespace = "SourceVariables"
region = var.aws_region
run_order = 1
}
}
stage {
name = "Build"
action {
category = "Build"
owner = "AWS"
name = "Build"
provider = "CodeBuild"
version = "1"
configuration = {
"ProjectName" = aws_codebuild_project.photo_lambda.name
}
input_artifacts = ["SourceArtifact"]
output_artifacts = ["BuildArtifact"]
namespace = "BuildVariables"
region = var.aws_region
run_order = 1
}
}
stage {
name = "Deploy"
action {
category = "Deploy"
owner = "AWS"
name = "Deploy_to_S3"
provider = "S3"
version = "1"
configuration = {
"BucketName" = aws_s3_bucket.build.id
"Extract" = "false"
"ObjectKey" =
"${local.lambda_name}/${local.lambda_name}.{datetime}.zip"
}
input_artifacts = ["BuildArtifact", ]
output_artifacts = []
region = var.aws_region
run_order = 1
}
}
}
resource "aws_cloudwatch_event_rule" "lambda" {
name = "${local.lambda_name}-build"
description = "Rule to start pipeline on commit into ${local.lambda_name} CodeCommit"
is_enabled = true
event_pattern = jsonencode(
{
detail = {
event = ["referenceCreated", "referenceUpdated"]
referenceName = ["master"]
referenceType = ["branch"]
}
detail-type = ["CodeCommit Repository State Change"]
resources = [aws_codecommit_repository.photo_lambda.arn]
source = ["aws.codecommit"]
}
)
tags = merge({ "Name" = local.lambda_name }, var.tags)
}
resource "aws_cloudwatch_event_target" "lambda" {
target_id = local.lambda_name
rule = aws_cloudwatch_event_rule.lambda.name
arn = aws_codepipeline.photo_lambda.arn
role_arn = aws_iam_role.event.arn
}
# policy that allows CloudWatch to adopt the roledata "aws_iam_policy_document" "event_assume" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["events.amazonaws.com"]
}
}
}
# Role for CloudWatch to adoptresource "aws_iam_role" "event" {
name = "${local.lambda_name}-pipeline-trigger"
description = "this allows EventBridge to trigger the pipeline when it notices a commit"
assume_role_policy =
data.aws_iam_policy_document.event_assume.json
force_detach_policies = true
tags = merge({ "Name" = "${local.lambda_name}-pipeline-trigger" }, var.tags)
}
# Permissions for CloudWatch to start the pipeline on our behalfdata "aws_iam_policy_document" "event" {
statement {
sid = "start"
resources = [aws_codepipeline.photo_lambda.arn]
actions = ["codepipeline:StartPipelineExecution"]
}
}
# And attach those permissions to the adopted roleresource "aws_iam_role_policy" "event" {
name = "${local.lambda_name}-pipeline-trigger"
role = aws_iam_role.event.name
policy = data.aws_iam_policy_document.event.json
}

Founder and CTO of Little Dog Digital, with a belief that technology can be simple, easy and fun. 30+ yearsbuilding robust, secure data driven solutions.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store