Skip to content

Commit cb88c54

Browse files
committed
wcow: enable custom frontend bridge communication via named pipe
Signed-off-by: Billy Owire <billyowire95@gmail.com>
1 parent 40a5c2d commit cb88c54

File tree

10 files changed

+166
-7
lines changed

10 files changed

+166
-7
lines changed

executor/containerdexecutor/executor_windows.go

+18
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/moby/buildkit/executor/oci"
1313
"github.com/moby/buildkit/snapshot"
1414
"github.com/moby/buildkit/solver/pb"
15+
"github.com/moby/buildkit/util/appdefaults"
1516
"github.com/moby/buildkit/util/network"
1617
"github.com/moby/sys/user"
1718
"github.com/opencontainers/runtime-spec/specs-go"
@@ -88,6 +89,23 @@ func (w *containerdExecutor) createOCISpec(ctx context.Context, id, _, _ string,
8889
return nil, nil, err
8990
}
9091
releasers = append(releasers, cleanup)
92+
93+
if v, ok := ctx.Value(appdefaults.ContextKeyCustomFrontend).(bool); ok && v {
94+
sid := func() string {
95+
for _, e := range meta.Env {
96+
if strings.HasPrefix(e, "BUILDKIT_SESSION_ID=") {
97+
return strings.TrimPrefix(e, "BUILDKIT_SESSION_ID=")
98+
}
99+
}
100+
return ""
101+
}()
102+
frontendGrpcBridge := appdefaults.FrontendGRPCPipe + sid
103+
spec.Mounts = append(spec.Mounts, specs.Mount{
104+
Source: frontendGrpcBridge,
105+
Destination: frontendGrpcBridge,
106+
Type: "",
107+
})
108+
}
91109
return spec, releaseAll, nil
92110
}
93111

frontend/dockerfile/builder/build.go

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package builder
22

33
import (
44
"context"
5+
"runtime"
56
"strings"
67
"sync"
78

@@ -22,6 +23,7 @@ import (
2223
"github.com/moby/buildkit/solver/errdefs"
2324
"github.com/moby/buildkit/solver/pb"
2425
"github.com/moby/buildkit/solver/result"
26+
"github.com/moby/buildkit/util/appdefaults"
2527
dockerspec "github.com/moby/docker-image-spec/specs-go/v1"
2628
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
2729
"github.com/pkg/errors"
@@ -59,6 +61,9 @@ func Build(ctx context.Context, c client.Client) (_ *client.Result, err error) {
5961
}
6062
return res, err
6163
} else if ref, cmdline, loc, ok := parser.DetectSyntax(src.Data); ok {
64+
if runtime.GOOS == "windows" {
65+
ctx = context.WithValue(ctx, appdefaults.ContextKeyCustomFrontend, true)
66+
}
6267
res, err := forwardGateway(ctx, c, ref, cmdline)
6368
if err != nil && len(errdefs.Sources(err)) == 0 {
6469
return nil, wrapSource(err, src.SourceMap, loc)

frontend/gateway/gateway.go

+12
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net"
1010
"os"
1111
"path/filepath"
12+
"runtime"
1213
"slices"
1314
"strconv"
1415
"strings"
@@ -501,6 +502,8 @@ func newBridgeForwarder(ctx context.Context, llbBridge frontend.FrontendLLBBridg
501502
}
502503

503504
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) {
505+
var listener net.Listener
506+
isWindowsPlatform := runtime.GOOS == "windows"
504507
ctx, cancel := context.WithCancelCause(ctx)
505508
lbf := newBridgeForwarder(ctx, llbBridge, exec, workers, inputs, sid, sm)
506509
serverOpt := []grpc.ServerOption{
@@ -514,11 +517,20 @@ func serveLLBBridgeForwarder(ctx context.Context, llbBridge frontend.FrontendLLB
514517
pb.RegisterLLBBridgeServer(server, lbf)
515518

516519
go func() {
520+
if isWindowsPlatform {
521+
listener = createNPipeListener(sid)
522+
if err := handleWindowsPipeConn(ctx, listener, lbf, cancel); err != nil {
523+
return
524+
}
525+
}
517526
serve(ctx, server, lbf.conn)
518527
select {
519528
case <-ctx.Done():
520529
default:
521530
lbf.isErrServerClosed = true
531+
if isWindowsPlatform && listener != nil {
532+
_ = listener.Close()
533+
}
522534
}
523535
cancel(errors.WithStack(context.Canceled))
524536
}()

frontend/gateway/gateway_unix.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//go:build !windows
2+
3+
package gateway
4+
5+
import (
6+
"context"
7+
"net"
8+
)
9+
10+
func createNPipeListener(_ string) net.Listener {
11+
return nil
12+
}
13+
14+
func handleWindowsPipeConn(_ context.Context, _ net.Listener, _ *llbBridgeForwarder, _ context.CancelCauseFunc) error {
15+
return nil
16+
}

frontend/gateway/gateway_windows.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//go:build windows
2+
3+
package gateway
4+
5+
import (
6+
"context"
7+
"net"
8+
9+
"github.com/Microsoft/go-winio"
10+
"github.com/moby/buildkit/util/appdefaults"
11+
"github.com/moby/buildkit/util/bklog"
12+
"github.com/pkg/errors"
13+
)
14+
15+
func createNPipeListener(sid string) net.Listener {
16+
pipeCfg := &winio.PipeConfig{
17+
SecurityDescriptor: "D:P(A;;GA;;;AU)",
18+
MessageMode: false,
19+
InputBufferSize: 4096,
20+
OutputBufferSize: 4096,
21+
}
22+
listener, err := winio.ListenPipe(appdefaults.FrontendGRPCPipe+sid, pipeCfg)
23+
if err != nil {
24+
bklog.L.Errorf("Failed to initialize named pipe listener: %s", err)
25+
return nil
26+
}
27+
return listener
28+
}
29+
30+
// handleWindowsPipeConn waits for a client to connect to the named pipe.
31+
// It assigns the connection to lbf.conn or cancels the context on error or timeout.
32+
func handleWindowsPipeConn(ctx context.Context, listener net.Listener, lbf *llbBridgeForwarder, cancel context.CancelCauseFunc) error {
33+
connCh := make(chan net.Conn, 1)
34+
errCh := make(chan error, 1)
35+
36+
go func() {
37+
defer func() {
38+
if r := recover(); r != nil {
39+
errCh <- errors.Errorf("panic in Accept: %v", r)
40+
}
41+
}()
42+
conn, err := listener.Accept()
43+
if err != nil {
44+
errCh <- err
45+
return
46+
}
47+
connCh <- conn
48+
}()
49+
50+
select {
51+
case <-ctx.Done():
52+
_ = listener.Close()
53+
return context.Cause(ctx)
54+
case err := <-errCh:
55+
lbf.isErrServerClosed = true
56+
cancel(errors.WithStack(err))
57+
return err
58+
case conn := <-connCh:
59+
lbf.conn = conn
60+
return nil
61+
}
62+
}

frontend/gateway/grpcclient/client.go

+12-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"net"
99
"os"
10+
"runtime"
1011
"strings"
1112
"sync"
1213
"syscall"
@@ -1235,10 +1236,17 @@ func (r *reference) StatFile(ctx context.Context, req client.StatRequest) (*fsty
12351236
}
12361237

12371238
func grpcClientConn(ctx context.Context) (context.Context, *grpc.ClientConn, error) {
1239+
addr := "localhost"
1240+
dialer := grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
1241+
return stdioConn(), nil
1242+
})
1243+
1244+
if runtime.GOOS == "windows" {
1245+
addr, dialer = nPipeDialer(sessionID())
1246+
}
1247+
12381248
dialOpts := []grpc.DialOption{
1239-
grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
1240-
return stdioConn(), nil
1241-
}),
1249+
dialer,
12421250
grpc.WithTransportCredentials(insecure.NewCredentials()),
12431251
grpc.WithUnaryInterceptor(grpcerrors.UnaryClientInterceptor),
12441252
grpc.WithStreamInterceptor(grpcerrors.StreamClientInterceptor),
@@ -1247,7 +1255,7 @@ func grpcClientConn(ctx context.Context) (context.Context, *grpc.ClientConn, err
12471255
}
12481256

12491257
//nolint:staticcheck // ignore SA1019 NewClient has different behavior and needs to be tested
1250-
cc, err := grpc.DialContext(ctx, "localhost", dialOpts...)
1258+
cc, err := grpc.DialContext(ctx, addr, dialOpts...)
12511259
if err != nil {
12521260
return ctx, nil, errors.Wrap(err, "failed to create grpc client")
12531261
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//go:build !windows
2+
3+
package grpcclient
4+
5+
import "google.golang.org/grpc"
6+
7+
func nPipeDialer(_ string) (string, grpc.DialOption) {
8+
return "", nil
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//go:build windows
2+
3+
package grpcclient
4+
5+
import (
6+
"context"
7+
"net"
8+
9+
"github.com/Microsoft/go-winio"
10+
"github.com/moby/buildkit/util/appdefaults"
11+
"github.com/pkg/errors"
12+
"google.golang.org/grpc"
13+
)
14+
15+
func nPipeDialer(sid string) (string, grpc.DialOption) {
16+
addr := appdefaults.FrontendGRPCPipe + sid
17+
dialFn := func(ctx context.Context, _ string) (net.Conn, error) {
18+
conn, err := winio.DialPipe(addr, nil)
19+
if err != nil {
20+
return nil, errors.Wrap(err, "Failed to connect to gRPC server")
21+
}
22+
return conn, nil
23+
}
24+
return addr, grpc.WithContextDialer(dialFn)
25+
}

util/appdefaults/appdefaults.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package appdefaults
22

3+
type contextKey string
4+
35
const (
4-
BridgeName = "buildkit0"
5-
BridgeSubnet = "10.10.0.0/16"
6+
BridgeName = "buildkit0"
7+
BridgeSubnet = "10.10.0.0/16"
8+
ContextKeyCustomFrontend = contextKey("custom.frontend.context.flag")
69
)

util/appdefaults/appdefaults_windows.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import (
66
)
77

88
const (
9-
Address = "npipe:////./pipe/buildkitd"
9+
Address = "npipe:////./pipe/buildkitd"
10+
FrontendGRPCPipe = `\\.\pipe\buildkitd-frontend-`
1011
)
1112

1213
var (

0 commit comments

Comments
 (0)