contributes: a module may answer one requirement several times
A module's contributes was map[string]map[string]any — one JSON object key
per requirement, structurally exactly one contribution to "route" ever.
minio needs two public hostnames (the S3 API and the console), which is
two different contributions to route from one module, and nothing let it
say so.
This is the same shape of problem ADR 0094 solved for secrets (a module
needing several values from one provider that gives one per pair):
contributes now accepts either the ordinary {label, port} object, or an
object of local names to several such objects. Detected per requirement
key by what's inside, since (unlike secrets' string-vs-object split) both
shapes are JSON objects: an ordinary contribution's fields are scalars, the
several-instance shape is local-name -> object. Confirmed against every
module.json in mesh-catalog before relying on that split.
Both route-proxy and the migration-era route-adapter already key generated
routers off the composed hostname (Values["name"]), not the module name,
so two contributions with the same From reach them as two independent
routes with no changes needed on the receiving side.
This commit is contained in:
@@ -233,6 +233,18 @@ type Manifest struct {
|
||||
// module that had to say both would eventually say one.
|
||||
Contributes map[string]map[string]any `json:"contributes,omitempty"`
|
||||
|
||||
// ContributesMany is the same key, `contributes`, where a module tells one provider several
|
||||
// things under local names — `"route": {"api": {"label": "files-api", "port": 9000}, "console":
|
||||
// {"label": "files", "port": 9001}}` — because a module may answer one requirement more than
|
||||
// once: an object store with a data API and a console are two different public names, not one
|
||||
// (novox/hq ADR 0094's sibling for `contributes` rather than `secrets` — "a module may need more
|
||||
// than one value from a provider that gives one per pair" applies exactly as well to what a
|
||||
// module gives a provider as to what it keeps from one). Each local name is a contribution of
|
||||
// its own, reaching the provider as its own entry in the file it receives.
|
||||
//
|
||||
// Filled from the manifest's `contributes` object by UnmarshalJSON; never written by hand.
|
||||
ContributesMany map[string]map[string]map[string]any `json:"-"`
|
||||
|
||||
// Receives is where this module wants its consumers' contributions written, per requirement
|
||||
// it provides.
|
||||
//
|
||||
@@ -615,16 +627,24 @@ func AccessID(path string) string { return "access-" + strings.TrimPrefix(path,
|
||||
// it contributes to.
|
||||
func (m Manifest) Wants() []string {
|
||||
out := append([]string{}, m.Requires...)
|
||||
for to := range m.Contributes {
|
||||
var already bool
|
||||
add := func(to string) {
|
||||
for _, r := range m.Requires {
|
||||
if r == to {
|
||||
already = true
|
||||
return
|
||||
}
|
||||
}
|
||||
if !already {
|
||||
out = append(out, to)
|
||||
for _, already := range out {
|
||||
if already == to {
|
||||
return
|
||||
}
|
||||
}
|
||||
out = append(out, to)
|
||||
}
|
||||
for to := range m.Contributes {
|
||||
add(to)
|
||||
}
|
||||
for to := range m.ContributesMany {
|
||||
add(to)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
@@ -679,6 +699,47 @@ func (m *Manifest) UnmarshalJSON(raw []byte) error {
|
||||
}
|
||||
delete(keys, "secrets")
|
||||
}
|
||||
contributesPlain := map[string]map[string]any{}
|
||||
contributesMany := map[string]map[string]map[string]any{}
|
||||
if contributes, ok := keys["contributes"]; ok && string(contributes) != "null" {
|
||||
var byTo map[string]json.RawMessage
|
||||
if err := json.Unmarshal(contributes, &byTo); err != nil {
|
||||
return fmt.Errorf("contributes: an object of requirement to values, or to {local name: values}: %w", err)
|
||||
}
|
||||
for to, v := range byTo {
|
||||
// Both shapes are JSON objects, unlike secrets' path-vs-object split, so the shapes are
|
||||
// told apart by what is INSIDE: an ordinary contribution's fields are scalars (a label,
|
||||
// a port); the several-instance shape is an object of local names, each itself an
|
||||
// object of fields. Confirmed against the whole catalogue before relying on it — no
|
||||
// contribution anywhere has an object-valued field.
|
||||
var fields map[string]json.RawMessage
|
||||
if err := json.Unmarshal(v, &fields); err != nil {
|
||||
return fmt.Errorf("contributes.%s: an object of values, or of local name to values: %w", to, err)
|
||||
}
|
||||
many := len(fields) > 0
|
||||
for _, field := range fields {
|
||||
trimmed := bytes.TrimSpace(field)
|
||||
if len(trimmed) == 0 || trimmed[0] != '{' {
|
||||
many = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if many {
|
||||
var locals map[string]map[string]any
|
||||
if err := json.Unmarshal(v, &locals); err != nil {
|
||||
return fmt.Errorf("contributes.%s: an object of local name to values: %w", to, err)
|
||||
}
|
||||
contributesMany[to] = locals
|
||||
continue
|
||||
}
|
||||
var values map[string]any
|
||||
if err := json.Unmarshal(v, &values); err != nil {
|
||||
return fmt.Errorf("contributes.%s: an object of values: %w", to, err)
|
||||
}
|
||||
contributesPlain[to] = values
|
||||
}
|
||||
delete(keys, "contributes")
|
||||
}
|
||||
rest, err := json.Marshal(keys)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -696,22 +757,46 @@ func (m *Manifest) UnmarshalJSON(raw []byte) error {
|
||||
if len(many) > 0 {
|
||||
m.SecretsMany = many
|
||||
}
|
||||
if len(contributesPlain) > 0 {
|
||||
m.Contributes = contributesPlain
|
||||
}
|
||||
if len(contributesMany) > 0 {
|
||||
m.ContributesMany = contributesMany
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON writes `secrets` back in the shape it was read: paths, and objects of local names.
|
||||
// MarshalJSON writes `secrets` and `contributes` back in the shape they were read: single values,
|
||||
// and objects of local names.
|
||||
func (m Manifest) MarshalJSON() ([]byte, error) {
|
||||
raw, err := json.Marshal(manifestFields(m))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(m.SecretsMany) == 0 {
|
||||
if len(m.SecretsMany) == 0 && len(m.ContributesMany) == 0 {
|
||||
return raw, nil
|
||||
}
|
||||
var keys map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &keys); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(m.ContributesMany) > 0 {
|
||||
mergedContributes := map[string]any{}
|
||||
for to, values := range m.Contributes {
|
||||
mergedContributes[to] = values
|
||||
}
|
||||
for to, locals := range m.ContributesMany {
|
||||
mergedContributes[to] = locals
|
||||
}
|
||||
contributes, err := json.Marshal(mergedContributes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys["contributes"] = contributes
|
||||
}
|
||||
if len(m.SecretsMany) == 0 {
|
||||
return json.Marshal(keys)
|
||||
}
|
||||
merged := map[string]any{}
|
||||
for to, path := range m.Secrets {
|
||||
merged[to] = path
|
||||
@@ -872,6 +957,25 @@ func ParseManifest(raw []byte) (Manifest, error) {
|
||||
"%s contributes nothing to %q; if it only needs one, require it", m.Module, to))
|
||||
}
|
||||
}
|
||||
for to, locals := range m.ContributesMany {
|
||||
if !name.MatchString(to) {
|
||||
problems = append(problems, fmt.Sprintf("%q is not a usable name to contribute to", to))
|
||||
}
|
||||
if len(locals) == 0 {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s contributes nothing to %q; if it only needs one, require it", m.Module, to))
|
||||
}
|
||||
for local, values := range locals {
|
||||
if !name.MatchString(local) {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s contributes to %q under %q, which is not a usable name", m.Module, to, local))
|
||||
}
|
||||
if len(values) == 0 {
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s contributes nothing to %q under %q", m.Module, to, local))
|
||||
}
|
||||
}
|
||||
}
|
||||
problems = append(problems, m.Build.problems(m.Module)...)
|
||||
// **What provides the artifact store cannot be delivered through it** (novox/hq 04-ISSUES/029).
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user