Files
mesh-catalog/modules/mesh-build-go/buildstep/publish.go
T

117 lines
5.4 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: it
// binds each seat to its client in mesh-contracts (ADR 0627 §1), over mesh-sdk's seat-neutral sender of parts (§2).
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"os"
"strings"
imageclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient"
goclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/gopackageregistryclient"
npmclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/npmpackageregistryclient"
ociclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/ociartifactregistryclient"
reg "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
goseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry"
npmseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry"
ociseat "git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry"
"git.novox.be/novox/mesh-delivery/sdk/parts"
)
// The registry seats a build module publishes on.
const (
ArtifactSeat = ociseat.Seat
ImageSeat = reg.Seat
GoSeat = goseat.Seat
NpmSeat = npmseat.Seat
)
// 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 := OnSeat(ctx, seat, publishWhat(in), func() (parts.Sent, error) {
return PublishParts(ctx, call, seat, in, []parts.Part{{Digest: digest, Size: int64(len(body)),
Open: func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(body)), nil }}})
}, SentHow(1))
return err
}
// publishWhat names a publish in a narration: its name, its version when it has one, and its digest.
func publishWhat(in reg.PublishInput) string {
what := "publish of " + in.Name
if in.Version != "" {
what += " " + in.Version
}
return what + " at " + in.Digest
}
// SentHow says what of a publish of parts travelled, for its stored line.
func SentHow(n int) func(parts.Sent) string {
return func(s parts.Sent) string {
return fmt.Sprintf("%d of its %d parts sent, the rest held", len(s.Parts), n)
}
}
// PublishParts publishes in with ps as its parts on seat, through that seat's client: in's fields are the seat's own
// (the four registry seats share the publish verb's input, design 51), the parts are written by the client.
func PublishParts(ctx context.Context, call SeatCall, seat string, in reg.PublishInput, ps []parts.Part) (parts.Sent, error) {
switch seat {
case ImageSeat:
s, err := imageclient.Publish(ctx, imageclient.Call(call), in, ps)
return s.Sent, err
case ArtifactSeat:
s, err := ociclient.Publish(ctx, ociclient.Call(call), ociseat.PublishInput{Name: in.Name, Kind: in.Kind, Version: in.Version,
Tag: in.Tag, Digest: in.Digest, Fingerprint: in.Fingerprint, Commit: in.Commit, Transfer: in.Transfer}, ps)
return s.Sent, err
case GoSeat:
s, err := goclient.Publish(ctx, goclient.Call(call), goseat.PublishInput{Name: in.Name, Kind: in.Kind, Version: in.Version,
Tag: in.Tag, Digest: in.Digest, Fingerprint: in.Fingerprint, Commit: in.Commit, Transfer: in.Transfer}, ps)
return s.Sent, err
case NpmSeat:
s, err := npmclient.Publish(ctx, npmclient.Call(call), npmseat.PublishInput{Name: in.Name, Kind: in.Kind, Version: in.Version,
Tag: in.Tag, Digest: in.Digest, Fingerprint: in.Fingerprint, Commit: in.Commit, Transfer: in.Transfer}, ps)
return s.Sent, err
}
return parts.Sent{}, fmt.Errorf("%s is no registry seat a build publishes on", seat)
}
// 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) (parts.Sent, error) {
ps := make([]parts.Part, 0, len(files))
for _, f := range files {
ps = append(ps, parts.Part{Digest: f.Digest, Size: f.Size, Open: func() (io.ReadCloser, error) { return os.Open(f.Path) }})
}
return OnSeat(ctx, seat, publishWhat(in), func() (parts.Sent, error) {
return PublishParts(ctx, call, seat, in, ps)
}, SentHow(len(files)))
}