docker: make log rotation a preference, 100m x 10 by default, judged by a step before daemon.json takes it (novox/hq issue 452)
mesh/merge-gate pass: builds docker → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery covered after its failure: a later walk that contains it was delivered: plan-1791734868297400826 (novox/mesh-catalog at 0a558319 into main,…
mesh/merge-gate pass: builds docker → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery covered after its failure: a later walk that contains it was delivered: plan-1791734868297400826 (novox/mesh-catalog at 0a558319 into main,…
The controller pastes a setting into daemon.json as text and the runtime is its only reader, so a bad size would surface as a runtime that does not start and a quote could add any key. A step refuses the value first and gates the module. The defaults are the one hand-set machine's, so it does not change; nothing restarts the runtime, which takes the keys at its next start.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
package main
|
||||
|
||||
// The container runtime's log rotation, checked before it reaches daemon.json (novox/hq issue 452).
|
||||
//
|
||||
// **Why a step and not the file alone.** daemon.json's log-driver and log-opts are filled by the controller
|
||||
// from this module's settings log-max-size and log-max-file, pasted as text (novox/hq issue 346): the
|
||||
// controller judges nothing but that a value is one line. The runtime is the only reader of daemon.json and
|
||||
// reads log-opts only when it starts, so a bad value would be found at the runtime's next start, as a runtime
|
||||
// that does not start, and a value with a quote in it could add any key to the file. So the module's
|
||||
// manifest writes the same two values into log-rotation.conf first, and runs this as a step before daemon.json:
|
||||
// a value refused here fails the step, and the host skips the rest of the module (novox/hq ADR 0136), so
|
||||
// daemon.json keeps what it had, and the refusal is in the apply's report.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// logRotation is what log-rotation.conf says, both values as the runtime takes them: text.
|
||||
type logRotation struct{ Size, Files string }
|
||||
|
||||
var (
|
||||
// A size as the json-file driver reads it, with the unit required and in lower case, as decided on issue
|
||||
// 452: a whole number from 1 with k, m or g, at most nine digits, so no value overflows the runtime's count.
|
||||
logSize = regexp.MustCompile(`^[1-9][0-9]{0,8}[kmg]$`)
|
||||
// A count of files from 1 to 99, without a leading zero.
|
||||
logFiles = regexp.MustCompile(`^[1-9][0-9]?$`)
|
||||
)
|
||||
|
||||
// parseLogRotation reads `size=` and `files=` once each; a comment or an empty line is skipped, and any other
|
||||
// line is refused, so a value that brought a line of its own is never read past.
|
||||
func parseLogRotation(text string) (logRotation, error) {
|
||||
var got logRotation
|
||||
seen := map[string]bool{}
|
||||
for _, line := range strings.Split(text, "\n") {
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
key, value, ok := strings.Cut(line, "=")
|
||||
if !ok || (key != "size" && key != "files") {
|
||||
return logRotation{}, fmt.Errorf("log-rotation.conf has a line that is not size= or files=: %q", line)
|
||||
}
|
||||
if seen[key] {
|
||||
return logRotation{}, fmt.Errorf("log-rotation.conf says %s twice", key)
|
||||
}
|
||||
seen[key] = true
|
||||
switch key {
|
||||
case "size":
|
||||
if !logSize.MatchString(value) {
|
||||
return logRotation{}, fmt.Errorf("log-max-size %q is not a size: a whole number from 1 with k, m or g "+
|
||||
"after it, such as 100m; daemon.json was left as it is", value)
|
||||
}
|
||||
got.Size = value
|
||||
case "files":
|
||||
if !logFiles.MatchString(value) {
|
||||
return logRotation{}, fmt.Errorf("log-max-file %q is not a count from 1 to 99; daemon.json was left "+
|
||||
"as it is", value)
|
||||
}
|
||||
got.Files = value
|
||||
}
|
||||
}
|
||||
for _, key := range []string{"size", "files"} {
|
||||
if !seen[key] {
|
||||
return logRotation{}, fmt.Errorf("log-rotation.conf does not say %s", key)
|
||||
}
|
||||
}
|
||||
return got, nil
|
||||
}
|
||||
|
||||
// checkLogRotationFile is the step: the file the manifest wrote, judged.
|
||||
func checkLogRotationFile(path string) error {
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = parseLogRotation(string(raw))
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user