REG1: distribution checks a publish against the builder's reservation, asked by the artifact's name
The container image and OCI artifact seats' holder asks the seat mesh-builds' verb reserved in place of builder_versions; the manifest invokes it and takes no builder package. Builds: design 47 G §381, §478, §504 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -12,7 +12,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -520,7 +519,7 @@ func tools(v *holder.Verbs, windows kept[holder.Window], callers func() (holder.
|
||||
func main() {
|
||||
base, _ := url.Parse("http://127.0.0.1:5001")
|
||||
store := &holder.OCI{Base: base, Upstream: &holder.Upstream{}}
|
||||
h := &holder.Holder{Objects: parts.BusObjects(holder.IncomingObjects), Builder: builds.BuilderVersions{Ask: stdio.Ask}, Store: store, Say: say, Finding: finding,
|
||||
h := &holder.Holder{Objects: parts.BusObjects(holder.IncomingObjects), Builder: holder.Reserved{Ask: stdio.Ask}, Store: store, Say: say, Finding: finding,
|
||||
Transfers: parts.BusTransfers[holder.Opening](holder.TransfersState), Cap: parts.BusCap(holder.IncomingObjects), Record: recordAct, Idle: parts.BusArm(holder.IdleState)}
|
||||
files := &holder.Files{Container: or(os.Getenv("MESH_REGISTRY_CONTAINER"), "mesh-registry"), Run: holder.ExecRunner, UID: os.Getuid()}
|
||||
v := &holder.Verbs{Holder: h, Catalog: store, Holds: kept[holder.Hold]{state: "holds", key: holder.Hold.Key}, Mirrors: mirrors{},
|
||||
|
||||
@@ -8,7 +8,7 @@ require (
|
||||
git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-sdk/go v0.0.0-00010101000000-000000000000
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// `oci-artifact-registry`, and of the old seat `mesh-artifact-store` until its window closes (novox/hq design 51 *The
|
||||
// four registry seats*, ADR 0605, ADR 0611, ADR 0617 §2). A build module publishes into a seat through `publish` and
|
||||
// `publish-part`: the parts land in distribution's own object store `incoming`, are hashed, checked against the
|
||||
// builder's `builder_versions` and stored in the artifact store, which listens on its node's loopback; the store's port
|
||||
// builder's reservation (`reserved`) and stored in the artifact store, which listens on its node's loopback; the store's port
|
||||
// is the program's, reads open, writes from its own process alone (front.go).
|
||||
package holder
|
||||
|
||||
|
||||
@@ -286,12 +286,12 @@ func TestTheHolderOfTheTwoImageSeats(t *testing.T) {
|
||||
_, err := b.h.Publish(ArtifactSeat, builder, req("t"))
|
||||
return err
|
||||
}, "at their cap"},
|
||||
{"a version the builder did not number", func(b *bed) error {
|
||||
{"a version the builder did not reserve", func(b *bed) error {
|
||||
b.h.Builder = fakeBuilder{ok: false}
|
||||
b.store.has[part.Digest] = true // held already: the publish ends at once
|
||||
_, err := b.h.Publish(ArtifactSeat, builder, req("t"))
|
||||
return err
|
||||
}, "the builder did not number it"},
|
||||
}, "the builder did not reserve it"},
|
||||
{"an image on the artifact seat", func(b *bed) error {
|
||||
r := req("t")
|
||||
r.Kind = KindImage
|
||||
@@ -843,9 +843,9 @@ func TestTheHolderOfTheTwoImageSeats(t *testing.T) {
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// The builder's question is its published package, taken by its series (ADR 0634 §1).
|
||||
if m.Packages["git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"] != "0.1" {
|
||||
t.Errorf("packages %v: want builds of series 0.1", m.Packages)
|
||||
// The reservation is asked through the seat mesh-builds, so the manifest takes no package of the builder (design 47 §381).
|
||||
if len(m.Packages) != 0 {
|
||||
t.Errorf("packages %v: want none", m.Packages)
|
||||
}
|
||||
// E15: the provision artifact-store retires (ADR 0632 §3); the bootstrap keeps it as an alias on port 5100 while
|
||||
// the running node-engines still find the store by it (issue 1966), and nothing else is provided or served.
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package holder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshbuilds"
|
||||
)
|
||||
|
||||
// Reserved is the holder's check of a publish against the builder's reservation (novox/hq design 47 §381, §478, §504): it asks the
|
||||
// seat mesh-builds' verb reserved with the publish's name and version, a package's name in its ecosystem, and accepts the publish
|
||||
// only when that version is the one reserved and an artifact of it has the publish's digest. It reads no catalog and a publish gains
|
||||
// no field. It is the Builder the holder's Check asks.
|
||||
type Reserved struct {
|
||||
// Ask is the runtime's request and reply over the bus (mesh-sdk stdio.Ask).
|
||||
Ask func(address string, body any) (json.RawMessage, error)
|
||||
}
|
||||
|
||||
// Address is the seat verb asked, as the manifest's `invokes` names it.
|
||||
const Address = "seat:" + meshbuilds.Seat + "." + meshbuilds.VerbReserved
|
||||
|
||||
// Recorded answers whether name@version with digest is reserved, and why not when it is not. An answer that cannot be read is an
|
||||
// error, never a yes; a refusal by the builder (a name no entry carries, a version it did not reserve) is a no that names it.
|
||||
func (r Reserved) Recorded(name, version, digest string) (bool, string, error) {
|
||||
raw, err := r.Ask(Address, meshbuilds.ReservedInput{Name: name, Version: version})
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("%s did not answer for %s@%s: %w", Address, name, version, err)
|
||||
}
|
||||
var a meshbuilds.ReservedAnswer
|
||||
if err := json.Unmarshal(raw, &a); err != nil || a.Version == "" {
|
||||
return false, "", fmt.Errorf("%s answered no reservation for %s@%s: %s", Address, name, version, raw)
|
||||
}
|
||||
if a.Version != version {
|
||||
return false, fmt.Sprintf("%s reserved %s for %s, not %s", a.Module, a.Version, name, version), nil
|
||||
}
|
||||
for _, art := range a.Artifacts {
|
||||
if art.Digest == digest {
|
||||
return true, "", nil
|
||||
}
|
||||
}
|
||||
return false, fmt.Sprintf("%s reserved %s@%s for another digest than %s", a.Module, name, version, digest), nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package holder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// A publish is checked against the builder's reservation by the seat mesh-builds' verb reserved, asked with the package's name
|
||||
// (design 47 §381, §478).
|
||||
func TestReservedChecksAPublishAgainstTheReservation(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
name string
|
||||
answer string
|
||||
err error
|
||||
ok bool
|
||||
why string // part of the reason, or of the error
|
||||
}{
|
||||
{name: "the reserved version and digest", answer: `{"module":"mesh-image","version":"0.1.7","artifacts":[{"artifact":"mesh-image:image","digest":"sha256:aa"}]}`, ok: true},
|
||||
{name: "another artifact of it carries the digest", answer: `{"module":"mesh-image","version":"0.1.7","artifacts":[{"artifact":"mesh-image:aux"},{"artifact":"mesh-image:image","digest":"sha256:aa"}]}`, ok: true},
|
||||
{name: "another digest", answer: `{"module":"mesh-image","version":"0.1.7","artifacts":[{"artifact":"mesh-image:image","digest":"sha256:bb"}]}`, why: "another digest than sha256:aa"},
|
||||
{name: "another version reserved", answer: `{"module":"mesh-image","version":"0.1.8","artifacts":[]}`, why: "reserved 0.1.8 for registry/mesh-image, not 0.1.7"},
|
||||
{name: "the builder refused the name", err: errors.New("no entry carries registry/mesh-image"), why: "no entry carries registry/mesh-image"},
|
||||
{name: "an answer that is none", answer: `{"error":"x"}`, why: "answered no reservation"},
|
||||
} {
|
||||
asked := ""
|
||||
r := Reserved{Ask: func(address string, body any) (json.RawMessage, error) {
|
||||
b, _ := json.Marshal(body)
|
||||
asked = address + " " + string(b)
|
||||
return json.RawMessage(c.answer), c.err
|
||||
}}
|
||||
ok, why, err := r.Recorded("registry/mesh-image", "0.1.7", "sha256:aa")
|
||||
if asked != `seat:mesh-builds.reserved {"name":"registry/mesh-image","version":"0.1.7"}` {
|
||||
t.Fatalf("%s: asked %s", c.name, asked)
|
||||
}
|
||||
if c.ok {
|
||||
if !ok || err != nil {
|
||||
t.Errorf("%s: not accepted: %v %v", c.name, why, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ok || !strings.Contains(why+fmtErr(err), c.why) {
|
||||
t.Errorf("%s: ok=%v why=%q err=%v, want %q", c.name, ok, why, err, c.why)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fmtErr(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
return err.Error()
|
||||
}
|
||||
@@ -129,7 +129,7 @@ type Objects = parts.Objects
|
||||
// TransferState is the state transfers.
|
||||
type TransferState = parts.Transfers[Opening]
|
||||
|
||||
// Builder is the builder's tool builder_versions, asked before anything is stored (ADR 0605 §4).
|
||||
// Builder is the builder's reservation (Reserved), asked before anything is stored (ADR 0605 §4, design 47 §381).
|
||||
type Builder interface {
|
||||
Recorded(name, version, digest string) (ok bool, why string, err error)
|
||||
}
|
||||
@@ -196,17 +196,17 @@ func (h *Holder) receiver() *parts.Receiver[Opening] {
|
||||
ok, err := h.Store.Has(t.Shape.Name, digest)
|
||||
return err == nil && ok
|
||||
},
|
||||
// builder_versions is asked before anything is stored (ADR 0605 §4).
|
||||
// The reservation is asked before anything is stored (ADR 0605 §4, design 47 §381).
|
||||
Check: func(t Transfer) error {
|
||||
if t.Shape.Version == "" {
|
||||
return nil
|
||||
}
|
||||
ok, why, err := h.Builder.Recorded(t.Shape.Name, t.Shape.Version, t.Shape.Digest)
|
||||
if err != nil {
|
||||
return errors.New("builder_versions did not answer, so nothing is stored: " + err.Error())
|
||||
return errors.New("the reservation could not be read, so nothing is stored: " + err.Error())
|
||||
}
|
||||
if !ok {
|
||||
return errors.New("the builder did not number it: " + why)
|
||||
return errors.New("the builder did not reserve it: " + why)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
{
|
||||
"module": "distribution",
|
||||
"version": "2",
|
||||
"packages": {
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds": "0.1"
|
||||
},
|
||||
"provides": [
|
||||
{
|
||||
"name": "artifact-store",
|
||||
@@ -63,7 +60,7 @@
|
||||
],
|
||||
"invokes": [
|
||||
"distribution.registry_callers",
|
||||
"mesh-delivery-builder.builder_versions",
|
||||
"seat:mesh-builds.reserved",
|
||||
"mesh-delivery-view.node_haves",
|
||||
"seat:node-engine.manifest-reads",
|
||||
"seat:mesh-hand-acts.hand-act"
|
||||
|
||||
Reference in New Issue
Block a user