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/build/build.go b/build/build.go index 90bb31d7..ec15e431 100644 --- a/build/build.go +++ b/build/build.go @@ -3,18 +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" ) @@ -40,7 +39,23 @@ 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 progress.Writer) (*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") + } + + pwOld := pw + c, pw, err := driver.Boot(ctx, drivers[0], pw) + if err != nil { + close(pwOld.Status()) + <-pwOld.Done() + return nil, err + } + so := client.SolveOpt{ Frontend: "dockerfile.v0", FrontendAttrs: map[string]string{}, @@ -127,45 +142,6 @@ func Build(ctx context.Context, c *client.Client, opt Options, pw *ProgressWrite 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 { - 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/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..9abcce86 100644 --- a/commands/build.go +++ b/commands/build.go @@ -7,12 +7,12 @@ 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" + "github.com/tonistiigi/buildx/util/progress" ) type buildOptions struct { @@ -94,17 +94,16 @@ 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, "buildx-buildkit-default", nil, dockerCli.Client()) if err != nil { return err } 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, 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 new file mode 100644 index 00000000..5f126cae --- /dev/null +++ b/driver/docker/driver.go @@ -0,0 +1,210 @@ +package docker + +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 +} + +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 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 "+buildkitImage, func() error { + rc, err := d.DockerAPI.ImageCreate(ctx, buildkitImage, types.ImageCreateOptions{}) + if err != nil { + return err + } + _, err = io.Copy(ioutil.Discard, rc) + return err + }); err != nil { + return err + } + if err := l.Wrap("creating container "+d.Name, func() error { + _, err := d.DockerAPI.ContainerCreate(ctx, &container.Config{ + Image: buildkitImage, + }, &container.HostConfig{ + Privileged: true, + }, &network.NetworkingConfig{}, d.Name) + if err != nil { + return err + } + 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 + } + 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{}) +} + +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 { + 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(ctx context.Context) (*client.Client, error) { + _, 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) +} diff --git a/driver/docker/factory.go b/driver/docker/factory.go new file mode 100644 index 00000000..469f7c66 --- /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{InitConfig: cfg, version: v}, nil +} diff --git a/driver/driver.go b/driver/driver.go new file mode 100644 index 00000000..0b03c1ec --- /dev/null +++ b/driver/driver.go @@ -0,0 +1,131 @@ +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 Status int + +const ( + Terminated Status = iota + Starting + Running + Stopping + Stopped +) + +type Info struct { + Status Status +} + +type Driver interface { + 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, pw progress.Writer) (*client.Client, progress.Writer, error) { + try := 0 + for { + info, err := d.Info(ctx) + if err != nil { + return nil, nil, err + } + try++ + if info.Status != Running { + if try > 2 { + return nil, nil, errors.Errorf("failed to bootstrap %T driver in attempts", d) + } + if err := d.Bootstrap(ctx, func(s *client.SolveStatus) { + if pw != nil { + pw.Status() <- s + } + }); err != nil { + return nil, nil, err + } + } + + c, err := d.Client(ctx) + if err != nil { + if errors.Cause(err) == ErrNotRunning && try <= 2 { + continue + } + return nil, nil, err + } + 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 +} + +func (p *pw) Status() chan *client.SolveStatus { + return p.status +} diff --git a/driver/manager.go b/driver/manager.go new file mode 100644 index 00000000..f3dd8ce5 --- /dev/null +++ b/driver/manager.go @@ -0,0 +1,70 @@ +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 GetDefaultFactory() (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 +} + +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, + }) +} diff --git a/go.mod b/go.mod index ceb7b07d..a1120fbb 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 @@ -52,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/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/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 eed85851..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 @@ -149,6 +149,9 @@ 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/pkg/stdcopy github.com/docker/docker/api/types/events github.com/docker/docker/api/types/filters github.com/docker/docker/api/types/registry @@ -157,17 +160,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 +177,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 @@ -245,29 +246,30 @@ 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/util/appdefaults +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 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 +360,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