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:
@@ -47,7 +47,7 @@ const (
|
|||||||
|
|
||||||
const usage = `mesh-bootstrap — make a bare machine into a mesh
|
const usage = `mesh-bootstrap — make a bare machine into a mesh
|
||||||
|
|
||||||
bootstrap the ten steps below (the default)
|
bootstrap the twelve steps below (the default)
|
||||||
version
|
version
|
||||||
|
|
||||||
1 preflight what has to be true before anything is changed
|
1 preflight what has to be true before anything is changed
|
||||||
@@ -61,6 +61,8 @@ const usage = `mesh-bootstrap — make a bare machine into a mesh
|
|||||||
9 publish push the control plane's image into it, for its first digest
|
9 publish push the control plane's image into it, for its first digest
|
||||||
10 control reinstall the control plane as an ordinary module, pinned to that digest
|
10 control reinstall the control plane as an ordinary module, pinned to that digest
|
||||||
11 retire drop the temporary control plane; the host removes it
|
11 retire drop the temporary control plane; the host removes it
|
||||||
|
12 builder publish the carried builder and install it, so this mesh can
|
||||||
|
make the rest of the catalogue rather than be handed it
|
||||||
|
|
||||||
--bundle the substrate template to build this machine's bundle from
|
--bundle the substrate template to build this machine's bundle from
|
||||||
(default ` + defaultTemplate + `)
|
(default ` + defaultTemplate + `)
|
||||||
@@ -74,8 +76,9 @@ const usage = `mesh-bootstrap — make a bare machine into a mesh
|
|||||||
controls, and what is cloned here is the trust anchor for
|
controls, and what is cloned here is the trust anchor for
|
||||||
everything this mesh will ever run
|
everything this mesh will ever run
|
||||||
--source-path the module's directory inside that repository, if not its root
|
--source-path the module's directory inside that repository, if not its root
|
||||||
--catalog a checkout of the mesh's catalogue, holding the registry's and the
|
--catalog a checkout of the mesh's catalogue, holding the registry's, the
|
||||||
control plane's manifests. Without it this stops after step 6
|
control plane's and the builder's manifests. Without it this stops
|
||||||
|
after step 6
|
||||||
--node the name this machine is known by (default: its hostname)
|
--node the name this machine is known by (default: its hostname)
|
||||||
--registry where this mesh keeps its own images (default ` + defaultRegistry + `)
|
--registry where this mesh keeps its own images (default ` + defaultRegistry + `)
|
||||||
every node pulls the control plane from this, so on a mesh of more
|
every node pulls the control plane from this, so on a mesh of more
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ const (
|
|||||||
StepPublish Step = "publish"
|
StepPublish Step = "publish"
|
||||||
StepControlPlane Step = "control-plane"
|
StepControlPlane Step = "control-plane"
|
||||||
StepRetire Step = "retire"
|
StepRetire Step = "retire"
|
||||||
|
StepBuilder Step = "builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Steps in the order they happen, so a failure can say "step 2 of 11".
|
// 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.
|
// mesh made, out of a repository and a commit it can name, and can therefore make again.
|
||||||
var Steps = []Step{
|
var Steps = []Step{
|
||||||
StepPreflight, StepLoad, StepBuild, StepBundle, StepApply, StepVerify,
|
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.
|
// Error is a failure, named by the step it happened in.
|
||||||
@@ -182,6 +183,12 @@ type Result struct {
|
|||||||
ImageTags []string `json:"image-tags,omitempty"`
|
ImageTags []string `json:"image-tags,omitempty"`
|
||||||
// ImageHeld is true when the machine already held it and nothing was loaded.
|
// ImageHeld is true when the machine already held it and nothing was loaded.
|
||||||
ImageHeld bool `json:"image-already-held,omitempty"`
|
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.
|
// 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
|
// 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)
|
return result, failed(StepControlPlane, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 10. retire -----------------------------------------------------------------------
|
// ---- 11. retire -----------------------------------------------------------------------
|
||||||
say("retire — the temporary control plane is dropped from the bundle")
|
say("retire — the temporary control plane is dropped from the bundle")
|
||||||
retired, err := RetireTheTemporaryControlPlane(ctx, o, sys, rewritten.Bundle, d.Run, say)
|
retired, err := RetireTheTemporaryControlPlane(ctx, o, sys, rewritten.Bundle, d.Run, say)
|
||||||
result.TemporaryRetired = retired.Gone || retired.Already
|
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)
|
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 " +
|
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.")
|
"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.")
|
say("what remains is somebody else's: adding nodes, and assigning what they should run.")
|
||||||
|
|
||||||
return result, nil
|
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,
|
func PublishControlPlane(ctx context.Context, o Options, d Deps, imageID string,
|
||||||
say func(string)) (Published, error) {
|
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}
|
out := Published{Tagged: remote + ":" + genesisTag}
|
||||||
|
|
||||||
// Asked first. A digest already served is a fact about the registry, and re-pushing an image
|
// 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 {
|
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 {
|
if _, err := d.Run(ctx, "docker", "push", out.Tagged); err != nil {
|
||||||
return out, fmt.Errorf(
|
return out, fmt.Errorf(
|
||||||
@@ -93,9 +105,9 @@ func PublishControlPlane(ctx context.Context, o Options, d Deps, imageID string,
|
|||||||
if pinned == "" {
|
if pinned == "" {
|
||||||
return out, fmt.Errorf(
|
return out, fmt.Errorf(
|
||||||
"%s was pushed and the registry does not serve it.\n"+
|
"%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 "+
|
"The next step names this module by the digest this was supposed to produce, so "+
|
||||||
"produce, so there is nothing to name. Check `docker push` and "+
|
"there is nothing to name. Check `docker push` and "+
|
||||||
"http://%s/v2/%s/tags/list", out.Tagged, o.Registry, ControlPlaneRepository)
|
"http://%s/v2/%s/tags/list", out.Tagged, o.Registry, repository)
|
||||||
}
|
}
|
||||||
out.Reference = pinned
|
out.Reference = pinned
|
||||||
say(" published " + pinned)
|
say(" published " + pinned)
|
||||||
|
|||||||
Reference in New Issue
Block a user