package bootstrap import ( "context" "fmt" "strings" ) // BuilderModule is the module that lets a mesh produce anything at all. const BuilderModule = "builder" // BuilderRepository is what its image is called in this mesh's own registry. const BuilderRepository = "mesh-builder" // Builder is what installing it produced. type Builder struct { Published Published Installed Installed // Account is true when a broker account was issued for it here. Account bool } // InstallBuilder gives a fresh mesh the thing that makes everything else. // // **Without this a mesh can run and cannot produce** (novox/hq ADR 0073). Genesis ends with a // control plane, a store, a queue and a registry — and almost every module in the catalogue is // waiting to be built, because a manifest names artifacts and nothing has made them. The builder is // one of the few things that cannot be built by the thing it is, so it is carried; and it is // already here, because it is what built the control plane. // // So this is the same two acts the control plane went through, in the same order and for the same // reason: publish the image so the mesh names it by a digest its own registry assigned rather than // by a local identity nothing else can fetch, then install it as an ordinary module pinned to that. // // And then the part only it needs: a broker account. A builder takes work from a queue and // announces what it made, and it holds its own credential for that like any module — asking for a // generic one produced an account that could do neither, which is what made this worth its own step // rather than a line in another. func InstallBuilder(ctx context.Context, o Options, d Deps, control controlPlane, imageID string, say func(string)) (Builder, error) { var out Builder published, err := publishAs(ctx, o, d, imageID, BuilderRepository, say) out.Published = published if err != nil { return out, err } manifest, err := readManifest(o.Catalogue, BuilderModule) if err != nil { return out, fmt.Errorf("%w\n"+ "This is the manifest that makes the builder an ordinary module. Without it the mesh "+ "has the image and no way to run it, so nothing can be built here", err) } pinned, places, err := pinPlaceholder(manifest, published.Reference, BuilderModule) if err != nil { return out, err } say(fmt.Sprintf(" pinned to %s, named in %d place(s)", published.Reference, places)) installed, err := registerAndAssign(ctx, o, control, BuilderModule, pinned, say) out.Installed = installed if err != nil { return out, err } // Before the push, so the account is in the declaration the machine receives rather than in // the one after it. A builder that arrives without its credential starts, finds nothing it may // read, and waits — which looks exactly like a builder with no work. account := o.Node + "-" + BuilderModule if _, err := control.tell(ctx, "builder", "issue", account, "--node", o.Node); err != nil { // Said and carried on. An account that already exists is the ordinary case on a re-run, // and the push below is what makes either state true on the machine. if !strings.Contains(err.Error(), "already") { return out, fmt.Errorf("the builder has no broker account, so it can take no work: %w", err) } say(" broker account " + account + " — already issued") } else { out.Account = true say(" broker account " + account + ", scoped to what it consumes and emits") } out.Installed.Pushed, err = pushNode(ctx, o, control, say) return out, err }