docker socket forwarding support

- This PR adds a new mount type: `docker.sock` (in addition to `cache`
  and `tmp`)
- It's then able to mount the LOCAL (as in, from the machine running
  dagger) docker socket inside the container by pretending to be an SSH
  Agent (hijacking the SSH agent forwarding support of buildkit)

Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
This commit is contained in:
Andrea Luzzardi 2021-06-04 15:56:59 -07:00
parent 04cd927006
commit ac34df319a
7 changed files with 102 additions and 5 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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"]
},
]