DevOps

Bookmark this to keep an eye on new things I am learning.


DevOps

GitHub workflows for then automation of build, test, and deployment of your code.

Return Home

Docker

Docker build and push images, with tag info for versioning. For this to run, you must create a TAG called vx.x.x and then do a git push with tags

name: "Docker publish"

on:
  push:
    tags:
      - 'v*'

jobs:
  build-and-push-image:
    runs-on: "ubuntu-latest"

    steps:
      - name: "Checkout"
        uses: actions/checkout@v4

      - name: "Set up Docker QEMU"
        uses: docker/setup-qemu-action@v3

      - name: "Set up Docker Buildx"
        uses: docker/setup-buildx-action@v3

      - name: "Log in to Docker Hub"
        uses: docker/login-action@v3
        with:
          username: $
          password: $

      - name: "Build and push Docker image tags"
        uses: docker/build-push-action@v5
        with:
          context: "."
          platforms: "linux/amd64,linux/arm64"
          push: true
          tags: |
            "$:latest"
            "$:$"  

^ back to top ^

Docker Hub

Copy README.md from GitHub and send to Docker Hub.

name: Update Docker Hub Description
on:
  push:
    branches: [ main ]
    paths:
      - README.md
      - .github/workflows/dockerhub-description.yml
  pull_request:
    branches: [ main ]
    paths:
      - README.md
      - .github/workflows/dockerhub-description.yml
jobs:
  dockerHubDescription:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Docker Hub Description
      uses: peter-evans/dockerhub-description@v4
      with:
        username: $
        password: $
        repository: petersem/basics
        short-description: $

^ back to top ^

Unit testing

Automation to run Jest unit tests on repo push to GitHub. Results are sent to discord, but options for NTFY and Gotify.

name: Jest Test Details

on:
  push:
    branches:
      - 'main'
  workflow_dispatch:

jobs:
  test-and-notify:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repo
        uses: actions/checkout@v6

      - name: Set up node
        uses: actions/setup-node@v6
        with:
          node-version: 'latest'

      - name: Install dependencies
        run: npm ci

      - name: Run Jest tests
        id: jest
        run: |
          set +e
          npm test -- --json --outputFile=jest-results.json --coverage --coverageReporters=json --coverageReporters=json-summary
          EXIT_CODE=$?
          echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
          set -e

      - name: Send discord notification
        if: always()
        env:
          EXIT_CODE: $
          BRANCH: $
          WEBHOOK_URL: $
          # GOTIFY_URL: $
          # GOTIFY_TOKEN: $
          # NTFY_URL: $
          # NTFY_TOKEN: $
        run: |
          PROJECT_NAME=$(jq -r '.name' package.json)

          SUMMARY=$(jq -r '
            .testResults
            | group_by(.status)
            | map("\(.[0].status): \(length)")
            | join("\n")
          ' jest-results.json)

          DETAILS=$(jq -r '
            .testResults
            | map(
                "# File: \(.name)\n"
                +
                (
                  .assertionResults
                  | map(
                      if .status == "passed" then
                        "  ✅ PASSED - \(.fullName)"
                      else
                        "  ❌ FAILED - \(.fullName)"
                      end
                    )
                  | join("\n")
                )
              )
            | join("\n\n")
          ' jest-results.json)

          COVERAGE=$(jq -r '
            .total
            | "Statements: \(.statements.pct)%\nBranches: \(.branches.pct)%\nFunctions: \(.functions.pct)%\nLines: \(.lines.pct)%"
          ' coverage/coverage-summary.json)

          # SAFE UNCOVERED (no em-dash, no unicode, no markdown triggers)
          UNCOVERED=$(jq -r '
            to_entries
            | map(
                .key as $file
                | .value as $cov
                | ($cov.lines.pct // 0) as $lines
                | ($cov.functions.pct // 0) as $funcs
                | ($cov.branches.pct // 0) as $branches
                | select($lines < 100 or $funcs < 100 or $branches < 100)
                | "\($file)\n  Lines: \($lines)%\n  Functions: \($funcs)%\n  Branches: \($branches)%"
              )
            | join("\n\n")
          ' coverage/coverage-final.json)

          # SAFE UNCOVERED LINES (no em-dash, no unicode, no markdown triggers)
          UNCOVERED_LINES=$(jq -r '
            to_entries
            | map(
                .key as $file
                | .value as $cov
                | ($cov.s // {}) as $counts
                | ($cov.statementMap // {}) as $stmts
                | [
                    $stmts
                    | to_entries[]
                    | select(($counts[.key|tostring] // 1) == 0)
                    | .value.start.line
                  ]
                | select(length > 0)
                | "\($file) - Uncovered lines: \(join(", "))"
              )
            | if length == 0 then
                "No uncovered lines 🎉"
              else
                join("\n")
              end
          ' coverage/coverage-final.json)

          if [ "$EXIT_CODE" = "0" ]; then
            STATUS="UnitTests passed"
            ICON="✅"
          else
            STATUS="UnitTests failed"
            ICON="❌"
          fi

          {
            echo "# Project: $PROJECT_NAME"
            echo "## Automated Unit Testing"
            echo "### $ICON $STATUS on branch **$BRANCH**."

            echo "**Summary**"
            echo '```'
            echo "$SUMMARY"
            echo '```'

            echo "**Test Details**"
            echo '```'
            echo "$DETAILS"
            echo '```'

            echo "**Coverage**"
            echo '```'
            echo "$COVERAGE"
            echo '```'

            echo "**Uncovered**"
            echo '```'
            echo "$UNCOVERED"
            echo '```'

            echo "**Lines Not Covered**"
            echo '```'
            echo "$UNCOVERED_LINES"
            echo '```'
          } > message.txt

          # Send to Discord
          jq -n --rawfile content message.txt '{content: $content}' \
            | curl -H "Content-Type: application/json" \
                  -X POST \
                  -d @- \
                  "$WEBHOOK_URL"

          # Send to Gotify
          # jq -n \
          #   --rawfile msg message.txt \
          #   --arg title "Unit Test Results: $PROJECT_NAME" \
          #   '{title: $title, message: $msg, priority: 5}' \
          #   | curl -X POST \
          #       -H "Content-Type: application/json" \
          #       -d @- \
          #       "$GOTIFY_URL/message?token=$GOTIFY_TOKEN"          

          # Send to ntfy (Markdown-compatible)
          # curl -X POST \
          #     -H "Title: Unit Test Results: $PROJECT_NAME" \
          #     -H "Priority: 5" \
          #     -H "Authorization: Bearer $NTFY_TOKEN" \
          #     --data-binary @message.txt \
          #     "$NTFY_URL"

^ back to top ^