Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
17 changes: 17 additions & 0 deletions .github/workflows/deploy-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,14 @@ jobs:
working-directory: infrastructure/instance
run: make init

- name: Set Terraform workspace
working-directory: infrastructure/instance
run: make workspace

- name: Adopt shared Lambda triggers for blue/green deploys
working-directory: infrastructure/instance
run: bash ../../utilities/scripts/manage_blue_green_event_source_mappings.sh prepare-state

- name: Terraform Plan
# Ignore cancellations to prevent Terraform from being killed while it holds a state lock
# A stuck process can still be killed with the force-cancel API operation
Expand Down Expand Up @@ -293,6 +301,15 @@ jobs:
working-directory: infrastructure/instance
run: make init

- name: Set Terraform workspace
working-directory: infrastructure/instance
run: make workspace

- name: Remove stale Lambda triggers for blue/green deploys
if: ${{ !failure() }}
working-directory: infrastructure/instance
run: bash ../../utilities/scripts/manage_blue_green_event_source_mappings.sh cleanup-stale

- name: Terraform Apply
# Ignore cancellations to prevent Terraform from being killed while it holds a state lock
# A stuck process can still be killed with the force-cancel API operation
Expand Down
6 changes: 6 additions & 0 deletions infrastructure/instance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,9 @@ Note: If you switch environment configuration in .env ensure that you run `make

If you want to apply Terraform to a workspace created by a PR you can set the above SUB_ENVIRONMENT to the `PR-number` and ENVIRONMENT set to `dev`.
E.g. `pr-57`. You can use this to test out changes when tests fail in CI.

## Blue/Green Lambda Trigger Handoff

For split sub-environments such as `int-blue`/`int-green` and `prod-blue`/`prod-green`, the deploy workflow now reimports the shared `delta_trigger` and `id_sync_sqs_trigger` resources into the target Terraform workspace before planning. On apply, it also deletes any stale trigger that still points at the target side's old dedicated Lambda function.

This removes the release-time `Disable delta` and `Disable ID sync` steps from the repository-managed deployment flow. The remaining operational follow-up is outside this repository: update the Jira Smart Checklist release templates to remove those manual checklist items once the automated flow has been rolled out.
139 changes: 139 additions & 0 deletions utilities/scripts/manage_blue_green_event_source_mappings.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#!/usr/bin/env bash

set -euo pipefail

mode="${1:-prepare-state}"
sub_environment="${SUB_ENVIRONMENT:-${sub_environment:-}}"
Comment thread
Thomas-Boyle marked this conversation as resolved.
Outdated

if [[ -z "${sub_environment}" ]]; then
echo "SUB_ENVIRONMENT must be set."
exit 1
fi

if [[ ! "${sub_environment}" =~ -(blue|green)$ ]]; then
Comment thread
Thomas-Boyle marked this conversation as resolved.
Outdated
echo "Skipping Lambda trigger handoff for ${sub_environment}."
exit 0
fi

current_colour="${BASH_REMATCH[1]}"
counterpart_colour="blue"
if [[ "${current_colour}" == "blue" ]]; then
counterpart_colour="green"
fi

counterpart_sub_environment="${sub_environment%-${current_colour}}-${counterpart_colour}"
current_workspace="$(terraform workspace show)"

if [[ "${current_workspace}" != "${sub_environment}" ]]; then
echo "Terraform workspace ${current_workspace} does not match SUB_ENVIRONMENT ${sub_environment}."
exit 1
fi

lookup_mapping_uuid() {
local event_source_arn="$1"
local function_name="$2"
local mapping_uuid

mapping_uuid="$(aws lambda list-event-source-mappings \
--event-source-arn "${event_source_arn}" \
--function-name "${function_name}" \
--query 'EventSourceMappings[0].UUID' \
--output text)"

if [[ "${mapping_uuid}" == "None" ]]; then
return 0
fi

printf '%s' "${mapping_uuid}"
}

resolve_event_source_arns() {
id_sync_queue_arn="$(terraform output -raw id_sync_queue_arn)"
events_table_name="$(terraform output -raw dynamodb_table_name)"
delta_event_source_arn="$(aws dynamodb describe-table \
--table-name "${events_table_name}" \
--query 'Table.LatestStreamArn' \
--output text)"

if [[ -z "${delta_event_source_arn}" || "${delta_event_source_arn}" == "None" ]]; then
echo "Unable to resolve the DynamoDB stream ARN for ${events_table_name}."
exit 1
fi
}

prepare_state() {
local address="$1"
local event_source_arn="$2"
local counterpart_function_name="$3"
local target_function_name="$4"
local mapping_uuid=""

mapping_uuid="$(lookup_mapping_uuid "${event_source_arn}" "${counterpart_function_name}")"
Comment thread
Thomas-Boyle marked this conversation as resolved.
Outdated
if [[ -z "${mapping_uuid}" ]]; then
mapping_uuid="$(lookup_mapping_uuid "${event_source_arn}" "${target_function_name}")"
fi

if [[ -z "${mapping_uuid}" ]]; then
echo "Unable to find an event source mapping for ${address}."
exit 1
fi

terraform state rm "${address}" >/dev/null 2>&1 || true
Comment thread
Thomas-Boyle marked this conversation as resolved.
Outdated
terraform import "${address}" "${mapping_uuid}" >/dev/null

echo "Imported ${address} into workspace ${sub_environment} using ${mapping_uuid}."
}

cleanup_stale_mapping() {
local event_source_arn="$1"
local counterpart_function_name="$2"
local target_function_name="$3"
local counterpart_uuid=""
local target_uuid=""

counterpart_uuid="$(lookup_mapping_uuid "${event_source_arn}" "${counterpart_function_name}")"
target_uuid="$(lookup_mapping_uuid "${event_source_arn}" "${target_function_name}")"

if [[ -z "${target_uuid}" || "${target_uuid}" == "${counterpart_uuid}" ]]; then
return 0
fi

aws lambda delete-event-source-mapping --uuid "${target_uuid}" >/dev/null
echo "Deleted stale event source mapping ${target_uuid} for ${target_function_name}."
}

resolve_event_source_arns

target_delta_function="imms-${sub_environment}-delta-lambda"
counterpart_delta_function="imms-${counterpart_sub_environment}-delta-lambda"
target_id_sync_function="imms-${sub_environment}-id-sync-lambda"
counterpart_id_sync_function="imms-${counterpart_sub_environment}-id-sync-lambda"

case "${mode}" in
prepare-state)
prepare_state \
"aws_lambda_event_source_mapping.delta_trigger" \
"${delta_event_source_arn}" \
"${counterpart_delta_function}" \
"${target_delta_function}"
prepare_state \
"aws_lambda_event_source_mapping.id_sync_sqs_trigger" \
"${id_sync_queue_arn}" \
"${counterpart_id_sync_function}" \
"${target_id_sync_function}"
;;
cleanup-stale)
cleanup_stale_mapping \
"${delta_event_source_arn}" \
"${counterpart_delta_function}" \
"${target_delta_function}"
cleanup_stale_mapping \
"${id_sync_queue_arn}" \
"${counterpart_id_sync_function}" \
"${target_id_sync_function}"
;;
*)
echo "Unsupported mode: ${mode}. Use prepare-state or cleanup-stale."
exit 1
;;
esac
Loading