ADR 0595 §1: the typed long form {name, storage, template, options,
configuration} decodes into Manifest.BusState, its name also on Requires;
a key it does not know is set aside and named, never refused (ADR 0578 §3).
The requirements tests are one table.
139 lines
5.1 KiB
Go
139 lines
5.1 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// OnlyWhenBound is a resource's field naming the optional requirement it is delivered only while
|
|
// bound (ADR 0330 §5). Said in the catalogue and dropped before the declaration is sent: the
|
|
// node-engine parses strictly and knows no such field.
|
|
const OnlyWhenBound = "only-when-bound"
|
|
|
|
// requirementForm is one entry of `requires` in its long form.
|
|
type requirementForm struct {
|
|
Name string `json:"name"`
|
|
Optional bool `json:"optional,omitempty"`
|
|
// Shares is who else may use the consumer's bucket (BucketShare, novox/hq ADR 0468 §8), on s3-bucket only.
|
|
Shares []BucketShare `json:"shares,omitempty"`
|
|
}
|
|
|
|
// requiresRead is `requires` as readRequires read it.
|
|
type requiresRead struct {
|
|
names []string
|
|
optional map[string]bool
|
|
shares map[string][]BucketShare
|
|
// busState are the entries asking for bus state (BusStateEntry), each also named in names; aside the paths of the
|
|
// keys they had that this package does not know, set aside (ADR 0578 §3).
|
|
busState []BusStateEntry
|
|
aside []string
|
|
}
|
|
|
|
// readRequires reads `requires` in its forms — a bare name, {name, optional}, or a bus-state entry (BusStateEntry,
|
|
// novox/hq ADR 0595 §1) — into the names, which of them are optional, and the bus-state entries.
|
|
func readRequires(raw json.RawMessage) (requiresRead, error) {
|
|
var entries []json.RawMessage
|
|
if err := json.Unmarshal(raw, &entries); err != nil {
|
|
return requiresRead{}, fmt.Errorf("requires: a list of names, or of {name, optional}: %w", err)
|
|
}
|
|
var names []string
|
|
optional := map[string]bool{}
|
|
var shares map[string][]BucketShare
|
|
var busState []BusStateEntry
|
|
var aside []string
|
|
for i, e := range entries {
|
|
var plain string
|
|
if err := json.Unmarshal(e, &plain); err == nil {
|
|
names = append(names, plain)
|
|
continue
|
|
}
|
|
if isBusStateEntry(e) {
|
|
entry, unknown, err := readBusStateEntry(e)
|
|
if err != nil {
|
|
return requiresRead{}, fmt.Errorf("requires[%d]: a bus-state entry is {name, storage, template, options, configuration} (novox/hq ADR 0595 §1): %w", i, err)
|
|
}
|
|
names = append(names, entry.Name)
|
|
busState = append(busState, entry)
|
|
for _, path := range unknown {
|
|
aside = append(aside, fmt.Sprintf("requires[%d]%s", i, path))
|
|
}
|
|
continue
|
|
}
|
|
var full requirementForm
|
|
dec := json.NewDecoder(bytes.NewReader(e))
|
|
dec.DisallowUnknownFields()
|
|
if err := dec.Decode(&full); err != nil {
|
|
return requiresRead{}, fmt.Errorf("requires[%d]: a name, or {name, optional} (novox/hq ADR 0330): %w", i, err)
|
|
}
|
|
if strings.TrimSpace(full.Name) == "" {
|
|
return requiresRead{}, fmt.Errorf("requires[%d] names no requirement: {name, optional}", i)
|
|
}
|
|
names = append(names, full.Name)
|
|
if err := checkBucketShares(full.Name, full.Shares); err != nil {
|
|
return requiresRead{}, fmt.Errorf("requires[%d]: %w", i, err)
|
|
}
|
|
if len(full.Shares) > 0 {
|
|
if shares == nil {
|
|
shares = map[string][]BucketShare{}
|
|
}
|
|
shares[full.Name] = full.Shares
|
|
}
|
|
if full.Optional {
|
|
optional[full.Name] = true
|
|
}
|
|
}
|
|
if len(optional) == 0 {
|
|
optional = nil
|
|
}
|
|
return requiresRead{names: names, optional: optional, shares: shares, busState: busState, aside: aside}, nil
|
|
}
|
|
|
|
// OptionalRequiresKey is where a stored manifest keeps which of its requirements are optional: beside
|
|
// `requires`, which stays a plain list of names (mesh-controller #281's review). A controller older than
|
|
// optional requirements reads `requires` as names; the long form inside it failed that read, and with it
|
|
// the whole catalogue, so a rollback below this controller would have composed no machine. An older
|
|
// controller sets this unknown key aside instead and leaves only the module that carries it out. A
|
|
// manifest may be written either way; it is stored this way.
|
|
const OptionalRequiresKey = "optional-requires"
|
|
|
|
// readOptionalRequires reads the stored form's list of optional requirements.
|
|
func readOptionalRequires(raw json.RawMessage) (map[string]bool, error) {
|
|
var names []string
|
|
if err := json.Unmarshal(raw, &names); err != nil {
|
|
return nil, fmt.Errorf("%s: a list of the names in `requires` that are optional: %w", OptionalRequiresKey, err)
|
|
}
|
|
out := map[string]bool{}
|
|
for _, n := range names {
|
|
out[n] = true
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// optionalRequiresList is the stored form's list: the optional requirements, sorted.
|
|
func optionalRequiresList(m Manifest) []string {
|
|
var out []string
|
|
for name, optional := range m.OptionalRequires {
|
|
if optional {
|
|
out = append(out, name)
|
|
}
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
// IsOptional is whether this module requires the name optionally (ADR 0330 §1). A name it contributes
|
|
// to without requiring, or requires as a bare name, is hard.
|
|
func (m Manifest) IsOptional(requirement string) bool { return m.OptionalRequires[requirement] }
|
|
|
|
// Absent is one optional requirement of one consumer that nothing assigned answers (ADR 0330 §3):
|
|
// left unbound rather than refused, and listed by `plan` and `node` (§7).
|
|
type Absent struct {
|
|
// Consumer is the module that requires it optionally.
|
|
Consumer string `json:"consumer"`
|
|
// Requirement is what it requires.
|
|
Requirement string `json:"requirement"`
|
|
}
|