# +=========================================================================================================================+
# | QA/Stagging CI/CD workflow                                                                                              |
# |                                                                                                                         |
# | Our QA/Stagging environment is hosted on servers in our office using a combination of:                                  |
# | - Kubernetes (10 Ubuntu 24.04 VM nodes, hosted within a Proxmox VE cluster of 2 servers, a Dell R730xd and a Dell R730) |
# | - Nginx Proxy Manager as a reverse proxy                                                                                |
# | - Pi-hole for DHCP                                                                                                      |
# | - Bind for custom DNS                                                                                                   |
# | On a cluster wide level:                                                                                                |
# | - Rook/Ceph                                                                                                             |
# | - MetalLb                                                                                                               |
# | - Nginx Ingress Controller                                                                                              |
# | - PosgreSQL Operator (CloudNativePG)                                                                                    |
# | - Custom PostgreSQL Database Operation (https://github.com/AlanBridgeman/postgres-controller)                           |
# | - MongoDB Community Operator                                                                                            |
# | On a namespace level:                                                                                                   |
# | - Redis                                                                                                                 |
# | - Hasshicorp Vault                                                                                                      |
# | - Mongo containers                                                                                                      |
# |                                                                                                                         |
# | It's worth noting we largely use Helm to deploy resources into the cluster.                                             |
# +=========================================================================================================================+

name: Build and deploy Main Bridgeman Accessible website to QA/Stagging (private Kubernetes cluster)

on:
  push:
    branches:
      - stagging
    # I'm leaving this here because I've tried to add and then had to remove this several times
    # In particular, the problem is conditions are OR'd NOT AND'd
    # This means controls via branch commit rules combined with this workflow achieve a better result than having this here
    #tags:
    #  - 'v[0-9]+.[0-9]+.[0-9]+' # Matches any semantic versioning tag (e.g. v1.0.0, v2.1.3, etc.)
  workflow_dispatch:

permissions:
  contents: write # Allows the workflow to push changes to the repository (e.g. for tagging)

jobs:
  # Run tests (particularly unit tests) on the codebase
  # This mostly relies on the `yarn test` command
  #test:
  #  runs-on: ubuntu-latest
  #  steps:
  #    - uses: actions/checkout@v4
  #
  #    - name: Set up Node.js version
  #      uses: actions/setup-node@v3
  #      with:
  #        # Take from [Repo README](https://github.com/actions/setup-node#readme)
  #        #  
  #        # > Version Spec of the version to use in SemVer notation.
  #        # > It also admits such aliases as lts/*, latest, nightly and canary builds
  #        # > Examples: 12.x, 10.15.1, >=10.15.0, lts/Hydrogen, 16-nightly, latest, node
  #        node-version: '18.x'
  #        
  #        # Take from [Repo README](https://github.com/actions/setup-node#readme)
  #        # 
  #        # > Optional registry to set up for auth. Will set the registry in a project level .npmrc and .yarnrc file, 
  #        # > and set up auth to read in from env.NODE_AUTH_TOKEN.
  #        # > Default: ''
  #        registry-url: 'https://npm.pkg.bridgemanaccessible.ca'
  #        
  #        # Take from [Repo README](https://github.com/actions/setup-node#readme)
  #        # 
  #        # > Optional scope for authenticating against scoped registries. 
  #        # > Will fall back to the repository owner when using the GitHub Packages registry (https://npm.pkg.github.com/).
  #        scope: '@BridgemanAccessible'
  #      
  #    - name: yarn install and test
  #      env:
  #        NODE_AUTH_TOKEN: ${{ secrets.PRIVATE_NPM_TOKEN }}
  #      run: |
  #        yarn install
  #        yarn test
  
  # Builds the codebase (builds the image)
  build:
    runs-on: self-hosted
    outputs:
      build_and_deploy: ${{ steps.version_check.outputs.build_and_deploy }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Prepare Environment Variables
        run: |
          echo "Harbor Username: ${{ secrets.HARBOR_USERNAME }}"
          echo "HARBOR_USERNAME=${{ secrets.HARBOR_USERNAME }}" >> $GITHUB_ENV
          echo "Harbor Password: ${{ secrets.HARBOR_PASSWORD }}"
          echo "HARBOR_PASSWORD=${{ secrets.HARBOR_PASSWORD }}" >> $GITHUB_ENV
      
      # Check if this is a "significant" commit that should trigger a build and deploy
      # This is done by checking if the latest git tag matches the version in the `package.json` file
      - name: Version Check
        id: version_check
        run: |
          echo 'Harbor Username: ${{ env.HARBOR_USERNAME }}'

          # Get the latest version from the `package.json` file
          # This should be updated if a new build and deploy is desired
          LATEST_VERSION=$(jq -r '.version' package.json)
          echo "LATEST_VERSION=$LATEST_VERSION" >> $GITHUB_ENV

          # Get the latest Git tag from the repository
          LATEST_TAG=$(git describe --tags --abbrev=0)
          echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV

          # If the git tag and the version ARE the same, this is a commit that SHOULD NOT trigger a build and deploy
          # Inversely, if the git tag and the version are NOT the same, this is a commit that SHOULD trigger a build and deploy
          if [[ "$LATEST_TAG" == "v$LATEST_VERSION" ]]; then
            echo "Latest tag matches the version in package.json, SHOULD NOT build and deploy"
            echo "build_and_deploy=false" >> $GITHUB_OUTPUT
          else
            echo "Latest tag does not match the version in package.json, SHOULD build and deploy"
            echo "build_and_deploy=true" >> $GITHUB_OUTPUT
          fi
      
      # Because a build and deploy is desired, we want to check if a new tag already exists or if we need to create one as part of the workflow
      # This is commented out because the approach here makes sense but the logic doesn't fully because we already know the git tag and the version don't match
      #- name: Tag Check
      #  id: tag_check
      #  if: steps.version_check.outputs.build_and_deploy == 'true'
      #  run: |
      #    # Check if a matching image build already exists
      #    MATCHING_IMAGE=$(docker image ls $(jq -r '.name' package.json) | grep ${{ env.LATEST_TAG }} || echo "Image not found")
      #
      #    # If a matching image exists, set the `create_tag` output to true (so that a new tag is created)
      #    if [[ "$MATCHING_IMAGE" == "Image not found" ]]; then
      #      echo "No matching image found, No new tag required"
      #      echo "create_tag=false" >> $GITHUB_OUTPUT
      #    else
      #      echo "Matching image found, new tag required"
      #      echo "create_tag=true" >> $GITHUB_OUTPUT
      #    fi
      
      # Conditional step to create a new Git tag
      # This is required because when we go to build and deploy the image we rely on the latest git tag
      # We do it this way so that ONLY situations where the git tag and the last built image are mal-aligned do we create a new tag 
      - name: Add Git Tag (if needed)
        if: steps.version_check.outputs.build_and_deploy == 'true'
        #if: steps.version_check.outputs.build_and_deploy && steps.tag_check.outputs.create_tag == 'true'
        run: |
          # Increment the version's patch number (within the `package.json` file)
          #jq '.version |= (. | split(".") | [.[0], .[1], (.[2] | tonumber + 1 | tostring)] | join("."))' package.json > temp.json && mv temp.json package.json
          
          # Update remote URL to use the GITHUB_TOKEN for authentication
          git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@git.bridgemanaccessible.ca/${{ github.repository }}.git
      
          # Setup git user details for committing the version change and tag
          git config user.name "Forgjo Actions"
          git config user.email "actions@git.bridgemanaccessible.ca"
      
          # Commit the version change to the `package.json` file
          #git add package.json
          #git commit -m "[Github Actions] Update version to $(jq -r '.version' package.json)"
      
          # Push the changes to the repository
          #git push origin HEAD:stagging
      
          # Create a new tag with the updated version number
          git tag -a "v${{ env.LATEST_VERSION }}" -m "Version ${{ env.LATEST_VERSION }}"
      
          # Push the new tag to the repository
          git push --tags
      
      # Run the build script found on self-hosted runner
      - name: Run Build Image Script
        if: steps.version_check.outputs.build_and_deploy == 'true'
        run: |
          IMAGE_NAME=ba-website

          echo "Repository Path: $GITHUB_WORKSPACE"
          echo "Name for Image: $IMAGE_NAME"

          # Not necessarily needed but keeping track of the current directory (so that we can return to it is just a good practice)
          CURR_DIR="$PWD"

          # Change into the repository directory
          cd "$GITHUB_WORKSPACE"

          IMAGE_TAG=$(git describe --tags --abbrev=0) # Use the most recent tag as the image tag
          IMAGE_FULL_NAME="containers.bridgemanaccessible.ca/k8s/${IMAGE_NAME}:${IMAGE_TAG}"

          echo "🔹 Building and pushing image..."
          docker build -t ${IMAGE_FULL_NAME} .
          docker login -u $HARBOR_USERNAME -p $HARBOR_PASSWORD containers.bridgemanaccessible.ca
          docker push ${IMAGE_FULL_NAME}
          
          cd "$CURR_DIR"

          echo "✅ Build successful!"

  deploy:
    runs-on: self-hosted
    needs: build
    if: needs.build.outputs.build_and_deploy == 'true'
    
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      
      - name: Get the latest tag
        id: get_latest_tag
        run: |
          LATEST_TAG=$(git describe --tags --abbrev=0)
          echo "LATEST_TAG=$LATEST_TAG" >> $GITHUB_ENV
          echo "IMAGE_VERSION=${LATEST_TAG#v}" >> $GITHUB_ENV
  
      #- name: Prepare Environment Variables
      #  run: |
      #    echo "HARBOR_USERNAME=${{ secrets.HARBOR_USERNAME }}" >> $GITHUB_ENV
      #    echo "HARBOR_PASSWORD=${{ secrets.HARBOR_PASSWORD }}" >> $GITHUB_ENV
      
      - name: Run Deploy Kubernetes Script
        run: |
          IMAGE_VERSION="${{ env.IMAGE_VERSION }}"
          NAMESPACE="ba-website"
          DEPLOYMENT_NAME="website"
          CONTAINER_NAME="website"
          IMAGE_NAME="ba-website"
          
          echo "Image Version: $IMAGE_VERSION"
          echo "Namespace: $NAMESPACE"
          echo "Deployment Name: $DEPLOYMENT_NAME"
          echo "Container Name: $CONTAINER_NAME"
          echo "Image Name: $IMAGE_NAME"

          update-k8s-deployment-image \
            --image-version $IMAGE_VERSION \
            --namespace $NAMESPACE \
            --deployment-name $DEPLOYMENT_NAME \
            --container-name $CONTAINER_NAME \
            --image-name "containers.bridgemanaccessible.ca/k8s/$IMAGE_NAME"