Files
mesh-delivery/contracts/go/clients/containerimageregistryclient/client.go
T

64 lines
3.0 KiB
Go

// Package containerimageregistryclient is the container-image-registry seat's client (novox/hq ADR 0627 §1, §2; ADR 0611, design 51
// *Publishing*): it publishes a file in parts to the seat's holder. `publish` opens the transfer naming every part,
// the holder answers the parts it lacks, and only those travel by `publish-part`, in chunks a bus message carries; a
// cut transfer goes on from what the holder answers when the same transfer is opened again. The sending in parts is
// mesh-sdk's seat-neutral runtime (parts); this client binds it to this seat's verbs and types alone. The call is the
// caller's own (a bundle's stdio.Ask, a process's call of a seat verb), so it holds no transport.
package containerimageregistryclient
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
"git.novox.be/novox/mesh-sdk/go/parts"
)
// Call asks one verb of the seat, `seat:<seat>.<verb>`, with a body, and answers its JSON.
type Call func(key string, body any) (json.RawMessage, error)
// Sent is what a Publish did: the transfer, whether the holder held its digest already, the parts it lacked and were
// sent, and the chunks they took.
type Sent struct {
Transfer string
parts.Sent
}
// Publish opens (or opens again) the transfer in on the seat and sends the parts the holder answers it lacks, each in
// chunks of at most parts.MaxChunk, in sequence, the last marked. in.Parts is written from parts. A part whose bytes
// do not hash to its digest is refused before anything of it is sent; a part the holder does not name is never read.
// Its outcome, published or refused, is the holder's event, which the caller waits for.
func Publish(ctx context.Context, call Call, in seat.PublishInput, ps []parts.Part) (Sent, error) {
open := func(_ context.Context, ps []parts.Part) (parts.Opened, error) {
in.Parts = make([]seat.Part, 0, len(ps))
for _, p := range ps {
in.Parts = append(in.Parts, seat.Part{Digest: p.Digest, Size: p.Size})
}
raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublish, in)
if err != nil {
return parts.Opened{}, fmt.Errorf("%s's publish of %s: %w", seat.Seat, in.Name, err)
}
var a seat.PublishAnswer
if err := json.Unmarshal(raw, &a); err != nil {
return parts.Opened{}, fmt.Errorf("%s's answer to publish: %w", seat.Seat, err)
}
return parts.Opened{Held: a.Held, Lacks: a.Lacks}, nil
}
chunk := func(_ context.Context, part string, sequence int, last bool, data []byte) error {
raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublishPart, seat.PublishPartInput{Transfer: in.Transfer, Part: part,
Sequence: int64(sequence), Last: last, Data: base64.StdEncoding.EncodeToString(data)})
if err != nil {
return fmt.Errorf("%s's publish-part: %w", seat.Seat, err)
}
var a seat.PublishPartAnswer
if err := json.Unmarshal(raw, &a); err != nil {
return fmt.Errorf("%s's answer to publish-part: %w", seat.Seat, err)
}
return nil
}
sent, err := parts.Send(ctx, open, chunk, ps)
return Sent{Transfer: in.Transfer, Sent: sent}, err
}