From 5db4d2cfbed4adbe81847ff08cdc802f2a69c2f5 Mon Sep 17 00:00:00 2001 From: Jun-te Kim Date: Tue, 3 Feb 2026 14:23:20 +0000 Subject: [PATCH] deploy --- .github/workflows/_build_image.yml | 27 ++++--- .github/workflows/_deploy_lambda.yml | 20 +++--- .github/workflows/deploy_terraform.yml | 38 +++++----- .../terraform/lambda/_template/README.md | 72 +++++++++++++++++-- .../terraform/lambda/_template/main.tf | 12 +--- .../terraform/lambda/_template/variables.tf | 15 ++-- .../terraform/lambda/address2UPRN/main.tf | 18 +---- .../lambda/address2UPRN/variables.tf | 15 ++-- 8 files changed, 124 insertions(+), 93 deletions(-) diff --git a/.github/workflows/_build_image.yml b/.github/workflows/_build_image.yml index 1008d592..9414b959 100644 --- a/.github/workflows/_build_image.yml +++ b/.github/workflows/_build_image.yml @@ -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 \ No newline at end of file + echo "image_digest=$DIGEST" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/_deploy_lambda.yml b/.github/workflows/_deploy_lambda.yml index 5abbbd99..5887da65 100644 --- a/.github/workflows/_deploy_lambda.yml +++ b/.github/workflows/_deploy_lambda.yml @@ -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 diff --git a/.github/workflows/deploy_terraform.yml b/.github/workflows/deploy_terraform.yml index b7743acd..339e742d 100644 --- a/.github/workflows/deploy_terraform.yml +++ b/.github/workflows/deploy_terraform.yml @@ -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 }} diff --git a/infrastructure/terraform/lambda/_template/README.md b/infrastructure/terraform/lambda/_template/README.md index 1f519a90..0dd612ee 100644 --- a/infrastructure/terraform/lambda/_template/README.md +++ b/infrastructure/terraform/lambda/_template/README.md @@ -1,10 +1,68 @@ -### Checklist for a new lambda +## Checklist for adding a new Lambda -- [ ] Copy cp -r lambda/_template lambda/ -- [ ] 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/ -Note: By default this does a sqs to lamba. Configure the terraform file for other connections between sqs->lambda \ No newline at end of file +- 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/` + (e.g. EventBridge, API Gateway, direct invoke) + +--- + +## Rule of thumb +CI decides what image to deploy. +Terraform only deploys it. diff --git a/infrastructure/terraform/lambda/_template/main.tf b/infrastructure/terraform/lambda/_template/main.tf index 0b3f008a..a0e8eb80 100644 --- a/infrastructure/terraform/lambda/_template/main.tf +++ b/infrastructure/terraform/lambda/_template/main.tf @@ -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 diff --git a/infrastructure/terraform/lambda/_template/variables.tf b/infrastructure/terraform/lambda/_template/variables.tf index 42ac1047..7ba0bedb 100644 --- a/infrastructure/terraform/lambda/_template/variables.tf +++ b/infrastructure/terraform/lambda/_template/variables.tf @@ -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 -} \ No newline at end of file diff --git a/infrastructure/terraform/lambda/address2UPRN/main.tf b/infrastructure/terraform/lambda/address2UPRN/main.tf index a5978186..8d8e489f 100644 --- a/infrastructure/terraform/lambda/address2UPRN/main.tf +++ b/infrastructure/terraform/lambda/address2UPRN/main.tf @@ -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 diff --git a/infrastructure/terraform/lambda/address2UPRN/variables.tf b/infrastructure/terraform/lambda/address2UPRN/variables.tf index 208b82b5..7ba0bedb 100644 --- a/infrastructure/terraform/lambda/address2UPRN/variables.tf +++ b/infrastructure/terraform/lambda/address2UPRN/variables.tf @@ -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" }