carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3// `carlos apps build-probe` — set, on an app that already exists, the path
4// `carlos deploy` polls to learn which build is ACTUALLY running.
5//
6// The probe already existed and could only be stamped at claim time:
7// SetAppBuildProbe had exactly one caller, the create door. So every app made
8// before the flag existed, and every app whose author did not think to pass
9// it, was permanently stuck on header-watching — the X-Carlos-Version header
10// the edge stamps from the route row the moment adoption moves a symlink,
11// whether or not any process restarted. Which means the fix for "deploy said
12// live over the old binary" was out of reach of exactly the apps that had the
13// problem (discussion #13).
14
15import (
16 "context"
17 "flag"
18 "fmt"
19 "log"
20 "strings"
21)
22
23const buildProbeUsage = `usage: carlos apps build-probe --app <name> </path|-> [--account <sqid|name>] [--account-id <sqid>] [--console <origin>]
24
25 </path> an absolute path, no query, whose response body names the running
26 build — "/healthz" serving "ok abc1234" is the shape. carlos deploy
27 polls it instead of the X-Carlos-Version header, which reports the
28 release adoption INTENDED rather than the build actually serving.
29 - clear the probe, back to watching the header.`
30
31// runAppsBuildProbe stamps the probe path, or clears it.
32//
33// "-" is the clear sentinel, the same one `carlos route --unit` and
34// `--environment` use and for the same reason: "" is a LEGAL value of this
35// field — it means header-watching — so an empty argument cannot be told from
36// an argument nobody meant to pass. The doors that clear say so out loud.
37//
38// The stored value is echoed back and compared rather than assumed, the same
39// discipline `apps create` applies to this exact field. It is what the deploy
40// watch trusts; a write that half-landed would leave a deploy quietly
41// believing a header again, which is the whole bug.
42func runAppsBuildProbe(args []string) {
43 fs := flag.NewFlagSet("apps build-probe", flag.ExitOnError)
44 app := appFlag(fs, "app whose build probe to set")
45 acctRaw := fs.String("account", "", "account (sqid or name) the app belongs to (default: your only account, or the configured/chosen one)")
46 acctIDRaw := fs.String("account-id", "", "account sqid, matched as a sqid ONLY — supersedes --account")
47 consoleFlag := consoleOption(fs, "console to act through (default: the only logged-in one)")
48 fs.Usage = flagUsage(fs, buildProbeUsage)
49 fs.Parse(args)
50 if strings.TrimSpace(*app) == "" || fs.NArg() != 1 {
51 log.Fatal(buildProbeUsage)
52 }
53 if err := validAppName(strings.TrimSpace(*app)); err != nil {
54 log.Fatalf("apps build-probe: %v", err)
55 }
56 probe := strings.TrimSpace(fs.Arg(0))
57 clearProbe := probe == "-"
58 if clearProbe {
59 probe = ""
60 } else {
61 if probe == "" {
62 log.Fatal(`apps build-probe: an empty probe is refused — pass "-" to clear it back to header-watching, or name the path`)
63 }
64 // Checked here, before the call, the same client-side grammar gate
65 // `apps create` applies: a shape the server is certain to refuse
66 // should cost no round trip and reach nothing.
67 if err := validBuildProbe(probe); err != nil {
68 log.Fatalf("apps build-probe: %v", err)
69 }
70 }
71
72 sess, err := resolveMode(*consoleFlag)
73 if err != nil {
74 log.Fatal(err)
75 }
76 if sess == nil {
77 log.Fatal("apps build-probe works through a console, and this shell is in direct bucket mode\n" +
78 "(CARLOS_DEPLOYMENT_BUCKET/CARLOS_DEPLOYMENT_DIR is set). The probe is console-tracked\n" +
79 "state on the app row: log in instead — carlos auth login --console <url>")
80 }
81 acct := resolvedAccount(sess, acctRaw, acctIDRaw)
82 got, err := sess.setBuildProbe(context.Background(), acct, strings.TrimSpace(*app), probe)
83 if err != nil {
84 log.Fatalf("apps build-probe: %v", err)
85 }
86 if got != probe {
87 log.Fatalf("apps build-probe: did not stick (console echoed %q, want %q) — the probe is what "+
88 "carlos deploy trusts, so a write that half-landed is worse than none", got, probe)
89 }
90 if clearProbe {
91 fmt.Fprintf(stdout, "cleared the build probe for %s — carlos deploy watches the X-Carlos-Version header again\n", strings.TrimSpace(*app))
92 fmt.Fprintf(stdout, " that header is stamped by the edge from the route row, so it reports the release adoption intended, not the build serving\n")
93 return
94 }
95 fmt.Fprintf(stdout, "build probe for %s: %s\n", strings.TrimSpace(*app), got)
96 fmt.Fprintf(stdout, " carlos deploy now asks the app's own process which build it is running, instead of trusting the router's header\n")
97}
98