mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-08-15 03:53:32 +00:00
Let providers evaluate MSP without asking permission first
Two mandatory round-trips stood between an interested MSP and their first screen, and neither was technical. setup.sh required four image digests shipped as literal <pin> placeholders, so the only way to get them was to ask. All four images are publicly readable, so there was never anything to hand out. setup.sh now resolves each blank pin to an immutable digest from its published tag via buildx imagetools and writes it back to .env; hand-set values are left alone. setup.sh then died outright without a licence file, so nobody could start the stack, create a workspace, or see the portal until a human minted a licence for them. The control plane already ran unlicensed via ProviderMSPPlanSourceEnvFallback; only the installer refused. A licence path that is set but missing is still a hard failure, since that is a misconfiguration rather than a choice. Unlicensed now means evaluation rather than the cheapest paid tier. The env fallback defaulted to msp_starter, handing every unlicensed deployment the full 5-client Starter allowance and leaving no boundary between evaluating and buying. Adds msp_eval at 2 workspaces: same capabilities, smaller cap, not purchasable, not on the public ladder. An isolation guarantee is the one claim a provider cannot evaluate from a screenshot, and both MSP leads this year went quiet at exactly this step. Contracts: cloud-paid records the unlicensed plan rule and the strictly-below-paid invariant; deployment-installability records credential-free, correspondence-free installability. Verification: TestMSPEvalCapStaysBelowCheapestPaidTier, TestCanonicalizePlanVersion_MSPEval, TestProviderMSPSetupScriptSupportsUnlicensedEvaluation. The last was negative-tested by reintroducing a <pin> placeholder and confirming it fails. ensure_image_pins exercised against the live registries. licensing, cloudcp, control-plane and installtests all green.
This commit is contained in:
parent
f668614afd
commit
6be2af1c19
12 changed files with 271 additions and 22 deletions
|
|
@ -74,7 +74,9 @@ func TestProviderMSPDeployEnvExampleMatchesBootstrapPath(t *testing.T) {
|
|||
"PULSE_PROVIDER_MSP_ROOT_SPACECHECK_DIR=/var/lib/pulse-provider-msp/spacecheck/root",
|
||||
"PULSE_PROVIDER_MSP_DOCKER_SPACECHECK_DIR=/var/lib/docker/.pulse-provider-msp-spacecheck",
|
||||
"CP_TRUSTED_PROXY_CIDRS=172.30.0.0/24",
|
||||
"CP_PROVIDER_MSP_LICENSE_FILE=./provider-msp-license.jwt",
|
||||
// Ships blank on purpose: blank is evaluation. The exact blank form is
|
||||
// asserted in TestProviderMSPSetupScriptSupportsUnlicensedEvaluation.
|
||||
"CP_PROVIDER_MSP_LICENSE_FILE=",
|
||||
"CP_ENTITLEMENT_SIGNING_PRIVATE_KEY=",
|
||||
"sudo -E ./setup.sh",
|
||||
"docker compose run --rm control-plane provider-msp bootstrap",
|
||||
|
|
@ -328,3 +330,51 @@ func assertNotContainsAny(t *testing.T, text string, forbidden ...string) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The unlicensed path is both a commercial boundary and the whole conversion
|
||||
// path, so it is pinned here rather than left to whoever next edits setup.sh.
|
||||
//
|
||||
// Before this, setup.sh required a licence file and four hand-supplied image
|
||||
// digests shipped as unfillable "<pin>" placeholders. That put two human
|
||||
// round-trips in front of a provider's first screen, for no technical reason:
|
||||
// all four images are public, and the control plane already ran unlicensed on
|
||||
// the environment fallback.
|
||||
func TestProviderMSPSetupScriptSupportsUnlicensedEvaluation(t *testing.T) {
|
||||
scriptBytes, err := os.ReadFile(repoFile("deploy", "provider-msp", "setup.sh"))
|
||||
if err != nil {
|
||||
t.Fatalf("read provider MSP setup: %v", err)
|
||||
}
|
||||
script := string(scriptBytes)
|
||||
|
||||
assertContainsAll(t, script,
|
||||
"evaluation mode, 2 client workspaces",
|
||||
"ensure_image_pins",
|
||||
"default_image_ref",
|
||||
"resolve_image_digest",
|
||||
"buildx imagetools inspect",
|
||||
)
|
||||
|
||||
// A blank licence path means evaluation. If it returns to the
|
||||
// must-be-non-empty list, setup.sh refuses to start unlicensed again.
|
||||
if strings.Contains(script, "CP_TRUSTED_PROXY_CIDRS CP_PROVIDER_MSP_LICENSE_FILE") {
|
||||
t.Fatal("CP_PROVIDER_MSP_LICENSE_FILE is back in the required non-empty list; blank must mean evaluation")
|
||||
}
|
||||
|
||||
envBytes, err := os.ReadFile(repoFile("deploy", "provider-msp", ".env.example"))
|
||||
if err != nil {
|
||||
t.Fatalf("read provider MSP env example: %v", err)
|
||||
}
|
||||
env := string(envBytes)
|
||||
|
||||
if strings.Contains(env, "<pin>") {
|
||||
t.Fatal(".env.example still ships <pin> image placeholders; setup.sh resolves blank pins from published tags instead")
|
||||
}
|
||||
if !strings.Contains(env, "\nCP_PROVIDER_MSP_LICENSE_FILE=\n") {
|
||||
t.Fatal(".env.example must ship a blank CP_PROVIDER_MSP_LICENSE_FILE so the shipped default is evaluation")
|
||||
}
|
||||
for _, key := range []string{"TRAEFIK_IMAGE", "DOCKER_SOCKET_PROXY_IMAGE", "CONTROL_PLANE_IMAGE", "CP_PULSE_IMAGE"} {
|
||||
if !strings.Contains(env, "\n"+key+"=\n") {
|
||||
t.Fatalf(".env.example must ship %s blank so setup.sh resolves its digest", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue