diff --git a/Dockerfile b/Dockerfile index 3dc9c06c60ff..ce5d8addf13c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax=docker/dockerfile-upstream:master -ARG RUNC_VERSION=v1.0.2 -ARG CONTAINERD_VERSION=v1.6.2 +ARG RUNC_VERSION=v1.1.12 +ARG CONTAINERD_VERSION=v1.6.28 # containerd v1.5 for integration tests ARG CONTAINERD_ALT_VERSION_15=v1.5.11 # containerd v1.4 for integration tests diff --git a/client/build_test.go b/client/build_test.go index 23642de80f60..6f3b2068c25e 100644 --- a/client/build_test.go +++ b/client/build_test.go @@ -63,7 +63,8 @@ func TestClientGatewayIntegration(t *testing.T) { ), integration.WithMirroredImages(integration.OfficialImages("busybox:latest"))) integration.Run(t, integration.TestFuncs( - testClientGatewayContainerSecurityMode, + testClientGatewayContainerSecurityModeCaps, + testClientGatewayContainerSecurityModeValidation, ), integration.WithMirroredImages(integration.OfficialImages("busybox:latest")), integration.WithMatrix("secmode", map[string]interface{}{ "sandbox": securitySandbox, @@ -72,7 +73,8 @@ func TestClientGatewayIntegration(t *testing.T) { ) integration.Run(t, integration.TestFuncs( - testClientGatewayContainerHostNetworking, + testClientGatewayContainerHostNetworkingAccess, + testClientGatewayContainerHostNetworkingValidation, ), integration.WithMirroredImages(integration.OfficialImages("busybox:latest")), integration.WithMatrix("netmode", map[string]interface{}{ @@ -1621,9 +1623,17 @@ func testClientGatewayExecFileActionError(t *testing.T, sb integration.Sandbox) checkAllReleasable(t, c, sb, true) } -// testClientGatewayContainerSecurityMode ensures that the correct security mode +// testClientGatewayContainerSecurityModeCaps ensures that the correct security mode // is propagated to the gateway container -func testClientGatewayContainerSecurityMode(t *testing.T, sb integration.Sandbox) { +func testClientGatewayContainerSecurityModeCaps(t *testing.T, sb integration.Sandbox) { + testClientGatewayContainerSecurityMode(t, sb, false) +} + +func testClientGatewayContainerSecurityModeValidation(t *testing.T, sb integration.Sandbox) { + testClientGatewayContainerSecurityMode(t, sb, true) +} + +func testClientGatewayContainerSecurityMode(t *testing.T, sb integration.Sandbox, expectFail bool) { requiresLinux(t) ctx := sb.Context() @@ -1649,6 +1659,9 @@ func testClientGatewayContainerSecurityMode(t *testing.T, sb integration.Sandbox require.EqualValues(t, 0xa80425fb, caps) } allowedEntitlements = []entitlements.Entitlement{} + if expectFail { + return + } } else { integration.SkipIfDockerd(t, sb) assertCaps = func(caps uint64) { @@ -1666,6 +1679,9 @@ func testClientGatewayContainerSecurityMode(t *testing.T, sb integration.Sandbox } mode = llb.SecurityModeInsecure allowedEntitlements = []entitlements.Entitlement{entitlements.EntitlementSecurityInsecure} + if expectFail { + allowedEntitlements = []entitlements.Entitlement{} + } } b := func(ctx context.Context, c client.Client) (*client.Result, error) { @@ -1715,6 +1731,12 @@ func testClientGatewayContainerSecurityMode(t *testing.T, sb integration.Sandbox t.Logf("Stdout: %q", stdout.String()) t.Logf("Stderr: %q", stderr.String()) + if expectFail { + require.Error(t, err) + require.Contains(t, err.Error(), "security.insecure is not allowed") + return nil, err + } + require.NoError(t, err) capsValue, err := strconv.ParseUint(strings.TrimSpace(stdout.String()), 16, 64) @@ -1729,7 +1751,13 @@ func testClientGatewayContainerSecurityMode(t *testing.T, sb integration.Sandbox AllowedEntitlements: allowedEntitlements, } _, err = c.Build(ctx, solveOpts, product, b, nil) - require.NoError(t, err) + + if expectFail { + require.Error(t, err) + require.Contains(t, err.Error(), "security.insecure is not allowed") + } else { + require.NoError(t, err) + } checkAllReleasable(t, c, sb, true) } @@ -1805,7 +1833,15 @@ func testClientGatewayContainerExtraHosts(t *testing.T, sb integration.Sandbox) checkAllReleasable(t, c, sb, true) } -func testClientGatewayContainerHostNetworking(t *testing.T, sb integration.Sandbox) { +func testClientGatewayContainerHostNetworkingAccess(t *testing.T, sb integration.Sandbox) { + testClientGatewayContainerHostNetworking(t, sb, false) +} + +func testClientGatewayContainerHostNetworkingValidation(t *testing.T, sb integration.Sandbox) { + testClientGatewayContainerHostNetworking(t, sb, true) +} + +func testClientGatewayContainerHostNetworking(t *testing.T, sb integration.Sandbox, expectFail bool) { if os.Getenv("BUILDKIT_RUN_NETWORK_INTEGRATION_TESTS") == "" { t.SkipNow() } @@ -1826,6 +1862,9 @@ func testClientGatewayContainerHostNetworking(t *testing.T, sb integration.Sandb if sb.Value("netmode") == hostNetwork { netMode = pb.NetMode_HOST allowedEntitlements = []entitlements.Entitlement{entitlements.EntitlementNetworkHost} + if expectFail { + allowedEntitlements = []entitlements.Entitlement{} + } } c, err := New(sb.Context(), sb.Address()) require.NoError(t, err) @@ -1884,7 +1923,12 @@ func testClientGatewayContainerHostNetworking(t *testing.T, sb integration.Sandb t.Logf("Stderr: %q", stderr.String()) if netMode == pb.NetMode_HOST { - require.NoError(t, err) + if expectFail { + require.Error(t, err) + require.Contains(t, err.Error(), "network.host is not allowed") + } else { + require.NoError(t, err) + } } else { require.Error(t, err) } diff --git a/client/client_test.go b/client/client_test.go index 98d924597563..5a0fc4991ea3 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -162,6 +162,8 @@ func TestIntegration(t *testing.T) { testUncompressedRegistryCacheImportExport, testStargzLazyRegistryCacheImportExport, testValidateDigestOrigin, + testMountStubsTimestamp, + testMountStubsDirectory, ) tests = append(tests, diffOpTestCases()...) integration.Run(t, tests, mirrors) @@ -5888,3 +5890,118 @@ func fixedWriteCloser(wc io.WriteCloser) func(map[string]string) (io.WriteCloser return wc, nil } } + +func testMountStubsDirectory(t *testing.T, sb integration.Sandbox) { + c, err := New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + st := llb.Image("busybox:latest").Run( + llb.Args([]string{"/bin/echo", "dummy"}), + llb.AddMount("/foo/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) + + for { + hd, err := tarR.Next() + if errors.Is(err, io.EOF) { + break + } + require.NoError(t, err) + if hd.Name == "foo/bar/" { + require.Fail(t, "foo/bar/ should not be in the tar") + } + } +} + +// https://github.com/moby/buildkit/issues/3148 +func testMountStubsTimestamp(t *testing.T, sb integration.Sandbox) { + 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) + } +} diff --git a/cmd/buildkitd/main.go b/cmd/buildkitd/main.go index 126ba0dbe2c8..c7c444df4e9a 100644 --- a/cmd/buildkitd/main.go +++ b/cmd/buildkitd/main.go @@ -636,8 +636,8 @@ func newController(c *cli.Context, cfg *config.Config) (*control.Controller, err return nil, err } frontends := map[string]frontend.Frontend{} - frontends["dockerfile.v0"] = forwarder.NewGatewayForwarder(wc, dockerfile.Build) - frontends["gateway.v0"] = gateway.NewGatewayFrontend(wc) + frontends["dockerfile.v0"] = forwarder.NewGatewayForwarder(wc.Infos(), dockerfile.Build) + frontends["gateway.v0"] = gateway.NewGatewayFrontend(wc.Infos()) cacheStorage, err := bboltcachestorage.NewStore(filepath.Join(cfg.Root, "cache.db")) if err != nil { diff --git a/executor/executor.go b/executor/executor.go index 4727af4b03ef..937acc227371 100644 --- a/executor/executor.go +++ b/executor/executor.go @@ -6,7 +6,8 @@ import ( "net" "syscall" - "github.com/moby/buildkit/snapshot" + "github.com/containerd/containerd/mount" + "github.com/docker/docker/pkg/idtools" "github.com/moby/buildkit/solver/pb" ) @@ -25,8 +26,13 @@ type Meta struct { SecurityMode pb.SecurityMode } +type MountableRef interface { + Mount() ([]mount.Mount, func() error, error) + IdentityMapping() *idtools.IdentityMapping +} + type Mountable interface { - Mount(ctx context.Context, readonly bool) (snapshot.Mountable, error) + Mount(ctx context.Context, readonly bool) (MountableRef, error) } type Mount struct { diff --git a/executor/oci/spec.go b/executor/oci/spec.go index 94b48a7aa9ff..012bfb725126 100644 --- a/executor/oci/spec.go +++ b/executor/oci/spec.go @@ -11,7 +11,6 @@ import ( "github.com/containerd/containerd/mount" "github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/oci" - "github.com/containerd/continuity/fs" "github.com/docker/docker/pkg/idtools" "github.com/mitchellh/hashstructure/v2" "github.com/moby/buildkit/executor" @@ -198,6 +197,7 @@ func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mou type mountRef struct { mount mount.Mount unmount func() error + subRefs map[string]mountRef } type submounts struct { @@ -213,12 +213,19 @@ func (s *submounts) subMount(m mount.Mount, subPath string) (mount.Mount, error) } h, err := hashstructure.Hash(m, hashstructure.FormatV2, nil) if err != nil { - return mount.Mount{}, nil + return mount.Mount{}, err } if mr, ok := s.m[h]; ok { - sm, err := sub(mr.mount, subPath) + if sm, ok := mr.subRefs[subPath]; ok { + return sm.mount, nil + } + sm, unmount, err := sub(mr.mount, subPath) if err != nil { - return mount.Mount{}, nil + return mount.Mount{}, err + } + mr.subRefs[subPath] = mountRef{ + mount: sm, + unmount: unmount, } return sm, nil } @@ -244,12 +251,17 @@ func (s *submounts) subMount(m mount.Mount, subPath string) (mount.Mount, error) Options: opts, }, unmount: lm.Unmount, + subRefs: map[string]mountRef{}, } - sm, err := sub(s.m[h].mount, subPath) + sm, unmount, err := sub(s.m[h].mount, subPath) if err != nil { return mount.Mount{}, err } + s.m[h].subRefs[subPath] = mountRef{ + mount: sm, + unmount: unmount, + } return sm, nil } @@ -259,6 +271,9 @@ func (s *submounts) cleanup() { for _, m := range s.m { func(m mountRef) { go func() { + for _, sm := range m.subRefs { + sm.unmount() + } m.unmount() wg.Done() }() @@ -267,15 +282,6 @@ func (s *submounts) cleanup() { wg.Wait() } -func sub(m mount.Mount, subPath string) (mount.Mount, error) { - src, err := fs.RootPath(m.Source, subPath) - if err != nil { - return mount.Mount{}, err - } - m.Source = src - return m, nil -} - func specMapping(s []idtools.IDMap) []specs.LinuxIDMapping { var ids []specs.LinuxIDMapping for _, item := range s { diff --git a/executor/oci/spec_freebsd.go b/executor/oci/spec_freebsd.go new file mode 100644 index 000000000000..0810bc428867 --- /dev/null +++ b/executor/oci/spec_freebsd.go @@ -0,0 +1,15 @@ +package oci + +import ( + "github.com/containerd/containerd/mount" + "github.com/containerd/continuity/fs" +) + +func sub(m mount.Mount, subPath string) (mount.Mount, func() error, error) { + src, err := fs.RootPath(m.Source, subPath) + if err != nil { + return mount.Mount{}, nil, err + } + m.Source = src + return m, func() error { return nil }, nil +} diff --git a/executor/oci/spec_linux.go b/executor/oci/spec_linux.go new file mode 100644 index 000000000000..abbf0879d87a --- /dev/null +++ b/executor/oci/spec_linux.go @@ -0,0 +1,57 @@ +//go:build linux +// +build linux + +package oci + +import ( + "os" + "strconv" + + "github.com/containerd/containerd/mount" + "github.com/containerd/continuity/fs" + "github.com/moby/buildkit/snapshot" + "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +func sub(m mount.Mount, subPath string) (mount.Mount, func() error, error) { + var retries = 10 + root := m.Source + for { + src, err := fs.RootPath(root, subPath) + if err != nil { + return mount.Mount{}, nil, err + } + // similar to runc.WithProcfd + fh, err := os.OpenFile(src, unix.O_PATH|unix.O_CLOEXEC, 0) + if err != nil { + return mount.Mount{}, nil, err + } + + fdPath := "/proc/self/fd/" + strconv.Itoa(int(fh.Fd())) + if resolved, err := os.Readlink(fdPath); err != nil { + fh.Close() + return mount.Mount{}, nil, err + } else if resolved != src { + retries-- + if retries <= 0 { + fh.Close() + return mount.Mount{}, nil, errors.Errorf("unable to safely resolve subpath %s", subPath) + } + fh.Close() + continue + } + + m.Source = fdPath + lm := snapshot.LocalMounterWithMounts([]mount.Mount{m}, snapshot.ForceRemount()) + mp, err := lm.Mount() + if err != nil { + fh.Close() + return mount.Mount{}, nil, err + } + m.Source = mp + fh.Close() // release the fd, we don't need it anymore + + return m, lm.Unmount, nil + } +} diff --git a/executor/oci/spec_windows.go b/executor/oci/spec_windows.go index 48b0969e3922..757bd397dec4 100644 --- a/executor/oci/spec_windows.go +++ b/executor/oci/spec_windows.go @@ -4,7 +4,9 @@ package oci import ( + "github.com/containerd/containerd/mount" "github.com/containerd/containerd/oci" + "github.com/containerd/continuity/fs" "github.com/docker/docker/pkg/idtools" "github.com/moby/buildkit/solver/pb" "github.com/pkg/errors" @@ -43,3 +45,12 @@ func generateRlimitOpts(ulimits []*pb.Ulimit) ([]oci.SpecOpts, error) { } return nil, errors.New("no support for POSIXRlimit on Windows") } + +func sub(m mount.Mount, subPath string) (mount.Mount, func() error, error) { + src, err := fs.RootPath(m.Source, subPath) + if err != nil { + return mount.Mount{}, nil, err + } + m.Source = src + return m, func() error { return nil }, nil +} diff --git a/executor/stubs.go b/executor/stubs.go index 2c13b13053a4..c179ff21a9ca 100644 --- a/executor/stubs.go +++ b/executor/stubs.go @@ -4,9 +4,12 @@ import ( "errors" "os" "path/filepath" + "strings" "syscall" "github.com/containerd/continuity/fs" + "github.com/moby/buildkit/util/system" + "github.com/sirupsen/logrus" ) func MountStubsCleaner(dir string, mounts []Mount) func() { @@ -36,14 +39,54 @@ func MountStubsCleaner(dir string, mounts []Mount) func() { return func() { for _, p := range paths { + p, err := fs.RootPath(dir, strings.TrimPrefix(p, dir)) + if err != nil { + continue + } + st, err := os.Lstat(p) if err != nil { continue } - if st.Size() != 0 { + if st.IsDir() { + entries, err := os.ReadDir(p) + if err != nil { + continue + } + if len(entries) != 0 { + continue + } + } else if st.Size() != 0 { continue } - os.Remove(p) + + // Back up the timestamps of the dir for reproducible builds + // https://github.com/moby/buildkit/issues/3148 + parent := filepath.Dir(p) + if realPath, err := fs.RootPath(dir, strings.TrimPrefix(parent, dir)); err != nil || realPath != parent { + continue + } + + dirSt, err := os.Stat(parent) + if err != nil { + logrus.WithError(err).Warnf("Failed to stat %q (parent of mount stub %q)", dir, p) + continue + } + mtime := dirSt.ModTime() + atime, err := system.Atime(dirSt) + if err != nil { + logrus.WithError(err).Warnf("Failed to stat atime of %q (parent of mount stub %q)", dir, p) + atime = mtime + } + + if err := os.Remove(p); err != nil { + logrus.WithError(err).Warnf("Failed to remove mount stub %q", p) + } + + // Restore the timestamps of the dir + if err := os.Chtimes(parent, atime, mtime); err != nil { + logrus.WithError(err).Warnf("Failed to restore time time mount stub timestamp (os.Chtimes(%q, %v, %v))", dir, atime, mtime) + } } } } diff --git a/frontend/frontend.go b/frontend/frontend.go index dedda54c6104..c6dc80755be9 100644 --- a/frontend/frontend.go +++ b/frontend/frontend.go @@ -4,6 +4,7 @@ import ( "context" "github.com/moby/buildkit/client/llb" + "github.com/moby/buildkit/executor" gw "github.com/moby/buildkit/frontend/gateway/client" "github.com/moby/buildkit/session" "github.com/moby/buildkit/solver/pb" @@ -11,7 +12,7 @@ import ( ) type Frontend interface { - Solve(ctx context.Context, llb FrontendLLBBridge, opt map[string]string, inputs map[string]*pb.Definition, sid string, sm *session.Manager) (*Result, error) + Solve(ctx context.Context, llb FrontendLLBBridge, exec executor.Executor, opt map[string]string, inputs map[string]*pb.Definition, sid string, sm *session.Manager) (*Result, error) } type FrontendLLBBridge interface { diff --git a/frontend/gateway/container.go b/frontend/gateway/container/container.go similarity index 95% rename from frontend/gateway/container.go rename to frontend/gateway/container/container.go index 45cf2d90eb50..27874819199e 100644 --- a/frontend/gateway/container.go +++ b/frontend/gateway/container/container.go @@ -1,4 +1,4 @@ -package gateway +package container import ( "context" @@ -43,7 +43,7 @@ type Mount struct { WorkerRef *worker.WorkerRef } -func NewContainer(ctx context.Context, w worker.Worker, sm *session.Manager, g session.Group, req NewContainerRequest) (client.Container, error) { +func NewContainer(ctx context.Context, cm cache.Manager, exec executor.Executor, sm *session.Manager, g session.Group, req NewContainerRequest) (client.Container, error) { ctx, cancel := context.WithCancel(ctx) eg, ctx := errgroup.WithContext(ctx) platform := opspb.Platform{ @@ -58,7 +58,7 @@ func NewContainer(ctx context.Context, w worker.Worker, sm *session.Manager, g s netMode: req.NetMode, extraHosts: req.ExtraHosts, platform: platform, - executor: w.Executor(), + executor: exec, errGroup: eg, ctx: ctx, cancel: cancel, @@ -79,9 +79,8 @@ func NewContainer(ctx context.Context, w worker.Worker, sm *session.Manager, g s } name := fmt.Sprintf("container %s", req.ContainerID) - mm := mounts.NewMountManager(name, w.CacheManager(), sm) - p, err := PrepareMounts(ctx, mm, w.CacheManager(), g, "", mnts, refs, func(m *opspb.Mount, ref cache.ImmutableRef) (cache.MutableRef, error) { - cm := w.CacheManager() + mm := mounts.NewMountManager(name, cm, sm) + p, err := PrepareMounts(ctx, mm, cm, g, "", mnts, refs, func(m *opspb.Mount, ref cache.ImmutableRef) (cache.MutableRef, error) { if m.Input != opspb.Empty { cm = refs[m.Input].Worker.CacheManager() } @@ -255,9 +254,9 @@ func PrepareMounts(ctx context.Context, mm *mounts.MountManager, cm cache.Manage }) root = active } - p.Root = mountWithSession(root, g) + p.Root = MountWithSession(root, g) } else { - mws := mountWithSession(mountable, g) + mws := MountWithSession(mountable, g) dest := m.Dest if !filepath.IsAbs(filepath.Clean(dest)) { dest = filepath.Join("/", cwd, dest) @@ -457,7 +456,7 @@ func addDefaultEnvvar(env []string, k, v string) []string { return append(env, k+"="+v) } -func mountWithSession(m cache.Mountable, g session.Group) executor.Mount { +func MountWithSession(m cache.Mountable, g session.Group) executor.Mount { _, readonly := m.(cache.ImmutableRef) return executor.Mount{ Src: &mountable{m: m, g: g}, diff --git a/frontend/gateway/util.go b/frontend/gateway/container/util.go similarity index 96% rename from frontend/gateway/util.go rename to frontend/gateway/container/util.go index 0de8353402fa..1a1fb25138e3 100644 --- a/frontend/gateway/util.go +++ b/frontend/gateway/container/util.go @@ -1,4 +1,4 @@ -package gateway +package container import ( "net" diff --git a/frontend/gateway/forwarder/forward.go b/frontend/gateway/forwarder/forward.go index 0a95de377dc2..69d94c337fe2 100644 --- a/frontend/gateway/forwarder/forward.go +++ b/frontend/gateway/forwarder/forward.go @@ -6,9 +6,10 @@ import ( cacheutil "github.com/moby/buildkit/cache/util" "github.com/moby/buildkit/client/llb" + "github.com/moby/buildkit/executor" "github.com/moby/buildkit/frontend" - "github.com/moby/buildkit/frontend/gateway" "github.com/moby/buildkit/frontend/gateway/client" + "github.com/moby/buildkit/frontend/gateway/container" gwpb "github.com/moby/buildkit/frontend/gateway/pb" "github.com/moby/buildkit/identity" "github.com/moby/buildkit/session" @@ -25,8 +26,8 @@ import ( "golang.org/x/sync/errgroup" ) -func llbBridgeToGatewayClient(ctx context.Context, llbBridge frontend.FrontendLLBBridge, opts map[string]string, inputs map[string]*opspb.Definition, w worker.Infos, sid string, sm *session.Manager) (*bridgeClient, error) { - bc := &bridgeClient{ +func LLBBridgeToGatewayClient(ctx context.Context, llbBridge frontend.FrontendLLBBridge, exec executor.Executor, opts map[string]string, inputs map[string]*opspb.Definition, w worker.Infos, sid string, sm *session.Manager) (*BridgeClient, error) { + bc := &BridgeClient{ opts: opts, inputs: inputs, FrontendLLBBridge: llbBridge, @@ -35,12 +36,13 @@ func llbBridgeToGatewayClient(ctx context.Context, llbBridge frontend.FrontendLL workers: w, final: map[*ref]struct{}{}, workerRefByID: make(map[string]*worker.WorkerRef), + executor: exec, } bc.buildOpts = bc.loadBuildOpts() return bc, nil } -type bridgeClient struct { +type BridgeClient struct { frontend.FrontendLLBBridge mu sync.Mutex opts map[string]string @@ -53,9 +55,10 @@ type bridgeClient struct { workerRefByID map[string]*worker.WorkerRef buildOpts client.BuildOpts ctrs []client.Container + executor executor.Executor } -func (c *bridgeClient) Solve(ctx context.Context, req client.SolveRequest) (*client.Result, error) { +func (c *BridgeClient) Solve(ctx context.Context, req client.SolveRequest) (*client.Result, error) { res, err := c.FrontendLLBBridge.Solve(ctx, frontend.SolveRequest{ Evaluate: req.Evaluate, Definition: req.Definition, @@ -91,7 +94,7 @@ func (c *bridgeClient) Solve(ctx context.Context, req client.SolveRequest) (*cli return cRes, nil } -func (c *bridgeClient) loadBuildOpts() client.BuildOpts { +func (c *BridgeClient) loadBuildOpts() client.BuildOpts { wis := c.workers.WorkerInfos() workers := make([]client.WorkerInfo, len(wis)) for i, w := range wis { @@ -112,11 +115,11 @@ func (c *bridgeClient) loadBuildOpts() client.BuildOpts { } } -func (c *bridgeClient) BuildOpts() client.BuildOpts { +func (c *BridgeClient) BuildOpts() client.BuildOpts { return c.buildOpts } -func (c *bridgeClient) Inputs(ctx context.Context) (map[string]llb.State, error) { +func (c *BridgeClient) Inputs(ctx context.Context) (map[string]llb.State, error) { inputs := make(map[string]llb.State) for key, def := range c.inputs { defop, err := llb.NewDefinitionOp(def) @@ -128,7 +131,7 @@ func (c *bridgeClient) Inputs(ctx context.Context) (map[string]llb.State, error) return inputs, nil } -func (c *bridgeClient) wrapSolveError(solveErr error) error { +func (c *BridgeClient) wrapSolveError(solveErr error) error { var ( ee *llberrdefs.ExecError fae *llberrdefs.FileActionError @@ -162,7 +165,7 @@ func (c *bridgeClient) wrapSolveError(solveErr error) error { return errdefs.WithSolveError(solveErr, subject, inputIDs, mountIDs) } -func (c *bridgeClient) registerResultIDs(results ...solver.Result) (ids []string, err error) { +func (c *BridgeClient) registerResultIDs(results ...solver.Result) (ids []string, err error) { c.mu.Lock() defer c.mu.Unlock() @@ -181,7 +184,7 @@ func (c *bridgeClient) registerResultIDs(results ...solver.Result) (ids []string return ids, nil } -func (c *bridgeClient) toFrontendResult(r *client.Result) (*frontend.Result, error) { +func (c *BridgeClient) toFrontendResult(r *client.Result) (*frontend.Result, error) { if r == nil { return nil, nil } @@ -212,7 +215,7 @@ func (c *bridgeClient) toFrontendResult(r *client.Result) (*frontend.Result, err return res, nil } -func (c *bridgeClient) discard(err error) { +func (c *BridgeClient) discard(err error) { for _, ctr := range c.ctrs { ctr.Release(context.TODO()) } @@ -230,15 +233,15 @@ func (c *bridgeClient) discard(err error) { } } -func (c *bridgeClient) Warn(ctx context.Context, dgst digest.Digest, msg string, opts client.WarnOpts) error { +func (c *BridgeClient) Warn(ctx context.Context, dgst digest.Digest, msg string, opts client.WarnOpts) error { return c.FrontendLLBBridge.Warn(ctx, dgst, msg, opts) } -func (c *bridgeClient) NewContainer(ctx context.Context, req client.NewContainerRequest) (client.Container, error) { - ctrReq := gateway.NewContainerRequest{ +func (c *BridgeClient) NewContainer(ctx context.Context, req client.NewContainerRequest) (client.Container, error) { + ctrReq := container.NewContainerRequest{ ContainerID: identity.NewID(), NetMode: req.NetMode, - Mounts: make([]gateway.Mount, len(req.Mounts)), + Mounts: make([]container.Mount, len(req.Mounts)), } eg, ctx := errgroup.WithContext(ctx) @@ -269,7 +272,7 @@ func (c *bridgeClient) NewContainer(ctx context.Context, req client.NewContainer return errors.Errorf("failed to find ref %s for %q mount", m.ResultID, m.Dest) } } - ctrReq.Mounts[i] = gateway.Mount{ + ctrReq.Mounts[i] = container.Mount{ WorkerRef: workerRef, Mount: &opspb.Mount{ Dest: m.Dest, @@ -290,18 +293,18 @@ func (c *bridgeClient) NewContainer(ctx context.Context, req client.NewContainer return nil, err } - ctrReq.ExtraHosts, err = gateway.ParseExtraHosts(req.ExtraHosts) + ctrReq.ExtraHosts, err = container.ParseExtraHosts(req.ExtraHosts) if err != nil { return nil, err } - w, err := c.workers.GetDefault() + cm, err := c.workers.DefaultCacheManager() if err != nil { return nil, err } group := session.NewGroup(c.sid) - ctr, err := gateway.NewContainer(ctx, w, c.sm, group, ctrReq) + ctr, err := container.NewContainer(ctx, cm, c.executor, c.sm, group, ctrReq) if err != nil { return nil, err } @@ -312,10 +315,10 @@ func (c *bridgeClient) NewContainer(ctx context.Context, req client.NewContainer type ref struct { solver.ResultProxy session session.Group - c *bridgeClient + c *BridgeClient } -func (c *bridgeClient) newRef(r solver.ResultProxy, s session.Group) (*ref, error) { +func (c *BridgeClient) newRef(r solver.ResultProxy, s session.Group) (*ref, error) { return &ref{ResultProxy: r, session: s, c: c}, nil } diff --git a/frontend/gateway/forwarder/frontend.go b/frontend/gateway/forwarder/frontend.go index 7cd25a0e8ea0..9b6381df517c 100644 --- a/frontend/gateway/forwarder/frontend.go +++ b/frontend/gateway/forwarder/frontend.go @@ -3,6 +3,7 @@ package forwarder import ( "context" + "github.com/moby/buildkit/executor" "github.com/moby/buildkit/frontend" "github.com/moby/buildkit/frontend/gateway/client" "github.com/moby/buildkit/session" @@ -22,8 +23,8 @@ type GatewayForwarder struct { f client.BuildFunc } -func (gf *GatewayForwarder) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBridge, opts map[string]string, inputs map[string]*pb.Definition, sid string, sm *session.Manager) (retRes *frontend.Result, retErr error) { - c, err := llbBridgeToGatewayClient(ctx, llbBridge, opts, inputs, gf.workers, sid, sm) +func (gf *GatewayForwarder) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBridge, exec executor.Executor, opts map[string]string, inputs map[string]*pb.Definition, sid string, sm *session.Manager) (retRes *frontend.Result, retErr error) { + c, err := LLBBridgeToGatewayClient(ctx, llbBridge, exec, opts, inputs, gf.workers, sid, sm) if err != nil { return nil, err } diff --git a/frontend/gateway/gateway.go b/frontend/gateway/gateway.go index 85a42e299def..540a345c3362 100644 --- a/frontend/gateway/gateway.go +++ b/frontend/gateway/gateway.go @@ -29,6 +29,7 @@ import ( "github.com/moby/buildkit/exporter/containerimage/exptypes" "github.com/moby/buildkit/frontend" gwclient "github.com/moby/buildkit/frontend/gateway/client" + "github.com/moby/buildkit/frontend/gateway/container" pb "github.com/moby/buildkit/frontend/gateway/pb" "github.com/moby/buildkit/identity" "github.com/moby/buildkit/session" @@ -83,7 +84,7 @@ func filterPrefix(opts map[string]string, pfx string) map[string]string { return m } -func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBridge, opts map[string]string, inputs map[string]*opspb.Definition, sid string, sm *session.Manager) (*frontend.Result, error) { +func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBridge, exec executor.Executor, opts map[string]string, inputs map[string]*opspb.Definition, sid string, sm *session.Manager) (*frontend.Result, error) { source, ok := opts[keySource] if !ok { return nil, errors.Errorf("no source specified for gateway") @@ -251,18 +252,13 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten } } - lbf, ctx, err := serveLLBBridgeForwarder(ctx, llbBridge, gf.workers, inputs, sid, sm) + lbf, ctx, err := serveLLBBridgeForwarder(ctx, llbBridge, exec, gf.workers, inputs, sid, sm) defer lbf.conn.Close() //nolint if err != nil { return nil, err } defer lbf.Discard() - w, err := gf.workers.GetDefault() - if err != nil { - return nil, err - } - mdmnt, release, err := metadataMount(frontendDef) if err != nil { return nil, err @@ -275,8 +271,7 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten mnts = append(mnts, *mdmnt) } - err = w.Executor().Run(ctx, "", mountWithSession(rootFS, session.NewGroup(sid)), mnts, executor.ProcessInfo{Meta: meta, Stdin: lbf.Stdin, Stdout: lbf.Stdout, Stderr: os.Stderr}, nil) - + err = exec.Run(ctx, "", container.MountWithSession(rootFS, session.NewGroup(sid)), mnts, executor.ProcessInfo{Meta: meta, Stdin: lbf.Stdin, Stdout: lbf.Stdout, Stderr: os.Stderr}, nil) if err != nil { if errdefs.IsCanceled(ctx, err) && lbf.isErrServerClosed { err = errors.Errorf("frontend grpc server closed unexpectedly") @@ -411,11 +406,11 @@ func (lbf *llbBridgeForwarder) Result() (*frontend.Result, error) { return lbf.result, nil } -func NewBridgeForwarder(ctx context.Context, llbBridge frontend.FrontendLLBBridge, workers worker.Infos, inputs map[string]*opspb.Definition, sid string, sm *session.Manager) LLBBridgeForwarder { - return newBridgeForwarder(ctx, llbBridge, workers, inputs, sid, sm) +func NewBridgeForwarder(ctx context.Context, llbBridge frontend.FrontendLLBBridge, exec executor.Executor, workers worker.Infos, inputs map[string]*opspb.Definition, sid string, sm *session.Manager) LLBBridgeForwarder { + return newBridgeForwarder(ctx, llbBridge, exec, workers, inputs, sid, sm) } -func newBridgeForwarder(ctx context.Context, llbBridge frontend.FrontendLLBBridge, workers worker.Infos, inputs map[string]*opspb.Definition, sid string, sm *session.Manager) *llbBridgeForwarder { +func newBridgeForwarder(ctx context.Context, llbBridge frontend.FrontendLLBBridge, exec executor.Executor, workers worker.Infos, inputs map[string]*opspb.Definition, sid string, sm *session.Manager) *llbBridgeForwarder { lbf := &llbBridgeForwarder{ callCtx: ctx, llbBridge: llbBridge, @@ -428,13 +423,14 @@ func newBridgeForwarder(ctx context.Context, llbBridge frontend.FrontendLLBBridg sid: sid, sm: sm, ctrs: map[string]gwclient.Container{}, + executor: exec, } return lbf } -func serveLLBBridgeForwarder(ctx context.Context, llbBridge frontend.FrontendLLBBridge, workers worker.Infos, inputs map[string]*opspb.Definition, sid string, sm *session.Manager) (*llbBridgeForwarder, context.Context, error) { +func serveLLBBridgeForwarder(ctx context.Context, llbBridge frontend.FrontendLLBBridge, exec executor.Executor, workers worker.Infos, inputs map[string]*opspb.Definition, sid string, sm *session.Manager) (*llbBridgeForwarder, context.Context, error) { ctx, cancel := context.WithCancel(ctx) - lbf := newBridgeForwarder(ctx, llbBridge, workers, inputs, sid, sm) + lbf := newBridgeForwarder(ctx, llbBridge, exec, workers, inputs, sid, sm) server := grpc.NewServer(grpc.UnaryInterceptor(grpcerrors.UnaryServerInterceptor), grpc.StreamInterceptor(grpcerrors.StreamServerInterceptor)) grpc_health_v1.RegisterHealthServer(server, health.NewServer()) pb.RegisterLLBBridgeServer(server, lbf) @@ -529,6 +525,7 @@ type llbBridgeForwarder struct { isErrServerClosed bool sid string sm *session.Manager + executor executor.Executor *pipe ctrs map[string]gwclient.Container ctrsMu sync.Mutex @@ -930,7 +927,7 @@ func (lbf *llbBridgeForwarder) Inputs(ctx context.Context, in *pb.InputsRequest) func (lbf *llbBridgeForwarder) NewContainer(ctx context.Context, in *pb.NewContainerRequest) (_ *pb.NewContainerResponse, err error) { bklog.G(ctx).Debugf("|<--- NewContainer %s", in.ContainerID) - ctrReq := NewContainerRequest{ + ctrReq := container.NewContainerRequest{ ContainerID: in.ContainerID, NetMode: in.Network, Platform: in.Platform, @@ -959,7 +956,7 @@ func (lbf *llbBridgeForwarder) NewContainer(ctx context.Context, in *pb.NewConta } } } - ctrReq.Mounts = append(ctrReq.Mounts, Mount{ + ctrReq.Mounts = append(ctrReq.Mounts, container.Mount{ WorkerRef: workerRef, Mount: &opspb.Mount{ Dest: m.Dest, @@ -977,17 +974,17 @@ func (lbf *llbBridgeForwarder) NewContainer(ctx context.Context, in *pb.NewConta // and we want the context to live for the duration of the container. group := session.NewGroup(lbf.sid) - w, err := lbf.workers.GetDefault() + cm, err := lbf.workers.DefaultCacheManager() if err != nil { return nil, stack.Enable(err) } - ctrReq.ExtraHosts, err = ParseExtraHosts(in.ExtraHosts) + ctrReq.ExtraHosts, err = container.ParseExtraHosts(in.ExtraHosts) if err != nil { return nil, stack.Enable(err) } - ctr, err := NewContainer(context.Background(), w, lbf.sm, group, ctrReq) + ctr, err := container.NewContainer(context.Background(), cm, lbf.executor, lbf.sm, group, ctrReq) if err != nil { return nil, stack.Enable(err) } diff --git a/snapshot/localmounter.go b/snapshot/localmounter.go index 9ddb7c1af642..304eebc9e02d 100644 --- a/snapshot/localmounter.go +++ b/snapshot/localmounter.go @@ -11,22 +11,39 @@ type Mounter interface { Unmount() error } +type LocalMounterOpt func(*localMounter) + // LocalMounter is a helper for mounting mountfactory to temporary path. In // addition it can mount binds without privileges -func LocalMounter(mountable Mountable) Mounter { - return &localMounter{mountable: mountable} +func LocalMounter(mountable Mountable, opts ...LocalMounterOpt) Mounter { + lm := &localMounter{mountable: mountable} + for _, opt := range opts { + opt(lm) + } + return lm } // LocalMounterWithMounts is a helper for mounting to temporary path. In // addition it can mount binds without privileges -func LocalMounterWithMounts(mounts []mount.Mount) Mounter { - return &localMounter{mounts: mounts} +func LocalMounterWithMounts(mounts []mount.Mount, opts ...LocalMounterOpt) Mounter { + lm := &localMounter{mounts: mounts} + for _, opt := range opts { + opt(lm) + } + return lm } type localMounter struct { - mu sync.Mutex - mounts []mount.Mount - mountable Mountable - target string - release func() error + mu sync.Mutex + mounts []mount.Mount + mountable Mountable + target string + release func() error + forceRemount bool +} + +func ForceRemount() LocalMounterOpt { + return func(lm *localMounter) { + lm.forceRemount = true + } } diff --git a/snapshot/localmounter_unix.go b/snapshot/localmounter_unix.go index ef73e263fc91..4bace4e664c7 100644 --- a/snapshot/localmounter_unix.go +++ b/snapshot/localmounter_unix.go @@ -4,8 +4,8 @@ package snapshot import ( - "io/ioutil" "os" + "path/filepath" "syscall" "github.com/containerd/containerd/mount" @@ -25,30 +25,48 @@ func (lm *localMounter) Mount() (string, error) { lm.release = release } + var isFile bool if len(lm.mounts) == 1 && (lm.mounts[0].Type == "bind" || lm.mounts[0].Type == "rbind") { - ro := false - for _, opt := range lm.mounts[0].Options { - if opt == "ro" { - ro = true - break + if !lm.forceRemount { + ro := false + for _, opt := range lm.mounts[0].Options { + if opt == "ro" { + ro = true + break + } } + if !ro { + return lm.mounts[0].Source, nil + } + } + fi, err := os.Stat(lm.mounts[0].Source) + if err != nil { + return "", err } - if !ro { - return lm.mounts[0].Source, nil + if !fi.IsDir() { + isFile = true } } - dir, err := ioutil.TempDir("", "buildkit-mount") + dest, err := os.MkdirTemp("", "buildkit-mount") if err != nil { return "", errors.Wrap(err, "failed to create temp dir") } - if err := mount.All(lm.mounts, dir); err != nil { - os.RemoveAll(dir) - return "", errors.Wrapf(err, "failed to mount %s: %+v", dir, lm.mounts) + if isFile { + dest = filepath.Join(dest, "file") + if err := os.WriteFile(dest, []byte{}, 0644); err != nil { + os.RemoveAll(dest) + return "", errors.Wrap(err, "failed to create temp file") + } + } + + if err := mount.All(lm.mounts, dest); err != nil { + os.RemoveAll(dest) + return "", errors.Wrapf(err, "failed to mount %s: %+v", dest, lm.mounts) } - lm.target = dir - return dir, nil + lm.target = dest + return dest, nil } func (lm *localMounter) Unmount() error { diff --git a/snapshot/snapshotter.go b/snapshot/snapshotter.go index edf95cee70cd..3150815bb3bc 100644 --- a/snapshot/snapshotter.go +++ b/snapshot/snapshotter.go @@ -10,14 +10,11 @@ import ( "github.com/containerd/containerd/pkg/userns" "github.com/containerd/containerd/snapshots" "github.com/docker/docker/pkg/idtools" + "github.com/moby/buildkit/executor" "github.com/pkg/errors" ) -type Mountable interface { - // ID() string - Mount() ([]mount.Mount, func() error, error) - IdentityMapping() *idtools.IdentityMapping -} +type Mountable = executor.MountableRef // Snapshotter defines interface that any snapshot implementation should satisfy type Snapshotter interface { diff --git a/solver/llbsolver/bridge.go b/solver/llbsolver/bridge.go index 8507280a109a..1707654fe4e5 100644 --- a/solver/llbsolver/bridge.go +++ b/solver/llbsolver/bridge.go @@ -11,6 +11,7 @@ import ( "github.com/moby/buildkit/cache/remotecache" "github.com/moby/buildkit/client" "github.com/moby/buildkit/client/llb" + "github.com/moby/buildkit/executor" "github.com/moby/buildkit/exporter/containerimage/exptypes" "github.com/moby/buildkit/frontend" gw "github.com/moby/buildkit/frontend/gateway/client" @@ -22,6 +23,7 @@ import ( "github.com/moby/buildkit/solver/pb" "github.com/moby/buildkit/util/bklog" "github.com/moby/buildkit/util/buildinfo" + "github.com/moby/buildkit/util/entitlements" "github.com/moby/buildkit/util/flightcontrol" "github.com/moby/buildkit/util/progress" "github.com/moby/buildkit/worker" @@ -38,6 +40,10 @@ type llbBridge struct { cms map[string]solver.CacheManager cmsMu sync.Mutex sm *session.Manager + + executorOnce sync.Once + executorErr error + executor executor.Executor } func (b *llbBridge) Warn(ctx context.Context, dgst digest.Digest, msg string, opts frontend.WarnOpts) error { @@ -135,6 +141,18 @@ func (b *llbBridge) loadResult(ctx context.Context, def *pb.Definition, cacheImp return res, bi, nil } +func (b *llbBridge) validateEntitlements(p executor.ProcessInfo) error { + ent, err := loadEntitlements(b.builder) + if err != nil { + return err + } + v := entitlements.Values{ + NetworkHost: p.Meta.NetMode == pb.NetMode_HOST, + SecurityInsecure: p.Meta.SecurityMode == pb.SecurityMode_INSECURE, + } + return ent.Check(v) +} + func (b *llbBridge) Solve(ctx context.Context, req frontend.SolveRequest, sid string) (res *frontend.Result, err error) { if req.Definition != nil && req.Definition.Def != nil && req.Frontend != "" { return nil, errors.New("cannot solve with both Definition and Frontend specified") @@ -150,7 +168,7 @@ func (b *llbBridge) Solve(ctx context.Context, req frontend.SolveRequest, sid st if !ok { return nil, errors.Errorf("invalid frontend: %s", req.Frontend) } - res, err = f.Solve(ctx, b, req.FrontendOpt, req.FrontendInputs, sid, b.sm) + res, err = f.Solve(ctx, b, b, req.FrontendOpt, req.FrontendInputs, sid, b.sm) if err != nil { return nil, err } @@ -187,6 +205,40 @@ func (b *llbBridge) Solve(ctx context.Context, req frontend.SolveRequest, sid st return } +func (b *llbBridge) Run(ctx context.Context, id string, rootfs executor.Mount, mounts []executor.Mount, process executor.ProcessInfo, started chan<- struct{}) error { + if err := b.validateEntitlements(process); err != nil { + return err + } + + if err := b.loadExecutor(); err != nil { + return err + } + return b.executor.Run(ctx, id, rootfs, mounts, process, started) +} + +func (b *llbBridge) Exec(ctx context.Context, id string, process executor.ProcessInfo) error { + if err := b.validateEntitlements(process); err != nil { + return err + } + + if err := b.loadExecutor(); err != nil { + return err + } + return b.executor.Exec(ctx, id, process) +} + +func (b *llbBridge) loadExecutor() error { + b.executorOnce.Do(func() { + w, err := b.resolveWorker() + if err != nil { + b.executorErr = err + return + } + b.executor = w.Executor() + }) + return b.executorErr +} + type resultProxy struct { cb func(context.Context) (solver.CachedResult, solver.BuildSources, error) def *pb.Definition diff --git a/solver/llbsolver/ops/exec.go b/solver/llbsolver/ops/exec.go index 6cca733c0bf2..c2b40d6a3aa5 100644 --- a/solver/llbsolver/ops/exec.go +++ b/solver/llbsolver/ops/exec.go @@ -13,7 +13,7 @@ import ( "github.com/containerd/containerd/platforms" "github.com/moby/buildkit/cache" "github.com/moby/buildkit/executor" - "github.com/moby/buildkit/frontend/gateway" + "github.com/moby/buildkit/frontend/gateway/container" "github.com/moby/buildkit/session" "github.com/moby/buildkit/session/secrets" "github.com/moby/buildkit/solver" @@ -258,7 +258,7 @@ func (e *execOp) Exec(ctx context.Context, g session.Group, inputs []solver.Resu } } - p, err := gateway.PrepareMounts(ctx, e.mm, e.cm, g, e.op.Meta.Cwd, e.op.Mounts, refs, func(m *pb.Mount, ref cache.ImmutableRef) (cache.MutableRef, error) { + p, err := container.PrepareMounts(ctx, e.mm, e.cm, g, e.op.Meta.Cwd, e.op.Mounts, refs, func(m *pb.Mount, ref cache.ImmutableRef) (cache.MutableRef, error) { desc := fmt.Sprintf("mount %s from exec %s", m.Dest, strings.Join(e.op.Meta.Args, " ")) return e.cm.New(ctx, ref, g, cache.WithDescription(desc)) }) @@ -305,7 +305,7 @@ func (e *execOp) Exec(ctx context.Context, g session.Group, inputs []solver.Resu return nil, err } - extraHosts, err := gateway.ParseExtraHosts(e.op.Meta.ExtraHosts) + extraHosts, err := container.ParseExtraHosts(e.op.Meta.ExtraHosts) if err != nil { return nil, err } diff --git a/solver/llbsolver/solver.go b/solver/llbsolver/solver.go index ee06233da5ad..3b1149087bd2 100644 --- a/solver/llbsolver/solver.go +++ b/solver/llbsolver/solver.go @@ -81,7 +81,7 @@ func (s *Solver) resolver() solver.ResolveOpFunc { } } -func (s *Solver) Bridge(b solver.Builder) frontend.FrontendLLBBridge { +func (s *Solver) bridge(b solver.Builder) *llbBridge { return &llbBridge{ builder: b, frontends: s.frontends, @@ -93,6 +93,10 @@ func (s *Solver) Bridge(b solver.Builder) frontend.FrontendLLBBridge { } } +func (s *Solver) Bridge(b solver.Builder) frontend.FrontendLLBBridge { + return s.bridge(b) +} + func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req frontend.SolveRequest, exp ExporterRequest, ent []entitlements.Entitlement) (*client.SolveResponse, error) { j, err := s.solver.NewJob(id) if err != nil { @@ -109,9 +113,10 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro j.SessionID = sessionID + br := s.bridge(j) var res *frontend.Result if s.gatewayForwarder != nil && req.Definition == nil && req.Frontend == "" { - fwd := gateway.NewBridgeForwarder(ctx, s.Bridge(j), s.workerController, req.FrontendInputs, sessionID, s.sm) + fwd := gateway.NewBridgeForwarder(ctx, br, br, s.workerController.Infos(), req.FrontendInputs, sessionID, s.sm) defer fwd.Discard() if err := s.gatewayForwarder.RegisterBuild(ctx, id, fwd); err != nil { return nil, err diff --git a/solver/llbsolver/vertex.go b/solver/llbsolver/vertex.go index 4f36c2eddbb3..ab1778686481 100644 --- a/solver/llbsolver/vertex.go +++ b/solver/llbsolver/vertex.go @@ -99,16 +99,12 @@ func ValidateEntitlements(ent entitlements.Set) LoadOpt { return func(op *pb.Op, _ *pb.OpMetadata, opt *solver.VertexOptions) error { switch op := op.Op.(type) { case *pb.Op_Exec: - if op.Exec.Network == pb.NetMode_HOST { - if !ent.Allowed(entitlements.EntitlementNetworkHost) { - return errors.Errorf("%s is not allowed", entitlements.EntitlementNetworkHost) - } + v := entitlements.Values{ + NetworkHost: op.Exec.Network == pb.NetMode_HOST, + SecurityInsecure: op.Exec.Security == pb.SecurityMode_INSECURE, } - - if op.Exec.Security == pb.SecurityMode_INSECURE { - if !ent.Allowed(entitlements.EntitlementSecurityInsecure) { - return errors.Errorf("%s is not allowed", entitlements.EntitlementSecurityInsecure) - } + if err := ent.Check(v); err != nil { + return err } } return nil diff --git a/util/entitlements/entitlements.go b/util/entitlements/entitlements.go index f65b426bb201..328580c326df 100644 --- a/util/entitlements/entitlements.go +++ b/util/entitlements/entitlements.go @@ -58,3 +58,23 @@ func (s Set) Allowed(e Entitlement) bool { _, ok := s[e] return ok } + +func (s Set) Check(v Values) error { + if v.NetworkHost { + if !s.Allowed(EntitlementNetworkHost) { + return errors.Errorf("%s is not allowed", EntitlementNetworkHost) + } + } + + if v.SecurityInsecure { + if !s.Allowed(EntitlementSecurityInsecure) { + return errors.Errorf("%s is not allowed", EntitlementSecurityInsecure) + } + } + return nil +} + +type Values struct { + NetworkHost bool + SecurityInsecure bool +} diff --git a/util/system/atime_unix.go b/util/system/atime_unix.go new file mode 100644 index 000000000000..9a7af36ffcc6 --- /dev/null +++ b/util/system/atime_unix.go @@ -0,0 +1,21 @@ +//go:build !windows +// +build !windows + +package system + +import ( + iofs "io/fs" + "syscall" + "time" + + "github.com/containerd/continuity/fs" + "github.com/pkg/errors" +) + +func Atime(st iofs.FileInfo) (time.Time, error) { + stSys, ok := st.Sys().(*syscall.Stat_t) + if !ok { + return time.Time{}, errors.Errorf("expected st.Sys() to be *syscall.Stat_t, got %T", st.Sys()) + } + return fs.StatATimeAsTime(stSys), nil +} diff --git a/util/system/atime_windows.go b/util/system/atime_windows.go new file mode 100644 index 000000000000..808408b613cf --- /dev/null +++ b/util/system/atime_windows.go @@ -0,0 +1,17 @@ +package system + +import ( + "fmt" + iofs "io/fs" + "syscall" + "time" +) + +func Atime(st iofs.FileInfo) (time.Time, error) { + stSys, ok := st.Sys().(*syscall.Win32FileAttributeData) + if !ok { + return time.Time{}, fmt.Errorf("expected st.Sys() to be *syscall.Win32FileAttributeData, got %T", st.Sys()) + } + // ref: https://github.com/golang/go/blob/go1.19.2/src/os/types_windows.go#L230 + return time.Unix(0, stSys.LastAccessTime.Nanoseconds()), nil +} diff --git a/worker/worker.go b/worker/worker.go index 86521c5bab40..39c20738b96f 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -38,6 +38,6 @@ type Worker interface { } type Infos interface { - GetDefault() (Worker, error) + DefaultCacheManager() (cache.Manager, error) WorkerInfos() []client.WorkerInfo } diff --git a/worker/workercontroller.go b/worker/workercontroller.go index 26ca9459231c..c611a47fd379 100644 --- a/worker/workercontroller.go +++ b/worker/workercontroller.go @@ -2,6 +2,7 @@ package worker import ( "github.com/containerd/containerd/filters" + "github.com/moby/buildkit/cache" "github.com/moby/buildkit/client" "github.com/pkg/errors" ) @@ -69,3 +70,25 @@ func (c *Controller) WorkerInfos() []client.WorkerInfo { } return out } + +func (c *Controller) Infos() Infos { + return &infosController{c: c} +} + +type infosController struct { + c *Controller +} + +var _ Infos = &infosController{} + +func (c *infosController) DefaultCacheManager() (cache.Manager, error) { + w, err := c.c.GetDefault() + if err != nil { + return nil, err + } + return w.CacheManager(), nil +} + +func (c *infosController) WorkerInfos() []client.WorkerInfo { + return c.c.WorkerInfos() +}