Skip to content

Commit 39e3ff8

Browse files
committed
feat: wcow: add support for bind and cache mounts
Currently, mounts are not supported for WCOW builds, see moby#5678. This commit introduces support for bind and cache mounts. The remaining two require a little more work and consultation with the platform teams for enlightment. WIP Checklist: - [x] Support for bind mounts - [x] Support for cache mounts - [x] add frontend/dockerfile integration tests - [x] add client integration tests (not all, `llb.AddMount` not complete) Fixes moby#5603 Signed-off-by: Anthony Nandaa <[email protected]>
1 parent c45cd57 commit 39e3ff8

File tree

10 files changed

+300
-61
lines changed

10 files changed

+300
-61
lines changed

client/client_test.go

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,15 +1568,22 @@ func testLocalSymlinkEscape(t *testing.T, sb integration.Sandbox) {
15681568
}
15691569

15701570
func testRelativeWorkDir(t *testing.T, sb integration.Sandbox) {
1571-
requiresLinux(t)
15721571
c, err := New(sb.Context(), sb.Address())
15731572
require.NoError(t, err)
15741573
defer c.Close()
15751574

1576-
pwd := llb.Image("docker.io/library/busybox:latest").
1575+
imgName := integration.UnixOrWindows(
1576+
"docker.io/library/busybox:latest",
1577+
"mcr.microsoft.com/windows/nanoserver:ltsc2022",
1578+
)
1579+
cmdStr := integration.UnixOrWindows(
1580+
`sh -c "pwd > /out/pwd"`,
1581+
`cmd /C "cd > /out/pwd"`,
1582+
)
1583+
pwd := llb.Image(imgName).
15771584
Dir("test1").
15781585
Dir("test2").
1579-
Run(llb.Shlex(`sh -c "pwd > /out/pwd"`)).
1586+
Run(llb.Shlex(cmdStr)).
15801587
AddMount("/out", llb.Scratch())
15811588

15821589
def, err := pwd.Marshal(sb.Context())
@@ -1596,22 +1603,32 @@ func testRelativeWorkDir(t *testing.T, sb integration.Sandbox) {
15961603

15971604
dt, err := os.ReadFile(filepath.Join(destDir, "pwd"))
15981605
require.NoError(t, err)
1599-
require.Equal(t, []byte("/test1/test2\n"), dt)
1606+
pathStr := integration.UnixOrWindows(
1607+
"/test1/test2\n",
1608+
"C:\\test1\\test2\r\n",
1609+
)
1610+
require.Equal(t, []byte(pathStr), dt)
16001611
}
16011612

16021613
// TODO: remove this test once `client.SolveOpt.LocalDirs`, now marked as deprecated, is removed.
16031614
// For more context on this test, please check:
16041615
// https://github.com/moby/buildkit/pull/4583#pullrequestreview-1847043452
16051616
func testSolverOptLocalDirsStillWorks(t *testing.T, sb integration.Sandbox) {
1606-
integration.SkipOnPlatform(t, "windows")
1607-
16081617
c, err := New(sb.Context(), sb.Address())
16091618
require.NoError(t, err)
16101619
defer c.Close()
16111620

1612-
out := llb.Image("docker.io/library/busybox:latest").
1621+
imgName := integration.UnixOrWindows(
1622+
"docker.io/library/busybox:latest",
1623+
"mcr.microsoft.com/windows/nanoserver:ltsc2022",
1624+
)
1625+
cmdStr := integration.UnixOrWindows(
1626+
`sh -c "/bin/rev < input.txt > /out/output.txt"`,
1627+
`cmd /C "type input.txt > /out/output.txt"`,
1628+
)
1629+
out := llb.Image(imgName).
16131630
File(llb.Copy(llb.Local("mylocal"), "input.txt", "input.txt")).
1614-
Run(llb.Shlex(`sh -c "/bin/rev < input.txt > /out/output.txt"`)).
1631+
Run(llb.Shlex(cmdStr)).
16151632
AddMount(`/out`, llb.Scratch())
16161633

16171634
def, err := out.Marshal(sb.Context())
@@ -1639,7 +1656,12 @@ func testSolverOptLocalDirsStillWorks(t *testing.T, sb integration.Sandbox) {
16391656

16401657
dt, err := os.ReadFile(filepath.Join(destDir.Name, "output.txt"))
16411658
require.NoError(t, err)
1642-
require.Equal(t, []byte("dlroW olleH"), dt)
1659+
// not reversed on Windows since there's no handy rev utility
1660+
revStr := integration.UnixOrWindows(
1661+
"dlroW olleH",
1662+
"Hello World",
1663+
)
1664+
require.Equal(t, []byte(revStr), dt)
16431665
}
16441666

16451667
func testFileOpMkdirMkfile(t *testing.T, sb integration.Sandbox) {
@@ -6836,12 +6858,19 @@ func testCacheMountNoCache(t *testing.T, sb integration.Sandbox) {
68366858
}
68376859

68386860
func testCopyFromEmptyImage(t *testing.T, sb integration.Sandbox) {
6839-
requiresLinux(t)
68406861
c, err := New(sb.Context(), sb.Address())
68416862
require.NoError(t, err)
68426863
defer c.Close()
68436864

6844-
for _, image := range []llb.State{llb.Scratch(), llb.Image("tonistiigi/test:nolayers")} {
6865+
// On Windows, the error messages are different for the Scratch image
6866+
// (which is coming from the OS). While the one for the no-layers image
6867+
// is being returned by Buildkit (in unix style).
6868+
winErrMsgs := []string{
6869+
"foo: The system cannot find the file specified", // for llb.Scratch()
6870+
"/foo: no such file or directory", // for tonistiigi/test:nolayers
6871+
}
6872+
6873+
for i, image := range []llb.State{llb.Scratch(), llb.Image("tonistiigi/test:nolayers")} {
68456874
st := llb.Scratch().File(llb.Copy(image, "/", "/"))
68466875
def, err := st.Marshal(sb.Context())
68476876
require.NoError(t, err)
@@ -6855,11 +6884,23 @@ func testCopyFromEmptyImage(t *testing.T, sb integration.Sandbox) {
68556884

68566885
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
68576886
require.Error(t, err)
6858-
require.Contains(t, err.Error(), "/foo: no such file or directory")
6887+
errMsg := integration.UnixOrWindows(
6888+
"/foo: no such file or directory",
6889+
winErrMsgs[i],
6890+
)
6891+
require.Contains(t, err.Error(), errMsg)
68596892

6860-
busybox := llb.Image("busybox:latest")
6893+
imgName := integration.UnixOrWindows(
6894+
"busybox:latest",
6895+
"mcr.microsoft.com/windows/nanoserver:ltsc2022",
6896+
)
6897+
busybox := llb.Image(imgName)
68616898

6862-
out := busybox.Run(llb.Shlex(`sh -e -c '[ $(ls /scratch | wc -l) = '0' ]'`))
6899+
cmdStr := integration.UnixOrWindows(
6900+
`sh -e -c '[ $(ls /scratch | wc -l) = '0' ]'`,
6901+
`cmd /C dir \scratch`,
6902+
)
6903+
out := busybox.Run(llb.Shlex(cmdStr))
68636904
out.AddMount("/scratch", image, llb.Readonly)
68646905

68656906
def, err = out.Marshal(sb.Context())

executor/oci/spec.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/moby/buildkit/solver/llbsolver/cdidevices"
2121
"github.com/moby/buildkit/util/network"
2222
rootlessmountopts "github.com/moby/buildkit/util/rootless/mountopts"
23+
"github.com/moby/buildkit/util/system"
2324
traceexec "github.com/moby/buildkit/util/tracing/exec"
2425
"github.com/moby/sys/userns"
2526
specs "github.com/opencontainers/runtime-spec/specs-go"
@@ -210,8 +211,8 @@ func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mou
210211
return nil, nil, err
211212
}
212213
s.Mounts = append(s.Mounts, specs.Mount{
213-
Destination: m.Dest,
214-
Type: mount.Type,
214+
Destination: system.GetAbsolutePath(m.Dest),
215+
Type: getMountType(mount.Type),
215216
Source: mount.Source,
216217
Options: mount.Options,
217218
})
@@ -252,6 +253,10 @@ type submounts struct {
252253

253254
func (s *submounts) subMount(m mount.Mount, subPath string) (mount.Mount, error) {
254255
if path.Join("/", subPath) == "/" {
256+
// for Windows, the mounting by HCS doesn't go through
257+
// WCIFS, hence we have to give the direct path of the
258+
// mount, which is in the /Files subdirectory.
259+
m.Source = getCompleteSourcePath(m.Source)
255260
return m, nil
256261
}
257262
if s.m == nil {

executor/oci/spec_unix.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//go:build !windows
2+
3+
package oci
4+
5+
// no effect for non-Windows
6+
func getMountType(mType string) string {
7+
return mType
8+
}
9+
10+
// no effect for non-Windows
11+
func getCompleteSourcePath(p string) string {
12+
return p
13+
}

executor/oci/spec_windows.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,16 @@ func generateCDIOpts(_ *cdidevices.Manager, devices []*pb.CDIDevice) ([]oci.Spec
119119
// https://github.com/cncf-tags/container-device-interface/issues/28
120120
return nil, errors.New("no support for CDI on Windows")
121121
}
122+
123+
func getMountType(_ string) string {
124+
// HCS shim doesn't expect a named type
125+
// for the mount.
126+
return ""
127+
}
128+
129+
// For Windows, the mounting by HCS doesn't go through
130+
// WCIFS, hence we have to give the direct path of the
131+
// mount, which is in the /Files subdirectory.
132+
func getCompleteSourcePath(p string) string {
133+
return filepath.Join(p, "Files")
134+
}

frontend/dockerfile/dockerfile2llb/convert_runmount.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/moby/buildkit/client/llb"
1010
"github.com/moby/buildkit/frontend/dockerfile/instructions"
1111
"github.com/moby/buildkit/solver/pb"
12+
"github.com/moby/buildkit/util/system"
1213
"github.com/pkg/errors"
1314
)
1415

@@ -113,12 +114,12 @@ func dispatchRunMounts(d *dispatchState, c *instructions.RunCommand, sources []*
113114
sharing = llb.CacheMountLocked
114115
}
115116
if mount.CacheID == "" {
116-
mount.CacheID = path.Clean(mount.Target)
117+
mount.CacheID = filepath.Clean(mount.Target)
117118
}
118119
mountOpts = append(mountOpts, llb.AsPersistentCacheDir(opt.cacheIDNamespace+"/"+mount.CacheID, sharing))
119120
}
120121
target := mount.Target
121-
if !filepath.IsAbs(filepath.Clean(mount.Target)) {
122+
if !system.IsAbsolutePath(filepath.Clean(mount.Target)) {
122123
dir, err := d.state.GetDir(context.TODO())
123124
if err != nil {
124125
return nil, err

0 commit comments

Comments
 (0)