mirror of
https://github.com/moby/buildkit.git
synced 2026-08-05 23:30:22 +00:00
Move client integration test declarations out of the large harness files into focused files for cache, exporters, sources, mounts, gateway behavior, and related client features. Keep registration lists grouped by source file, move non-gateway tests out of the gateway harness, and remove the now-empty build_test.go. This is intended as a mechanical split with no test logic changes. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
1081 lines
33 KiB
Go
1081 lines
33 KiB
Go
package client
|
|
|
|
import (
|
|
"archive/tar"
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/moby/buildkit/client/llb"
|
|
"github.com/moby/buildkit/session"
|
|
"github.com/moby/buildkit/session/secrets/secretsprovider"
|
|
"github.com/moby/buildkit/session/sshforward/sshprovider"
|
|
"github.com/moby/buildkit/util/testutil"
|
|
"github.com/moby/buildkit/util/testutil/integration"
|
|
"github.com/pkg/errors"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/crypto/ssh/agent"
|
|
)
|
|
|
|
func testBuildMultiMount(t *testing.T, sb integration.Sandbox) {
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
imgAlpine := integration.UnixOrWindows(
|
|
"docker.io/library/alpine:latest",
|
|
"nanoserver",
|
|
)
|
|
imgBusybox := integration.UnixOrWindows(
|
|
"docker.io/library/busybox:latest",
|
|
"nanoserver",
|
|
)
|
|
cmdStr1 := integration.UnixOrWindows(
|
|
"/bin/ls -l",
|
|
"cmd /C dir",
|
|
)
|
|
cmdStr2 := integration.UnixOrWindows(
|
|
"/bin/cp -a /busybox/etc/passwd baz",
|
|
`cmd /C copy C:\\busybox\\License.txt baz`,
|
|
)
|
|
|
|
alpine := llb.Image(imgAlpine)
|
|
ls := alpine.Run(llb.Shlex(cmdStr1))
|
|
busybox := llb.Image(imgBusybox)
|
|
cp := ls.Run(llb.Shlex(cmdStr2))
|
|
cp.AddMount("/busybox", busybox)
|
|
|
|
def, err := cp.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
|
|
checkAllReleasable(t, c, sb, true)
|
|
}
|
|
|
|
func testCachedMounts(t *testing.T, sb integration.Sandbox) {
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
imgName := integration.UnixOrWindows("busybox:latest", "nanoserver:latest")
|
|
scratch := func() llb.State {
|
|
return integration.UnixOrWindows(llb.Scratch(), llb.Image(imgName))
|
|
}
|
|
|
|
busybox := llb.Image(imgName)
|
|
// setup base for one of the cache sources
|
|
cmdPrefix := integration.UnixOrWindows(
|
|
`sh -c "echo -n`,
|
|
`cmd /C "echo`,
|
|
)
|
|
st := busybox.Run(llb.Shlexf(`%s base > baz"`, cmdPrefix), llb.Dir("/wd"))
|
|
base := st.AddMount("/wd", scratch())
|
|
|
|
st = busybox.Run(llb.Shlexf(`%s first > foo"`, cmdPrefix), llb.Dir("/wd"))
|
|
|
|
st.AddMount("/wd", scratch(), llb.AsPersistentCacheDir("mycache1", llb.CacheMountShared))
|
|
cmdStr := integration.UnixOrWindows(
|
|
`sh -c "cat foo && echo -n second > /wd2/bar"`,
|
|
`cmd /C type foo && echo second > C:\\wd2\\bar`,
|
|
)
|
|
st = st.Run(llb.Shlex(cmdStr), llb.Dir("/wd"))
|
|
st.AddMount("/wd", scratch(), llb.AsPersistentCacheDir("mycache1", llb.CacheMountShared))
|
|
st.AddMount("/wd2", base, llb.AsPersistentCacheDir("mycache2", llb.CacheMountShared))
|
|
|
|
def, err := st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
|
|
// repeat to make sure cache works
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
|
|
// second build using cache directories
|
|
cmdStr = integration.UnixOrWindows(
|
|
`sh -c "cp /src0/foo . && cp /src1/bar . && cp /src1/baz ."`,
|
|
`cmd /C copy C:\\src0\\foo . && copy C:\\src1\\bar . && copy C:\\src1\\baz .`,
|
|
)
|
|
st = busybox.Run(llb.Shlex(cmdStr), llb.Dir("/wd"))
|
|
out := st.AddMount("/wd", scratch())
|
|
st.AddMount("/src0", scratch(), llb.AsPersistentCacheDir("mycache1", llb.CacheMountShared))
|
|
st.AddMount("/src1", base, llb.AsPersistentCacheDir("mycache2", llb.CacheMountShared))
|
|
|
|
destDir := t.TempDir()
|
|
|
|
def, err = out.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Exports: []ExportEntry{
|
|
{
|
|
Type: ExporterLocal,
|
|
OutputDir: destDir,
|
|
},
|
|
},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
|
|
newLine := integration.UnixOrWindows("", " \r\n")
|
|
dt, err := os.ReadFile(filepath.Join(destDir, "foo"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, "first"+newLine, string(dt))
|
|
|
|
dt, err = os.ReadFile(filepath.Join(destDir, "bar"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, "second"+newLine, string(dt))
|
|
|
|
dt, err = os.ReadFile(filepath.Join(destDir, "baz"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, "base"+newLine, string(dt))
|
|
|
|
checkAllReleasable(t, c, sb, true)
|
|
}
|
|
|
|
func testCacheMountNoCache(t *testing.T, sb integration.Sandbox) {
|
|
requiresLinux(t)
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
busybox := llb.Image("busybox:latest")
|
|
|
|
out := busybox.Run(llb.Shlex(`sh -e -c "touch /m1/foo; touch /m2/bar"`))
|
|
out.AddMount("/m1", llb.Scratch(), llb.AsPersistentCacheDir("mycache1", llb.CacheMountLocked))
|
|
out.AddMount("/m2", llb.Scratch(), llb.AsPersistentCacheDir("mycache2", llb.CacheMountLocked))
|
|
|
|
def, err := out.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
|
|
out = busybox.Run(llb.Shlex(`sh -e -c "[[ ! -f /m1/foo ]]; touch /m1/foo2;"`), llb.IgnoreCache)
|
|
out.AddMount("/m1", llb.Scratch(), llb.AsPersistentCacheDir("mycache1", llb.CacheMountLocked))
|
|
|
|
def, err = out.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
|
|
out = busybox.Run(llb.Shlex(`sh -e -c "[[ -f /m1/foo2 ]]; [[ -f /m2/bar ]];"`))
|
|
out.AddMount("/m1", llb.Scratch(), llb.AsPersistentCacheDir("mycache1", llb.CacheMountLocked))
|
|
out.AddMount("/m2", llb.Scratch(), llb.AsPersistentCacheDir("mycache2", llb.CacheMountLocked))
|
|
|
|
def, err = out.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func testDuplicateCacheMount(t *testing.T, sb integration.Sandbox) {
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
imgName := integration.UnixOrWindows("busybox:latest", "nanoserver:latest")
|
|
busybox := llb.Image(imgName)
|
|
scratch := func() llb.State {
|
|
return integration.UnixOrWindows(llb.Scratch(), llb.Image(imgName))
|
|
}
|
|
|
|
cmdStr := integration.UnixOrWindows(
|
|
`sh -e -c "[[ ! -f /m2/foo ]]; touch /m1/foo; [[ -f /m2/foo ]];"`,
|
|
`cmd /C echo a > \\m1\\foo && dir \\m2\\foo`,
|
|
)
|
|
out := busybox.Run(llb.Shlex(cmdStr))
|
|
out.AddMount("/m1", scratch(), llb.AsPersistentCacheDir("mycache1", llb.CacheMountLocked))
|
|
out.AddMount("/m2", scratch(), llb.AsPersistentCacheDir("mycache1", llb.CacheMountLocked))
|
|
|
|
def, err := out.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func testLayerLimitOnMounts(t *testing.T, sb integration.Sandbox) {
|
|
integration.SkipOnPlatform(t, "windows")
|
|
|
|
ctx := sb.Context()
|
|
|
|
c, err := New(ctx, sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
base := llb.Image("busybox:latest")
|
|
|
|
const numLayers = 110
|
|
|
|
for range numLayers {
|
|
base = base.Run(llb.Shlex("sh -c 'echo hello >> /hello'")).Root()
|
|
}
|
|
|
|
def, err := base.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(ctx, def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
|
|
ls := llb.Image("busybox:latest").
|
|
Run(llb.Shlexf("ls -l /base/hello"))
|
|
ls.AddMount("/base", base, llb.Readonly)
|
|
|
|
def, err = ls.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(ctx, def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func testLLBMountPerformance(t *testing.T, sb integration.Sandbox) {
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
imgName := integration.UnixOrWindows("busybox:latest", "nanoserver:latest")
|
|
srcPath := integration.UnixOrWindows("/bin", "/Windows")
|
|
|
|
mntInput := llb.Image(imgName)
|
|
st := llb.Image(imgName)
|
|
var mnts []llb.State
|
|
// Reduce iterations on Windows due to significantly slower container operations
|
|
numIterations := integration.UnixOrWindows(20, 5)
|
|
for range numIterations {
|
|
execSt := st.Run(
|
|
llb.Args(integration.UnixOrWindows(
|
|
[]string{"true"},
|
|
[]string{"cmd", "/C", "exit 0"},
|
|
)),
|
|
)
|
|
mnts = append(mnts, mntInput)
|
|
for j := range mnts {
|
|
mnts[j] = execSt.AddMount(fmt.Sprintf("/tmp/bin%d", j), mnts[j], llb.SourcePath(srcPath))
|
|
}
|
|
st = execSt.Root()
|
|
}
|
|
|
|
def, err := st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
// Windows images take longer time, especially on CI systems
|
|
// With reduced iterations (5 vs 20), use generous timeout
|
|
timeout := integration.UnixOrWindows(time.Minute, 10*time.Minute)
|
|
timeoutCtx, cancel := context.WithTimeoutCause(sb.Context(), timeout, nil)
|
|
defer cancel()
|
|
_, err = c.Solve(timeoutCtx, def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func testLockedCacheMounts(t *testing.T, sb integration.Sandbox) {
|
|
requiresLinux(t)
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
busybox := llb.Image("busybox:latest")
|
|
st := busybox.Run(llb.Shlex(`sh -e -c "touch one; if [[ -f two ]]; then exit 0; fi; for i in $(seq 10); do if [[ -f two ]]; then exit 1; fi; usleep 200000; done"`), llb.Dir("/wd"))
|
|
st.AddMount("/wd", llb.Scratch(), llb.AsPersistentCacheDir("mycache1", llb.CacheMountLocked))
|
|
|
|
st2 := busybox.Run(llb.Shlex(`sh -e -c "touch two; if [[ -f one ]]; then exit 0; fi; for i in $(seq 10); do if [[ -f one ]]; then exit 1; fi; usleep 200000; done"`), llb.Dir("/wd"))
|
|
st2.AddMount("/wd", llb.Scratch(), llb.AsPersistentCacheDir("mycache1", llb.CacheMountLocked))
|
|
|
|
out := busybox.Run(llb.Shlex("true"))
|
|
out.AddMount("/m1", st.Root())
|
|
out.AddMount("/m2", st2.Root())
|
|
|
|
def, err := out.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// testMountStubsDirectory verifies that BuildKit cleans up stub directories created for tmpfs
|
|
// mounts after a Run step completes. Empty stubs should be removed, stubs with user content
|
|
// should be kept, and pre-existing directories used as mount targets should be preserved.
|
|
func testMountStubsDirectory(t *testing.T, sb integration.Sandbox) {
|
|
// Skipped on Windows because the test relies on tmpfs mounts, multiple simultaneous AddMount
|
|
// calls, and llb.Scratch() — none of which are supported on Windows containers. The stub
|
|
// cleanup behavior is also Linux-specific (tied to overlayfs mount point handling).
|
|
integration.SkipOnPlatform(t, "windows", "requires tmpfs, multiple mounts, and Scratch — all unsupported on Windows")
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
st := llb.Image("busybox:latest").
|
|
File(llb.Mkdir("/test", 0700)).
|
|
File(llb.Mkdir("/test/qux/", 0700)).
|
|
Run(
|
|
llb.Args([]string{"touch", "/test/baz/keep"}),
|
|
// check stubs directory is removed
|
|
llb.AddMount("/test/foo", llb.Scratch(), llb.Tmpfs()),
|
|
// check that stubs directory are recursively removed
|
|
llb.AddMount("/test/bar/x/y", llb.Scratch(), llb.Tmpfs()),
|
|
// check that only empty stubs directories are removed
|
|
llb.AddMount("/test/baz/x", llb.Scratch(), llb.Tmpfs()),
|
|
// check that previously existing directory are not removed
|
|
llb.AddMount("/test/qux", llb.Scratch(), llb.Tmpfs()),
|
|
).Root()
|
|
st = llb.Scratch().File(llb.Copy(st, "/test", "/", &llb.CopyInfo{CopyDirContentsOnly: true}))
|
|
def, err := st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
tmpDir := t.TempDir()
|
|
tarFile := filepath.Join(tmpDir, "out.tar")
|
|
tarFileW, err := os.Create(tarFile)
|
|
require.NoError(t, err)
|
|
defer tarFileW.Close()
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Exports: []ExportEntry{
|
|
{
|
|
Type: ExporterTar,
|
|
Output: fixedWriteCloser(tarFileW),
|
|
},
|
|
},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
tarFileW.Close()
|
|
|
|
dt, err := os.ReadFile(tarFile)
|
|
require.NoError(t, err)
|
|
|
|
m, err := testutil.ReadTarToMap(dt, false)
|
|
require.NoError(t, err)
|
|
|
|
keys := make([]string, 0, len(m))
|
|
for k := range m {
|
|
keys = append(keys, k)
|
|
}
|
|
|
|
require.ElementsMatch(t, []string{
|
|
"baz/",
|
|
"baz/keep",
|
|
"qux/",
|
|
}, keys)
|
|
}
|
|
|
|
// testMountStubsTimestamp verifies that timestamps set on directories used as mount points
|
|
// (and their parents) are preserved after the mount is removed.
|
|
// https://github.com/moby/buildkit/issues/3148
|
|
func testMountStubsTimestamp(t *testing.T, sb integration.Sandbox) {
|
|
// Skipped on Windows because the test relies on tmpfs mounts, multiple AddMount calls,
|
|
// Scratch, and Linux-specific commands — all unsupported on Windows containers.
|
|
integration.SkipOnPlatform(t, "windows", "requires tmpfs, multiple mounts, and Scratch — all unsupported on Windows")
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
const sourceDateEpoch = int64(1234567890) // Fri Feb 13 11:31:30 PM UTC 2009
|
|
st := llb.Image("busybox:latest").Run(
|
|
llb.Args([]string{
|
|
"/bin/touch", fmt.Sprintf("--date=@%d", sourceDateEpoch),
|
|
"/bin",
|
|
"/etc",
|
|
"/var",
|
|
"/var/foo",
|
|
"/tmp",
|
|
"/tmp/foo2",
|
|
"/tmp/foo2/bar",
|
|
}),
|
|
llb.AddMount("/var/foo", llb.Scratch(), llb.Tmpfs()),
|
|
llb.AddMount("/tmp/foo2/bar", llb.Scratch(), llb.Tmpfs()),
|
|
)
|
|
def, err := st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
tmpDir := t.TempDir()
|
|
tarFile := filepath.Join(tmpDir, "out.tar")
|
|
tarFileW, err := os.Create(tarFile)
|
|
require.NoError(t, err)
|
|
defer tarFileW.Close()
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Exports: []ExportEntry{
|
|
{
|
|
Type: ExporterTar,
|
|
Output: fixedWriteCloser(tarFileW),
|
|
},
|
|
},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
tarFileW.Close()
|
|
|
|
tarFileR, err := os.Open(tarFile)
|
|
require.NoError(t, err)
|
|
defer tarFileR.Close()
|
|
tarR := tar.NewReader(tarFileR)
|
|
touched := map[string]*tar.Header{
|
|
"bin/": nil, // Regular dir
|
|
"etc/": nil, // Parent of file mounts (etc/{resolv.conf, hosts})
|
|
"var/": nil, // Parent of dir mount (var/foo/)
|
|
"tmp/": nil, // Grandparent of dir mount (tmp/foo2/bar/)
|
|
// No support for reproducing the timestamps of mount point directories such as var/foo/ and tmp/foo2/bar/,
|
|
// because the touched timestamp value is lost when the mount is unmounted.
|
|
}
|
|
for {
|
|
hd, err := tarR.Next()
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
require.NoError(t, err)
|
|
if x, ok := touched[hd.Name]; ok && x == nil {
|
|
touched[hd.Name] = hd
|
|
}
|
|
}
|
|
for name, hd := range touched {
|
|
t.Logf("Verifying %q (%+v)", name, hd)
|
|
require.NotNil(t, hd, name)
|
|
require.Equal(t, sourceDateEpoch, hd.ModTime.Unix(), name)
|
|
}
|
|
}
|
|
|
|
// #319
|
|
func testMountWithNoSource(t *testing.T, sb integration.Sandbox) {
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
imgName := integration.UnixOrWindows("busybox:latest", "nanoserver:latest")
|
|
busybox := llb.Image(imgName)
|
|
st := integration.UnixOrWindows(llb.Scratch(), llb.Image(imgName))
|
|
|
|
var nilState llb.State
|
|
|
|
// This should never actually be run, but we want to succeed
|
|
// if it was, because we expect an error below, or a daemon
|
|
// panic if the issue has regressed.
|
|
cmdArgs := integration.UnixOrWindows(
|
|
[]string{"/bin/true"},
|
|
[]string{"cmd", "/C", "exit 0"},
|
|
)
|
|
run := busybox.Run(
|
|
llb.Args(cmdArgs),
|
|
llb.AddMount("/nil", nilState, llb.SourcePath("/"), llb.Readonly))
|
|
|
|
st = run.AddMount("/mnt", st)
|
|
|
|
def, err := st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
|
|
checkAllReleasable(t, c, sb, true)
|
|
}
|
|
|
|
func testRawSocketMount(t *testing.T, sb integration.Sandbox) {
|
|
integration.SkipOnPlatform(t, "windows")
|
|
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
dir := t.TempDir()
|
|
sockPath := filepath.Join(dir, "test.sock")
|
|
listener := net.ListenConfig{}
|
|
l, err := listener.Listen(context.TODO(), "unix", sockPath)
|
|
require.NoError(t, err)
|
|
defer l.Close()
|
|
|
|
var called atomic.Bool
|
|
srv := &http.Server{
|
|
ReadHeaderTimeout: 10 * time.Second,
|
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
called.Store(true)
|
|
w.WriteHeader(http.StatusOK)
|
|
}),
|
|
}
|
|
go srv.Serve(l)
|
|
defer srv.Close()
|
|
|
|
st := llb.Image("alpine:latest").
|
|
Run(llb.Shlex(`apk add --no-cache curl`)).
|
|
Run(llb.Shlex(`curl --unix-socket /tmp/test.sock http://./foo`),
|
|
llb.AddSSHSocket(llb.SSHSocketTarget("/tmp/test.sock")),
|
|
).Root()
|
|
|
|
def, err := st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
raw, err := sshprovider.NewSSHAgentProvider([]sshprovider.AgentConfig{{
|
|
Paths: []string{sockPath},
|
|
Raw: true,
|
|
}})
|
|
require.NoError(t, err)
|
|
|
|
ch := make(chan *SolveStatus)
|
|
go func() {
|
|
for status := range ch {
|
|
for _, l := range status.Logs {
|
|
t.Log(string(l.Data))
|
|
}
|
|
}
|
|
}()
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Session: []session.Attachable{raw},
|
|
}, ch)
|
|
require.NoError(t, err)
|
|
require.True(t, called.Load(), "server should have been called")
|
|
}
|
|
|
|
// #324
|
|
func testReadonlyRootFS(t *testing.T, sb integration.Sandbox) {
|
|
integration.SkipOnPlatform(t, "windows")
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
busybox := llb.Image("docker.io/library/busybox:latest")
|
|
st := llb.Scratch()
|
|
|
|
// The path /foo should be unwriteable.
|
|
run := busybox.Run(
|
|
llb.ReadonlyRootFS(),
|
|
llb.Args([]string{"/bin/touch", "/foo"}))
|
|
st = run.AddMount("/mnt", st)
|
|
|
|
def, err := st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.Error(t, err)
|
|
// Would prefer to detect more specifically "Read-only file
|
|
// system" but that isn't exposed here (it is on the stdio
|
|
// which we don't see).
|
|
require.Contains(t, err.Error(), "process \"/bin/touch /foo\" did not complete successfully")
|
|
|
|
checkAllReleasable(t, c, sb, true)
|
|
}
|
|
|
|
func testRunCacheWithMounts(t *testing.T, sb integration.Sandbox) {
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
imgName := integration.UnixOrWindows("busybox:latest", "nanoserver:latest")
|
|
busybox := llb.Image(imgName)
|
|
|
|
imgAlpine := integration.UnixOrWindows("alpine:latest", "nanoserver:plus")
|
|
alpineImage := llb.Image(imgAlpine)
|
|
|
|
// On Windows ARM64, nanoserver:plus and nanoserver:latest map to the same
|
|
// image, so we create a marker file to distinguish the mounted image.
|
|
mountSource := alpineImage
|
|
if runtime.GOOS == "windows" {
|
|
mountSource = alpineImage.Run(
|
|
llb.Shlex(`cmd /C echo 1> C:/marker`),
|
|
).Root()
|
|
}
|
|
|
|
cmdStr := integration.UnixOrWindows(
|
|
`sh -e -c "[[ -f /m1/sbin/apk ]]"`,
|
|
`cmd /C if exist C:/m1/marker (exit 0) else (exit 1)`,
|
|
)
|
|
out := busybox.Run(llb.Shlex(cmdStr))
|
|
out.AddMount("/m1", mountSource, llb.Readonly)
|
|
|
|
def, err := out.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
|
|
cmdStr = integration.UnixOrWindows(
|
|
`sh -e -c "[[ ! -f /m1/sbin/apk ]]"`,
|
|
`cmd /C if exist C:/m1/marker (exit 1) else (exit 0)`,
|
|
)
|
|
out = busybox.Run(llb.Shlex(cmdStr))
|
|
out.AddMount("/m1", llb.Image(imgName), llb.Readonly)
|
|
|
|
def, err = out.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
/*
|
|
testSecretEnv verifies that secrets can be injected as environment variables during a build.
|
|
It tests four scenarios: (1) a provided secret is accessible via env var, (2) an optional
|
|
secret that is not provided results in an unset/empty env var, (3) a required secret that
|
|
is not provided causes an error, and (4) multiple secrets with custom IDs are resolved correctly.
|
|
Works on both Linux (busybox) and Windows (nanoserver).
|
|
*/
|
|
func testSecretEnv(t *testing.T, sb integration.Sandbox) {
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
imgName := integration.UnixOrWindows("busybox:latest", "nanoserver:latest")
|
|
var st llb.ExecState
|
|
|
|
// Test 1: Verify secret value is accessible as environment variable
|
|
switch imgName {
|
|
case "nanoserver:latest":
|
|
st = llb.Image(imgName).
|
|
Run(llb.Shlex(`cmd /C "if "%MY_SECRET%"=="foo-secret" (exit 0) else (exit 1)"`), llb.AddSecret("MY_SECRET", llb.SecretAsEnv(true)))
|
|
case "busybox:latest":
|
|
st = llb.Image(imgName).
|
|
Run(llb.Shlex(`sh -c '[ "$(echo ${MY_SECRET})" = 'foo-secret' ]'`), llb.AddSecret("MY_SECRET", llb.SecretAsEnv(true)))
|
|
}
|
|
|
|
def, err := st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Session: []session.Attachable{secretsprovider.FromMap(map[string][]byte{
|
|
"MY_SECRET": []byte("foo-secret"),
|
|
})},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Test 2: Optional secret not provided should be unset/empty
|
|
switch imgName {
|
|
case "nanoserver:latest":
|
|
st = llb.Image(imgName).
|
|
Run(llb.Shlex(`cmd /C "if not defined MY_SECRET (exit 0) else (exit 1)"`), llb.AddSecret("MY_SECRET", llb.SecretAsEnv(true), llb.SecretOptional))
|
|
case "busybox:latest":
|
|
st = llb.Image(imgName).
|
|
Run(llb.Shlex(`sh -c '[ -z "${MY_SECRET}" ]'`), llb.AddSecret("MY_SECRET", llb.SecretAsEnv(true), llb.SecretOptional))
|
|
}
|
|
|
|
def, err = st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Session: []session.Attachable{secretsprovider.FromMap(map[string][]byte{})},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Test 3: Required secret not provided should error
|
|
switch imgName {
|
|
case "nanoserver:latest":
|
|
st = llb.Image(imgName).
|
|
Run(llb.Shlex(`cmd /C "echo foo"`), llb.AddSecret("MY_SECRET", llb.SecretAsEnv(true)))
|
|
case "busybox:latest":
|
|
st = llb.Image(imgName).
|
|
Run(llb.Shlex(`echo foo`), llb.AddSecret("MY_SECRET", llb.SecretAsEnv(true)))
|
|
}
|
|
|
|
def, err = st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Session: []session.Attachable{secretsprovider.FromMap(map[string][]byte{})},
|
|
}, nil)
|
|
require.Error(t, err)
|
|
|
|
// Test 4: Multiple secrets with custom IDs
|
|
switch imgName {
|
|
case "nanoserver:latest":
|
|
st = llb.Image(imgName).
|
|
Run(llb.Shlex(`cmd /C "if "%MYPASSWORD%-%MYTOKEN%"=="pw-token" (exit 0) else (exit 1)"`),
|
|
llb.AddSecret("MYPASSWORD", llb.SecretID("pass"), llb.SecretAsEnv(true)),
|
|
llb.AddSecret("MYTOKEN", llb.SecretAsEnv(true)),
|
|
)
|
|
case "busybox:latest":
|
|
st = llb.Image(imgName).
|
|
Run(llb.Shlex(`sh -c '[ "$(echo ${MYPASSWORD}-${MYTOKEN})" = "pw-token" ]' `),
|
|
llb.AddSecret("MYPASSWORD", llb.SecretID("pass"), llb.SecretAsEnv(true)),
|
|
llb.AddSecret("MYTOKEN", llb.SecretAsEnv(true)),
|
|
)
|
|
}
|
|
|
|
def, err = st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Session: []session.Attachable{secretsprovider.FromMap(map[string][]byte{
|
|
"pass": []byte("pw"),
|
|
"MYTOKEN": []byte("token"),
|
|
})},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
/*
|
|
testSecretMounts verifies file-based secret mounts: content readability, optional/required
|
|
behavior, custom permissions, and empty secrets. Skipped on Windows because BuildKit uses
|
|
tmpfs for secret mounts, which Windows does not support. See testSecretEnv for a
|
|
cross-platform alternative using environment-based secrets.
|
|
*/
|
|
func testSecretMounts(t *testing.T, sb integration.Sandbox) {
|
|
// Windows vs Linux secret implementation differences:
|
|
//
|
|
// Linux: Secrets are mounted as files using tmpfs at /run/secrets/ (RAM-based, encrypted)
|
|
//
|
|
// Windows: Secrets are stored in C:\ProgramData\Docker\internal\secrets (clear text on disk)
|
|
// Symbolic links point to the desired target (default: C:\ProgramData\Docker\secrets)
|
|
// Windows does NOT support tmpfs or non-directory file bind-mounts
|
|
// UID/GID/mode options are NOT supported on Windows
|
|
// Recommend BitLocker for at-rest encryption
|
|
//
|
|
// BuildKit Issue: The "invalid windows mount type: 'tmpfs'" error occurs because BuildKit
|
|
// currently tries to use tmpfs for all secret mounts. This needs to be fixed in BuildKit's
|
|
// secret mount implementation to use the Windows symlink approach instead.
|
|
|
|
// For now, this test is Linux-only until BuildKit properly implements Windows secret mounts
|
|
// without tmpfs. Use testSecretEnv for Windows-compatible environment-based secrets.
|
|
integration.SkipOnPlatform(t, "windows", "Windows does not support tmpfs for secret mounts")
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
// Test 1: Basic secret mount with content verification (Linux only)
|
|
st := llb.Image("busybox:latest").
|
|
Run(llb.Shlex(`sh -c 'mount | grep mysecret | grep "type tmpfs" && [ "$(cat /run/secrets/mysecret)" = 'foo-secret' ]'`), llb.AddSecret("/run/secrets/mysecret"))
|
|
|
|
def, err := st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Session: []session.Attachable{secretsprovider.FromMap(map[string][]byte{
|
|
"/run/secrets/mysecret": []byte("foo-secret"),
|
|
})},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Test 2: Optional secret - mount should not exist when secret not present in SolveOpt
|
|
st = llb.Image("busybox:latest").
|
|
Run(llb.Shlex(`test ! -f /run/secrets/mysecret2`), llb.AddSecret("/run/secrets/mysecret2", llb.SecretOptional))
|
|
|
|
def, err = st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Session: []session.Attachable{secretsprovider.FromMap(map[string][]byte{})},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Test 3: Required secret missing - should error
|
|
st = llb.Image("busybox:latest").
|
|
Run(llb.Shlex(`echo secret3`), llb.AddSecret("/run/secrets/mysecret3"))
|
|
|
|
def, err = st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Session: []session.Attachable{secretsprovider.FromMap(map[string][]byte{})},
|
|
}, nil)
|
|
require.Error(t, err)
|
|
|
|
// Test 4: Secret with custom ID and file permissions
|
|
st = llb.Image("busybox:latest").
|
|
Run(llb.Shlex(`sh -c '[ "$(stat -c "%u %g %f" /run/secrets/mysecret4)" = "1 1 81ff" ]' `), llb.AddSecret("/run/secrets/mysecret4", llb.SecretID("mysecret"), llb.SecretFileOpt(1, 1, 0777)))
|
|
|
|
def, err = st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Session: []session.Attachable{secretsprovider.FromMap(map[string][]byte{
|
|
"mysecret": []byte("pw"),
|
|
})},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Test 5: Empty secret still creates secret file
|
|
st = llb.Image("busybox:latest").
|
|
Run(llb.Shlex(`test -f /run/secrets/mysecret5`), llb.AddSecret("/run/secrets/mysecret5", llb.SecretID("mysecret")))
|
|
|
|
def, err = st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Session: []session.Attachable{secretsprovider.FromMap(map[string][]byte{
|
|
"mysecret": []byte(""),
|
|
})},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func testSharedCacheMounts(t *testing.T, sb integration.Sandbox) {
|
|
requiresLinux(t)
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
busybox := llb.Image("busybox:latest")
|
|
st := busybox.Run(llb.Shlex(`sh -e -c "touch one; while [[ ! -f two ]]; do ls -l; usleep 500000; done"`), llb.Dir("/wd"))
|
|
st.AddMount("/wd", llb.Scratch(), llb.AsPersistentCacheDir("mycache1", llb.CacheMountShared))
|
|
|
|
st2 := busybox.Run(llb.Shlex(`sh -e -c "touch two; while [[ ! -f one ]]; do ls -l; usleep 500000; done"`), llb.Dir("/wd"))
|
|
st2.AddMount("/wd", llb.Scratch(), llb.AsPersistentCacheDir("mycache1", llb.CacheMountShared))
|
|
|
|
out := busybox.Run(llb.Shlex("true"))
|
|
out.AddMount("/m1", st.Root())
|
|
out.AddMount("/m2", st2.Root())
|
|
|
|
def, err := out.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// #2334
|
|
func testSharedCacheMountsNoScratch(t *testing.T, sb integration.Sandbox) {
|
|
requiresLinux(t)
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
busybox := llb.Image("busybox:latest")
|
|
st := busybox.Run(llb.Shlex(`sh -e -c "touch one; while [[ ! -f two ]]; do ls -l; usleep 500000; done"`), llb.Dir("/wd"))
|
|
st.AddMount("/wd", llb.Image("busybox:latest"), llb.AsPersistentCacheDir("mycache1", llb.CacheMountShared))
|
|
|
|
st2 := busybox.Run(llb.Shlex(`sh -e -c "touch two; while [[ ! -f one ]]; do ls -l; usleep 500000; done"`), llb.Dir("/wd"))
|
|
st2.AddMount("/wd", llb.Image("busybox:latest"), llb.AsPersistentCacheDir("mycache1", llb.CacheMountShared))
|
|
|
|
out := busybox.Run(llb.Shlex("true"))
|
|
out.AddMount("/m1", st.Root())
|
|
out.AddMount("/m2", st2.Root())
|
|
|
|
def, err := out.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func testSSHMount(t *testing.T, sb integration.Sandbox) {
|
|
integration.SkipOnPlatform(t, "windows")
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
a := agent.NewKeyring()
|
|
|
|
k, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
require.NoError(t, err)
|
|
|
|
err = a.Add(agent.AddedKey{PrivateKey: k})
|
|
require.NoError(t, err)
|
|
|
|
sockPath, err := makeSSHAgentSock(t, a)
|
|
require.NoError(t, err)
|
|
|
|
ssh, err := sshprovider.NewSSHAgentProvider([]sshprovider.AgentConfig{{
|
|
Paths: []string{sockPath},
|
|
}})
|
|
require.NoError(t, err)
|
|
|
|
// no ssh exposed
|
|
st := llb.Image("busybox:latest").Run(llb.Shlex(`nosuchcmd`), llb.AddSSHSocket())
|
|
def, err := st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "no SSH key ")
|
|
|
|
// custom ID not exposed
|
|
st = llb.Image("busybox:latest").Run(llb.Shlex(`nosuchcmd`), llb.AddSSHSocket(llb.SSHID("customID")))
|
|
def, err = st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Session: []session.Attachable{ssh},
|
|
}, nil)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "unset ssh forward key customID")
|
|
|
|
// missing custom ID ignored on optional
|
|
st = llb.Image("busybox:latest").Run(llb.Shlex(`ls`), llb.AddSSHSocket(llb.SSHID("customID"), llb.SSHOptional))
|
|
def, err = st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Session: []session.Attachable{ssh},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
|
|
// valid socket
|
|
st = llb.Image("alpine:latest").
|
|
Run(llb.Shlex(`apk add --no-cache openssh`)).
|
|
Run(llb.Shlex(`sh -c 'echo -n $SSH_AUTH_SOCK > /out/sock && ssh-add -l > /out/out'`),
|
|
llb.AddSSHSocket())
|
|
|
|
out := st.AddMount("/out", llb.Scratch())
|
|
def, err = out.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
destDir := t.TempDir()
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Exports: []ExportEntry{
|
|
{
|
|
Type: ExporterLocal,
|
|
OutputDir: destDir,
|
|
},
|
|
},
|
|
Session: []session.Attachable{ssh},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
|
|
dt, err := os.ReadFile(filepath.Join(destDir, "sock"))
|
|
require.NoError(t, err)
|
|
require.Equal(t, "/run/buildkit/ssh_agent.0", string(dt))
|
|
|
|
dt, err = os.ReadFile(filepath.Join(destDir, "out"))
|
|
require.NoError(t, err)
|
|
require.Contains(t, string(dt), "2048")
|
|
require.Contains(t, string(dt), "(RSA)")
|
|
|
|
// forbidden command
|
|
st = llb.Image("alpine:latest").
|
|
Run(llb.Shlex(`apk add --no-cache openssh`)).
|
|
Run(llb.Shlex(`sh -c 'ssh-keygen -f /tmp/key -N "" && ssh-add -k /tmp/key 2> /out/out || true'`),
|
|
llb.AddSSHSocket())
|
|
|
|
out = st.AddMount("/out", llb.Scratch())
|
|
def, err = out.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Exports: []ExportEntry{
|
|
{
|
|
Type: ExporterLocal,
|
|
OutputDir: destDir,
|
|
},
|
|
},
|
|
Session: []session.Attachable{ssh},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
|
|
dt, err = os.ReadFile(filepath.Join(destDir, "out"))
|
|
require.NoError(t, err)
|
|
require.Contains(t, string(dt), "agent refused operation")
|
|
|
|
// valid socket from key on disk
|
|
st = llb.Image("alpine:latest").
|
|
Run(llb.Shlex(`apk add --no-cache openssh`)).
|
|
Run(llb.Shlex(`sh -c 'ssh-add -l > /out/out'`),
|
|
llb.AddSSHSocket())
|
|
|
|
out = st.AddMount("/out", llb.Scratch())
|
|
def, err = out.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
k, err = rsa.GenerateKey(rand.Reader, 2048)
|
|
require.NoError(t, err)
|
|
|
|
dt = pem.EncodeToMemory(
|
|
&pem.Block{
|
|
Type: "RSA PRIVATE KEY",
|
|
Bytes: x509.MarshalPKCS1PrivateKey(k),
|
|
},
|
|
)
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
err = os.WriteFile(filepath.Join(tmpDir, "key"), dt, 0600)
|
|
require.NoError(t, err)
|
|
|
|
ssh, err = sshprovider.NewSSHAgentProvider([]sshprovider.AgentConfig{{
|
|
Paths: []string{filepath.Join(tmpDir, "key")},
|
|
}})
|
|
require.NoError(t, err)
|
|
|
|
destDir = t.TempDir()
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{
|
|
Exports: []ExportEntry{
|
|
{
|
|
Type: ExporterLocal,
|
|
OutputDir: destDir,
|
|
},
|
|
},
|
|
Session: []session.Attachable{ssh},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
|
|
dt, err = os.ReadFile(filepath.Join(destDir, "out"))
|
|
require.NoError(t, err)
|
|
require.Contains(t, string(dt), "2048")
|
|
require.Contains(t, string(dt), "(RSA)")
|
|
}
|
|
|
|
func testTmpfsMounts(t *testing.T, sb integration.Sandbox) {
|
|
requiresLinux(t)
|
|
c, err := New(sb.Context(), sb.Address())
|
|
require.NoError(t, err)
|
|
defer c.Close()
|
|
|
|
st := llb.Image("busybox:latest").
|
|
Run(llb.Shlex(`sh -c 'mount | grep /foobar | grep "type tmpfs" && touch /foobar/test'`), llb.AddMount("/foobar", llb.Scratch(), llb.Tmpfs()))
|
|
|
|
def, err := st.Marshal(sb.Context())
|
|
require.NoError(t, err)
|
|
|
|
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func cacheMountFile(base llb.State, cacheID, name string) llb.State {
|
|
check := base.Run(llb.Shlexf(`sh -e -c "echo %s >/dev/null; cp /cache/%s /out/%s"`, cacheID, name, name))
|
|
check.AddMount("/cache", llb.Scratch(), llb.AsPersistentCacheDir(cacheID, llb.CacheMountShared))
|
|
return check.AddMount("/out", llb.Scratch())
|
|
}
|
|
|
|
func makeSSHAgentSock(t *testing.T, agent agent.Agent) (p string, err error) {
|
|
tmpDir := integration.Tmpdir(t)
|
|
sockPath := filepath.Join(tmpDir.Name, "ssh_auth_sock")
|
|
|
|
listener := net.ListenConfig{}
|
|
l, err := listener.Listen(context.TODO(), "unix", sockPath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
t.Cleanup(func() {
|
|
require.NoError(t, l.Close())
|
|
})
|
|
|
|
s := &server{l: l}
|
|
go s.run(agent)
|
|
|
|
return sockPath, nil
|
|
}
|
|
|
|
type server struct {
|
|
l net.Listener
|
|
}
|
|
|
|
func (s *server) run(a agent.Agent) error {
|
|
for {
|
|
c, err := s.l.Accept()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
go agent.ServeAgent(a, c)
|
|
}
|
|
}
|