fix: chown migrated legacy storage entries to dokku
`SaveEntry` and `touchMigrationFlag` wrote files via `os.WriteFile` and `os.Create` and never chowned them, so the install-time legacy-mount migration produced root-owned `legacy-*.json` files in `/var/lib/dokku/data/storage-registry/entries/`. The dokku user that runs `ps:rebuild` could not read them and every rebuild on a 0.37.x to 0.38.0 upgrade failed with permission denied. The companion `repairRegistryOwnership` pass in `TriggerInstall` rewrites ownership across the whole registry tree so installs that already ran the buggy code are fixed on the next package upgrade, since the per-app migration flag would otherwise cause `MigrateLegacyMounts` to skip the broken files forever.
This commit is contained in:
@@ -5,36 +5,26 @@ import (
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
dockeroptions "github.com/dokku/dokku/plugins/docker-options"
|
||||
)
|
||||
|
||||
// setupMigrationEnv points DOKKU_LIB_ROOT and DOKKU_ROOT at temp dirs and
|
||||
// tells the permission helpers to chown to the current user (a no-op),
|
||||
// so the test works without root and without the dokku system user.
|
||||
// Mirrors the helper in docker-options/migration_test.go.
|
||||
// setupMigrationEnv reuses withTempLibRoot for DOKKU_LIB_ROOT plus the
|
||||
// permission-helper user/group overrides, then layers DOKKU_ROOT and
|
||||
// PLUGIN_PATH on top so DokkuApps can find the staged apps. Mirrors the
|
||||
// docker-options migration test fixture.
|
||||
func setupMigrationEnv(t *testing.T) (libRoot string, dokkuRoot string) {
|
||||
t.Helper()
|
||||
libRoot = t.TempDir()
|
||||
libRoot = withTempLibRoot(t)
|
||||
dokkuRoot = t.TempDir()
|
||||
|
||||
t.Setenv("DOKKU_LIB_ROOT", libRoot)
|
||||
t.Setenv("DOKKU_ROOT", dokkuRoot)
|
||||
t.Setenv("PLUGIN_PATH", filepath.Join(libRoot, "plugins"))
|
||||
|
||||
current, err := user.Current()
|
||||
if err != nil {
|
||||
t.Fatalf("user.Current: %v", err)
|
||||
}
|
||||
group, err := user.LookupGroupId(current.Gid)
|
||||
if err != nil {
|
||||
t.Fatalf("user.LookupGroupId: %v", err)
|
||||
}
|
||||
t.Setenv("DOKKU_SYSTEM_USER", current.Username)
|
||||
t.Setenv("DOKKU_SYSTEM_GROUP", group.Name)
|
||||
|
||||
// MigrateLegacyMounts creates the flag dir before iterating apps;
|
||||
// migrateApp (the per-app helper) assumes it already exists. Pre-
|
||||
// create it so tests that call migrateApp directly don't trip.
|
||||
@@ -300,6 +290,40 @@ func TestMigrateAppRefusesEntryNameCollision(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestMigrateAppChownsLegacyEntryFiles is the regression for #8557:
|
||||
// migrating from the install trigger (which runs as root) must produce
|
||||
// dokku-owned legacy-*.json files. setupMigrationEnv overrides the
|
||||
// system user/group to the current process user, so we assert against
|
||||
// that uid rather than the literal dokku one.
|
||||
func TestMigrateAppChownsLegacyEntryFiles(t *testing.T) {
|
||||
_, dokkuRoot := setupMigrationEnv(t)
|
||||
stageApp(t, dokkuRoot, "alpha", map[string][]string{
|
||||
"deploy": {"-v /var/log:/log"},
|
||||
})
|
||||
|
||||
if err := migrateApp("alpha"); err != nil {
|
||||
t.Fatalf("migrateApp: %v", err)
|
||||
}
|
||||
|
||||
entry := LegacyMountToEntry("/var/log:/log")
|
||||
info, err := os.Stat(entryPath(entry.Name))
|
||||
if err != nil {
|
||||
t.Fatalf("stat entry: %v", err)
|
||||
}
|
||||
stat, ok := info.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
t.Fatalf("stat.Sys is not *syscall.Stat_t")
|
||||
}
|
||||
|
||||
current, err := user.Current()
|
||||
if err != nil {
|
||||
t.Fatalf("user.Current: %v", err)
|
||||
}
|
||||
if got := strconv.Itoa(int(stat.Uid)); got != current.Uid {
|
||||
t.Errorf("entry file uid = %s, want %s", got, current.Uid)
|
||||
}
|
||||
}
|
||||
|
||||
func equalSorted(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user