V1 judge-if-config: a resource's if-config names a declared config value and places it
Builds: design 42 zfs-owns-zfs §2; answer A-24
This commit is contained in:
@@ -813,10 +813,60 @@ func TypedSettingProblems(m catalogue.Manifest) []string {
|
||||
return problems
|
||||
}
|
||||
|
||||
// configPlaced is every value a file, a unit, a directory's owner, a contribution or a served fact asks for, and
|
||||
// every value a mergeable file takes.
|
||||
// ifConfigKeys is the keys a resource's `if-config` names, each `<key>` or `<key>=<value>` by its key, and what is
|
||||
// not a list of strings (design 42 zfs-owns-zfs §2).
|
||||
func ifConfigKeys(r map[string]any) (keys []string, malformed bool) {
|
||||
raw, said := r[catalogue.IfConfigField]
|
||||
if !said {
|
||||
return nil, false
|
||||
}
|
||||
list, ok := raw.([]any)
|
||||
if !ok {
|
||||
return nil, true
|
||||
}
|
||||
for _, e := range list {
|
||||
s, ok := e.(string)
|
||||
if !ok {
|
||||
malformed = true
|
||||
continue
|
||||
}
|
||||
key, _, _ := strings.Cut(s, "=")
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return keys, malformed
|
||||
}
|
||||
|
||||
// IfConfigProblems are the resources whose `if-config` names a key the module does not declare under `config`, on a
|
||||
// resource of any type (design 42 zfs-owns-zfs §2, answer A-24).
|
||||
func IfConfigProblems(m catalogue.Manifest) []string {
|
||||
var out []string
|
||||
for _, r := range m.Resources {
|
||||
keys, malformed := ifConfigKeys(r)
|
||||
if malformed {
|
||||
out = append(out, fmt.Sprintf("%s's resource %v says %s as something but a list of <key> and <key>=<value> "+
|
||||
"strings", m.Module, r["id"], catalogue.IfConfigField))
|
||||
}
|
||||
for _, key := range keys {
|
||||
if _, declared := m.Settings[key]; !declared {
|
||||
out = append(out, fmt.Sprintf("%s's resource %v says %s with %q, and the module declares no such value "+
|
||||
"under %s: declare it, or name a declared one (design 42 zfs-owns-zfs §2)", m.Module, r["id"],
|
||||
catalogue.IfConfigField, key, catalogue.ConfigField))
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// configPlaced is every value a file, a unit, a directory's owner, a contribution or a served fact asks for, every
|
||||
// value a mergeable file takes, and every value a resource is declared under (`if-config`).
|
||||
func configPlaced(m catalogue.Manifest) map[string]bool {
|
||||
placed := settingKeysUsedBy(m)
|
||||
for _, r := range m.Resources {
|
||||
keys, _ := ifConfigKeys(r)
|
||||
for _, k := range keys {
|
||||
placed[k] = true
|
||||
}
|
||||
}
|
||||
for _, r := range m.Resources {
|
||||
for _, k := range takenKeyList(r) {
|
||||
placed[k] = true
|
||||
@@ -2783,6 +2833,8 @@ func parseManifest(raw []byte, predefined catalogue.PredefinedList) (catalogue.M
|
||||
problems = append(problems, SettingProblems(m)...)
|
||||
problems = append(problems, TypedSettingProblems(m)...)
|
||||
}
|
||||
// And the config values its resources are declared under (design 42 zfs-owns-zfs §2).
|
||||
problems = append(problems, IfConfigProblems(m)...)
|
||||
// And what it requires optionally, and the files it delivers only while bound (novox/hq ADR 0330).
|
||||
problems = append(problems, OptionalProblems(m)...)
|
||||
// And which setting keys each mergeable file takes (setting_takes.go, novox/hq ADR 0326).
|
||||
|
||||
@@ -3532,6 +3532,37 @@ func caseConfigStillRefusesAValueNoItemPlaces(t *testing.T) {
|
||||
refused(t, m, `"k"`, "placed nowhere")
|
||||
}
|
||||
|
||||
// Design 42 zfs-owns-zfs §2, V1/AC1 to AC3 (A-24): `if-config` names a declared value, on a resource of any type, and
|
||||
// places it.
|
||||
func caseIfConfigNamesADeclaredValueAndPlacesIt(t *testing.T) {
|
||||
m := inConfigForm()
|
||||
resourceOf(m, "notifier")["if-config"] = []any{"nowhere"}
|
||||
refused(t, m, "notifier", `resource notifier`, "if-config", `"nowhere"`)
|
||||
m = inConfigForm()
|
||||
resourceOf(m, "rules")["if-config"] = []any{"theme=dark", "nowhere=on"}
|
||||
refused(t, m, `"nowhere"`)
|
||||
m = inConfigForm()
|
||||
resourceOf(m, "rules")["if-config"] = "theme"
|
||||
refused(t, m, "if-config", "list")
|
||||
// <key> and <key>=<value> of a declared key, on a file, a service and a directory.
|
||||
m = inConfigForm()
|
||||
resourceOf(m, "configuration")["if-config"] = []any{"width", "theme=dark"}
|
||||
resourceOf(m, "notifier")["if-config"] = []any{"position=top-left"}
|
||||
m["resources"] = append(m["resources"].([]any), map[string]any{"id": "cache", "type": "directory", "path": "/x/c",
|
||||
"mode": "0755", "if-config": []any{"theme"}})
|
||||
if _, err := parseRaw(t, m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// A value placed only by if-config is not placed nowhere.
|
||||
m = inConfigForm()
|
||||
m["config"].(map[string]any)["zed"] = map[string]any{"type": "string", "description": "whether the daemon runs", "default": "off"}
|
||||
refused(t, m, `"zed"`, "placed nowhere")
|
||||
resourceOf(m, "notifier")["if-config"] = []any{"zed=on"}
|
||||
if _, err := parseRaw(t, m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Consumer data said to live in a provision the module requires is protected only as well as that
|
||||
// provision's provider protects its consumers' data.
|
||||
func caseConsumerDataInARequiredProvisionIsJudgedAgainstItsProvider(t *testing.T) {
|
||||
@@ -6126,6 +6157,7 @@ func TestRulesConfig(t *testing.T) {
|
||||
t.Run("ConfigRefusesAValueWithNeitherDefaultNorRequired", caseConfigRefusesAValueWithNeitherDefaultNorRequired)
|
||||
t.Run("ConfigRefusesAValueWithoutTypeOrDescription", caseConfigRefusesAValueWithoutTypeOrDescription)
|
||||
t.Run("ConfigStillRefusesAValueNoItemPlaces", caseConfigStillRefusesAValueNoItemPlaces)
|
||||
t.Run("IfConfigNamesADeclaredValueAndPlacesIt", caseIfConfigNamesADeclaredValueAndPlacesIt)
|
||||
}
|
||||
|
||||
// TestRulesClaims runs the tests of one kind as subtests, each by its former name.
|
||||
|
||||
Reference in New Issue
Block a user