diff --git a/modules/mesh-build-go/cmd/mesh-build-go/asks.go b/modules/mesh-build-go/cmd/mesh-build-go/asks.go index dd516941..bb0713da 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/asks.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/asks.go @@ -100,6 +100,9 @@ func (m *Module) Puller(steps []StepTool, source buildstep.AskSource) *buildstep if err := m.room(); err != nil { return nil, err } + if a.Compose != nil { + return m.compose(ctx, ask.Fetch, a) + } return buildstep.RunAsk(ctx, ask, a) }} } diff --git a/modules/mesh-build-go/cmd/mesh-build-go/compose.go b/modules/mesh-build-go/cmd/mesh-build-go/compose.go new file mode 100644 index 00000000..c733a15a --- /dev/null +++ b/modules/mesh-build-go/cmd/mesh-build-go/compose.go @@ -0,0 +1,210 @@ +package main + +// **A compose ask runs mesh-sdk's composer at a commit** (novox/hq ADR 0482 clause 4, issues 1621 and 1622): the +// assigner asks it, to compose a group with its mesh-sdk member's head, and to read the group's manifests with the +// mesh-sdk the running controller and node-engine pin (ADR 0482 clause 5, issue 1623). The module fetches the commit +// (its fetch step: the forge key and the network, none of the change's code), builds the composer package in the Go +// module the ask names, in a run step shut off with nothing fetched, and keeps the program under its key +// (delivery.ComposerKey), so each commit is built once on a node. It fills the request's manifests from the delivery +// indexer's manifests state, by digest, and runs the program shut off: no network, none of the account, the request a +// file of a workspace of its own. The answer is the composer's verdict whole (delivery.ComposeAnswer); the module +// judges nothing of it. + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "sync" + + "git.novox.be/novox/mesh-sdk/go/buildstep" + "git.novox.be/novox/mesh-sdk/go/composer" + "git.novox.be/novox/mesh-sdk/go/delivery" +) + +// ManifestsState is the delivery indexer's manifests by digest, which a compose ask's request names (the module reads +// it, module.json reads). +const ManifestsState = delivery.IndexerModule + ".manifests" + +// composerLocks hold one mutex per composer key, so a composer two asks need at once is built once. +var composerLocks sync.Map + +// composerPath is where the composer of key is kept: the cache directory, beside Go's own caches. +func (m *Module) composerPath(key string) string { + return filepath.Join(m.Cache, "composers", key, "mesh-compose") +} + +// compose runs a compose ask: the composer of its commit, built once, over its request with the manifests filled. +func (m *Module) compose(ctx context.Context, fetch buildstep.StepFunc, a delivery.StepAsk) (any, error) { + job := *a.Compose + repository, _ := job.Fetch["repository"].(string) + commit, _ := job.Fetch["commit"].(string) + installed, err := m.Probe(ctx) + if err != nil { + return nil, err + } + key := delivery.ComposerKey(repository, commit, job.Dir, job.Package, delivery.ToolchainUsed{Version: installed, Recipe: m.Recipe.Version()}) + exe, built, err := m.composerOf(ctx, fetch, job, key) + if err != nil { + return nil, err + } + req, err := composer.Decode(job.Request) + if err != nil { + return nil, buildstep.Refuse("the compose ask %s: %v", a.ID, err) + } + if err := composer.Fill(&req, m.manifestOf); err != nil { + return nil, err + } + verdict, err := m.runComposer(ctx, exe, req) + if err != nil { + return nil, err + } + return delivery.ComposeAnswer{Built: built, Key: key, Verdict: verdict}, nil +} + +// manifestOf is the manifest the indexer keeps under digest, as its repository holds it. +func (m *Module) manifestOf(digest string) ([]byte, error) { + if m.ReadManifest == nil { + return nil, errors.New("this module reads no manifests state") + } + key, err := delivery.ManifestsKey(digest) + if err != nil { + return nil, err + } + raw, err := m.ReadManifest(key) + if err != nil { + return nil, err + } + if raw == nil { + return nil, fmt.Errorf("the indexer keeps no manifest of digest %s", digest) + } + man, err := delivery.DecodeManifest(raw) + if err != nil { + return nil, err + } + return []byte(man.JSON), nil +} + +// composerOf is the composer kept under key, or, when none is, the one built now from the fetched commit (built true). +func (m *Module) composerOf(ctx context.Context, fetch buildstep.StepFunc, job delivery.ComposeJob, key string) (string, bool, error) { + exe := m.composerPath(key) + mu, _ := composerLocks.LoadOrStore(exe, &sync.Mutex{}) + mu.(*sync.Mutex).Lock() + defer mu.(*sync.Mutex).Unlock() + if st, err := os.Stat(exe); err == nil && st.Mode().IsRegular() { + return exe, false, nil + } + if fetch == nil { + return "", false, buildstep.Refuse("this build module fetches nothing") + } + out, err := fetch(ctx, job.Fetch) + if err != nil { + return "", false, err + } + raw, err := json.Marshal(out) + if err != nil { + return "", false, err + } + var f struct { + Workspace string `json:"workspace"` + } + if err := json.Unmarshal(raw, &f); err != nil || f.Workspace == "" { + return "", false, fmt.Errorf("the fetch answered no workspace: %s", raw) + } + defer m.releaseSaid(f.Workspace) + done := m.spaces().Hold(f.Workspace) + defer done() + dir := filepath.Join(m.Work, f.Workspace, "tree", job.Dir) + if _, err := os.Stat(filepath.Join(dir, "go.mod")); err != nil { + return "", false, fmt.Errorf("%s at %s holds no go.mod in %s, so no composer can be built there", job.Fetch["repository"], job.Fetch["commit"], job.Dir) + } + into := filepath.Join(m.Work, f.Workspace, "composer", "mesh-compose") + if err := os.MkdirAll(filepath.Dir(into), 0o700); err != nil { + return "", false, err + } + run, err := m.stepRunner(f.Workspace, false) + if err != nil { + return "", false, err + } + env := append(m.goEnvFor(dir), "GOPROXY=off", "GOTOOLCHAIN=local", "CGO_ENABLED=0") + r := run(ctx, env, "go", "-C", dir, "build", "-trimpath", "-o", into, job.Package) + if r.Status != 0 || r.Err != "" { + // The words Go says when the module holds no such package (a mesh-sdk from before the composer) are kept whole, + // so the asker tells that apart from a fault. + return "", false, fmt.Errorf("the composer %s does not build at %s: %s", job.Package, job.Fetch["commit"], compileError(r.Err+"\n"+r.Stderr+"\n"+r.Stdout)) + } + if err := copyExecutable(into, exe); err != nil { + return "", false, fmt.Errorf("the composer could not be kept: %w", err) + } + return exe, true, nil +} + +// copyExecutable copies a built program into place through a file beside it, so a half-written program is never kept. +func copyExecutable(from, to string) error { + if err := os.MkdirAll(filepath.Dir(to), 0o700); err != nil { + return err + } + src, err := os.Open(from) + if err != nil { + return err + } + defer src.Close() + tmp := to + ".new" + dst, err := os.OpenFile(tmp, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o700) + if err != nil { + return err + } + if _, err := io.Copy(dst, src); err != nil { + dst.Close() + return err + } + if err := dst.Close(); err != nil { + return err + } + return os.Rename(tmp, to) +} + +// releaseSaid removes a workspace once its steps ended, saying a failure: the sweep removes it later. +func (m *Module) releaseSaid(workspace string) { + if err := m.spaces().Release(workspace); err != nil && m.Say != nil { + m.Say("the compose ask's workspace %s: %v", workspace, err) + } +} + +// runComposer runs the composer over req in a workspace of its own, shut off, and answers its verdict: the last line of +// its standard output, a composer.Verdict of the version this module reads. +func (m *Module) runComposer(ctx context.Context, exe string, req composer.Request) (json.RawMessage, error) { + id := buildstep.NewID() + done := m.spaces().Hold(id) + defer m.releaseSaid(id) + defer done() + ws := filepath.Join(m.Work, id) + if err := os.MkdirAll(ws, 0o700); err != nil { + return nil, err + } + raw, err := json.Marshal(req) + if err != nil { + return nil, err + } + file := filepath.Join(ws, "request.json") + if err := os.WriteFile(file, raw, 0o600); err != nil { + return nil, err + } + run := m.Run + if !m.Inside { + iso := m.isolation(id, false) + iso.ReadOnly = append(iso.ReadOnly, exe) + run = buildstep.Isolated(m.Run, iso) + } + r := run(ctx, nil, exe, "-request", file) + line := strings.TrimSpace(lastLine(r.Stdout)) + var v composer.Verdict + if r.Status != 0 || r.Err != "" || json.Unmarshal([]byte(line), &v) != nil || v.V != composer.Version { + return nil, fmt.Errorf("the composer answered no verdict (status %d %s): %s", r.Status, r.Err, firstLine(r.Stderr+"\n"+r.Stdout)) + } + return json.RawMessage(line), nil +} diff --git a/modules/mesh-build-go/cmd/mesh-build-go/compose_integration_test.go b/modules/mesh-build-go/cmd/mesh-build-go/compose_integration_test.go new file mode 100644 index 00000000..ad1fe4d0 --- /dev/null +++ b/modules/mesh-build-go/cmd/mesh-build-go/compose_integration_test.go @@ -0,0 +1,156 @@ +//go:build integration + +package main + +import ( + "context" + "encoding/json" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/nats-io/nats.go" + "github.com/nats-io/nats.go/jetstream" + + "git.novox.be/novox/mesh-sdk/go/buildstep" + "git.novox.be/novox/mesh-sdk/go/composer" + "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-sdk/go/forgekey" + "git.novox.be/novox/mesh-sdk/go/testbus" +) + +// sdkPin is the mesh-sdk version this module's go.mod requires: the composer the test builds is that version's. +func sdkPin(t *testing.T) string { + t.Helper() + raw, err := os.ReadFile("../../go.mod") + if err != nil { + t.Fatal(err) + } + for _, l := range strings.Split(string(raw), "\n") { + if f := strings.Fields(l); len(f) >= 2 && f[0] == "git.novox.be/novox/mesh-sdk/go" { + return f[1] + } + } + t.Fatal("go.mod requires no mesh-sdk") + return "" +} + +// goEnvOf is one value of this machine's go env. +func goEnvOf(t *testing.T, name string) string { + t.Helper() + out, err := exec.Command("go", "env", name).Output() + if err != nil { + t.Fatalf("go env %s: %v", name, err) + } + return strings.TrimSpace(string(out)) +} + +// 1622/AC1, AC3 against the real bus: a compose ask put on the Go build seat's check stream is pulled by the module +// started whole on that bus, which fetches the commit (here a tree of a module requiring mesh-sdk at this module's pin, +// as a reader repository is), builds mesh-sdk's composer there with nothing fetched, fills the request's manifests from +// the indexer's state and runs the composer; the step key . passes with the composer's verdict. A second +// ask of the same commit runs the kept composer: fetched and built once. +func TestAComposeAskIsBuiltOnceAndAnsweredOnTheBus(t *testing.T) { + nc, err := nats.Connect(testbus.URL(t)) + if err != nil { + t.Fatal(err) + } + defer nc.Close() + js, err := jetstream.New(nc) + if err != nil { + t.Fatal(err) + } + ctx, stop := context.WithCancel(context.Background()) + defer stop() + if err := buildstep.DeclareAskWorkers(ctx, js, delivery.AskGo, 2*time.Minute); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = js.DeleteStream(context.Background(), "SEAT_NODE_BUILD_GO") }) + + states := newStates() + m := moduleWith(states, "go1.26.0") + m.Work, m.Cache, m.Inside = t.TempDir(), t.TempDir(), true + m.Run = StepRunner(t.TempDir()) + m.GoModCache = goEnvOf(t, "GOMODCACHE") // the module cache this module's own build filled, read and not fetched + run, gocache := m.Run, goEnvOf(t, "GOCACHE") // this machine's build cache, so the composer's packages compile once + m.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran { + return run(ctx, append(env, "GOCACHE="+gocache), name, args...) + } + read, digests := indexerHolds(t, `{"module":"demo","version":"1"}`) + m.ReadManifest = read + + gosum, err := os.ReadFile("../../go.sum") + if err != nil { + t.Fatal(err) + } + fetches := 0 + steps := fakeSteps(new([]string)) + for i := range steps { + if steps[i].Name == "build_go_fetch" { + steps[i].Run = func(ctx context.Context, args map[string]any) (any, error) { + fetches++ + ws := buildstep.NewID() + tree := filepath.Join(m.Work, ws, "tree") + if err := os.MkdirAll(tree, 0o700); err != nil { + return nil, err + } + mod := "module reader\n\ngo 1.26.0\n\nrequire git.novox.be/novox/mesh-sdk/go " + sdkPin(t) + "\n" + if err := os.WriteFile(filepath.Join(tree, "go.mod"), []byte(mod), 0o600); err != nil { + return nil, err + } + return map[string]any{"workspace": ws}, os.WriteFile(filepath.Join(tree, "go.sum"), gosum, 0o600) + } + } + } + p := m.Puller(steps, &buildstep.JetStreamAsks{JS: js, Language: delivery.AskGo}) + p.PullWait = 200 * time.Millisecond + go p.Pull(ctx) + + ask := func() delivery.Step { + a := composeAskOf(t, digests[0]) + a.Compose.Fetch["repository"], a.Compose.Dir = "novox/mesh-controller", delivery.RootPath + raw, err := delivery.EncodeStepAsk(a) + if err != nil { + t.Fatal(err) + } + subject, _ := delivery.AskSubject(delivery.AskGo, delivery.AskCheck) + if _, err := js.Publish(ctx, subject, raw); err != nil { + t.Fatal(err) + } + key, _ := delivery.AskStepKey("ace", a.ID) + for deadline := time.Now().Add(90 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) { + states.mu.Lock() + v := states.steps[key] + states.mu.Unlock() + if len(v) > 0 && v[len(v)-1].State != delivery.StepRunning { + return v[len(v)-1] + } + } + t.Fatalf("the compose ask %s never ended", a.ID) + return delivery.Step{} + } + for n, wantBuilt := range []bool{true, false} { + step := ask() + if step.State != delivery.StepPassed { + t.Fatalf("ask %d: %s: %s", n+1, step.State, step.Reason) + } + var ans delivery.ComposeAnswer + var v composer.Verdict + if err := json.Unmarshal(step.Result, &ans); err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(ans.Verdict, &v); err != nil { + t.Fatal(err) + } + if ans.Built != wantBuilt || !strings.HasPrefix(ans.Key, "composer-") || len(v.Cases) != 1 || !v.Cases[0].Manifests["demo"].Accepted || + !strings.Contains(v.SDK, sdkPin(t)) { + t.Fatalf("ask %d: answer built %v key %s, verdict %+v; want built %v and demo accepted by mesh-sdk %s", n+1, ans.Built, ans.Key, v, wantBuilt, sdkPin(t)) + } + } + if fetches != 1 { + t.Errorf("two asks of one commit fetched %d times, want once", fetches) + } +} diff --git a/modules/mesh-build-go/cmd/mesh-build-go/compose_test.go b/modules/mesh-build-go/cmd/mesh-build-go/compose_test.go new file mode 100644 index 00000000..14a70ea0 --- /dev/null +++ b/modules/mesh-build-go/cmd/mesh-build-go/compose_test.go @@ -0,0 +1,125 @@ +package main + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "slices" + "strings" + "testing" + "time" + + "git.novox.be/novox/mesh-sdk/go/buildstep" + "git.novox.be/novox/mesh-sdk/go/composer" + "git.novox.be/novox/mesh-sdk/go/delivery" +) + +const composeCommit = "4444444444444444444444444444444444444444" + +// indexerHolds is the delivery indexer's manifests state holding each body under its digest, as the indexer writes it. +func indexerHolds(t *testing.T, bodies ...string) (func(string) (json.RawMessage, error), []string) { + t.Helper() + kept, digests := map[string]json.RawMessage{}, []string{} + for _, b := range bodies { + d := delivery.ManifestDigest([]byte(b)) + key, _ := delivery.ManifestsKey(d) + raw, err := delivery.EncodeManifest(delivery.Manifest{Module: "demo", JSON: b}) + if err != nil { + t.Fatal(err) + } + kept[key], digests = raw, append(digests, d) + } + return func(key string) (json.RawMessage, error) { return kept[key], nil }, digests +} + +func composeAskOf(t *testing.T, digest string) delivery.StepAsk { + t.Helper() + req, _ := json.Marshal(composer.Request{V: composer.Version, Cases: []composer.Case{{Name: "change", + Manifests: map[string]composer.Manifest{"demo": {Digest: digest}}}}}) + return delivery.StepAsk{ID: buildstep.NewID(), Kind: delivery.AskCheck, Language: delivery.AskGo, Asker: "mesh-delivery-assigner", + At: time.Now(), Compose: &delivery.ComposeJob{Fetch: map[string]any{"repository": "novox/mesh-sdk", "commit": composeCommit}, + Dir: "go", Package: composer.Package, Request: req}} +} + +// composeModule is a module with work and cache directories of its own, running its commands as they are (Inside: a +// unit test has no bubblewrap to shut them off in). +func composeModule(t *testing.T) *Module { + t.Helper() + m := moduleWith(newStates(), "go1.26.0") + m.Work, m.Cache, m.Inside, m.Run = t.TempDir(), t.TempDir(), true, StepRunner(t.TempDir()) + return m +} + +// 1622/AC3: a composer kept under the ask's key is run again, nothing fetched or built: the answer says it was not +// built, names the key, and carries the composer's verdict whole; the request it read had its manifests filled from the +// indexer's state. +func TestAKeptComposerIsRunWithTheManifestsFilled(t *testing.T) { + m := composeModule(t) + read, digests := indexerHolds(t, `{"module":"demo","version":"1"}`) + m.ReadManifest = read + a := composeAskOf(t, digests[0]) + key := delivery.ComposerKey("novox/mesh-sdk", composeCommit, "go", composer.Package, delivery.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()}) + exe := m.composerPath(key) + if err := os.MkdirAll(filepath.Dir(exe), 0o700); err != nil { + t.Fatal(err) + } + // A composer that answers which manifests its request held, filled or not, as a verdict. + script := "#!/bin/sh\n" + + `if grep -q '"digest"' "$2"; then echo '{"v":1,"sdk":"stand-in","cases":[{"name":"unfilled","manifests":{}}]}'; ` + + `else echo '{"v":1,"sdk":"stand-in","cases":[{"name":"change","manifests":{"demo":{"accepted":true}}}]}'; fi` + "\n" + if err := os.WriteFile(exe, []byte(script), 0o700); err != nil { + t.Fatal(err) + } + fetched := 0 + fetch := func(context.Context, map[string]any) (any, error) { fetched++; return nil, nil } + out, err := m.compose(context.Background(), fetch, a) + if err != nil { + t.Fatal(err) + } + ans := out.(delivery.ComposeAnswer) + var v composer.Verdict + if err := json.Unmarshal(ans.Verdict, &v); err != nil { + t.Fatal(err) + } + if ans.Built || ans.Key != key || fetched != 0 || len(v.Cases) != 1 || v.Cases[0].Name != "change" || !v.Cases[0].Manifests["demo"].Accepted { + t.Fatalf("answer = %+v (fetched %d), verdict %+v", ans, fetched, v) + } + if entries, _ := os.ReadDir(m.Work); len(entries) != 0 { + t.Errorf("the run's workspace was left: %v", entries) + } +} + +// 1622: a manifest the indexer does not keep fails the ask, naming its digest; a fetched commit with no go.mod where +// the ask builds fails it naming the directory, and its workspace is released. +func TestAComposeAskFailsLoudly(t *testing.T) { + m := composeModule(t) + m.ReadManifest, _ = indexerHolds(t) + a := composeAskOf(t, "feedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeed") + key := delivery.ComposerKey("novox/mesh-sdk", composeCommit, "go", composer.Package, delivery.ToolchainUsed{Version: "go1.26.0", Recipe: m.Recipe.Version()}) + os.MkdirAll(filepath.Dir(m.composerPath(key)), 0o700) + os.WriteFile(m.composerPath(key), []byte("#!/bin/sh\nexit 0\n"), 0o700) + if _, err := m.compose(context.Background(), nil, a); err == nil || !strings.Contains(err.Error(), "feedfeed") { + t.Errorf("an unkept manifest: %v", err) + } + m2 := composeModule(t) + fetch := func(context.Context, map[string]any) (any, error) { + if err := os.MkdirAll(filepath.Join(m2.Work, "ws0000000000a", "tree", "go"), 0o700); err != nil { + return nil, err + } + return map[string]any{"workspace": "ws0000000000a"}, nil + } + if _, err := m2.compose(context.Background(), fetch, composeAskOf(t, "x")); err == nil || !strings.Contains(err.Error(), "no go.mod in go") { + t.Errorf("a commit with no go.mod: %v", err) + } + if entries, _ := os.ReadDir(m2.Work); len(entries) != 0 { + t.Errorf("the fetched workspace was left: %v", entries) + } +} + +// 1622: the module reads the delivery indexer's manifests, which a compose ask names by digest. +func TestTheModuleReadsTheIndexersManifests(t *testing.T) { + if m := readManifest(t); !slices.Contains(m.Reads, ManifestsState) { + t.Errorf("reads = %v, lacks %s", m.Reads, ManifestsState) + } +} diff --git a/modules/mesh-build-go/cmd/mesh-build-go/fetch.go b/modules/mesh-build-go/cmd/mesh-build-go/fetch.go index e5a1b49b..6dc0e424 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/fetch.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/fetch.go @@ -288,10 +288,14 @@ func inTestdata(path string) bool { // git, through the account's rewrite of the forge's address to ssh), and nothing that asks a question. For a directory // that vendors, see goEnvFor. func (m *Module) goEnv() []string { + modcache := filepath.Join(m.Cache, "gomod") + if m.GoModCache != "" { + modcache = m.GoModCache + } return []string{ "GOTOOLCHAIN=local", "GOFLAGS=-mod=mod", - "GOMODCACHE=" + filepath.Join(m.Cache, "gomod"), + "GOMODCACHE=" + modcache, "GOCACHE=" + filepath.Join(m.Cache, "gobuild"), "GOPRIVATE=git.novox.be", "GIT_TERMINAL_PROMPT=0", diff --git a/modules/mesh-build-go/cmd/mesh-build-go/main.go b/modules/mesh-build-go/cmd/mesh-build-go/main.go index 3becd9d0..d9e0da20 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/main.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/main.go @@ -54,6 +54,7 @@ func main() { m.RegistryFile = m.Home + "/registry" m.Settings = m.Home + "/settings.json" m.ReadKept = readOf(stdio.State(KeptState)).Get + m.ReadManifest = readOf(stdio.State(ManifestsState)).Get m.Probe = ProbeGo(m.Home, m.Cache) m.Bubblewrap = ProbeBubblewrap // Outside a launch by the runtime — a build, a check — there is no node and no state to write. diff --git a/modules/mesh-build-go/cmd/mesh-build-go/module.go b/modules/mesh-build-go/cmd/mesh-build-go/module.go index 3db6e3fb..48903962 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/module.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/module.go @@ -82,6 +82,10 @@ type Module struct { Settings string Capacity func() int ReadKept func(key string) (json.RawMessage, error) + // ReadManifest reads a key of the delivery indexer's manifests state (ManifestsState), which a compose ask's request + // names its manifests by (compose.go); GoModCache, when set, is the Go module cache in place of Cache's (a test). + ReadManifest func(key string) (json.RawMessage, error) + GoModCache string spacesOnce sync.Once workspaces *buildstep.Workspaces diff --git a/modules/mesh-build-go/go.mod b/modules/mesh-build-go/go.mod index 79763029..6e098605 100644 --- a/modules/mesh-build-go/go.mod +++ b/modules/mesh-build-go/go.mod @@ -3,7 +3,7 @@ module meshbuildgo go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261023213200-403a5d72ab93 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024021031-66bf26c0d721 github.com/distribution/distribution/v3 v3.1.2 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 diff --git a/modules/mesh-build-go/go.sum b/modules/mesh-build-go/go.sum index 81e122b8..2e3015dc 100644 --- a/modules/mesh-build-go/go.sum +++ b/modules/mesh-build-go/go.sum @@ -1,5 +1,7 @@ git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261023213200-403a5d72ab93 h1:FAiFY2KBKEoZSH49OZTt7/zRBIQxyl5g/KhYrpggbN4= git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261023213200-403a5d72ab93/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024021031-66bf26c0d721 h1:N//dDGmry1F3HKeBTWd+z/SfK+wb8bUUMJF0OlgR0/g= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024021031-66bf26c0d721/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= diff --git a/modules/mesh-build-go/module.json b/modules/mesh-build-go/module.json index 4d2b080a..600345e3 100644 --- a/modules/mesh-build-go/module.json +++ b/modules/mesh-build-go/module.json @@ -20,7 +20,8 @@ } ], "reads": [ - "mesh-delivery-checker.kept" + "mesh-delivery-checker.kept", + "mesh-delivery-indexer.manifests" ], "config": { "capacity": {