Skip to content

Week 02 Assignment

Build a production-ready deployment script that applies all Week 02 skills: functions, error handling, argument parsing, logging, and automation.

Overview

You will write a script called deploy.sh that simulates a web application deployment:

  1. Parses options: --env (staging|production), --version TAG, --dry-run, --rollback
  2. Validates preconditions: required tools, environment variables, network connectivity
  3. Creates a timestamped backup of the current deployment
  4. Deploys the new version (simulated with cp or rsync)
  5. Runs a health check after deployment
  6. Rolls back automatically if the health check fails
  7. Logs everything with timestamps to a log file

Requirements

Skill Where required
Argument parsing with case --env, --version, --dry-run, --rollback
Functions (min 5) validate, backup, deploy, health_check, rollback
trap EXIT Cleanup on any exit
Error handling (die) All precondition failures
Logging with timestamps Every operation
rsync or cp The actual file deployment
Exit codes Health check returns non-zero on failure
set -euo pipefail Required
shellcheck Zero warnings

Script Specification

Usage

deploy.sh [OPTIONS]

Options:
  -e, --env ENV          Target environment: staging or production (required)
  -v, --version TAG      Version/tag to deploy (default: latest)
  -d, --dry-run          Show what would be done without doing it
  -r, --rollback         Roll back to the previous deployment
  -h, --help             Show this help

Environment variables required:
  DEPLOY_SOURCE          Path to the build artifact to deploy
  DEPLOY_TARGET          Path to the deployment directory
  HEALTH_CHECK_URL       URL to ping for health check

Examples:
  deploy.sh -e staging -v v1.2.3
  deploy.sh -e production --dry-run
  deploy.sh -e production --rollback

Behavior

  1. validate: Check that DEPLOY_SOURCE, DEPLOY_TARGET, and HEALTH_CHECK_URL are set. Check that rsync and curl are installed.
  2. backup: Copy the current $DEPLOY_TARGET to ${DEPLOY_TARGET}_backup_$(date +%Y%m%d_%H%M%S).
  3. deploy: Rsync $DEPLOY_SOURCE to $DEPLOY_TARGET. Skip if --dry-run.
  4. health_check: curl -sf "$HEALTH_CHECK_URL" — return 0 on success, 1 on failure.
  5. rollback: Restore from the most recent backup. Called automatically if health check fails.

Starter Template

#!/usr/bin/env bash
# deploy.sh — Simulated web app deployment script
# Usage: deploy.sh --env <staging|production> [--version TAG] [--dry-run]
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_NAME="$(basename "$0")"
LOGFILE="/tmp/deploy_$(date +%Y%m%d).log"

# --- defaults ---
ENV=""
VERSION="latest"
DRY_RUN=false
ROLLBACK=false
BACKUP_PATH=""

# --- helpers ---
log()  { echo "[$(date '+%H:%M:%S')] $*" | tee -a "$LOGFILE"; }
die()  { echo "ERROR: $*" >&2; exit 1; }
info() { log "INFO: $*"; }
warn() { log "WARN: $*"; }

# --- usage ---
usage() { cat << 'EOF'
Usage: deploy.sh -e ENV [-v VERSION] [-d] [-r] [-h]
EOF
}

# --- argument parsing ---
parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            -e|--env)      ENV="${2:?--env requires an argument}"; shift 2 ;;
            -v|--version)  VERSION="${2:?--version requires an argument}"; shift 2 ;;
            -d|--dry-run)  DRY_RUN=true; shift ;;
            -r|--rollback) ROLLBACK=true; shift ;;
            -h|--help)     usage; exit 0 ;;
            *)             die "Unknown option: $1" ;;
        esac
    done
    [[ -n "$ENV" ]] || die "Environment required. Use -e staging or -e production."
    [[ "$ENV" == "staging" || "$ENV" == "production" ]] || die "ENV must be staging or production"
}

# --- core functions ---
validate() {
    info "Validating prerequisites..."
    : "${DEPLOY_SOURCE:?DEPLOY_SOURCE env var must be set}"
    : "${DEPLOY_TARGET:?DEPLOY_TARGET env var must be set}"
    : "${HEALTH_CHECK_URL:?HEALTH_CHECK_URL env var must be set}"
    command -v rsync >/dev/null || die "rsync not found"
    command -v curl  >/dev/null || die "curl not found"
    info "Validation OK"
}

backup() {
    BACKUP_PATH="${DEPLOY_TARGET}_backup_$(date +%Y%m%d_%H%M%S)"
    if "$DRY_RUN"; then
        info "DRY RUN: Would backup $DEPLOY_TARGET -> $BACKUP_PATH"
        return
    fi
    [[ -d "$DEPLOY_TARGET" ]] || { info "No existing deployment to backup"; return; }
    cp -r "$DEPLOY_TARGET" "$BACKUP_PATH"
    info "Backup created: $BACKUP_PATH"
}

deploy() {
    info "Deploying version $VERSION to $ENV..."
    if "$DRY_RUN"; then
        info "DRY RUN: Would rsync $DEPLOY_SOURCE/ -> $DEPLOY_TARGET/"
        return
    fi
    rsync -av --delete "$DEPLOY_SOURCE/" "$DEPLOY_TARGET/"
    info "Deploy complete"
}

health_check() {
    info "Running health check: $HEALTH_CHECK_URL"
    if curl -sf --max-time 10 "$HEALTH_CHECK_URL" >/dev/null; then
        info "Health check PASSED"
        return 0
    else
        warn "Health check FAILED"
        return 1
    fi
}

rollback() {
    local latest_backup
    latest_backup=$(ls -dt "${DEPLOY_TARGET}_backup_"* 2>/dev/null | head -1 || echo "")
    [[ -n "$latest_backup" ]] || die "No backup found to roll back to"
    info "Rolling back to: $latest_backup"
    "$DRY_RUN" && { info "DRY RUN: Would restore $latest_backup -> $DEPLOY_TARGET"; return; }
    rsync -av --delete "$latest_backup/" "$DEPLOY_TARGET/"
    info "Rollback complete"
}

# --- main ---
main() {
    parse_args "$@"
    info "=== Deployment started: $ENV v$VERSION ==="

    if "$ROLLBACK"; then
        validate
        rollback
        exit 0
    fi

    validate
    backup
    deploy

    if ! "$DRY_RUN" && ! health_check; then
        warn "Deployment failed health check — initiating rollback"
        rollback
        die "Deployment rolled back due to failed health check"
    fi

    info "=== Deployment successful ==="
}

main "$@"

Grading Criteria

Criterion Points
All 5 functions implemented 20
Argument parsing (all 4 options) 15
--dry-run mode works correctly 15
--rollback mode works correctly 15
Automatic rollback on health check failure 15
Logging with timestamps 10
shellcheck clean 10
Total 100

Stretch Challenges

  1. Add a --notify flag that sends a Slack message on success or failure using curl and a webhook URL.
  2. Add lock file support so two deployments cannot run simultaneously.
  3. Add a --keep N option that retains only the last N backups, deleting older ones.
  4. Write three bats test cases for the script.