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
|
return problems
|
||||||
}
|
}
|
||||||
|
|
||||||
// configPlaced is every value a file, a unit, a directory's owner, a contribution or a served fact asks for, and
|
// ifConfigKeys is the keys a resource's `if-config` names, each `<key>` or `<key>=<value>` by its key, and what is
|
||||||
// every value a mergeable file takes.
|
// 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 {
|
func configPlaced(m catalogue.Manifest) map[string]bool {
|
||||||
placed := settingKeysUsedBy(m)
|
placed := settingKeysUsedBy(m)
|
||||||
|
for _, r := range m.Resources {
|
||||||
|
keys, _ := ifConfigKeys(r)
|
||||||
|
for _, k := range keys {
|
||||||
|
placed[k] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, r := range m.Resources {
|
for _, r := range m.Resources {
|
||||||
for _, k := range takenKeyList(r) {
|
for _, k := range takenKeyList(r) {
|
||||||
placed[k] = true
|
placed[k] = true
|
||||||
@@ -2783,6 +2833,8 @@ func parseManifest(raw []byte, predefined catalogue.PredefinedList) (catalogue.M
|
|||||||
problems = append(problems, SettingProblems(m)...)
|
problems = append(problems, SettingProblems(m)...)
|
||||||
problems = append(problems, TypedSettingProblems(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).
|
// And what it requires optionally, and the files it delivers only while bound (novox/hq ADR 0330).
|
||||||
problems = append(problems, OptionalProblems(m)...)
|
problems = append(problems, OptionalProblems(m)...)
|
||||||
// And which setting keys each mergeable file takes (setting_takes.go, novox/hq ADR 0326).
|
// 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")
|
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
|
// 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.
|
// provision's provider protects its consumers' data.
|
||||||
func caseConsumerDataInARequiredProvisionIsJudgedAgainstItsProvider(t *testing.T) {
|
func caseConsumerDataInARequiredProvisionIsJudgedAgainstItsProvider(t *testing.T) {
|
||||||
@@ -6126,6 +6157,7 @@ func TestRulesConfig(t *testing.T) {
|
|||||||
t.Run("ConfigRefusesAValueWithNeitherDefaultNorRequired", caseConfigRefusesAValueWithNeitherDefaultNorRequired)
|
t.Run("ConfigRefusesAValueWithNeitherDefaultNorRequired", caseConfigRefusesAValueWithNeitherDefaultNorRequired)
|
||||||
t.Run("ConfigRefusesAValueWithoutTypeOrDescription", caseConfigRefusesAValueWithoutTypeOrDescription)
|
t.Run("ConfigRefusesAValueWithoutTypeOrDescription", caseConfigRefusesAValueWithoutTypeOrDescription)
|
||||||
t.Run("ConfigStillRefusesAValueNoItemPlaces", caseConfigStillRefusesAValueNoItemPlaces)
|
t.Run("ConfigStillRefusesAValueNoItemPlaces", caseConfigStillRefusesAValueNoItemPlaces)
|
||||||
|
t.Run("IfConfigNamesADeclaredValueAndPlacesIt", caseIfConfigNamesADeclaredValueAndPlacesIt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRulesClaims runs the tests of one kind as subtests, each by its former name.
|
// TestRulesClaims runs the tests of one kind as subtests, each by its former name.
|
||||||
|
|||||||
Reference in New Issue
Block a user