From b1d875410ae0bbec9203f6ba3dce21923bdca773 Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 23 Oct 2026 09:10:09 +0200 Subject: [PATCH] logrotate: dry-run the base against logs of its own, so the shut-off unit level does not stat the machine's /var/log (issue 1891) The unit level runs in bubblewrap, where /var/log is not there; the base's /var/log/wtmp block has no missingok, so logrotate -d failed its stat and every mesh-catalog check that reached this test failed. --- .../cmd/logrotate-tools/manifest_test.go | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/modules/logrotate/cmd/logrotate-tools/manifest_test.go b/modules/logrotate/cmd/logrotate-tools/manifest_test.go index b6ca26cd..84bbfaa0 100644 --- a/modules/logrotate/cmd/logrotate-tools/manifest_test.go +++ b/modules/logrotate/cmd/logrotate-tools/manifest_test.go @@ -39,12 +39,8 @@ func TestTheBaseParses(t *testing.T) { t.Skip("logrotate is not installed here; the base configuration is not dry-run") } dir := t.TempDir() - content := strings.ReplaceAll(templateText(t, manifest(t).resource(t, "config")), "include "+RulesDir, "include "+filepath.Join(dir, "d")) - if err := os.Mkdir(filepath.Join(dir, "d"), 0o755); err != nil { - t.Fatal(err) - } conf := filepath.Join(dir, "logrotate.conf") - if err := os.WriteFile(conf, []byte(content), 0o644); err != nil { + if err := os.WriteFile(conf, []byte(dryRunBase(t, dir)), 0o644); err != nil { t.Fatal(err) } out, err := exec.Command(logrotate, "-d", "-s", filepath.Join(dir, "state"), conf).CombinedOutput() @@ -52,3 +48,40 @@ func TestTheBaseParses(t *testing.T) { t.Fatalf("logrotate -d: %v\n%s", err, out) } } + +// The dry run proves the base parses, not what the machine's logs are: the unit level runs shut off in +// bubblewrap (mesh-sdk buildstep.Isolated), where /var/log is not there, and a base block without +// missingok then fails its stat (issue 1891). +func TestTheDryRunReadsNothingOfTheMachine(t *testing.T) { + dir := t.TempDir() + content := dryRunBase(t, dir) + for _, machine := range []string{"include " + RulesDir, "/var/log/"} { + if strings.Contains(content, machine) { + t.Fatalf("the dry run's base still names %s of the machine:\n%s", machine, content) + } + } + for _, log := range []string{"wtmp", "btmp"} { + if _, err := os.Stat(filepath.Join(dir, "log", log)); err != nil { + t.Fatalf("the base's own log %s is not in the dry run's directory: %v", log, err) + } + } +} + +// dryRunBase is the base configuration pointed into dir: its include at dir/d, and its own logs at dir/log, +// both made there. +func dryRunBase(t *testing.T, dir string) string { + t.Helper() + content := strings.ReplaceAll(templateText(t, manifest(t).resource(t, "config")), "include "+RulesDir, "include "+filepath.Join(dir, "d")) + content = strings.ReplaceAll(content, "/var/log/", filepath.Join(dir, "log")+"/") + for _, d := range []string{"d", "log"} { + if err := os.Mkdir(filepath.Join(dir, d), 0o755); err != nil { + t.Fatal(err) + } + } + for _, log := range []string{"wtmp", "btmp"} { + if err := os.WriteFile(filepath.Join(dir, "log", log), nil, 0o644); err != nil { + t.Fatal(err) + } + } + return content +}