Skip to content

Commit 7ba4fb1

Browse files
committed
feat: wcow: add support for bind and cache mounts
Currently, mounts are not supported for WCOW builds, see #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 #5603 Signed-off-by: Anthony Nandaa <[email protected]>
1 parent 2428a6c commit 7ba4fb1

File tree

13 files changed

+310
-63
lines changed

13 files changed

+310
-63
lines changed

cache/contenthash/checksum.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,10 @@ func (cc *cacheContext) scanChecksum(ctx context.Context, m *mount, p string, fo
862862
}
863863

864864
func (cc *cacheContext) checksum(ctx context.Context, root *iradix.Node[*CacheRecord], txn *iradix.Txn[*CacheRecord], m *mount, k []byte, followTrailing bool) (*CacheRecord, bool, error) {
865+
// only applies for Windows, it's a no-op on non-Windows.
866+
enableProcessPrivileges()
867+
defer disableProcessPrivileges()
868+
865869
origk := k
866870
k, cr, err := getFollowLinks(root, k, followTrailing)
867871
if err != nil {

cache/contenthash/checksum_unix.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,9 @@ import "path/filepath"
77
func (cc *cacheContext) walk(scanPath string, walkFunc filepath.WalkFunc) error {
88
return filepath.Walk(scanPath, walkFunc)
99
}
10+
11+
// This is a no-op on non-Windows
12+
func enableProcessPrivileges() {}
13+
14+
// This is a no-op on non-Windows
15+
func disableProcessPrivileges() {}

cache/contenthash/checksum_windows.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,24 @@ import (
66
"github.com/Microsoft/go-winio"
77
)
88

9+
var privileges = []string{winio.SeBackupPrivilege}
10+
911
func (cc *cacheContext) walk(scanPath string, walkFunc filepath.WalkFunc) error {
1012
// elevating the admin privileges to walk special files/directory
1113
// like `System Volume Information`, etc. See similar in #4994
12-
privileges := []string{winio.SeBackupPrivilege}
1314
return winio.RunWithPrivileges(privileges, func() error {
1415
return filepath.Walk(scanPath, walkFunc)
1516
})
1617
}
18+
19+
// Adds the SeBackupPrivilege to the process
20+
// to be able to access some special files and directories.
21+
func enableProcessPrivileges() {
22+
_ = winio.EnableProcessPrivileges(privileges)
23+
}
24+
25+
// Disables the SeBackupPrivilege on the process
26+
// once the group of functions that needed it is complete.
27+
func disableProcessPrivileges() {
28+
_ = winio.DisableProcessPrivileges(privileges)
29+
}

client/client_test.go

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

15721572
func testRelativeWorkDir(t *testing.T, sb integration.Sandbox) {
1573-
requiresLinux(t)
15741573
c, err := New(sb.Context(), sb.Address())
15751574
require.NoError(t, err)
15761575
defer c.Close()
15771576

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

15841591
def, err := pwd.Marshal(sb.Context())
@@ -1598,22 +1605,32 @@ func testRelativeWorkDir(t *testing.T, sb integration.Sandbox) {
15981605

15991606
dt, err := os.ReadFile(filepath.Join(destDir, "pwd"))
16001607
require.NoError(t, err)
1601-
require.Equal(t, []byte("/test1/test2\n"), dt)
1608+
pathStr := integration.UnixOrWindows(
1609+
"/test1/test2\n",
1610+
"C:\\test1\\test2\r\n",
1611+
)
1612+
require.Equal(t, []byte(pathStr), dt)
16021613
}
16031614

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

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

16191636
def, err := out.Marshal(sb.Context())
@@ -1641,7 +1658,12 @@ func testSolverOptLocalDirsStillWorks(t *testing.T, sb integration.Sandbox) {
16411658

16421659
dt, err := os.ReadFile(filepath.Join(destDir.Name, "output.txt"))
16431660
require.NoError(t, err)
1644-
require.Equal(t, []byte("dlroW olleH"), dt)
1661+
// not reversed on Windows since there's no handy rev utility
1662+
revStr := integration.UnixOrWindows(
1663+
"dlroW olleH",
1664+
"Hello World",
1665+
)
1666+
require.Equal(t, []byte(revStr), dt)
16451667
}
16461668

16471669
func testFileOpMkdirMkfile(t *testing.T, sb integration.Sandbox) {
@@ -6838,12 +6860,19 @@ func testCacheMountNoCache(t *testing.T, sb integration.Sandbox) {
68386860
}
68396861

68406862
func testCopyFromEmptyImage(t *testing.T, sb integration.Sandbox) {
6841-
requiresLinux(t)
68426863
c, err := New(sb.Context(), sb.Address())
68436864
require.NoError(t, err)
68446865
defer c.Close()
68456866

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

68586887
_, err = c.Solve(sb.Context(), def, SolveOpt{}, nil)
68596888
require.Error(t, err)
6860-
require.Contains(t, err.Error(), "/foo: no such file or directory")
6889+
errMsg := integration.UnixOrWindows(
6890+
"/foo: no such file or directory",
6891+
winErrMsgs[i],
6892+
)
6893+
require.Contains(t, err.Error(), errMsg)
68616894

6862-
busybox := llb.Image("busybox:latest")
6895+
imgName := integration.UnixOrWindows(
6896+
"busybox:latest",
6897+
"mcr.microsoft.com/windows/nanoserver:ltsc2022",
6898+
)
6899+
busybox := llb.Image(imgName)
68636900

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

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

executor/oci/spec.go

Lines changed: 5 additions & 3 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
})
@@ -251,7 +252,8 @@ type submounts struct {
251252
}
252253

253254
func (s *submounts) subMount(m mount.Mount, subPath string) (mount.Mount, error) {
254-
if path.Join("/", subPath) == "/" {
255+
// for Windows, always go through the sub-mounting process
256+
if path.Join("/", subPath) == "/" && runtime.GOOS != "windows" {
255257
return m, nil
256258
}
257259
if s.m == nil {

executor/oci/spec_unix.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
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+
}

executor/oci/spec_windows.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,9 @@ 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+
}

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)