deploy: no build-probe advisory on static deploys, and cover --label
A static route has no process and no /api/version, so telling the operator to set a build probe there (the no-probe advisory in runDeploy) sets them up to configure a probe that 404s — which then fails and times out every future `carlos deploy` for that site. Gate the advisory on !staticDeploy; the non-static message is unchanged. Also add the test this branch was missing: TestE2EDeployThroughConsole now passes --label through `carlos deploy` and asserts the stored manifest carries it, since the bug this branch fixes was exactly a silently-passed "". TestE2EStaticDeployThroughConsole pins the first fix by asserting the build-probe advisory never appears on a static deploy.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XxxmnFjdNxLc3M16ooFG95
2 files changed,
+25
−3
deploy.go+9 −1deploy_e2e_test.go+16 −2
CI passed — run details
diff --git a/deploy.go b/deploy.go| index 8c1d5902b..ea045fb40 100644 |
| --- a/deploy.go |
| +++ b/deploy.go |
| @@ -334,7 +334,15 @@ func runDeploy(args []string) { |
| // per app, so the line names it rather than describing the problem |
| // and leaving the reader to find the verb. |
| mutedf(stdout, "watching the X-Carlos-Version header: it reports the release ADOPTED, not the build running\n") |
| - mutedf(stdout, " a probe proves the process actually cycled — carlos apps build-probe --app %s /api/version\n", *app) |
| + // The advisory below assumes a process to probe. A static route has |
| + // none — no /api/version, no exec child to cycle — so the suggested |
| + // command would only teach the operator to configure a probe that |
| + // 404s, which then fails and times out every future `carlos deploy` |
| + // for that site. Static deploys get the line above (still true: the |
| + // header reports adoption, not a running build) and stop there. |
| + if !staticDeploy { |
| + mutedf(stdout, " a probe proves the process actually cycled — carlos apps build-probe --app %s /api/version\n", *app) |
| + } |
| } |
| targets := make([]watchTarget, 0, len(hosts)) |
| for _, h := range hosts { |
diff --git a/deploy_e2e_test.go b/deploy_e2e_test.go| index 76f9e5357..dfc9ae2f9 100644 |
| --- a/deploy_e2e_test.go |
| +++ b/deploy_e2e_test.go |
| @@ -212,6 +212,13 @@ func TestE2EStaticDeployThroughConsole(t *testing.T) { |
| if strings.Contains(out, "watching "+host) { |
| t.Fatalf("static note still repeats the host the deploying line just printed:\n%s", out) |
| } |
| + // A static route has no process and no /api/version, so the no-probe |
| + // advisory (deploy.go, gated on !staticDeploy) must not tell the |
| + // operator to set one — following that advice on a static site sets a |
| + // probe that 404s and fails every later `carlos deploy` for it. |
| + if strings.Contains(out, "build-probe") { |
| + t.Fatalf("static deploy suggested a build probe, which a static route can never serve:\n%s", out) |
| + } |
| ptr, err := release.Channel(context.Background(), relStore, pub, release.OpsAccount, app, channel) |
| if err != nil { |
| t.Fatalf("channel after deploy: %v", err) |
| @@ -266,6 +273,7 @@ func TestE2EDeployThroughConsole(t *testing.T) { |
| app = "titogo" |
| channel = "canary/e2e" |
| version = "abc1234" |
| + label = "e2e rollout" |
| ) |
| tmp := t.TempDir() |
| @@ -377,10 +385,13 @@ func TestE2EDeployThroughConsole(t *testing.T) { |
| // --- the verb ------------------------------------------------------ |
| buf.Reset() |
| - runDeploy([]string{"-app", app, "-account", "ops", "-version", version, "-timeout", "30s", artifact}) |
| + runDeploy([]string{"-app", app, "-account", "ops", "-version", version, "-label", label, "-timeout", "30s", artifact}) |
| out := buf.String() |
| - // Ship happened, against the bucket, signed. |
| + // Ship happened, against the bucket, signed — and --label, wired |
| + // straight through to runShipAPI (deploy.go), landed in the manifest |
| + // rather than the silently-passed "" this branch exists to fix |
| + // (discussion #13). |
| m, err := release.GetManifest(context.Background(), relStore, release.OpsAccount, app, version) |
| if err != nil { |
| t.Fatalf("manifest after deploy: %v", err) |
| @@ -388,6 +399,9 @@ func TestE2EDeployThroughConsole(t *testing.T) { |
| if m.Version != version || m.Kind != "binary" { |
| t.Fatalf("manifest = %+v, want %s binary", m, version) |
| } |
| + if m.Label != label { |
| + t.Fatalf("manifest label = %q, want %q — carlos deploy --label is not reaching the release", m.Label, label) |
| + } |
| // Promote happened, onto the channel the RECORD named — never typed. |
| ptr, err := release.Channel(context.Background(), relStore, pub, release.OpsAccount, app, channel) |
| if err != nil { |