Installation sets up the builder, so a raised mesh can produce
Genesis ended with a mesh that runs and cannot make anything: every module in the catalogue names artifacts and nothing had built them, so the first thing anybody had to do was install a builder by hand. The installer already carries one — it is what built the control plane — so this is the same two acts the control plane goes through, in the same order: publish it, so the mesh names it by a digest its own registry assigned rather than 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, issued before the push so it arrives with the declaration rather than after it. Verified on a bare machine: the install ends with a builder running, and that mesh then built the shared base images and a module on top of them with nobody helping it.
This commit is contained in:
@@ -52,6 +52,7 @@ const (
|
||||
StepPublish Step = "publish"
|
||||
StepControlPlane Step = "control-plane"
|
||||
StepRetire Step = "retire"
|
||||
StepBuilder Step = "builder"
|
||||
)
|
||||
|
||||
// Steps in the order they happen, so a failure can say "step 2 of 11".
|
||||
@@ -67,7 +68,7 @@ const (
|
||||
// mesh made, out of a repository and a commit it can name, and can therefore make again.
|
||||
var Steps = []Step{
|
||||
StepPreflight, StepLoad, StepBuild, StepBundle, StepApply, StepVerify,
|
||||
StepEnrol, StepRegistry, StepPublish, StepControlPlane, StepRetire,
|
||||
StepEnrol, StepRegistry, StepPublish, StepControlPlane, StepRetire, StepBuilder,
|
||||
}
|
||||
|
||||
// Error is a failure, named by the step it happened in.
|
||||
@@ -182,6 +183,12 @@ type Result struct {
|
||||
ImageTags []string `json:"image-tags,omitempty"`
|
||||
// ImageHeld is true when the machine already held it and nothing was loaded.
|
||||
ImageHeld bool `json:"image-already-held,omitempty"`
|
||||
// BuilderPublished is where the builder's image ended up, and BuilderInstalled whether it is
|
||||
// a module on this machine. A mesh without them runs and cannot produce.
|
||||
BuilderPublished string `json:"builder-image,omitempty"`
|
||||
BuilderInstalled bool `json:"builder-installed,omitempty"`
|
||||
BuilderAccount bool `json:"builder-account-issued,omitempty"`
|
||||
|
||||
// Built is what the genesis build produced, and BuiltFrom is the commit it actually built.
|
||||
//
|
||||
// Reported because they are the difference between a mesh that can rebuild its control plane
|
||||
@@ -500,7 +507,7 @@ func Run(ctx context.Context, o Options, d Deps, say func(string)) (Result, erro
|
||||
return result, failed(StepControlPlane, err)
|
||||
}
|
||||
|
||||
// ---- 10. retire -----------------------------------------------------------------------
|
||||
// ---- 11. retire -----------------------------------------------------------------------
|
||||
say("retire — the temporary control plane is dropped from the bundle")
|
||||
retired, err := RetireTheTemporaryControlPlane(ctx, o, sys, rewritten.Bundle, d.Run, say)
|
||||
result.TemporaryRetired = retired.Gone || retired.Already
|
||||
@@ -509,8 +516,28 @@ func Run(ctx context.Context, o Options, d Deps, say func(string)) (Result, erro
|
||||
return result, failed(StepRetire, err)
|
||||
}
|
||||
|
||||
// ---- 12. builder ----------------------------------------------------------------------
|
||||
//
|
||||
// **Last, and part of installing rather than after it.** What the steps above produce is a mesh
|
||||
// that runs and cannot make anything — every module in the catalogue names artifacts and
|
||||
// nothing has built them. The builder is carried in this installer, because it is what built
|
||||
// the control plane; putting it in the registry and installing it as a module is what turns a
|
||||
// mesh that runs into a mesh that can produce.
|
||||
say("builder — the mesh gets the thing that makes everything else")
|
||||
// The PERMANENT control plane, not the temporary one: by here the temporary is gone, and the
|
||||
// module this installs is assigned through the thing that will still be running afterwards.
|
||||
permanentControl := controlPlane{container: ControlPlaneModule, run: d.Run, timeout: o.Timeout}
|
||||
builder, err := InstallBuilder(ctx, o, d, permanentControl, loaded.ID, say)
|
||||
result.BuilderPublished = builder.Published.Reference
|
||||
result.BuilderInstalled = builder.Installed.Assigned || builder.Installed.Known
|
||||
result.BuilderAccount = builder.Account
|
||||
if err != nil {
|
||||
return result, failed(StepBuilder, err)
|
||||
}
|
||||
|
||||
say("\nthis machine is a mesh of one node, and the control plane it runs is a module " +
|
||||
"pinned to an image its own registry serves.")
|
||||
say("it holds a builder, so it can make the rest of the catalogue rather than be handed it.")
|
||||
say("what remains is somebody else's: adding nodes, and assigning what they should run.")
|
||||
|
||||
return result, nil
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
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 := pinImage(manifest, published.Reference)
|
||||
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
|
||||
}
|
||||
@@ -57,7 +57,19 @@ type Published struct {
|
||||
func PublishControlPlane(ctx context.Context, o Options, d Deps, imageID string,
|
||||
say func(string)) (Published, error) {
|
||||
|
||||
remote := o.Registry + "/" + ControlPlaneRepository
|
||||
return publishAs(ctx, o, d, imageID, ControlPlaneRepository, say)
|
||||
}
|
||||
|
||||
// publishAs puts one locally held image into this mesh's registry, under a repository name.
|
||||
//
|
||||
// **The same act for every image genesis has to place**, which is now two: the control plane it
|
||||
// built, and the builder it carried. They arrive differently and are published identically — the
|
||||
// registry does not care where an image came from, and a second copy of this that drifted would be
|
||||
// the kind of difference nobody finds until one of them stops working.
|
||||
func publishAs(ctx context.Context, o Options, d Deps, imageID, repository string,
|
||||
say func(string)) (Published, error) {
|
||||
|
||||
remote := o.Registry + "/" + repository
|
||||
out := Published{Tagged: remote + ":" + genesisTag}
|
||||
|
||||
// Asked first. A digest already served is a fact about the registry, and re-pushing an image
|
||||
@@ -72,7 +84,7 @@ func PublishControlPlane(ctx context.Context, o Options, d Deps, imageID string,
|
||||
}
|
||||
|
||||
if _, err := d.Run(ctx, "docker", "tag", imageID, out.Tagged); err != nil {
|
||||
return out, fmt.Errorf("cannot tag the carried image as %s: %w", out.Tagged, err)
|
||||
return out, fmt.Errorf("cannot tag %s as %s: %w", imageID, out.Tagged, err)
|
||||
}
|
||||
if _, err := d.Run(ctx, "docker", "push", out.Tagged); err != nil {
|
||||
return out, fmt.Errorf(
|
||||
@@ -93,9 +105,9 @@ func PublishControlPlane(ctx context.Context, o Options, d Deps, imageID string,
|
||||
if pinned == "" {
|
||||
return out, fmt.Errorf(
|
||||
"%s was pushed and the registry does not serve it.\n"+
|
||||
"The next step names the control plane's module by the digest this was supposed to "+
|
||||
"produce, so there is nothing to name. Check `docker push` and "+
|
||||
"http://%s/v2/%s/tags/list", out.Tagged, o.Registry, ControlPlaneRepository)
|
||||
"The next step names this module by the digest this was supposed to produce, so "+
|
||||
"there is nothing to name. Check `docker push` and "+
|
||||
"http://%s/v2/%s/tags/list", out.Tagged, o.Registry, repository)
|
||||
}
|
||||
out.Reference = pinned
|
||||
say(" published " + pinned)
|
||||
|
||||
Reference in New Issue
Block a user