Merge pull request 'contributes: a module's grant carries no value where it contributed several times' (#57) from fix/several-contributions-collide-in-grants-v2 into main
This commit was merged in pull request #57.
This commit is contained in:
@@ -1120,17 +1120,34 @@ func boundFile(n Needed, path, as string) (map[string]any, error) {
|
||||
// arrangement refused is the ordinary one. A node running eight services against one database is
|
||||
// not an edge case; it is what a machine looks like. Now each consumer has its own credential and
|
||||
// there is nothing left to refuse.
|
||||
//
|
||||
// **One credential, even where a module contributes several times.** A module may answer one
|
||||
// requirement more than once (ADR 0094's sibling for `contributes`) — an object store's data API
|
||||
// and its console are two different names, not one. There is still only one `Needed` for it, one
|
||||
// credential minted, one grant to settle: a pair credential is not a place to put a label or a
|
||||
// port. So where several of this module's contributions reach the same requirement, none of them
|
||||
// is "the" value — settling to the first, arbitrarily, would hand the grant one contribution's
|
||||
// values under a credential the OTHER contribution's consumer never sees, and would collide with
|
||||
// that contribution's own entry from contributions() besides. Empty values, still granted: the
|
||||
// module asked, gets its credential, and each named contribution reaches the provider on its own.
|
||||
func (r Resolution) ContributionsFrom(requirement, module string, settings SettingsBy) (
|
||||
map[string]any, bool, error) {
|
||||
all, err := r.contributions(settings, nil, nil)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
var mine []map[string]any
|
||||
for _, g := range all[requirement] {
|
||||
if g.From == module {
|
||||
return g.Values, true, nil
|
||||
mine = append(mine, g.Values)
|
||||
}
|
||||
}
|
||||
if len(mine) == 1 {
|
||||
return mine[0], true, nil
|
||||
}
|
||||
if len(mine) > 1 {
|
||||
return map[string]any{}, true, nil
|
||||
}
|
||||
// It contributes no payload — but a require-only consumer of a parameterless provision (one whose
|
||||
// `serves` names no consumer-supplied key: `redis-cache`, `amqp`) still ASKS for it and must be
|
||||
// granted a credential. Keying "asks" on contributions alone marked those grants withdrawn
|
||||
|
||||
@@ -122,3 +122,46 @@ func TestASingleRouteStillResolvesTheOrdinaryWay(t *testing.T) {
|
||||
t.Fatalf("the plain shape regressed: %+v", given)
|
||||
}
|
||||
}
|
||||
|
||||
// ContributionsFrom is what mints the ONE pair credential a requiring module is granted
|
||||
// (cmd/mesh-controller/plan.go's grantsFor) — a separate path from Declaration()'s raw file, and
|
||||
// the one the two-routes test above never exercised. Where a module contributes several times,
|
||||
// there is no single "the" value: settling to whichever sorts first would both misrepresent the
|
||||
// grant and collide with that same contribution's own entry from contributions(), which is
|
||||
// exactly the duplicate a live plan against minio surfaced (files-api appearing once with a
|
||||
// credential, once without, while files got neither).
|
||||
func TestContributionsFromHasNoSingleValueWhenAModuleContributesSeveralTimes(t *testing.T) {
|
||||
minio, err := ParseManifest([]byte(twoRoutes))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := Resolve(shelf(minimalRouteProxy(), minio), []string{"route-proxy", "minio"}, workstation(), World{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
values, asks, err := got.ContributionsFrom("route", "minio", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !asks {
|
||||
t.Fatal("minio still requires route, so it still asks")
|
||||
}
|
||||
if len(values) != 0 {
|
||||
t.Fatalf("no single value represents two contributions, got %+v", values)
|
||||
}
|
||||
}
|
||||
|
||||
// The ordinary, single-contribution case is unchanged: exactly one match still settles to it.
|
||||
func TestContributionsFromReturnsTheOneValueForAnOrdinaryContribution(t *testing.T) {
|
||||
got, err := Resolve(shelf(proxy(), published("board", "board", 8080)), []string{"board"}, workstation(), World{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
values, asks, err := got.ContributionsFrom("reverse-proxy", "board", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !asks || values["host"] != "board" {
|
||||
t.Fatalf("the ordinary single contribution should still settle to its own value: %+v", values)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user