oficina / home Public

The drain's provisioner: carlos, and the credential it holds

Creates the instance and attaches the team's hostname, through the carlos
CLI rather than a second implementation of a moving console API.

The service credential reaches the child through its environment and never
through an argument, so it is not readable from a process listing; a test
asserts both halves of that. The child's environment is otherwise minimal —
credential, HOME, PATH — so nothing else this process holds leaks into it.

Both steps treat "already exists" as success, because Run requires
Provision to be idempotent: the drain can crash between building an
instance and recording it, and the retry has to converge on the end state
rather than fail for ever on work already done. For the same reason the
consumer id is derived from (label, app) rather than random — a retry that
minted a fresh one would leave the directory naming one identity while the
instance held another.

Labels and app names are checked to be plain before they reach a command
line.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 2beb423e86ea41250f0883658e14510c214170ba parent 8d5a748
3 files changed, +269 −0
  • internal/drain/carlos.go +112 −0
  • internal/drain/carlos_test.go +142 −0
  • internal/drain/carlosenv.go +15 −0
diff --git a/internal/drain/carlos.go b/internal/drain/carlos.go
new file mode 100644
index 0000000..f7fde14
--- /dev/null
+++ b/internal/drain/carlos.go
@@ -0,0 +1,112 @@
+package drain
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "os/exec"
+ "strings"
+)
+
+// Carlos provisions through the carlos console API, holding the service
+// credential that authorises it. Nothing else in this repository holds one.
+//
+// It shells out to the carlos CLI rather than reimplementing the console
+// API. The CLI is the supported surface, it already handles console
+// selection, retries and the account resolution this would otherwise have to
+// copy, and a wire client here would be a second implementation to keep in
+// step with a moving API for no gain.
+type Carlos struct {
+ // Account is the carlos account sqid or name the instances belong to.
+ Account string
+ // Token is the service credential (CARLOS_SERVICE_TOKEN). Role
+ // `instance`, which is what POST .../instances admits. It is passed to
+ // the child through its environment and never appears in an argument,
+ // so it cannot be read out of a process listing.
+ Token string
+ // Domain is the suffix an instance host is created under, e.g.
+ // "bds.oncarlos.com".
+ Domain string
+ // Suite is the public suffix a team's instance is reached at, e.g.
+ // "oficina.works", giving <label>.<app>.<suite>.
+ Suite string
+ // run executes a carlos invocation. Injectable so the command this
+ // builds can be tested without creating real infrastructure.
+ run func(ctx context.Context, env []string, args ...string) ([]byte, error)
+}
+
+// NewCarlos refuses an incomplete configuration rather than discovering it
+// at the first provision, when a half-built instance is already the outcome.
+func NewCarlos(account, token, domain, suite string) (*Carlos, error) {
+ if account == "" || token == "" || domain == "" || suite == "" {
+ return nil, errors.New("drain: carlos needs an account, a token, a domain and a suite")
+ }
+ return &Carlos{Account: account, Token: token, Domain: domain, Suite: suite, run: runCarlos}, nil
+}
+
+func runCarlos(ctx context.Context, env []string, args ...string) ([]byte, error) {
+ cmd := exec.CommandContext(ctx, "carlos", args...)
+ cmd.Env = env
+ return cmd.CombinedOutput()
+}
+
+// Provision creates the instance for one (label, app) and attaches the
+// team's hostname to it.
+//
+// It is idempotent, which Run requires: the drain can crash between building
+// and recording, and the retry must converge rather than build a second
+// instance. Both steps treat "already exists" as success, because the state
+// the caller wants is the state that already holds.
+func (c *Carlos) Provision(ctx context.Context, label, app string) (string, string, error) {
+ if !plainLabel(label) || !plainLabel(app) {
+ return "", "", errors.New("drain: refusing a label or app that is not plain")
+ }
+ host := label + "-" + app + "." + c.Domain
+ origin := "https://" + label + "." + app + "." + c.Suite
+ env := []string{"CARLOS_SERVICE_TOKEN=" + c.Token, "HOME=" + homeForCarlos(), "PATH=" + pathForCarlos()}
+
+ if out, err := c.run(ctx, env, "instances", "create", "--app", app, "--account", c.Account, "--host", host); err != nil {
+ if !alreadyExists(out) {
+ return "", "", fmt.Errorf("drain: creating %s: %w: %s", host, err, trim(out))
+ }
+ }
+ if out, err := c.run(ctx, env, "domains", "attach", "--app", app, "--account", c.Account, "--route", host, label+"."+app+"."+c.Suite); err != nil {
+ if !alreadyExists(out) {
+ return "", "", fmt.Errorf("drain: attaching %s: %w: %s", origin, err, trim(out))
+ }
+ }
+ // The consumer id names the instance to the home. It is derived rather
+ // than random so a retry after a lost recording produces the same value
+ // and converges, instead of leaving the directory with one id and the
+ // instance holding another.
+ return origin, label + "-" + app, nil
+}
+
+// alreadyExists recognises the console refusing work that is already done.
+// Treated as success because Provision's contract is the end state, not the
+// transition — a retry that failed here would never converge.
+func alreadyExists(out []byte) bool {
+ s := strings.ToLower(string(out))
+ return strings.Contains(s, "already exists") || strings.Contains(s, "already attached")
+}
+
+func plainLabel(s string) bool {
+ if len(s) == 0 || len(s) > 63 || s[0] == '-' || s[len(s)-1] == '-' {
+ return false
+ }
+ for i := 0; i < len(s); i++ {
+ c := s[i]
+ if !(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-') {
+ return false
+ }
+ }
+ return true
+}
+
+func trim(out []byte) string {
+ s := strings.TrimSpace(string(out))
+ if len(s) > 300 {
+ return s[:300] + "…"
+ }
+ return s
+}
diff --git a/internal/drain/carlos_test.go b/internal/drain/carlos_test.go
new file mode 100644
index 0000000..bffe741
--- /dev/null
+++ b/internal/drain/carlos_test.go
@@ -0,0 +1,142 @@
+package drain
+
+import (
+ "context"
+ "errors"
+ "strings"
+ "testing"
+)
+
+type recorded struct {
+ args []string
+ env []string
+}
+
+func fakeCarlos(t *testing.T, results map[string]error, out map[string]string) (*Carlos, *[]recorded) {
+ t.Helper()
+ c, err := NewCarlos("bds", "svc-secret-token", "bds.oncarlos.com", "oficina.works")
+ if err != nil {
+ t.Fatal(err)
+ }
+ var calls []recorded
+ c.run = func(_ context.Context, env []string, args ...string) ([]byte, error) {
+ calls = append(calls, recorded{args: args, env: env})
+ key := args[0] + " " + args[1]
+ return []byte(out[key]), results[key]
+ }
+ return c, &calls
+}
+
+func TestProvisionCreatesTheInstanceAndAttachesTheHostname(t *testing.T) {
+ c, calls := fakeCarlos(t, nil, nil)
+ origin, consumer, err := c.Provision(context.Background(), "acme", "docs")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if origin != "https://acme.docs.oficina.works" || consumer != "acme-docs" {
+ t.Fatalf("origin %q consumer %q", origin, consumer)
+ }
+ if len(*calls) != 2 {
+ t.Fatalf("calls %v", *calls)
+ }
+ create := strings.Join((*calls)[0].args, " ")
+ if create != "instances create --app docs --account bds --host acme-docs.bds.oncarlos.com" {
+ t.Fatalf("create: %s", create)
+ }
+ attach := strings.Join((*calls)[1].args, " ")
+ if attach != "domains attach --app docs --account bds --route acme-docs.bds.oncarlos.com acme.docs.oficina.works" {
+ t.Fatalf("attach: %s", attach)
+ }
+}
+
+// The credential must reach the child through its environment and never
+// through an argument, or it is readable in a process listing by anyone on
+// the box.
+func TestTheServiceTokenIsNeverAnArgument(t *testing.T) {
+ c, calls := fakeCarlos(t, nil, nil)
+ if _, _, err := c.Provision(context.Background(), "acme", "docs"); err != nil {
+ t.Fatal(err)
+ }
+ for _, call := range *calls {
+ for _, arg := range call.args {
+ if strings.Contains(arg, "svc-secret-token") {
+ t.Fatalf("the service token appeared in an argument: %v", call.args)
+ }
+ }
+ var carried bool
+ for _, e := range call.env {
+ if e == "CARLOS_SERVICE_TOKEN=svc-secret-token" {
+ carried = true
+ }
+ }
+ if !carried {
+ t.Fatalf("the service token did not reach the child: %v", call.env)
+ }
+ }
+}
+
+// Run requires Provision to be idempotent: the drain can crash between
+// building and recording, and the retry has to converge rather than fail
+// for ever on work that is already done.
+func TestProvisionTreatsExistingInfrastructureAsSuccess(t *testing.T) {
+ c, _ := fakeCarlos(t,
+ map[string]error{"instances create": errors.New("exit 1"), "domains attach": errors.New("exit 1")},
+ map[string]string{"instances create": "instance already exists", "domains attach": "hostname already attached"})
+ origin, consumer, err := c.Provision(context.Background(), "acme", "docs")
+ if err != nil {
+ t.Fatalf("a retry over existing infrastructure failed: %v", err)
+ }
+ if origin != "https://acme.docs.oficina.works" || consumer != "acme-docs" {
+ t.Fatalf("origin %q consumer %q", origin, consumer)
+ }
+}
+
+// And a retry must name the same consumer as the first attempt, or the
+// directory records one identity while the instance holds another.
+func TestTheConsumerIdIsStableAcrossRetries(t *testing.T) {
+ c, _ := fakeCarlos(t, nil, nil)
+ _, first, err := c.Provision(context.Background(), "acme", "docs")
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, second, err := c.Provision(context.Background(), "acme", "docs")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if first != second {
+ t.Fatalf("consumer id changed between attempts: %q then %q", first, second)
+ }
+}
+
+func TestProvisionRefusesAnythingNotPlain(t *testing.T) {
+ c, calls := fakeCarlos(t, nil, nil)
+ for _, bad := range []string{"", "-acme", "acme-", "ACME", "a b", "a/b", "../etc", "a.b", strings.Repeat("a", 64)} {
+ if _, _, err := c.Provision(context.Background(), bad, "docs"); err == nil {
+ t.Fatalf("label %q was accepted", bad)
+ }
+ if _, _, err := c.Provision(context.Background(), "acme", bad); err == nil {
+ t.Fatalf("app %q was accepted", bad)
+ }
+ }
+ if len(*calls) != 0 {
+ t.Fatalf("a refused label still invoked carlos: %v", *calls)
+ }
+}
+
+// A real failure must not be reported as a provisioned instance.
+func TestAGenuineFailureIsAnError(t *testing.T) {
+ c, _ := fakeCarlos(t,
+ map[string]error{"instances create": errors.New("exit 1")},
+ map[string]string{"instances create": "instances are not enabled for docs"})
+ if _, _, err := c.Provision(context.Background(), "acme", "docs"); err == nil {
+ t.Fatal("a refused create was reported as success")
+ }
+}
+
+func TestNewCarlosRefusesAnIncompleteConfiguration(t *testing.T) {
+ for _, c := range [][4]string{{"", "t", "d", "s"}, {"a", "", "d", "s"}, {"a", "t", "", "s"}, {"a", "t", "d", ""}} {
+ if _, err := NewCarlos(c[0], c[1], c[2], c[3]); err == nil {
+ t.Fatalf("accepted %v", c)
+ }
+ }
+}
diff --git a/internal/drain/carlosenv.go b/internal/drain/carlosenv.go
new file mode 100644
index 0000000..cc01c97
--- /dev/null
+++ b/internal/drain/carlosenv.go
@@ -0,0 +1,15 @@
+package drain
+
+import "os"
+
+// The child gets a deliberately minimal environment: the service credential,
+// a HOME so the CLI can find its console configuration, and a PATH so it can
+// be found at all. Everything else the drain's own process holds — other
+// credentials included — stays out of it.
+func homeForCarlos() string { return os.Getenv("HOME") }
+func pathForCarlos() string {
+ if p := os.Getenv("PATH"); p != "" {
+ return p
+ }
+ return "/usr/local/bin:/usr/bin:/bin"
+}