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), never named in names, which name provisions; 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 of the provisions, which of them are optional, and the bus-state entries, // whose local names stay off the names: a local name is no provision, and nothing provides it. 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, types} (novox/hq ADR 0595 §1): %w", i, err) } 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"` }