buildstep: keep a check ask's cache layer out of every step's reach, move through an os.Root, and layer the build cache alone
Review of 62a50eb: the layer lay in the workspace, which every step binds read-write,
so a change's command could swap a kept directory for a symlink and the move, run
outside the sandbox, would write into the shared cache (ADR 0541 §2). The layer now
lies beside the workspace (Workspaces.CacheLayer, removed by Release and Sweep),
Command refuses a layer within reach of the workspace, a writable directory or a
cache, and every move goes through an os.Root on the layer and keeps no symlink. Only
LayerCaches (the build cache) are layered; a module cache keeps its throwaway layer
(issue 1921).
This commit is contained in:
@@ -66,8 +66,9 @@ func TestARunStepReachesNothingAndLeavesTheCachesUnchanged(t *testing.T) {
|
||||
|
||||
// 1921/S2: with the ask's cache layer, what one command writes into the cache the next command of the ask reads,
|
||||
// above what the shared cache holds, and the shared cache stays unchanged.
|
||||
layer := filepath.Join(filepath.Dir(workspace), "ws1.cachelayer")
|
||||
asked := Isolated(forgekey.ExecRunnerFor(0), Isolation{Workspace: workspace, Caches: []string{cacheDir},
|
||||
CacheLayer: filepath.Join(workspace, "cachelayer")})
|
||||
CacheLayer: layer, LayerCaches: []string{cacheDir}})
|
||||
r = asked(context.Background(), []string{"PATH=/usr/bin:/bin"}, "/bin/sh", "-c", "cat "+filepath.Join(cacheDir, "warm")+" && mkdir -p "+filepath.Join(cacheDir, "ab")+" && echo compiled > "+filepath.Join(cacheDir, "ab", "one-d"))
|
||||
if r.Status != 0 || r.Err != "" || strings.TrimSpace(r.Stdout) != "warm" {
|
||||
t.Fatalf("the first command of the ask did not read the shared cache and write its own (status %d, %s): %s%s", r.Status, r.Err, r.Stdout, r.Stderr)
|
||||
@@ -76,10 +77,23 @@ func TestARunStepReachesNothingAndLeavesTheCachesUnchanged(t *testing.T) {
|
||||
if r.Status != 0 || r.Err != "" || r.Stdout != "compiled\nwarm\n" {
|
||||
t.Fatalf("the next command of the ask did not find what the first wrote (status %d, %s): %q %s", r.Status, r.Err, r.Stdout, r.Stderr)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(cacheDir, "ab")); !os.IsNotExist(err) {
|
||||
t.Fatalf("the ask's commands wrote the shared cache: %v", err)
|
||||
// A command cannot reach the layer (it lies outside every directory the step binds), and a symlink it writes into
|
||||
// the cache is not kept, so nothing it does makes the keeping write outside the layer.
|
||||
r = asked(context.Background(), []string{"PATH=/usr/bin:/bin"}, "/bin/sh", "-c",
|
||||
"ls "+layer+" >/dev/null 2>&1 && echo reached; ln -s "+cacheDir+" "+filepath.Join(cacheDir, "esc")+" && mkdir "+filepath.Join(cacheDir, "cd")+" && echo x > "+filepath.Join(cacheDir, "cd", "f"))
|
||||
if r.Status != 0 || r.Err != "" || strings.Contains(r.Stdout, "reached") {
|
||||
t.Fatalf("the command reached the cache layer or could not write the cache (status %d, %s): %q %s", r.Status, r.Err, r.Stdout, r.Stderr)
|
||||
}
|
||||
if err := os.RemoveAll(filepath.Join(workspace, "cachelayer")); err != nil {
|
||||
t.Fatalf("the ask's cache layer cannot be removed with its workspace: %v", err)
|
||||
if fi, err := os.Lstat(filepath.Join(keptLayer(layer, 0), "esc")); err == nil {
|
||||
t.Fatalf("a symlink the command wrote was kept: %v", fi.Mode())
|
||||
}
|
||||
if b, err := os.ReadFile(filepath.Join(keptLayer(layer, 0), "cd", "f")); err != nil || string(b) != "x\n" {
|
||||
t.Fatalf("the command's file was not kept: %q %v", b, err)
|
||||
}
|
||||
if entries, _ := os.ReadDir(cacheDir); len(entries) != 1 || entries[0].Name() != "warm" {
|
||||
t.Fatalf("the ask's commands wrote the shared cache: %v", entries)
|
||||
}
|
||||
if err := os.RemoveAll(layer); err != nil {
|
||||
t.Fatalf("the ask's cache layer cannot be removed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user