deploy: name the destination URL before ship on every path
One instance prints its URL; a fleet-sized channel prints an aggregate count (the live lines and timeout diagnostics already name hosts one by one); the static note stops repeating hosts the deploying line just printed. The held-for-bake path now names where the promote was going before it returns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3 files changed,
+55
−2
deploy.go+23 −2deploy_e2e_test.go+8 −0deploy_test.go+24 −0
diff --git a/deploy.go b/deploy.go| index 08da41667..a74fc7264 100644 |
| --- a/deploy.go |
| +++ b/deploy.go |
| @@ -70,6 +70,9 @@ func runDeploy(args []string) { |
| if err != nil { |
| log.Fatalf("deploy: %v", err) |
| } |
| + if line := deployingLine(hosts, channel); line != "" { |
| + fmt.Fprintln(stdout, line) |
| + } |
| // printBanner=false: the "using ..." line already printed above, before |
| // the instance list — so a listing failure still names the console — |
| // and runShipAPI would otherwise print it a second time. |
| @@ -98,8 +101,8 @@ func runDeploy(args []string) { |
| // one window (defaultDeployTimeout, pinned by test), so nothing here |
| // adjusts the clock — the operator just gets told why "promoted" can |
| // sit in front of the old page for a minute. |
| - mutedf(stdout, "watching %s — a static route re-checks its channel about every %ds, so the URL can take that long to flip\n", |
| - strings.Join(hosts, ", "), int(staticRecheckWindow.Seconds())) |
| + mutedf(stdout, "a static route re-checks its channel about every %ds, so the URL can take that long to flip\n", |
| + int(staticRecheckWindow.Seconds())) |
| } |
| targets := make([]watchTarget, 0, len(hosts)) |
| for _, h := range hosts { |
| @@ -125,6 +128,24 @@ const defaultDeployTimeout = 120 * time.Second |
| // constant would promise a match this process cannot know it has. |
| const staticRecheckWindow = 60 * time.Second |
| +// deployingLine names the destination a deploy is about to write to, |
| +// printed BEFORE ship so every later path — the bake hold, the watch, |
| +// even a ship failure — has already said where this deploy was going. |
| +// One instance gets its URL; a fleet-sized channel gets a count (the |
| +// per-host "live" lines and timeout diagnostics name hosts as they |
| +// resolve), and the no-instance path gets nothing: there is no URL to |
| +// name, and its own "promoted, nothing to watch" message stays the |
| +// honest report. |
| +func deployingLine(hosts []string, channel string) string { |
| + switch len(hosts) { |
| + case 0: |
| + return "" |
| + case 1: |
| + return "deploying https://" + hosts[0] |
| + } |
| + return fmt.Sprintf("deploying %d instances on %s", len(hosts), channel) |
| +} |
| + |
| // deployTargets decides what this deploy promotes to and what it then watches, |
| // and reports whether it took the static path. It is a function, not a stretch |
| // of runDeploy, so the decision can be tested without a console: `channels` is |
diff --git a/deploy_e2e_test.go b/deploy_e2e_test.go| index db2f6e6c9..a810f457e 100644 |
| --- a/deploy_e2e_test.go |
| +++ b/deploy_e2e_test.go |
| @@ -199,6 +199,7 @@ func TestE2EStaticDeployThroughConsole(t *testing.T) { |
| "-channel", channel, "-host", host, "-timeout", "30s", siteDir}) |
| out := buf.String() |
| for _, wantLine := range []string{ |
| + "deploying https://" + host, |
| "shipped " + app + " sha0001", |
| "promoted " + app + " sha0001 -> " + channel, |
| "re-checks its channel", |
| @@ -208,6 +209,9 @@ func TestE2EStaticDeployThroughConsole(t *testing.T) { |
| t.Fatalf("static deploy stdout missing %q:\n%s", wantLine, out) |
| } |
| } |
| + if strings.Contains(out, "watching "+host) { |
| + t.Fatalf("static note still repeats the host the deploying line just printed:\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) |
| @@ -409,6 +413,7 @@ func TestE2EDeployThroughConsole(t *testing.T) { |
| // The three lines an operator reads, in order, and the "live" line names |
| // the PUBLIC url — not the loopback origin the watch happened to dial. |
| for _, want := range []string{ |
| + "deploying https://" + host, |
| "shipped " + app + " " + version, |
| "promoted " + app + " " + version + " -> " + channel, |
| "live https://" + host, |
| @@ -420,6 +425,9 @@ func TestE2EDeployThroughConsole(t *testing.T) { |
| if i, j := strings.Index(out, "shipped"), strings.Index(out, "promoted"); i > j { |
| t.Fatalf("promoted printed before shipped:\n%s", out) |
| } |
| + if i, j := strings.Index(out, "deploying https://"), strings.Index(out, "shipped"); i < 0 || i > j { |
| + t.Fatalf("deploying line missing or printed after shipped:\n%s", out) |
| + } |
| if strings.Contains(out, edgeSrv.URL) { |
| t.Fatalf("deploy printed the test's loopback origin instead of the public URL:\n%s", out) |
| } |
diff --git a/deploy_test.go b/deploy_test.go| index c90cffa10..df287c329 100644 |
| --- a/deploy_test.go |
| +++ b/deploy_test.go |
| @@ -398,3 +398,27 @@ func TestWatchVersionCacheBustsEveryPoll(t *testing.T) { |
| t.Fatalf("cache-bust param did not vary between polls: %v", queries) |
| } |
| } |
| + |
| +// TestDeployingLine pins the destination line deploy prints before ship: |
| +// the one place every later path (held, watch, even a ship failure) gets |
| +// its "where is this going" from. Zero hosts is the no-instance path, |
| +// which keeps its own honest "nothing to watch" message instead. |
| +func TestDeployingLine(t *testing.T) { |
| + cases := []struct { |
| + name string |
| + hosts []string |
| + channel string |
| + want string |
| + }{ |
| + {"no hosts", nil, "stable", ""}, |
| + {"one host", []string{"hello.baf.oncarlos.com"}, "stable", |
| + "deploying https://hello.baf.oncarlos.com"}, |
| + {"fleet", []string{"a.example.com", "b.example.com", "c.example.com"}, "stable", |
| + "deploying 3 instances on stable"}, |
| + } |
| + for _, c := range cases { |
| + if got := deployingLine(c.hosts, c.channel); got != c.want { |
| + t.Errorf("%s: deployingLine = %q, want %q", c.name, got, c.want) |
| + } |
| + } |
| +} |