From 8438557ff714424b029e9903bde0f861c5e54350 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 25 Mar 2019 15:14:39 -0700 Subject: [PATCH 1/6] driver: basic types startpoint Signed-off-by: Tonis Tiigi --- driver/driver.go | 36 ++++++++++++++++++++++++++++++ driver/manager.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 driver/driver.go create mode 100644 driver/manager.go diff --git a/driver/driver.go b/driver/driver.go new file mode 100644 index 00000000..b01d724c --- /dev/null +++ b/driver/driver.go @@ -0,0 +1,36 @@ +package driver + +import ( + "context" + + "github.com/moby/buildkit/client" + specs "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +type Logger func(*client.SolveStatus) + +type Status int + +const ( + Terminated Status = iota + Starting + Running + Stopping + Stopped +) + +type Info struct { + Status Status + Platforms []specs.Platform +} + +var ErrNotRunning = errors.Errorf("driver not running") + +type Driver interface { + Bootstrap(context.Context, Logger) error + Info(context.Context) (Info, error) + Stop(ctx context.Context, force bool) error + Rm(ctx context.Context, force bool) error + Client() (client.Client, error) +} diff --git a/driver/manager.go b/driver/manager.go new file mode 100644 index 00000000..b78224f8 --- /dev/null +++ b/driver/manager.go @@ -0,0 +1,56 @@ +package driver + +import ( + "context" + "sort" + + dockerclient "github.com/docker/docker/client" + "github.com/pkg/errors" +) + +type Factory interface { + Name() string + Usage() string + Priority() int // take initConfig? + New(ctx context.Context, cfg InitConfig) (Driver, error) +} + +type BuildkitConfig struct { + // Entitlements []string + // Rootless bool +} + +type InitConfig struct { + // This object needs updates to be generic for different drivers + Name string + DockerAPI dockerclient.APIClient + BuildkitConfig BuildkitConfig + Meta map[string]interface{} +} + +var drivers map[string]Factory + +func Register(f Factory) { + if drivers == nil { + drivers = map[string]Factory{} + } + drivers[f.Name()] = f +} + +func GetDefaultDriver() (Factory, error) { + if len(drivers) == 0 { + return nil, errors.Errorf("no drivers available") + } + type p struct { + f Factory + priority int + } + dd := make([]p, 0, len(drivers)) + for _, f := range drivers { + dd = append(dd, p{f: f, priority: f.Priority()}) + } + sort.Slice(dd, func(i, j int) bool { + return dd[i].priority < dd[j].priority + }) + return dd[0].f, nil +} From 49f67b7e96664b7fa3f9115631119a001ce3417d Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 25 Mar 2019 16:33:03 -0700 Subject: [PATCH 2/6] driver: docker driver base Signed-off-by: Tonis Tiigi --- driver/docker/driver.go | 35 +++++++++++++++++++++++++++++++++++ driver/docker/factory.go | 40 ++++++++++++++++++++++++++++++++++++++++ driver/driver.go | 7 ++++--- 3 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 driver/docker/driver.go create mode 100644 driver/docker/factory.go diff --git a/driver/docker/driver.go b/driver/docker/driver.go new file mode 100644 index 00000000..f7356b77 --- /dev/null +++ b/driver/docker/driver.go @@ -0,0 +1,35 @@ +package docker + +import ( + "context" + + dockertypes "github.com/docker/docker/api/types" + "github.com/moby/buildkit/client" + "github.com/pkg/errors" + "github.com/tonistiigi/buildx/driver" +) + +type Driver struct { + config driver.InitConfig + version dockertypes.Version +} + +func (d *Driver) Bootstrap(context.Context, driver.Logger) error { + return errors.Errorf("bootstrap not implemented for %T", d) +} + +func (d *Driver) Info(context.Context) (driver.Info, error) { + return driver.Info{}, errors.Errorf("info not implemented for %T", d) +} + +func (d *Driver) Stop(ctx context.Context, force bool) error { + return errors.Errorf("stop not implemented for %T", d) +} + +func (d *Driver) Rm(ctx context.Context, force bool) error { + return errors.Errorf("rm not implemented for %T", d) +} + +func (d *Driver) Client() (*client.Client, error) { + return nil, errors.Errorf("client not implemented for %T", d) +} diff --git a/driver/docker/factory.go b/driver/docker/factory.go new file mode 100644 index 00000000..f9905754 --- /dev/null +++ b/driver/docker/factory.go @@ -0,0 +1,40 @@ +package docker + +import ( + "context" + + "github.com/pkg/errors" + "github.com/tonistiigi/buildx/driver" +) + +func init() { + driver.Register(&factory{}) +} + +type factory struct { +} + +func (*factory) Name() string { + return "docker" +} + +func (*factory) Usage() string { + return "docker" +} + +func (*factory) Priority() int { + return 30 +} + +func (*factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver, error) { + if cfg.DockerAPI == nil { + return nil, errors.Errorf("docker driver requires docker API access") + } + + v, err := cfg.DockerAPI.ServerVersion(ctx) + if err != nil { + return nil, errors.Wrapf(driver.ErrNotConnecting, err.Error()) + } + + return &Driver{config: cfg, version: v}, nil +} diff --git a/driver/driver.go b/driver/driver.go index b01d724c..e90f7877 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -8,6 +8,9 @@ import ( "github.com/pkg/errors" ) +var ErrNotRunning = errors.Errorf("driver not running") +var ErrNotConnecting = errors.Errorf("driver not connection") + type Logger func(*client.SolveStatus) type Status int @@ -25,12 +28,10 @@ type Info struct { Platforms []specs.Platform } -var ErrNotRunning = errors.Errorf("driver not running") - type Driver interface { Bootstrap(context.Context, Logger) error Info(context.Context) (Info, error) Stop(ctx context.Context, force bool) error Rm(ctx context.Context, force bool) error - Client() (client.Client, error) + Client() (*client.Client, error) } From f302881c0d390790a6888b4e3df08722cfa03b3d Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 25 Mar 2019 19:01:31 -0700 Subject: [PATCH 3/6] driver: start implemeting bootstrap for docker Signed-off-by: Tonis Tiigi --- build/build.go | 19 ++++++++++- cmd/buildx/main.go | 2 ++ commands/build.go | 8 ++--- commands/root.go | 4 +++ driver/docker/driver.go | 70 ++++++++++++++++++++++++++++++++++++---- driver/docker/factory.go | 2 +- driver/driver.go | 42 +++++++++++++++++++++--- driver/manager.go | 16 ++++++++- 8 files changed, 144 insertions(+), 19 deletions(-) diff --git a/build/build.go b/build/build.go index 90bb31d7..92ca65b0 100644 --- a/build/build.go +++ b/build/build.go @@ -15,6 +15,7 @@ import ( "github.com/moby/buildkit/util/progress/progressui" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" + "github.com/tonistiigi/buildx/driver" "golang.org/x/sync/errgroup" ) @@ -40,7 +41,20 @@ type Inputs struct { InStream io.Reader } -func Build(ctx context.Context, c *client.Client, opt Options, pw *ProgressWriter) (*client.SolveResponse, error) { +func Build(ctx context.Context, drivers []driver.Driver, opt Options, pw *ProgressWriter) (*client.SolveResponse, error) { + if len(drivers) == 0 { + return nil, errors.Errorf("driver required for build") + } + + if len(drivers) > 1 { + return nil, errors.Errorf("multiple drivers currently not supported") + } + + c, err := driver.Boot(ctx, drivers[0], pw.Status()) + if err != nil { + return nil, err + } + so := client.SolveOpt{ Frontend: "dockerfile.v0", FrontendAttrs: map[string]string{}, @@ -142,6 +156,9 @@ func (pw *ProgressWriter) Err() error { } func (pw *ProgressWriter) Status() chan *client.SolveStatus { + if pw == nil { + return nil + } return pw.status } diff --git a/cmd/buildx/main.go b/cmd/buildx/main.go index c991dca0..a8e2ff31 100644 --- a/cmd/buildx/main.go +++ b/cmd/buildx/main.go @@ -7,6 +7,8 @@ import ( "github.com/spf13/cobra" "github.com/tonistiigi/buildx/commands" "github.com/tonistiigi/buildx/version" + + _ "github.com/tonistiigi/buildx/driver/docker" ) func main() { diff --git a/commands/build.go b/commands/build.go index ec638fe8..af007716 100644 --- a/commands/build.go +++ b/commands/build.go @@ -7,12 +7,11 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/moby/buildkit/client" "github.com/moby/buildkit/session/auth/authprovider" "github.com/moby/buildkit/util/appcontext" - bkappdefaults "github.com/moby/buildkit/util/appdefaults" "github.com/spf13/cobra" "github.com/tonistiigi/buildx/build" + "github.com/tonistiigi/buildx/driver" ) type buildOptions struct { @@ -94,8 +93,7 @@ func runBuild(dockerCli command.Cli, in buildOptions) error { } opts.Exports = outputs - // TODO: temporary - c, err := client.New(ctx, bkappdefaults.Address, client.WithFailFast()) + d, err := driver.GetDriver(ctx, "buildkit.default", nil, dockerCli.Client()) if err != nil { return err } @@ -104,7 +102,7 @@ func runBuild(dockerCli command.Cli, in buildOptions) error { defer cancel() pw := build.NewProgressWriter(ctx2, os.Stderr, in.progress) - _, err = build.Build(ctx, c, opts, pw) + _, err = build.Build(ctx, []driver.Driver{d}, opts, pw) return err } diff --git a/commands/root.go b/commands/root.go index d693160f..eb858ae2 100644 --- a/commands/root.go +++ b/commands/root.go @@ -1,6 +1,7 @@ package commands import ( + "github.com/docker/cli/cli-plugins/plugin" "github.com/docker/cli/cli/command" "github.com/spf13/cobra" ) @@ -9,6 +10,9 @@ func NewRootCmd(dockerCli command.Cli) *cobra.Command { cmd := &cobra.Command{ Short: "Build with BuildKit", Use: "buildx", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return plugin.PersistentPreRunE(cmd, args) + }, } addCommands(cmd, dockerCli) return cmd diff --git a/driver/docker/driver.go b/driver/docker/driver.go index f7356b77..558569d6 100644 --- a/driver/docker/driver.go +++ b/driver/docker/driver.go @@ -2,24 +2,82 @@ package docker import ( "context" + "io" + "io/ioutil" + "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/moby/buildkit/client" "github.com/pkg/errors" "github.com/tonistiigi/buildx/driver" ) type Driver struct { - config driver.InitConfig + driver.InitConfig version dockertypes.Version } -func (d *Driver) Bootstrap(context.Context, driver.Logger) error { - return errors.Errorf("bootstrap not implemented for %T", d) +func (d *Driver) Bootstrap(ctx context.Context, l driver.Logger) error { + _, err := d.DockerAPI.ContainerInspect(ctx, d.Name) + if err != nil { + if dockerclient.IsErrNotFound(err) { + return d.create(ctx, l) + } + return err + } + return d.start(ctx, l) } -func (d *Driver) Info(context.Context) (driver.Info, error) { - return driver.Info{}, errors.Errorf("info not implemented for %T", d) +func (d *Driver) create(ctx context.Context, l driver.Logger) error { + rc, err := d.DockerAPI.ImageCreate(ctx, "moby/buildkit", types.ImageCreateOptions{}) + if err != nil { + return err + } + _, err = io.Copy(ioutil.Discard, rc) + if err != nil { + return err + } + _, err = d.DockerAPI.ContainerCreate(ctx, &container.Config{ + Image: "moby/buildkit", + }, &container.HostConfig{ + Privileged: true, + }, &network.NetworkingConfig{}, d.Name) + if err != nil { + return err + } + if err := d.start(ctx, l); err != nil { + return err + } + return nil +} + +func (d *Driver) start(ctx context.Context, l driver.Logger) error { + return d.DockerAPI.ContainerStart(ctx, d.Name, types.ContainerStartOptions{}) +} + +func (d *Driver) Info(ctx context.Context) (*driver.Info, error) { + container, err := d.DockerAPI.ContainerInspect(ctx, d.Name) + if err != nil { + if dockerclient.IsErrNotFound(err) { + return &driver.Info{ + Status: driver.Terminated, + }, nil + } + return nil, err + } + + if container.State.Running { + return &driver.Info{ + Status: driver.Running, + }, nil + } + + return &driver.Info{ + Status: driver.Stopped, + }, nil } func (d *Driver) Stop(ctx context.Context, force bool) error { @@ -30,6 +88,6 @@ func (d *Driver) Rm(ctx context.Context, force bool) error { return errors.Errorf("rm not implemented for %T", d) } -func (d *Driver) Client() (*client.Client, error) { +func (d *Driver) Client(ctx context.Context) (*client.Client, error) { return nil, errors.Errorf("client not implemented for %T", d) } diff --git a/driver/docker/factory.go b/driver/docker/factory.go index f9905754..469f7c66 100644 --- a/driver/docker/factory.go +++ b/driver/docker/factory.go @@ -36,5 +36,5 @@ func (*factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver, return nil, errors.Wrapf(driver.ErrNotConnecting, err.Error()) } - return &Driver{config: cfg, version: v}, nil + return &Driver{InitConfig: cfg, version: v}, nil } diff --git a/driver/driver.go b/driver/driver.go index e90f7877..464cb6e5 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -4,7 +4,6 @@ import ( "context" "github.com/moby/buildkit/client" - specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) @@ -24,14 +23,47 @@ const ( ) type Info struct { - Status Status - Platforms []specs.Platform + Status Status } type Driver interface { Bootstrap(context.Context, Logger) error - Info(context.Context) (Info, error) + Info(context.Context) (*Info, error) Stop(ctx context.Context, force bool) error Rm(ctx context.Context, force bool) error - Client() (*client.Client, error) + Client(ctx context.Context) (*client.Client, error) +} + +func Boot(ctx context.Context, d Driver, status chan *client.SolveStatus) (*client.Client, error) { + try := 0 + for { + info, err := d.Info(ctx) + if err != nil { + return nil, err + } + try++ + if info.Status != Running { + if try > 2 { + return nil, errors.Errorf("failed to bootstrap %T driver in attempts", d) + } + if err := d.Bootstrap(ctx, func(s *client.SolveStatus) { + if status != nil { + status <- s + } + }); err != nil { + return nil, err + } + } + + c, err := d.Client(ctx) + if err != nil { + if errors.Cause(err) == ErrNotRunning && try <= 2 { + continue + } + return nil, err + } + return c, nil + } + + return nil, errors.Errorf("boot not implemented") } diff --git a/driver/manager.go b/driver/manager.go index b78224f8..f3dd8ce5 100644 --- a/driver/manager.go +++ b/driver/manager.go @@ -37,7 +37,7 @@ func Register(f Factory) { drivers[f.Name()] = f } -func GetDefaultDriver() (Factory, error) { +func GetDefaultFactory() (Factory, error) { if len(drivers) == 0 { return nil, errors.Errorf("no drivers available") } @@ -54,3 +54,17 @@ func GetDefaultDriver() (Factory, error) { }) return dd[0].f, nil } + +func GetDriver(ctx context.Context, name string, f Factory, api dockerclient.APIClient) (Driver, error) { + if f == nil { + var err error + f, err = GetDefaultFactory() + if err != nil { + return nil, err + } + } + return f.New(ctx, InitConfig{ + Name: name, + DockerAPI: api, + }) +} From a6d893efca6a92d009aacc20a2e0fcc53b35395a Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 26 Mar 2019 09:55:09 -0700 Subject: [PATCH 4/6] driver: add logging support to bootstrap Signed-off-by: Tonis Tiigi --- build/build.go | 53 +++-------------------- commands/build.go | 3 +- driver/docker/driver.go | 58 ++++++++++++++----------- driver/driver.go | 86 +++++++++++++++++++++++++++++++------ go.mod | 1 + util/progress/printer.go | 52 ++++++++++++++++++++++ util/progress/progress.go | 90 +++++++++++++++++++++++++++++++++++++++ util/progress/writer.go | 11 +++++ vendor/modules.txt | 16 +++---- 9 files changed, 278 insertions(+), 92 deletions(-) create mode 100644 util/progress/printer.go create mode 100644 util/progress/progress.go create mode 100644 util/progress/writer.go diff --git a/build/build.go b/build/build.go index 92ca65b0..ec15e431 100644 --- a/build/build.go +++ b/build/build.go @@ -3,19 +3,17 @@ package build import ( "context" "io" - "os" "path/filepath" "strconv" "strings" - "github.com/containerd/console" "github.com/containerd/containerd/platforms" "github.com/moby/buildkit/client" "github.com/moby/buildkit/session" - "github.com/moby/buildkit/util/progress/progressui" specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/tonistiigi/buildx/driver" + "github.com/tonistiigi/buildx/util/progress" "golang.org/x/sync/errgroup" ) @@ -41,7 +39,7 @@ type Inputs struct { InStream io.Reader } -func Build(ctx context.Context, drivers []driver.Driver, opt Options, pw *ProgressWriter) (*client.SolveResponse, error) { +func Build(ctx context.Context, drivers []driver.Driver, opt Options, pw progress.Writer) (*client.SolveResponse, error) { if len(drivers) == 0 { return nil, errors.Errorf("driver required for build") } @@ -50,8 +48,11 @@ func Build(ctx context.Context, drivers []driver.Driver, opt Options, pw *Progre return nil, errors.Errorf("multiple drivers currently not supported") } - c, err := driver.Boot(ctx, drivers[0], pw.Status()) + pwOld := pw + c, pw, err := driver.Boot(ctx, drivers[0], pw) if err != nil { + close(pwOld.Status()) + <-pwOld.Done() return nil, err } @@ -141,48 +142,6 @@ func Build(ctx context.Context, drivers []driver.Driver, opt Options, pw *Progre return resp, nil } -type ProgressWriter struct { - status chan *client.SolveStatus - done <-chan struct{} - err error -} - -func (pw *ProgressWriter) Done() <-chan struct{} { - return pw.done -} - -func (pw *ProgressWriter) Err() error { - return pw.err -} - -func (pw *ProgressWriter) Status() chan *client.SolveStatus { - if pw == nil { - return nil - } - return pw.status -} - -func NewProgressWriter(ctx context.Context, out *os.File, mode string) *ProgressWriter { - statusCh := make(chan *client.SolveStatus) - doneCh := make(chan struct{}) - - pw := &ProgressWriter{ - status: statusCh, - done: doneCh, - } - - go func() { - var c console.Console - if cons, err := console.ConsoleFromFile(out); err == nil && (mode == "auto" || mode == "tty") { - c = cons - } - // not using shared context to not disrupt display but let is finish reporting errors - pw.err = progressui.DisplaySolveStatus(ctx, "", c, out, statusCh) - close(doneCh) - }() - return pw -} - func LoadInputs(inp Inputs, target *client.SolveOpt) error { if inp.ContextPath == "" { return errors.New("please specify build context (e.g. \".\" for the current directory)") diff --git a/commands/build.go b/commands/build.go index af007716..25acf6cb 100644 --- a/commands/build.go +++ b/commands/build.go @@ -12,6 +12,7 @@ import ( "github.com/spf13/cobra" "github.com/tonistiigi/buildx/build" "github.com/tonistiigi/buildx/driver" + "github.com/tonistiigi/buildx/util/progress" ) type buildOptions struct { @@ -100,7 +101,7 @@ func runBuild(dockerCli command.Cli, in buildOptions) error { ctx2, cancel := context.WithCancel(context.TODO()) defer cancel() - pw := build.NewProgressWriter(ctx2, os.Stderr, in.progress) + pw := progress.NewPrinter(ctx2, os.Stderr, in.progress) _, err = build.Build(ctx, []driver.Driver{d}, opts, pw) diff --git a/driver/docker/driver.go b/driver/docker/driver.go index 558569d6..d51f564a 100644 --- a/driver/docker/driver.go +++ b/driver/docker/driver.go @@ -13,6 +13,7 @@ import ( "github.com/moby/buildkit/client" "github.com/pkg/errors" "github.com/tonistiigi/buildx/driver" + "github.com/tonistiigi/buildx/util/progress" ) type Driver struct { @@ -20,41 +21,50 @@ type Driver struct { version dockertypes.Version } -func (d *Driver) Bootstrap(ctx context.Context, l driver.Logger) error { - _, err := d.DockerAPI.ContainerInspect(ctx, d.Name) - if err != nil { - if dockerclient.IsErrNotFound(err) { - return d.create(ctx, l) +func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error { + return progress.Wrap("[internal] booting buildkit", l, func(sub progress.SubLogger) error { + _, err := d.DockerAPI.ContainerInspect(ctx, d.Name) + if err != nil { + if dockerclient.IsErrNotFound(err) { + return d.create(ctx, sub) + } + return err } - return err - } - return d.start(ctx, l) + return d.start(ctx, sub) + }) } -func (d *Driver) create(ctx context.Context, l driver.Logger) error { - rc, err := d.DockerAPI.ImageCreate(ctx, "moby/buildkit", types.ImageCreateOptions{}) - if err != nil { - return err - } - _, err = io.Copy(ioutil.Discard, rc) - if err != 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 != nil { + return err + } + _, err = io.Copy(ioutil.Discard, rc) return err - } - _, err = d.DockerAPI.ContainerCreate(ctx, &container.Config{ - Image: "moby/buildkit", - }, &container.HostConfig{ - Privileged: true, - }, &network.NetworkingConfig{}, d.Name) - if err != nil { + }); err != nil { return err } - if err := d.start(ctx, l); err != nil { + if err := l.Wrap("creating container "+d.Name, func() error { + _, err := d.DockerAPI.ContainerCreate(ctx, &container.Config{ + Image: "moby/buildkit", + }, &container.HostConfig{ + Privileged: true, + }, &network.NetworkingConfig{}, d.Name) + if err != nil { + return err + } + if err := d.start(ctx, l); err != nil { + return err + } + return nil + }); err != nil { return err } return nil } -func (d *Driver) start(ctx context.Context, l driver.Logger) error { +func (d *Driver) start(ctx context.Context, l progress.SubLogger) error { return d.DockerAPI.ContainerStart(ctx, d.Name, types.ContainerStartOptions{}) } diff --git a/driver/driver.go b/driver/driver.go index 464cb6e5..0b03c1ec 100644 --- a/driver/driver.go +++ b/driver/driver.go @@ -2,16 +2,16 @@ package driver import ( "context" + "time" "github.com/moby/buildkit/client" "github.com/pkg/errors" + "github.com/tonistiigi/buildx/util/progress" ) var ErrNotRunning = errors.Errorf("driver not running") var ErrNotConnecting = errors.Errorf("driver not connection") -type Logger func(*client.SolveStatus) - type Status int const ( @@ -27,31 +27,31 @@ type Info struct { } type Driver interface { - Bootstrap(context.Context, Logger) error + Bootstrap(context.Context, progress.Logger) error Info(context.Context) (*Info, error) Stop(ctx context.Context, force bool) error Rm(ctx context.Context, force bool) error Client(ctx context.Context) (*client.Client, error) } -func Boot(ctx context.Context, d Driver, status chan *client.SolveStatus) (*client.Client, error) { +func Boot(ctx context.Context, d Driver, pw progress.Writer) (*client.Client, progress.Writer, error) { try := 0 for { info, err := d.Info(ctx) if err != nil { - return nil, err + return nil, nil, err } try++ if info.Status != Running { if try > 2 { - return nil, errors.Errorf("failed to bootstrap %T driver in attempts", d) + return nil, nil, errors.Errorf("failed to bootstrap %T driver in attempts", d) } if err := d.Bootstrap(ctx, func(s *client.SolveStatus) { - if status != nil { - status <- s + if pw != nil { + pw.Status() <- s } }); err != nil { - return nil, err + return nil, nil, err } } @@ -60,10 +60,72 @@ func Boot(ctx context.Context, d Driver, status chan *client.SolveStatus) (*clie if errors.Cause(err) == ErrNotRunning && try <= 2 { continue } - return nil, err + return nil, nil, err } - return c, nil + return c, newResetWriter(pw), nil } +} + +func newResetWriter(in progress.Writer) progress.Writer { + w := &pw{Writer: in, status: make(chan *client.SolveStatus), tm: time.Now()} + go func() { + for { + select { + case <-in.Done(): + return + case st, ok := <-w.status: + if !ok { + close(in.Status()) + return + } + if w.diff == nil { + for _, v := range st.Vertexes { + if v.Started != nil { + d := v.Started.Sub(w.tm) + w.diff = &d + } + } + } + if w.diff != nil { + for _, v := range st.Vertexes { + if v.Started != nil { + d := v.Started.Add(-*w.diff) + v.Started = &d + } + if v.Completed != nil { + d := v.Completed.Add(-*w.diff) + v.Completed = &d + } + } + for _, v := range st.Statuses { + if v.Started != nil { + d := v.Started.Add(-*w.diff) + v.Started = &d + } + if v.Completed != nil { + d := v.Completed.Add(-*w.diff) + v.Completed = &d + } + v.Timestamp = v.Timestamp.Add(-*w.diff) + } + for _, v := range st.Logs { + v.Timestamp = v.Timestamp.Add(-*w.diff) + } + } + in.Status() <- st + } + } + }() + return w +} + +type pw struct { + progress.Writer + tm time.Time + diff *time.Duration + status chan *client.SolveStatus +} - return nil, errors.Errorf("boot not implemented") +func (p *pw) Status() chan *client.SolveStatus { + return p.status } diff --git a/go.mod b/go.mod index ceb7b07d..6fc06909 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 // indirect github.com/docker/cli v0.0.0-20190321234815-f40f9c240ab0 github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496 // indirect + github.com/docker/docker v1.14.0-0.20190319215453-e7b5f7dbe98c github.com/docker/docker-credential-helpers v0.6.1 // indirect github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect github.com/docker/go-connections v0.4.0 // indirect diff --git a/util/progress/printer.go b/util/progress/printer.go new file mode 100644 index 00000000..7d415a60 --- /dev/null +++ b/util/progress/printer.go @@ -0,0 +1,52 @@ +package progress + +import ( + "context" + "os" + + "github.com/containerd/console" + "github.com/moby/buildkit/client" + "github.com/moby/buildkit/util/progress/progressui" +) + +type printer struct { + status chan *client.SolveStatus + done <-chan struct{} + err error +} + +func (p *printer) Done() <-chan struct{} { + return p.done +} + +func (p *printer) Err() error { + return p.err +} + +func (p *printer) Status() chan *client.SolveStatus { + if p == nil { + return nil + } + return p.status +} + +func NewPrinter(ctx context.Context, out *os.File, mode string) Writer { + statusCh := make(chan *client.SolveStatus) + doneCh := make(chan struct{}) + + pw := &printer{ + status: statusCh, + done: doneCh, + } + + go func() { + var c console.Console + if cons, err := console.ConsoleFromFile(out); err == nil && (mode == "auto" || mode == "tty") { + c = cons + } + // not using shared context to not disrupt display but let is finish reporting errors + pw.err = progressui.DisplaySolveStatus(ctx, "", c, out, statusCh) + close(doneCh) + }() + return pw +} diff --git a/util/progress/progress.go b/util/progress/progress.go new file mode 100644 index 00000000..6dbba421 --- /dev/null +++ b/util/progress/progress.go @@ -0,0 +1,90 @@ +package progress + +import ( + "time" + + "github.com/moby/buildkit/client" + "github.com/moby/buildkit/identity" + "github.com/opencontainers/go-digest" +) + +type Logger func(*client.SolveStatus) + +type SubLogger interface { + Wrap(name string, fn func() error) error + Log(stream int, dt []byte) +} + +func Wrap(name string, l Logger, fn func(SubLogger) error) (err error) { + dgst := digest.FromBytes([]byte(identity.NewID())) + tm := time.Now() + l(&client.SolveStatus{ + Vertexes: []*client.Vertex{{ + Digest: dgst, + Name: name, + Started: &tm, + }}, + }) + + defer func() { + tm2 := time.Now() + errMsg := "" + if err != nil { + errMsg = err.Error() + } + l(&client.SolveStatus{ + Vertexes: []*client.Vertex{{ + Digest: dgst, + Name: name, + Started: &tm, + Completed: &tm2, + Error: errMsg, + }}, + }) + }() + + return fn(&subLogger{dgst, l}) +} + +type subLogger struct { + dgst digest.Digest + logger Logger +} + +func (sl *subLogger) Wrap(name string, fn func() error) (err error) { + tm := time.Now() + sl.logger(&client.SolveStatus{ + Statuses: []*client.VertexStatus{{ + Vertex: sl.dgst, + ID: name, + Timestamp: time.Now(), + Started: &tm, + }}, + }) + + defer func() { + tm2 := time.Now() + sl.logger(&client.SolveStatus{ + Statuses: []*client.VertexStatus{{ + Vertex: sl.dgst, + ID: name, + Timestamp: time.Now(), + Started: &tm, + Completed: &tm2, + }}, + }) + }() + + return fn() +} + +func (sl *subLogger) Log(stream int, dt []byte) { + sl.logger(&client.SolveStatus{ + Logs: []*client.VertexLog{{ + Vertex: sl.dgst, + Stream: stream, + Data: dt, + Timestamp: time.Now(), + }}, + }) +} diff --git a/util/progress/writer.go b/util/progress/writer.go new file mode 100644 index 00000000..12907669 --- /dev/null +++ b/util/progress/writer.go @@ -0,0 +1,11 @@ +package progress + +import ( + "github.com/moby/buildkit/client" +) + +type Writer interface { + Done() <-chan struct{} + Err() error + Status() chan *client.SolveStatus +} diff --git a/vendor/modules.txt b/vendor/modules.txt index eed85851..26e3a157 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -149,6 +149,8 @@ github.com/docker/distribution/metrics # github.com/docker/docker v1.14.0-0.20190319215453-e7b5f7dbe98c github.com/docker/docker/client github.com/docker/docker/api/types +github.com/docker/docker/api/types/container +github.com/docker/docker/api/types/network github.com/docker/docker/api/types/events github.com/docker/docker/api/types/filters github.com/docker/docker/api/types/registry @@ -157,17 +159,16 @@ github.com/docker/docker/pkg/system github.com/docker/docker/pkg/term github.com/docker/docker/registry github.com/docker/docker/api -github.com/docker/docker/api/types/container github.com/docker/docker/api/types/image -github.com/docker/docker/api/types/network github.com/docker/docker/api/types/swarm github.com/docker/docker/api/types/time github.com/docker/docker/api/types/versions github.com/docker/docker/api/types/volume github.com/docker/docker/errdefs -github.com/docker/docker/pkg/jsonmessage -github.com/docker/docker/api/types/blkiodev github.com/docker/docker/api/types/mount +github.com/docker/docker/api/types/blkiodev +github.com/docker/docker/api/types/strslice +github.com/docker/docker/pkg/jsonmessage github.com/docker/docker/pkg/idtools github.com/docker/docker/pkg/mount github.com/docker/docker/pkg/term/windows @@ -175,9 +176,8 @@ github.com/docker/docker/pkg/ioutils github.com/docker/docker/pkg/stringid github.com/docker/docker/pkg/tarsum github.com/docker/docker/registry/resumable -github.com/docker/docker/pkg/fileutils -github.com/docker/docker/api/types/strslice github.com/docker/docker/api/types/swarm/runtime +github.com/docker/docker/pkg/fileutils github.com/docker/docker/pkg/longpath # github.com/docker/docker-credential-helpers v0.6.1 github.com/docker/docker-credential-helpers/client @@ -253,7 +253,6 @@ github.com/moby/buildkit/session/sshforward/sshprovider github.com/moby/buildkit/util/progress/progressui github.com/moby/buildkit/session/auth/authprovider github.com/moby/buildkit/util/appcontext -github.com/moby/buildkit/util/appdefaults github.com/moby/buildkit/api/services/control github.com/moby/buildkit/api/types github.com/moby/buildkit/client/buildid @@ -268,6 +267,7 @@ github.com/moby/buildkit/session/filesync github.com/moby/buildkit/session/grpchijack github.com/moby/buildkit/solver/pb github.com/moby/buildkit/util/apicaps +github.com/moby/buildkit/util/appdefaults github.com/moby/buildkit/util/entitlements github.com/moby/buildkit/session/secrets github.com/moby/buildkit/session/sshforward @@ -358,8 +358,8 @@ golang.org/x/net/trace golang.org/x/net/http/httpguts golang.org/x/net/http2/hpack golang.org/x/net/idna -golang.org/x/net/internal/timeseries golang.org/x/net/proxy +golang.org/x/net/internal/timeseries golang.org/x/net/context/ctxhttp golang.org/x/net/internal/socks # golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f From 15dae19cf8b48463249ca35e23a2ce0d80d32b0f Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 26 Mar 2019 11:37:07 -0700 Subject: [PATCH 5/6] driver: add client method Signed-off-by: Tonis Tiigi --- Dockerfile | 4 -- commands/build.go | 2 +- driver/docker/driver.go | 117 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 113 insertions(+), 10 deletions(-) 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) } From b3d7b5a2ec1e8b2a40d2e2d5031eb27e49dd4f17 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Tue, 26 Mar 2019 11:39:37 -0700 Subject: [PATCH 6/6] vendor: update buildkit Signed-off-by: Tonis Tiigi --- go.mod | 3 +- go.sum | 5 +- .../docker/docker/pkg/stdcopy/stdcopy.go | 190 ++++++++++++++++++ .../github.com/moby/buildkit/client/client.go | 45 ++++- .../buildkit/client/connhelper/connhelper.go | 37 ++++ vendor/modules.txt | 10 +- 6 files changed, 279 insertions(+), 11 deletions(-) create mode 100644 vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go create mode 100644 vendor/github.com/moby/buildkit/client/connhelper/connhelper.go diff --git a/go.mod b/go.mod index 6fc06909..a1120fbb 100644 --- a/go.mod +++ b/go.mod @@ -53,9 +53,10 @@ require ( github.com/mattn/go-sqlite3 v1.10.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/miekg/pkcs11 v0.0.0-20190322140431-074fd7a1ed19 // indirect - github.com/moby/buildkit v0.4.1-0.20190322070013-03feb5e28f65 + github.com/moby/buildkit v0.4.1-0.20190326070013-325bd96b6b62 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/opencontainers/go-digest v1.0.0-rc1 github.com/opencontainers/image-spec v1.0.1 github.com/opencontainers/runtime-spec v1.0.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect diff --git a/go.sum b/go.sum index 915169bd..7077fffc 100644 --- a/go.sum +++ b/go.sum @@ -63,7 +63,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853 h1:tTngnoO/B6HQnJ+pK8tN7kEAhmhIfaJOutqq/A4/JTM= github.com/denisenkom/go-mssqldb v0.0.0-20190315220205-a8ed825ac853/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc= -github.com/docker/cli v0.0.0-20190131223713-234462756460/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v0.0.0-20190321234815-f40f9c240ab0 h1:E7NTtHfZYV+iu35yZ49AbrxqhMHpiOl3FstDYm38vQ0= github.com/docker/cli v0.0.0-20190321234815-f40f9c240ab0/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/compose-on-kubernetes v0.4.19-0.20190128150448-356b2919c496 h1:90ytrX1dbzL7Uf/hHiuWwvywC+gikHv4hkAy4CwRTbs= @@ -184,8 +183,8 @@ github.com/miekg/pkcs11 v0.0.0-20190322140431-074fd7a1ed19/go.mod h1:WCBAbTOdfhH github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/moby/buildkit v0.4.1-0.20190322070013-03feb5e28f65 h1:t9GGPuQ85Reqzlnu7Vy7QBb+SrztgASQ5ek8CSEqdyI= -github.com/moby/buildkit v0.4.1-0.20190322070013-03feb5e28f65/go.mod h1:hxXlABlNvROrpptYGUMgF2V7VrHpjphi23E63UjlVP4= +github.com/moby/buildkit v0.4.1-0.20190326070013-325bd96b6b62 h1:GulTASjmiroM1LFiX4HX1zo7nQ7vRm3BbzdUou7UMaw= +github.com/moby/buildkit v0.4.1-0.20190326070013-325bd96b6b62/go.mod h1:CGtUCEvQ3mcP73SmixGWRxEOECm+hCUJsdfNVKPNzVA= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= diff --git a/vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go b/vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go new file mode 100644 index 00000000..8f6e0a73 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go @@ -0,0 +1,190 @@ +package stdcopy // import "github.com/docker/docker/pkg/stdcopy" + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "sync" +) + +// StdType is the type of standard stream +// a writer can multiplex to. +type StdType byte + +const ( + // Stdin represents standard input stream type. + Stdin StdType = iota + // Stdout represents standard output stream type. + Stdout + // Stderr represents standard error steam type. + Stderr + // Systemerr represents errors originating from the system that make it + // into the multiplexed stream. + Systemerr + + stdWriterPrefixLen = 8 + stdWriterFdIndex = 0 + stdWriterSizeIndex = 4 + + startingBufLen = 32*1024 + stdWriterPrefixLen + 1 +) + +var bufPool = &sync.Pool{New: func() interface{} { return bytes.NewBuffer(nil) }} + +// stdWriter is wrapper of io.Writer with extra customized info. +type stdWriter struct { + io.Writer + prefix byte +} + +// Write sends the buffer to the underneath writer. +// It inserts the prefix header before the buffer, +// so stdcopy.StdCopy knows where to multiplex the output. +// It makes stdWriter to implement io.Writer. +func (w *stdWriter) Write(p []byte) (n int, err error) { + if w == nil || w.Writer == nil { + return 0, errors.New("Writer not instantiated") + } + if p == nil { + return 0, nil + } + + header := [stdWriterPrefixLen]byte{stdWriterFdIndex: w.prefix} + binary.BigEndian.PutUint32(header[stdWriterSizeIndex:], uint32(len(p))) + buf := bufPool.Get().(*bytes.Buffer) + buf.Write(header[:]) + buf.Write(p) + + n, err = w.Writer.Write(buf.Bytes()) + n -= stdWriterPrefixLen + if n < 0 { + n = 0 + } + + buf.Reset() + bufPool.Put(buf) + return +} + +// NewStdWriter instantiates a new Writer. +// Everything written to it will be encapsulated using a custom format, +// and written to the underlying `w` stream. +// This allows multiple write streams (e.g. stdout and stderr) to be muxed into a single connection. +// `t` indicates the id of the stream to encapsulate. +// It can be stdcopy.Stdin, stdcopy.Stdout, stdcopy.Stderr. +func NewStdWriter(w io.Writer, t StdType) io.Writer { + return &stdWriter{ + Writer: w, + prefix: byte(t), + } +} + +// StdCopy is a modified version of io.Copy. +// +// StdCopy will demultiplex `src`, assuming that it contains two streams, +// previously multiplexed together using a StdWriter instance. +// As it reads from `src`, StdCopy will write to `dstout` and `dsterr`. +// +// StdCopy will read until it hits EOF on `src`. It will then return a nil error. +// In other words: if `err` is non nil, it indicates a real underlying error. +// +// `written` will hold the total number of bytes written to `dstout` and `dsterr`. +func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) { + var ( + buf = make([]byte, startingBufLen) + bufLen = len(buf) + nr, nw int + er, ew error + out io.Writer + frameSize int + ) + + for { + // Make sure we have at least a full header + for nr < stdWriterPrefixLen { + var nr2 int + nr2, er = src.Read(buf[nr:]) + nr += nr2 + if er == io.EOF { + if nr < stdWriterPrefixLen { + return written, nil + } + break + } + if er != nil { + return 0, er + } + } + + stream := StdType(buf[stdWriterFdIndex]) + // Check the first byte to know where to write + switch stream { + case Stdin: + fallthrough + case Stdout: + // Write on stdout + out = dstout + case Stderr: + // Write on stderr + out = dsterr + case Systemerr: + // If we're on Systemerr, we won't write anywhere. + // NB: if this code changes later, make sure you don't try to write + // to outstream if Systemerr is the stream + out = nil + default: + return 0, fmt.Errorf("Unrecognized input header: %d", buf[stdWriterFdIndex]) + } + + // Retrieve the size of the frame + frameSize = int(binary.BigEndian.Uint32(buf[stdWriterSizeIndex : stdWriterSizeIndex+4])) + + // Check if the buffer is big enough to read the frame. + // Extend it if necessary. + if frameSize+stdWriterPrefixLen > bufLen { + buf = append(buf, make([]byte, frameSize+stdWriterPrefixLen-bufLen+1)...) + bufLen = len(buf) + } + + // While the amount of bytes read is less than the size of the frame + header, we keep reading + for nr < frameSize+stdWriterPrefixLen { + var nr2 int + nr2, er = src.Read(buf[nr:]) + nr += nr2 + if er == io.EOF { + if nr < frameSize+stdWriterPrefixLen { + return written, nil + } + break + } + if er != nil { + return 0, er + } + } + + // we might have an error from the source mixed up in our multiplexed + // stream. if we do, return it. + if stream == Systemerr { + return written, fmt.Errorf("error from daemon in stream: %s", string(buf[stdWriterPrefixLen:frameSize+stdWriterPrefixLen])) + } + + // Write the retrieved frame (without header) + nw, ew = out.Write(buf[stdWriterPrefixLen : frameSize+stdWriterPrefixLen]) + if ew != nil { + return 0, ew + } + + // If the frame has not been fully written: error + if nw != frameSize { + return 0, io.ErrShortWrite + } + written += int64(nw) + + // Move the rest of the buffer to the beginning + copy(buf, buf[frameSize+stdWriterPrefixLen:]) + // Move the index + nr -= frameSize + stdWriterPrefixLen + } +} diff --git a/vendor/github.com/moby/buildkit/client/client.go b/vendor/github.com/moby/buildkit/client/client.go index 6126acdc..6b824094 100644 --- a/vendor/github.com/moby/buildkit/client/client.go +++ b/vendor/github.com/moby/buildkit/client/client.go @@ -5,9 +5,12 @@ import ( "crypto/tls" "crypto/x509" "io/ioutil" + "net" + "time" "github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc" controlapi "github.com/moby/buildkit/api/services/control" + "github.com/moby/buildkit/client/connhelper" "github.com/moby/buildkit/util/appdefaults" opentracing "github.com/opentracing/opentracing-go" "github.com/pkg/errors" @@ -23,9 +26,8 @@ type ClientOpt interface{} // New returns a new buildkit client. Address can be empty for the system-default address. func New(ctx context.Context, address string, opts ...ClientOpt) (*Client, error) { - gopts := []grpc.DialOption{ - grpc.WithDialer(dialer), - } + gopts := []grpc.DialOption{} + needDialer := true needWithInsecure := true for _, o := range opts { if _, ok := o.(*withFailFast); ok { @@ -44,6 +46,19 @@ func New(ctx context.Context, address string, opts ...ClientOpt) (*Client, error grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(wt.tracer, otgrpc.LogPayloads())), grpc.WithStreamInterceptor(otgrpc.OpenTracingStreamClientInterceptor(wt.tracer))) } + if wd, ok := o.(*withDialer); ok { + gopts = append(gopts, grpc.WithDialer(wd.dialer)) + needDialer = false + } + } + if needDialer { + dialFn, err := resolveDialer(address) + if err != nil { + return nil, err + } + // TODO(AkihiroSuda): use WithContextDialer (requires grpc 1.19) + // https://github.com/grpc/grpc-go/commit/40cb5618f475e7b9d61aa7920ae4b04ef9bbaf89 + gopts = append(gopts, grpc.WithDialer(dialFn)) } if needWithInsecure { gopts = append(gopts, grpc.WithInsecure()) @@ -75,6 +90,14 @@ func WithFailFast() ClientOpt { return &withFailFast{} } +type withDialer struct { + dialer func(string, time.Duration) (net.Conn, error) +} + +func WithDialer(df func(string, time.Duration) (net.Conn, error)) ClientOpt { + return &withDialer{dialer: df} +} + type withCredentials struct { ServerName string CACert string @@ -128,3 +151,19 @@ func WithTracer(t opentracing.Tracer) ClientOpt { type withTracer struct { tracer opentracing.Tracer } + +func resolveDialer(address string) (func(string, time.Duration) (net.Conn, error), error) { + ch, err := connhelper.GetConnectionHelper(address) + if err != nil { + return nil, err + } + if ch != nil { + f := func(a string, _ time.Duration) (net.Conn, error) { + ctx := context.Background() + return ch.ContextDialer(ctx, a) + } + return f, nil + } + // basic dialer + return dialer, nil +} diff --git a/vendor/github.com/moby/buildkit/client/connhelper/connhelper.go b/vendor/github.com/moby/buildkit/client/connhelper/connhelper.go new file mode 100644 index 00000000..9fa5edcd --- /dev/null +++ b/vendor/github.com/moby/buildkit/client/connhelper/connhelper.go @@ -0,0 +1,37 @@ +// Package connhelper provides helpers for connecting to a remote daemon host with custom logic. +package connhelper + +import ( + "context" + "net" + "net/url" + + "github.com/docker/cli/cli/connhelper/commandconn" +) + +// ConnectionHelper allows to connect to a remote host with custom stream provider binary. +type ConnectionHelper struct { + // ContextDialer can be passed to grpc.WithContextDialer + ContextDialer func(ctx context.Context, addr string) (net.Conn, error) +} + +// GetConnectionHelper returns BuildKit-specific connection helper for the given URL. +// GetConnectionHelper returns nil without error when no helper is registered for the scheme. +// +// docker:// URL requires BuildKit v0.5.0 or later in the container. +func GetConnectionHelper(daemonURL string) (*ConnectionHelper, error) { + u, err := url.Parse(daemonURL) + if err != nil { + return nil, err + } + switch scheme := u.Scheme; scheme { + case "docker": + container := u.Host + return &ConnectionHelper{ + ContextDialer: func(ctx context.Context, addr string) (net.Conn, error) { + return commandconn.New(ctx, "docker", "exec", "-i", container, "buildctl", "dial-stdio") + }, + }, nil + } + return nil, err +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 26e3a157..c6118478 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -116,8 +116,8 @@ github.com/docker/cli/cli/version github.com/docker/cli/internal/containerizedengine github.com/docker/cli/opts github.com/docker/cli/types -github.com/docker/cli/cli/config/credentials github.com/docker/cli/cli/connhelper/commandconn +github.com/docker/cli/cli/config/credentials github.com/docker/cli/cli/connhelper/ssh github.com/docker/cli/kubernetes github.com/docker/cli/cli/manifest/types @@ -151,6 +151,7 @@ github.com/docker/docker/client github.com/docker/docker/api/types github.com/docker/docker/api/types/container github.com/docker/docker/api/types/network +github.com/docker/docker/pkg/stdcopy github.com/docker/docker/api/types/events github.com/docker/docker/api/types/filters github.com/docker/docker/api/types/registry @@ -245,23 +246,24 @@ github.com/konsorten/go-windows-terminal-sequences github.com/matttproud/golang_protobuf_extensions/pbutil # github.com/miekg/pkcs11 v0.0.0-20190322140431-074fd7a1ed19 github.com/miekg/pkcs11 -# github.com/moby/buildkit v0.4.1-0.20190322070013-03feb5e28f65 +# github.com/moby/buildkit v0.4.1-0.20190326070013-325bd96b6b62 github.com/moby/buildkit/client github.com/moby/buildkit/session github.com/moby/buildkit/session/secrets/secretsprovider github.com/moby/buildkit/session/sshforward/sshprovider -github.com/moby/buildkit/util/progress/progressui github.com/moby/buildkit/session/auth/authprovider github.com/moby/buildkit/util/appcontext +github.com/moby/buildkit/identity +github.com/moby/buildkit/util/progress/progressui github.com/moby/buildkit/api/services/control github.com/moby/buildkit/api/types github.com/moby/buildkit/client/buildid +github.com/moby/buildkit/client/connhelper github.com/moby/buildkit/client/llb github.com/moby/buildkit/client/ociindex github.com/moby/buildkit/frontend/gateway/client github.com/moby/buildkit/frontend/gateway/grpcclient github.com/moby/buildkit/frontend/gateway/pb -github.com/moby/buildkit/identity github.com/moby/buildkit/session/content github.com/moby/buildkit/session/filesync github.com/moby/buildkit/session/grpchijack