SDK-2: every reader reads both reference forms: artifact-store:// and the one a registry seat answers (ADR 0605 §9, design 51)
A done key's reference may be <seat>/<module>/<artifact>@<digest> through oci-artifact-registry (a bundle, an archive) or container-image-registry (an image, an upstream copy); catalogue.InRegistrySeat reads it, InArtifactStore and Routed read it as they read a kept reference, Recorded keeps it as it is. Every exported name stays. A build's list of every form (ADR 0624) is lead-47's, on feat/adr0618-reads. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -2,6 +2,8 @@ package catalogue
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
contractseats "git.novox.be/novox/mesh-contracts/go/seats"
|
||||
)
|
||||
|
||||
// ArtifactStoreScheme marks a reference to something in the mesh's artifact store, kept without
|
||||
@@ -17,7 +19,7 @@ const ArtifactStoreScheme = ArtifactStoreProvision + "://"
|
||||
// in it — an image id from a genesis build that had nowhere to publish, a package version — is
|
||||
// what it was.
|
||||
func Recorded(reference string) string {
|
||||
if strings.HasPrefix(reference, ArtifactStoreScheme) {
|
||||
if _, _, seat := InRegistrySeat(reference); seat || strings.HasPrefix(reference, ArtifactStoreScheme) {
|
||||
return reference
|
||||
}
|
||||
if rest, isURL := strings.CutPrefix(reference, "http://"); isURL {
|
||||
@@ -39,11 +41,41 @@ func isRegistryHost(component string) bool {
|
||||
return component == "localhost" || strings.ContainsAny(component, ".:")
|
||||
}
|
||||
|
||||
// InArtifactStore reports whether a reference is a kept one, and what it names there.
|
||||
// InArtifactStore reports whether a reference is a kept one, and what it names there: `<module>/<artifact>@<digest>`
|
||||
// for an image, `<module>/<artifact>/blobs/<digest>` for a bundle or an archive. **It reads both forms** (novox/hq ADR
|
||||
// 0605 §9): the older `artifact-store://<path>`, and the form a registry seat answers, `<seat>/<module>/<artifact>@<digest>`
|
||||
// through container-image-registry or oci-artifact-registry, whose blob it names the way the older form did.
|
||||
func InArtifactStore(reference string) (path string, kept bool) {
|
||||
if seat, rest, ok := InRegistrySeat(reference); ok {
|
||||
if seat == contractseats.OciArtifactRegistry {
|
||||
name, digest, _ := strings.Cut(rest, "@")
|
||||
return name + "/blobs/" + digest, true
|
||||
}
|
||||
return rest, true
|
||||
}
|
||||
return strings.CutPrefix(reference, ArtifactStoreScheme)
|
||||
}
|
||||
|
||||
// RegistrySeats are the seats a reference of the registry form names first, the two that front the artifact store
|
||||
// (novox/hq ADR 0605 §1, §9).
|
||||
var RegistrySeats = []string{contractseats.ContainerImageRegistry, contractseats.OciArtifactRegistry}
|
||||
|
||||
// InRegistrySeat reads a reference in the form a registry seat answers, `<seat>/<module>/<artifact>@sha256:<digest>`:
|
||||
// the seat it came through and the rest. A seat's name holds no dot or port, so no image of an outside registry reads
|
||||
// as one, and a kept `artifact-store://` reference is not one.
|
||||
func InRegistrySeat(reference string) (seat, path string, ok bool) {
|
||||
first, rest, found := strings.Cut(reference, "/")
|
||||
if !found || !strings.Contains(rest, "@sha256:") || strings.Count(rest, "/") != 1 {
|
||||
return "", "", false
|
||||
}
|
||||
for _, s := range RegistrySeats {
|
||||
if first == s {
|
||||
return s, rest, true
|
||||
}
|
||||
}
|
||||
return "", "", false
|
||||
}
|
||||
|
||||
// Routed is a kept reference as a machine fetches it, through the artifact store at `address`
|
||||
// (host:port). A reference that is not a kept one is what it was.
|
||||
func Routed(reference, address string) string {
|
||||
|
||||
@@ -20,10 +20,26 @@ func TestAReferenceIsRecordedWithoutTheStoresAddress(t *testing.T) {
|
||||
"@novox/sdk@1.2.3": "@novox/sdk@1.2.3",
|
||||
"gitea/gitea@" + digest: "gitea/gitea@" + digest,
|
||||
"https://registry.example/v2/gitea/config/blobs/" + digest: "https://registry.example/v2/gitea/config/blobs/" + digest,
|
||||
// The form a registry seat answers is recorded as it is (ADR 0605 §9).
|
||||
"container-image-registry/gitea/server@" + digest: "container-image-registry/gitea/server@" + digest,
|
||||
"oci-artifact-registry/gitea/config@" + digest: "oci-artifact-registry/gitea/config@" + digest,
|
||||
}
|
||||
for announced, want := range cases {
|
||||
if got := Recorded(announced); got != want {
|
||||
t.Errorf("Recorded(%q) = %q, want %q", announced, got, want)
|
||||
}
|
||||
}
|
||||
// And every reader reads both forms: a kept reference and the seat's are fetched from the same place.
|
||||
for reference, want := range map[string]string{
|
||||
ArtifactStoreScheme + "gitea/server@" + digest: "10.0.0.1:5000/gitea/server@" + digest,
|
||||
"container-image-registry/gitea/server@" + digest: "10.0.0.1:5000/gitea/server@" + digest,
|
||||
ArtifactStoreScheme + "gitea/config/blobs/" + digest: "http://10.0.0.1:5000/v2/gitea/config/blobs/" + digest,
|
||||
"oci-artifact-registry/gitea/config@" + digest: "http://10.0.0.1:5000/v2/gitea/config/blobs/" + digest,
|
||||
"registry.example/gitea/server@" + digest: "registry.example/gitea/server@" + digest,
|
||||
"mesh-artifact-store/gitea/server@" + digest: "mesh-artifact-store/gitea/server@" + digest,
|
||||
} {
|
||||
if got := Routed(reference, "10.0.0.1:5000"); got != want {
|
||||
t.Errorf("Routed(%q) = %q, want %q", reference, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
contractseats "git.novox.be/novox/mesh-contracts/go/seats"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
)
|
||||
|
||||
// The builder's keys (novox/hq ADR 0429 clauses 5 and 16, ADR 0437 clause 5, ADR 0405 clauses 2 and 7, ADR 0453
|
||||
@@ -72,6 +75,12 @@ func ParseBuilderBuildKey(key string) (module, fingerprint string, err error) {
|
||||
// artifact-store://<module>/<artifact>@<digest> catalogue.Recorded keeps without its address
|
||||
// package <name>@<version> the version published from the trunk
|
||||
//
|
||||
// From the registry milestone a done key names the reference its seat answered (novox/hq ADR 0605 §9, design 51 *The
|
||||
// done key names the reference the seat answered*): `<seat>/<module>/<artifact>@<digest>`, <seat> the registry seat it
|
||||
// came through (oci-artifact-registry for a bundle or an archive, container-image-registry for an image or an upstream
|
||||
// copy), and `<name>@<version>` for a package as before. A key written in the older form keeps it; this reads both, and
|
||||
// no build is built again for its form (catalogue.InArtifactStore reads both the same way).
|
||||
//
|
||||
// <artifact> is Name and <digest> is Digest. A reference is empty for a candidate's image or package, which is never
|
||||
// pushed or published (ADR 0498 clause 6), and in a key done before R4e; the controller delivers only from a key whose
|
||||
// every artifact names one (Referenced, clause 8). A kind left empty (a key written before kinds) is not checked.
|
||||
@@ -90,6 +99,17 @@ func (a BuiltArtifact) referenceProblem() string {
|
||||
if r == "" || a.Kind == "" {
|
||||
return ""
|
||||
}
|
||||
if seat, path, ok := catalogue.InRegistrySeat(r); ok {
|
||||
want := contractseats.ContainerImageRegistry
|
||||
if a.Kind == "bundle" || a.Kind == "archive" {
|
||||
want = contractseats.OciArtifactRegistry
|
||||
}
|
||||
module, rest, _ := strings.Cut(path, "/")
|
||||
if a.Kind == "package" || seat != want || module == "" || rest != a.Name+"@"+a.Digest {
|
||||
return "is not " + want + "/<module>/" + a.Name + "@" + a.Digest
|
||||
}
|
||||
return ""
|
||||
}
|
||||
switch a.Kind {
|
||||
case "bundle", "archive":
|
||||
path, ok := strings.CutPrefix(r, artifactStore)
|
||||
|
||||
@@ -24,6 +24,11 @@ func TestADoneKeyNamesEachArtifactsReferenceInTheFormOfItsKind(t *testing.T) {
|
||||
artifactOf("lib", "package", d, "@novox/mesh-sdk@0.1.15"),
|
||||
artifactOf("lib", "package", d, "left-pad@1.3.0"),
|
||||
artifactOf("old", "", d, "anything at all"),
|
||||
// The form a registry seat answers (ADR 0605 §9), beside the older one.
|
||||
artifactOf("code", "bundle", d, "oci-artifact-registry/mail/code@"+d),
|
||||
artifactOf("files", "archive", d, "oci-artifact-registry/mail/files@"+d),
|
||||
artifactOf("server", "image", d, "container-image-registry/mail/server@"+d),
|
||||
artifactOf("nats", "upstream", d, "container-image-registry/nats/nats@"+d),
|
||||
}
|
||||
for _, a := range good {
|
||||
b := doneBuild()
|
||||
@@ -43,6 +48,11 @@ func TestADoneKeyNamesEachArtifactsReferenceInTheFormOfItsKind(t *testing.T) {
|
||||
artifactOf("server", "image", d, "artifact-store://mail/server/blobs/"+d),
|
||||
artifactOf("lib", "package", d, "@novox/mesh-sdk"),
|
||||
artifactOf("lib", "package", d, "0.1.15"),
|
||||
artifactOf("code", "bundle", d, "container-image-registry/mail/code@"+d),
|
||||
artifactOf("server", "image", d, "oci-artifact-registry/mail/server@"+d),
|
||||
artifactOf("server", "image", d, "container-image-registry/mail/server@"+other),
|
||||
artifactOf("server", "image", d, "container-image-registry/mail/other@"+d),
|
||||
artifactOf("lib", "package", d, "container-image-registry/mail/lib@"+d),
|
||||
}
|
||||
for _, a := range bad {
|
||||
b := doneBuild()
|
||||
|
||||
Reference in New Issue
Block a user