Preview [PR 35 - cms/main_page/contact] #463
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Workflow for building and deploying Hugo site (Production + Preview) | |
| name: Deploy Hugo site - Production and Preview | |
| run-name: ${{ github.event_name == 'pull_request' && format('Preview [PR {0} - {1}] {2} ', github.event.pull_request.number, github.head_ref, github.event.head_commit.message) || format('[{0}] {1}', github.ref_name, github.event.head_commit.message) }} | |
| on: | |
| # Only push to default branch — avoids double trigger when pushing to a PR branch (push + pull_request synchronize) | |
| push: | |
| branches: [main] | |
| # Runs on pull requests for Deploy Preview Links (one run per commit on a PR) | |
| pull_request: | |
| types: [opened, synchronize, reopened, closed] | |
| # Allows you to run this workflow manually from the Actions tab (choice input with options) | |
| workflow_dispatch: | |
| inputs: | |
| nomad_job_template: | |
| required: false | |
| description: The Nomad job template to use for the deployment. | |
| type: choice | |
| options: | |
| - nomad/template.nomad | |
| - nomad/decap.template.nomad | |
| default: nomad/template.nomad | |
| use_decap: | |
| required: false | |
| description: Override Decap usage. Set to "true" or "false" to override branch-based determination; leave as "auto" for automatic. | |
| type: choice | |
| options: | |
| - auto | |
| - "true" | |
| - "false" | |
| default: auto | |
| workflow_call: # allows reusing the workflow (string input; choice not supported for workflow_call) | |
| # inputs: | |
| # nomad_job_template: | |
| # required: false | |
| # description: The Nomad job template to use for the deployment (e.g. nomad/template.nomad or nomad/decap.template.nomad). | |
| # type: string | |
| # default: nomad/template.nomad | |
| # use_decap: | |
| # required: false | |
| # description: Override USE_DECAP ("true" or "false"); leave empty for branch-based determination. | |
| # type: string | |
| # default: "" | |
| # target: | |
| # required: false | |
| # type: string | |
| # secrets: | |
| # token: | |
| # required: false | |
| # env: | |
| # CONTAINER_PROJECT: "coastal-science.github.io" # "rcg-containers" # repo name dynamically extracted from `github.repository` by removing `github.owner` | |
| # Allow one concurrent deployment | |
| concurrency: | |
| group: ${{ github.repository }}-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || format('branch-{0}', github.ref_name) }} | |
| cancel-in-progress: true | |
| jobs: | |
| generate-timestamp: | |
| permissions: {} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Set TIMESTAMP and get REPO_NAME | |
| id: date | |
| run: | | |
| export DATE=$(date +'%Y%m%d-%H%M%S') | |
| REPO_NAME=${GITHUB_REPOSITORY#$GITHUB_REPOSITORY_OWNER/} | |
| echo "TIMESTAMP=$DATE" >> $GITHUB_ENV | |
| echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV | |
| outputs: | |
| TIMESTAMP: ${{ env.TIMESTAMP }} | |
| REPO_NAME: ${{ env.REPO_NAME }} | |
| ###################################################### | |
| ## Build and publish a staging site to GitHub Pages ## | |
| ###################################################### | |
| configure-pages: | |
| runs-on: ubuntu-latest | |
| permissions: # read-all | |
| contents: read | |
| pages: read | |
| steps: | |
| - name: Setup Pages | |
| id: pages | |
| uses: actions/configure-pages@v2 | |
| outputs: | |
| base_url: ${{ steps.pages.outputs.base_url }} | |
| # Build job | |
| build-website: | |
| needs: | |
| - generate-timestamp | |
| - configure-pages | |
| runs-on: ubuntu-latest | |
| container: | |
| image: peaceiris/hugo | |
| env: | |
| HUGO_VERSION: "${{ vars.HUGO_VERSION }}" # 0.147.8 # latest # 0.126.1 | |
| HUGO_FORMS: ${{ vars.HUGO_FORMS }} | |
| HUGO_GOOGLEANALYTICS: ${{ vars.HUGO_GOOGLEANALYTICS }} | |
| REPO_NAME: ${{ needs.generate-timestamp.outputs.REPO_NAME }} | |
| # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Setup Hugo | |
| uses: peaceiris/actions-hugo@v2 | |
| with: | |
| hugo-version: ${{ vars.HUGO_VERSION }} | |
| extended: true | |
| - name: Pre-Build ls | |
| run: | | |
| ls -lah | |
| echo "${{ needs.generate-timestamp.outputs.TIMESTAMP }}" from env | |
| echo "${{ needs.configure-pages.outputs.base_url }}/" from env | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: true # Fetch Hugo themes (true OR recursive) # failing on ubuntu container | |
| fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod | |
| # set-safe-directory: '/__w/coastal-science.github.io/coastal-science.github.io' # The process '/usr/bin/git' failed with exit code 128 | |
| # set-safe-directory: '/__w/${{ env.REPO_NAME }}/${{ env.REPO_NAME }}' # The process '/usr/bin/git' failed with exit code 128, resorting to manual call | |
| - name: Set safe directory workaround | |
| run: | | |
| git config --global --add safe.directory /__w/${{ env.REPO_NAME }}/${{ env.REPO_NAME }} | |
| - name: Build with Hugo | |
| env: | |
| # For maximum backward compatibility with Hugo modules | |
| HUGO_ENVIRONMENT: staging | |
| HUGO_ENV: staging | |
| run: | | |
| hugo \ | |
| --gc \ | |
| --buildDrafts \ | |
| --minify \ | |
| --baseURL "${{ needs.configure-pages.outputs.base_url }}/" | |
| - name: Post-Build ls | |
| run: ls -lah public/ | |
| - name: Upload artifact | |
| uses: actions/upload-pages-artifact@v3 | |
| with: | |
| path: ./public | |
| # Deployment job | |
| deploy-gh-pages: | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deployment.outputs.page_url }} | |
| # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages | |
| permissions: | |
| contents: write | |
| id-token: write | |
| pages: write | |
| runs-on: ubuntu-latest | |
| needs: build-website | |
| steps: | |
| - name: Deploy to GitHub Pages | |
| id: deployment | |
| uses: actions/deploy-pages@v4 | |
| ############################################ | |
| ## Create and publish docker image ## | |
| ## (handles both production and preview) ## | |
| ############################################ | |
| build-website-and-container-build: | |
| environment: docker-image | |
| # if: github.ref == 'refs/heads/main' | |
| # Skip build if PR is closed (cleanup job handles that) | |
| if: github.event_name != 'pull_request' || github.event.action != 'closed' | |
| needs: | |
| - generate-timestamp | |
| env: | |
| HUGO_VERSION: ${{ vars.HUGO_VERSION }} # 0.147.8 # latest # 0.126.1 | |
| HUGO_FORMS: ${{ vars.HUGO_FORMS }} | |
| HUGO_GOOGLEANALYTICS: ${{ vars.HUGO_GOOGLEANALYTICS }} | |
| REGISTRY: ghcr.io # docker.github.sfu.ca | |
| IMAGE_NAME: ${{ github.repository }} | |
| REPO_NAME: ${{ needs.generate-timestamp.outputs.REPO_NAME }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| # This is used to complete the identity challenge | |
| # with sigstore/fulcio when running outside of PRs. | |
| id-token: write | |
| outputs: | |
| tags: ${{ steps.meta.outputs.tags }} | |
| commit_sha: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }} | |
| pr_number: ${{ github.event.pull_request.number || '' }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: true # Fetch Hugo themes (true OR recursive) # failing on ubuntu container | |
| fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod | |
| # set-safe-directory: '/__w/coastal-science.github.io/coastal-science.github.io' | |
| # set-safe-directory: '/__w/${{ env.REPO_NAME }}/${{ env.REPO_NAME }}' # The process '/usr/bin/git' failed with exit code 128, resorting to manual call | |
| - name: Set safe directory workaround | |
| run: | | |
| git config --global --add safe.directory /__w/${{ env.REPO_NAME }}/${{ env.REPO_NAME }} | |
| - name: Generate tags | |
| id: tags | |
| run: | | |
| # Get the current branch name | |
| branch_name=$(git rev-parse --abbrev-ref HEAD) | |
| # docker tag limit is 128 characters | |
| # The timestamp (YYYY_MM_DD_HH_MM_SS), commit id (7), | |
| # and punctuation (additional _) take up 29 characters. | |
| # For 3 catalogs the branch names is can occupy the | |
| # remaining characters = floor((128-29*3)/3) = 13 | |
| branch_name=${branch_name:0:13} | |
| # Get the abbreviated commit hash | |
| commit_hash=$(git rev-parse --short HEAD) | |
| # Get the current commit datetime (without timezone) | |
| commit_date=$(git show -s --format=%ci HEAD) | |
| # remove last five characters (timezone information) | |
| commit_date=${commit_date:0:${#commit_date}-5} #-5 digits causes 'expression < 0' error. Instead dynamically get the string length. | |
| # Get the current commit datetime (without timezone) | |
| commit_date=$(git show -s --date=format:'%Y%m%d-%H%M' --format=%cd HEAD) | |
| # Combine the parts into the desired format | |
| result="${branch_name}_${commit_hash}_${commit_date}" | |
| # remove leading/trailing spaces | |
| # formatted_result=$(awk '{$1=$1;print}') | |
| formatted_result=$(echo $result | xargs) | |
| # Replace spaces, colons, and dashes with underscores | |
| formatted_result=$(echo "$formatted_result" | sed 's/[ :\-]/_/g') | |
| #branch_name=$(echo $branch_name | xargs) | |
| #branch_name=$(echo "$branch_name" | sed 's/[ :\-]/_/g') | |
| commit_hash=$(echo $commit_hash | xargs) | |
| commit_hash=$(echo "$commit_hash" | sed 's/[ :\-]/_/g') | |
| commit_date=$(echo $commit_date | xargs) | |
| #commit_date=$(echo "$commit_date" | sed 's/[ :\-]/_/g') | |
| # Print the final result and return it | |
| echo "$formatted_result" | |
| #echo "branch_name=${branch_name}" >> "$GITHUB_OUTPUT" | |
| echo "commit_hash=${commit_hash}" >> "$GITHUB_OUTPUT" | |
| echo "commit_date=${commit_date}" >> "$GITHUB_OUTPUT" | |
| - name: List repos in workspace | |
| run: | | |
| ls -lah | |
| du -sh * | |
| echo The current workspace repo is.. | |
| pwd | |
| - name: Setup Hugo | |
| uses: peaceiris/actions-hugo@v2 | |
| with: | |
| hugo-version: ${{ vars.HUGO_VERSION }} | |
| extended: true | |
| - name: Build and Minify with Hugo | |
| env: | |
| # For maximum backward compatibility with Hugo modules | |
| HUGO_ENVIRONMENT: ${{ github.event_name == 'pull_request' && 'preview' || 'production' }} | |
| HUGO_ENV: ${{ github.event_name == 'pull_request' && 'preview' || 'production' }} | |
| run: | | |
| hugo \ | |
| --gc \ | |
| --minify \ | |
| --baseURL "${{ env.HUGO_BASEURL }}/" | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| # Login against a Docker registry except on PR | |
| # https://github.com/docker/login-action | |
| - name: Log into registry ${{ env.REGISTRY }} | |
| uses: docker/login-action@v3 #343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # Extract metadata (tags, labels) for Docker | |
| # https://github.com/docker/metadata-action | |
| - name: Extract Docker metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 #96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} # /${{ env.SERVICE }} | |
| labels: | | |
| org.opencontainers.image.version=${{ needs.generate-timestamp.outputs.TIMESTAMP }} | |
| tags: | | |
| # branch event | |
| type=ref,event=branch | |
| # pull request event | |
| type=ref,event=pr,format=pr-{{number}} | |
| # minimal (short sha) | |
| type=sha | |
| # pull request sha with prefix | |
| type=sha,prefix=pr- | |
| # commit date tag (for branches only) | |
| type=raw,value=${{ steps.tags.outputs.commit_date }} | |
| # latest tag (for branches only) | |
| type=raw,value=latest | |
| - name: Build and push Docker image | |
| id: build-and-push | |
| uses: docker/build-push-action@v6 | |
| env: | |
| HUGO_BASEURL: "${{ vars.HUGO_BASEURL }}" | |
| with: | |
| context: . | |
| file: Dockerfile.prod | |
| build-contexts: | | |
| public=./public | |
| nginx=./nginx | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| push: true | |
| # - name: Build website with Dockerfile.prod | |
| # env: | |
| # HUGO_BASEURL: "${{ vars.HUGO_BASEURL }}" | |
| # ORIGINS: "${{ vars.ORIGINS }}" | |
| # OAUTH_CLIENT_ID: "${{ vars.OAUTH_GITHUB_CLIENT_ID }}" | |
| # OAUTH_GITHUB_CLIENT_SECRET: "${{ secrets.OAUTH_GITHUB_CLIENT_SECRET }}" | |
| # CMS_BACKEND_DEBUG: ${{ vars.CMS_BACKEND_DEBUG || ''}} | |
| # run: | | |
| # echo "${{ steps.meta.outputs.labels }}" | |
| # prefix="--label " | |
| # LABELS=$(sed "s/^/$prefix/" <<< "${{ steps.meta.outputs.labels }}") | |
| # echo $LABELS | |
| # echo "${{ steps.meta.outputs.tags }}" | |
| # docker compose -f docker-compose.yaml -f docker-compose.override.yaml build website | |
| # docker tag website ${{ steps.meta.outputs.tags }} | |
| # # docker tag website ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.branch_name }} | |
| # docker tag website ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.commit_hash }} | |
| # docker tag website ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.commit_date }} | |
| # docker tag website ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest | |
| # docker push --all-tags ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| # Deploy docker image to orchestrator (handles both production and preview) | |
| deploy-service: | |
| # Skip deployment if PR is closed (cleanup job handles that) | |
| if: github.event_name != 'pull_request' || github.event.action != 'closed' | |
| # if: false | |
| # if: github.ref == 'refs/heads/main' | |
| needs: | |
| - generate-timestamp | |
| - build-website-and-container-build | |
| permissions: | |
| contents: read | |
| packages: read | |
| statuses: write | |
| environment: | |
| name: docker-image | |
| # url: https://${{ vars.HUGO_BASEURL }} | |
| url: ${{ steps.set-env.outputs.nomad_url }} | |
| # url: https://${{ vars.__SERVICE__ }}-${{ env.ENVIRONMENT == 'production' && 'latest' || 'dev' }}.ruthjoy.researchcomputinggroup.ca | |
| runs-on: ubuntu-latest | |
| # runs-on: [ self-hosted, rcg ] | |
| # container: | |
| # image: alpine:3.18 | |
| env: | |
| NOMAD_VERSION: ${{ vars.NOMAD_VERSION || '1.11.1' }} | |
| REPO_NAME: ${{ needs.generate-timestamp.outputs.REPO_NAME }} | |
| SERVICE: "${{ vars.__SERVICE__ }}" | |
| JOB_DRIVER: "${{ vars.__JOB_DRIVER__ }}" | |
| __REGISTRY__: ghcr.io # docker.github.sfu.ca | |
| # CONTAINER_PROJECT: "coastal-science.github.io" # "rcg-containers" # repo name dynamically extracted from `github.resposity` by removing `github.owner` | |
| CONTAINER_PROJECT: ${{ needs.generate-timestamp.outputs.REPO_NAME }} | |
| # __IMAGE_NAME__="${{ secrets.REGISTRY_SERVER }}/${{ env.CONTAINER_PROJECT }}/${{ env.SERVICE }}" | |
| IMAGE_NAME: ${{ vars.__REGISTRY__ }}/${{ github.repository }} | |
| FORCE_PULL: ${{ vars.__IMAGE_FORCE_PULL__ }} | |
| #__DATACENTERS__="${{ secrets.NOMAD_SERVER_DATACENTERS }}" \ | |
| # __NAMESPACE__="${{ secrets.NOMAD_SERVER_NAMESPACE }}" \ | |
| NOMAD_SERVER_DATACENTERS: "${{ vars.__DATACENTERS__ }}" | |
| NOMAD_SERVER_NAMESPACE: "${{ vars.__NAMESPACE__ }}" | |
| __REGISTRY_USERNAME__: "${{ github.actor }}" | |
| __REGISTRY_PASSWORD__: "${{ secrets.GITHUB_TOKEN }}" | |
| __RESEARCHER__: "${{ secrets.__RESEARCHER__ }}" | |
| PR_NUMBER: ${{ github.event.pull_request.number || '' }} | |
| PR_SHA: ${{ github.event.pull_request.head.sha || github.sha }} | |
| outputs: | |
| url: ${{ steps.set-env.outputs.nomad_url }} | |
| nomad_url_1: ${{ steps.set-env.outputs.nomad_url }} | |
| environment_suffix: ${{ steps.set-env.outputs.environment_suffix }} | |
| environment: ${{ steps.set-env.outputs.environment }} | |
| job_name: ${{ steps.setup-nomad.outputs.job_name }} | |
| steps: | |
| - name: Determine environment and suffix | |
| id: set-env | |
| run: | | |
| # TODO: Option (based on needs) to deploy to nomad url with ENVIRONMENT_SUFFIX=commit_sha (if not a PR) | |
| # GITHUB_SHA_SHORT="${{ github.sha }}".SubString(0, 8))" | |
| GITHUB_SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7) | |
| if [ "${{ github.ref }}" = "refs/heads/main" ]; then | |
| ENVIRONMENT="production" # TODO: suspicion that the variable is unused and can be removed | |
| ENVIRONMENT_SUFFIX="prod" | |
| NOMAD_JOB_TEMPLATE=nomad/decap.template.nomad | |
| NOMAD_FILE="${{ vars.__SERVICE__ }}.nomad" | |
| IMAGE_TAG="main" # branch name of production main branch | |
| else | |
| ENVIRONMENT="development" | |
| ENVIRONMENT_SUFFIX="dev" # default suffix for development environment. Overridden by PR number for PRs. | |
| NOMAD_JOB_TEMPLATE=nomad/template.nomad | |
| NOMAD_FILE="${{ vars.__SERVICE__ }}.nomad" | |
| fi | |
| # Update from input when it exists and is different from branch-based value | |
| if [ -n "${{ inputs.nomad_job_template }}" ] && [ "${{ inputs.nomad_job_template }}" != "${NOMAD_JOB_TEMPLATE}" ]; then | |
| echo "::notice title=Nomad template updated::nomad_job_template was '${NOMAD_JOB_TEMPLATE}' (branch-based), updated to '${{ inputs.nomad_job_template }}' (input)." | |
| NOMAD_JOB_TEMPLATE="${{ inputs.nomad_job_template }}" | |
| fi | |
| echo NOMAD_FILE=${NOMAD_FILE} | |
| # Extract branch name from refs/heads/branch-name, fallback to commit SHA | |
| # GITHUB_REF is automatically set by GitHub Actions (e.g., refs/heads/branch-name) | |
| # GITHUB_SHA is automatically set by GitHub Actions (full commit SHA) | |
| BRANCH_NAME="$GITHUB_REF_NAME" | |
| PR_NUMBER="${{ github.event.pull_request.number }}" | |
| # If GITHUB_REF_NAME matches '<pr_number>/merge', extract just the branch name for BRANCH_NAME. | |
| # Otherwise, use GITHUB_REF_NAME as-is. | |
| # GITHUB_REF_NAME format for PRs: "<pr_number>/merge" | |
| if [[ "${GITHUB_REF_NAME}" =~ ^[0-9]+/merge$ ]]; then | |
| # For PRs, get the source branch name from github.head_ref if available | |
| if [ -n "${{ github.head_ref }}" ]; then | |
| BRANCH_NAME="${{ github.head_ref }}" | |
| IMAGE_TAG="pr-${PR_NUMBER}" | |
| ENVIRONMENT_SUFFIX="pr-${PR_NUMBER}-${GITHUB_SHORT_SHA}" | |
| echo "::notice title=PR environment suffix::pr-${PR_NUMBER}-${GITHUB_SHORT_SHA}" | |
| fi | |
| else | |
| if [ -n "${GITHUB_REF_NAME}" ]; then | |
| IMAGE_TAG="${GITHUB_REF_NAME}" | |
| else | |
| IMAGE_TAG="${GITHUB_SHORT_SHA}" | |
| fi | |
| fi | |
| echo ENVIRONMENT=${ENVIRONMENT}, \ | |
| ENVIRONMENT_SUFFIX=${ENVIRONMENT_SUFFIX}, \ | |
| BRANCH_NAME=${BRANCH_NAME}, \ | |
| GITHUB_REF=${GITHUB_REF}, \ | |
| GITHUB_REF_NAME=${GITHUB_REF_NAME}, \ | |
| github.head_ref=${{ github.head_ref }}, \ | |
| PR_NUMBER=${PR_NUMBER}, \ | |
| IMAGE_TAG=${IMAGE_TAG}, \ | |
| NOMAD_JOB_TEMPLATE=${NOMAD_JOB_TEMPLATE}, \ | |
| NOMAD_FILE=${NOMAD_FILE} | |
| # Construct URL from Nomad template pattern: ${__SERVICE__}-${__ENVIRONMENT__}.${__RESEARCHER__}.researchcomputinggroup.ca | |
| NOMAD_URL="https://${{ vars.__SERVICE__ }}-${ENVIRONMENT_SUFFIX}.${{ secrets.__RESEARCHER__ }}.researchcomputinggroup.ca" | |
| # Determine WEB_URL based on event type | |
| # WEB_URL="${{ github.ref == 'refs/heads/main' && secrets.__DOMAIN_NAME__ || 'sinkhole' }}" | |
| # Determine WEB_URL and PREVIEW_URL based on event type | |
| # Using shell logic below instead of GitHub expressions to avoid parsing issues | |
| WEB_URL="sinkhole.${__RESEARCHER__}.researchcomputinggroup.ca" | |
| # PREVIEW_URL="" # TODO: (remove) PREVIEW_URL was used for PRs, but now we use NOMAD_URL | |
| # if [ "${{ github.event_name }}" = "pull_request" ]; then | |
| # # For PRs | |
| # PREVIEW_URL="${NOMAD_URL}" | |
| # fi | |
| if [ "${{ github.ref }}" = "refs/heads/main" ]; then | |
| # For production main branch | |
| WEB_URL="${{ secrets.__DOMAIN_NAME__ }}" | |
| fi | |
| echo "environment=${ENVIRONMENT}" >> $GITHUB_OUTPUT | |
| echo "environment_suffix=${ENVIRONMENT_SUFFIX}" >> $GITHUB_OUTPUT | |
| echo "nomad_url=${NOMAD_URL}" >> $GITHUB_OUTPUT | |
| echo "web_url=${WEB_URL}" >> $GITHUB_OUTPUT | |
| echo "nomad_job_template=${NOMAD_JOB_TEMPLATE}" >> $GITHUB_OUTPUT | |
| echo "nomad_file=${NOMAD_FILE}" >> $GITHUB_OUTPUT | |
| echo "preview_url=${PREVIEW_URL}" >> $GITHUB_OUTPUT | |
| echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT | |
| echo "Determined environment: ${ENVIRONMENT}, suffix: ${ENVIRONMENT_SUFFIX}, image_tag: ${IMAGE_TAG}" | |
| - name: Prepare Environment. Setup `nomad` CLI. | |
| id: setup-nomad-cli | |
| uses: hashicorp/setup-nomad@v1.0.0 | |
| with: | |
| version: ${{ env.NOMAD_VERSION}} # Specify the desired version, or "latest" | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: true # Fetch Hugo themes (true OR recursive) # failing on ubuntu container | |
| fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod | |
| # set-safe-directory: '/__w/coastal-science.github.io/coastal-science.github.io' # The process '/usr/bin/git' failed with exit code 128 | |
| # set-safe-directory: '${{ env.REPO_NAME }}/${{ env.REPO_NAME }}' # The process '/usr/bin/git' failed with exit code 128, resorting to manual call | |
| - name: Set safe directory workaround | |
| run: | | |
| git config --global --add safe.directory /__w/${{ env.REPO_NAME }}/${{ env.REPO_NAME }} | |
| - name: Test Nomad | |
| run: NOMAD_TOKEN=${{ secrets.NOMAD_SERVER_TOKEN }} NOMAD_ADDR=${{ secrets.NOMAD_SERVER_ADDRESS }} nomad status | |
| - name: Setup Nomad Job | |
| id: setup-nomad | |
| env: | |
| ENVIRONMENT_SUFFIX: ${{ steps.set-env.outputs.environment_suffix }} | |
| ENVIRONMENT: ${{ steps.set-env.outputs.environment }} | |
| WEB_URL: ${{ steps.set-env.outputs.web_url }} | |
| NOMAD_JOB_TEMPLATE: ${{ steps.set-env.outputs.nomad_job_template }} | |
| NOMAD_FILE: ${{ steps.set-env.outputs.nomad_file }} | |
| IMAGE_TAG: ${{ steps.set-env.outputs.image_tag }} | |
| run: | | |
| # Use ENVIRONMENT_SUFFIX consistently for both PRs and branches | |
| JOB_NAME="${{ env.SERVICE }}-${ENVIRONMENT_SUFFIX}" | |
| # NOMAD_JOB_TEMPLATE="nomad/template.nomad" | |
| NOMAD_JOB_TEMPLATE="${NOMAD_JOB_TEMPLATE}" | |
| NOMAD_OUTPUT_FILE="${NOMAD_FILE}" | |
| export __SERVICE__="${{ env.SERVICE }}" | |
| export __JOB_DRIVER__="${{ env.JOB_DRIVER }}" | |
| export __ENVIRONMENT__="${ENVIRONMENT_SUFFIX}" | |
| export __WEB_URL__="${WEB_URL}" | |
| export __JOB_NAME__="${JOB_NAME}" | |
| export __IMAGE_NAME__="${{ env.IMAGE_NAME }}" | |
| export __IMAGE_TAG__="${IMAGE_TAG}" | |
| export __IMAGE_FORCE_PULL__="${{ env.FORCE_PULL || 'false' }}" | |
| export __DATACENTERS__="${{ env.NOMAD_SERVER_DATACENTERS }}" | |
| export __NAMESPACE__="${{ env.NOMAD_SERVER_NAMESPACE }}" | |
| export __RESEARCHER__="${{ env.__RESEARCHER__ }}" | |
| export __REGISTRY_USERNAME__="${{ env.__REGISTRY_USERNAME__ }}" | |
| export __REGISTRY_PASSWORD__="${{ env.__REGISTRY_PASSWORD__ }}" | |
| # USE_DECAP: workflow input overrides branch-based determination when set | |
| USE_DECAP_INPUT="${{ inputs.use_decap }}" | |
| if [ -n "${USE_DECAP_INPUT}" ] && [ "${USE_DECAP_INPUT}" != "auto" ]; then | |
| USE_DECAP_INPUT=$(echo "${USE_DECAP_INPUT}" | tr '[:upper:]' '[:lower:]') | |
| case "${USE_DECAP_INPUT}" in | |
| true | yes) | |
| export __USE_DECAP__="true" | |
| echo "::notice title=USE_DECAP override::use_decap input set to 'true' (overrides environment/branch-based auto determination)." | |
| ;; | |
| false | no) | |
| export __USE_DECAP__="false" | |
| echo "::notice title=USE_DECAP override::use_decap input set to 'false' (overrides environment/branch-based auto determination)." | |
| ;; | |
| *) echo "::warning title=Invalid use_decap::Ignoring use_decap='${USE_DECAP_INPUT}'; using environment/branch-based value." ;; | |
| esac | |
| fi | |
| if [ -z "${__USE_DECAP__:-}" ]; then | |
| env_lower=$(echo "${ENVIRONMENT}" | tr '[:upper:]' '[:lower:]') | |
| export __USE_DECAP__="$([ "$env_lower" = "main" ] || [ "$env_lower" = "prod" ] || [ "$env_lower" = "production" ] && echo 'true' || echo 'false')" | |
| fi | |
| export __DECAP_IMAGE_NAME__="itsmejoeeey/docker-decap-cms-standalone" | |
| export __DECAP_IMAGE_TAG__="latest" | |
| export __CMS_BACKEND_DEBUG__="${{ vars.CMS_BACKEND_DEBUG || 0 }}" | |
| export __ORIGINS__="${{ vars.ORIGINS }}" | |
| export __OAUTH_CLIENT_ID__="${{ vars.OAUTH_GITHUB_CLIENT_ID }}" | |
| export __OAUTH_CLIENT_SECRET__="${{ secrets.OAUTH_GITHUB_CLIENT_SECRET }}" | |
| # Substitute variables in template | |
| echo "Extracting __*__ variables from template: $NOMAD_JOB_TEMPLATE" | |
| echo "Output file: $NOMAD_OUTPUT_FILE" | |
| # GNU gettext envsubst format: '${VAR1} ${VAR2} ${VAR3}' | |
| # Extract all ${__*__} patterns, get unique variable names (with __ prefix/suffix) | |
| # envsubst only replaces variables in the format string; others (e.g. ${meta.role}, ${node.unique.name}, ${NOMAD_ADDR_http}) are left unchanged. | |
| SHELL_FORMAT_STRING=$(grep -oE '\$\{__[A-Z_]+__\}' "$NOMAD_JOB_TEMPLATE" | sort -u | awk '{printf "%s ", $1}') | |
| if [ -z "$SHELL_FORMAT_STRING" ]; then | |
| echo "Warning: No __*__ variables found in template" | |
| fi | |
| echo "Substituting variables: $SHELL_FORMAT_STRING" | |
| envsubst "$SHELL_FORMAT_STRING" < "$NOMAD_JOB_TEMPLATE" > "$NOMAD_OUTPUT_FILE" | |
| echo "===== Rendered Nomad job =====" | |
| cat "$NOMAD_OUTPUT_FILE" | |
| echo "===== Validating substituted variables and rendered Nomad job =====" | |
| # Extract all remaining __*__ variables after envsubst and report if any non-substituted variables remain | |
| REMAINING_SUBSTITUTIONS=$(grep -oE '\$\{__[A-Z_]+__\}' "$NOMAD_OUTPUT_FILE" | sort -u) | |
| if [ -n "$REMAINING_SUBSTITUTIONS" ]; then | |
| echo "Warning: The following variable substitutions were NOT completed in $NOMAD_OUTPUT_FILE:" | |
| echo "$REMAINING_SUBSTITUTIONS" | |
| exit 1 | |
| else | |
| echo "All __*__ variables were successfully substituted in $NOMAD_OUTPUT_FILE." | |
| fi | |
| NOMAD_TOKEN=${{ secrets.NOMAD_SERVER_TOKEN }} NOMAD_ADDR=${{ secrets.NOMAD_SERVER_ADDRESS }} \ | |
| nomad job validate "$NOMAD_OUTPUT_FILE" || { | |
| echo "===== Nomad job file =====" | |
| cat "$NOMAD_OUTPUT_FILE" | |
| exit 1 | |
| } | |
| echo "nomad_file=${NOMAD_OUTPUT_FILE}" >> $GITHUB_OUTPUT | |
| echo "job_name=${JOB_NAME}" >> $GITHUB_OUTPUT | |
| - name: Deploy TIMESTAMPED Website To Nomad | |
| # if: github.event_name != 'pull_request' | |
| id: deploy | |
| env: | |
| JOB_NAME: ${{ steps.setup-nomad.outputs.job_name }} | |
| NOMAD_FILE: ${{ steps.setup-nomad.outputs.nomad_file }} | |
| run: | | |
| nomad version | |
| NOMAD_TOKEN=${{ secrets.NOMAD_SERVER_TOKEN }} NOMAD_ADDR=${{ secrets.NOMAD_SERVER_ADDRESS }} \ | |
| nomad job stop -verbose ${JOB_NAME} || true | |
| echo "Nomad job stop result=$?" | |
| sleep 10s | |
| echo "Submitting job from: ${NOMAD_FILE}" | |
| NOMAD_TOKEN=${{ secrets.NOMAD_SERVER_TOKEN }} NOMAD_ADDR=${{ secrets.NOMAD_SERVER_ADDRESS }} \ | |
| nomad job run -verbose "${NOMAD_FILE}" | |
| DEPLOY_RESULT=$? | |
| echo "Nomad job deploy result=${DEPLOY_RESULT}" | |
| echo "deploy_success=$([ ${DEPLOY_RESULT} -eq 0 ] && echo 'true' || echo 'false')" >> $GITHUB_OUTPUT | |
| - name: Create GitHub commit status for deployment | |
| if: github.event_name == 'pull_request' | |
| run: | | |
| STATUS_URL="https://api.github.com/repos/${{ github.repository }}/statuses/${{ env.PR_SHA }}" | |
| # Determine status parameters based on deployment result | |
| if [ "${{ steps.deploy.outputs.deploy_success }}" = "true" ]; then | |
| STATE="success" | |
| TARGET_URL="${{ steps.set-env.outputs.nomad_url }}" | |
| DESCRIPTION="Preview deployment ready" | |
| else | |
| STATE="failure" | |
| TARGET_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| DESCRIPTION="Preview deployment failed" | |
| fi | |
| curl -X POST "${STATUS_URL}" \ | |
| -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ | |
| -H "Accept: application/vnd.github.v3+json" \ | |
| -d "{ | |
| \"state\": \"${STATE}\", | |
| \"target_url\": \"${TARGET_URL}\", | |
| \"description\": \"${DESCRIPTION}\", | |
| \"context\": \"deploy/preview\" | |
| }" | |
| echo "Created commit status: deploy/preview -> ${STATE} (${TARGET_URL})" | |
| - name: Summary output URLs | |
| run: | | |
| if [ "${{ github.event_name }}" != "pull_request" ]; then | |
| echo https://"${{ secrets.__DOMAIN_NAME__ }}" >> "$GITHUB_STEP_SUMMARY" | |
| echo "${{ steps.set-env.outputs.nomad_url }}" >> "$GITHUB_STEP_SUMMARY" | |
| else | |
| echo "${{ steps.set-env.outputs.nomad_url }}" >> "$GITHUB_STEP_SUMMARY" | |
| fi | |
| # Cleanup PR Nomad jobs after deploy: (1) synchronize → stop previous PR job(s) for this PR; (2) push main → stop merged PR's job. Both need deploy-service. Conditionals on steps. | |
| cleanup-pr-after-deploy: | |
| if: (github.event_name == 'pull_request' && github.event.action == 'synchronize') || (github.event_name == 'push' && github.ref == 'refs/heads/main') | |
| needs: | |
| - deploy-service | |
| permissions: | |
| contents: read | |
| packages: read | |
| environment: | |
| name: docker-image | |
| runs-on: ubuntu-latest | |
| env: | |
| NOMAD_VERSION: ${{ vars.NOMAD_VERSION || '1.11.1' }} | |
| SERVICE: "${{ vars.__SERVICE__ }}" | |
| steps: | |
| - name: Checkout repository (required for local action) | |
| uses: actions/checkout@v4 | |
| - name: Prepare Environment. Setup `nomad` CLI | |
| uses: hashicorp/setup-nomad@v1.0.0 | |
| with: | |
| version: ${{ env.NOMAD_VERSION }} | |
| # --- synchronize: stop previous PR job(s) for this PR (keep current) --- | |
| - name: 1. Stop and purge previous PR job(s) for this PR | |
| if: github.event_name == 'pull_request' && github.event.action == 'synchronize' | |
| env: | |
| NOMAD_TOKEN: ${{ secrets.NOMAD_SERVER_TOKEN }} | |
| NOMAD_ADDR: ${{ secrets.NOMAD_SERVER_ADDRESS }} | |
| CURRENT_JOB: ${{ needs.deploy-service.outputs.job_name }} | |
| PREFIX: ${{ env.SERVICE }}-pr-${{ github.event.pull_request.number }}- | |
| run: | | |
| echo "Current job (keep): ${CURRENT_JOB}" | |
| echo "Looking for other jobs with prefix: ${PREFIX}" | |
| # List all jobs and filter by prefix in the first column; skip header row. | |
| JOBS=$(NOMAD_TOKEN="${NOMAD_TOKEN}" NOMAD_ADDR="${NOMAD_ADDR}" nomad job status 2>/dev/null \ | |
| | awk -v p="${PREFIX}" 'NR>1 && index($1, p) == 1 {print $1}' || true) | |
| # Each job ID is on its own line; count non-empty lines for total. | |
| TOTAL=$(printf '%s\n' "${JOBS}" | sed '/^$/d' | wc -l | tr -d ' ') | |
| echo "Found ${TOTAL} matching job(s):" | |
| echo "${JOBS}" | |
| CLEANED=0 | |
| for job in ${JOBS}; do | |
| [ -z "${job}" ] && continue | |
| if [ "${job}" != "${CURRENT_JOB}" ]; then | |
| echo "Stopping and purging previous PR job: ${job}" | |
| NOMAD_TOKEN="${NOMAD_TOKEN}" NOMAD_ADDR="${NOMAD_ADDR}" nomad job stop -purge -verbose "${job}" || true | |
| CLEANED=$((CLEANED + 1)) | |
| fi | |
| done | |
| if [ "${CLEANED}" -eq 0 ]; then | |
| echo "No previous PR jobs to clean up." | |
| else | |
| echo "Cleaned up ${CLEANED} previous PR job(s)." | |
| fi | |
| # --- push main: stop merged PR's job (from GitHub API by commit SHA, not message) --- | |
| - name: Get merged PR number from commit (GitHub API) | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| id: pr | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| # On a push event (e.g. merge to main), github.event.pull_request isn’t set, so we get the merged PR number from the commits API (GET .../commits/{sha}/pulls) using github.sha instead of the event payload. | |
| # GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls returns PRs associated with this commit. | |
| # Works for merge commits and custom merge messages; does not depend on message template. | |
| PAYLOAD=$(gh api "repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" --jq '.[0] // empty' 2>/dev/null || true) | |
| PR_NUM=$(echo "$PAYLOAD" | jq -r '.number // empty' 2>/dev/null) | |
| HEAD_SHA=$(echo "$PAYLOAD" | jq -r '.head.sha // empty' 2>/dev/null) | |
| if [ -n "${PR_NUM}" ]; then | |
| echo "pr_number=${PR_NUM}" >> $GITHUB_OUTPUT | |
| echo "head_sha=${HEAD_SHA}" >> $GITHUB_OUTPUT | |
| echo "Found merged PR #${PR_NUM} head_sha=${HEAD_SHA} (from API)" | |
| else | |
| echo "pr_number=" >> $GITHUB_OUTPUT | |
| echo "head_sha=" >> $GITHUB_OUTPUT | |
| echo "No PR associated with commit ${{ github.sha }} (e.g. direct push to main)" | |
| fi | |
| - name: Determine Nomad job name (SERVICE-pr-N-SHORT_SHA) | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.pr.outputs.pr_number != '' | |
| id: job-name-merged | |
| run: | | |
| PR_NUM="${{ steps.pr.outputs.pr_number }}" | |
| SHORT_SHA=$(echo "${{ steps.pr.outputs.head_sha }}" | cut -c1-7) | |
| echo "job_name=${{ env.SERVICE }}-pr-${PR_NUM}-${SHORT_SHA}" >> $GITHUB_OUTPUT | |
| echo "Nomad job name: ${{ env.SERVICE }}-pr-${PR_NUM}-${SHORT_SHA}" | |
| - name: 2. Cleanup merged PR Nomad job when push to main | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.pr.outputs.pr_number != '' | |
| uses: ./.github/actions/cleanup-pr-nomad-job | |
| with: | |
| job_name: ${{ steps.job-name-merged.outputs.job_name }} | |
| nomad_version: ${{ env.NOMAD_VERSION }} | |
| nomad_token: ${{ secrets.NOMAD_SERVER_TOKEN }} | |
| nomad_addr: ${{ secrets.NOMAD_SERVER_ADDRESS }} | |
| # Cleanup Nomad deployment when PR is closed (without merge). When merged, cleanup runs in cleanup-pr-after-deploy. Job name = SERVICE-pr-N-SHORT_SHA. | |
| cleanup-pr-deployment: | |
| if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == false | |
| permissions: | |
| contents: read | |
| packages: read | |
| environment: | |
| name: docker-image | |
| runs-on: ubuntu-latest | |
| env: | |
| NOMAD_VERSION: ${{ vars.NOMAD_VERSION || '1.11.1' }} | |
| SERVICE: "${{ vars.__SERVICE__ }}" | |
| steps: | |
| - name: Checkout repository (required for local action) | |
| uses: actions/checkout@v4 | |
| - name: Determine Nomad job name (SERVICE-pr-N-SHORT_SHA) | |
| id: job-name | |
| run: | | |
| PR_NUM="${{ github.event.pull_request.number }}" | |
| SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) | |
| echo "job_name=${{ env.SERVICE }}-pr-${PR_NUM}-${SHORT_SHA}" >> $GITHUB_OUTPUT | |
| echo "Nomad job name: ${{ env.SERVICE }}-pr-${PR_NUM}-${SHORT_SHA}" | |
| - name: 3. Cleanup PR Nomad job when PR is closed (without merge) | |
| uses: ./.github/actions/cleanup-pr-nomad-job | |
| with: | |
| job_name: ${{ steps.job-name.outputs.job_name }} | |
| nomad_version: ${{ env.NOMAD_VERSION }} | |
| nomad_token: ${{ secrets.NOMAD_SERVER_TOKEN }} | |
| nomad_addr: ${{ secrets.NOMAD_SERVER_ADDRESS }} | |
| # Cleanup workspace on the runner | |
| cleanup-runner-workspace: | |
| if: ${{ always() }} | |
| permissions: {} | |
| needs: | |
| - build-website-and-container-build | |
| - deploy-service | |
| runs-on: ubuntu-latest | |
| # runs-on: [ self-hosted, rcg ] | |
| steps: | |
| - name: Current workspace | |
| run: du -shc ${GITHUB_WORKSPACE} | |
| - name: Clean Up Docker Images | |
| if: needs.build-website-and-container-build.result != 'skipped' | |
| run: docker rmi -f $(docker images '${{ needs.build-website-and-container-build.outputs.tags }}' -a -q) || echo "No docker images found to remove...skipping removal." | |
| - name: Clean Up Workspace | |
| run: rm -rf ${GITHUB_WORKSPACE} |