This commit is contained in:
Jun-te Kim 2026-02-03 14:23:20 +00:00
parent c52cd37a74
commit 5db4d2cfbe
8 changed files with 124 additions and 93 deletions

View file

@ -21,16 +21,20 @@ on:
outputs:
image_digest:
description: "Pushed image digest"
description: "Pushed image digest (sha256:...)"
value: ${{ jobs.build.outputs.image_digest }}
ecr_repo_url:
description: "ECR repository URL (no tag, no digest)"
value: ${{ jobs.build.outputs.ecr_repo_url }}
secrets:
AWS_ACCESS_KEY_ID:
required: true
AWS_SECRET_ACCESS_KEY:
required: true
AWS_REGION:
required: true
required: true
jobs:
build:
@ -38,6 +42,7 @@ jobs:
outputs:
image_digest: ${{ steps.digest.outputs.image_digest }}
ecr_repo_url: ${{ steps.repo.outputs.ecr_repo_url }}
steps:
- uses: actions/checkout@v4
@ -49,16 +54,20 @@ jobs:
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- uses: aws-actions/amazon-ecr-login@v2
- name: Login to ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Resolve ECR repo URL
id: repo
run: |
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
ECR_REPO_URL="${AWS_ACCOUNT_ID}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/${{ inputs.ecr_repo }}"
echo "ecr_repo_url=$ECR_REPO_URL" >> "$GITHUB_OUTPUT"
- name: Build & push image
run: |
IMAGE_TAG=${GITHUB_SHA}
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
IMAGE_URI=${AWS_ACCOUNT_ID}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/${{ inputs.ecr_repo }}:${IMAGE_TAG}
echo "Using IMAGE_URI=$IMAGE_URI"
IMAGE_URI="${{ steps.repo.outputs.ecr_repo_url }}:${IMAGE_TAG}"
docker build \
-f ${{ inputs.dockerfile_path }} \
@ -76,4 +85,4 @@ jobs:
--query 'imageDetails[0].imageDigest' \
--output text)
echo "image_digest=$DIGEST" >> $GITHUB_OUTPUT
echo "image_digest=$DIGEST" >> "$GITHUB_OUTPUT"

View file

@ -6,13 +6,17 @@ on:
lambda_name:
required: true
type: string
lambda_path:
required: true
type: string
stage:
required: true
type: string
image_digest:
image_uri:
description: "Full ECR image URI including digest"
required: true
type: string
@ -22,8 +26,7 @@ on:
AWS_SECRET_ACCESS_KEY:
required: true
AWS_REGION:
required: true
required: true
jobs:
deploy:
@ -55,14 +58,11 @@ jobs:
run: |
terraform plan \
-var="stage=${{ inputs.stage }}" \
-var="image_digest=${{ inputs.image_digest }}" \
-var="lambda_name=${{ inputs.lambda_name }}" \
-var="image_uri=${{ inputs.image_uri }}" \
-out=lambdaplan
# Uncomment when ready
# - name: Terraform Apply
# working-directory: ${{ inputs.lambda_path }}
# run: |
# terraform apply \
# -auto-approve \
# -var="stage=${{ inputs.stage }}" \
# -var="image_digest=${{ inputs.image_digest }}" \
# lambdaplan
# run: terraform apply -auto-approve lambdaplan

View file

@ -5,7 +5,6 @@ on:
branches:
- "**"
jobs:
determine_stage:
runs-on: ubuntu-latest
@ -27,9 +26,8 @@ jobs:
echo "stage=dev" >> "$GITHUB_OUTPUT"
fi
echo "Resolved STAGE=$BRANCH → $(cat $GITHUB_OUTPUT)"
# ============================================================
# 1⃣ Shared Terraform (plan only for now)
# 1⃣ Shared Terraform (infra)
# ============================================================
shared_terraform:
needs: determine_stage
@ -38,39 +36,35 @@ jobs:
STAGE: ${{ needs.determine_stage.outputs.stage }}
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
# This will need to be changed to env imports when we have different env to dynamically allocate prod, staging etc
aws-access-key-id: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.DEV_AWS_REGION }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init (shared)
- name: Terraform Init
working-directory: infrastructure/terraform/shared
run: terraform init -reconfigure
- name: Terraform Workspace (shared)
- name: Terraform Workspace
working-directory: infrastructure/terraform/shared
run: terraform workspace select ${STAGE} || terraform workspace new ${STAGE}
- name: Terraform Plan (shared)
- name: Terraform Plan
working-directory: infrastructure/terraform/shared
run: terraform plan -var-file=${STAGE}.tfvars -out=tfplan
- name: Terraform Apply (shared)
- name: Terraform Apply
if: env.STAGE == 'prod'
working-directory: infrastructure/terraform/shared
run: terraform apply -auto-approve -var-file=${STAGE}.tfvars tfplan
run: terraform apply -auto-approve tfplan
# ============================================================
# 2⃣ Build Docker image (tag = GitHub SHA, digest resolved)
# 2⃣ Build image
# ============================================================
address2uprn_image:
needs: [determine_stage, shared_terraform]
@ -84,17 +78,17 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.DEV_AWS_REGION }}
# # ============================================================
# # 3⃣ Deploy Lambda (Terraform, immutable digest)
# # ============================================================
# ============================================================
# 3⃣ Deploy Lambda
# ============================================================
address2uprn_lambda:
needs: [address2uprn_image, determine_stage]
uses: ./.github/workflows/_deploy_lambda.yml
with:
lambda_name: address2UPRN
lambda_path: infrastructure/terraform/lambda/address2UPRN
lambda_name: address2uprn
lambda_path: infrastructure/terraform/lambda/address2uprn
stage: ${{ needs.determine_stage.outputs.stage }}
image_digest: ${{ needs.image.outputs.image_digest }}
image_uri: ${{ needs.address2uprn_image.outputs.ecr_repo_url }}@${{ needs.address2uprn_image.outputs.image_digest }}
secrets:
AWS_ACCESS_KEY_ID: ${{ secrets.DEV_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DEV_AWS_SECRET_ACCESS_KEY }}

View file

@ -1,10 +1,68 @@
### Checklist for a new lambda
## Checklist for adding a new Lambda
- [ ] Copy cp -r lambda/_template lambda/<name>
- [ ] Add ECR repo in shared/main.tf
- [ ] Set bucket name in provider.tf
- [ ] Add shared output for repo name/url
- [ ] Push to GitHub (CI will deploy)
### 1. Create the Lambda scaffold
- Copy the template:
cp -r lambda/_template lambda/<lambda_name>
Note: By default this does a sqs to lamba. Configure the terraform file for other connections between sqs->lambda
- Update `variables.tf` if required
- Ensure the Terraform module accepts:
- `lambda_name`
- `stage`
- `image_uri`
---
### 2. Add infrastructure prerequisites (shared stack)
- Add a new ECR repository in:
infrastructure/terraform/shared/main.tf
- Apply the shared stack (or let CI apply it on `prod`)
- Verify the ECR repository exists in AWS
Note:
No Terraform outputs are required for the ECR repo.
The CI pipeline resolves the repository URL dynamically at build time.
---
### 3. Add Docker build configuration
- Create a `Dockerfile` for the Lambda
- Verify the Dockerfile path and build context
- Add a new image build job in `deploy_terraform.yml` using `_build_image.yml`
---
### 4. Wire the Lambda deploy job (CI)
- Add a deploy job using `_deploy_lambda.yml`
- Pass the following inputs:
- `lambda_name`
- `lambda_path`
- `stage`
- `image_uri` (constructed as `repo@sha256:digest`)
- Ensure the deploy job depends on the image build job
---
### 5. Deploy
- Push changes to GitHub
- CI will:
1. Build and push the Docker image
2. Resolve the image digest
3. Deploy the Lambda using the immutable `image_uri`
---
## Notes
- Terraform remote state is not used for image wiring
- Image tags are not used; deployments are digest-based
- By default, the template wires SQS → Lambda
To change this, update the Terraform in `lambda/<lambda_name>`
(e.g. EventBridge, API Gateway, direct invoke)
---
## Rule of thumb
CI decides what image to deploy.
Terraform only deploys it.

View file

@ -1,19 +1,11 @@
data "terraform_remote_state" "shared" {
backend = "s3"
config = {
bucket = "assessment-model-terraform-state"
key = "terraform.tfstate"
region = "eu-west-2"
}
}
module "lambda" {
source = "../modules/lambda_with_sqs"
name = REPLACE ME #"address2uprn" for example
stage = var.stage
image_uri = "${data.terraform_remote_state.shared.outputs.REPLACE_ME_repository_url}@${var.image_digest}"
image_uri = var.image_uri
environment = {
STAGE = var.stage

View file

@ -1,12 +1,9 @@
variable "region" {
type = string
default = "eu-west-2"
variable "lambda_name" {
type = string
description = "Logical name of the lambda (e.g. address2uprn)"
}
variable "stage" {
type = string
variable "image_uri" {
type = string
description = "Full ECR image URI including digest"
}
variable "image_digest" {
type = string
}

View file

@ -1,26 +1,10 @@
############################################
# Read shared state to get outputs
############################################
data "terraform_remote_state" "shared" {
backend = "s3"
config = {
bucket = "assessment-model-terraform-state"
key = "terraform.tfstate"
region = "eu-west-2"
}
}
############################################
# Address2UPRN Lambda (via reusable module)
############################################
module "address2uprn" {
source = "../modules/lambda_with_sqs"
name = "address2uprn"
stage = var.stage
image_uri = "${data.terraform_remote_state.shared.outputs.address2uprn_repository_url}@${var.image_digest}"
image_uri = var.image_uri
environment = {
STAGE = var.stage

View file

@ -1,12 +1,9 @@
variable "region" {
type = string
default = "eu-west-2"
variable "lambda_name" {
type = string
description = "Logical name of the lambda (e.g. address2uprn)"
}
variable "stage" {
type = string
}
variable "image_digest" {
type = string
variable "image_uri" {
type = string
description = "Full ECR image URI including digest"
}