Skip to content

Commit f3dfc6a

Browse files
authored
VED-1157: Ack backend Lambda deployment to a dedicated pipeline stage (#1382)
* Implement ack backend image deployment in CI/CD workflows - Added support for building and deploying the ack backend image in the continuous deployment, backend deployment, and PR workflows. - Introduced new variables for ack backend image version and build options in the relevant YAML files. - Created a new ECR repository for the ack backend and updated the Terraform configuration accordingly. - Enhanced the teardown process to clean up the ack backend image references. This update ensures that the ack backend is properly integrated into the CI/CD pipeline, improving deployment consistency and management. * Refactor image URI resolution in CI/CD teardown workflow and update ECR lifecycle policy format - Simplified the process of resolving image URIs in the PR teardown workflow by introducing a reusable function that provides a placeholder if the output is unavailable. - Updated the ECR lifecycle policy in Terraform to use the `jsonencode` function for better readability and maintainability. * Remove deprecated ECR repository references and associated lifecycle configurations in temp.tf. This change is part of the cleanup process following the ack backend ECR ownership migration. * Refactor CI/CD workflows to streamline lambda image deployment - Replaced deprecated image version variables with a JSON map for lambda image overrides in the continuous deployment workflow. - Consolidated the deployment of lambda images into a single job with a matrix strategy for better maintainability. - Updated output variables to reflect the new image URI structure and removed redundant steps for deploying individual lambda images. - Enhanced documentation for new input parameters related to lambda image deployment. * Refactor deployment workflows to simplify image handling and cleanup process * Refactor backend deployment workflows to use lambda build flags and simplify image build conditions * Enhance deployment workflows by adding tf_var_suffix input for lambda artifacts and updating related processing logic * Fix diff_base_sha assignment for synchronize action in PR deploy workflow * Remove unused ECR lifecycle policy from ackbackend ECR repository configuration * chore: empty commit
1 parent 0c38597 commit f3dfc6a

File tree

9 files changed

+219
-122
lines changed

9 files changed

+219
-122
lines changed

.github/workflows/continuous-deployment.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ jobs:
2020
uses: ./.github/workflows/deploy-backend.yml
2121
with:
2222
apigee_environment: internal-dev
23-
build_recordprocessor_image: false
2423
diff_base_sha: ${{ github.event.before }}
2524
diff_head_sha: ${{ github.sha }}
2625
run_diff_check: true
@@ -86,9 +85,7 @@ jobs:
8685
uses: ./.github/workflows/deploy-backend.yml
8786
with:
8887
apigee_environment: ${{ matrix.sub_environment_name }}
89-
recordprocessor_image_version: ${{ needs.deploy-internal-dev-backend.outputs.recordprocessor_image_version }}
90-
diff_base_sha: ${{ github.event.before }}
91-
diff_head_sha: ${{ github.sha }}
88+
lambda_image_overrides: ${{ needs.deploy-internal-dev-backend.outputs.image_uris_json }}
9289
run_diff_check: false
9390
create_mns_subscription: true
9491
environment: dev

.github/workflows/deploy-backend.yml

Lines changed: 125 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,21 @@ on:
66
apigee_environment:
77
required: true
88
type: string
9-
build_recordprocessor_image:
9+
lambda_build_flags:
10+
description: >
11+
JSON map of lambda_name -> force-build flag.
12+
e.g. {"recordprocessor":true,"ack-backend":false}
1013
required: false
11-
type: boolean
12-
default: false
13-
recordprocessor_image_version:
14+
type: string
15+
default: "{}"
16+
lambda_image_overrides:
17+
description: >
18+
JSON map of lambda_name -> immutable image selector for reuse mode.
19+
Supports tags, sha256 digests, or full image URIs. Empty object means
20+
no explicit override.
1421
required: false
1522
type: string
16-
default: ""
23+
default: "{}"
1724
diff_base_sha:
1825
required: false
1926
type: string
@@ -37,9 +44,9 @@ on:
3744
required: true
3845
type: string
3946
outputs:
40-
recordprocessor_image_version:
41-
description: Selected immutable image selector used for recordprocessor deployment
42-
value: ${{ jobs.deploy-recordprocessor-image.outputs.image_uri }}
47+
image_uris_json:
48+
description: JSON map of lambda_name -> immutable image URI used for deployment
49+
value: ${{ jobs.terraform-plan.outputs.image_uris_json }}
4350
workflow_dispatch:
4451
inputs:
4552
apigee_environment:
@@ -63,16 +70,20 @@ on:
6370
- dev
6471
- preprod
6572
- prod
66-
build_recordprocessor_image:
67-
description: Build and publish a new recordprocessor image
68-
required: true
69-
type: boolean
70-
default: true
71-
recordprocessor_image_version:
72-
description: Existing immutable recordprocessor image selector (tag, digest, or image URI) for reuse mode
73+
lambda_build_flags:
74+
description: >
75+
JSON map of lambda_name -> force-build flag.
76+
e.g. {"recordprocessor":true,"ack-backend":false}
7377
required: false
7478
type: string
75-
default: ""
79+
default: "{}"
80+
lambda_image_overrides:
81+
description: >
82+
JSON map of lambda_name -> immutable image selector for reuse mode.
83+
e.g. {"recordprocessor":"internal-dev-git-abc123","ack-backend":"123456789012.dkr.ecr.eu-west-2.amazonaws.com/imms-ackbackend-repo@sha256:..."}
84+
required: false
85+
type: string
86+
default: "{}"
7687
diff_base_sha:
7788
description: Base commit SHA for diff checks
7889
required: false
@@ -100,25 +111,42 @@ env: # Sonarcloud - do not allow direct usage of untrusted data
100111
run-name: Deploy Backend - ${{ inputs.environment }} ${{ inputs.sub_environment }}
101112

102113
jobs:
103-
deploy-recordprocessor-image:
104-
name: Deploy recordprocessor image
114+
deploy-lambda-images:
115+
name: Deploy ${{ matrix.lambda_name }} image
116+
strategy:
117+
# Surface every lambda image failure from the matrix in a single run.
118+
fail-fast: false
119+
matrix:
120+
include:
121+
- lambda_name: recordprocessor
122+
tf_var_suffix: recordprocessor
123+
ecr_repository: imms-recordprocessor-repo
124+
dockerfile_path: lambdas/recordprocessor/Dockerfile
125+
lambda_paths: |
126+
lambdas/recordprocessor/
127+
- lambda_name: ack-backend
128+
tf_var_suffix: ack_backend
129+
ecr_repository: imms-ackbackend-repo
130+
dockerfile_path: lambdas/ack_backend/Dockerfile
131+
lambda_paths: |
132+
lambdas/ack_backend/
105133
uses: ./.github/workflows/deploy-lambda-artifact.yml
106134
with:
107-
lambda_name: recordprocessor
135+
lambda_name: ${{ matrix.lambda_name }}
136+
tf_var_suffix: ${{ matrix.tf_var_suffix }}
108137
environment: ${{ inputs.environment }}
109138
sub_environment: ${{ inputs.sub_environment }}
110-
build_image: ${{ inputs.build_recordprocessor_image }}
111-
image_version: ${{ inputs.recordprocessor_image_version }}
139+
build_image: ${{ fromJson(inputs.lambda_build_flags)[matrix.lambda_name] || false }}
140+
image_version: ${{ fromJson(inputs.lambda_image_overrides)[matrix.lambda_name] || '' }}
112141
run_diff_check: ${{ inputs.run_diff_check }}
113142
diff_base_sha: ${{ inputs.diff_base_sha }}
114143
diff_head_sha: ${{ inputs.diff_head_sha }}
115-
lambda_paths: |
116-
lambdas/recordprocessor/
144+
lambda_paths: ${{ matrix.lambda_paths }}
117145
shared_paths: |
118146
lambdas/shared/src/common/
119147
docker_context_path: lambdas
120-
dockerfile_path: lambdas/recordprocessor/Dockerfile
121-
ecr_repository: imms-recordprocessor-repo
148+
dockerfile_path: ${{ matrix.dockerfile_path }}
149+
ecr_repository: ${{ matrix.ecr_repository }}
122150
image_tag_prefix: ${{ inputs.sub_environment }}-
123151
allow_implicit_tag_prefix_reuse: ${{ inputs.sub_environment == 'internal-dev' || startsWith(inputs.sub_environment, 'pr-') }}
124152

@@ -127,13 +155,12 @@ jobs:
127155
id-token: write
128156
contents: read
129157
needs:
130-
- deploy-recordprocessor-image
131-
if: ${{ !cancelled() && needs.deploy-recordprocessor-image.result == 'success' }}
158+
- deploy-lambda-images
159+
if: ${{ !cancelled() && needs.deploy-lambda-images.result == 'success' }}
132160
outputs:
133-
recordprocessor_image_uri: ${{ needs.deploy-recordprocessor-image.outputs.image_uri }}
161+
image_uris_json: ${{ steps.lambda-images.outputs.image_uris_json }}
162+
terraform_image_uris_json: ${{ steps.lambda-images.outputs.terraform_image_uris_json }}
134163
runs-on: ubuntu-latest
135-
env:
136-
TF_VAR_recordprocessor_image_uri: ${{ needs.deploy-recordprocessor-image.outputs.image_uri }}
137164
environment:
138165
name: ${{ inputs.environment }}
139166
steps:
@@ -151,6 +178,58 @@ jobs:
151178
with:
152179
terraform_version: "1.12.2"
153180

181+
- name: Download lambda deployment manifests
182+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
183+
with:
184+
pattern: deploy-manifest-*-${{ inputs.environment }}-${{ inputs.sub_environment }}-${{ github.run_attempt }}
185+
path: ${{ runner.temp }}/lambda-manifests
186+
merge-multiple: true
187+
188+
- name: Assemble lambda image map and export Terraform vars
189+
id: lambda-images
190+
run: |
191+
set -euo pipefail
192+
193+
manifest_dir="${RUNNER_TEMP}/lambda-manifests"
194+
shopt -s nullglob
195+
manifest_files=("${manifest_dir}"/*.json)
196+
197+
if [ "${#manifest_files[@]}" -eq 0 ]; then
198+
echo "No lambda deployment manifests found."
199+
exit 1
200+
fi
201+
202+
jq -e -s '
203+
all(
204+
.[];
205+
.lambda_name != null
206+
and .lambda_name != ""
207+
and .tf_var_suffix != null
208+
and .tf_var_suffix != ""
209+
and .image_uri != null
210+
and .image_uri != ""
211+
)
212+
and ((map(.lambda_name) | unique | length) == length)
213+
and ((map(.tf_var_suffix) | unique | length) == length)
214+
' "${manifest_files[@]}" > /dev/null
215+
216+
image_uris_json="$(
217+
jq -cs 'map(select(.lambda_name != null and .image_uri != null) | {(.lambda_name): .image_uri}) | add' \
218+
"${manifest_files[@]}"
219+
)"
220+
221+
terraform_image_uris_json="$(
222+
jq -cs 'map(select(.tf_var_suffix != null and .tf_var_suffix != "" and .image_uri != null) | {(.tf_var_suffix): .image_uri}) | add' \
223+
"${manifest_files[@]}"
224+
)"
225+
226+
echo "image_uris_json=${image_uris_json}" >> "$GITHUB_OUTPUT"
227+
echo "terraform_image_uris_json=${terraform_image_uris_json}" >> "$GITHUB_OUTPUT"
228+
jq -er '
229+
to_entries[]
230+
| "TF_VAR_\(.key)_image_uri=\(.value)"
231+
' <<< "${terraform_image_uris_json}" >> "$GITHUB_ENV"
232+
154233
- name: Terraform Init
155234
working-directory: infrastructure/instance
156235
run: make init
@@ -175,8 +254,6 @@ jobs:
175254
needs: terraform-plan
176255
if: ${{ !cancelled() && needs.terraform-plan.result == 'success' }}
177256
runs-on: ubuntu-latest
178-
env:
179-
TF_VAR_recordprocessor_image_uri: ${{ needs.terraform-plan.outputs.recordprocessor_image_uri }}
180257
environment:
181258
name: ${{ inputs.environment }}
182259
steps:
@@ -193,6 +270,22 @@ jobs:
193270
with:
194271
terraform_version: "1.12.2"
195272

273+
- name: Restore lambda image Terraform vars
274+
env:
275+
TERRAFORM_IMAGE_URIS_JSON: ${{ needs.terraform-plan.outputs.terraform_image_uris_json }}
276+
run: |
277+
set -euo pipefail
278+
279+
if [ -z "${TERRAFORM_IMAGE_URIS_JSON}" ] || [ "${TERRAFORM_IMAGE_URIS_JSON}" = "null" ]; then
280+
echo "terraform-plan did not emit terraform_image_uris_json."
281+
exit 1
282+
fi
283+
284+
jq -er '
285+
to_entries[]
286+
| "TF_VAR_\(.key)_image_uri=\(.value)"
287+
' <<< "${TERRAFORM_IMAGE_URIS_JSON}" >> "$GITHUB_ENV"
288+
196289
- name: Retrieve Terraform Plan
197290
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
198291
with:

.github/workflows/deploy-lambda-artifact.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ on:
77
description: Logical lambda identifier for logging and artifacts
88
required: true
99
type: string
10+
tf_var_suffix:
11+
description: Terraform variable suffix used to export TF_VAR_<suffix>_image_uri
12+
required: true
13+
type: string
1014
environment:
1115
description: GitHub environment containing AWS account variables
1216
required: true
@@ -106,6 +110,7 @@ jobs:
106110
DOCKERFILE_PATH: ${{ inputs.dockerfile_path }}
107111
TAG_PREFIX: ${{ inputs.image_tag_prefix }}
108112
LAMBDA_NAME: ${{ inputs.lambda_name }}
113+
TF_VAR_SUFFIX: ${{ inputs.tf_var_suffix }}
109114
ENVIRONMENT: ${{ inputs.environment }}
110115
SUB_ENVIRONMENT: ${{ inputs.sub_environment }}
111116
BUILD_IMAGE: ${{ inputs.build_image }}
@@ -391,6 +396,7 @@ jobs:
391396
392397
jq -n \
393398
--arg lambda_name "${LAMBDA_NAME}" \
399+
--arg tf_var_suffix "${TF_VAR_SUFFIX}" \
394400
--arg environment "${ENVIRONMENT}" \
395401
--arg sub_environment "${SUB_ENVIRONMENT}" \
396402
--arg deployment_mode "${DEPLOYMENT_MODE}" \
@@ -408,6 +414,7 @@ jobs:
408414
--arg build_timestamp "${build_timestamp}" \
409415
'{
410416
lambda_name: $lambda_name,
417+
tf_var_suffix: $tf_var_suffix,
411418
environment: $environment,
412419
sub_environment: $sub_environment,
413420
deployment_mode: $deployment_mode,

.github/workflows/pr-deploy-and-test.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ jobs:
2020
uses: ./.github/workflows/deploy-backend.yml
2121
with:
2222
apigee_environment: internal-dev
23-
build_recordprocessor_image: ${{ github.event.action == 'opened' || github.event.action == 'reopened' }}
23+
lambda_build_flags: >-
24+
${{ (github.event.action == 'opened' || github.event.action == 'reopened')
25+
&& '{"recordprocessor":true,"ack-backend":true}'
26+
|| '{}' }}
2427
diff_base_sha: ${{ github.event.action == 'synchronize' && github.event.before || github.event.pull_request.base.sha }}
2528
diff_head_sha: ${{ github.event.pull_request.head.sha }}
2629
run_diff_check: ${{ github.event.action == 'synchronize' }}

.github/workflows/pr-teardown.yml

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,13 @@ jobs:
4848
make init apigee_environment=$APIGEE_ENVIRONMENT environment=$BACKEND_ENVIRONMENT sub_environment=$BACKEND_SUB_ENVIRONMENT
4949
make workspace apigee_environment=$APIGEE_ENVIRONMENT environment=$BACKEND_ENVIRONMENT sub_environment=$BACKEND_SUB_ENVIRONMENT
5050
echo "ID_SYNC_QUEUE_ARN=$(make -s output name=id_sync_queue_arn)" >> $GITHUB_ENV
51-
recordprocessor_image_uri="$(make -s output name=recordprocessor_image_uri 2>/dev/null || true)"
52-
if [ -z "${recordprocessor_image_uri}" ]; then
53-
# Destroy still evaluates variable validation, so provide a non-empty fallback when output is unavailable.
54-
recordprocessor_image_uri="placeholder.dkr.ecr.eu-west-2.amazonaws.com/imms-recordprocessor-repo@sha256:0000000000000000000000000000000000000000000000000000000000000000"
55-
fi
56-
echo "TF_VAR_recordprocessor_image_uri=${recordprocessor_image_uri}" >> $GITHUB_ENV
51+
# Destroy still evaluates variable validation, so provide a non-empty fallback when output is unavailable.
52+
resolve_or_placeholder() {
53+
local uri="$(make -s output "name=$1" 2>/dev/null || true)"
54+
echo "${uri:-placeholder.dkr.ecr.eu-west-2.amazonaws.com/$2@sha256:0000000000000000000000000000000000000000000000000000000000000000}"
55+
}
56+
echo "TF_VAR_recordprocessor_image_uri=$(resolve_or_placeholder recordprocessor_image_uri imms-recordprocessor-repo)" >> $GITHUB_ENV
57+
echo "TF_VAR_ack_backend_image_uri=$(resolve_or_placeholder ack_backend_image_uri imms-ackbackend-repo)" >> $GITHUB_ENV
5758
5859
- name: Install poetry
5960
run: pip install poetry==2.1.4
@@ -128,4 +129,6 @@ jobs:
128129
--output json
129130
}
130131
131-
cleanup_repo_by_prefix "imms-recordprocessor-repo"
132+
for repository_name in imms-recordprocessor-repo imms-ackbackend-repo; do
133+
cleanup_repo_by_prefix "${repository_name}"
134+
done
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
resource "aws_ecr_repository" "ackbackend_repository" {
2+
image_scanning_configuration {
3+
scan_on_push = true
4+
}
5+
image_tag_mutability = "IMMUTABLE"
6+
name = "imms-ackbackend-repo"
7+
}
8+
9+
resource "aws_ecr_repository_policy" "ackbackend_repository_lambda_image_retrieval_policy" {
10+
repository = aws_ecr_repository.ackbackend_repository.name
11+
12+
policy = jsonencode({
13+
Version = "2012-10-17"
14+
Statement = [
15+
{
16+
Sid = "LambdaECRImageRetrievalPolicy"
17+
Effect = "Allow"
18+
Principal = {
19+
Service = "lambda.amazonaws.com"
20+
}
21+
Action = [
22+
"ecr:BatchGetImage",
23+
"ecr:GetDownloadUrlForLayer"
24+
]
25+
Condition = {
26+
StringLike = {
27+
"aws:sourceArn" = "arn:aws:lambda:${var.aws_region}:${var.imms_account_id}:function:imms-*-ack-lambda"
28+
}
29+
}
30+
}
31+
]
32+
})
33+
}

0 commit comments

Comments
 (0)