diff --git a/modules/mesh-delivery-builder/internal/build/run_test.go b/modules/mesh-delivery-builder/internal/build/run_test.go index 769e9cd8..2e927ef1 100644 --- a/modules/mesh-delivery-builder/internal/build/run_test.go +++ b/modules/mesh-delivery-builder/internal/build/run_test.go @@ -1365,39 +1365,48 @@ func aDoneKeyKeepsEachFormACountedNodeReads(t *testing.T) { // The builder's own forms (forms.Current): a bundle a build step answers in the registry seat's form (mesh-build-go's // oci-artifact-registry//@) keeps the artifact-store form it derives for each counted node // whose engine does not state form:seat-reference, that one in the single reference (ADR 0624 §1, §3). - seatRef, storeRef := "oci-artifact-registry/m/code@"+digest, "artifact-store://m/code/blobs/"+digest seat := []string{forms.FormNeed(forms.FormSeatReference), "word:module"} - for _, row := range []struct { - name string - present, counted map[string][]string - reference string - references []string - needs []string - }{ - {"seat form answered, B away stating nothing: the derived store form stays, in the single reference", - map[string][]string{"a": seat}, map[string][]string{"a": seat, "b": nil}, storeRef, []string{storeRef, seatRef}, - []string{"form:artifact-store", "form:seat-reference"}}, - {"seat form answered, every counted engine reads it: the seat form only", - map[string][]string{"a": seat}, map[string][]string{"a": seat, "b": seat}, seatRef, nil, []string{"form:seat-reference"}}, - {"seat form answered, a present engine stating nothing: the store form only", - map[string][]string{"a": seat, "b": nil}, map[string][]string{"a": seat, "b": nil}, storeRef, nil, []string{"form:artifact-store"}}, - {"seat form answered, no node present: the store form", nil, map[string][]string{"a": seat}, storeRef, nil, []string{"form:artifact-store"}}, + // Each kind the seat form names (a bundle through oci-artifact-registry, an image and an upstream copy through + // container-image-registry, the copy under the holder's own upstream//), with the artifact-store form it derives. + for _, k := range []struct{ kind, seatRef, storeRef string }{ + {"bundle", "oci-artifact-registry/m/code@" + digest, "artifact-store://m/code/blobs/" + digest}, + {"image", "container-image-registry/m/code@" + digest, "artifact-store://m/code@" + digest}, + {"upstream", "container-image-registry/upstream/quay.io:443/org/app@" + digest, "artifact-store://upstream/quay.io:443/org/app@" + digest}, } { - a := forms.Current.Choose(delivery.BuiltArtifact{Name: "code", Kind: "bundle", Digest: digest}, []string{seatRef}, row.present, row.counted) - if a.Reference != row.reference || !slices.Equal(a.References, row.references) { - t.Errorf("%s: %q %v, want %q %v", row.name, a.Reference, a.References, row.reference, row.references) - } - needs := forms.Current.Needs([]delivery.BuiltArtifact{a}) - if !slices.Equal(needs, row.needs) { - t.Errorf("%s: needs %v, want %v", row.name, needs, row.needs) - } - for node, stated := range row.counted { - if forms.Current.Held(needs, stated) { - t.Errorf("%s: the want of %s is held", row.name, node) + seatRef, storeRef := k.seatRef, k.storeRef + for _, row := range []struct { + name string + present, counted map[string][]string + reference string + references []string + needs []string + }{ + {"seat form answered, B away stating nothing: the derived store form stays, in the single reference", + map[string][]string{"a": seat}, map[string][]string{"a": seat, "b": nil}, storeRef, []string{storeRef, seatRef}, + []string{"form:artifact-store", "form:seat-reference"}}, + {"seat form answered, every counted engine reads it: the seat form only", + map[string][]string{"a": seat}, map[string][]string{"a": seat, "b": seat}, seatRef, nil, []string{"form:seat-reference"}}, + {"seat form answered, a present engine stating nothing: the store form only", + map[string][]string{"a": seat, "b": nil}, map[string][]string{"a": seat, "b": nil}, storeRef, nil, []string{"form:artifact-store"}}, + {"seat form answered, no node present: the store form", nil, map[string][]string{"a": seat}, storeRef, nil, []string{"form:artifact-store"}}, + } { + a := forms.Current.Choose(delivery.BuiltArtifact{Name: "code", Kind: k.kind, Digest: digest}, []string{seatRef}, row.present, row.counted) + if a.Reference != row.reference || !slices.Equal(a.References, row.references) { + t.Errorf("%s %s: %q %v, want %q %v", k.kind, row.name, a.Reference, a.References, row.reference, row.references) + } + needs := forms.Current.Needs([]delivery.BuiltArtifact{a}) + if !slices.Equal(needs, row.needs) { + t.Errorf("%s %s: needs %v, want %v", k.kind, row.name, needs, row.needs) + } + for node, stated := range row.counted { + if forms.Current.Held(needs, stated) { + t.Errorf("%s %s: the want of %s is held", k.kind, row.name, node) + } + } + // mesh-sdk's referenceProblem does not yet know the holder's upstream// naming (a request to the SDK's owner). + if _, err := delivery.EncodeBuilderBuild(delivery.BuilderBuild{State: delivery.BuildDone, Manifest: strings.TrimPrefix(digest, "sha256:"), Artifacts: []delivery.BuiltArtifact{a}, Needs: needs}); err != nil && k.kind != "upstream" { + t.Errorf("%s %s: %v", k.kind, row.name, err) } - } - if _, err := delivery.EncodeBuilderBuild(delivery.BuilderBuild{State: delivery.BuildDone, Manifest: strings.TrimPrefix(digest, "sha256:"), Artifacts: []delivery.BuiltArtifact{a}, Needs: needs}); err != nil { - t.Errorf("%s: %v", row.name, err) } } // What the assigner holds a want by (ADR 0624 §2), from the same forms. diff --git a/sdk/forms/forms.go b/sdk/forms/forms.go index 579e9cf8..4183ef84 100644 --- a/sdk/forms/forms.go +++ b/sdk/forms/forms.go @@ -32,12 +32,31 @@ const ( FormPackageVersion = "package-version" ) -// FormSeatReference is the form a registry seat answers for a bundle or an archive it published, -// oci-artifact-registry//@sha256: (novox/hq ADR 0605 §9; catalogue.SeatReference, read back by -// catalogue.InRegistrySeat), read by a node-engine that states form:seat-reference (catalogue.ReferenceFormSeat). Not a -// first form: an engine stating nothing reads its blob only in the older artifact-store form, which it derives (Older). +// FormSeatReference is the form a registry seat answers for what it published, +// //@sha256: (novox/hq ADR 0605 §9; catalogue.SeatReference, read back by +// catalogue.InRegistrySeat): a bundle or an archive through oci-artifact-registry, an image through +// container-image-registry, and an upstream copy through container-image-registry under the holder's own naming, +// container-image-registry/upstream//@sha256:. It is read by a node-engine that states +// form:seat-reference (catalogue.ReferenceFormSeat). Not a first form: an engine stating nothing reads the artifact in the +// older artifact-store form, which it derives (Older). const FormSeatReference = catalogue.ReferenceFormSeat +// upstreamPrefix opens an upstream copy's name in the image seat's answer: the holder's naming, upstream//. +const upstreamPrefix = "upstream/" + +// inSeatReference reads a reference in the seat form: the seat it came through and the name and digest after it, +// /@ (or upstream//@ for an upstream copy of the image seat). +func inSeatReference(r string) (seat, path string, ok bool) { + if seat, path, ok = catalogue.InRegistrySeat(r); ok { + return seat, path, true + } + rest, found := strings.CutPrefix(r, catalogue.ContainerImageRegistrySeat+"/"+upstreamPrefix) + if !found || !strings.Contains(rest, "@sha256:") { + return "", "", false + } + return catalogue.ContainerImageRegistrySeat, upstreamPrefix + rest, true +} + // ArtifactForm is one reference form: its name, whether it is a first form, the artifact kinds it names, how a // reference is told to be in it, and, for a form that names the same artifact an older form does, that older reference // (Older, nil when it derives none): the old-form window writes it beside the answered one (ADR 0624 §1, §3). @@ -55,21 +74,29 @@ type ArtifactForms []ArtifactForm // Current are the forms the builder writes and the assigner knows: the three first forms (delivery.BuiltArtifact's // reference by kind), then the registry seat's form of a bundle or an archive. var Current = ArtifactForms{ - {Name: FormArtifactStore, First: true, Is: func(r string) bool { return strings.HasPrefix(r, artifactStore) }, - Kinds: []string{catalogue.ArtifactBundle, catalogue.ArtifactArchive, catalogue.ArtifactImage, catalogue.ArtifactUpstream}}, + // The registry form precedes the artifact-store form: what a build key kept of an image is recorded without the + // registry's address (catalogue.Recorded), so the artifact-store form is the newest first form an image has, the one + // Kept keeps for a node that states nothing. {Name: FormRegistry, First: true, Is: func(r string) bool { return !strings.HasPrefix(r, artifactStore) && strings.Contains(r, "@sha256:") }, Kinds: []string{catalogue.ArtifactImage, catalogue.ArtifactUpstream}}, + {Name: FormArtifactStore, First: true, Is: func(r string) bool { return strings.HasPrefix(r, artifactStore) }, + Kinds: []string{catalogue.ArtifactBundle, catalogue.ArtifactArchive, catalogue.ArtifactImage, catalogue.ArtifactUpstream}}, {Name: FormPackageVersion, First: true, Is: func(r string) bool { return strings.LastIndex(r, "@") > 0 && !strings.Contains(r, "@sha256:") }, Kinds: []string{catalogue.ArtifactPackage}}, - {Name: FormSeatReference, Kinds: []string{catalogue.ArtifactBundle, catalogue.ArtifactArchive}, + {Name: FormSeatReference, Kinds: []string{catalogue.ArtifactBundle, catalogue.ArtifactArchive, catalogue.ArtifactImage, catalogue.ArtifactUpstream}, Is: func(r string) bool { - seat, _, ok := catalogue.InRegistrySeat(r) - return ok && seat == catalogue.OciArtifactRegistrySeat + seat, _, ok := inSeatReference(r) + return ok && (seat == catalogue.OciArtifactRegistrySeat || seat == catalogue.ContainerImageRegistrySeat) }, - // The same blob in the artifact store's form: oci-artifact-registry//@ is artifact-store:////blobs/. + // The same artifact in the artifact store's form: oci-artifact-registry//@ is artifact-store:////blobs/; + // container-image-registry//@ (an upstream copy's upstream//

@ likewise) is artifact-store:///@. Older: func(r string) string { - path, _ := catalogue.InArtifactStore(r) - return catalogue.ArtifactStoreScheme + path + seat, rest, _ := inSeatReference(r) + if seat == catalogue.OciArtifactRegistrySeat { + path, _ := catalogue.InArtifactStore(r) + return catalogue.ArtifactStoreScheme + path + } + return catalogue.ArtifactStoreScheme + rest }}, }