build: basic buildkit progress support
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
@ -396,6 +396,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
|
||||
Target: options.target,
|
||||
RemoteContext: remote,
|
||||
Platform: options.platform,
|
||||
Version: types.BuilderV1,
|
||||
}
|
||||
|
||||
if s != nil {
|
||||
@ -420,9 +421,9 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
|
||||
defer response.Body.Close()
|
||||
|
||||
imageID := ""
|
||||
aux := func(m jsonmessage.JSONMessage) {
|
||||
aux := func(msg jsonmessage.JSONMessage) {
|
||||
var result types.BuildResult
|
||||
if err := json.Unmarshal(*m.Aux, &result); err != nil {
|
||||
if err := json.Unmarshal(*msg.Aux, &result); err != nil {
|
||||
fmt.Fprintf(dockerCli.Err(), "Failed to parse aux message: %s", err)
|
||||
} else {
|
||||
imageID = result.ID
|
||||
|
||||
@ -1,18 +1,27 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
controlapi "github.com/moby/buildkit/api/services/control"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/session/auth/authprovider"
|
||||
"github.com/moby/buildkit/session/filesync"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tonistiigi/fsutil"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@ -29,13 +38,13 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
|
||||
return errors.Errorf("buildkit not supported by daemon")
|
||||
}
|
||||
|
||||
remote := clientSessionRemote
|
||||
local := false
|
||||
var remote string
|
||||
var body io.Reader
|
||||
switch {
|
||||
case options.contextFromStdin():
|
||||
return errors.Errorf("stdin not implemented")
|
||||
body = os.Stdin
|
||||
case isLocalDir(options.context):
|
||||
local = true
|
||||
remote = clientSessionRemote
|
||||
case urlutil.IsGitURL(options.context):
|
||||
remote = options.context
|
||||
case urlutil.IsURL(options.context):
|
||||
@ -51,7 +60,7 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
|
||||
// statusContext = opentracing.ContextWithSpan(statusContext, span)
|
||||
// }
|
||||
|
||||
if local {
|
||||
if remote == clientSessionRemote {
|
||||
s.Allow(filesync.NewFSSyncProvider([]filesync.SyncedDir{
|
||||
{
|
||||
Name: "context",
|
||||
@ -70,7 +79,7 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
eg.Go(func() error {
|
||||
return s.Run(ctx, dockerCli.Client().DialSession)
|
||||
return s.Run(context.TODO(), dockerCli.Client().DialSession)
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
@ -78,6 +87,8 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
|
||||
s.Close()
|
||||
}()
|
||||
|
||||
buildID := stringid.GenerateRandomID()
|
||||
|
||||
configFile := dockerCli.ConfigFile()
|
||||
buildOptions := types.ImageBuildOptions{
|
||||
Memory: options.memory.Value(),
|
||||
@ -109,16 +120,50 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
|
||||
Target: options.target,
|
||||
RemoteContext: remote,
|
||||
Platform: options.platform,
|
||||
SessionID: "buildkit:" + s.ID(),
|
||||
SessionID: s.ID(),
|
||||
Version: types.BuilderBuildKit,
|
||||
BuildID: buildID,
|
||||
}
|
||||
|
||||
response, err := dockerCli.Client().ImageBuild(ctx, nil, buildOptions)
|
||||
response, err := dockerCli.Client().ImageBuild(context.Background(), body, buildOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if _, err := io.Copy(os.Stdout, response.Body); err != nil {
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
eg.Go(func() error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return dockerCli.Client().BuildCancel(context.TODO(), buildID)
|
||||
case <-done:
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
t := newTracer()
|
||||
var auxCb func(jsonmessage.JSONMessage)
|
||||
if c, err := console.ConsoleFromFile(os.Stderr); err == nil {
|
||||
// not using shared context to not disrupt display but let is finish reporting errors
|
||||
auxCb = t.write
|
||||
eg.Go(func() error {
|
||||
return progressui.DisplaySolveStatus(context.TODO(), c, t.displayCh)
|
||||
})
|
||||
defer close(t.displayCh)
|
||||
}
|
||||
err = jsonmessage.DisplayJSONMessagesStream(response.Body, os.Stdout, dockerCli.Out().FD(), dockerCli.Out().IsTerminal(), auxCb)
|
||||
if err != nil {
|
||||
if jerr, ok := err.(*jsonmessage.JSONError); ok {
|
||||
// If no error code is set, default to 1
|
||||
if jerr.Code == 0 {
|
||||
jerr.Code = 1
|
||||
}
|
||||
// if options.quiet {
|
||||
// fmt.Fprintf(dockerCli.Err(), "%s%s", progBuff, buildBuff)
|
||||
// }
|
||||
return cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@ -133,3 +178,60 @@ func resetUIDAndGID(s *fsutil.Stat) bool {
|
||||
s.Gid = uint32(0)
|
||||
return true
|
||||
}
|
||||
|
||||
type tracer struct {
|
||||
displayCh chan *client.SolveStatus
|
||||
}
|
||||
|
||||
func newTracer() *tracer {
|
||||
return &tracer{
|
||||
displayCh: make(chan *client.SolveStatus),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tracer) write(msg jsonmessage.JSONMessage) {
|
||||
var resp controlapi.StatusResponse
|
||||
|
||||
var dt []byte
|
||||
if err := json.Unmarshal(*msg.Aux, &dt); err != nil {
|
||||
return
|
||||
}
|
||||
if err := (&resp).Unmarshal(dt); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s := client.SolveStatus{}
|
||||
for _, v := range resp.Vertexes {
|
||||
s.Vertexes = append(s.Vertexes, &client.Vertex{
|
||||
Digest: v.Digest,
|
||||
Inputs: v.Inputs,
|
||||
Name: v.Name,
|
||||
Started: v.Started,
|
||||
Completed: v.Completed,
|
||||
Error: v.Error,
|
||||
Cached: v.Cached,
|
||||
})
|
||||
}
|
||||
for _, v := range resp.Statuses {
|
||||
s.Statuses = append(s.Statuses, &client.VertexStatus{
|
||||
ID: v.ID,
|
||||
Vertex: v.Vertex,
|
||||
Name: v.Name,
|
||||
Total: v.Total,
|
||||
Current: v.Current,
|
||||
Timestamp: v.Timestamp,
|
||||
Started: v.Started,
|
||||
Completed: v.Completed,
|
||||
})
|
||||
}
|
||||
for _, v := range resp.Logs {
|
||||
s.Logs = append(s.Logs, &client.VertexLog{
|
||||
Vertex: v.Vertex,
|
||||
Stream: int(v.Stream),
|
||||
Data: v.Msg,
|
||||
Timestamp: v.Timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
t.displayCh <- &s
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
|
||||
// Count the times of calling for handleTarget,
|
||||
// if it is called more that once, that should be considered an error in a trusted push.
|
||||
cnt := 0
|
||||
handleTarget := func(m jsonmessage.JSONMessage) {
|
||||
handleTarget := func(msg jsonmessage.JSONMessage) {
|
||||
cnt++
|
||||
if cnt > 1 {
|
||||
// handleTarget should only be called once. This will be treated as an error.
|
||||
@ -57,7 +57,7 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
|
||||
}
|
||||
|
||||
var pushResult types.PushResult
|
||||
err := json.Unmarshal(*m.Aux, &pushResult)
|
||||
err := json.Unmarshal(*msg.Aux, &pushResult)
|
||||
if err == nil && pushResult.Tag != "" {
|
||||
if dgst, err := digest.Parse(pushResult.Digest); err == nil {
|
||||
h, err := hex.DecodeString(dgst.Hex())
|
||||
|
||||
Reference in New Issue
Block a user