version: 0.2

# Deploy on merge to main. For each service it compares the commit sha CURRENTLY
# RUNNING in ECS against HEAD; only services whose own paths (or the shared base
# image) changed in that range are rebuilt and rolled. Everything else is skipped.
#
# Triggered by the EventBridge rule b2g-main-deploy on:
#   CodeCommit Pull Request State Change -> pullRequestMergeStatusUpdated
#   with isMerged=True and destinationReference=refs/heads/main
# i.e. ONLY when a pull request is merged into main. A direct push to main does NOT
# deploy; reconcile one with `aws codebuild start-build --project-name b2g-cd`, which is
# safe because the plan below compares each service against the sha it is actually running.
#
# Images are tagged with the commit sha and the task definition is pinned to that tag,
# so "which commit is in UAT" is answerable from the task definition and rollback is
#   aws ecs update-service --cluster b2g-prod --service <svc> --task-definition <family>:<rev>
#
# Requires gitCloneDepth 0 - the diff range starts at an arbitrary older commit.

env:
  variables:
    AWS_DEFAULT_REGION: "ap-south-1"
    # Which tier this project deploys to. Set per CodeBuild project, so the
    # target is visible in the project configuration rather than inferred
    # from git state at run time.
    TARGET_ENV: "prod"

phases:
  install:
    runtime-versions:
      python: 3.12

  pre_build:
    commands:
      - AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
      - ECR_REGISTRY="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com"
      - aws ecr get-login-password --region "$AWS_DEFAULT_REGION" | docker login --username AWS --password-stdin "$ECR_REGISTRY"
      - HEAD_SHA=$(git rev-parse HEAD)
      - echo "Deploying from $HEAD_SHA"
      - python3 ci/pipeline.py --env "$TARGET_ENV" plan --head "$HEAD_SHA" > /tmp/plan.json
      - cat /tmp/plan.json
      - python3 -c "import json;p=json.load(open('/tmp/plan.json'));print('\n'.join(s['name'] for s in p['services']))" > /tmp/services.txt
      - REBUILD_BASE=$(python3 -c "import json;print(json.load(open('/tmp/plan.json'))['base'])")

  build:
    commands:
      - |
        set -e
        if [ ! -s /tmp/services.txt ]; then
          echo "Nothing to deploy - every service is already running this commit."
          exit 0
        fi
        if [ "$REBUILD_BASE" = "True" ]; then
          echo "=================== shared base image"
          python3 ci/pipeline.py --env "$TARGET_ENV" build-base --tag "$HEAD_SHA" --push
        else
          # Backends build FROM it, so make sure the published base is present locally.
          docker pull "${ECR_REGISTRY}/b2g/php-nginx-base:8.2"
        fi
        while read -r SVC; do
          [ -z "$SVC" ] && continue
          echo "=================== $SVC"
          python3 ci/pipeline.py --env "$TARGET_ENV" build --service "$SVC" --tag "$HEAD_SHA" --push
          python3 ci/pipeline.py --env "$TARGET_ENV" smoke --service "$SVC" --tag "$HEAD_SHA"
        done < /tmp/services.txt

  post_build:
    commands:
      # Deploy only after every image has been built AND smoke-tested, so a broken image
      # in the set does not leave the cluster half-rolled.
      - |
        set -e
        # Nothing to deploy (no changed services, or a re-run of an already-live
        # commit): the plan writes an empty name list as a lone newline, which -s
        # treats as non-empty, so guard on actual (non-whitespace) content instead.
        # Otherwise "wait --services $(paste ...)" gets no argument and argparse
        # exits 2, failing an otherwise successful build.
        if ! grep -q '[^[:space:]]' /tmp/services.txt 2>/dev/null; then
          echo "No services to deploy."
          exit 0
        fi
        if [ "$CODEBUILD_BUILD_SUCCEEDING" != "1" ]; then
          echo "Build phase failed - refusing to deploy."
          exit 1
        fi
        # Start every rollout first, then wait for all of them together. One rollout takes
        # ~4.5 min (new task healthy + old task drained), so waiting per service made a
        # 16-service deploy take 70+ min and time the build out with the cluster half
        # rolled. ECS rolling updates keep the old tasks serving until the new ones are
        # healthy, so starting them concurrently does not reduce availability.
        while read -r SVC; do
          [ -z "$SVC" ] && continue
          python3 ci/pipeline.py --env "$TARGET_ENV" deploy --service "$SVC" --tag "$HEAD_SHA" --no-wait
        done < /tmp/services.txt
        SVCS=$(paste -sd' ' /tmp/services.txt)
        [ -n "$SVCS" ] && python3 ci/pipeline.py --env "$TARGET_ENV" wait --services $SVCS
        echo "=================== deployed:"
        cat /tmp/services.txt
