diff --git a/.github/workflows/aws-backend-deploy.yml b/.github/workflows/aws-backend-deploy.yml index 73f6a1fb4..ba3a9a8d7 100644 --- a/.github/workflows/aws-backend-deploy.yml +++ b/.github/workflows/aws-backend-deploy.yml @@ -4,12 +4,12 @@ on: inputs: env: type: choice - description: 'AWS Incubator Env' - options: - - dev - - prod + description: "AWS Incubator Env" + options: + - dev + - prod ref: - description: 'Branch, Tag, or SHA' + description: "Branch, Tag, or SHA" required: true env: AWS_SHARED_CLUSTER: incubator-prod @@ -19,95 +19,108 @@ env: DOCKER_PATH: backend jobs: setup_env: - name: Set-up environment + name: Set-up environment runs-on: ubuntu-latest steps: - - name: Debug Action - uses: hmarr/debug-action@v1.0.0 - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event.inputs.ref }} - - name: Set AWS Env & Image Tag per workflow - run: | - SHORT_SHA=$(git rev-parse --short HEAD) - if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then - INPUT_ENV=${{ github.event.inputs.env }}; INPUT_REF=${{ github.event.inputs.ref }} - echo AWS_APPENV="$AWS_APP_NAME"-$INPUT_ENV >> $GITHUB_ENV - echo IMAGE_TAG=$SHORT_SHA >> $GITHUB_ENV - fi + - name: Debug Action + uses: hmarr/debug-action@v1.0.0 + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.ref }} + - name: Set AWS Env & Image Tag per workflow + run: | + if [[ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; then + INPUT_ENV=${{ github.event.inputs.env }}; INPUT_REF=${{ github.event.inputs.ref }} + echo AWS_APPENV="$AWS_APP_NAME"-$INPUT_ENV >> $GITHUB_ENV + echo IMAGE_TAG=$(git rev-parse --short HEAD) >> $GITHUB_ENV + echo BUILD_SHA=$(git rev-parse --short HEAD) >> $GITHUB_ENV + fi outputs: AWS_APPENV: ${{ env.AWS_APPENV }} IMAGE_TAG: ${{ env.IMAGE_TAG }} + BUILD_SHA: ${{ env.BUILD_SHA }} build: name: Build & Push Docker Image runs-on: ubuntu-latest needs: [setup_env] steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event.inputs.ref }} - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.INCUBATOR_AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.INCUBATOR_AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v1 - - name: Init Docker Cache - uses: jpribyl/action-docker-layer-caching@v0.1.0 - with: - key: ${{ github.workflow }}-2-{hash} - restore-keys: | - ${{ github.workflow }}-2- - - name: Build & Push Image to ECR - uses: kciter/aws-ecr-action@v3 - with: - access_key_id: ${{ secrets.INCUBATOR_AWS_ACCESS_KEY_ID }} - secret_access_key: ${{ secrets.INCUBATOR_AWS_SECRET_ACCESS_KEY }} - account_id: ${{ secrets.INCUBATOR_AWS_ACCOUNT_ID }} - repo: ${{ needs.setup_env.outputs.AWS_APPENV }} - region: ${{ env.AWS_REGION }} - tags: latest,${{ needs.setup_env.outputs.IMAGE_TAG }} - dockerfile: ${{ env.DOCKERFILE }} - path: ${{ env.DOCKER_PATH }} + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.ref }} + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.INCUBATOR_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.INCUBATOR_AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Debug Build Variables + env: + BUILD_SHA: ${{ needs.setup_env.outputs.BUILD_SHA }} + IMAGE_TAG: ${{ needs.setup_env.outputs.IMAGE_TAG }} + run: | + echo "=== Build Debug Information ===" + echo "BUILD_SHA: $BUILD_SHA" + echo "IMAGE_TAG: $IMAGE_TAG" + echo "DOCKERFILE: ${{ env.DOCKERFILE }}" + echo "DOCKER_PATH: ${{ env.DOCKER_PATH }}" + echo "================================" + - name: Build & Push Image to ECR + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + ECR_REPOSITORY: ${{ needs.setup_env.outputs.AWS_APPENV }} + IMAGE_TAG: ${{ needs.setup_env.outputs.IMAGE_TAG }} + BUILD_SHA: ${{ needs.setup_env.outputs.BUILD_SHA }} + run: | + docker buildx build \ + --platform linux/amd64 \ + --cache-from type=gha \ + --cache-to type=gha,mode=max \ + --push \ + --build-arg BUILD_SHA=$BUILD_SHA \ + -f ${{ env.DOCKERFILE }} \ + -t $ECR_REGISTRY/$ECR_REPOSITORY:latest \ + -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \ + ${{ env.DOCKER_PATH }} deploy: name: Deploy to AWS ECS runs-on: ubuntu-latest needs: [setup_env, build] steps: - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.INCUBATOR_AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.INCUBATOR_AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v1 - - name: Pull Task Definition & write to file - id: aws-task-definition - run: | - aws ecs describe-task-definition \ - --task-definition ${{ needs.setup_env.outputs.AWS_APPENV }} \ - --query taskDefinition | \ - jq 'del(.taskDefinitionArn,.revision,.status,.registeredBy,.registeredAt,.compatibilities,.requiresAttributes)' > task-def.json - - name: Interpolate new Docker Image into Task Definition - id: task-definition - uses: aws-actions/amazon-ecs-render-task-definition@v1 - with: - task-definition: task-def.json - container-name: ${{ needs.setup_env.outputs.AWS_APPENV }} - image: ${{ steps.login-ecr.outputs.registry }}/${{ needs.setup_env.outputs.AWS_APPENV }}:${{ needs.setup_env.outputs.IMAGE_TAG }} - - name: Deploy Amazon ECS - uses: aws-actions/amazon-ecs-deploy-task-definition@v1 - with: - task-definition: ${{ steps.task-definition.outputs.task-definition }} - service: ${{ needs.setup_env.outputs.AWS_APPENV }} - cluster: ${{ env.AWS_SHARED_CLUSTER }} - wait-for-service-stability: true - wait-for-minutes: 5 minutes - + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.INCUBATOR_AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.INCUBATOR_AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + - name: Pull Task Definition & write to file + id: aws-task-definition + run: | + aws ecs describe-task-definition \ + --task-definition ${{ needs.setup_env.outputs.AWS_APPENV }} \ + --query taskDefinition | \ + jq 'del(.taskDefinitionArn,.revision,.status,.registeredBy,.registeredAt,.compatibilities,.requiresAttributes)' > task-def.json + - name: Interpolate new Docker Image into Task Definition + id: task-definition + uses: aws-actions/amazon-ecs-render-task-definition@v1 + with: + task-definition: task-def.json + container-name: ${{ needs.setup_env.outputs.AWS_APPENV }} + image: ${{ steps.login-ecr.outputs.registry }}/${{ needs.setup_env.outputs.AWS_APPENV }}:${{ needs.setup_env.outputs.IMAGE_TAG }} + - name: Deploy Amazon ECS + uses: aws-actions/amazon-ecs-deploy-task-definition@v1 + with: + task-definition: ${{ steps.task-definition.outputs.task-definition }} + service: ${{ needs.setup_env.outputs.AWS_APPENV }} + cluster: ${{ env.AWS_SHARED_CLUSTER }} + wait-for-service-stability: true + wait-for-minutes: 5 diff --git a/backend/Dockerfile.prod b/backend/Dockerfile.prod index 382e9350b..b80ccf41c 100644 --- a/backend/Dockerfile.prod +++ b/backend/Dockerfile.prod @@ -7,9 +7,12 @@ RUN yarn install --pure-lockfile COPY . . FROM node:18.12.0-slim AS api-production +ARG BUILD_SHA EXPOSE 4000 -USER node WORKDIR /srv/backend COPY --from=api-builder /srv/backend/node_modules ./node_modules COPY . . +ENV BUILD_SHA=${BUILD_SHA} +RUN echo "${BUILD_SHA:-unknown}" > BUILD_INFO +USER node CMD ["npm", "run", "start"] diff --git a/backend/controllers/healthCheck.controller.js b/backend/controllers/healthCheck.controller.js index 068d5557e..8be9bbed9 100644 --- a/backend/controllers/healthCheck.controller.js +++ b/backend/controllers/healthCheck.controller.js @@ -1,7 +1,53 @@ +const { execSync } = require('child_process'); +const fs = require('fs'); + const HealthCheckController = {}; -HealthCheckController.isAlive = (_, res) => { - res.status(200).send("I'm Alive!"); +function getBuildInfo() { + // Method 1: BUILD_SHA environment variable (from Docker build arg) - PRIORITY + if ( + process.env.BUILD_SHA && + process.env.BUILD_SHA !== 'undefined' && + process.env.BUILD_SHA !== '' + ) { + return process.env.BUILD_SHA; + } + + // Method 2: Check BUILD_INFO file (created during Docker build) + try { + if (fs.existsSync('/srv/backend/BUILD_INFO')) { + const buildInfo = fs.readFileSync('/srv/backend/BUILD_INFO', 'utf8').trim(); + if (buildInfo && buildInfo !== 'unknown' && buildInfo !== '') { + return buildInfo; + } + } + } catch { + // BUILD_INFO file not available + } + + // Method 3: Try git command (for local development) + try { + const gitSha = execSync('git rev-parse --short HEAD 2>/dev/null', { encoding: 'utf8', shell: true }).trim(); + if (gitSha && gitSha !== '' && gitSha !== 'unknown') { + return gitSha; + } + } catch { + // Git not available + } + + return 'unknown'; } +// Cache build info at startup to avoid repeated file I/O and Git calls +const cachedBuildInfo = getBuildInfo(); +const buildTimestamp = new Date().toISOString(); + +HealthCheckController.isAlive = (_, res) => { + res + .status(200) + .send( + `I'm Alive! Build: ${cachedBuildInfo} | Built: ${buildTimestamp} | Checked: ${new Date().toISOString()}`, + ); +}; + module.exports = HealthCheckController;