Skip to content

CI-CD

CI-CD #80

Workflow file for this run

name: CI-CD
on:
workflow_dispatch:
inputs:
env:
description: 'Please select your command'
type: choice
options:
- SCHEMA_CHANGED
- JUST_INDEX
- MIGRATE_UP
- MIGRATE_DOWN_UP
build:
description: 'Build new image'
type: boolean
default: false
env:
REGION: eu-central-1
PLATFORM: ${{ github.ref_name == 'main' && 'prod' || 'dev' }}
REPO_NAME: ${{ github.event.repository.name }}
ECR_REPOSITORY: ${{ github.event.repository.name }}-${{ github.ref_name == 'main' && 'prod' || 'dev' }}
AWS_ACCOUNT_ID: ${{ github.ref_name == 'main' && secrets.ACCOUNT_ID_PROD || secrets.ACCOUNT_ID_DEV }}
permissions:
id-token: write
contents: read
jobs:
validate-branch:
runs-on: ubuntu-latest
steps:
- name: Check branch name
run: |
if [[ "${{ github.ref_name }}" != "main" && "${{ github.ref_name }}" != "dev" ]]; then
echo "Error: Workflow can only run on 'main' or 'dev' branches"
exit 1
fi
build:
name: Build
runs-on: ubuntu-latest
environment: ${{ github.ref_name == 'main' && 'production' || 'development' }}
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{env.AWS_ACCOUNT_ID}}:role/platform-github-action-role-${{env.PLATFORM}}
aws-region: ${{ env.REGION }}
role-duration-seconds: 3600
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Check out code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set ECR environment variables
run: |
echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV
echo "IMAGE_TAG=$PLATFORM-${GITHUB_SHA::8}" >> $GITHUB_ENV
- name: Check if image exists
run: |
if aws ecr describe-images --repository-name $ECR_REPOSITORY --image-ids imageTag=$IMAGE_TAG 2>/dev/null; then
echo "IMAGE_EXISTS=true" >> $GITHUB_ENV
else
echo "IMAGE_EXISTS=false" >> $GITHUB_ENV
fi
- name: If skip build or image exists, set image tag to latest remote
id: get-latest-image-tag
if: inputs.build == false || env.IMAGE_EXISTS == 'true'
run: |
LATEST_TAG=$(aws ecr describe-images --repository-name $ECR_REPOSITORY --query 'sort_by(imageDetails,& imagePushedAt)[-1].imageTags[0]' --output text)
if [ -n "$LATEST_TAG" ]; then
echo "IMAGE_TAG=$LATEST_TAG" >> $GITHUB_ENV
fi
- name: Build, tag, and push image to Amazon ECR
id: build-image
if: env.IMAGE_EXISTS == 'false' && inputs.build == true
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
- uses: act10ns/slack@v1
if: always() && inputs.build == true
with:
status: ${{ job.status }}
channel: 'a-deployments'
config: .github/config/slack.yml
webhook-url: ${{ secrets.SLACK_WEBHOOK }}
outputs:
image_tag: ${{ env.IMAGE_TAG }}
deploy:
runs-on: ubuntu-latest
needs: build
environment: ${{ github.ref_name == 'main' && 'production' || 'development' }}
if: always() && !cancelled()
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{env.AWS_ACCOUNT_ID}}:role/platform-github-action-role-${{env.PLATFORM}}
aws-region: ${{ env.REGION }}
role-duration-seconds: 3600
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Set ECR environment variables
run: |
echo "ECR_REGISTRY=${{ steps.login-ecr.outputs.registry }}" >> $GITHUB_ENV
echo "IMAGE_TAG=${{ needs.build.outputs.image_tag }}" >> $GITHUB_ENV
- name: Get Latest Image ARN
id: get-image-arn
run: |
IMAGE_URI="$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
echo "Verifying image: $IMAGE_URI"
if ! aws ecr describe-images --repository-name $ECR_REPOSITORY --image-ids imageTag=$IMAGE_TAG 2>/dev/null; then
echo "Image verification failed: $IMAGE_URI not found in repository $ECR_REPOSITORY"
exit 1
fi
echo "Image verification successful"
echo "IMAGE_URI=$IMAGE_URI" >> $GITHUB_ENV
- name: Create Task Definition
id: task-def
run: |
MIGRATE_SETUP='node -e "require('\''./generated/src/db/Migrations.bs.js'\'').runUpMigrations(true, true)"'
MIGRATE_UP='node -e "require('\''./generated/src/db/Migrations.bs.js'\'').runUpMigrations(true)"'
MIGRATE_DOWN='node -e "require('\''./generated/src/db/Migrations.bs.js'\'').runDownMigrations(true)"'
GRANT_PERMISSIONS='pnpm ts-node scripts/grant-aggregate-permissions.ts'
RUN_INDEXER='TUI_OFF=true pnpm ts-node generated/src/Index.bs.js'
case "${{ inputs.env }}" in
"SCHEMA_CHANGED")
CMD="$MIGRATE_SETUP && $GRANT_PERMISSIONS && $RUN_INDEXER"
;;
"MIGRATE_UP")
CMD="$MIGRATE_UP && $GRANT_PERMISSIONS && $RUN_INDEXER"
;;
"MIGRATE_DOWN_UP")
CMD="$MIGRATE_DOWN && $MIGRATE_UP && $GRANT_PERMISSIONS && $RUN_INDEXER"
;;
"JUST_INDEX")
CMD="$GRANT_PERMISSIONS && $RUN_INDEXER"
;;
*)
echo "Invalid command type" && exit 1
;;
esac
aws ecs describe-task-definition --task-definition indexer-${{ env.PLATFORM }} --query 'taskDefinition' > task-definition-full.json
jq 'del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)' task-definition-full.json > task-definition.json
RUNNING_TASK_ARN=$(aws ecs list-tasks --cluster ${{env.PLATFORM}}-services-cluster --service-name ${{ env.REPO_NAME }}-${{env.PLATFORM}}-svc --desired-status RUNNING --query 'taskArns[0]' --output text)
if [[ "$RUNNING_TASK_ARN" != "None" && -n "$RUNNING_TASK_ARN" ]]; then
RUNNING_TASK_DEF_ARN=$(aws ecs describe-tasks --cluster ${{env.PLATFORM}}-services-cluster --tasks $RUNNING_TASK_ARN --query 'tasks[0].taskDefinitionArn' --output text)
if [[ "$RUNNING_TASK_DEF_ARN" != "None" && -n "$RUNNING_TASK_DEF_ARN" ]]; then
aws ecs describe-task-definition --task-definition indexer-${{ env.PLATFORM }} --query 'taskDefinition' > running-task-definition.json
CURRENT_DB=$(jq -r '.containerDefinitions[] | select(.name == "graphql-engine") | .environment[] | select(.name == "HASURA_GRAPHQL_DATABASE_URL") | .value' running-task-definition.json | grep -o 'blue\|green')
else
CURRENT_DB=$(jq -r '.containerDefinitions[] | select(.name == "graphql-engine") | .environment[] | select(.name == "HASURA_GRAPHQL_DATABASE_URL") | .value' task-definition.json | grep -o 'blue\|green')
fi
else
CURRENT_DB=$(jq -r '.containerDefinitions[] | select(.name == "graphql-engine") | .environment[] | select(.name == "HASURA_GRAPHQL_DATABASE_URL") | .value' task-definition.json | grep -o 'blue\|green')
fi
if [[ "${{ inputs.env }}" == "SCHEMA_CHANGED" || "${{ inputs.env }}" == "MIGRATE_DOWN_UP" ]]; then
NEW_DB=$([ "$CURRENT_DB" = "blue" ] && echo "green" || echo "blue")
echo "Switching database to $NEW_DB from $CURRENT_DB"
else
NEW_DB=$CURRENT_DB
echo "Keeping current database $CURRENT_DB as chosen command is not SCHEMA_CHANGED or MIGRATE_DOWN_UP"
fi
jq --arg cmd "$CMD" --arg img "$IMAGE_URI" --arg new_db "$NEW_DB" '
(.containerDefinitions[] | select(.name == "graphql-engine") | .environment[] | select(.name == "HASURA_GRAPHQL_DATABASE_URL") | .value) |= sub("/(blue|green)\\?"; "/\($new_db)?") |
(.containerDefinitions[] | select(.name == "indexer-${{ env.PLATFORM }}") | .environment[] | select(.name == "ENVIO_PG_DATABASE") | .value) = $new_db |
(.containerDefinitions[] | select(.name == "indexer-${{ env.PLATFORM }}") | .command) = ["sh", "-c", $cmd] |
(.containerDefinitions[] | select(.name == "indexer-${{ env.PLATFORM }}") | .image) = $img
' task-definition.json > updated-task-definition.json
mv updated-task-definition.json task-definition.json
echo "TASK_DEFINITION_ARN=$(aws ecs register-task-definition --cli-input-json file://task-definition.json --query 'taskDefinition.taskDefinitionArn' --output text)" >> $GITHUB_ENV
- name: Create CodeDeploy Deployment
timeout-minutes: 45
run: |
cat > appspec.json << EOF
{
"version": "0.0",
"Resources": [
{
"TargetService": {
"Type": "AWS::ECS::Service",
"Properties": {
"TaskDefinition": "${{ env.TASK_DEFINITION_ARN }}",
"LoadBalancerInfo": {
"ContainerName": "graphql-engine",
"ContainerPort": 8080
},
"PlatformVersion": "LATEST"
}
}
}
]
}
EOF
APPSPEC_CONTENT=$(cat appspec.json | jq -c . | jq -R .)
cat > revision.json << EOF
{
"revisionType": "AppSpecContent",
"appSpecContent": {
"content": ${APPSPEC_CONTENT}
}
}
EOF
echo "Revision JSON content:"
cat revision.json
aws deploy create-deployment \
--application-name ${{ env.REPO_NAME }}-${{ env.PLATFORM }} \
--deployment-group-name ${{ env.REPO_NAME }}-${{ env.PLATFORM }}-dg \
--revision file://revision.json \
--deployment-config-name CodeDeployDefault.ECSAllAtOnce \
--region ${{ env.REGION }} \
--description "Deploying ${{ env.REPO_NAME }}-${{ env.PLATFORM }} with task definition ${{ env.TASK_DEFINITION_ARN }}"
DEPLOYMENT_ID=$(aws deploy list-deployments \
--application-name ${{ env.REPO_NAME }}-${{ env.PLATFORM }} \
--deployment-group-name ${{ env.REPO_NAME }}-${{ env.PLATFORM }}-dg \
--query 'deployments[0]' \
--output text)
echo "Waiting for deployment $DEPLOYMENT_ID to complete..."
while true; do
STATUS=$(aws deploy get-deployment \
--deployment-id $DEPLOYMENT_ID \
--query 'deploymentInfo.status' \
--output text)
if [[ "$STATUS" == "Succeeded" ]]; then
echo "Deployment completed successfully!"
break
elif [[ "$STATUS" == "Failed" || "$STATUS" == "Stopped" ]]; then
echo "Deployment failed with status: $STATUS"
aws deploy get-deployment \
--deployment-id $DEPLOYMENT_ID \
--query 'deploymentInfo.errorInformation' \
--output json
exit 1
fi
echo "Deployment status: $STATUS. Waiting..."
sleep 30
done
- name: Update HTTPS Target Group
run: |
# Define the ECS cluster, service, and region
# Get ECS service load balancer information
SERVICE_INFO=$(aws ecs describe-services \
--cluster ${{env.PLATFORM}}-services-cluster \
--services ${{ env.REPO_NAME }}-${{env.PLATFORM}}-svc \
--query 'services[0].loadBalancers' \
--region ${{ env.REGION }} \
--output json)
# Extract the target group ARN used by the service on HTTP port (port 80 or custom port)
TARGET_GROUP_ARN=$(echo $SERVICE_INFO | jq -r '.[] | select(.containerPort == 8080) | .targetGroupArn')
if [ -z "$TARGET_GROUP_ARN" ]; then
echo "Error: Could not find HTTP target group"
exit 1
fi
echo "Found HTTP Target Group ARN: $TARGET_GROUP_ARN"
# Get Load Balancer ARN associated with the target group
LB_ARN=$(aws elbv2 describe-target-groups \
--target-group-arns "$TARGET_GROUP_ARN" \
--query 'TargetGroups[0].LoadBalancerArns[0]' \
--output text \
--region ${{ env.REGION }})
if [ -z "$LB_ARN" ]; then
echo "Error: Could not find Load Balancer ARN"
exit 1
fi
echo "Found Load Balancer ARN: $LB_ARN"
# Get the HTTPS Listener ARN (port 443) from the Load Balancer
HTTPS_LISTENER_ARN=$(aws elbv2 describe-listeners \
--load-balancer-arn "$LB_ARN" \
--query 'Listeners[?Port==`443`].ListenerArn' \
--output text \
--region ${{ env.REGION }})
if [ -z "$HTTPS_LISTENER_ARN" ]; then
echo "Error: Could not find HTTPS listener (port 443)"
exit 1
fi
echo "Found HTTPS Listener ARN: $HTTPS_LISTENER_ARN"
# Modify the HTTPS listener to forward traffic to the HTTP target group
aws elbv2 modify-listener \
--listener-arn "$HTTPS_LISTENER_ARN" \
--default-actions Type=forward,TargetGroupArn="$TARGET_GROUP_ARN" \
--region ${{ env.REGION }}
echo "Successfully updated HTTPS listener to forward to HTTP target group"
- name: Slack Notification
uses: act10ns/slack@v1
with:
status: ${{ job.status }}
channel: 'a-deployments'
config: .github/config/slack.yml
webhook-url: ${{ secrets.SLACK_WEBHOOK }}
steps: ${{ toJson(steps) }}
if: always()