104 lines
3.6 KiB
Go
104 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"git.novox.be/novox/mesh-sdk/go/delivery/buildrun"
|
|
"log"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// toolchainStreams are the stream each build module carries its toolchain.reported event on, as the bus names it
|
|
// (declared by the build modules, heard by this module through consumes).
|
|
var toolchainStreams = map[string]string{buildrun.GoBuildModule: "TOOLCHAINS_GO", buildrun.NodeBuildModule: "TOOLCHAINS_NODE"}
|
|
|
|
// toolchainWatch keeps the build modules' toolchains in a buildrun.ToolchainSet and tells the builder when one changed. A
|
|
// build module's bucket is made when it is assigned, after the builder started, and the builder holds no timer (ADR 0437
|
|
// §8): it watches the buckets that exist at start, and a build module says each time it wrote its toolchains key
|
|
// (toolchain.reported, handled by Reported); a bucket not watched yet is then watched.
|
|
type toolchainWatch struct {
|
|
ctx context.Context
|
|
js bucketOpener
|
|
set *buildrun.ToolchainSet
|
|
changed func(ctx context.Context) // a language's entries changed: the failed builds are tried again
|
|
logger *log.Logger
|
|
errs chan error
|
|
mu sync.Mutex
|
|
watched map[string]bool
|
|
}
|
|
|
|
func newToolchainWatch(ctx context.Context, js bucketOpener, set *buildrun.ToolchainSet, changed func(context.Context), logger *log.Logger) *toolchainWatch {
|
|
return &toolchainWatch{ctx: ctx, js: js, set: set, changed: changed, logger: logger, errs: make(chan error, len(buildrun.LanguageOf)), watched: map[string]bool{}}
|
|
}
|
|
|
|
// start watches the buckets that exist now; a missing one is said and waits for its build module's event.
|
|
func (w *toolchainWatch) start() error {
|
|
for module := range buildrun.LanguageOf {
|
|
if err := w.ensure(module); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *toolchainWatch) ensure(module string) error {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
if w.watched[module] {
|
|
return nil
|
|
}
|
|
name := delivery.ToolchainsBucket(module)
|
|
kv, err := w.js.KeyValue(w.ctx, name)
|
|
switch {
|
|
case errors.Is(err, jetstream.ErrBucketNotFound):
|
|
w.logger.Printf("the bucket %s does not exist yet: it is watched when %s reports a toolchain", name, module)
|
|
return nil
|
|
case err != nil:
|
|
return fmt.Errorf("the bucket %s: %w", name, err)
|
|
}
|
|
watcher, err := settle(w.ctx, kv, func(e jetstream.KeyValueEntry) { w.set.Put(module, e.Key(), e.Value(), !opOf(e)) })
|
|
if err != nil {
|
|
return err
|
|
}
|
|
w.watched[module] = true
|
|
go func() {
|
|
w.errs <- follow(w.ctx, watcher, func(e jetstream.KeyValueEntry) {
|
|
if w.set.Put(module, e.Key(), e.Value(), !opOf(e)) {
|
|
w.changed(w.ctx)
|
|
}
|
|
})
|
|
}()
|
|
w.changed(w.ctx) // what the settle read is new to the builder
|
|
return nil
|
|
}
|
|
|
|
// delivered is the part of a JetStream message the watch uses.
|
|
type delivered interface {
|
|
Subject() string
|
|
Ack() error
|
|
Term() error
|
|
}
|
|
|
|
// Reported takes one toolchain.reported event of a build module in: the module is read off the subject,
|
|
// mesh.stream.<module>.event.toolchain.reported, and its bucket is watched if it is not. A subject of no build module is
|
|
// terminated; a failure stays unacknowledged, and the server delivers it again.
|
|
func (w *toolchainWatch) Reported(m delivered) {
|
|
module, _, _ := strings.Cut(strings.TrimPrefix(m.Subject(), "mesh.stream."), ".event.")
|
|
if _, known := buildrun.LanguageOf[module]; !known || !strings.HasPrefix(m.Subject(), "mesh.stream.") {
|
|
w.logger.Printf("%s: not a build module's event, dropped", m.Subject())
|
|
_ = m.Term()
|
|
return
|
|
}
|
|
if err := w.ensure(module); err != nil {
|
|
w.logger.Printf("%s: left for redelivery: %v", m.Subject(), err)
|
|
return
|
|
}
|
|
_ = m.Ack()
|
|
}
|