Merge pull request #1111 from tiborvass/experimental-buildkit
Support for experimental BuildKit
Upstream-commit: 2daec78609
Component: cli
This commit is contained in:
@ -16,6 +16,15 @@ const (
|
||||
defaultDiskUsageContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Names}}"
|
||||
defaultDiskUsageVolumeTableFormat = "table {{.Name}}\t{{.Links}}\t{{.Size}}"
|
||||
defaultDiskUsageTableFormat = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
|
||||
defaultBuildCacheVerboseFormat = `
|
||||
ID: {{.ID}}
|
||||
Description: {{.Description}}
|
||||
Mutable: {{.Mutable}}
|
||||
Size: {{.Size}}
|
||||
CreatedAt: {{.CreatedAt}}
|
||||
LastUsedAt: {{.LastUsedAt}}
|
||||
UsageCount: {{.UsageCount}}
|
||||
`
|
||||
|
||||
typeHeader = "TYPE"
|
||||
totalHeader = "TOTAL"
|
||||
@ -34,6 +43,7 @@ type DiskUsageContext struct {
|
||||
Images []*types.ImageSummary
|
||||
Containers []*types.Container
|
||||
Volumes []*types.Volume
|
||||
BuildCache []*types.BuildCache
|
||||
BuilderSize int64
|
||||
}
|
||||
|
||||
@ -100,6 +110,7 @@ func (ctx *DiskUsageContext) Write() (err error) {
|
||||
|
||||
err = ctx.contextFormat(tmpl, &diskUsageBuilderContext{
|
||||
builderSize: ctx.BuilderSize,
|
||||
buildCache: ctx.BuildCache,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -184,6 +195,13 @@ func (ctx *DiskUsageContext) verboseWrite() error {
|
||||
|
||||
// And build cache
|
||||
fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
|
||||
|
||||
t := template.Must(template.New("buildcache").Parse(defaultBuildCacheVerboseFormat))
|
||||
|
||||
for _, v := range ctx.BuildCache {
|
||||
t.Execute(ctx.Output, *v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -366,6 +384,7 @@ func (c *diskUsageVolumesContext) Reclaimable() string {
|
||||
type diskUsageBuilderContext struct {
|
||||
HeaderContext
|
||||
builderSize int64
|
||||
buildCache []*types.BuildCache
|
||||
}
|
||||
|
||||
func (c *diskUsageBuilderContext) MarshalJSON() ([]byte, error) {
|
||||
@ -377,11 +396,17 @@ func (c *diskUsageBuilderContext) Type() string {
|
||||
}
|
||||
|
||||
func (c *diskUsageBuilderContext) TotalCount() string {
|
||||
return ""
|
||||
return fmt.Sprintf("%d", len(c.buildCache))
|
||||
}
|
||||
|
||||
func (c *diskUsageBuilderContext) Active() string {
|
||||
return ""
|
||||
numActive := 0
|
||||
for _, bc := range c.buildCache {
|
||||
if bc.InUse {
|
||||
numActive++
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%d", numActive)
|
||||
}
|
||||
|
||||
func (c *diskUsageBuilderContext) Size() string {
|
||||
@ -389,5 +414,12 @@ func (c *diskUsageBuilderContext) Size() string {
|
||||
}
|
||||
|
||||
func (c *diskUsageBuilderContext) Reclaimable() string {
|
||||
return c.Size()
|
||||
var inUseBytes int64
|
||||
for _, bc := range c.buildCache {
|
||||
if bc.InUse {
|
||||
inUseBytes += bc.Size
|
||||
}
|
||||
}
|
||||
|
||||
return units.HumanSize(float64(c.builderSize - inUseBytes))
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ func TestDiskUsageContextFormatWrite(t *testing.T) {
|
||||
Images 0 0 0B 0B
|
||||
Containers 0 0 0B 0B
|
||||
Local Volumes 0 0 0B 0B
|
||||
Build Cache 0B 0B
|
||||
Build Cache 0 0 0B 0B
|
||||
`,
|
||||
},
|
||||
{
|
||||
@ -76,7 +76,7 @@ Build cache usage: 0B
|
||||
Images 0 0 0B 0B
|
||||
Containers 0 0 0B 0B
|
||||
Local Volumes 0 0 0B 0B
|
||||
Build Cache 0B 0B
|
||||
Build Cache 0 0 0B 0B
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
||||
@ -2,4 +2,4 @@ TYPE ACTIVE
|
||||
Images 0
|
||||
Containers 0
|
||||
Local Volumes 0
|
||||
Build Cache
|
||||
Build Cache 0
|
||||
|
||||
@ -17,8 +17,8 @@ size: 0B
|
||||
reclaimable: 0B
|
||||
|
||||
type: Build Cache
|
||||
total:
|
||||
active:
|
||||
total: 0
|
||||
active: 0
|
||||
size: 0B
|
||||
reclaimable: 0B
|
||||
|
||||
|
||||
@ -35,6 +35,8 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var errStdinConflict = errors.New("invalid argument: can't use stdin for both build context and dockerfile")
|
||||
|
||||
type buildOptions struct {
|
||||
context string
|
||||
dockerfileName string
|
||||
@ -55,6 +57,7 @@ type buildOptions struct {
|
||||
isolation string
|
||||
quiet bool
|
||||
noCache bool
|
||||
console opts.NullableBool
|
||||
rm bool
|
||||
forceRm bool
|
||||
pull bool
|
||||
@ -149,6 +152,9 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags.SetAnnotation("stream", "experimental", nil)
|
||||
flags.SetAnnotation("stream", "version", []string{"1.31"})
|
||||
|
||||
flags.Var(&options.console, "console", "Show console output (with buildkit only) (true, false, auto)")
|
||||
flags.SetAnnotation("console", "experimental", nil)
|
||||
flags.SetAnnotation("console", "version", []string{"1.38"})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -170,6 +176,10 @@ func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
|
||||
|
||||
// nolint: gocyclo
|
||||
func runBuild(dockerCli command.Cli, options buildOptions) error {
|
||||
if os.Getenv("DOCKER_BUILDKIT") != "" {
|
||||
return runBuildBuildKit(dockerCli, options)
|
||||
}
|
||||
|
||||
var (
|
||||
buildCtx io.ReadCloser
|
||||
dockerfileCtx io.ReadCloser
|
||||
@ -188,7 +198,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
|
||||
|
||||
if options.dockerfileFromStdin() {
|
||||
if options.contextFromStdin() {
|
||||
return errors.New("invalid argument: can't use stdin for both build context and dockerfile")
|
||||
return errStdinConflict
|
||||
}
|
||||
dockerfileCtx = dockerCli.In()
|
||||
}
|
||||
@ -362,37 +372,11 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
|
||||
|
||||
configFile := dockerCli.ConfigFile()
|
||||
authConfigs, _ := configFile.GetAllCredentials()
|
||||
buildOptions := types.ImageBuildOptions{
|
||||
Memory: options.memory.Value(),
|
||||
MemorySwap: options.memorySwap.Value(),
|
||||
Tags: options.tags.GetAll(),
|
||||
SuppressOutput: options.quiet,
|
||||
NoCache: options.noCache,
|
||||
Remove: options.rm,
|
||||
ForceRemove: options.forceRm,
|
||||
PullParent: options.pull,
|
||||
Isolation: container.Isolation(options.isolation),
|
||||
CPUSetCPUs: options.cpuSetCpus,
|
||||
CPUSetMems: options.cpuSetMems,
|
||||
CPUShares: options.cpuShares,
|
||||
CPUQuota: options.cpuQuota,
|
||||
CPUPeriod: options.cpuPeriod,
|
||||
CgroupParent: options.cgroupParent,
|
||||
Dockerfile: relDockerfile,
|
||||
ShmSize: options.shmSize.Value(),
|
||||
Ulimits: options.ulimits.GetList(),
|
||||
BuildArgs: configFile.ParseProxyConfig(dockerCli.Client().DaemonHost(), options.buildArgs.GetAll()),
|
||||
AuthConfigs: authConfigs,
|
||||
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
|
||||
CacheFrom: options.cacheFrom,
|
||||
SecurityOpt: options.securityOpt,
|
||||
NetworkMode: options.networkMode,
|
||||
Squash: options.squash,
|
||||
ExtraHosts: options.extraHosts.GetAll(),
|
||||
Target: options.target,
|
||||
RemoteContext: remote,
|
||||
Platform: options.platform,
|
||||
}
|
||||
buildOptions := imageBuildOptions(dockerCli, options)
|
||||
buildOptions.Version = types.BuilderV1
|
||||
buildOptions.Dockerfile = relDockerfile
|
||||
buildOptions.AuthConfigs = authConfigs
|
||||
buildOptions.RemoteContext = remote
|
||||
|
||||
if s != nil {
|
||||
go func() {
|
||||
@ -416,9 +400,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
|
||||
@ -602,3 +586,35 @@ func replaceDockerfileForContentTrust(ctx context.Context, inputTarStream io.Rea
|
||||
|
||||
return pipeReader
|
||||
}
|
||||
|
||||
func imageBuildOptions(dockerCli command.Cli, options buildOptions) types.ImageBuildOptions {
|
||||
configFile := dockerCli.ConfigFile()
|
||||
return types.ImageBuildOptions{
|
||||
Memory: options.memory.Value(),
|
||||
MemorySwap: options.memorySwap.Value(),
|
||||
Tags: options.tags.GetAll(),
|
||||
SuppressOutput: options.quiet,
|
||||
NoCache: options.noCache,
|
||||
Remove: options.rm,
|
||||
ForceRemove: options.forceRm,
|
||||
PullParent: options.pull,
|
||||
Isolation: container.Isolation(options.isolation),
|
||||
CPUSetCPUs: options.cpuSetCpus,
|
||||
CPUSetMems: options.cpuSetMems,
|
||||
CPUShares: options.cpuShares,
|
||||
CPUQuota: options.cpuQuota,
|
||||
CPUPeriod: options.cpuPeriod,
|
||||
CgroupParent: options.cgroupParent,
|
||||
ShmSize: options.shmSize.Value(),
|
||||
Ulimits: options.ulimits.GetList(),
|
||||
BuildArgs: configFile.ParseProxyConfig(dockerCli.Client().DaemonHost(), options.buildArgs.GetAll()),
|
||||
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
|
||||
CacheFrom: options.cacheFrom,
|
||||
SecurityOpt: options.securityOpt,
|
||||
NetworkMode: options.networkMode,
|
||||
Squash: options.squash,
|
||||
ExtraHosts: options.extraHosts.GetAll(),
|
||||
Target: options.target,
|
||||
Platform: options.platform,
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,59 +81,82 @@ func ValidateContextDirectory(srcPath string, excludes []string) error {
|
||||
})
|
||||
}
|
||||
|
||||
// GetContextFromReader will read the contents of the given reader as either a
|
||||
// Dockerfile or tar archive. Returns a tar archive used as a context and a
|
||||
// path to the Dockerfile inside the tar.
|
||||
func GetContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
|
||||
buf := bufio.NewReader(r)
|
||||
// DetectArchiveReader detects whether the input stream is an archive or a
|
||||
// Dockerfile and returns a buffered version of input, safe to consume in lieu
|
||||
// of input. If an archive is detected, isArchive is set to true, and to false
|
||||
// otherwise, in which case it is safe to assume input represents the contents
|
||||
// of a Dockerfile.
|
||||
func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, isArchive bool, err error) {
|
||||
buf := bufio.NewReader(input)
|
||||
|
||||
magic, err := buf.Peek(archiveHeaderSize)
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, "", errors.Errorf("failed to peek context header from STDIN: %v", err)
|
||||
return nil, false, errors.Errorf("failed to peek context header from STDIN: %v", err)
|
||||
}
|
||||
|
||||
if IsArchive(magic) {
|
||||
return ioutils.NewReadCloserWrapper(buf, func() error { return r.Close() }), dockerfileName, nil
|
||||
return ioutils.NewReadCloserWrapper(buf, func() error { return input.Close() }), IsArchive(magic), nil
|
||||
}
|
||||
|
||||
// WriteTempDockerfile writes a Dockerfile stream to a temporary file with a
|
||||
// name specified by DefaultDockerfileName and returns the path to the
|
||||
// temporary directory containing the Dockerfile.
|
||||
func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) {
|
||||
// err is a named return value, due to the defer call below.
|
||||
dockerfileDir, err = ioutil.TempDir("", "docker-build-tempdockerfile-")
|
||||
if err != nil {
|
||||
return "", errors.Errorf("unable to create temporary context directory: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
os.RemoveAll(dockerfileDir)
|
||||
}
|
||||
}()
|
||||
|
||||
f, err := os.Create(filepath.Join(dockerfileDir, DefaultDockerfileName))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := io.Copy(f, rc); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dockerfileDir, rc.Close()
|
||||
}
|
||||
|
||||
// GetContextFromReader will read the contents of the given reader as either a
|
||||
// Dockerfile or tar archive. Returns a tar archive used as a context and a
|
||||
// path to the Dockerfile inside the tar.
|
||||
func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
|
||||
rc, isArchive, err := DetectArchiveReader(rc)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if isArchive {
|
||||
return rc, dockerfileName, nil
|
||||
}
|
||||
|
||||
// Input should be read as a Dockerfile.
|
||||
|
||||
if dockerfileName == "-" {
|
||||
return nil, "", errors.New("build context is not an archive")
|
||||
}
|
||||
|
||||
// Input should be read as a Dockerfile.
|
||||
tmpDir, err := ioutil.TempDir("", "docker-build-context-")
|
||||
if err != nil {
|
||||
return nil, "", errors.Errorf("unable to create temporary context directory: %v", err)
|
||||
}
|
||||
|
||||
f, err := os.Create(filepath.Join(tmpDir, DefaultDockerfileName))
|
||||
dockerfileDir, err := WriteTempDockerfile(rc)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
_, err = io.Copy(f, buf)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if err := r.Close(); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
tar, err := archive.Tar(tmpDir, archive.Uncompressed)
|
||||
tar, err := archive.Tar(dockerfileDir, archive.Uncompressed)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return ioutils.NewReadCloserWrapper(tar, func() error {
|
||||
err := tar.Close()
|
||||
os.RemoveAll(tmpDir)
|
||||
os.RemoveAll(dockerfileDir)
|
||||
return err
|
||||
}), DefaultDockerfileName, nil
|
||||
|
||||
}
|
||||
|
||||
// IsArchive checks for the magic bytes of a tar or any supported compression
|
||||
|
||||
300
components/cli/cli/command/image/build_buildkit.go
Normal file
300
components/cli/cli/command/image/build_buildkit.go
Normal file
@ -0,0 +1,300 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/image/build"
|
||||
"github.com/docker/docker/api/types"
|
||||
"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"
|
||||
)
|
||||
|
||||
const uploadRequestRemote = "upload-request"
|
||||
|
||||
var errDockerfileConflict = errors.New("ambiguous Dockerfile source: both stdin and flag correspond to Dockerfiles")
|
||||
|
||||
//nolint: gocyclo
|
||||
func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
s, err := trySession(dockerCli, options.context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s == nil {
|
||||
return errors.Errorf("buildkit not supported by daemon")
|
||||
}
|
||||
|
||||
var (
|
||||
remote string
|
||||
body io.Reader
|
||||
dockerfileName = options.dockerfileName
|
||||
dockerfileReader io.ReadCloser
|
||||
dockerfileDir string
|
||||
contextDir string
|
||||
)
|
||||
|
||||
switch {
|
||||
case options.contextFromStdin():
|
||||
if options.dockerfileFromStdin() {
|
||||
return errStdinConflict
|
||||
}
|
||||
rc, isArchive, err := build.DetectArchiveReader(os.Stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isArchive {
|
||||
body = rc
|
||||
remote = uploadRequestRemote
|
||||
} else {
|
||||
if options.dockerfileName != "" {
|
||||
return errDockerfileConflict
|
||||
}
|
||||
dockerfileReader = rc
|
||||
remote = clientSessionRemote
|
||||
// TODO: make fssync handle empty contextdir
|
||||
contextDir, _ = ioutil.TempDir("", "empty-dir")
|
||||
defer os.RemoveAll(contextDir)
|
||||
}
|
||||
case isLocalDir(options.context):
|
||||
contextDir = options.context
|
||||
if options.dockerfileFromStdin() {
|
||||
dockerfileReader = os.Stdin
|
||||
} else if options.dockerfileName != "" {
|
||||
dockerfileName = filepath.Base(options.dockerfileName)
|
||||
dockerfileDir = filepath.Dir(options.dockerfileName)
|
||||
} else {
|
||||
dockerfileDir = options.context
|
||||
}
|
||||
remote = clientSessionRemote
|
||||
case urlutil.IsGitURL(options.context):
|
||||
remote = options.context
|
||||
case urlutil.IsURL(options.context):
|
||||
remote = options.context
|
||||
default:
|
||||
return errors.Errorf("unable to prepare context: path %q not found", options.context)
|
||||
}
|
||||
|
||||
if dockerfileReader != nil {
|
||||
dockerfileName = build.DefaultDockerfileName
|
||||
dockerfileDir, err = build.WriteTempDockerfile(dockerfileReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(dockerfileDir)
|
||||
}
|
||||
|
||||
if dockerfileDir != "" {
|
||||
s.Allow(filesync.NewFSSyncProvider([]filesync.SyncedDir{
|
||||
{
|
||||
Name: "context",
|
||||
Dir: contextDir,
|
||||
Map: resetUIDAndGID,
|
||||
},
|
||||
{
|
||||
Name: "dockerfile",
|
||||
Dir: dockerfileDir,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
s.Allow(authprovider.NewDockerAuthProvider())
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
eg.Go(func() error {
|
||||
return s.Run(context.TODO(), dockerCli.Client().DialSession)
|
||||
})
|
||||
|
||||
buildID := stringid.GenerateRandomID()
|
||||
if body != nil {
|
||||
eg.Go(func() error {
|
||||
buildOptions := types.ImageBuildOptions{
|
||||
Version: types.BuilderBuildKit,
|
||||
BuildID: uploadRequestRemote + ":" + buildID,
|
||||
}
|
||||
|
||||
response, err := dockerCli.Client().ImageBuild(context.Background(), body, buildOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
eg.Go(func() error {
|
||||
defer func() { // make sure the Status ends cleanly on build errors
|
||||
s.Close()
|
||||
}()
|
||||
|
||||
buildOptions := imageBuildOptions(dockerCli, options)
|
||||
buildOptions.Version = types.BuilderBuildKit
|
||||
buildOptions.Dockerfile = dockerfileName
|
||||
//buildOptions.AuthConfigs = authConfigs // handled by session
|
||||
buildOptions.RemoteContext = remote
|
||||
buildOptions.SessionID = s.ID()
|
||||
buildOptions.BuildID = buildID
|
||||
return doBuild(ctx, eg, dockerCli, options, buildOptions)
|
||||
})
|
||||
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
func doBuild(ctx context.Context, eg *errgroup.Group, dockerCli command.Cli, options buildOptions, buildOptions types.ImageBuildOptions) (finalErr error) {
|
||||
response, err := dockerCli.Client().ImageBuild(context.Background(), nil, buildOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
eg.Go(func() error {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return dockerCli.Client().BuildCancel(context.TODO(), buildOptions.BuildID)
|
||||
case <-done:
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
t := newTracer()
|
||||
ssArr := []*client.SolveStatus{}
|
||||
|
||||
displayStatus := func(displayCh chan *client.SolveStatus) {
|
||||
var c console.Console
|
||||
out := os.Stderr
|
||||
// TODO: Handle interactive output in non-interactive environment.
|
||||
consoleOpt := options.console.Value()
|
||||
if cons, err := console.ConsoleFromFile(out); err == nil && (consoleOpt == nil || *consoleOpt) {
|
||||
c = cons
|
||||
}
|
||||
// not using shared context to not disrupt display but let is finish reporting errors
|
||||
eg.Go(func() error {
|
||||
return progressui.DisplaySolveStatus(context.TODO(), c, out, displayCh)
|
||||
})
|
||||
}
|
||||
|
||||
if options.quiet {
|
||||
eg.Go(func() error {
|
||||
// TODO: make sure t.displayCh closes
|
||||
for ss := range t.displayCh {
|
||||
ssArr = append(ssArr, ss)
|
||||
}
|
||||
<-done
|
||||
// TODO: verify that finalErr is indeed set when error occurs
|
||||
if finalErr != nil {
|
||||
displayCh := make(chan *client.SolveStatus)
|
||||
go func() {
|
||||
for _, ss := range ssArr {
|
||||
displayCh <- ss
|
||||
}
|
||||
close(displayCh)
|
||||
}()
|
||||
displayStatus(displayCh)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
displayStatus(t.displayCh)
|
||||
}
|
||||
defer close(t.displayCh)
|
||||
err = jsonmessage.DisplayJSONMessagesStream(response.Body, os.Stdout, dockerCli.Out().FD(), dockerCli.Out().IsTerminal(), t.write)
|
||||
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
|
||||
}
|
||||
return cli.StatusError{Status: jerr.Message, StatusCode: jerr.Code}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func resetUIDAndGID(s *fsutil.Stat) bool {
|
||||
s.Uid = 0
|
||||
s.Gid = 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
|
||||
|
||||
if msg.ID != "moby.buildkit.trace" {
|
||||
return
|
||||
}
|
||||
|
||||
var dt []byte
|
||||
// ignoring all messages that are not understood
|
||||
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())
|
||||
|
||||
@ -59,6 +59,7 @@ func runDiskUsage(dockerCli command.Cli, opts diskUsageOptions) error {
|
||||
},
|
||||
LayersSize: du.LayersSize,
|
||||
BuilderSize: du.BuilderSize,
|
||||
BuildCache: du.BuildCache,
|
||||
Images: du.Images,
|
||||
Containers: du.Containers,
|
||||
Volumes: du.Volumes,
|
||||
|
||||
Reference in New Issue
Block a user