diff --git a/internal/catalogue/contributes_test.go b/internal/catalogue/contributes_test.go index e572736..1065295 100644 --- a/internal/catalogue/contributes_test.go +++ b/internal/catalogue/contributes_test.go @@ -333,6 +333,30 @@ func TestAConsumersOwnContributionsAreStillThere(t *testing.T) { } } +func TestARequireOnlyConsumerOfAParameterlessProvisionStillAsks(t *testing.T) { + // A consumer that requires a parameterless provision (one whose serves names no consumer key — + // redis-cache, amqp) contributes no payload, but it still ASKS for the provision and must be + // granted a credential. Keying "asks" on contributions alone gave its grant From="" — read as + // withdrawn — so the provider never created the account and the consumer authenticated nowhere. + r := Resolution{ + Node: "laptop", + Modules: []Manifest{{ + Module: "amqp-ping", + Requires: []string{"amqp"}, + }}, + } + values, asks, err := r.ContributionsFrom("amqp", "amqp-ping", SettingsBy{}) + if err != nil { + t.Fatal(err) + } + if !asks { + t.Fatal("a require-only consumer was treated as not asking; its grant would be withdrawn and it would never be provisioned") + } + if values == nil { + t.Fatalf("asks is true but values is nil; expected an empty contribution, got %v", values) + } +} + func TestAConsumerThatStoppedAskingIsWithdrawn(t *testing.T) { // Withdrawal is how a credential is taken away. The provisioner removes what nobody asks for, // and it can only do that if the mesh stops asking — a consumer that was unassigned would diff --git a/internal/catalogue/declaration.go b/internal/catalogue/declaration.go index 2274829..c02669e 100644 --- a/internal/catalogue/declaration.go +++ b/internal/catalogue/declaration.go @@ -637,6 +637,21 @@ func (r Resolution) ContributionsFrom(requirement, module string, settings Setti return g.Values, 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 + // (From=""), so the provider never created the account and the consumer authenticated nowhere. A + // module asks iff it still requires the provision, whether or not it hands anything up with it. + for _, m := range r.Modules { + if m.Module != module { + continue + } + for _, req := range m.Requires { + if req == requirement { + return map[string]any{}, true, nil + } + } + } // Nothing on that machine asks for this any more. Said as "not found" rather than as an // error: it is how a credential is withdrawn, and the provider removes what nobody asks for. return nil, false, nil