Let a provider serve its consumers its own composed names (hq ADR 0346, issue 624)
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate head 205f1541efa5 merged into trunk 6f1a05366c1d — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 205f1541efa5 merged into trunk 6f1a05366c1d — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate head 205f1541efa5 merged into trunk 6f1a05366c1d — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 205f1541efa5 merged into trunk 6f1a05366c1d — pass: its merge-check.sh passed
The forge writes every package tarball at its public name, and its consumers had no source for that name but a second copy kept by hand, though the mesh composes it for the forge's route. A served value may now name the provider's own names; a name its machine does not compose leaves the key out, and the plan says so.
This commit is contained in:
@@ -289,9 +289,15 @@ func theRestOfTheMesh(ctx context.Context, inv *inventory.Inventory,
|
||||
continue
|
||||
}
|
||||
caps, _ := inv.ProfileOf(ctx, n.Name)
|
||||
// Its public domain too (novox/hq ADR 0346): a provider elsewhere serves its own composed names,
|
||||
// and composes them as its own plan does, under its own domain.
|
||||
domain, err := inv.PublicDomainOf(ctx, n.Name)
|
||||
if err != nil {
|
||||
return catalogue.World{}, err
|
||||
}
|
||||
others = append(others, candidate{
|
||||
catalogue.Node{Name: n.Name, Site: siteOf[n.Name], Capabilities: caps,
|
||||
At: onNetwork[n.Name]}, theirs})
|
||||
At: onNetwork[n.Name], PublicDomain: domain}, theirs})
|
||||
}
|
||||
|
||||
offered := map[string][]catalogue.Provider{}
|
||||
@@ -314,8 +320,18 @@ func theRestOfTheMesh(ctx context.Context, inv *inventory.Inventory,
|
||||
if err != nil {
|
||||
return catalogue.World{}, err
|
||||
}
|
||||
// And its own composed names, as that node composes them (novox/hq ADR 0346): a name it
|
||||
// does not compose leaves the key out, said under each consumer's need.
|
||||
layers, err := inv.SettingsFor(ctx, o.node.Name, m.Module)
|
||||
if err != nil {
|
||||
return catalogue.World{}, err
|
||||
}
|
||||
serves, leftOut, err := got.ServedOwnNames(m, name, serves, catalogue.WithDefaults(m, layers))
|
||||
if err != nil {
|
||||
return catalogue.World{}, err
|
||||
}
|
||||
offered[name] = append(offered[name], catalogue.Provider{
|
||||
Node: o.node.Name, At: o.node.At, Serves: serves, Module: m.Module})
|
||||
Node: o.node.Name, At: o.node.At, Serves: serves, Module: m.Module, LeftOut: leftOut})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1426,6 +1442,16 @@ func planCommand(ctx context.Context, args []string) error {
|
||||
// in this output would have told anybody that.
|
||||
for _, n := range plan.Needs {
|
||||
fmt.Printf(" needs %s from %s, for %s\n", n.Name, n.From, n.For)
|
||||
// And a served key the provider left out, because its machine composes no name it asks for
|
||||
// (novox/hq ADR 0346 §3).
|
||||
for _, line := range n.LeftOut {
|
||||
fmt.Printf(" %s\n", line)
|
||||
}
|
||||
}
|
||||
// What the providers on this machine leave out of what they serve, and why: said where the remedy
|
||||
// is (novox/hq ADR 0346 §3).
|
||||
for _, line := range plan.ServedLeftOut(settings) {
|
||||
fmt.Printf("%s: %s\n", args[0], line)
|
||||
}
|
||||
for _, line := range unboundLines(plan) {
|
||||
fmt.Println(line)
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/catalogue"
|
||||
)
|
||||
|
||||
// novox/hq ADR 0346, issue 624: a provider on another machine serves its consumers its own composed
|
||||
// names, as that machine composes them; one it does not compose is left out, said under the need.
|
||||
func TestAProviderElsewhereServesItsOwnComposedName(t *testing.T) {
|
||||
open := aMesh(t)
|
||||
ctx := t.Context()
|
||||
forge, err := catalogue.ParseManifest([]byte(`{"module":"forge","version":"1","requires":["route"],
|
||||
"provides":[{"name":"npm-package-registry","scope":"mesh"}],
|
||||
"contributes":{"route":{"web":{"label":"git","port":3000}}},
|
||||
"serves":{"npm-package-registry":{"scheme":"http","port":3000,"npm-path":"/api/packages/novox/npm/",
|
||||
"public-root":"https://${bound:route:name-web}/api/packages/novox/npm/"}}}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
register(t, open, catalogue.Manifest{Module: "proxy", Version: "1", Provides: catalogue.FromAnywhere("route"),
|
||||
Receives: map[string]string{"route": "/var/lib/proxy/routes.json"}})
|
||||
register(t, open, forge)
|
||||
register(t, open, catalogue.Manifest{Module: "lab", Version: "1", Requires: []string{"npm-package-registry"},
|
||||
Binds: map[string]string{"npm-package-registry": "/var/lib/lab/package-registry.json"}})
|
||||
for _, a := range [][2]string{{"anchor", "proxy"}, {"anchor", "forge"}, {"laptop", "lab"}} {
|
||||
if _, err := assign(ctx, open, a[0], a[1]); err != nil {
|
||||
t.Fatalf("assign %s %s: %v", a[0], a[1], err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := open.inventory.SetPublicDomain(ctx, "anchor", "example.test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
plan, _, err := planFor(ctx, open, "laptop")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n := need(t, plan, "lab", "npm-package-registry")
|
||||
if n.Serves["public-root"] != "https://git.example.test/api/packages/novox/npm/" || len(n.LeftOut) != 0 {
|
||||
t.Fatalf("the consumer elsewhere is served the forge's public root: %v, left out %v", n.Serves, n.LeftOut)
|
||||
}
|
||||
|
||||
if err := open.inventory.SetPublicDomain(ctx, "anchor", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
plan, _, err = planFor(ctx, open, "laptop")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n = need(t, plan, "lab", "npm-package-registry")
|
||||
if _, there := n.Serves["public-root"]; there {
|
||||
t.Fatalf("anchor composes no public name, so the key is left out: %v", n.Serves)
|
||||
}
|
||||
if len(n.LeftOut) != 1 || !strings.Contains(n.LeftOut[0], `without "public-root"`) {
|
||||
t.Fatalf("and the need says so: %v", n.LeftOut)
|
||||
}
|
||||
}
|
||||
@@ -127,6 +127,7 @@ func boundNeed(n Needed, bound Chosen, catalogue map[string]Manifest, node Node,
|
||||
identity = pm.IdentityBoundOf(n.Name)
|
||||
}
|
||||
held.From, held.At, held.Module, held.Serves, held.Identity = p.Node, p.At, p.Module, p.Serves, identity
|
||||
held.LeftOut = p.LeftOut
|
||||
return held, true, ""
|
||||
}
|
||||
|
||||
|
||||
@@ -143,6 +143,8 @@ func CheckServes(m Manifest) []string {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s serves %s, and the value it serves as %q %s", m.Module, provision, key, err))
|
||||
}
|
||||
// And the provider's own names it may serve (novox/hq ADR 0346).
|
||||
problems = append(problems, checkServedOwnNames(m, provision, key, text)...)
|
||||
// A label longer than DNS keeps is not truncated here (asDNSLabel), so the offer's own
|
||||
// bound has to keep the identity inside one (ADR 0225).
|
||||
if strings.Contains(text, "${consumer:as:dns}") {
|
||||
|
||||
@@ -2039,7 +2039,13 @@ func (r Resolution) servedOnThisMachine(provision string, with Rendering) (map[s
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("%s serving %s: %w", m.Module, provision, err)
|
||||
}
|
||||
return settled, true, nil
|
||||
// And the provider's own names, as this machine composes them (novox/hq ADR 0346); one it does
|
||||
// not compose leaves its key out, said by the plan on this machine (ServedLeftOut).
|
||||
named, _, err := r.ServedOwnNames(m, provision, settled, WithDefaults(m, with.Settings[m.Module]))
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("%s serving %s: %w", m.Module, provision, err)
|
||||
}
|
||||
return named, true, nil
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
@@ -111,6 +111,9 @@ type Provider struct {
|
||||
// (novox/hq to-be 23), and the pair is what tells the holder of a seat apart from another module
|
||||
// providing the same thing (ADR 0110).
|
||||
Module string
|
||||
// LeftOut says each served key left out of Serves because that node composes no name it asks
|
||||
// for (novox/hq ADR 0346 §3): said under each consumer's need.
|
||||
LeftOut []string
|
||||
}
|
||||
|
||||
// Held is a claim somebody already has, used for the scopes wider than one node.
|
||||
@@ -230,6 +233,9 @@ type Needed struct {
|
||||
// state, not a consumer missing its key. Set by the plan, which is the only layer that knows a
|
||||
// licence's manager; empty for every consumer.
|
||||
Manager bool
|
||||
// LeftOut says each served key the provider left out, because its machine composes no name the
|
||||
// value asks for (novox/hq ADR 0346 §3).
|
||||
LeftOut []string
|
||||
// Module is the providing module on From, empty for a need answered by a record or by nothing
|
||||
// that serves it. A provider is a (node, module) pair (novox/hq to-be 23), and a binding to data
|
||||
// is a binding to the pair (ADR 0232).
|
||||
@@ -582,7 +588,7 @@ func Resolve(catalogue map[string]Manifest, assigned []string, node Node, world
|
||||
bound = pm.IdentityBoundOf(want)
|
||||
}
|
||||
needs = append(needs, Needed{Name: want, From: p.Node, At: p.At, Module: p.Module,
|
||||
Serves: p.Serves, For: because[want], SharedOwn: shared, Identity: bound})
|
||||
Serves: p.Serves, For: because[want], SharedOwn: shared, Identity: bound, LeftOut: p.LeftOut})
|
||||
}
|
||||
switch {
|
||||
case world.Unchecked:
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A provider may tell its consumers its own composed names (novox/hq ADR 0346, issue 624).
|
||||
//
|
||||
// **The name is already the mesh's.** A module contributes a label to a requirement — a route — and
|
||||
// the mesh joins it with the providing machine's domains; the module reads the result in its own files
|
||||
// as `${bound:<requirement>:name}` (novox/hq 04-ISSUES/122). Its consumers could not be told it: a
|
||||
// served value named only the consumer (ADR 0202) and settings. The forge's package registry writes
|
||||
// every tarball at its public name, and a consumer that must trust that name had no source for it but a
|
||||
// second copy kept by hand. So a served value may say `${bound:<requirement>:<name>}` for a requirement
|
||||
// the provider contributes to, and the mesh fills it from the same composition the provider's own files
|
||||
// and its proxy receive.
|
||||
//
|
||||
// **A name the providing machine does not compose leaves the key out** (ADR 0346 §3): a machine with
|
||||
// no public domain has no public name to give, and an empty value or a refusal would be worse than a
|
||||
// consumer told nothing — a refused provider machine takes every provision it offers down with it. The
|
||||
// plan says which key was left out, and why, on the provider's machine and under each consumer's need.
|
||||
|
||||
// ownNameFacts are what a served value may name of the provider's own composition: the names, as its
|
||||
// own files read them (withOwnNames).
|
||||
var ownNameFacts = []string{"name", "internal-name"}
|
||||
|
||||
// requirementsContributedTo is every requirement m contributes to, sorted.
|
||||
func requirementsContributedTo(m Manifest) []string {
|
||||
seen := map[string]bool{}
|
||||
for to := range m.Contributes {
|
||||
seen[to] = true
|
||||
}
|
||||
for to := range m.ContributesMany {
|
||||
seen[to] = true
|
||||
}
|
||||
out := make([]string, 0, len(seen))
|
||||
for to := range seen {
|
||||
out = append(out, to)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// ownNameKeys is every name a served value of m may say for one requirement it contributes to:
|
||||
// `name` and `internal-name`, and `name-<local>` and `internal-name-<local>` for each local name it
|
||||
// contributes under.
|
||||
func ownNameKeys(m Manifest, to string) []string {
|
||||
keys := append([]string{}, ownNameFacts...)
|
||||
for _, local := range sortedKeys(m.ContributesMany[to]) {
|
||||
for _, fact := range ownNameFacts {
|
||||
keys = append(keys, fact+"-"+local)
|
||||
}
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// ServedOwnNames fills the provider's own composed names into what it serves one provision's
|
||||
// consumers, from this resolution's composition of the provider's machine (ADR 0346).
|
||||
//
|
||||
// A value naming a name this machine does not compose is left out, with its key, and said. The map is
|
||||
// copied only when something changes, as ServedTo does: the caller's may be the manifest's.
|
||||
func (r Resolution) ServedOwnNames(m Manifest, provision string, serves map[string]any, layers []Layer) (
|
||||
map[string]any, []string, error) {
|
||||
var asking []string
|
||||
for _, key := range sortedAnyKeys(serves) {
|
||||
if text, ok := serves[key].(string); ok && bound.MatchString(text) {
|
||||
asking = append(asking, key)
|
||||
}
|
||||
}
|
||||
if len(asking) == 0 {
|
||||
return serves, nil, nil
|
||||
}
|
||||
own := map[string]map[string]string{}
|
||||
for _, to := range requirementsContributedTo(m) {
|
||||
names, err := r.ownNames(m, to, layers)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
values := map[string]string{}
|
||||
withOwnNames(values, names)
|
||||
own[to] = values
|
||||
}
|
||||
out := make(map[string]any, len(serves))
|
||||
for k, v := range serves {
|
||||
out[k] = v
|
||||
}
|
||||
var said []string
|
||||
for _, key := range asking {
|
||||
text := serves[key].(string)
|
||||
var missing string
|
||||
filled := bound.ReplaceAllStringFunc(text, func(token string) string {
|
||||
parts := bound.FindStringSubmatch(token)
|
||||
if value := own[parts[1]][parts[2]]; value != "" && parts[3] == "" {
|
||||
return value
|
||||
}
|
||||
if missing == "" {
|
||||
missing = token
|
||||
}
|
||||
return token
|
||||
})
|
||||
if missing != "" {
|
||||
delete(out, key)
|
||||
said = append(said, fmt.Sprintf(
|
||||
"%s serves %s without %q: it names %s, and %s composes no such name for %s (it has no "+
|
||||
"public domain, or the endpoint is not reached publicly) (novox/hq ADR 0346)",
|
||||
m.Module, provision, key, missing, orHere(r.Node), m.Module))
|
||||
continue
|
||||
}
|
||||
out[key] = filled
|
||||
}
|
||||
return out, said, nil
|
||||
}
|
||||
|
||||
func orHere(node string) string {
|
||||
if node == "" {
|
||||
return "this machine"
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// ServedLeftOut is what the providers in this node's own set leave out of what they serve, because
|
||||
// this machine composes no name a served value asks for (ADR 0346 §3): said on the provider's machine,
|
||||
// where the remedy is.
|
||||
func (r Resolution) ServedLeftOut(settings SettingsBy) []string {
|
||||
var said []string
|
||||
for _, m := range r.Modules {
|
||||
for _, provision := range sortedServes(m.Serves) {
|
||||
_, lines, err := r.ServedOwnNames(m, provision, m.Serves[provision], WithDefaults(m, settings[m.Module]))
|
||||
if err != nil {
|
||||
said = append(said, fmt.Sprintf("%s serves %s, and its own names could not be composed: %v",
|
||||
m.Module, provision, err))
|
||||
continue
|
||||
}
|
||||
said = append(said, lines...)
|
||||
}
|
||||
}
|
||||
return said
|
||||
}
|
||||
|
||||
// checkServedOwnNames refuses a served `${bound:…}` that names a requirement the module does not
|
||||
// contribute to, a fact other than its own names, or a local name it does not contribute under; and
|
||||
// one with a default, since a served value is left out rather than given one (ADR 0346 §3, §4).
|
||||
func checkServedOwnNames(m Manifest, provision, key, text string) []string {
|
||||
var problems []string
|
||||
contributes := requirementsContributedTo(m)
|
||||
for _, parts := range bound.FindAllStringSubmatch(text, -1) {
|
||||
to, fact, fallback := parts[1], parts[2], parts[3]
|
||||
says := fmt.Sprintf("%s serves %s, and the value it serves as %q says %s", m.Module, provision, key, parts[0])
|
||||
switch {
|
||||
case fallback != "":
|
||||
problems = append(problems, says+": a served name takes no default; one the machine does not "+
|
||||
"compose leaves the key out (novox/hq ADR 0346)")
|
||||
case !oneOf(contributes, to):
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s, and %s contributes to %s: a served value names only the provider's own names "+
|
||||
"(novox/hq ADR 0346)", says, m.Module, orNothing(quoted(contributes))))
|
||||
case !oneOf(ownNameKeys(m, to), fact):
|
||||
problems = append(problems, fmt.Sprintf("%s, and what %s is called through %s is %s (novox/hq ADR 0346)",
|
||||
says, m.Module, to, strings.Join(ownNameKeys(m, to), ", ")))
|
||||
}
|
||||
}
|
||||
return problems
|
||||
}
|
||||
|
||||
func quoted(values []string) []string {
|
||||
out := make([]string, len(values))
|
||||
for i, v := range values {
|
||||
out[i] = fmt.Sprintf("%q", v)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package catalogue
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// novox/hq ADR 0346, issue 624: the forge's package registry writes every tarball at its public name,
|
||||
// and the lab, its consumer, must trust that name and no other. The name is the mesh's own composition
|
||||
// of the forge's route, which the forge's files read as ${bound:route:name-web}; a consumer can now be
|
||||
// served it.
|
||||
|
||||
const aRegistryForge = `{"module":"forge","version":"1","requires":["route"],
|
||||
"provides":[{"name":"npm-package-registry","scope":"mesh"}],
|
||||
"contributes":{"route":{"web":{"label":"git","port":3000}}},
|
||||
"serves":{"npm-package-registry":{"scheme":"http","port":3000,"npm-path":"/api/packages/novox/npm/",
|
||||
"public-root":"https://${bound:route:name-web}/api/packages/novox/npm/"}}}`
|
||||
|
||||
func aForgeManifest(t *testing.T) Manifest {
|
||||
t.Helper()
|
||||
m, err := ParseManifest([]byte(aRegistryForge))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func TestAProviderServesItsOwnComposedName(t *testing.T) {
|
||||
m := aForgeManifest(t)
|
||||
r := Resolution{Node: "home", PublicDomain: "example.org"}
|
||||
got, said, err := r.ServedOwnNames(m, "npm-package-registry", m.Serves["npm-package-registry"], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got["public-root"] != "https://git.example.org/api/packages/novox/npm/" || len(said) != 0 {
|
||||
t.Fatalf("the public root is the route's composed name: %v, said %v", got, said)
|
||||
}
|
||||
if got["npm-path"] != "/api/packages/novox/npm/" || got["port"] != float64(3000) {
|
||||
t.Fatalf("the rest is served as it was: %v", got)
|
||||
}
|
||||
if m.Serves["npm-package-registry"]["public-root"] != "https://${bound:route:name-web}/api/packages/novox/npm/" {
|
||||
t.Fatal("the manifest's own map was rewritten")
|
||||
}
|
||||
}
|
||||
|
||||
func TestANameTheMachineDoesNotComposeLeavesTheKeyOutAndSaysSo(t *testing.T) {
|
||||
m := aForgeManifest(t)
|
||||
r := Resolution{Node: "home"} // no public domain
|
||||
got, said, err := r.ServedOwnNames(m, "npm-package-registry", m.Serves["npm-package-registry"], nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, there := got["public-root"]; there {
|
||||
t.Fatalf("a name not composed is left out, never written as the placeholder or empty: %v", got)
|
||||
}
|
||||
if got["npm-path"] == nil {
|
||||
t.Fatalf("only the key that names it is left out: %v", got)
|
||||
}
|
||||
if len(said) != 1 || !strings.Contains(said[0], `forge serves npm-package-registry without "public-root"`) ||
|
||||
!strings.Contains(said[0], "home composes no such name") {
|
||||
t.Fatalf("said: %v", said)
|
||||
}
|
||||
if lines := (Resolution{Node: "home", Modules: []Manifest{m}}).ServedLeftOut(nil); len(lines) != 1 {
|
||||
t.Fatalf("the plan on the provider's machine says it: %v", lines)
|
||||
}
|
||||
if lines := (Resolution{Node: "home", PublicDomain: "example.org", Modules: []Manifest{m}}).ServedLeftOut(nil); len(lines) != 0 {
|
||||
t.Fatalf("nothing to say when the name is composed: %v", lines)
|
||||
}
|
||||
}
|
||||
|
||||
// On the provider's own machine the consumer is served the same name (ADR 0346 §2).
|
||||
func TestAConsumerBesideTheProviderIsServedItsName(t *testing.T) {
|
||||
m := aForgeManifest(t)
|
||||
consumer := Manifest{Module: "lab", Version: "1", Requires: []string{"npm-package-registry"},
|
||||
Binds: map[string]string{"npm-package-registry": "/var/lib/lab/package-registry.json"}}
|
||||
node := workstation()
|
||||
node.PublicDomain = "example.org"
|
||||
got, err := Resolve(shelf(minimalRouteProxy(), m, consumer), []string{"route-proxy", "forge", "lab"}, node, World{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
served, answered, err := got.servedOnThisMachine("npm-package-registry", Rendering{})
|
||||
if err != nil || !answered {
|
||||
t.Fatalf("answered %v: %v", answered, err)
|
||||
}
|
||||
if served["public-root"] != "https://git.example.org/api/packages/novox/npm/" {
|
||||
t.Fatalf("served beside the provider: %v", served)
|
||||
}
|
||||
out, err := got.Declaration(Rendering{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var binding string
|
||||
for _, r := range out {
|
||||
if r["path"] == "/var/lib/lab/package-registry.json" {
|
||||
binding, _ = r["content"].(string)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(binding, `"public-root": "https://git.example.org/api/packages/novox/npm/"`) &&
|
||||
!strings.Contains(binding, `"public-root":"https://git.example.org/api/packages/novox/npm/"`) {
|
||||
t.Fatalf("the consumer's binding carries the name:\n%s", binding)
|
||||
}
|
||||
if strings.Contains(binding, "${bound:") {
|
||||
t.Fatalf("a placeholder reached the consumer:\n%s", binding)
|
||||
}
|
||||
}
|
||||
|
||||
// From another machine, the provider's served values carry what that machine composed, and a key it
|
||||
// left out is said on the consumer's need (ADR 0346 §2, §3).
|
||||
func TestAConsumerElsewhereIsToldWhatTheProviderLeftOut(t *testing.T) {
|
||||
consumer := Manifest{Module: "lab", Version: "1", Requires: []string{"npm-package-registry"},
|
||||
Binds: map[string]string{"npm-package-registry": "/var/lib/lab/package-registry.json"}}
|
||||
node := workstation()
|
||||
node.At = "workstation.internal"
|
||||
world := World{Offered: map[string][]Provider{"npm-package-registry": {{Node: "home", At: "home.internal",
|
||||
Module: "forge", Serves: map[string]any{"port": 3000},
|
||||
LeftOut: []string{`forge serves npm-package-registry without "public-root"`}}}}}
|
||||
got, err := Resolve(shelf(aForgeManifest(t), consumer), []string{"lab"}, node, world)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, n := range got.Needs {
|
||||
if n.Name == "npm-package-registry" {
|
||||
if len(n.LeftOut) != 1 {
|
||||
t.Fatalf("the need carries what was left out: %+v", n)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Fatalf("no need: %+v", got.Needs)
|
||||
}
|
||||
|
||||
func TestAServedNameIsCheckedWhenTheDefinitionIsRead(t *testing.T) {
|
||||
for bad, want := range map[string]string{
|
||||
`"public-root":"https://${bound:route:name-api}/x"`: "what forge is called through route is name, internal-name, name-web, internal-name-web",
|
||||
`"public-root":"https://${bound:postgres-database:name}/x"`: `forge contributes to "route"`,
|
||||
`"public-root":"https://${bound:route:name-web|default:x}/x"`: "takes no default",
|
||||
`"public-root":"https://${bound:route:at}/x"`: "what forge is called through route",
|
||||
} {
|
||||
raw := strings.Replace(aRegistryForge, `"public-root":"https://${bound:route:name-web}/api/packages/novox/npm/"`, bad, 1)
|
||||
_, err := ParseManifest([]byte(raw))
|
||||
if err == nil || !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("%s: want a refusal saying %q, got %v", bad, want, err)
|
||||
}
|
||||
}
|
||||
if _, err := ParseManifest([]byte(aRegistryForge)); err != nil {
|
||||
t.Fatalf("its own name is accepted: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user