diff --git a/modules/mesh-grants/grants_test.go b/modules/mesh-grants/grants_test.go index 03acd28..c2b8bb6 100644 --- a/modules/mesh-grants/grants_test.go +++ b/modules/mesh-grants/grants_test.go @@ -406,6 +406,28 @@ func TestGrants(t *testing.T) { } }) } + // A module whose only runtime-loaded part is a helper bundle (route-proxy's route-provider) says `loads` in + // the repository manifest's build.artifacts; the indexer's manifest has no derived Bundles (issue 1945). + for _, row := range []struct { + name, build string + carried bool + }{ + {"a bundle that says loads is carried by node-tools", `,"build":{"artifacts":[{"name":"helper","kind":"bundle","language":"go","system":"arch","from":"cmd/h","binary":"h","loads":["h"]}]}`, true}, + {"a module with no loading bundle is left to its own account", ``, false}, + } { + t.Run(row.name, func(t *testing.T) { + b := fixture() + b.manifestOf("mod-z", `{"module":"mod-z","version":"1","own-secrets":{"broker":"/var/lib/z/broker"}`+row.build+`}`) + b.assign("a", "mod-z") + b.manifestOf("node-tools", `{"module":"node-tools","version":"1","own-secrets":{"broker":"/var/lib/nt/broker"}}`) + b.assign("a", "node-tools") + users := shadowOf(t, b) + tools := users[slices.IndexFunc(users, func(u grants.User) bool { return u.User == "a.node-tools" })] + if got := slices.Contains(tools.Subscribe, "mesh.mod.mod-z.tool.>"); got != row.carried { + t.Errorf("a.node-tools subscribes to mod-z's tools = %v, want %v: %v", got, row.carried, tools.Subscribe) + } + }) + } b := fixture() users := shadowOf(t, b) y := users[slices.IndexFunc(users, func(u grants.User) bool { return u.User == "b.mod-y" })] diff --git a/modules/mesh-grants/internal/grants/records.go b/modules/mesh-grants/internal/grants/records.go index 94beb82..e53beab 100644 --- a/modules/mesh-grants/internal/grants/records.go +++ b/modules/mesh-grants/internal/grants/records.go @@ -101,11 +101,7 @@ func declaredFor(m catalogue.Manifest, seats map[string]catalogue.SeatDeclaratio Streams: streamsOf(m), Queues: queueOwnersOf(m), Processes: processesOf(m), Checks: catalogue.HealthChecks(m), RunsAs: m.RunsAs, Issued: issuedTools(m), } - for _, b := range m.Bundles { - if len(b.Loads) > 0 { - d.Loads = true - } - } + d.Loads = loadsABundle(m) if _, reads := m.OwnSecrets["broker"]; !reads { d.NoAccount = true } @@ -129,6 +125,38 @@ func declaredFor(m catalogue.Manifest, seats map[string]catalogue.SeatDeclaratio return d } +// loadsABundle says the node's tool runtime loads a bundle of the module. The controller reads it from the resolved +// manifest's Bundles, which a build derives and the indexer's manifest does not carry (the builder's build record names +// the repository's manifest digest), so the same answer is derived here from `build.artifacts` by mesh-sdk's rule +// (the build's own rule for what a bundle loads): a bundle that says `loads`, else every bundle of a module that declares tools and that no +// resource of the module runs. Without it a module whose only runtime-loaded part is a helper (route-proxy's +// route-provider) was left out of node-tools' grants (issue 1945). +func loadsABundle(m catalogue.Manifest) bool { + for _, b := range m.Bundles { + if len(b.Loads) > 0 { + return true + } + } + if m.Build == nil { + return false + } + run := map[string]bool{} + for _, r := range m.Resources { + if a, ok := r["artifact"].(string); ok && a != "" { + run[a] = true + } + } + for _, a := range m.Build.Artifacts { + if a.Kind != catalogue.ArtifactBundle { + continue + } + if len(a.Loads) > 0 || (a.Loads == nil && len(m.Tools) > 0 && !run[a.Name]) { + return true + } + } + return false +} + func waitsOf(m catalogue.Manifest, seats map[string]catalogue.SeatDeclaration) []buscompose.Wait { var out []buscompose.Wait for _, w := range m.Waits {