contributes: a module's grant carries no value where it contributed several times #57
@@ -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
|
// 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
|
// 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.
|
// 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) (
|
func (r Resolution) ContributionsFrom(requirement, module string, settings SettingsBy) (
|
||||||
map[string]any, bool, error) {
|
map[string]any, bool, error) {
|
||||||
all, err := r.contributions(settings, nil, nil)
|
all, err := r.contributions(settings, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
var mine []map[string]any
|
||||||
for _, g := range all[requirement] {
|
for _, g := range all[requirement] {
|
||||||
if g.From == module {
|
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
|
// 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
|
// `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
|
// 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)
|
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