Files
mesh-catalog/modules/mesh-build-go/buildstep/publish.go
T
jschoubben d47d231ec3 CAT-BI1: mesh-build-image publishes and copies through container-image-registry
A trunk image is written as an OCI layout and published by the seat's
publish and publish-part, only the parts the holder lacks; an upstream copy is
the seat's mirror, ended on the holder's published or refused. The module
holds no registry address: artifact-store, files/registry and the copier go,
and the step answers the seat's reference.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 15:48:55 +01:00

66 lines
2.8 KiB
Go

package buildstep
// The publish of a done build's file through a registry seat (novox/hq design 51 *Publishing*, ADR 0611; CAT-BG1,
// CAT-BN3): from the build module's own process, after the build step ended, through the seat's verbs publish and
// publish-part, which each build module's manifest `invokes`, in chunks, only what the holder lacks. The holder checks
// the build with the builder and says published or refused on the seat's events. One place for every build module, so
// the move to the registry clients is made here once.
import (
"bytes"
"context"
"encoding/json"
"io"
"os"
"strings"
reg "git.novox.be/novox/mesh-contracts/go/seats/containerimageregistry"
"git.novox.be/novox/mesh-sdk/go/publish"
)
// The registry seats a build module publishes on.
const (
ArtifactSeat = "oci-artifact-registry"
ImageSeat = reg.Seat
GoSeat = "go-package-registry"
NpmSeat = "npm-package-registry"
)
// SeatCall asks one verb of a seat, `seat:<seat>.<verb>`, with a body, and answers its JSON (the bundle's stdio.Ask).
type SeatCall = func(key string, body any) (json.RawMessage, error)
// TransferName is a transfer's name, the build module's own: the name, a qualifier and the digest's head, safe as a
// transfer id.
func TransferName(name, qualifier, digest string) string {
head := strings.TrimPrefix(digest, "sha256:")
if len(head) > 16 {
head = head[:16]
}
return strings.NewReplacer("/", "-", ".", "-", " ", "-", "*", "-", ">", "-", "@", "-").Replace(name + "-" + qualifier + "-" + head)
}
// PublishOne publishes body, of digest, as name (at version, when it has one) of kind on seat, in one part.
func PublishOne(ctx context.Context, call SeatCall, seat, name, kind, version, digest, transfer string, body []byte) error {
in := reg.PublishInput{Name: name, Kind: kind, Version: version, Digest: digest, Transfer: transfer}
_, err := publish.Publish(ctx, publish.Call(call), seat, in, []publish.Part{{Digest: digest, Size: int64(len(body)),
Open: func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(body)), nil }}})
return err
}
// File is one part of a publish kept on disk: an image's config, layer or manifest, by its digest and size.
type File struct {
Digest string
Size int64
Path string
}
// PublishFiles publishes in with files as its parts (an image's config, layers and manifest, CAT-BI1): the holder
// answers the parts it lacks, and only those are read and sent.
func PublishFiles(ctx context.Context, call SeatCall, seat string, in reg.PublishInput, files []File) (publish.Sent, error) {
ps := make([]publish.Part, 0, len(files))
for _, f := range files {
ps = append(ps, publish.Part{Digest: f.Digest, Size: f.Size, Open: func() (io.ReadCloser, error) { return os.Open(f.Path) }})
}
return publish.Publish(ctx, publish.Call(call), seat, in, ps)
}