ADR 0593 §7: the composer with its refusals leaves mesh-sdk, so mesh-sdk carries no composition rule. The catalogue functions only the node-engine's composition reaches (Resolve, Resolution's Compose, Declaration, DataItems, LeftOut, Rules and the rest, the settings, placeholder and template refusals, the rendering helpers) are copied with their tests into internal/compose/resolution; a method of a mesh-sdk shape becomes a function taking it first. The composition calls its own copies, and the captured catalogue its tests read lives here now, shared by mesh-compose's tests. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
147 lines
7.2 KiB
Go
147 lines
7.2 KiB
Go
package resolution
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
// filler is one namespace's pass: the patterns it fills with, and the fields it reads them in, by
|
|
// resource type ("*" for any type).
|
|
type filler struct {
|
|
namespace string
|
|
patterns []*regexp.Regexp
|
|
fields map[string][]string
|
|
// why, when set, is the rule that keeps the namespace out of every other field, said with a
|
|
// refusal of one written there.
|
|
why string
|
|
}
|
|
|
|
// fillers is the table of what each pass fills where — read from the passes themselves: settingInto,
|
|
// dirInto, accessInto, intoFile (whose ${secret:…} the node-engine fills), boundInto, portInto,
|
|
// seatInto, machineInto and contributionsInto. A pass that comes to read another field adds it here,
|
|
// or the sweep refuses the placeholder it would have filled.
|
|
var fillers = []filler{
|
|
{namespace: "setting", patterns: []*regexp.Regexp{settingRef}, fields: map[string][]string{"file": {"content"}, "service": {"unit"}, "directory": {"owner"}, "container": {"groups"}}},
|
|
{namespace: "dir", patterns: []*regexp.Regexp{dirRef}, fields: map[string][]string{"*": {"path", "content", "volumes", "env", "env-file"}}},
|
|
{namespace: "access", patterns: []*regexp.Regexp{accessRef}, fields: map[string][]string{"*": {"path", "content", "volumes", "env", "env-file"}}},
|
|
{namespace: "secret", patterns: []*regexp.Regexp{placeholder}, fields: map[string][]string{"file": {"content"}},
|
|
why: "a secret is never filled into an environment variable or any other field: put it in a file the " +
|
|
"module declares and mount that (novox/hq ADR 0086)"},
|
|
{namespace: "bound", patterns: []*regexp.Regexp{bound}, fields: map[string][]string{"file": {"content"}}},
|
|
{namespace: "port", patterns: []*regexp.Regexp{ofPort}, fields: map[string][]string{"file": {"content"}, "container": {"env"}, "process": {"env"}}},
|
|
{namespace: "seat", patterns: []*regexp.Regexp{ofSeat, ofSeatReach}, fields: map[string][]string{"file": {"content"}, "container": {"env"}, "process": {"env"}}},
|
|
{namespace: "machine", patterns: []*regexp.Regexp{ofMachine}, fields: map[string][]string{"*": {"path", "owner", "content", "name", "user", "root", "home"}}},
|
|
// Placed in a file's content by their holders, and judged there by their own rules
|
|
// (placeholderProblems, seatPlaceholderProblems).
|
|
{namespace: "environment", patterns: []*regexp.Regexp{ofEnvironment}, fields: map[string][]string{"*": {"content"}}},
|
|
{namespace: "shell", patterns: []*regexp.Regexp{ofShell}, fields: map[string][]string{"*": {"content"}}},
|
|
{namespace: "contribution", patterns: []*regexp.Regexp{ofContribution}, fields: map[string][]string{"*": {"content"}}},
|
|
// What the machine's modules back up, placed by the backup holder's file (backup_placement.go, issue 794).
|
|
{namespace: mcat.BackupNamespace, patterns: []*regexp.Regexp{ofBackup}, fields: map[string][]string{"*": {"content"}}},
|
|
// A collection, placed by its collector (collections.go): filled by the provisioner's client.
|
|
{namespace: mcat.CollectedNamespace, patterns: []*regexp.Regexp{ofCollected}, fields: map[string][]string{"*": {"content"}}},
|
|
// A provider's consumers' credentials (consumers.go): filled by the provisioner's client.
|
|
{namespace: mcat.ConsumersNamespace, patterns: []*regexp.Regexp{ofConsumers}, fields: map[string][]string{"file": {"content"}}},
|
|
// Filled in what a provider serves (consumer_into_serves.go), never in a resource.
|
|
{namespace: "consumer", patterns: []*regexp.Regexp{consumerFact}},
|
|
}
|
|
|
|
// reads is whether this pass fills a field of a resource of this type.
|
|
func (f filler) reads(kind, field string) bool {
|
|
return oneOf(f.fields[kind], field) || oneOf(f.fields["*"], field)
|
|
}
|
|
|
|
// ofKnownNamespace is any `${<known namespace>:` and what follows it up to its brace or the end of
|
|
// its line, whatever its case, unless what follows the colon is a shell's parameter operator (`-`,
|
|
// `=`, `+`, `?`): what remains of one once every pass's pattern is set aside is a misspelling.
|
|
var ofKnownNamespace = regexp.MustCompile(`(?im)\$\{(?:` + strings.Join(namespacesOf(fillers), "|") +
|
|
`):(?:[^-=+?}\n][^}\n]*\}?|\}|$)`)
|
|
|
|
// namespaceShaped is a placeholder in the mesh's shape: a lower-case word, a colon, and a key that
|
|
// begins with a letter or a digit and holds no space, brace or `$`.
|
|
var namespaceShaped = regexp.MustCompile(`\$\{[a-z][a-z0-9_-]*:[A-Za-z0-9][^\s{}$]*\}`)
|
|
|
|
func namespacesOf(fs []filler) []string {
|
|
out := make([]string, len(fs))
|
|
for i, f := range fs {
|
|
out[i] = f.namespace
|
|
}
|
|
return out
|
|
}
|
|
|
|
// unconsumedPlaceholders is every placeholder in one resource of a definition that no pass fills
|
|
// where it stands, each named with the module, the resource, the field and the token. The same
|
|
// function at the catalogue check and at composition, over the resource as the manifest wrote it.
|
|
func unconsumedPlaceholders(module string, r map[string]any) []string {
|
|
kind := fmt.Sprint(r["type"])
|
|
var problems []string
|
|
var sweep func(top, field string, v any)
|
|
sweep = func(top, field string, v any) {
|
|
switch v := v.(type) {
|
|
case string:
|
|
rest := v
|
|
for _, f := range fillers {
|
|
for _, p := range f.patterns {
|
|
if !f.reads(kind, top) {
|
|
for _, token := range p.FindAllString(rest, -1) {
|
|
problems = append(problems, fmt.Sprintf(
|
|
"%s's resource %v holds %s in its %s, and ${%s:…} is not filled in this field: "+
|
|
"it would reach the machine as that text (novox/hq issue 231)%s",
|
|
module, r["id"], token, field, f.namespace, whereFilled(f)))
|
|
}
|
|
}
|
|
rest = p.ReplaceAllString(rest, "")
|
|
}
|
|
}
|
|
for _, token := range ofKnownNamespace.FindAllString(rest, -1) {
|
|
problems = append(problems, fmt.Sprintf(
|
|
"%s's resource %v holds %s in its %s, which is no placeholder the mesh fills: a "+
|
|
"misspelt key, or a namespace in the wrong case, would reach the machine as that "+
|
|
"text (novox/hq issue 231)", module, r["id"], token, field))
|
|
}
|
|
rest = ofKnownNamespace.ReplaceAllString(rest, "")
|
|
for _, token := range namespaceShaped.FindAllString(rest, -1) {
|
|
problems = append(problems, fmt.Sprintf(
|
|
"%s's resource %v holds %s in its %s, and no pass of the mesh fills it: a misspelt "+
|
|
"placeholder would reach the machine as that text (novox/hq issue 231). The mesh "+
|
|
"fills ${%s:…}; the shell's own syntax belongs in the module's shell code (ADR 0204)",
|
|
module, r["id"], token, field, strings.Join(namespacesOf(fillers), ":…}, ${")))
|
|
}
|
|
case []any:
|
|
for i, e := range v {
|
|
sweep(top, fmt.Sprintf("%s[%d]", field, i), e)
|
|
}
|
|
case map[string]any:
|
|
for _, k := range sortedKeys(v) {
|
|
sweep(top, field+"."+k, v[k])
|
|
}
|
|
}
|
|
}
|
|
for _, k := range sortedKeys(r) {
|
|
sweep(k, k, r[k])
|
|
}
|
|
return problems
|
|
}
|
|
|
|
// whereFilled says where a namespace's pass does fill, for a refusal of one written elsewhere.
|
|
func whereFilled(f filler) string {
|
|
if f.why != "" {
|
|
return ". " + strings.ToUpper(f.why[:1]) + f.why[1:]
|
|
}
|
|
if len(f.fields) == 0 {
|
|
return "; it is filled only in what a provider serves"
|
|
}
|
|
var where []string
|
|
for _, kind := range sortedKeys(f.fields) {
|
|
of := "a " + kind + "'s"
|
|
if kind == "*" {
|
|
of = "any resource's"
|
|
}
|
|
where = append(where, of+" "+strings.Join(f.fields[kind], ", "))
|
|
}
|
|
return "; it is filled in " + strings.Join(where, "; ")
|
|
}
|