117 lines
5.4 KiB
Go
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)))
|
|
}
|