Week 5 – Daily Practice Tasks (CI CD with GitHub Actions) - snir1551/DevOps-Linux GitHub Wiki
-
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:
- In the project’s root, creating a new folder .github/workflows.
- Inside .github/workflows, create a file named ci.yml.
- 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
- modifying the ci.yml file to include a 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:
- 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.
- Integrate the workflow with Slack (i chose Slack)
- Store the webhook URL in the GitHub repo secrets (SLACK_WEBHOOK_URL).
- 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 }}
- created simple backend and frontend folder and files
- 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!"