#!/bin/bash # generate-docker-steps.sh - Generate Woodpecker CI pipeline steps for Docker build/push/link # # Outputs valid Woodpecker YAML for: # - Kaniko Docker build & push steps (one per service) # - Gitea package linking step # - npm package publish step (optional) # # Usage: # generate-docker-steps.sh \ # --registry git.uscllc.com \ # --org usc \ # --repo uconnect \ # --service backend-api:src/backend-api/Dockerfile \ # --service web-portal:src/web-portal/Dockerfile \ # --branches main,develop \ # [--build-arg backend-api:NEXT_PUBLIC_API_URL=https://api.example.com] \ # [--npm-package @uconnect/schemas:src/schemas] \ # [--npm-registry https://git.uscllc.com/api/packages/usc/npm/] \ # [--depends-on build] set -e # Defaults REGISTRY="" ORG="" REPO="" BRANCHES="main,develop" DEPENDS_ON="build" declare -a SERVICES=() declare -a BUILD_ARGS=() declare -a NPM_PACKAGES=() NPM_REGISTRY="" show_help() { cat <<'EOF' Usage: generate-docker-steps.sh [OPTIONS] Generate Woodpecker CI YAML for Docker build/push/link via Kaniko. Required: --registry Gitea hostname (e.g., git.uscllc.com) --org Gitea organization (e.g., usc) --repo Repository name (e.g., uconnect) --service Service to build (repeatable) Optional: --branches Comma-separated branches (default: main,develop) --depends-on Step name Docker builds depend on (default: build) --build-arg Build arg for a service (repeatable) --npm-package npm package to publish (repeatable) --npm-registry npm registry URL for publishing --kaniko-setup-only Output just the kaniko_setup YAML anchor -h, --help Show this help Examples: # Mosaic Stack pattern generate-docker-steps.sh \ --registry git.mosaicstack.dev --org mosaic --repo stack \ --service stack-api:apps/api/Dockerfile \ --service stack-web:apps/web/Dockerfile \ --build-arg stack-web:NEXT_PUBLIC_API_URL=https://api.mosaicstack.dev # U-Connect pattern generate-docker-steps.sh \ --registry git.uscllc.com --org usc --repo uconnect \ --service uconnect-backend-api:src/backend-api/Dockerfile \ --service uconnect-web-portal:src/web-portal/Dockerfile \ --service uconnect-ingest-api:src/ingest-api/Dockerfile \ --branches main,develop EOF exit 0 } KANIKO_SETUP_ONLY=false # Parse arguments while [[ $# -gt 0 ]]; do case $1 in --registry) REGISTRY="$2"; shift 2 ;; --org) ORG="$2"; shift 2 ;; --repo) REPO="$2"; shift 2 ;; --service) SERVICES+=("$2"); shift 2 ;; --branches) BRANCHES="$2"; shift 2 ;; --depends-on) DEPENDS_ON="$2"; shift 2 ;; --build-arg) BUILD_ARGS+=("$2"); shift 2 ;; --npm-package) NPM_PACKAGES+=("$2"); shift 2 ;; --npm-registry) NPM_REGISTRY="$2"; shift 2 ;; --kaniko-setup-only) KANIKO_SETUP_ONLY=true; shift ;; -h|--help) show_help ;; *) echo "Unknown option: $1" >&2; exit 1 ;; esac done # Validate required args if [[ -z "$REGISTRY" ]]; then echo "Error: --registry is required" >&2; exit 1; fi if [[ -z "$ORG" ]]; then echo "Error: --org is required" >&2; exit 1; fi if [[ -z "$REPO" ]]; then echo "Error: --repo is required" >&2; exit 1; fi if [[ ${#SERVICES[@]} -eq 0 && "$KANIKO_SETUP_ONLY" != true ]]; then echo "Error: at least one --service is required" >&2; exit 1 fi # Parse branches into YAML list IFS=',' read -ra BRANCH_LIST <<< "$BRANCHES" BRANCH_YAML="[" for i in "${!BRANCH_LIST[@]}"; do if [[ $i -gt 0 ]]; then BRANCH_YAML="$BRANCH_YAML, "; fi BRANCH_YAML="$BRANCH_YAML${BRANCH_LIST[$i]}" done BRANCH_YAML="$BRANCH_YAML]" # Helper: get build args for a specific service get_build_args_for_service() { local svc_name="$1" local args=() for ba in "${BUILD_ARGS[@]}"; do local ba_svc="${ba%%:*}" local ba_val="${ba#*:}" if [[ "$ba_svc" == "$svc_name" ]]; then args+=("$ba_val") fi done echo "${args[@]}" } # Helper: determine Dockerfile context from path # e.g., apps/api/Dockerfile -> . (monorepo root) # docker/postgres/Dockerfile -> docker/postgres get_context() { local dockerfile="$1" local dir dir=$(dirname "$dockerfile") # If Dockerfile is at project root or in a top-level apps/src dir, use "." if [[ "$dir" == "." || "$dir" == apps/* || "$dir" == src/* || "$dir" == packages/* ]]; then echo "." else echo "$dir" fi } # ============================================================ # Output: YAML anchor for kaniko setup # ============================================================ emit_kaniko_anchor() { cat < /kaniko/.docker/config.json EOF } if [[ "$KANIKO_SETUP_ONLY" == true ]]; then emit_kaniko_anchor exit 0 fi # ============================================================ # Output: Header comment # ============================================================ cat < .npmrc EOF # Detect scope from first package FIRST_PKG="${NPM_PACKAGES[0]}" PKG_NAME="${FIRST_PKG%%:*}" SCOPE="${PKG_NAME%%/*}" if [[ "$SCOPE" == @* ]]; then echo " echo \"${SCOPE}:registry=${NPM_REGISTRY}\" >> .npmrc" fi for pkg in "${NPM_PACKAGES[@]}"; do PKG_NAME="${pkg%%:*}" PKG_PATH="${pkg#*:}" cat </dev/null || echo "0.0.0") if [ "\$\$CURRENT" = "\$\$PUBLISHED" ]; then echo "${PKG_NAME}@\$\$CURRENT already published, skipping" else echo "Publishing ${PKG_NAME}@\$\$CURRENT (was \$\$PUBLISHED)" npm publish -w ${PKG_NAME} fi EOF done cat <