diff --git a/Dockerfile b/Dockerfile index 510cd458..b5d31dbe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -60,10 +60,6 @@ COPY ./hack/demo-env/tmux.conf /root/.tmux.conf COPY --from=dockerd-release /usr/local/bin /usr/local/bin COPY --from=docker-cli-build /go/src/github.com/docker/cli/build/docker /usr/local/bin -# Temporary buildkitd binaries. To be removed. -COPY --from=moby/buildkit /usr/bin/build* /usr/local/bin -VOLUME /var/lib/buildkit - WORKDIR /work COPY ./hack/demo-env/examples . COPY --from=binaries / /usr/local/bin/ diff --git a/commands/build.go b/commands/build.go index 25acf6cb..9abcce86 100644 --- a/commands/build.go +++ b/commands/build.go @@ -94,7 +94,7 @@ func runBuild(dockerCli command.Cli, in buildOptions) error { } opts.Exports = outputs - d, err := driver.GetDriver(ctx, "buildkit.default", nil, dockerCli.Client()) + d, err := driver.GetDriver(ctx, "buildx-buildkit-default", nil, dockerCli.Client()) if err != nil { return err } diff --git a/driver/docker/driver.go b/driver/docker/driver.go index d51f564a..5f126cae 100644 --- a/driver/docker/driver.go +++ b/driver/docker/driver.go @@ -4,18 +4,24 @@ import ( "context" "io" "io/ioutil" + "net" + "os" + "time" "github.com/docker/docker/api/types" dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" dockerclient "github.com/docker/docker/client" + "github.com/docker/docker/pkg/stdcopy" "github.com/moby/buildkit/client" "github.com/pkg/errors" "github.com/tonistiigi/buildx/driver" "github.com/tonistiigi/buildx/util/progress" ) +var buildkitImage = "moby/buildkit:master" // TODO: make this verified and configuratble + type Driver struct { driver.InitConfig version dockertypes.Version @@ -30,13 +36,21 @@ func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error { } return err } - return d.start(ctx, sub) + return sub.Wrap("starting container "+d.Name, func() error { + if err := d.start(ctx, sub); err != nil { + return err + } + if err := d.wait(ctx); err != nil { + return err + } + return nil + }) }) } func (d *Driver) create(ctx context.Context, l progress.SubLogger) error { - if err := l.Wrap("pulling image moby/buildkit", func() error { - rc, err := d.DockerAPI.ImageCreate(ctx, "moby/buildkit", types.ImageCreateOptions{}) + if err := l.Wrap("pulling image "+buildkitImage, func() error { + rc, err := d.DockerAPI.ImageCreate(ctx, buildkitImage, types.ImageCreateOptions{}) if err != nil { return err } @@ -47,7 +61,7 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error { } if err := l.Wrap("creating container "+d.Name, func() error { _, err := d.DockerAPI.ContainerCreate(ctx, &container.Config{ - Image: "moby/buildkit", + Image: buildkitImage, }, &container.HostConfig{ Privileged: true, }, &network.NetworkingConfig{}, d.Name) @@ -57,6 +71,9 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error { if err := d.start(ctx, l); err != nil { return err } + if err := d.wait(ctx); err != nil { + return err + } return nil }); err != nil { return err @@ -64,6 +81,68 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error { return nil } +func (d *Driver) wait(ctx context.Context) error { + try := 0 + for { + if err := d.run(ctx, []string{"buildctl", "debug", "workers"}); err != nil { + if try > 10 { + return err + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(time.Duration(100+try*20) * time.Millisecond): + try++ + continue + } + } + return nil + } +} + +func (d *Driver) exec(ctx context.Context, cmd []string) (string, net.Conn, error) { + execConfig := types.ExecConfig{ + Cmd: cmd, + AttachStdin: true, + AttachStdout: true, + AttachStderr: true, + } + response, err := d.DockerAPI.ContainerExecCreate(ctx, d.Name, execConfig) + if err != nil { + return "", nil, err + } + + execID := response.ID + if execID == "" { + return "", nil, errors.New("exec ID empty") + } + + resp, err := d.DockerAPI.ContainerExecAttach(ctx, execID, types.ExecStartCheck{}) + if err != nil { + return "", nil, err + } + return execID, resp.Conn, nil +} + +func (d *Driver) run(ctx context.Context, cmd []string) error { + id, conn, err := d.exec(ctx, cmd) + if err != nil { + return err + } + if _, err := io.Copy(ioutil.Discard, conn); err != nil { + return err + } + conn.Close() + resp, err := d.DockerAPI.ContainerExecInspect(ctx, id) + if err != nil { + return err + } + if resp.ExitCode != 0 { + return errors.Errorf("exit code %d", resp.ExitCode) + } + return nil +} + func (d *Driver) start(ctx context.Context, l progress.SubLogger) error { return d.DockerAPI.ContainerStart(ctx, d.Name, types.ContainerStartOptions{}) } @@ -99,5 +178,33 @@ func (d *Driver) Rm(ctx context.Context, force bool) error { } func (d *Driver) Client(ctx context.Context) (*client.Client, error) { - return nil, errors.Errorf("client not implemented for %T", d) + _, conn, err := d.exec(ctx, []string{"buildctl", "dial-stdio"}) + if err != nil { + return nil, err + } + + conn = demuxConn(conn) + + return client.New(ctx, "", client.WithDialer(func(string, time.Duration) (net.Conn, error) { + return conn, nil + })) +} + +func demuxConn(c net.Conn) net.Conn { + pr, pw := io.Pipe() + // TODO: rewrite parser with Reader() to avoid goroutine switch + go stdcopy.StdCopy(pw, os.Stdout, c) + return &demux{ + Conn: c, + Reader: pr, + } +} + +type demux struct { + net.Conn + io.Reader +} + +func (d *demux) Read(dt []byte) (int, error) { + return d.Reader.Read(dt) }