Add provider MSP compose install proof runner

This commit is contained in:
rcourtman 2026-06-02 15:06:49 +01:00
parent 7e21263947
commit 5505f930c6
4 changed files with 188 additions and 0 deletions

View file

@ -47,6 +47,11 @@ PULSE_EMAIL_REPLY_TO=support@example.com
# readiness, creates two proof client workspaces, verifies tenant-local agent
# ingest and portal handoff, creates and verifies a backup, dry-runs restore and
# recovery, then removes the proof workspaces.
# ./run-install-proof.sh \
# --account-name "Example MSP" \
# --owner-email owner@example.com
#
# Equivalent one-off command:
# docker compose run --rm control-plane provider-msp install-proof \
# --account-name "Example MSP" \
# --owner-email owner@example.com

View file

@ -0,0 +1,143 @@
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'USAGE'
Usage:
./run-install-proof.sh --account-name "Example MSP" --owner-email owner@example.com [options] [-- extra install-proof flags]
Runs the provider-hosted MSP compose install proof on a Docker host:
1. validates .env and docker-compose.yml
2. optionally pulls the pinned Traefik and control-plane images
3. runs provider-msp install-proof through the compose control-plane service
4. starts the provider stack
5. runs provider-msp status as a final operator check
Options:
--account-name NAME Provider MSP account display name
--owner-email EMAIL Provider owner email address
--workspace-count COUNT Proof client workspace count (default: 2)
--install-type pve|pbs Agent install command type to prove (default: pve)
--target-path PATH Tenant-local handoff target path
--skip-compose-pull Do not run docker compose pull before proof
--skip-runtime-image-pull Pass --skip-image-pull to install-proof
--no-start Do not start the long-running provider stack after proof
-h, --help Show this help
Environment equivalents:
PROVIDER_MSP_ACCOUNT_NAME
PROVIDER_MSP_OWNER_EMAIL
PROVIDER_MSP_PROOF_WORKSPACE_COUNT
PROVIDER_MSP_PROOF_INSTALL_TYPE
PROVIDER_MSP_PROOF_TARGET_PATH
PROVIDER_MSP_SKIP_COMPOSE_PULL=1
PROVIDER_MSP_SKIP_RUNTIME_IMAGE_PULL=1
PROVIDER_MSP_START_CONTROL_PLANE_AFTER_PROOF=0
USAGE
}
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$script_dir"
account_name="${PROVIDER_MSP_ACCOUNT_NAME:-}"
owner_email="${PROVIDER_MSP_OWNER_EMAIL:-}"
workspace_count="${PROVIDER_MSP_PROOF_WORKSPACE_COUNT:-2}"
install_type="${PROVIDER_MSP_PROOF_INSTALL_TYPE:-pve}"
target_path="${PROVIDER_MSP_PROOF_TARGET_PATH:-/settings/infrastructure?add=linux-host}"
skip_compose_pull="${PROVIDER_MSP_SKIP_COMPOSE_PULL:-0}"
skip_runtime_image_pull="${PROVIDER_MSP_SKIP_RUNTIME_IMAGE_PULL:-0}"
start_after_proof="${PROVIDER_MSP_START_CONTROL_PLANE_AFTER_PROOF:-1}"
extra_install_args=()
while (($# > 0)); do
case "$1" in
--account-name)
account_name="${2:-}"
shift 2
;;
--owner-email)
owner_email="${2:-}"
shift 2
;;
--workspace-count)
workspace_count="${2:-}"
shift 2
;;
--install-type)
install_type="${2:-}"
shift 2
;;
--target-path)
target_path="${2:-}"
shift 2
;;
--skip-compose-pull)
skip_compose_pull=1
shift
;;
--skip-runtime-image-pull)
skip_runtime_image_pull=1
shift
;;
--no-start)
start_after_proof=0
shift
;;
-h|--help)
usage
exit 0
;;
--)
shift
extra_install_args+=("$@")
break
;;
*)
echo "unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
if [[ -z "$account_name" ]]; then
echo "--account-name or PROVIDER_MSP_ACCOUNT_NAME is required" >&2
exit 2
fi
if [[ -z "$owner_email" ]]; then
echo "--owner-email or PROVIDER_MSP_OWNER_EMAIL is required" >&2
exit 2
fi
if [[ ! -f .env ]]; then
echo "deploy/provider-msp/.env is required; copy .env.example to .env and fill in the provider values" >&2
exit 2
fi
docker compose version >/dev/null
docker version >/dev/null
docker compose config --quiet
if [[ "$skip_compose_pull" != "1" ]]; then
docker compose pull traefik control-plane
fi
install_args=(
provider-msp install-proof
--account-name "$account_name"
--owner-email "$owner_email"
--workspace-count "$workspace_count"
--install-type "$install_type"
--target-path "$target_path"
)
if [[ "$skip_runtime_image_pull" == "1" ]]; then
install_args+=(--skip-image-pull)
fi
install_args+=("${extra_install_args[@]}")
docker compose run --rm --no-deps control-plane "${install_args[@]}"
if [[ "$start_after_proof" != "0" ]]; then
docker compose up -d traefik control-plane
docker compose run --rm --no-deps control-plane provider-msp status
docker compose ps
fi

View file

@ -144,6 +144,12 @@ surfaces.
dry-run restore into a separate target data dir, dry-run failed-workspace
recovery, remove proof workspaces when requested, and report final
operational status.
`deploy/provider-msp/run-install-proof.sh` is the compose-level operator
wrapper for that rehearsal. It must validate the provider `.env` and compose
config, require a reachable Docker daemon, optionally pull the pinned
provider images, run the one-off `provider-msp install-proof` command through
the packaged control-plane service, start the long-running provider stack,
and finish with `provider-msp status`.
`pulse-control-plane provider-msp status` is the non-mutating operational
companion to that proof: it must report registry readiness, tenant
state/health counts, stuck provisioning workspaces, Docker runtime

View file

@ -2,6 +2,7 @@ package installtests
import (
"os"
"os/exec"
"strings"
"testing"
@ -49,6 +50,7 @@ func TestProviderMSPDeployEnvExampleMatchesBootstrapPath(t *testing.T) {
"docker compose run --rm control-plane provider-msp bootstrap",
"docker compose run --rm control-plane provider-msp preflight",
"docker compose run --rm control-plane provider-msp status",
"./run-install-proof.sh",
"docker compose run --rm control-plane provider-msp install-proof",
"docker compose run --rm control-plane provider-msp recover --all-degraded --dry-run",
"docker compose run --rm control-plane provider-msp recover --all-degraded",
@ -72,6 +74,38 @@ func TestProviderMSPDeployEnvExampleMatchesBootstrapPath(t *testing.T) {
)
}
func TestProviderMSPInstallProofRunnerMatchesComposeContract(t *testing.T) {
scriptPath := repoFile("deploy", "provider-msp", "run-install-proof.sh")
result := exec.Command("bash", "-n", scriptPath)
if output, err := result.CombinedOutput(); err != nil {
t.Fatalf("provider MSP install-proof runner shell syntax failed: %v\n%s", err, output)
}
scriptBytes, err := os.ReadFile(scriptPath)
if err != nil {
t.Fatalf("read provider MSP install-proof runner: %v", err)
}
text := string(scriptBytes)
assertContainsAll(t, text,
"docker compose config --quiet",
"docker version >/dev/null",
"docker compose pull traefik control-plane",
"provider-msp install-proof",
"--account-name",
"--owner-email",
"--workspace-count",
"--install-type",
"--target-path",
"--skip-image-pull",
"docker compose run --rm --no-deps control-plane",
"docker compose up -d traefik control-plane",
"provider-msp status",
)
assertNotContainsAny(t, text,
"STRIPE_",
"CP_PUBLIC_CLOUD_SIGNUP_ENABLED",
)
}
func TestProviderMSPTraefikUsesProviderNetwork(t *testing.T) {
traefikBytes, err := os.ReadFile(repoFile("deploy", "provider-msp", "traefik.yml"))
if err != nil {