From 5505f930c6b6e704b02948633f344ee56f8b6ab8 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 2 Jun 2026 15:06:49 +0100 Subject: [PATCH] Add provider MSP compose install proof runner --- deploy/provider-msp/.env.example | 5 + deploy/provider-msp/run-install-proof.sh | 143 ++++++++++++++++++ .../subsystems/deployment-installability.md | 6 + .../installtests/provider_msp_deploy_test.go | 34 +++++ 4 files changed, 188 insertions(+) create mode 100755 deploy/provider-msp/run-install-proof.sh diff --git a/deploy/provider-msp/.env.example b/deploy/provider-msp/.env.example index 4affcb823..01f78533b 100644 --- a/deploy/provider-msp/.env.example +++ b/deploy/provider-msp/.env.example @@ -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 diff --git a/deploy/provider-msp/run-install-proof.sh b/deploy/provider-msp/run-install-proof.sh new file mode 100755 index 000000000..999e83457 --- /dev/null +++ b/deploy/provider-msp/run-install-proof.sh @@ -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 diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index ec64db14a..c0c1d828c 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -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 diff --git a/scripts/installtests/provider_msp_deploy_test.go b/scripts/installtests/provider_msp_deploy_test.go index e9f6cb18e..58f9508ff 100644 --- a/scripts/installtests/provider_msp_deploy_test.go +++ b/scripts/installtests/provider_msp_deploy_test.go @@ -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 {