Make buildah's runtime directory before every buildah run, so mesh-build-image's health does not fail for want of it (issue 1884) #456

Merged
mesh-tools merged 1 commits from fix/1884-runtime-dir into main 2026-10-22 21:48:47 +00:00
3 changed files with 108 additions and 8 deletions
@@ -4,8 +4,6 @@ import (
"context"
"fmt"
"net/http"
"os"
"path/filepath"
"time"
"git.novox.be/novox/mesh-sdk/go/buildstep"
@@ -60,6 +58,11 @@ type Look struct {
// missing is a *buildstep.Refusal naming it, so a step ends refused and the health says the same.
func (m *Module) look(ctx context.Context) (Look, error) {
l := Look{Account: m.Account}
// The runtime directory comes first: buildah refuses before anything else when its XDG_RUNTIME_DIR is not there
// (issue 1884), so a look that made it only after `buildah --version` never made it at all.
if err := m.ensureRuntimeDir(); err != nil {
return l, &buildstep.Refusal{Text: err.Error()}
}
var err error
if l.Buildah, err = m.buildahVersion(ctx); err != nil {
return l, &buildstep.Refusal{Text: err.Error()}
@@ -67,11 +70,6 @@ func (m *Module) look(ctx context.Context) (Look, error) {
if l.UIDs, l.GIDs, err = m.ranges(); err != nil {
return l, &buildstep.Refusal{Text: err.Error(), Have: l.Buildah}
}
for _, d := range []string{m.Store, filepath.Join(m.Store, "xdg")} {
if err := os.MkdirAll(d, 0o700); err != nil {
return l, err
}
}
if err := m.userNamespace(ctx); err != nil {
return l, &buildstep.Refusal{Text: err.Error(), Have: l.Buildah}
}
@@ -111,6 +111,25 @@ func (m *Module) storeFlags() []string {
// env is what buildah is given beyond the base environment: a runtime directory of its own inside the store, so
// nothing is read from the account's runtime directory, and no registry credential file anywhere (the module has none
// and REGISTRY_AUTH_FILE is never set).
//
// Every call makes the runtime directory first if it is not there (issue 1884): a directory removed after the last look
// is made again before buildah runs. A failure to make it is not lost: buildah then names the directory in its own
// error, and the next look refuses with it.
func (m *Module) env() []string {
return []string{"XDG_RUNTIME_DIR=" + filepath.Join(m.Store, "xdg")}
_ = m.ensureRuntimeDir()
return []string{"XDG_RUNTIME_DIR=" + m.runtimeDir()}
}
// runtimeDir is buildah's runtime directory, inside the store.
func (m *Module) runtimeDir() string { return filepath.Join(m.Store, "xdg") }
// ensureRuntimeDir makes the store and the runtime directory inside it, 0700 and under the module's own account (the
// process runs as it), when they are not there; it changes nothing that is.
func (m *Module) ensureRuntimeDir() error {
for _, d := range []string{m.Store, m.runtimeDir()} {
if err := os.MkdirAll(d, 0o700); err != nil {
return fmt.Errorf("buildah's runtime directory %s cannot be made: %w", m.runtimeDir(), err)
}
}
return nil
}
@@ -0,0 +1,83 @@
package main
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/forgekey"
"git.novox.be/novox/mesh-sdk/go/health"
)
// buildahLike refuses as buildah does when the XDG_RUNTIME_DIR it is given is not there (seen on ace, issue 1884):
// "lstat <dir>: no such file or directory", before it does anything else.
func buildahLike(t *testing.T, run forgekey.Runner, seen *[]string) forgekey.Runner {
t.Helper()
return func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
if name == "buildah" {
for _, e := range env {
dir, ok := strings.CutPrefix(e, "XDG_RUNTIME_DIR=")
if !ok {
continue
}
if _, err := os.Lstat(dir); err != nil {
*seen = append(*seen, "missing")
return forgekey.Ran{Status: 125, Stderr: "Error: lstat " + dir + ": no such file or directory\n"}
}
*seen = append(*seen, "present")
}
}
return run(ctx, env, name, args...)
}
}
// 1884/AC1: given a store directory that exists without its runtime directory, when the health is looked at, then the
// module makes the runtime directory (0700) before it runs buildah, and the health is healthy.
func TestHealthMakesTheRuntimeDirectoryBeforeBuildah(t *testing.T) {
f := newFake(t)
m := moduleWith(t, f, newStates())
var seen []string
m.Run = buildahLike(t, m.Run, &seen)
xdg := filepath.Join(m.Store, "xdg")
if _, err := os.Stat(xdg); !os.IsNotExist(err) {
t.Fatalf("the test needs a store without its runtime directory: %v", err)
}
a := m.Health(context.Background())
if a.State != health.StateHealthy || !a.Healthy {
t.Fatalf("health = %+v, want healthy", a)
}
st, err := os.Stat(xdg)
if err != nil {
t.Fatalf("the runtime directory was not made: %v", err)
}
if st.Mode().Perm() != 0o700 || !st.IsDir() {
t.Errorf("the runtime directory is %v, want a directory 0700", st.Mode())
}
for _, s := range seen {
if s != "present" {
t.Fatalf("buildah ran without its runtime directory: %v", seen)
}
}
}
// 1884/AC2: given a module whose runtime directory was removed after its last look, when a build step runs buildah, then
// the runtime directory is made again before buildah runs.
func TestEveryBuildahRunMakesTheRuntimeDirectoryAgain(t *testing.T) {
f := newFake(t)
m := moduleWith(t, f, newStates())
var seen []string
m.Run = buildahLike(t, m.Run, &seen)
if err := m.Check(context.Background()); err != nil {
t.Fatal(err)
}
if err := os.RemoveAll(filepath.Join(m.Store, "xdg")); err != nil {
t.Fatal(err)
}
seen = nil
m.remove("localhost/x:y")
if len(seen) != 1 || seen[0] != "present" {
t.Fatalf("buildah's runs saw their runtime directory %v, want [present]", seen)
}
}