carlos / platform Public

registry: status_named — the app's own opt-in to the status page

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 33c9ba830e8405d69110a49aadbc05ebd81a206c parent ac36bcd
3 files changed, +32 −7
  • cli.go +2 −1
  • internal/registry/registry.go +9 −6
  • internal/registry/registry_test.go +21 −0
diff --git a/cli.go b/cli.go
index dd602f9c0..3ddc749a9 100644
--- a/cli.go
+++ b/cli.go
@@ -55,6 +55,7 @@ func runAdd(args []string) {
addr := fs.String("addr", "", "TCP upstream host:port (whole-app tenants)")
channel := fs.String("channel", "stable", "release channel to follow")
hibernate := fs.Bool("hibernate", false, "hibernate when idle")
+ statusNamed := fs.Bool("status-named", false, "opt this app into being named on the public status page")
backing := fs.String("backing", "", `process backing: "" (agent exec child) | "unit" (carlos-app@<host> systemd unit)`)
dbPath := fs.String("db", "", "instance database path")
fs.Parse(args)
@@ -89,7 +90,7 @@ func runAdd(args []string) {
}
if err := r.Upsert(registry.Route{
Host: host, Slug: *slug, Kind: *kind, Socket: *socket, Addr: *addr,
- App: *app, Channel: *channel, Hibernate: *hibernate, Backing: *backing,
+ App: *app, Channel: *channel, Hibernate: *hibernate, StatusNamed: *statusNamed, Backing: *backing,
DBPath: *dbPath, Status: "ready",
}); err != nil {
log.Fatal(err)
diff --git a/internal/registry/registry.go b/internal/registry/registry.go
index 6c572811a..ea2994893 100644
--- a/internal/registry/registry.go
+++ b/internal/registry/registry.go
@@ -49,6 +49,7 @@ type Route struct {
Canary bool
Replication string // "" | "stream" | "batched" | "daily"
NextDue int64
+ StatusNamed bool // publish this route's app by name on the status page — the app's own opt-in
// Backing is who owns the instance's process: "" = an exec child of the
// agent (the activator spawns live/<host> itself — the Eleven shape), or
@@ -123,6 +124,7 @@ CREATE TABLE IF NOT EXISTS routes (
`ALTER TABLE routes ADD COLUMN replication TEXT NOT NULL DEFAULT ''`,
`ALTER TABLE routes ADD COLUMN next_due INTEGER NOT NULL DEFAULT 0`,
`ALTER TABLE routes ADD COLUMN backing TEXT NOT NULL DEFAULT ''`,
+ `ALTER TABLE routes ADD COLUMN status_named INTEGER NOT NULL DEFAULT 0`,
} {
if _, err := r.sql.Exec(stmt); err != nil && !strings.Contains(err.Error(), "duplicate column") {
return err
@@ -133,17 +135,18 @@ CREATE TABLE IF NOT EXISTS routes (
const routeCols = `host, slug, kind, socket, addr, status, last_error, db_path,
version, prev_version, app, pool, region, channel, hibernate, canary,
-replication, next_due, backing, created_at`
+replication, next_due, backing, status_named, created_at`
func scanRoute(scan func(...any) error) (Route, error) {
var t Route
- var hib, can int
+ var hib, can, sn int
err := scan(&t.Host, &t.Slug, &t.Kind, &t.Socket, &t.Addr, &t.Status,
&t.LastError, &t.DBPath, &t.Version, &t.PrevVersion, &t.App, &t.Pool,
&t.Region, &t.Channel, &hib, &can, &t.Replication, &t.NextDue, &t.Backing,
- &t.CreatedAt)
+ &sn, &t.CreatedAt)
t.Hibernate = hib != 0
t.Canary = can != 0
+ t.StatusNamed = sn != 0
return t, err
}
@@ -218,7 +221,7 @@ func (r *Registry) Upsert(t Route) error {
}
_, err := r.sql.Exec(`
INSERT INTO routes (`+routeCols+`)
-VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
+VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
ON CONFLICT(host) DO UPDATE SET
slug=excluded.slug, kind=excluded.kind, socket=excluded.socket,
addr=excluded.addr, status=excluded.status, last_error=excluded.last_error,
@@ -227,11 +230,11 @@ ON CONFLICT(host) DO UPDATE SET
region=excluded.region, channel=excluded.channel,
hibernate=excluded.hibernate, canary=excluded.canary,
replication=excluded.replication, next_due=excluded.next_due,
- backing=excluded.backing`,
+ backing=excluded.backing, status_named=excluded.status_named`,
t.Host, t.Slug, t.Kind, t.Socket, t.Addr, t.Status, t.LastError,
t.DBPath, t.Version, t.PrevVersion, t.App, t.Pool, t.Region, t.Channel,
b2i(t.Hibernate), b2i(t.Canary), t.Replication, t.NextDue, t.Backing,
- t.CreatedAt)
+ b2i(t.StatusNamed), t.CreatedAt)
return err
}
diff --git a/internal/registry/registry_test.go b/internal/registry/registry_test.go
index 3ac555555..07ab272f4 100644
--- a/internal/registry/registry_test.go
+++ b/internal/registry/registry_test.go
@@ -196,3 +196,24 @@ func TestSetVersionsAndHibernateAreNarrow(t *testing.T) {
t.Fatalf("narrow update clobbered unrelated columns: %+v", got)
}
}
+
+func TestStatusNamedRoundTrip(t *testing.T) {
+ reg, err := Open(filepath.Join(t.TempDir(), "registry.db"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ r := Route{Host: "kass.training", Kind: "instance", App: "kass", StatusNamed: true}
+ if err := reg.Upsert(r); err != nil {
+ t.Fatal(err)
+ }
+ got, err := reg.Get("kass.training")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got == nil {
+ t.Fatal("Get returned nil for an upserted route")
+ }
+ if !got.StatusNamed {
+ t.Error("status_named did not round-trip")
+ }
+}