Allow a setting in a unit's name only as a template's whole instance, never leading with a dash, at most 64 characters
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered

Third review of mesh-catalog #147. A setting anywhere else in a unit's name could make the unit another
unit or another kind; it is now refused where the manifest is read. A value beginning with '-' would be
read by systemctl as an option, and a long one is no pool's name.
This commit is contained in:
jochen
2026-10-09 01:51:37 +02:00
parent e4d2868679
commit eb5f9d2f53
3 changed files with 81 additions and 3 deletions
+30 -3
View File
@@ -74,7 +74,33 @@ func settingInto(resource map[string]any, layers []Layer, module string) error {
// unitPart is what a setting may put into a unit's name: the characters systemd allows in a unit name,
// less the instance's `@` and the escape's `\`, and at least one of them. Anything else — a space, a slash,
// a newline that would begin a directive in the unit file the name ends up in — is refused, never written.
var unitPart = regexp.MustCompile(`^[A-Za-z0-9:_.-]+$`)
var unitPart = regexp.MustCompile(`^[A-Za-z0-9:_.][A-Za-z0-9:_.-]{0,63}$`)
// unitInstance is the one place a setting may stand in a unit's name: the whole instance of a template,
// after its `@` and before its suffix — `zfs-scrub-weekly@${setting:scrub-pool}.timer` — so a value can
// make the unit another instance of the same template and nothing else (third review of mesh-catalog #147).
var unitInstance = regexp.MustCompile(`^[A-Za-z0-9:_.-]+@\$\{setting:[a-z0-9][a-z0-9_.-]*\}\.[a-z]+$`)
// UnitSettingProblems refuses, where a manifest is read, a service whose unit name carries a setting
// anywhere but as a template's whole instance.
func UnitSettingProblems(m Manifest) []string {
var problems []string
for _, r := range m.Resources {
if fmt.Sprint(r["type"]) != "service" {
continue
}
unit, _ := r["unit"].(string)
if len(settingsUsed(unit)) == 0 && !strings.Contains(unit, "${setting:") {
continue
}
if !unitInstance.MatchString(unit) {
problems = append(problems, fmt.Sprintf("%s: the service %v's unit %q carries a setting outside a template's "+
"instance; a setting may stand only as the whole instance, after the @ and before the suffix "+
"(name@${setting:key}.timer)", m.Module, r["id"], unit))
}
}
return problems
}
// settingIntoUnit fills ${setting:…} in a service resource's unit name: a module that holds the
// distribution's timer for the pool the operator names cannot write the pool into its definition
@@ -96,8 +122,9 @@ func settingIntoUnit(resource map[string]any, layers []Layer, module string) err
}
v := plainly(value)
if !unitPart.MatchString(v) {
return fmt.Errorf("%s: the setting %q is %q, which cannot be part of the unit %s: a unit's name takes "+
"letters, digits, ':', '_', '.' and '-' only", module, key, v, unit)
return fmt.Errorf("%s: the setting %q is %q, which cannot be the instance of the unit %s: an instance "+
"takes letters, digits, ':', '_', '.' and '-', does not begin with '-' (a systemctl option), and is "+
"at most 64 characters", module, key, v, unit)
}
unit = strings.ReplaceAll(unit, "${setting:"+key+"}", v)
}