diff --git a/.github/workflows/deploy-to-dev.yml b/.github/workflows/deploy-to-dev.yml index 6c473e2..d8d97b6 100644 --- a/.github/workflows/deploy-to-dev.yml +++ b/.github/workflows/deploy-to-dev.yml @@ -1,4 +1,7 @@ -name: Deploy Survey to Dev (GitOps) +# Post AWS->DO migration: main builds/pushes to GHCR and deploys straight to +# do-prod (the only live cluster — EKS dev/prod are retired). Filename kept to +# preserve any required-status-check config; see the job below for the real target. +name: Build & Deploy Survey to Prod (GitOps) on: push: @@ -29,7 +32,11 @@ jobs: cache-dependency-path: web/package-lock.json - name: Install templ - run: go install github.com/a-h/templ/cmd/templ@latest + # Pin the CLI to the go.mod runtime version (github.com/a-h/templ) so + # `templ generate` emits code the pinned runtime can compile. `@latest` + # drifts ahead of the runtime (e.g. templ.ResolveAttributeValue) and + # breaks the build. Bump this in lockstep with go.mod. + run: go install github.com/a-h/templ/cmd/templ@v0.3.960 - name: Install dependencies run: go mod download @@ -52,6 +59,9 @@ jobs: build-and-deploy: runs-on: ubuntu-latest if: github.event_name == 'push' && github.ref == 'refs/heads/main' + permissions: + contents: read + packages: write # push to ghcr.io/openmeet-team/survey via GITHUB_TOKEN outputs: image_tag: ${{ steps.set-outputs.outputs.image_tag }} @@ -59,18 +69,16 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 + # Auth to GHCR. GITHUB_TOKEN works once the package is linked to this repo + # (Package settings -> Manage Actions access -> add survey = Write). + # To use a PAT instead: set password to ${{ secrets.GHCR_PAT }} and drop the + # packages: write permission above. + - name: Log in to GHCR + uses: docker/login-action@v3 with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ vars.AWS_REGION || 'us-east-1' }} - - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 - with: - mask-password: true + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Set image tag id: set-outputs @@ -78,20 +86,15 @@ jobs: echo "IMAGE_TAG=${{ github.sha }}" >> $GITHUB_ENV echo "image_tag=${{ github.sha }}" >> $GITHUB_OUTPUT - - name: Build, tag, and push image to Amazon ECR + - name: Build and push image to GHCR env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - ECR_REPOSITORY: openmeet-ecr/survey - GIT_REVISION: ${{ github.sha }} - GIT_BRANCH: ${{ github.ref_name }} + IMAGE: ghcr.io/openmeet-team/survey run: | - echo "Building image with tag: $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" - docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . - docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG - - # Tag as latest for main branch - docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:latest - docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest + set -euo pipefail + echo "Building $IMAGE:$IMAGE_TAG" + docker build -t "$IMAGE:$IMAGE_TAG" -t "$IMAGE:latest" . + docker push "$IMAGE:$IMAGE_TAG" + docker push "$IMAGE:latest" gitops-update: needs: [build-and-deploy] @@ -100,9 +103,8 @@ jobs: with: service_name: survey image_tag: ${{ needs.build-and-deploy.outputs.image_tag }} - environment: dev - ecr_registry: 433321780850.dkr.ecr.us-east-1.amazonaws.com - ecr_repository: openmeet-ecr/survey - commit_message: "GitOps: Update Survey to ${{ needs.build-and-deploy.outputs.image_tag }} from main branch" + image_name: ghcr.io/openmeet-team/survey + environment: do-prod + commit_message: "GitOps: deploy survey ${{ needs.build-and-deploy.outputs.image_tag }} to do-prod (main)" secrets: GH_PAT: ${{ secrets.GH_PAT_INFRASTRUCTURE }} diff --git a/.github/workflows/update-image-gitops.yml b/.github/workflows/update-image-gitops.yml index ed2134f..e46b562 100644 --- a/.github/workflows/update-image-gitops.yml +++ b/.github/workflows/update-image-gitops.yml @@ -4,25 +4,22 @@ on: workflow_call: inputs: service_name: - description: 'Service name (api, platform, survey, etc.)' + description: 'Service name (api, platform, biz, ...) — used for log/commit context' required: true type: string image_tag: - description: 'Docker image tag to deploy' + description: 'Image tag (git SHA) to deploy' required: true type: string - environment: - description: 'Target environment (dev, prod, pr-123, etc.)' - required: true - type: string - ecr_registry: - description: 'ECR registry URL' + image_name: + description: 'Full GHCR image name to pin (matched against images[].newName), e.g. ghcr.io/openmeet-team/openmeet-api' required: true type: string - ecr_repository: - description: 'ECR repository name' - required: true + environment: + description: 'Target overlay under k8s/environments/ (do-prod is the only live cluster post-migration)' + required: false type: string + default: do-prod commit_message: description: 'Git commit message' required: false @@ -30,7 +27,7 @@ on: default: 'Update image tag' secrets: GH_PAT: - description: 'GitHub PAT with repo access' + description: 'GitHub PAT with write access to the openmeet-infrastructure repo' required: true jobs: @@ -44,67 +41,76 @@ jobs: token: ${{ secrets.GH_PAT }} ref: main - - name: Update kustomization.yaml with new image tag + - name: Pin ${{ inputs.service_name }} image to ${{ inputs.image_tag }} + env: + IMAGE_NAME: ${{ inputs.image_name }} + IMAGE_TAG: ${{ inputs.image_tag }} + ENVIRONMENT: ${{ inputs.environment }} run: | - KUSTOMIZATION_PATH="k8s/environments/${{ inputs.environment }}/kustomization.yaml" - IMAGE_FULL="${{ inputs.ecr_registry }}/${{ inputs.ecr_repository }}:${{ inputs.image_tag }}" - - echo "Updating $KUSTOMIZATION_PATH" - echo "Service: ${{ inputs.service_name }}" - echo "New image: $IMAGE_FULL" - - # Update the image tag in the kustomization file - # This uses sed to find the line with the service image and update it - sed -i "s|image: .*${{ inputs.ecr_repository }}:.*|image: $IMAGE_FULL|" "$KUSTOMIZATION_PATH" + set -euo pipefail + KUSTOMIZATION_PATH="k8s/environments/${ENVIRONMENT}/kustomization.yaml" + echo "Pinning $IMAGE_NAME -> $IMAGE_TAG in $KUSTOMIZATION_PATH" + + # Single source of truth: the overlay's `images:` transformer. Set newTag + # on EVERY entry whose newName is this service's GHCR image. Keying on + # newName (not the ECR lookup key) updates all references at once — + # e.g. survey has two entries (bare `survey` + the ECR name) that share + # one newName. yq (mikefarah) is preinstalled on ubuntu-latest runners. + yq -i '(.images[] | select(.newName == strenv(IMAGE_NAME))).newTag = strenv(IMAGE_TAG)' "$KUSTOMIZATION_PATH" + + # Guard: fail loudly if nothing matched (typo in image_name, or the + # overlay has no images[] entry for this service) — never push a no-op + # that silently fails to deploy. + matched="$(yq '[.images[] | select(.newName == strenv(IMAGE_NAME) and .newTag == strenv(IMAGE_TAG))] | length' "$KUSTOMIZATION_PATH")" + if [ "$matched" -eq 0 ]; then + echo "::error::No images[] entry with newName=$IMAGE_NAME in $KUSTOMIZATION_PATH — nothing to deploy." + exit 1 + fi + echo "Updated $matched image reference(s)." - # Show the diff git diff "$KUSTOMIZATION_PATH" - name: Commit and push changes + env: + ENVIRONMENT: ${{ inputs.environment }} + COMMIT_MESSAGE: ${{ inputs.commit_message }} run: | + set -euo pipefail git config --global user.email "github-actions@github.com" git config --global user.name "GitHub Actions Bot" - KUSTOMIZATION_PATH="k8s/environments/${{ inputs.environment }}/kustomization.yaml" - + KUSTOMIZATION_PATH="k8s/environments/${ENVIRONMENT}/kustomization.yaml" git add "$KUSTOMIZATION_PATH" - # Check if there are changes to commit if git diff --staged --quiet; then - echo "No changes to commit - image tag may already be up to date" + echo "No changes to commit — image tag already up to date." exit 0 fi - git commit -m "${{ inputs.commit_message }}" + git commit -m "$COMMIT_MESSAGE" - # Push with retry logic to handle concurrent updates + # Push with retry to handle concurrent gitops updates from sibling repos. MAX_RETRIES=5 RETRY_COUNT=0 - while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do echo "Attempting to push (attempt $((RETRY_COUNT + 1))/$MAX_RETRIES)..." - if git push origin main; then - echo "Successfully pushed changes" + echo "✅ Successfully pushed changes" exit 0 - else - echo "Push failed, pulling and retrying..." - RETRY_COUNT=$((RETRY_COUNT + 1)) - - if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then - # Pull with rebase to get latest changes - git pull --rebase origin main - sleep $((RETRY_COUNT * 2)) # Exponential backoff - fi + fi + echo "⚠️ Push failed, pulling and retrying..." + RETRY_COUNT=$((RETRY_COUNT + 1)) + if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then + git pull --rebase origin main + sleep $((RETRY_COUNT * 2)) fi done - echo "Failed to push after $MAX_RETRIES attempts" + echo "❌ Failed to push after $MAX_RETRIES attempts" exit 1 - - name: Trigger ArgoCD sync (optional) - if: inputs.environment == 'dev' - continue-on-error: true + - name: Deployment note run: | - echo "ArgoCD will auto-detect the change within 3 minutes" - echo "Or manually sync via UI: https://argocd-dev.openmeet.net" + echo "Committed new tag to k8s/environments/${{ inputs.environment }}." + echo "ArgoCD (do-prod app, automated sync) will roll this out within ~3 minutes." + echo "Watch: kubectl -n prod rollout status deployment/${{ inputs.service_name }}"