Add provider MSP upgrade runner

This commit is contained in:
rcourtman 2026-06-02 16:19:51 +01:00
parent e86143f07f
commit a0f2fcf1fe
5 changed files with 299 additions and 0 deletions

View file

@ -60,6 +60,20 @@ PULSE_EMAIL_REPLY_TO=support@example.com
# provider MSP backup archive is available:
# docker compose run --rm control-plane provider-msp status --require-backup
#
# Pre-upgrade / pre-maintenance plan. This is non-mutating and does not pull
# images:
# ./upgrade.sh --dry-run
#
# Apply a provider control-plane upgrade after updating the image pins in this
# file. The runner creates and verifies a fresh backup, dry-runs restore into a
# separate target data dir, pulls provider images, starts Traefik/control-plane,
# and prints the tenant runtime reconcile plan:
# ./upgrade.sh
#
# Also reconcile all client runtimes to the configured CP_PULSE_IMAGE line after
# the provider services are updated:
# ./upgrade.sh --rollout-tenants
#
# Fresh-install provider proof. This bootstraps the owner account, checks install
# readiness, creates two proof client workspaces, verifies tenant-local agent
# ingest and portal handoff, creates and verifies a backup, dry-runs restore and

View file

@ -121,6 +121,7 @@ install_deploy_bundle() {
"traefik-dynamic.yml"
".env.example"
"run-install-proof.sh"
"upgrade.sh"
)
if [[ -n "${src_dir}" ]]; then
@ -166,6 +167,7 @@ This script needs these files present on disk:
- traefik-dynamic.yml
- .env.example
- run-install-proof.sh
- upgrade.sh
Run it from deploy/provider-msp/, or set PULSE_PROVIDER_MSP_BUNDLE_URL to a
tar.gz containing those files.
@ -178,6 +180,7 @@ EOF
install -m 0644 "${src_dir}/traefik-dynamic.yml" "${PULSE_PROVIDER_MSP_INSTALL_DIR}/traefik-dynamic.yml"
install -m 0644 "${src_dir}/.env.example" "${PULSE_PROVIDER_MSP_INSTALL_DIR}/.env.example"
install -m 0755 "${src_dir}/run-install-proof.sh" "${PULSE_PROVIDER_MSP_INSTALL_DIR}/run-install-proof.sh"
install -m 0755 "${src_dir}/upgrade.sh" "${PULSE_PROVIDER_MSP_INSTALL_DIR}/upgrade.sh"
}
env_value() {

222
deploy/provider-msp/upgrade.sh Executable file
View file

@ -0,0 +1,222 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
usage() {
cat <<'USAGE'
Usage:
./upgrade.sh [options]
Runs the provider-hosted MSP pre-upgrade and upgrade flow:
1. validates .env and docker-compose.yml
2. checks provider status and install preflight
3. creates and verifies a fresh provider MSP backup
4. dry-runs restore into a separate target data directory
5. pulls and starts the provider control-plane and Traefik services
6. prints the tenant runtime reconcile plan
7. optionally reconciles all tenant runtimes onto the configured runtime line
Options:
--dry-run Print the non-mutating upgrade plan only
--rollout-tenants Execute tenant-runtime reconcile --all after provider services are updated
--prune-previous Remove preserved pre-rollout tenant containers after successful tenant rollout
--skip-compose-pull Do not run docker compose pull for provider services
--skip-runtime-image-pull Pass --skip-image-pull to provider-msp preflight
--backup-output PATH Write the fresh backup archive to PATH
--restore-target DIR Restore dry-run target data dir (default: <data-dir>/upgrade-restore-drill)
--run-id ID Operator-visible tenant reconcile run id
--health-timeout DURATION Tenant rollout health timeout (default: 90s)
-h, --help Show this help
Environment equivalents:
PROVIDER_MSP_UPGRADE_DRY_RUN=1
PROVIDER_MSP_UPGRADE_ROLLOUT_TENANTS=1
PROVIDER_MSP_UPGRADE_PRUNE_PREVIOUS=1
PROVIDER_MSP_SKIP_COMPOSE_PULL=1
PROVIDER_MSP_SKIP_RUNTIME_IMAGE_PULL=1
PROVIDER_MSP_UPGRADE_BACKUP_OUTPUT=/data/backups/provider-msp/pre-upgrade.tar.gz
PROVIDER_MSP_UPGRADE_RESTORE_TARGET=/data/upgrade-restore-drill
PROVIDER_MSP_UPGRADE_RUN_ID=provider-msp-upgrade-20260602T120000Z
PROVIDER_MSP_UPGRADE_HEALTH_TIMEOUT=90s
USAGE
}
die() {
echo "error: $*" >&2
exit 1
}
truthy() {
case "$(echo "${1:-}" | tr '[:upper:]' '[:lower:]')" in
true|1|yes|on) return 0 ;;
*) return 1 ;;
esac
}
env_value() {
local key="$1"
local env_path="${2:-.env}"
local value
if [[ ! -f "${env_path}" ]]; then
return 0
fi
value="$(grep -E "^${key}=" "${env_path}" | tail -n 1 | cut -d= -f2- || true)"
value="${value%\"}"; value="${value#\"}"
value="${value%\'}"; value="${value#\'}"
echo "${value}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
}
extract_field() {
local key="$1"
awk -F= -v key="${key}" '$1 == key { print substr($0, length($1) + 2); exit }'
}
run_control() {
docker compose run --rm --no-deps control-plane "$@"
}
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${script_dir}"
dry_run="${PROVIDER_MSP_UPGRADE_DRY_RUN:-0}"
rollout_tenants="${PROVIDER_MSP_UPGRADE_ROLLOUT_TENANTS:-0}"
prune_previous="${PROVIDER_MSP_UPGRADE_PRUNE_PREVIOUS:-0}"
skip_compose_pull="${PROVIDER_MSP_SKIP_COMPOSE_PULL:-0}"
skip_runtime_image_pull="${PROVIDER_MSP_SKIP_RUNTIME_IMAGE_PULL:-0}"
backup_output="${PROVIDER_MSP_UPGRADE_BACKUP_OUTPUT:-}"
restore_target="${PROVIDER_MSP_UPGRADE_RESTORE_TARGET:-}"
run_id="${PROVIDER_MSP_UPGRADE_RUN_ID:-provider-msp-upgrade-$(date -u +'%Y%m%dT%H%M%SZ')}"
health_timeout="${PROVIDER_MSP_UPGRADE_HEALTH_TIMEOUT:-90s}"
while (($# > 0)); do
case "$1" in
--dry-run)
dry_run=1
shift
;;
--rollout-tenants)
rollout_tenants=1
shift
;;
--prune-previous)
prune_previous=1
shift
;;
--skip-compose-pull)
skip_compose_pull=1
shift
;;
--skip-runtime-image-pull)
skip_runtime_image_pull=1
shift
;;
--backup-output)
backup_output="${2:-}"
shift 2
;;
--restore-target)
restore_target="${2:-}"
shift 2
;;
--run-id)
run_id="${2:-}"
shift 2
;;
--health-timeout)
health_timeout="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
if [[ ! -f .env ]]; then
die "deploy/provider-msp/.env is required; copy .env.example to .env and fill in the provider values"
fi
provider_data_dir="$(env_value PULSE_PROVIDER_MSP_DATA_DIR .env)"
provider_data_dir="${provider_data_dir:-/data}"
if [[ -z "${restore_target}" ]]; then
restore_target="${provider_data_dir%/}/upgrade-restore-drill"
fi
if [[ "${restore_target%/}" == "${provider_data_dir%/}" ]]; then
die "--restore-target must not point at the live provider data dir (${provider_data_dir})"
fi
docker compose version >/dev/null
docker version >/dev/null
docker compose config --quiet
preflight_args=(provider-msp preflight)
if truthy "${skip_runtime_image_pull}" || truthy "${dry_run}"; then
preflight_args+=(--skip-image-pull)
fi
echo "provider_msp_upgrade_dry_run=$(truthy "${dry_run}" && echo true || echo false)"
echo "provider_msp_upgrade_run_id=${run_id}"
echo "provider_msp_upgrade_restore_target=${restore_target}"
run_control provider-msp status
run_control "${preflight_args[@]}"
if truthy "${dry_run}"; then
run_control tenant-runtime reconcile --all --dry-run
echo "tenant_runtime_rollout_applied=false"
echo "provider_msp_upgrade_plan_ok=true"
exit 0
fi
backup_args=(provider-msp backup create)
if [[ -n "${backup_output}" ]]; then
backup_args+=(--output "${backup_output}")
fi
backup_output_text="$(run_control "${backup_args[@]}")"
printf '%s\n' "${backup_output_text}"
archive_path="$(printf '%s\n' "${backup_output_text}" | extract_field archive_path)"
if [[ -z "${archive_path}" ]]; then
die "provider-msp backup create did not print archive_path"
fi
run_control provider-msp backup verify "${archive_path}"
run_control provider-msp backup restore "${archive_path}" --target-data-dir "${restore_target}" --dry-run
run_control provider-msp status --require-backup
if ! truthy "${skip_compose_pull}"; then
docker compose pull traefik control-plane
fi
docker compose up -d traefik control-plane
run_control provider-msp status --require-backup
run_control tenant-runtime reconcile --all --dry-run
if truthy "${rollout_tenants}"; then
reconcile_args=(
tenant-runtime reconcile
--all
--run-id "${run_id}"
--health-timeout "${health_timeout}"
)
if truthy "${prune_previous}"; then
reconcile_args+=(--prune-previous)
fi
run_control "${reconcile_args[@]}"
run_control provider-msp status --require-backup
echo "tenant_runtime_rollout_applied=true"
else
echo "tenant_runtime_rollout_applied=false"
echo "tenant_runtime_rollout_next_command=docker compose run --rm --no-deps control-plane tenant-runtime reconcile --all --run-id ${run_id} --health-timeout ${health_timeout}"
fi
docker compose ps
echo "backup_path=${archive_path}"
echo "restore_target_data_dir=${restore_target}"
echo "provider_msp_upgrade_ok=true"