carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "net/http"
5 "sort"
6)
7
8// The armed-regions endpoint (issue #346): one authoritative answer to
9// "which regions can serve right now", for consumer pickers — titogo's
10// signup picker and move doors consume it through a 60s cache and hide
11// themselves when it is unreachable, so it is never in a request path.
12//
13// GET /api/cli/apps/{account}/{app}/regions
14// {"regions":[{"code":"ie","armed":true},{"code":"uk","armed":true}],
15// "default":"ie"}
16//
17// Codes are CARLOS placement codes, never AWS region strings. armed is
18// EXACTLY the create door's predicate (#329's armedPlacements — a picker
19// built on this can never offer what create would refuse): a box publishes
20// the code, has a dial address, and can host (#110 — see armedPlacements
21// for why reachability alone advertised five regions that served 502).
22// The home region appears under its NAME
23// (Config.HomeRegion) with armed true — create accepts it by normalizing
24// the name to the record's "" — and is also the default. A deployment with
25// no named home and no placement-publishing boxes answers empty lists, and
26// consumers hide their pickers.
27//
28// Nearest-region additions (§9.3): each entry may carry aws_region and
29// name, and the answer carries app_default (regiondefault.go). regions and
30// default are unchanged for every older reader.
31//
32// The instance family's gate, deliberately: the answer is deployment
33// topology (which regions exist), the same information the create door's
34// refusal message already hands the same bearer.
35func (s *Server) handleAPIRegions(w http.ResponseWriter, r *http.Request) {
36 _, acct, app, ok := s.instanceGate(w, r)
37 if !ok {
38 return
39 }
40 type region struct {
41 Code string `json:"code"`
42 Armed bool `json:"armed"`
43 AWSRegion string `json:"aws_region,omitempty"`
44 Name string `json:"name,omitempty"`
45 }
46 set, _ := armedPlacements(r.Context(), s.cfg.Releases)
47 cands := s.candsOrHome(r.Context())
48 named := func(code string) region {
49 c, _ := findCandidate(cands, s.homeRegionIn(code))
50 return region{Code: code, AWSRegion: c.AWSRegion, Name: s.placeOf(s.homeRegionIn(code), cands)}
51 }
52 out := make([]region, 0, len(set)+1)
53 if s.cfg.HomeRegion != "" {
54 h := named(s.cfg.HomeRegion)
55 h.Armed = true
56 out = append(out, h)
57 // A box that publishes the home's code explicitly (a future coded
58 // home) must not appear twice.
59 delete(set, s.cfg.HomeRegion)
60 }
61 for code, armed := range set {
62 e := named(code)
63 e.Armed = armed
64 out = append(out, e)
65 }
66 sort.Slice(out, func(i, j int) bool { return out[i].Code < out[j].Code })
67 // regions and default are what older clients and titogo read; they stay
68 // as they were. app_default is the nearest-region addition (§9.3).
69 writeJSON(w, http.StatusOK, map[string]any{
70 "regions": out, "default": s.cfg.HomeRegion,
71 "app_default": s.appDefault(r.Context(), acct, app),
72 })
73}
74