Skip to content

Commit 8e2f044

Browse files
authored
KEH-1670: Update cicd pipeline setup (#28)
* update cicd pipeline setup * update Makefile for running local * update the lambda image to latest hash
1 parent da81cf8 commit 8e2f044

7 files changed

Lines changed: 210 additions & 60 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ cd aws_lambda_script
6565

6666
Run the project locally (with UI)
6767
```bash
68-
make run
68+
make run-local
6969
```
7070
or
7171
```bash

aws_lambda_script/Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
FROM public.ecr.aws/lambda/python:3.12
33
# Set the working directory in the container
44
WORKDIR /var/task
5+
# Upgrade tooling and awslambdaric
6+
RUN pip install --no-cache-dir --upgrade pip setuptools wheel && \
7+
pip install --no-cache-dir --upgrade awslambdaric
58
# Create a non-root user and group manually
69
RUN mkdir -p /home/appuser && \
710
chown -R 1000:1000 /home/appuser

aws_lambda_script/app/resources.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,11 +457,13 @@ def post(self):
457457
for user in proj["user"]
458458
if user["email"] in new_project_emails
459459
)
460+
logger.error("PROJECT '%s' WITH EMAIL '%s' ALREADY EXISTS", new_project_name, matching_email)
460461
abort(
461462
409,
462463
description=f"Project with the same name '{new_project_name}', and owner '{matching_email}' already exists",
463464
)
464465
data["projects"].append(new_project)
466+
logger.info("PROJECT '%s' ADDED SUCCESSFULLY", new_project_name)
465467
write_data(data, "new_project_data.json")
466468

467469
return new_project, 201
@@ -559,7 +561,7 @@ def put(self, project_name):
559561

560562
# Update the project details
561563
project.update(updated_project)
562-
564+
logger.info("PROJECT '%s' UPDATED SUCCESSFULLY", project_name)
563565
write_data(data, "duplicates.json")
564566

565567
duplicate_data = read_data("duplicates.json")

aws_lambda_script/requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ botocore==1.35.38
44
flask-restx==1.3.0
55
aws-wsgi==0.2.7
66
pyjwt>=2.7.0
7-
requests==2.32.3
8-
cryptography==43.0.1
7+
requests==2.32.4
8+
cryptography==44.0.1

concourse/ci.yml

Lines changed: 163 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
resource_types:
2+
- name: key-value
3+
type: registry-image
4+
source:
5+
repository: gstack/keyval-resource
6+
17
resources:
28
- name: resource-repo
39
type: git
@@ -6,14 +12,112 @@ resources:
612
uri: https://github.com/ONS-Innovation/keh-tech-audit-tool-api
713
branch: ((branch))
814
access_token: ((github_access_token))
15+
- name: github-release
16+
type: github-release
17+
source:
18+
owner: ONS-Innovation
19+
repository: keh-tech-audit-tool-api
20+
access_token: ((github_access_token))
21+
- name: github-release-tag
22+
type: key-value
23+
source:
24+
file: tag-output/tag
25+
26+
# Define the common terraform task as an anchor
27+
terraform-task: &terraform-task
28+
task: terraform-deploy
29+
privileged: true
30+
config:
31+
platform: linux
32+
image_resource:
33+
type: docker-image
34+
source:
35+
repository: hashicorp/terraform
36+
inputs:
37+
- name: resource-repo
38+
- name: github-release-tag
39+
params:
40+
secrets: ((sdp_((env))_secrets_tat_api))
41+
github_access_token: ((github_access_token))
42+
env: ((env))
43+
branch: ((branch))
44+
run:
45+
path: sh
46+
args:
47+
- -cx
48+
- |
49+
echo "DEBUG: Environment is ${env}"
50+
echo "DEBUG: Tag is ${tag}"
51+
export tag=$(cat github-release-tag/tag)
52+
if [[ "$env" == "prod" ]] && [[ "$branch" != "main" && "$branch" != "master" ]]; then
53+
echo "Not on main branch, skipping terraform for prod."
54+
exit 0
55+
fi
56+
apk add --no-cache jq curl
57+
if [[ "$env" == "prod" && ! "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
58+
echo "ERROR: Tag '$tag' is not in semantic versioning format (vX.Y.Z)"
59+
exit 1
60+
fi
61+
chmod u+x ./resource-repo/concourse/scripts/terraform_infra.sh
62+
./resource-repo/concourse/scripts/terraform_infra.sh
63+
timeout: 30m
964

1065
jobs:
11-
- name: build-and-push
66+
- name: calculate-tag
1267
public: true
1368
plan:
69+
- get: github-release
70+
trigger: true
71+
- get: resource-repo
72+
trigger: false
73+
- task: calculate-tag
74+
config:
75+
platform: linux
76+
image_resource:
77+
type: docker-image
78+
source:
79+
repository: alpine
80+
inputs:
81+
- name: resource-repo
82+
- name: github-release
83+
outputs:
84+
- name: tag-output
85+
params:
86+
branch: ((branch))
87+
run:
88+
path: sh
89+
args:
90+
- -cx
91+
- |
92+
apk add --no-cache git
93+
echo "Calculating tag for branch: ${branch}"
94+
if [[ "$branch" == "main" || "$branch" == "master" ]]; then
95+
# Get the latest tag that matches the format vX.Y.Z
96+
tag=$(git -C resource-repo tag --list 'v[0-9]*.[0-9]*.[0-9]*' | sort -V | tail -n 1)
97+
if [[ -z "$tag" ]]; then
98+
echo "No valid semantic versioning tags (vX.Y.Z) found. Cannot set pipeline."
99+
exit 1
100+
fi
101+
else
102+
# Remove non-alphanumeric characters and take the first 7 characters
103+
tag=$(echo "${branch}" | tr -cd '[:alnum:]' | cut -c1-7)
104+
fi
105+
echo "Calculated tag: ${tag}"
106+
# Write the tag to a file for output
107+
echo "${tag}" > tag-output/tag
108+
- put: github-release-tag
109+
params:
110+
directory: tag-output
111+
112+
- name: deploy-after-github-release-dev
113+
public: true
114+
plan:
115+
- get: github-release-tag
116+
passed: [calculate-tag]
117+
trigger: true
14118
- get: resource-repo
15119
timeout: 5m
16-
- task: build-image
120+
- task: deploy-release
17121
privileged: true
18122
config:
19123
platform: linux
@@ -22,56 +126,85 @@ jobs:
22126
source:
23127
repository: hashicorp/terraform
24128
inputs:
25-
- name: resource-repo
129+
- name: resource-repo
130+
- name: github-release-tag
26131
params:
27132
aws_account_id: ((aws_account_sdp_((env))))
28133
aws_role_arn: arn:aws:iam::((aws_account_sdp_((env)))):role/sdp-concourse-((env))
29134
secrets: ((sdp_((env))_secrets_tat_api))
135+
env: dev
136+
tag: github-release-tag/tag # Use the release tag from the resource
137+
repo_name: ((repo_name))
30138
run: # binary used to build the image
31139
path: sh
32140
args:
33141
- -cx
34142
- |
35143
apk add --no-cache aws-cli podman jq iptables curl
36-
37-
if [[ "((env))" == "prod" ]]; then
38-
tag=$(curl "https://api.github.com/repos/ONS-Innovation/keh-tech-audit-tool-api/releases" | jq -r '.[0].tag_name')
39-
export tag
40-
else
41-
export tag=((tag))
42-
fi
43-
git rev-parse --abbrev-ref HEAD
144+
export repo_name=((repo_name))
145+
export tag=$(cat github-release-tag/tag)
146+
# Write the tag to a file for output
147+
echo "${tag}" > release-tag-output/tag
148+
echo "Using tag: ${tag}"
44149
chmod u+x ./resource-repo/concourse/scripts/assume_role.sh
45150
chmod u+x ./resource-repo/concourse/scripts/build_image.sh
46151
source ./resource-repo/concourse/scripts/assume_role.sh
47152
./resource-repo/concourse/scripts/build_image.sh
48153
timeout: 10m
49-
- task: terraform
154+
- <<: *terraform-task
155+
params:
156+
secrets: ((sdp_((env))_secrets_tat_api))
157+
github_access_token: ((github_access_token))
158+
env: dev
159+
- name: release-build-and-push-prod
160+
public: true
161+
plan:
162+
- get: resource-repo
163+
passed: [deploy-after-github-release-dev]
164+
trigger: false # Manual trigger only
165+
timeout: 5m
166+
- get: github-release-tag
167+
passed: [deploy-after-github-release-dev]
168+
- task: build-image
50169
privileged: true
51170
config:
52171
platform: linux
53172
image_resource:
54173
type: docker-image
55-
source: {repository: hashicorp/terraform}
174+
source:
175+
repository: hashicorp/terraform
56176
inputs:
57-
- name: resource-repo
177+
- name: resource-repo
178+
- name: github-release-tag
58179
params:
59-
secrets: ((sdp_((env))_secrets_tat_api))
60-
github_access_token: ((github_access_token))
180+
aws_account_id: ((aws_account_sdp_prod))
181+
aws_role_arn: arn:aws:iam::((aws_account_sdp_prod)):role/sdp-concourse-prod
182+
secrets: ((sdp_prod_secrets_tat_api))
183+
env: prod
184+
tag: github-release-tag/tag
185+
repo_name: ((repo_name))
186+
branch: ((branch))
61187
run:
62188
path: sh
63189
args:
64-
- -cx
65-
- |
66-
apk add --no-cache jq curl
67-
if [[ "((env))" == "prod" ]]; then
68-
tag=$(curl "https://api.github.com/repos/ONS-Innovation/keh-tech-audit-tool-api/releases" | jq -r '.[0].tag_name')
69-
export tag
70-
else
71-
export tag=((tag))
72-
fi
73-
chmod u+x ./resource-repo/concourse/scripts/terraform_infra.sh
74-
export env=((env))
75-
./resource-repo/concourse/scripts/terraform_infra.sh
76-
timeout: 30m
77-
190+
- -cx
191+
- |
192+
if [[ "$branch" != "main" ]]; then
193+
echo "Not on main branch, skipping build."
194+
exit 0
195+
fi
196+
apk add --no-cache aws-cli podman jq iptables curl
197+
export repo_name=((repo_name))
198+
export tag=$(cat github-release-tag/tag)
199+
echo "Using release tag: ${tag}"
200+
chmod u+x ./resource-repo/concourse/scripts/assume_role.sh
201+
chmod u+x ./resource-repo/concourse/scripts/build_image.sh
202+
source ./resource-repo/concourse/scripts/assume_role.sh
203+
./resource-repo/concourse/scripts/build_image.sh
204+
timeout: 10m
205+
- <<: *terraform-task
206+
params:
207+
github_access_token: ((github_access_token))
208+
env: prod
209+
secrets: ((sdp_prod_secrets_tat_api))
210+

concourse/scripts/set_pipeline.sh

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
1-
repo_name=${1}
1+
#!/bin/bash
2+
set -eo pipefail
3+
# Usage: ./set_pipeline.sh
24

3-
if [[ $# -gt 1 ]]; then
4-
branch=${2}
5-
git rev-parse --verify ${branch}
6-
if [[ $? -ne 0 ]]; then
7-
echo "Branch \"${branch}\" does not exist"
8-
exit 1
9-
fi
10-
else
11-
branch=$(git rev-parse --abbrev-ref HEAD)
12-
fi
5+
# Define repository name
6+
repo_name="tech-audit-tool-api"
137

14-
if [[ ${branch} == "main" || ${branch} == "master" ]]; then
15-
env="prod"
16-
else
17-
env="dev"
8+
# Always use the current branch
9+
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || { echo "Failed to get branch name"; exit 1; })
10+
11+
if ! git rev-parse --verify "${branch}" >/dev/null 2>&1; then
12+
echo "Branch \"${branch}\" does not exist. Cannot set a pipeline without a valid branch."
13+
exit 1
1814
fi
1915

20-
if [[ ${env} == "dev" ]]; then
21-
tag=$(git rev-parse HEAD)
16+
if [[ ${branch} == "main" || ${branch} == "master" ]]; then
17+
pipeline_name=${repo_name}
2218
else
23-
tag=$(git tag | tail -n 1)
19+
# Remove non-alphanumeric characters and take the first 7 characters
20+
sanitized_branch=$(echo "${branch}" | tr -cd '[:alnum:]' | cut -c1-7)
21+
pipeline_name=${repo_name}-${sanitized_branch}
2422
fi
2523

26-
fly -t aws-sdp set-pipeline -c concourse/ci.yml -p ${repo_name}-${branch} -v branch=${branch} -v tag=${tag} -v env=${env}
24+
fly -t aws-sdp set-pipeline -c concourse/ci.yml -p ${pipeline_name} -v branch=${branch} -v repo_name=${repo_name} -v env=dev
25+
echo "Pipeline \"${pipeline_name}\" has been set successfully."
2726

terraform/lambda/main.tf

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,10 @@ resource "aws_security_group" "lambda_sg" {
148148
resource "aws_lambda_function" "tech_audit_lambda" {
149149
function_name = "${var.domain}-${var.service_subdomain}-lambda"
150150
package_type = "Image"
151-
image_uri = "${var.aws_account_id}.dkr.ecr.${var.region}.amazonaws.com/${var.ecr_repository}:${var.container_ver}"
152-
151+
152+
# Use digest instead of tag (immutable)
153+
image_uri = "${var.aws_account_id}.dkr.ecr.${var.region}.amazonaws.com/${var.ecr_repository}@${data.aws_ecr_image.lambda_image.image_digest}"
154+
153155
vpc_config {
154156
subnet_ids = data.terraform_remote_state.vpc.outputs.private_subnets
155157
security_group_ids = [aws_security_group.lambda_sg.id] // Dedicated security group for Lambda function
@@ -162,17 +164,20 @@ resource "aws_lambda_function" "tech_audit_lambda" {
162164

163165
environment {
164166
variables = {
165-
TECH_AUDIT_DATA_BUCKET = data.terraform_remote_state.storage.outputs.tech_audit_data_bucket_name
166-
TECH_AUDIT_SECRET_MANAGER = data.terraform_remote_state.secrets.outputs.secret_name
167-
AWS_COGNITO_TOKEN_URL = "https://${var.domain}-${var.service_subdomain}.auth.eu-west-2.amazoncognito.com/oauth2/token"
167+
TECH_AUDIT_DATA_BUCKET = data.terraform_remote_state.storage.outputs.tech_audit_data_bucket_name
168+
TECH_AUDIT_SECRET_MANAGER = data.terraform_remote_state.secrets.outputs.secret_name
169+
AWS_COGNITO_TOKEN_URL = "https://${var.domain}-${var.service_subdomain}.auth.eu-west-2.amazoncognito.com/oauth2/token"
170+
IMAGE_DIGEST = data.aws_ecr_image.lambda_image.image_digest
171+
IMAGE_TAG = var.container_ver
168172
}
169173
}
170174

171175
depends_on = [
172176
aws_iam_role_policy.lambda_s3_access,
173177
aws_iam_role_policy.lambda_additional_permissions,
174178
aws_iam_role_policy_attachment.lambda_basic_execution,
175-
aws_iam_role_policy_attachment.lambda_vpc_access
179+
aws_iam_role_policy_attachment.lambda_vpc_access,
180+
data.aws_ecr_image.lambda_image
176181
]
177182
}
178183

@@ -181,6 +186,14 @@ resource "aws_iam_role_policy_attachment" "lambda_vpc_access" {
181186
role = aws_iam_role.lambda_execution_role.name
182187
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
183188
depends_on = [aws_iam_role.lambda_execution_role]
189+
}
190+
191+
# Resolve the pushed image (must exist before terraform apply)
192+
data "aws_ecr_image" "lambda_image" {
193+
repository_name = var.ecr_repository
194+
image_tag = var.container_ver
195+
}
196+
184197
}
185198

186199
# CloudWatch log group for the Lambda
@@ -190,4 +203,4 @@ resource "aws_cloudwatch_log_group" "lambda_log_group" {
190203
tags = {
191204
Name = "${var.domain}-${var.service_subdomain}-lambda-log-group"
192205
}
193-
}
206+
}

0 commit comments

Comments
 (0)