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
66 lines
2.8 KiB
Go
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)
|
|
}
|