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:
- Parses options:
--env (staging|production),--version TAG,--dry-run,--rollback - Validates preconditions: required tools, environment variables, network connectivity
- Creates a timestamped backup of the current deployment
- Deploys the new version (simulated with
cporrsync) - Runs a health check after deployment
- Rolls back automatically if the health check fails
- 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¶
- validate: Check that
DEPLOY_SOURCE,DEPLOY_TARGET, andHEALTH_CHECK_URLare set. Check thatrsyncandcurlare installed. - backup: Copy the current
$DEPLOY_TARGETto${DEPLOY_TARGET}_backup_$(date +%Y%m%d_%H%M%S). - deploy: Rsync
$DEPLOY_SOURCEto$DEPLOY_TARGET. Skip if--dry-run. - health_check:
curl -sf "$HEALTH_CHECK_URL"— return 0 on success, 1 on failure. - 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¶
- Add a
--notifyflag that sends a Slack message on success or failure usingcurland a webhook URL. - Add lock file support so two deployments cannot run simultaneously.
- Add a
--keep Noption that retains only the last N backups, deleting older ones. - Write three
batstest cases for the script.