🔀 Week 3: Git & GitHub Mastery

Day 5: GitHub Actions - CI Basics, Workflow Files, Automation

Duration: 5 Hours

📚 Learning Objectives

By the end of this session, you will be able to:

  • Understand CI/CD concepts and benefits
  • Create GitHub Actions workflow files
  • Set up automated testing on pull requests
  • Use workflow triggers and conditions
  • Implement basic automation pipelines

📖 Core Concepts (2 Hours)

What is CI/CD?

CI/CD (Continuous Integration/Continuous Deployment) is the practice of automating the building, testing, and deployment of code changes.

  • Continuous Integration: Automatically build and test code on every push
  • Continuous Delivery: Automatically prepare code for release
  • Continuous Deployment: Automatically deploy to production
  • Benefits: Faster feedback, fewer bugs, consistent releases

CI/CD Pipeline Overview

Developer → CI/CD Pipeline → Production │ │ │ │ git push │ │ └────────────→ ┌────┴────┐ │ │ Build │ │ └────┬────┘ │ │ │ ┌────┴────┐ │ │ Test │ │ └────┬────┘ │ │ │ ┌────┴────┐ │ │ Review │ │ └────┬────┘ │ │ │ ┌────┴────┐ Deploy │ │ Deploy │──────────────→ └─────────┘ Key Stages: 1. Build - Compile code, create artifacts 2. Test - Run automated tests 3. Review - Code review (PRs) 4. Deploy - Release to environment

What is GitHub Actions?

# GitHub Actions = CI/CD built into GitHub Components: ┌─────────────────────────────────────────────┐ │ Workflow │ │ └── .github/workflows/ci.yml │ │ │ │ │ ├── Event (trigger) │ │ │ └── on: push, pull_request │ │ │ │ │ └── Jobs │ │ └── build: │ │ └── Steps │ │ ├── checkout code │ │ ├── setup environment │ │ ├── run tests │ │ └── deploy │ └─────────────────────────────────────────────┘ # Workflows are defined in YAML files # Location: .github/workflows/*.yml

Basic Workflow Structure

# .github/workflows/ci.yml name: CI Pipeline # Workflow name on: # Triggers push: branches: [main] pull_request: branches: [main] jobs: # Jobs to run build: # Job name runs-on: ubuntu-latest # Runner OS steps: # Steps in job - name: Checkout code uses: actions/checkout@v4 - name: Run tests run: echo "Running tests..." - name: Build run: echo "Building..."

Workflow Triggers (Events)

# Push triggers on: push: branches: [main, develop] paths: - 'src/**' # Only when src changes - '!docs/**' # Ignore docs changes tags: - 'v*' # When version tag pushed # Pull request triggers on: pull_request: branches: [main] types: [opened, synchronize, reopened] # Schedule (cron) on: schedule: - cron: '0 0 * * *' # Daily at midnight UTC - cron: '0 */6 * * *' # Every 6 hours # Manual trigger on: workflow_dispatch: inputs: environment: description: 'Environment to deploy' required: true default: 'staging' # Multiple triggers on: push: branches: [main] pull_request: branches: [main] workflow_dispatch:

Jobs and Steps

jobs: # Job 1: Test test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run tests run: | echo "Running unit tests" ./run-tests.sh # Job 2: Build (depends on test) build: needs: test # Run after 'test' job runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Build application run: ./build.sh # Job 3: Deploy (depends on build) deploy: needs: build runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' # Only on main steps: - name: Deploy run: ./deploy.sh # Jobs run in parallel by default # Use 'needs' to create dependencies

Using Actions

# Actions are reusable workflow components # Format: owner/repo@version steps: # Checkout repository code - uses: actions/checkout@v4 # Setup Node.js - uses: actions/setup-node@v4 with: node-version: '20' # Setup Python - uses: actions/setup-python@v5 with: python-version: '3.11' # Cache dependencies - uses: actions/cache@v4 with: path: ~/.npm key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} # Upload artifact - uses: actions/upload-artifact@v4 with: name: build-output path: dist/ # Download artifact - uses: actions/download-artifact@v4 with: name: build-output # Find actions: github.com/marketplace?type=actions

Environment Variables and Secrets

# Environment variables env: # Workflow level NODE_ENV: production jobs: build: env: # Job level BUILD_TYPE: release steps: - name: Build env: # Step level DEBUG: 'true' run: | echo "Node env: $NODE_ENV" echo "Build: $BUILD_TYPE" echo "Debug: $DEBUG" # Using GitHub context variables steps: - name: Print info run: | echo "Repository: ${{ github.repository }}" echo "Branch: ${{ github.ref_name }}" echo "Commit: ${{ github.sha }}" echo "Actor: ${{ github.actor }}" echo "Event: ${{ github.event_name }}" # Secrets (sensitive data) # Set in: Settings → Secrets and variables → Actions steps: - name: Deploy env: API_KEY: ${{ secrets.API_KEY }} AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }} run: ./deploy.sh # Never print secrets! # run: echo "${{ secrets.API_KEY }}" # BAD!

Matrix Strategy

# Run tests on multiple configurations jobs: test: runs-on: ubuntu-latest strategy: matrix: node-version: [16, 18, 20] os: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - run: npm test # This creates 9 jobs (3 node versions × 3 OS) # Exclude combinations strategy: matrix: os: [ubuntu-latest, windows-latest] node: [16, 18, 20] exclude: - os: windows-latest node: 16 # Include extra combinations strategy: matrix: os: [ubuntu-latest] node: [18, 20] include: - os: ubuntu-latest node: 20 experimental: true

Conditionals

jobs: deploy: runs-on: ubuntu-latest # Only run on main branch if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v4 # Only on push events - name: Deploy to staging if: github.event_name == 'push' run: ./deploy-staging.sh # Only if previous step succeeded - name: Notify success if: success() run: echo "Deployment succeeded!" # Only if previous step failed - name: Notify failure if: failure() run: echo "Deployment failed!" # Always run (even if cancelled) - name: Cleanup if: always() run: ./cleanup.sh # Common conditions: # github.event_name == 'push' # github.event_name == 'pull_request' # github.ref == 'refs/heads/main' # contains(github.event.head_commit.message, '[skip ci]') # startsWith(github.ref, 'refs/tags/v')

Complete CI Example

# .github/workflows/ci.yml name: CI on: push: branches: [main] pull_request: branches: [main] jobs: lint: name: Lint Code runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Run ShellCheck run: | sudo apt-get install -y shellcheck shellcheck src/*.sh test: name: Run Tests runs-on: ubuntu-latest needs: lint steps: - uses: actions/checkout@v4 - name: Make scripts executable run: chmod +x src/*.sh - name: Run tests run: | echo "Testing hello.sh..." ./src/hello.sh echo "Testing health-check.sh..." ./src/health-check.sh build: name: Build runs-on: ubuntu-latest needs: test steps: - uses: actions/checkout@v4 - name: Create release archive run: | mkdir -p dist tar -czvf dist/scripts-${{ github.sha }}.tar.gz src/ - name: Upload artifact uses: actions/upload-artifact@v4 with: name: scripts-bundle path: dist/ deploy: name: Deploy runs-on: ubuntu-latest needs: build if: github.ref == 'refs/heads/main' && github.event_name == 'push' steps: - name: Download artifact uses: actions/download-artifact@v4 with: name: scripts-bundle - name: Deploy (simulation) run: | echo "Deploying to production..." ls -la echo "Deployment complete!"

PR Status Checks

# Workflow for PR checks # .github/workflows/pr-check.yml name: PR Checks on: pull_request: branches: [main] jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Check file formatting run: | # Check for trailing whitespace if grep -r '[[:space:]]$' src/; then echo "Found trailing whitespace!" exit 1 fi - name: Validate scripts run: | for script in src/*.sh; do if ! bash -n "$script"; then echo "Syntax error in $script" exit 1 fi done echo "All scripts valid!" # Enable as required check: # Settings → Branches → Branch protection rules # → Require status checks → Select "check"

🔬 Hands-on Lab (2.5 Hours)

Lab 1: Create Your First Workflow

  • Create workflow directory
  • Write basic CI workflow
  • Push and observe the run
# Lab 1: First workflow cd ~/git-practice # Create workflows directory mkdir -p .github/workflows # Create basic workflow cat > .github/workflows/hello.yml << 'EOF' name: Hello World on: push: branches: [main] pull_request: branches: [main] workflow_dispatch: # Manual trigger jobs: greet: runs-on: ubuntu-latest steps: - name: Say Hello run: echo "Hello from GitHub Actions!" - name: Print GitHub Context run: | echo "Repository: ${{ github.repository }}" echo "Branch: ${{ github.ref_name }}" echo "Actor: ${{ github.actor }}" echo "Event: ${{ github.event_name }}" - name: Print Date run: date EOF # Commit and push git add .github/workflows/hello.yml git commit -m "Add first GitHub Actions workflow" git push origin main # Go to GitHub: # 1. Click "Actions" tab # 2. See your workflow running! # 3. Click on it to view logs

Lab 2: CI Pipeline for Scripts

  • Create testing workflow
  • Run script tests
  • Add linting
# Lab 2: CI for shell scripts cd ~/git-practice cat > .github/workflows/ci.yml << 'EOF' name: CI Pipeline on: push: branches: [main] pull_request: branches: [main] jobs: lint: name: Lint Shell Scripts runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Install ShellCheck run: sudo apt-get install -y shellcheck - name: Run ShellCheck run: | echo "Checking shell scripts..." find src -name "*.sh" -exec shellcheck {} \; echo "Linting passed!" test: name: Test Scripts runs-on: ubuntu-latest needs: lint steps: - name: Checkout code uses: actions/checkout@v4 - name: Make scripts executable run: chmod +x src/*.sh - name: Test hello.sh run: | echo "Testing hello.sh..." output=$(./src/hello.sh) if [[ "$output" == *"Hello"* ]]; then echo "✓ hello.sh works!" else echo "✗ hello.sh failed" exit 1 fi - name: Test health-check.sh run: | echo "Testing health-check.sh..." ./src/health-check.sh echo "✓ health-check.sh works!" build: name: Create Package runs-on: ubuntu-latest needs: test steps: - name: Checkout code uses: actions/checkout@v4 - name: Create tarball run: | VERSION=$(date +%Y%m%d-%H%M%S) tar -czvf scripts-${VERSION}.tar.gz src/ docs/ config.txt README.md echo "Created scripts-${VERSION}.tar.gz" - name: Upload artifact uses: actions/upload-artifact@v4 with: name: scripts-package path: scripts-*.tar.gz retention-days: 7 EOF # Commit and push git add .github/workflows/ci.yml git commit -m "Add CI pipeline with lint, test, and build" git push origin main # Check Actions tab for the pipeline running!

Lab 3: PR Checks Workflow

  • Create workflow for pull requests
  • Test by creating a PR
  • See status checks on PR
# Lab 3: PR validation cd ~/git-practice cat > .github/workflows/pr-checks.yml << 'EOF' name: PR Checks on: pull_request: branches: [main] jobs: validate: name: Validate PR runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Check PR title run: | TITLE="${{ github.event.pull_request.title }}" if [[ ${#TITLE} -lt 10 ]]; then echo "PR title too short (min 10 chars)" exit 1 fi echo "PR title OK: $TITLE" - name: Check for TODO comments run: | if grep -r "TODO" src/; then echo "Warning: Found TODO comments" fi - name: Validate shell scripts run: | for script in src/*.sh; do echo "Validating $script..." if ! bash -n "$script"; then echo "Syntax error in $script" exit 1 fi done echo "All scripts have valid syntax" EOF git add .github/workflows/pr-checks.yml git commit -m "Add PR validation workflow" git push origin main # Now create a test PR git checkout -b feature/test-pr-checks echo '# Test file' > test-file.md git add test-file.md git commit -m "Add test file for PR checks" git push -u origin feature/test-pr-checks # Go to GitHub and create a PR # Watch the checks run!

Lab 4: Scheduled Workflow

  • Create a scheduled health check
  • Understand cron syntax
  • View scheduled run history
# Lab 4: Scheduled workflow cd ~/git-practice cat > .github/workflows/scheduled.yml << 'EOF' name: Scheduled Health Check on: schedule: # Run every day at 9 AM UTC - cron: '0 9 * * *' # Allow manual trigger for testing workflow_dispatch: jobs: health: name: Daily Health Check runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: System info run: | echo "=== Daily Health Check ===" echo "Date: $(date)" echo "Runner OS: ${{ runner.os }}" echo "" echo "Disk space:" df -h echo "" echo "Memory:" free -h - name: Check repository run: | echo "Repository stats:" echo "Files: $(find . -type f | wc -l)" echo "Shell scripts: $(find . -name '*.sh' | wc -l)" echo "Commits: $(git rev-list --count HEAD)" - name: Run health script run: | chmod +x src/health-check.sh ./src/health-check.sh EOF git add .github/workflows/scheduled.yml git commit -m "Add scheduled health check workflow" git push origin main # To test immediately: # Go to Actions → Scheduled Health Check → Run workflow

Lab 5: Matrix Testing

  • Create matrix workflow
  • Test on multiple platforms
  • Understand parallel execution
# Lab 5: Matrix builds cd ~/git-practice cat > .github/workflows/matrix.yml << 'EOF' name: Matrix Test on: push: branches: [main] workflow_dispatch: jobs: test: name: Test on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, ubuntu-22.04, macos-latest] fail-fast: false # Continue other jobs if one fails steps: - name: Checkout code uses: actions/checkout@v4 - name: Show OS info run: | echo "Running on: ${{ matrix.os }}" uname -a - name: Run scripts (Linux/macOS) run: | chmod +x src/*.sh ./src/hello.sh multi-version: name: Python ${{ matrix.python }} runs-on: ubuntu-latest strategy: matrix: python: ['3.9', '3.10', '3.11', '3.12'] steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Python ${{ matrix.python }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Show Python version run: | python --version echo "Testing with Python ${{ matrix.python }}" EOF git add .github/workflows/matrix.yml git commit -m "Add matrix testing workflow" git push origin main # Watch multiple jobs run in parallel!

🎉 Week 3 Complete!

What you've learned this week:

  • Git fundamentals: init, add, commit, status, log
  • Branching and merging strategies
  • Working with remote repositories
  • GitHub workflows: PRs, Issues, Projects
  • GitHub Actions for CI/CD automation

📝 Week 3 Project

Weekend Project: Complete Git Repository

Create a professional repository that includes:

  • Well-organized directory structure
  • Comprehensive README.md
  • .gitignore file
  • At least 3 useful shell scripts
  • GitHub Actions CI pipeline that tests all scripts
  • Branch protection rules on main
  • Issue templates
  • PR template

📝 Practice Exercises (30 Minutes)

  1. Create a workflow that runs on every push and prints repository info
  2. Add a job that uploads logs as an artifact
  3. Create a matrix workflow that tests on Ubuntu and macOS
  4. Add a manually triggered workflow with input parameters
  5. Enable required status checks on your main branch

✅ Week 3 Final Checklist

  • Confident with Git basics (init, add, commit, status, log)
  • Can create and merge branches
  • Can resolve merge conflicts
  • Comfortable with remote operations (push, pull, fetch)
  • Can create and manage Pull Requests
  • Know how to use GitHub Issues and Projects
  • Can write GitHub Actions workflows
  • Understand CI/CD concepts
  • Can set up automated testing on PRs