clearview/build-and-push.sh
Ivo Oskamp e304b2b3d4 Refactor scanner into modular package and add AlertHub-style frontend
- Split scanner.py into scanners/ package (entra, mailbox, sharepoint, common)
- Add Exchange Online PowerShell probe scripts under scanners/exo_scripts
- Frontend overhaul: AlertHub-style sidebar layout, dark logo asset, expanded app.js/index.html/styles.css
- Backend updates across main.py, worker.py, models.py, schemas.py, csv_import.py
- Update Dockerfile and build-and-push.sh
- Update TECHNICAL.md, changelog-develop.md, add summary changelog.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 13:49:04 +02:00

237 lines
7.5 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# ============================================================================
# build-and-push.sh
#
# Purpose:
# - Build & push Docker images for each service under ./containers/*
# - Two modes:
# t (test) = only push :dev
# r (release) = push :<version>, :dev, :latest
# version is read from the top of changelog.md
#
# No git operations: committing and tagging is done manually.
#
# Usage:
# ./build-and-push.sh [mode]
# - mode = t -> test build, push :dev only
# - mode = r -> release build, version taken from changelog.md
# - omitted -> prompt (default: t)
#
# Requirements:
# - docs/changelog.md (relative to repo root), with the most recent release
# at the top as:
# ## vX.Y.Z — YYYY-MM-DD
# (the version is parsed from the first such line)
# - One Dockerfile per service under ./containers/<service>/Dockerfile
# ============================================================================
DOCKER_REGISTRY="gitea.oskamp.info"
DOCKER_NAMESPACE="ivooskamp"
CHANGELOG_FILE="docs/changelog.md"
CONTAINERS_DIR="containers"
# --- Input: prompt if missing ------------------------------------------------
MODE="${1:-}"
if [[ -z "${MODE}" ]]; then
echo "Select build type: [t] test build (push :dev only), [r] release build (default: t)"
read -r MODE
MODE="${MODE:-t}"
fi
case "$MODE" in
t|test) MODE="t" ;;
r|release) MODE="r" ;;
*)
echo "[ERROR] Unknown mode '$MODE' (use 't' for test or 'r' for release)."
exit 1
;;
esac
# --- Helpers -----------------------------------------------------------------
check_docker_ready() {
if ! docker info >/dev/null 2>&1; then
echo "[ERROR] Docker daemon not reachable. Is Docker running and do you have permission to use it?"
exit 1
fi
}
ensure_registry_login() {
local cfg="${HOME}/.docker/config.json"
if [[ ! -f "$cfg" ]]; then
echo "[ERROR] Docker config not found at $cfg. Please login: docker login ${DOCKER_REGISTRY}"
exit 1
fi
if ! grep -q "\"${DOCKER_REGISTRY}\"" "$cfg"; then
echo "[ERROR] No registry auth found for ${DOCKER_REGISTRY}. Please run: docker login ${DOCKER_REGISTRY}"
exit 1
fi
}
validate_repo_component() {
local comp="$1"
if [[ ! "$comp" =~ ^[a-z0-9]+([._-][a-z0-9]+)*$ ]]; then
echo "[ERROR] Invalid repository component '$comp'."
echo " Must match: ^[a-z0-9]+([._-][a-z0-9]+)*$ (lowercase, digits, ., _, - as separators)."
return 1
fi
}
validate_tag() {
local tag="$1"
local len="${#tag}"
if (( len < 1 || len > 128 )); then
echo "[ERROR] Invalid tag length ($len). Must be between 1 and 128 characters."
return 1
fi
if [[ ! "$tag" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]*$ ]]; then
echo "[ERROR] Invalid tag '$tag'. Allowed: [A-Za-z0-9_.-], must start with alphanumeric or underscore."
return 1
fi
}
# Parse the first "## vX.Y.Z ..." heading from changelog.md.
# Accepts: ## v1.0.3 — 2026-04-24
# ## v1.0.3 - 2026-04-24
# ## v1.0.3
read_version_from_changelog() {
if [[ ! -f "$CHANGELOG_FILE" ]]; then
echo "[ERROR] $CHANGELOG_FILE not found in $(pwd)." >&2
exit 1
fi
local line
# Match lines starting with "## v<digits>.<digits>.<digits>"
line="$(grep -m1 -E '^##[[:space:]]+v[0-9]+\.[0-9]+\.[0-9]+' "$CHANGELOG_FILE" || true)"
if [[ -z "$line" ]]; then
echo "[ERROR] No release heading found in $CHANGELOG_FILE (expected e.g. '## v1.0.3 — 2026-04-24' near the top)." >&2
exit 1
fi
# Extract the vX.Y.Z token
local version
version="$(echo "$line" | grep -oE 'v[0-9]+\.[0-9]+\.[0-9]+' | head -n1)"
if [[ -z "$version" ]]; then
echo "[ERROR] Could not parse version from line: $line" >&2
exit 1
fi
echo "$version"
}
# --- Preflight ---------------------------------------------------------------
if [[ ! -d "$CONTAINERS_DIR" ]]; then
echo "[ERROR] '$CONTAINERS_DIR' directory missing. Expected ./${CONTAINERS_DIR}/<service>/ with a Dockerfile."
exit 1
fi
check_docker_ready
ensure_registry_login
validate_repo_component "$DOCKER_NAMESPACE"
# Informational: show branch and HEAD if this happens to be a git repo.
BRANCH_INFO=""
HEAD_INFO=""
if [[ -d ".git" ]]; then
BRANCH_INFO="$(git branch --show-current 2>/dev/null || echo unknown)"
HEAD_INFO="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)"
echo "[INFO] Repo: $(pwd)"
echo "[INFO] Current branch: $BRANCH_INFO"
echo "[INFO] HEAD (sha): $HEAD_INFO"
else
echo "[INFO] Repo: $(pwd) (not a git checkout)"
fi
# --- Determine version (release only) ----------------------------------------
VERSION=""
if [[ "$MODE" == "r" ]]; then
VERSION="$(read_version_from_changelog)"
echo "[INFO] Release version (from $CHANGELOG_FILE): $VERSION"
validate_tag "$VERSION"
validate_tag "latest"
# Ask for confirmation so you never accidentally re-push an old version or a wrong one.
read -r -p "Proceed building & pushing as ${VERSION}? [y/N] " CONFIRM
CONFIRM="${CONFIRM:-N}"
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
echo "[INFO] Aborted by user."
exit 0
fi
else
echo "[INFO] Test build: only :dev will be pushed."
fi
validate_tag "dev"
# --- Build & push per service ------------------------------------------------
shopt -s nullglob
services=( "$CONTAINERS_DIR"/* )
if [[ ${#services[@]} -eq 0 ]]; then
echo "[ERROR] No services found under $CONTAINERS_DIR"
exit 1
fi
BUILT_IMAGES=()
for svc_path in "${services[@]}"; do
[[ -d "$svc_path" ]] || continue
svc="$(basename "$svc_path")"
dockerfile="$svc_path/Dockerfile"
validate_repo_component "$svc"
if [[ ! -f "$dockerfile" ]]; then
echo "[WARNING] Skipping '${svc}': Dockerfile not found in ${svc_path}"
continue
fi
IMAGE_BASE="${DOCKER_REGISTRY}/${DOCKER_NAMESPACE}/${svc}"
if [[ "$MODE" == "r" ]]; then
echo "============================================================"
echo "[INFO] Building ${svc} -> tags: ${VERSION}, dev, latest"
echo "============================================================"
docker build \
-t "${IMAGE_BASE}:${VERSION}" \
-t "${IMAGE_BASE}:dev" \
-t "${IMAGE_BASE}:latest" \
"$svc_path"
docker push "${IMAGE_BASE}:${VERSION}"
docker push "${IMAGE_BASE}:dev"
docker push "${IMAGE_BASE}:latest"
BUILT_IMAGES+=("${IMAGE_BASE}:${VERSION}" "${IMAGE_BASE}:dev" "${IMAGE_BASE}:latest")
else
echo "============================================================"
echo "[INFO] Test build ${svc} -> tag: dev"
echo "============================================================"
docker build -t "${IMAGE_BASE}:dev" "$svc_path"
docker push "${IMAGE_BASE}:dev"
BUILT_IMAGES+=("${IMAGE_BASE}:dev")
fi
done
# --- Summary -----------------------------------------------------------------
echo ""
echo "============================================================"
if [[ "$MODE" == "r" ]]; then
echo "[SUMMARY] Release build & push complete: $VERSION"
else
echo "[SUMMARY] Test build & push complete (:dev only)"
fi
if [[ -n "$BRANCH_INFO" ]]; then
echo "[INFO] Branch: $BRANCH_INFO HEAD: $HEAD_INFO"
fi
echo "[INFO] Images pushed:"
for img in "${BUILT_IMAGES[@]}"; do
echo " - $img"
done
echo "============================================================"
echo ""
echo "[REMINDER] No git operations were performed. If this was a release,"
echo " commit and tag manually, e.g.:"
if [[ "$MODE" == "r" ]]; then
echo " git add -A && git commit -m \"Release ${VERSION}\""
echo " git tag -a ${VERSION} -m \"Release ${VERSION}\""
echo " git push && git push --tags"
fi