diff --git a/client/client.go b/client/client.go index 74e5e723..644c47f2 100644 --- a/client/client.go +++ b/client/client.go @@ -114,7 +114,11 @@ func (c *Client) buildfn(ctx context.Context, st *state.State, env *environment. // Setup solve options opts := bk.SolveOpt{ LocalDirs: localdirs, - Session: []session.Attachable{auth, secrets}, + Session: []session.Attachable{ + auth, + secrets, + solver.NewDockerSocketProvider(), + }, } // Call buildkit solver diff --git a/environment/pipeline.go b/environment/pipeline.go index 08c4f74b..d00793aa 100644 --- a/environment/pipeline.go +++ b/environment/pipeline.go @@ -468,6 +468,11 @@ func (p *Pipeline) mount(ctx context.Context, dest string, mnt *compiler.Value) llb.Scratch(), llb.Tmpfs(), ), nil + case "docker.sock": + return llb.AddSSHSocket( + llb.SSHID(solver.DockerSocketID), + llb.SSHSocketTarget(dest), + ), nil default: return nil, fmt.Errorf("invalid mount source: %q", s) } diff --git a/solver/dockersocketprovider.go b/solver/dockersocketprovider.go new file mode 100644 index 00000000..6eb79091 --- /dev/null +++ b/solver/dockersocketprovider.go @@ -0,0 +1,62 @@ +package solver + +import ( + "context" + "fmt" + "net" + "time" + + "github.com/moby/buildkit/session" + "github.com/moby/buildkit/session/sshforward" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +const ( + DockerSocketID = "docker.sock" + DockerSocketPath = "/var/run/docker.sock" +) + +type DockerSocketProvider struct { +} + +func NewDockerSocketProvider() session.Attachable { + return &DockerSocketProvider{} +} + +func (sp *DockerSocketProvider) Register(server *grpc.Server) { + sshforward.RegisterSSHServer(server, sp) +} + +func (sp *DockerSocketProvider) CheckAgent(ctx context.Context, req *sshforward.CheckAgentRequest) (*sshforward.CheckAgentResponse, error) { + id := sshforward.DefaultID + if req.ID != "" { + id = req.ID + } + if id != DockerSocketID { + return &sshforward.CheckAgentResponse{}, fmt.Errorf("invalid socket forward key %s", id) + } + return &sshforward.CheckAgentResponse{}, nil +} + +func (sp *DockerSocketProvider) ForwardAgent(stream sshforward.SSH_ForwardAgentServer) error { + id := sshforward.DefaultID + + opts, _ := metadata.FromIncomingContext(stream.Context()) // if no metadata continue with empty object + + if v, ok := opts[sshforward.KeySSHID]; ok && len(v) > 0 && v[0] != "" { + id = v[0] + } + + if id != DockerSocketID { + return fmt.Errorf("invalid socket forward key %s", id) + } + + conn, err := net.DialTimeout("unix", DockerSocketPath, time.Second) + if err != nil { + return fmt.Errorf("failed to connect to %s: %w", DockerSocketPath, err) + } + defer conn.Close() + + return sshforward.Copy(context.TODO(), conn, stream, nil) +} diff --git a/solver/solver.go b/solver/solver.go index 7ec67cf8..1df62ac5 100644 --- a/solver/solver.go +++ b/solver/solver.go @@ -157,7 +157,11 @@ func (s Solver) Export(ctx context.Context, st llb.State, img *dockerfile2llb.Im opts := bk.SolveOpt{ Exports: []bk.ExportEntry{output}, - Session: []session.Attachable{s.opts.Auth, s.opts.Secrets}, + Session: []session.Attachable{ + s.opts.Auth, + s.opts.Secrets, + NewDockerSocketProvider(), + }, } ch := make(chan *bk.SolveStatus) diff --git a/stdlib/dagger/op/op.cue b/stdlib/dagger/op/op.cue index aeb94423..1eed447f 100644 --- a/stdlib/dagger/op/op.cue +++ b/stdlib/dagger/op/op.cue @@ -52,7 +52,7 @@ package op // `true` means also ignoring the mount cache volumes always?: true | *false dir: string | *"/" - mount: [string]: "tmpfs" | "cache" | {from: _, path: string | *"/"} | {secret: _} + mount: [string]: "tmpfs" | "cache" | "docker.sock" | {from: _, path: string | *"/"} | {secret: _} // Map of hostnames to ip hosts?: [string]: string // User to exec with (if left empty, will default to the set user in the image) diff --git a/tests/compute.bats b/tests/compute.bats index 27b17ce1..17e45ad7 100644 --- a/tests/compute.bats +++ b/tests/compute.bats @@ -65,11 +65,11 @@ setup() { run "$DAGGER" compute --input-string 'in=foobar' "$TESTDIR"/compute/input/default assert_success assert_line '{"in":"foobar","test":"received: foobar"}' - + run "$DAGGER" compute --input-string=foobar "$TESTDIR"/compute/input/default assert_failure assert_output --partial 'failed to parse input: input-string' - + run "$DAGGER" compute --input-dir=foobar "$TESTDIR"/compute/input/default assert_failure assert_output --partial 'failed to parse input: input-dir' @@ -106,6 +106,10 @@ setup() { assert_output "secret=mySecret" } +@test "compute: docker socket" { + run "$DAGGER" compute "$TESTDIR"/compute/dockersocket +} + @test "compute: exclude" { "$DAGGER" up -w "$TESTDIR"/compute/exclude } diff --git a/tests/compute/dockersocket/main.cue b/tests/compute/dockersocket/main.cue new file mode 100644 index 00000000..7b97f7b2 --- /dev/null +++ b/tests/compute/dockersocket/main.cue @@ -0,0 +1,18 @@ +package main + +import ( + "dagger.io/dagger/op" + "dagger.io/docker" +) + +TestDockerSocket: #up: [ + op.#Load & { + from: docker.#Client + }, + + op.#Exec & { + always: true + mount: "/var/run/docker.sock": "docker.sock" + args: ["docker", "info"] + }, +]