diff --git a/.forgejo/workflows/stagging.yml b/.forgejo/workflows/stagging.yml new file mode 100644 index 0000000..52b851a --- /dev/null +++ b/.forgejo/workflows/stagging.yml @@ -0,0 +1,158 @@ +# +=========================================================================================================================+ +# | 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 Harbor Helm Index to QA/Stagging (private Kubernetes cluster) + +on: + push: + branches: + - stagging + workflow_dispatch: + +permissions: + contents: write # Allows the workflow to push changes to the repository (e.g. for tagging) + +jobs: + # 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: | + # 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 + + # 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' + run: | + # 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 "Forgejo Actions" + git config user.email "actions@git.bridgemanaccessible.ca" + + # 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: | + 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="harbor-helm-index" + DEPLOYMENT_NAME="helm-index" + CONTAINER_NAME="helm-index" + IMAGE_NAME="harbor-helm-index" + + 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" \ No newline at end of file diff --git a/package.json b/package.json index 12d9f99..8a918a3 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,5 @@ "pg": "^8.13.3", "typeorm": "^0.3.20", "uuid": "^11.0.5" - }, - "packageManager": "yarn@1.22.22" + } } diff --git a/src/server.ts b/src/server.ts index d751551..8c1a373 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,5 +1,4 @@ import path from 'path'; -import { Application } from 'express'; import { App, Initializer, globalTemplateValues } from '@BridgemanAccessible/ba-web-framework'; import { createConn } from './utils/db'; @@ -20,20 +19,30 @@ async function createInitialDatabaseConnection() { await createConn('postgres', connOptions); } -async function onStart(app: Application) { +async function onStart(app: App) { // Create the initial database connection createInitialDatabaseConnection(); } async function main() { - await new App().run(new Initializer({ - controllersPath: path.join(__dirname, 'routes'), - staticFilesPath: path.join(__dirname, 'static'), - view: { - engine: 'ejs', - filesPath: path.join(__dirname, 'pages') - } - }, globalTemplateValues({ company: process.env.COMPANY, titleSuffix: process.env.WEBSITE_TITLE_SUFFIX, hostname: process.env.HOSTNAME })), onStart); + await new App().run( + new Initializer( + { + controllersPath: path.join(__dirname, 'routes'), + staticFilesPath: path.join(__dirname, 'static'), + view: { + engine: 'ejs', + filesPath: path.join(__dirname, 'pages') + } + }, + globalTemplateValues({ + company: process.env.COMPANY, + titleSuffix: process.env.WEBSITE_TITLE_SUFFIX, + hostname: process.env.HOSTNAME + }) + ), + onStart + ); } main() \ No newline at end of file