Week 5 – Daily Practice Tasks (CI CD with GitHub Actions) - snir1551/DevOps-Linux GitHub Wiki

Week 5 – Daily Practice Tasks (CI/CD with GitHub Actions)

Task 1 – Introduction to GitHub Actions

  • GitHub Action:

    A GitHub Action is a reusable unit of work that performs a specific task in a workflow. For example, an action can:

    • Check out your repository code.

    • Set up a specific programming environment.

    • Run a test suite.

    • Deploy an application.

    • Actions are like building blocks you can combine to create automated workflows.

  • The difference between a job and a step:

Concept Description
Job A job is a collection of steps that run on the same virtual machine (runner). Jobs are independent by default and can run in parallel, unless configured to depend on others. Each job has its own environment.
Step A step is a single task or command within a job. Steps run sequentially within the same job. They can either run an action (e.g., checkout code) or a custom command (e.g., npm install).
  • triggers a workflow:

    A workflow is triggered by events.
    These are specific activities in your repository or external systems that start the workflow.
    Common triggers include:

    • Push to a branch.
    • Pull request opened, synchronized, or closed.
    • Issue created or labeled.
    • Scheduled time (e.g., daily at midnight using cron).
    • Manual dispatch (on demand).

    You define these triggers in your workflow YAML file under the on keyword.

    Example:

    on:
     push:
       branches:
          - main
     pull_request:
    

Task 2 – Basic CI Pipeline for Testing

  1. In the project’s root, creating a new folder .github/workflows.
  2. Inside .github/workflows, create a file named ci.yml.
  3. A template for a Node.js app:
name: CI Pipeline

on:
  push:
    branches:
      - "**"
    paths:
      - 'backend/**'
  pull_request:
    branches:
      - "**"
    paths:
      - 'backend/**'

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


    steps:
    - name: Checkout Repository
      uses: actions/checkout@v3

    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'

    - name: Check Node.js Version
      run: node --version

    - name: Install Dependencies
      working-directory: backend
      run: npm install

    - name: Run Tests
      working-directory: backend
      run: npm test


  1. modifying the ci.yml file to include a matrix strategy:

Task 3 – Matrix Strategy

strategy:
      matrix:
        node-version: [18, 20]

- name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}

we can confirm the workflow runs once for each version by checking the 'GitHub Actions' run results.
for example in my case: https://github.com/snir1551/DevOps-Linux/actions/runs/15452153050/job/43496762587


In the backend job, we add a step to upload the test log file as an artifact:

Task 4 – Artifacts and Post-job Monitoring

    - name: Run Tests
      working-directory: backend
      run: npm test > test-report.txt

    - name: Upload Test Report Artifact
      uses: actions/upload-artifact@v3
      with:
        name: test-report-${{ matrix.node-version }}
        path: backend/test-report.txt

    - name: Start App on Port 3000
      working-directory: backend
      run: |
        npm start &
        echo "Waiting for server..."
        sleep 5

    - name: Check Service Availability (localhost:3000)
      run: |
        curl --fail http://localhost:3000 || echo "Service check failed on localhost:3000"

we can download an artifact zip file from the link above, and see it has 'test-report.txt' file inside of it.
we validate availability with curl -I http://localhost:3000.


Task 5 – Slack/Discord Integration

  1. Integrate the workflow with Slack (i chose Slack)
  2. Store the webhook URL in the GitHub repo secrets (SLACK_WEBHOOK_URL).
  3. Add a Slack notification step to the workflow: add to the yml for slack:
- name: Notify Slack on Success
      if: success()
      uses: slackapi/[email protected]
      with:
        payload: |
          {
            "text": " *Job `${{ github.job }}` succeeded!*\n• Repo: `${{ github.repository }}`\n• Branch: `${{ github.ref_name }}`\n• Actor: `${{ github.actor }}`"
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

    - name: Notify Slack on Failure
      if: failure()
      uses: slackapi/[email protected]
      with:
        payload: |
          {
            "text": " *Job `${{ github.job }}` failed!*\n• Repo: `${{ github.repository }}`\n• Branch: `${{ github.ref_name }}`\n• Actor: `${{ github.actor }}`"
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

image


Task 6 – Combined Frontend and Backend CI/CD

  1. created simple backend and frontend folder and files
  2. enhanced the yml, this is the final yml file:

name: CI Pipeline

on:
  push:
    branches:
      - "**"
    paths:
      - "week5_practice/**"
  pull_request:
    branches:
      - "**"
    paths:
      - "week5_practice/**"
        
  
jobs:
  backend:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18, 20]

    steps:
    - name: Record Start Time
      id: start_time
      run: echo "start_time=$(date +%s)" >> $GITHUB_OUTPUT

    - name: Checkout Repository
      uses: actions/checkout@v3

    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}

    - name: Check Node.js Version
      run: node --version

    - name: Install Dependencies
      working-directory: week5_practice/backend
      run: npm install

    - name: Run Tests
      working-directory: week5_practice/backend
      run: |
        npm test -- --no-color > test-report.txt
        echo "Build finished successfully at $(date)" >> test-results.log 

    - name: Upload Backend Test Report
      uses: actions/upload-artifact@v4
      with:
        name: backend-test-report-${{ matrix.node-version }}
        path: week5_practice/backend/test-report.txt

    - name: Start App on Port 3001
      working-directory: week5_practice/backend
      run: |
        npm run start &
        echo "Waiting for server..."
        sleep 5

    - name: Check Service Availability (localhost:3001)
      run: |
        curl --fail http://localhost:3001 || echo "Service check failed on localhost:3001"


    - name: Record End Time and Duration
      id: end_time
      if: always()
      run: |
        end=$(date +%s)
        start=${{ steps.start_time.outputs.start_time }}
        duration=$((end - start))
        echo "duration=$duration" >> $GITHUB_OUTPUT

    - name: Notify Slack on Success
      if: success()
      uses: slackapi/[email protected]
      with:
        payload: |
          {
            "text": " *Job:* Frontend (Node.js `${{ matrix.node-version }}`)\n• *Status:* `${{ job.status }}`\n• *Duration:* `${{ env.JOB_DURATION }}` seconds\n• *Workflow:* `${{ github.workflow }}`\n• *Run:* #${{ github.run_number }}\n• *Repo:* `${{ github.repository }}`"
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

    - name: Notify Slack on Failure
      if: failure()
      uses: slackapi/[email protected]
      with:
        payload: |
          {
             "text": " *Job FAILED!*\n*Job:* Frontend (Node.js `${{ matrix.node-version }}`)\n• *Status:* `${{ job.status }}`\n• *Duration:* `${{ env.JOB_DURATION }}` seconds\n• *Workflow:* `${{ github.workflow }}`\n• *Run:* #${{ github.run_number }}\n• *Repo:* `${{ github.repository }}`"
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

    - name: Slack Manual Test
      uses: slackapi/[email protected]
      with:
        payload: '{"text": " This is a manual Slack test from Snir!"}'
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

    - name: Echo job completion
      if: success()
      run: echo "Backend job for Node.js ${{ matrix.node-version }} completed successfully!"

  frontend:
    runs-on: ubuntu-latest
    needs: backend

    strategy:
      matrix:
        node-version: [18, 20]

    steps:
    - name: Checkout Repository
      uses: actions/checkout@v3

    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}

    - name: Check Node.js Version
      run: node --version

    - name: Install Dependencies
      working-directory: week5_practice/frontend
      run: npm install

    - name: Run Tests
      working-directory: week5_practice/frontend
      run: npm test -- --no-color > test-report.txt

    - name: Upload Frontend Test Report
      uses: actions/upload-artifact@v4
      with:
        name: frontend-test-report-${{ matrix.node-version }}
        path: week5_practice/frontend/test-report.txt

    - name: Start App on Port 3000
      working-directory: week5_practice/frontend
      run: |
        npm run start &
        echo "Waiting for server..."
        sleep 5

    - name: Check Service Availability (localhost:3000)
      run: |
        curl --fail http://localhost:3000 || echo "Service check failed on localhost:3000"


    - name: Notify Slack on Success
      if: success()
      uses: slackapi/[email protected]
      with:
        payload: |
          {
            "text": " *Job:* Frontend (Node.js `${{ matrix.node-version }}`)\n• *Status:* `${{ job.status }}`\n• *Duration:* `${{ env.JOB_DURATION }}` seconds\n• *Workflow:* `${{ github.workflow }}`\n• *Run:* #${{ github.run_number }}\n• *Repo:* `${{ github.repository }}`"
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

    - name: Notify Slack on Failure
      if: failure()
      uses: slackapi/[email protected]
      with:
        payload: |
          {
            "text": " *Job FAILED!*\n*Job:* Frontend (Node.js `${{ matrix.node-version }}`)\n• *Status:* `${{ job.status }}`\n• *Duration:* `${{ env.JOB_DURATION }}` seconds\n• *Workflow:* `${{ github.workflow }}`\n• *Run:* #${{ github.run_number }}\n• *Repo:* `${{ github.repository }}`"
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

    - name: Slack Manual Test
      uses: slackapi/[email protected]
      with:
        payload: '{"text": " This is a manual Slack test from Snir!"}'
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

    - name: Echo job completion
      if: success()
      run: echo "Frontend job for Node.js ${{ matrix.node-version }} completed successfully!"


⚠️ **GitHub.com Fallback** ⚠️