The delivery repository's contracts and SDK live under modules/ as mesh-delivery-contracts and mesh-delivery-sdk, their Go module paths renamed to match the folders (the TypeScript names already did), and every importer in mesh-delivery, mesh-catalog, mesh-host, mesh-tools and mesh-sdk follows in the same group: a design as it should be, one path per module, checked by the group's check building every importer. Builds: design 47 G §427, §88 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
102 lines
3.7 KiB
Go
102 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/clients/meshcatalogclient"
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshbuilds"
|
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshcatalog"
|
|
"git.novox.be/novox/mesh-sdk/go/process"
|
|
|
|
"mesh-delivery-builder/internal/build"
|
|
)
|
|
|
|
// **The runner's build step ask is the builder's one entry point for a candidate** (design 47 G A-97, A-84, A-99): the seat
|
|
// mesh-builds' accepted kind `step`, heard on its subject, names the entry of one module by fingerprint; the builder reads it through
|
|
// the seat mesh-catalog's `entry`, builds it (build.Builder.BuildStep) and says the end as the seat's event step-ended.<node>, the
|
|
// ask's id as the event's id, which the runner holds the end to.
|
|
|
|
// entryWaits answers an entry by fingerprint from the catalog half or the seat's `entry`, waiting for the answer the entries
|
|
// keeper is told of (meshcatalogclient.Entries.Changed), never a timer.
|
|
type entryWaits struct {
|
|
branches *meshcatalogclient.Branches
|
|
manifest *entryManifests
|
|
mu sync.Mutex
|
|
waiting map[string][]chan meshcatalogclient.EntryRow
|
|
}
|
|
|
|
func newEntryWaits(branches *meshcatalogclient.Branches, manifests *entryManifests) *entryWaits {
|
|
w := &entryWaits{branches: branches, manifest: manifests, waiting: map[string][]chan meshcatalogclient.EntryRow{}}
|
|
prior := branches.Entries.Changed
|
|
branches.Entries.Changed = func(row meshcatalogclient.EntryRow) {
|
|
w.mu.Lock()
|
|
chans := w.waiting[row.Fingerprint]
|
|
delete(w.waiting, row.Fingerprint)
|
|
w.mu.Unlock()
|
|
for _, ch := range chans {
|
|
ch <- row
|
|
}
|
|
if prior != nil {
|
|
prior(row)
|
|
}
|
|
}
|
|
return w
|
|
}
|
|
|
|
// entry is build.Builder.StepEntry.
|
|
func (w *entryWaits) entry(ctx context.Context, fingerprint string) (meshcatalog.CatalogEntry, error) {
|
|
ch := make(chan meshcatalogclient.EntryRow, 1)
|
|
w.mu.Lock()
|
|
w.waiting[fingerprint] = append(w.waiting[fingerprint], ch)
|
|
w.mu.Unlock()
|
|
e, state := w.branches.Resolve(ctx, fingerprint)
|
|
if state == meshcatalogclient.EntryAsked {
|
|
select {
|
|
case row := <-ch:
|
|
if row.Entry == nil {
|
|
return meshcatalog.CatalogEntry{}, fmt.Errorf("the seat mesh-catalog holds no entry of it (%s)", row.State)
|
|
}
|
|
e, state = *row.Entry, row.State
|
|
case <-ctx.Done():
|
|
return meshcatalog.CatalogEntry{}, ctx.Err()
|
|
}
|
|
}
|
|
if state != meshcatalogclient.EntryAnswered {
|
|
return meshcatalog.CatalogEntry{}, fmt.Errorf("the seat mesh-catalog's entry of it is %s", state)
|
|
}
|
|
return e, w.manifest.keep(e) // the manifest the build is planned from
|
|
}
|
|
|
|
// serveSteps hears the step asks on the seat's accept subject and answers each one's end, in its own goroutine.
|
|
func serveSteps(ctx context.Context, conn *nats.Conn, js jetstream.JetStream, cred process.Account, b *build.Builder, logf func(string, ...any)) error {
|
|
sub, err := conn.Subscribe(meshbuilds.AcceptSubjectStep, func(m *nats.Msg) {
|
|
id := m.Header.Get("x-event-id")
|
|
if id == "" {
|
|
id = m.Header.Get(nats.MsgIdHdr)
|
|
}
|
|
var run meshbuilds.StepRun
|
|
if err := json.Unmarshal(m.Data, &run); err != nil || id == "" || run.Step != meshbuilds.StepRunStepBuild {
|
|
logf("a step ask on %s that is no build step with an id is left: %v", m.Subject, err)
|
|
return
|
|
}
|
|
go func() {
|
|
end := b.BuildStep(ctx, run)
|
|
end.ID = id
|
|
if err := emitSeatEvent(context.WithoutCancel(ctx), js, cred, meshbuilds.SubjectStepEnded(cred.Node), id, end); err != nil {
|
|
logf("saying the end of the build step %s: %v", id, err)
|
|
}
|
|
}()
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
go func() { <-ctx.Done(); _ = sub.Drain() }()
|
|
return nil
|
|
}
|