Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
146 changes: 146 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
name: CD - Deploy to AWS EKS

on:
workflow_run:
workflows: ["CI - Build & Test"]
branches: [DevOps]
types: [completed]
workflow_dispatch:
inputs:
environment:
description: "Target environment"
required: true
type: choice
options:
- dev
- staging
- prod
image_tag:
description: "Docker image tag to deploy"
required: true
type: string

permissions:
id-token: write
contents: read

env:
AWS_REGION: us-east-1
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
deploy-infrastructure:
name: Deploy Infrastructure
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
environment: ${{ inputs.environment || 'dev' }}

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.7.0

- name: Terraform Init
run: |
terraform init \
-backend-config="key=infrastructure/${{ inputs.environment || 'dev' }}/terraform.tfstate"
working-directory: terraform

- name: Terraform Plan
run: |
terraform plan \
-var-file="environments/${{ inputs.environment || 'dev' }}.tfvars" \
-var="db_password=${{ secrets.DB_PASSWORD }}" \
-var="db_username=${{ secrets.DB_USERNAME }}" \
-out=tfplan
working-directory: terraform

- name: Terraform Apply
if: github.ref == 'refs/heads/DevOps'
run: terraform apply -auto-approve tfplan
working-directory: terraform

outputs:
eks_cluster_name: ${{ steps.tf-output.outputs.cluster_name }}

deploy-application:
name: Deploy Application to EKS
runs-on: ubuntu-latest
needs: deploy-infrastructure
environment: ${{ inputs.environment || 'dev' }}

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}

- name: Update kubeconfig
run: |
ENV=${{ inputs.environment || 'dev' }}
aws eks update-kubeconfig \
--name bankapp-${ENV}-eks \
--region ${{ env.AWS_REGION }}

- name: Set image tag
id: image-tag
run: |
if [ -n "${{ inputs.image_tag }}" ]; then
echo "tag=${{ inputs.image_tag }}" >> $GITHUB_OUTPUT
else
echo "tag=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
fi
Comment on lines +103 to +107
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 CD deploys wrong Docker image tag when triggered by workflow_run

When the CD workflow is triggered via workflow_run (after CI completes on the DevOps branch), the image tag computed at line 106 uses ${GITHUB_SHA::7}. However, for workflow_run events, GITHUB_SHA is the last commit on the default branch, not the DevOps branch commit that CI built and pushed.

Root Cause and Impact

The CI workflow (.github/workflows/ci.yml:91) tags Docker images with type=sha,prefix= which generates a tag from the CI run's commit SHA (the DevOps branch commit). When CI completes and triggers the CD workflow via workflow_run, the GitHub Actions context changes:

  • In CI (push to DevOps): GITHUB_SHA = DevOps branch commit, e.g. abc1234
  • In CD (workflow_run): GITHUB_SHA = default branch HEAD, e.g. def5678

Since inputs.image_tag is empty for workflow_run triggers (it's only set for workflow_dispatch), the else branch executes: echo "tag=${GITHUB_SHA::7}". This produces a tag like def5678 that was never pushed to GHCR — the actual image was tagged abc1234.

At .github/workflows/cd.yml:122-123, kubectl set image attempts to pull ghcr.io/<repo>:def5678, which doesn't exist, causing the deployment to fail with an image pull error.

Prompt for agents
In .github/workflows/cd.yml, the image tag logic at lines 100-107 needs to handle the workflow_run trigger correctly. For workflow_run events, GITHUB_SHA refers to the default branch, not the branch that triggered CI. To fix this, use github.event.workflow_run.head_sha instead of GITHUB_SHA when the trigger is workflow_run. For example, replace the 'Set image tag' step with logic like:

  if [ -n "${{ inputs.image_tag }}" ]; then
    echo "tag=${{ inputs.image_tag }}" >> $GITHUB_OUTPUT
  elif [ "${{ github.event_name }}" = "workflow_run" ]; then
    echo "tag=$(echo ${{ github.event.workflow_run.head_sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
  else
    echo "tag=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
  fi

Also consider the same issue for the actions/checkout step at line 84 — when triggered by workflow_run, it checks out the default branch HEAD, not the DevOps branch commit that CI tested. You may want to add ref: ${{ github.event.workflow_run.head_sha }} to the checkout step.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


- name: Deploy Kubernetes manifests
run: |
ENV=${{ inputs.environment || 'dev' }}
IMAGE_TAG=${{ steps.image-tag.outputs.tag }}

kubectl apply -f kubernetes/bankapp-namespace.yaml
kubectl apply -f kubernetes/configmap.yaml
kubectl apply -f kubernetes/secrets.yaml
kubectl apply -f kubernetes/persistent-volume.yaml
kubectl apply -f kubernetes/persistent-volume-claim.yaml
kubectl apply -f kubernetes/mysql-deployment.yml
kubectl apply -f kubernetes/mysql-service.yaml

kubectl set image deployment/bankapp-deploy \
bankapp=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${IMAGE_TAG} \
-n bankapp-namespace || \
kubectl apply -f kubernetes/bankapp-deployment.yml

kubectl apply -f kubernetes/bankapp-service.yaml
kubectl apply -f kubernetes/bankapp-ingress.yml
kubectl apply -f kubernetes/bankapp-hpa.yml

- name: Wait for deployment rollout
run: |
kubectl rollout status deployment/bankapp-deploy \
-n bankapp-namespace \
--timeout=300s

- name: Verify deployment
run: |
echo "=== Deployment Status ==="
kubectl get deployments -n bankapp-namespace
echo ""
echo "=== Pod Status ==="
kubectl get pods -n bankapp-namespace
echo ""
echo "=== Service Endpoints ==="
kubectl get svc -n bankapp-namespace
128 changes: 128 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: CI - Build & Test

on:
push:
branches: [DevOps, main]
pull_request:
branches: [DevOps, main]

permissions:
contents: read
packages: write

env:
JAVA_VERSION: "17"
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-test:
name: Build & Test
runs-on: ubuntu-latest

services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: testpassword
MYSQL_DATABASE: BankDB
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping -h localhost"
--health-interval=10s
--health-timeout=5s
--health-retries=5

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK ${{ env.JAVA_VERSION }}
uses: actions/setup-java@v4
with:
java-version: ${{ env.JAVA_VERSION }}
distribution: temurin
cache: maven

- name: Build with Maven
run: mvn clean package -DskipTests=true -B

- name: Run tests
run: mvn test -B
env:
SPRING_DATASOURCE_URL: jdbc:mysql://localhost:3306/BankDB?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: testpassword

- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: bankapp-jar
path: target/*.jar
retention-days: 5

docker-build:
name: Build & Push Docker Image
runs-on: ubuntu-latest
needs: build-and-test
if: github.event_name == 'push'

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=sha,prefix=
type=ref,event=branch
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

terraform-validate:
name: Terraform Validate
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.7.0

- name: Terraform Format Check
run: terraform fmt -check -recursive
working-directory: terraform

- name: Terraform Init (validation only)
run: terraform init -backend=false
working-directory: terraform

- name: Terraform Validate
run: terraform validate
working-directory: terraform
Loading