Register a module at artifacts the store holds, by hand, recorded as a build and a hand act, so the controller can be delivered without the builder (issue 1888, C8a and C8b)
This commit is contained in:
@@ -64,6 +64,9 @@ var handActVerbs = []handActVerb{
|
||||
{Verb: "plans stop", Decision: "the operator's answer to an ask is their decision, not a repair (ADR 0259)",
|
||||
DecidedFor: []string{conditions.CauseOperatorAnswer}},
|
||||
{Verb: "plans close"},
|
||||
// A module registered at references by hand, because the builder had no key for it (ADR 0526 clause 3): a repair
|
||||
// the builder should have made, so it counts.
|
||||
{Verb: "register"},
|
||||
// A walk started by a person instead of its delivery's owner (novox/hq ADR 0239): the owner down, or
|
||||
// not trusted with it — either is a repair the owner should have made. Unless the operator chose it on
|
||||
// a warrant (ADR 0259).
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -171,6 +172,10 @@ type keyTake struct {
|
||||
Module string
|
||||
Trunk sdkdelivery.Module
|
||||
Ask registrationAsk
|
||||
// Hand names, by artifact, the references the controller's terminal registers the module at instead of a key's
|
||||
// (ADR 0526 clause 3, `register --reference`): the key, when the builder has one, supplies the artifacts not named
|
||||
// here. Nil for every take-in but that one.
|
||||
Hand map[string]string
|
||||
}
|
||||
|
||||
// takeInKey takes a module in from the builder's key for the fingerprint its trunk value names: records the key as a
|
||||
@@ -234,7 +239,7 @@ func buildOfKey(ctx context.Context, keys keyReads, t keyTake, kc keyContext) (
|
||||
waits := func(format string, a ...any) (inventory.Build, catalogue.Manifest, error) {
|
||||
return inventory.Build{}, catalogue.Manifest{}, fmt.Errorf("%w: "+format, append([]any{errKeyWaits}, a...)...)
|
||||
}
|
||||
if t.Trunk.FP2 == "" {
|
||||
if t.Trunk.FP2 == "" && t.Hand == nil {
|
||||
why := t.Trunk.None2
|
||||
if why == "" {
|
||||
why = "the indexer wrote no fingerprint of the new form"
|
||||
@@ -242,6 +247,12 @@ func buildOfKey(ctx context.Context, keys keyReads, t keyTake, kc keyContext) (
|
||||
return waits("%s at %.8s has no fingerprint to take a build by: %s", t.Module, t.Trunk.Commit, why)
|
||||
}
|
||||
k, err := keys.Build(ctx, t.Module, t.Trunk.FP2)
|
||||
if t.Hand != nil {
|
||||
// **Registered at a reference, no key needed** (ADR 0526 clause 3): the key the builder holds, if any, is the
|
||||
// start; the references given replace or add its artifacts, and the key counts as done.
|
||||
k = handKey(t, k)
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return waits("%s's key cannot be read: %v", t.Module, err)
|
||||
}
|
||||
@@ -272,6 +283,11 @@ func buildOfKey(ctx context.Context, keys keyReads, t keyTake, kc keyContext) (
|
||||
return inventory.Build{}, catalogue.Manifest{}, fmt.Errorf("the key of %s names the manifest of %s",
|
||||
t.Module, manifest.Module)
|
||||
}
|
||||
if t.Hand != nil {
|
||||
if err := handCoversEvery(manifest, k, t.Hand); err != nil {
|
||||
return inventory.Build{}, catalogue.Manifest{}, err
|
||||
}
|
||||
}
|
||||
built, err := builtOfKey(ctx, kc.archive, manifest, k)
|
||||
if err != nil {
|
||||
return waits("%v", err)
|
||||
@@ -308,6 +324,10 @@ func buildOfKey(ctx context.Context, keys keyReads, t keyTake, kc keyContext) (
|
||||
break
|
||||
}
|
||||
}
|
||||
if t.Hand != nil {
|
||||
// A build made by hand, said so: the id is its own, so it never reads as the builder's key.
|
||||
kept.ID, kept.On = "hand-"+keyBuildID(k)+"-"+time.Now().UTC().Format("20060102T150405Z"), "hand"
|
||||
}
|
||||
for _, a := range k.Build.Artifacts {
|
||||
kept.Made = append(kept.Made, inventory.Artifact{Name: a.Name, Kind: kindOf(manifest, a),
|
||||
Reference: catalogue.Recorded(a.Reference)})
|
||||
@@ -458,3 +478,66 @@ func pathsBuildSource(m catalogue.Manifest, dir string) []string {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// handKey is the key a registration at references is made from: the builder's key when it has one (its other artifacts
|
||||
// stand), with each reference given in place of or beside the key's artifact of that name, and counted done.
|
||||
func handKey(t keyTake, k builderkeys.Key) builderkeys.Key {
|
||||
if !k.Found {
|
||||
k = builderkeys.Key{Module: t.Module, Fingerprint: t.Trunk.FP2, Found: true}
|
||||
}
|
||||
build := k.Build
|
||||
build.State, build.By, build.Error = sdkdelivery.BuildDone, "hand", ""
|
||||
if build.Manifest == "" {
|
||||
build.Manifest = t.Trunk.Manifest
|
||||
}
|
||||
build.Artifacts = nil
|
||||
for _, a := range k.Build.Artifacts {
|
||||
if _, replaced := t.Hand[a.Name]; !replaced {
|
||||
build.Artifacts = append(build.Artifacts, a)
|
||||
}
|
||||
}
|
||||
names := make([]string, 0, len(t.Hand))
|
||||
for name := range t.Hand {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
reference := t.Hand[name]
|
||||
build.Artifacts = append(build.Artifacts, sdkdelivery.BuiltArtifact{Name: name,
|
||||
Digest: handDigestOf.FindString(reference), Reference: reference})
|
||||
}
|
||||
k.Build = build
|
||||
return k
|
||||
}
|
||||
|
||||
// handCoversEvery refuses a registration at references that names an artifact the manifest does not declare, or leaves
|
||||
// one it declares without a reference (the key's or a given one).
|
||||
func handCoversEvery(m catalogue.Manifest, k builderkeys.Key, hand map[string]string) error {
|
||||
declared := map[string]bool{}
|
||||
if m.Build != nil {
|
||||
for _, a := range m.Build.Artifacts {
|
||||
declared[a.Name] = true
|
||||
}
|
||||
}
|
||||
for name := range hand {
|
||||
if !declared[name] {
|
||||
return fmt.Errorf("--reference %s: %s declares no artifact of that name. Nothing was registered", name, m.Module)
|
||||
}
|
||||
}
|
||||
have := map[string]bool{}
|
||||
for _, a := range k.Build.Artifacts {
|
||||
have[a.Name] = true
|
||||
}
|
||||
var missing []string
|
||||
for name := range declared {
|
||||
if !have[name] {
|
||||
missing = append(missing, name)
|
||||
}
|
||||
}
|
||||
sort.Strings(missing)
|
||||
if len(missing) > 0 {
|
||||
return fmt.Errorf("%s declares the artifact(s) %s and no --reference names them, nor does a key of the builder. "+
|
||||
"Nothing was registered", m.Module, strings.Join(missing, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -313,7 +313,9 @@ var usageText = `mesh-controller — the control plane
|
||||
operator key set <public> [--replace] tell the mesh which operator key to seal to
|
||||
operator key show the operator key, and what it can recover
|
||||
register <module> <owner>/<repository> <path> register a module from a repository the catalogue does not
|
||||
build, from the builder's done key of its fingerprint (ADR 0501 §4)
|
||||
build, from the builder's done key of its fingerprint (ADR 0501 §4); with
|
||||
--reference <artifact>=<reference> (repeatable) and --why, at artifacts the store
|
||||
holds, recorded as a build by hand (ADR 0526 §3)
|
||||
switch [--json] the switch's list: each module, the build it runs, and what the builder holds
|
||||
builds [<module>] what has been built lately, and what came of it
|
||||
queue [--json] every ask in the build queue: waiting, in flight (where, how long), dead
|
||||
|
||||
@@ -3,11 +3,17 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"github.com/novox/mesh-controller/internal/artifacts"
|
||||
"github.com/novox/mesh-controller/internal/builderkeys"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
)
|
||||
|
||||
// The terminal's exception becomes a registration, not a build (novox/hq ADR 0501 §4; issue 1851, A11b).
|
||||
@@ -17,16 +23,49 @@ import (
|
||||
// indexer's trunk value of that module names — the registration a build asked at the terminal used to make for a
|
||||
// repository the catalogue does not build (ADR 0432 §3, ADR 0420 §7). No verb reaches it (terminalOnlyCommands), and it
|
||||
// asks no build: a key not done is waited for, and said.
|
||||
//
|
||||
// **With `--reference <artifact>=<reference>`** (ADR 0526 clause 3; issue 1888), repeatable, the module is registered at
|
||||
// references the artifact store holds instead of a builder's key: the hand path for a controller, builder or build module
|
||||
// the builder cannot build (the R4g fallback). The references replace or add the artifacts of the builder's key when it
|
||||
// has one; every artifact the manifest declares must end up named. It is recorded as a build by hand, so the store's
|
||||
// sweep holds the blobs, and in the hand-act log with its `--why`.
|
||||
func registerCommand(ctx context.Context, args []string) error {
|
||||
if len(args) != 3 {
|
||||
return errors.New("register <module> <owner>/<repository> <path> (the path \".\" for a module at the repository's root)")
|
||||
set := flag.NewFlagSet("register", flag.ContinueOnError)
|
||||
references := map[string]string{}
|
||||
var given []string
|
||||
set.Func("reference", "<artifact>=<reference>: register the module at this artifact, which the store holds, "+
|
||||
"instead of a builder's key (repeatable; needs --why)", func(v string) error {
|
||||
artifact, reference, err := parseReference(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, twice := references[artifact]; twice {
|
||||
return fmt.Errorf("--reference names %s twice", artifact)
|
||||
}
|
||||
references[artifact] = reference
|
||||
given = append(given, v)
|
||||
return nil
|
||||
})
|
||||
hand := addHandActFlags(set)
|
||||
positionals, err := parseAround(set, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(positionals) != 3 {
|
||||
return errors.New("register <module> <owner>/<repository> <path> [--reference <artifact>=<reference> ... --why <text>] " +
|
||||
"(the path \".\" for a module at the repository's root)")
|
||||
}
|
||||
if !startedAtTheTerminal() {
|
||||
return terminalRefusal("register is run at the controller's terminal only, never through a verb: it registers " +
|
||||
"a module from a repository the catalogue does not build, and whoever may call a verb includes agents " +
|
||||
"(novox/hq ADR 0266, ADR 0501 §4). Nothing was done")
|
||||
"(novox/hq ADR 0266, ADR 0501 §4, ADR 0526 §3). Nothing was done")
|
||||
}
|
||||
module, repository, path := args[0], strings.Trim(args[1], "/"), strings.Trim(args[2], "/")
|
||||
if len(references) > 0 {
|
||||
if err := hand.require("register --reference"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
module, repository, path := positionals[0], strings.Trim(positionals[1], "/"), strings.Trim(positionals[2], "/")
|
||||
if path == sdkdelivery.RootPath {
|
||||
path = ""
|
||||
}
|
||||
@@ -51,8 +90,25 @@ func registerCommand(ctx context.Context, args []string) error {
|
||||
if err := sameModuleSource(module, repository, path, trunk); err != nil {
|
||||
return err
|
||||
}
|
||||
manifest, kept, err := takeInKey(ctx, open.inventory, keys, keyTake{Module: module, Trunk: trunk,
|
||||
Ask: registrationAsk{Terminal: true}})
|
||||
take := keyTake{Module: module, Trunk: trunk, Ask: registrationAsk{Terminal: true}}
|
||||
if len(references) > 0 {
|
||||
// Every reference is one the store holds, before anything is recorded: a registration at bytes that are not
|
||||
// there would be sent to the machines and fail there (ADR 0526 clause 3).
|
||||
if err := handStoreHoldsEvery(ctx, open.inventory, module, references); err != nil {
|
||||
return err
|
||||
}
|
||||
take.Hand = references
|
||||
}
|
||||
// And written down before it is done, with its why (novox/hq to-be 45 §7): a placement by hand is not undone by the
|
||||
// next push, and the log says who put it there.
|
||||
if take.Hand != nil {
|
||||
act := []string{module, repository, positionalPath(path)}
|
||||
for _, g := range given {
|
||||
act = append(act, "--reference", g)
|
||||
}
|
||||
hand.record(ctx, "register", act)
|
||||
}
|
||||
manifest, kept, err := takeInKey(ctx, open.inventory, keys, take)
|
||||
if errors.Is(err, errKeyWaits) {
|
||||
return fmt.Errorf("%s is not registered yet, and nothing was asked: %v. Run register again once its key is done",
|
||||
module, err)
|
||||
@@ -60,11 +116,85 @@ func registerCommand(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if take.Hand != nil {
|
||||
fmt.Printf("%s %s registered at the references given (%s at %.8s), as asked at the controller's terminal, "+
|
||||
"recorded as a build by hand; assigned nowhere\n", manifest.Module, manifest.Version, repository, kept.Commit)
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("%s %s registered from the builder's key %s (%s at %.8s), as asked at the controller's terminal; "+
|
||||
"assigned nowhere\n", manifest.Module, manifest.Version, short(kept.SourceFingerprint), repository, kept.Commit)
|
||||
return nil
|
||||
}
|
||||
|
||||
// positionalPath is the path as the command was given it: "." for a module at the repository's root.
|
||||
func positionalPath(path string) string {
|
||||
if path == "" {
|
||||
return sdkdelivery.RootPath
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// handDigestOf is the sha256 digest a recorded reference ends in.
|
||||
var handDigestOf = regexp.MustCompile(`sha256:[0-9a-f]{64}$`)
|
||||
|
||||
// parseReference reads one `--reference <artifact>=<reference>`: a reference into the mesh's artifact store, by digest.
|
||||
func parseReference(v string) (artifact, reference string, err error) {
|
||||
artifact, reference, ok := strings.Cut(v, "=")
|
||||
artifact, reference = strings.TrimSpace(artifact), strings.TrimSpace(reference)
|
||||
if !ok || artifact == "" || reference == "" {
|
||||
return "", "", fmt.Errorf("--reference %q is not <artifact>=<reference>", v)
|
||||
}
|
||||
if _, ours := catalogue.InArtifactStore(reference); !ours {
|
||||
return "", "", fmt.Errorf("--reference %s: %q is not a reference into the artifact store (%s...)", artifact,
|
||||
reference, catalogue.ArtifactStoreScheme)
|
||||
}
|
||||
if !handDigestOf.MatchString(reference) {
|
||||
return "", "", fmt.Errorf("--reference %s: %q names no digest (it ends in sha256:<64 hex digits>)", artifact, reference)
|
||||
}
|
||||
return artifact, reference, nil
|
||||
}
|
||||
|
||||
// handStoreHolds says whether the artifact store holds what a recorded reference names; a variable so a test needs no store.
|
||||
var handStoreHolds = func(ctx context.Context, inv *inventory.Inventory, reference string) error {
|
||||
shelf, err := inv.Catalogue(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
address, err := artifactStoreAddress(ctx, inv, shelf, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if address == "" {
|
||||
return errors.New("the artifact store is on no network the controller reaches")
|
||||
}
|
||||
return artifacts.Store{Address: address}.Holds(ctx, reference)
|
||||
}
|
||||
|
||||
// handStoreHoldsEvery refuses a reference that names another module's repository, or bytes the store does not hold.
|
||||
func handStoreHoldsEvery(ctx context.Context, inv *inventory.Inventory, module string, references map[string]string) error {
|
||||
names := make([]string, 0, len(references))
|
||||
for artifact := range references {
|
||||
names = append(names, artifact)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, artifact := range names {
|
||||
reference := references[artifact]
|
||||
if path, _ := catalogue.InArtifactStore(reference); !strings.HasPrefix(path, module+"/") {
|
||||
return fmt.Errorf("--reference %s: %s is not in %s's repository of the store. Nothing was registered",
|
||||
artifact, reference, module)
|
||||
}
|
||||
if err := handStoreHolds(ctx, inv, reference); err != nil {
|
||||
if errors.Is(err, artifacts.Gone) {
|
||||
return fmt.Errorf("--reference %s: the artifact store does not hold %s. Nothing was registered", artifact,
|
||||
reference)
|
||||
}
|
||||
return fmt.Errorf("--reference %s: the artifact store could not say whether it holds %s: %w. Nothing was "+
|
||||
"registered", artifact, reference, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// sameModuleSource refuses a registration whose repository or path is not the one the indexer's trunk value names: the
|
||||
// operator names exactly what is registered (ADR 0501 §4).
|
||||
func sameModuleSource(module, repository, path string, trunk sdkdelivery.Module) error {
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"testing"
|
||||
|
||||
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"github.com/novox/mesh-controller/internal/artifacts"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
)
|
||||
|
||||
// A11b (ADR 0501 §4): the terminal's registration names exactly the module's repository and path the indexer holds,
|
||||
@@ -36,3 +38,83 @@ func TestTheTerminalsRegistrationNamesItsSourceExactlyAndNoVerbReachesIt(t *test
|
||||
t.Fatalf("register through a verb: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// C8 (ADR 0526 clause 3; issue 1888): a reference is one into the artifact store, by digest; the store must hold it.
|
||||
func TestAReferenceToRegisterAtIsRefusedUnlessTheStoreHoldsIt(t *testing.T) {
|
||||
good := "artifact-store://x/tools/blobs/" + shaOf("t")
|
||||
if a, r, err := parseReference("tools=" + good); err != nil || a != "tools" || r != good {
|
||||
t.Fatalf("a good reference: %q %q %v", a, r, err)
|
||||
}
|
||||
for _, bad := range []string{"tools", "=" + good, "tools=", "tools=docker.io/library/x@" + shaOf("t"),
|
||||
"tools=artifact-store://x/tools/blobs/latest"} {
|
||||
if _, _, err := parseReference(bad); err == nil {
|
||||
t.Errorf("%q is taken", bad)
|
||||
}
|
||||
}
|
||||
held := map[string]bool{good: true}
|
||||
was := handStoreHolds
|
||||
t.Cleanup(func() { handStoreHolds = was })
|
||||
handStoreHolds = func(_ context.Context, _ *inventory.Inventory, reference string) error {
|
||||
if held[reference] {
|
||||
return nil
|
||||
}
|
||||
return artifacts.Gone
|
||||
}
|
||||
if err := handStoreHoldsEvery(t.Context(), nil, "x", map[string]string{"tools": good}); err != nil {
|
||||
t.Fatalf("a held reference is refused: %v", err)
|
||||
}
|
||||
gone := "artifact-store://x/tools/blobs/" + shaOf("other")
|
||||
if err := handStoreHoldsEvery(t.Context(), nil, "x", map[string]string{"tools": gone}); err == nil ||
|
||||
!strings.Contains(err.Error(), "does not hold") {
|
||||
t.Errorf("a digest the store does not hold: %v", err)
|
||||
}
|
||||
if err := handStoreHoldsEvery(t.Context(), nil, "x", map[string]string{"tools": "artifact-store://y/tools/blobs/" + shaOf("t")}); err == nil {
|
||||
t.Error("another module's repository is taken")
|
||||
}
|
||||
}
|
||||
|
||||
const handManifest = `{"module":"x","version":"1","tools":["x_read"],
|
||||
"build":{"artifacts":[{"name":"tools","kind":"bundle","language":"typescript","entrypoints":["dist/index.js"]}]}}`
|
||||
|
||||
// C8: a module with no key at all is registered at the references given, recorded as a build by hand under an id of its
|
||||
// own; an artifact the manifest does not declare, or a declared one nothing names, is refused.
|
||||
func TestAModuleIsTakenInAtReferencesWithNoKey(t *testing.T) {
|
||||
keys, trunk := aKeyOf(handManifest, 1)
|
||||
keys.keys = nil
|
||||
ref := "artifact-store://x/tools/blobs/" + shaOf("t")
|
||||
kept, resolved, err := buildOfKey(t.Context(), keys, keyTake{Module: "x", Trunk: trunk, Hand: map[string]string{"tools": ref}}, keyContext{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if kept.On != "hand" || !strings.HasPrefix(kept.ID, "hand-") || len(kept.Made) != 1 || kept.Made[0].Reference != ref ||
|
||||
kept.Made[0].Kind != "bundle" || len(resolved.Bundles) != 1 {
|
||||
t.Errorf("the build by hand: %+v %+v", kept, resolved.Bundles)
|
||||
}
|
||||
if _, _, err := buildOfKey(t.Context(), keys, keyTake{Module: "x", Trunk: trunk, Hand: map[string]string{"nope": ref}},
|
||||
keyContext{}); err == nil || !strings.Contains(err.Error(), "declares no artifact") {
|
||||
t.Errorf("an undeclared artifact: %v", err)
|
||||
}
|
||||
if _, _, err := buildOfKey(t.Context(), keys, keyTake{Module: "x", Trunk: trunk, Hand: map[string]string{}},
|
||||
keyContext{}); err == nil || !strings.Contains(err.Error(), "no --reference names them") {
|
||||
t.Errorf("a declared artifact nothing names: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// C8: the builder's key, when there is one, supplies what the references do not name.
|
||||
func TestAHandReferenceReplacesOneArtifactOfTheBuildersKey(t *testing.T) {
|
||||
keys, trunk := aKeyOf(tsBundle, 2,
|
||||
sdkdelivery.BuiltArtifact{Name: "tools", Kind: "bundle", Digest: shaOf("t"), Reference: "artifact-store://x/tools/blobs/" + shaOf("t")},
|
||||
sdkdelivery.BuiltArtifact{Name: "img", Kind: "image", Digest: shaOf("i"), Reference: "artifact-store://x/img@" + shaOf("i")})
|
||||
hand := "artifact-store://x/tools/blobs/" + shaOf("hand")
|
||||
kept, _, err := buildOfKey(t.Context(), keys, keyTake{Module: "x", Trunk: trunk, Hand: map[string]string{"tools": hand}}, keyContext{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := map[string]string{}
|
||||
for _, m := range kept.Made {
|
||||
got[m.Name] = m.Reference
|
||||
}
|
||||
if got["tools"] != hand || got["img"] != "artifact-store://x/img@"+shaOf("i") || len(got) != 2 {
|
||||
t.Errorf("Made: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,3 +291,32 @@ func (s Store) HoldsManifest(ctx context.Context, reference string) (bool, error
|
||||
}
|
||||
return s.has(ctx, s.url(repository, kind, digest), anyManifest...)
|
||||
}
|
||||
|
||||
// Holds is whether the store holds what a recorded reference names: the blob of an archive, the manifest of an image.
|
||||
// Gone when it does not; asks and changes nothing (ADR 0526 clause 3: a registration by hand is refused at bytes that
|
||||
// are not there).
|
||||
func (s Store) Holds(ctx context.Context, reference string) error {
|
||||
path, ours := catalogue.InArtifactStore(reference)
|
||||
if !ours {
|
||||
return fmt.Errorf("%w: %s", ErrNotOurs, reference)
|
||||
}
|
||||
repository, kind, digest, err := split(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s.Address == "" {
|
||||
return fmt.Errorf("this mesh has no artifact store on its network to ask about %s", reference)
|
||||
}
|
||||
if kind == "blobs" {
|
||||
_, err := s.blobSize(ctx, repository, digest)
|
||||
return err
|
||||
}
|
||||
held, err := s.has(ctx, s.url(repository, kind, digest), anyManifest...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !held {
|
||||
return Gone
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user