pull/1466/merge
Ilya Dmitrichenko 3 years ago committed by GitHub
commit 6f1e0b73fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -14,7 +14,8 @@ import (
"strings" "strings"
"github.com/docker/buildx/bake/hclparser" "github.com/docker/buildx/bake/hclparser"
"github.com/docker/buildx/build" "github.com/docker/buildx/options"
"github.com/docker/buildx/util/buildflags" "github.com/docker/buildx/util/buildflags"
"github.com/docker/buildx/util/platformutil" "github.com/docker/buildx/util/platformutil"
"github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config"
@ -776,8 +777,8 @@ func (t *Target) AddOverrides(overrides map[string]Override) error {
return nil return nil
} }
func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) { func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]options.Options, error) {
m2 := make(map[string]build.Options, len(m)) m2 := make(map[string]options.Options, len(m))
for k, v := range m { for k, v := range m {
bo, err := toBuildOpt(v, inp) bo, err := toBuildOpt(v, inp)
if err != nil { if err != nil {
@ -788,14 +789,14 @@ func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Optio
return m2, nil return m2, nil
} }
func updateContext(t *build.Inputs, inp *Input) { func updateContext(t *options.Inputs, inp *Input) {
if inp == nil || inp.State == nil { if inp == nil || inp.State == nil {
return return
} }
for k, v := range t.NamedContexts { for k, v := range t.NamedContexts {
if v.Path == "." { if v.Path == "." {
t.NamedContexts[k] = build.NamedContext{Path: inp.URL} t.NamedContexts[k] = options.NamedContext{Path: inp.URL}
} }
if strings.HasPrefix(v.Path, "cwd://") || strings.HasPrefix(v.Path, "target:") || strings.HasPrefix(v.Path, "docker-image:") { if strings.HasPrefix(v.Path, "cwd://") || strings.HasPrefix(v.Path, "target:") || strings.HasPrefix(v.Path, "docker-image:") {
continue continue
@ -804,7 +805,7 @@ func updateContext(t *build.Inputs, inp *Input) {
continue continue
} }
st := llb.Scratch().File(llb.Copy(*inp.State, v.Path, "/"), llb.WithCustomNamef("set context %s to %s", k, v.Path)) st := llb.Scratch().File(llb.Copy(*inp.State, v.Path, "/"), llb.WithCustomNamef("set context %s to %s", k, v.Path))
t.NamedContexts[k] = build.NamedContext{State: &st} t.NamedContexts[k] = options.NamedContext{State: &st}
} }
if t.ContextPath == "." { if t.ContextPath == "." {
@ -824,7 +825,7 @@ func updateContext(t *build.Inputs, inp *Input) {
// validateContextsEntitlements is a basic check to ensure contexts do not // validateContextsEntitlements is a basic check to ensure contexts do not
// escape local directories when loaded from remote sources. This is to be // escape local directories when loaded from remote sources. This is to be
// replaced with proper entitlements support in the future. // replaced with proper entitlements support in the future.
func validateContextsEntitlements(t build.Inputs, inp *Input) error { func validateContextsEntitlements(t options.Inputs, inp *Input) error {
if inp == nil || inp.State == nil { if inp == nil || inp.State == nil {
return nil return nil
} }
@ -874,7 +875,7 @@ func checkPath(p string) error {
return nil return nil
} }
func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { func toBuildOpt(t *Target, inp *Input) (*options.Options, error) {
if v := t.Context; v != nil && *v == "-" { if v := t.Context; v != nil && *v == "-" {
return nil, errors.Errorf("context from stdin not allowed in bake") return nil, errors.Errorf("context from stdin not allowed in bake")
} }
@ -927,7 +928,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
networkMode = *t.NetworkMode networkMode = *t.NetworkMode
} }
bi := build.Inputs{ bi := options.Inputs{
ContextPath: contextPath, ContextPath: contextPath,
DockerfilePath: dockerfilePath, DockerfilePath: dockerfilePath,
NamedContexts: toNamedContexts(t.Contexts), NamedContexts: toNamedContexts(t.Contexts),
@ -941,7 +942,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
} }
for k, v := range bi.NamedContexts { for k, v := range bi.NamedContexts {
if strings.HasPrefix(v.Path, "cwd://") { if strings.HasPrefix(v.Path, "cwd://") {
bi.NamedContexts[k] = build.NamedContext{Path: path.Clean(strings.TrimPrefix(v.Path, "cwd://"))} bi.NamedContexts[k] = options.NamedContext{Path: path.Clean(strings.TrimPrefix(v.Path, "cwd://"))}
} }
} }
@ -951,7 +952,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
t.Context = &bi.ContextPath t.Context = &bi.ContextPath
bo := &build.Options{ bo := &options.Options{
Inputs: bi, Inputs: bi,
Tags: t.Tags, Tags: t.Tags,
BuildArgs: args, BuildArgs: args,
@ -1089,10 +1090,10 @@ func sliceEqual(s1, s2 []string) bool {
return true return true
} }
func toNamedContexts(m map[string]string) map[string]build.NamedContext { func toNamedContexts(m map[string]string) map[string]options.NamedContext {
m2 := make(map[string]build.NamedContext, len(m)) m2 := make(map[string]options.NamedContext, len(m))
for k, v := range m { for k, v := range m {
m2[k] = build.NamedContext{Path: v} m2[k] = options.NamedContext{Path: v}
} }
return m2 return m2
} }

@ -23,13 +23,14 @@ import (
"github.com/containerd/containerd/images" "github.com/containerd/containerd/images"
"github.com/containerd/containerd/platforms" "github.com/containerd/containerd/platforms"
"github.com/docker/buildx/builder" "github.com/docker/buildx/builder"
"github.com/docker/buildx/options"
"github.com/docker/buildx/driver" "github.com/docker/buildx/driver"
"github.com/docker/buildx/util/dockerutil" "github.com/docker/buildx/util/dockerutil"
"github.com/docker/buildx/util/imagetools" "github.com/docker/buildx/util/imagetools"
"github.com/docker/buildx/util/progress" "github.com/docker/buildx/util/progress"
"github.com/docker/buildx/util/resolver" "github.com/docker/buildx/util/resolver"
"github.com/docker/buildx/util/waitmap" "github.com/docker/buildx/util/waitmap"
"github.com/docker/cli/opts"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/builder/remotecontext/urlutil" "github.com/docker/docker/builder/remotecontext/urlutil"
@ -38,7 +39,6 @@ import (
"github.com/moby/buildkit/client/llb" "github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/exporter/containerimage/exptypes" "github.com/moby/buildkit/exporter/containerimage/exptypes"
gateway "github.com/moby/buildkit/frontend/gateway/client" gateway "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/upload/uploadprovider" "github.com/moby/buildkit/session/upload/uploadprovider"
"github.com/moby/buildkit/solver/errdefs" "github.com/moby/buildkit/solver/errdefs"
"github.com/moby/buildkit/solver/pb" "github.com/moby/buildkit/solver/pb"
@ -63,281 +63,6 @@ const (
printFallbackImage = "docker/dockerfile-upstream:1.4-outline@sha256:627443ff4e2d0f635d429cfc1da5388bcd5a70949c38adcd3cd7c4e5df67c73c" printFallbackImage = "docker/dockerfile-upstream:1.4-outline@sha256:627443ff4e2d0f635d429cfc1da5388bcd5a70949c38adcd3cd7c4e5df67c73c"
) )
type Options struct {
Inputs Inputs
Allow []entitlements.Entitlement
Attests map[string]*string
BuildArgs map[string]string
CacheFrom []client.CacheOptionsEntry
CacheTo []client.CacheOptionsEntry
CgroupParent string
Exports []client.ExportEntry
ExtraHosts []string
ImageIDFile string
Labels map[string]string
NetworkMode string
NoCache bool
NoCacheFilter []string
Platforms []specs.Platform
Pull bool
Session []session.Attachable
ShmSize opts.MemBytes
Tags []string
Target string
Ulimits *opts.UlimitOpt
// Linked marks this target as exclusively linked (not requested by the user).
Linked bool
PrintFunc *PrintFunc
}
type PrintFunc struct {
Name string
Format string
}
type Inputs struct {
ContextPath string
DockerfilePath string
InStream io.Reader
ContextState *llb.State
DockerfileInline string
NamedContexts map[string]NamedContext
}
type NamedContext struct {
Path string
State *llb.State
}
func filterAvailableNodes(nodes []builder.Node) ([]builder.Node, error) {
out := make([]builder.Node, 0, len(nodes))
err := errors.Errorf("no drivers found")
for _, n := range nodes {
if n.Err == nil && n.Driver != nil {
out = append(out, n)
}
if n.Err != nil {
err = n.Err
}
}
if len(out) > 0 {
return out, nil
}
return nil, err
}
type driverPair struct {
driverIndex int
platforms []specs.Platform
so *client.SolveOpt
bopts gateway.BuildOpts
}
func driverIndexes(m map[string][]driverPair) []int {
out := make([]int, 0, len(m))
visited := map[int]struct{}{}
for _, dp := range m {
for _, d := range dp {
if _, ok := visited[d.driverIndex]; ok {
continue
}
visited[d.driverIndex] = struct{}{}
out = append(out, d.driverIndex)
}
}
return out
}
func allIndexes(l int) []int {
out := make([]int, 0, l)
for i := 0; i < l; i++ {
out = append(out, i)
}
return out
}
func ensureBooted(ctx context.Context, nodes []builder.Node, idxs []int, pw progress.Writer) ([]*client.Client, error) {
clients := make([]*client.Client, len(nodes))
baseCtx := ctx
eg, ctx := errgroup.WithContext(ctx)
for _, i := range idxs {
func(i int) {
eg.Go(func() error {
c, err := driver.Boot(ctx, baseCtx, nodes[i].Driver, pw)
if err != nil {
return err
}
clients[i] = c
return nil
})
}(i)
}
if err := eg.Wait(); err != nil {
return nil, err
}
return clients, nil
}
func splitToDriverPairs(availablePlatforms map[string]int, opt map[string]Options) map[string][]driverPair {
m := map[string][]driverPair{}
for k, opt := range opt {
mm := map[int][]specs.Platform{}
for _, p := range opt.Platforms {
k := platforms.Format(p)
idx := availablePlatforms[k] // default 0
pp := mm[idx]
pp = append(pp, p)
mm[idx] = pp
}
// if no platform is specified, use first driver
if len(mm) == 0 {
mm[0] = nil
}
dps := make([]driverPair, 0, 2)
for idx, pp := range mm {
dps = append(dps, driverPair{driverIndex: idx, platforms: pp})
}
m[k] = dps
}
return m
}
func resolveDrivers(ctx context.Context, nodes []builder.Node, opt map[string]Options, pw progress.Writer) (map[string][]driverPair, []*client.Client, error) {
dps, clients, err := resolveDriversBase(ctx, nodes, opt, pw)
if err != nil {
return nil, nil, err
}
bopts := make([]gateway.BuildOpts, len(clients))
span, ctx := tracing.StartSpan(ctx, "load buildkit capabilities", trace.WithSpanKind(trace.SpanKindInternal))
eg, ctx := errgroup.WithContext(ctx)
for i, c := range clients {
if c == nil {
continue
}
func(i int, c *client.Client) {
eg.Go(func() error {
clients[i].Build(ctx, client.SolveOpt{}, "buildx", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
bopts[i] = c.BuildOpts()
return nil, nil
}, nil)
return nil
})
}(i, c)
}
err = eg.Wait()
tracing.FinishWithError(span, err)
if err != nil {
return nil, nil, err
}
for key := range dps {
for i, dp := range dps[key] {
dps[key][i].bopts = bopts[dp.driverIndex]
}
}
return dps, clients, nil
}
func resolveDriversBase(ctx context.Context, nodes []builder.Node, opt map[string]Options, pw progress.Writer) (map[string][]driverPair, []*client.Client, error) {
availablePlatforms := map[string]int{}
for i, node := range nodes {
for _, p := range node.Platforms {
availablePlatforms[platforms.Format(p)] = i
}
}
undetectedPlatform := false
allPlatforms := map[string]int{}
for _, opt := range opt {
for _, p := range opt.Platforms {
k := platforms.Format(p)
allPlatforms[k] = -1
if _, ok := availablePlatforms[k]; !ok {
undetectedPlatform = true
}
}
}
// fast path
if len(nodes) == 1 || len(allPlatforms) == 0 {
m := map[string][]driverPair{}
for k, opt := range opt {
m[k] = []driverPair{{driverIndex: 0, platforms: opt.Platforms}}
}
clients, err := ensureBooted(ctx, nodes, driverIndexes(m), pw)
if err != nil {
return nil, nil, err
}
return m, clients, nil
}
// map based on existing platforms
if !undetectedPlatform {
m := splitToDriverPairs(availablePlatforms, opt)
clients, err := ensureBooted(ctx, nodes, driverIndexes(m), pw)
if err != nil {
return nil, nil, err
}
return m, clients, nil
}
// boot all drivers in k
clients, err := ensureBooted(ctx, nodes, allIndexes(len(nodes)), pw)
if err != nil {
return nil, nil, err
}
eg, ctx := errgroup.WithContext(ctx)
workers := make([][]*client.WorkerInfo, len(clients))
for i, c := range clients {
if c == nil {
continue
}
func(i int) {
eg.Go(func() error {
ww, err := clients[i].ListWorkers(ctx)
if err != nil {
return errors.Wrap(err, "listing workers")
}
workers[i] = ww
return nil
})
}(i)
}
if err := eg.Wait(); err != nil {
return nil, nil, err
}
for i, ww := range workers {
for _, w := range ww {
for _, p := range w.Platforms {
p = platforms.Normalize(p)
ps := platforms.Format(p)
if _, ok := availablePlatforms[ps]; !ok {
availablePlatforms[ps] = i
}
}
}
}
return splitToDriverPairs(availablePlatforms, opt), clients, nil
}
func toRepoOnly(in string) (string, error) { func toRepoOnly(in string) (string, error) {
m := map[string]struct{}{} m := map[string]struct{}{}
p := strings.Split(in, ",") p := strings.Split(in, ",")
@ -355,7 +80,7 @@ func toRepoOnly(in string) (string, error) {
return strings.Join(out, ","), nil return strings.Join(out, ","), nil
} }
func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Options, bopts gateway.BuildOpts, configDir string, pw progress.Writer, dl dockerLoadCallback) (solveOpt *client.SolveOpt, release func(), err error) { func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt options.Options, bopts gateway.BuildOpts, configDir string, pw progress.Writer, dl dockerLoadCallback) (solveOpt *client.SolveOpt, release func(), err error) {
nodeDriver := node.Driver nodeDriver := node.Driver
defers := make([]func(), 0, 2) defers := make([]func(), 0, 2)
releaseF := func() { releaseF := func() {
@ -795,20 +520,15 @@ func Invoke(ctx context.Context, cfg ContainerConfig) error {
return err return err
} }
func Build(ctx context.Context, nodes []builder.Node, opt map[string]Options, docker *dockerutil.Client, configDir string, w progress.Writer) (resp map[string]*client.SolveResponse, err error) { func Build(ctx context.Context, nodes []builder.Node, opt map[string]options.Options, docker *dockerutil.Client, configDir string, w progress.Writer) (resp map[string]*client.SolveResponse, err error) {
return BuildWithResultHandler(ctx, nodes, opt, docker, configDir, w, nil, false) return BuildWithResultHandler(ctx, nodes, opt, docker, configDir, w, nil, false)
} }
func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[string]Options, docker *dockerutil.Client, configDir string, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultContext), allowNoOutput bool) (resp map[string]*client.SolveResponse, err error) { func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[string]options.Options, docker *dockerutil.Client, configDir string, w progress.Writer, resultHandleFunc func(driverIndex int, rCtx *ResultContext), allowNoOutput bool) (resp map[string]*client.SolveResponse, err error) {
if len(nodes) == 0 { if len(nodes) == 0 {
return nil, errors.Errorf("driver required for build") return nil, errors.Errorf("driver required for build")
} }
nodes, err = filterAvailableNodes(nodes)
if err != nil {
return nil, errors.Wrapf(err, "no valid drivers found")
}
var noMobyDriver driver.Driver var noMobyDriver driver.Driver
for _, n := range nodes { for _, n := range nodes {
if !n.Driver.IsMobyDriver() { if !n.Driver.IsMobyDriver() {
@ -980,92 +700,99 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
return nil return nil
} }
if pushNames != "" { if pushNames == "" {
progress.Write(pw, fmt.Sprintf("merging manifest list %s", pushNames), func() error { return nil
descs := make([]specs.Descriptor, 0, len(res)) }
for _, r := range res { progress.Write(pw, fmt.Sprintf("merging manifest list %s", pushNames), func() error {
s, ok := r.ExporterResponse[exptypes.ExporterImageDigestKey] descs := make([]specs.Descriptor, 0, len(res))
if ok {
descs = append(descs, specs.Descriptor{ for _, r := range res {
Digest: digest.Digest(s), s, ok := r.ExporterResponse[exptypes.ExporterImageDigestKey]
MediaType: images.MediaTypeDockerSchema2ManifestList, if ok {
Size: -1, descs = append(descs, specs.Descriptor{
}) Digest: digest.Digest(s),
} MediaType: images.MediaTypeDockerSchema2ManifestList,
Size: -1,
})
} }
if len(descs) > 0 { }
var imageopt imagetools.Opt
for _, dp := range dps {
imageopt = nodes[dp.driverIndex].ImageOpt
break
}
names := strings.Split(pushNames, ",")
if insecurePush { if len(descs) == 0 {
insecureTrue := true return nil
httpTrue := true }
nn, err := reference.ParseNormalizedNamed(names[0])
if err != nil {
return err
}
imageopt.RegistryConfig = map[string]resolver.RegistryConfig{
reference.Domain(nn): {
Insecure: &insecureTrue,
PlainHTTP: &httpTrue,
},
}
}
itpull := imagetools.New(imageopt) var imageopt imagetools.Opt
for _, dp := range dps {
imageopt = nodes[dp.driverIndex].ImageOpt
break
}
names := strings.Split(pushNames, ",")
ref, err := reference.ParseNormalizedNamed(names[0]) if insecurePush {
if err != nil { insecureTrue := true
return err httpTrue := true
} nn, err := reference.ParseNormalizedNamed(names[0])
ref = reference.TagNameOnly(ref) if err != nil {
return err
}
imageopt.RegistryConfig = map[string]resolver.RegistryConfig{
reference.Domain(nn): {
Insecure: &insecureTrue,
PlainHTTP: &httpTrue,
},
}
}
srcs := make([]*imagetools.Source, len(descs)) itpull := imagetools.New(imageopt)
for i, desc := range descs {
srcs[i] = &imagetools.Source{
Desc: desc,
Ref: ref,
}
}
dt, desc, err := itpull.Combine(ctx, srcs) ref, err := reference.ParseNormalizedNamed(names[0])
if err != nil { if err != nil {
return err return err
} }
if opt.ImageIDFile != "" { ref = reference.TagNameOnly(ref)
if err := os.WriteFile(opt.ImageIDFile, []byte(desc.Digest), 0644); err != nil {
return err
}
}
itpush := imagetools.New(imageopt) srcs := make([]*imagetools.Source, len(descs))
for i, desc := range descs {
srcs[i] = &imagetools.Source{
Desc: desc,
Ref: ref,
}
}
for _, n := range names { dt, desc, err := itpull.Combine(ctx, srcs)
nn, err := reference.ParseNormalizedNamed(n) if err != nil {
if err != nil { return err
return err }
} if opt.ImageIDFile != "" {
if err := itpush.Push(ctx, nn, desc, dt); err != nil { if err := os.WriteFile(opt.ImageIDFile, []byte(desc.Digest), 0644); err != nil {
return err return err
} }
} }
respMu.Lock() itpush := imagetools.New(imageopt)
resp[k] = &client.SolveResponse{
ExporterResponse: map[string]string{ for _, n := range names {
"containerimage.digest": desc.Digest.String(), nn, err := reference.ParseNormalizedNamed(n)
}, if err != nil {
} return err
respMu.Unlock()
} }
return nil if err := itpush.Push(ctx, nn, desc, dt); err != nil {
}) return err
} }
}
respMu.Lock()
resp[k] = &client.SolveResponse{
ExporterResponse: map[string]string{
"containerimage.digest": desc.Digest.String(),
},
}
respMu.Unlock()
return nil
})
return nil return nil
}) })
@ -1157,22 +884,23 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
return nil, err return nil, err
} }
var reqErr *errdefs.UnsupportedSubrequestError var reqErr *errdefs.UnsupportedSubrequestError
if !isFallback { if isFallback {
if errors.As(err, &reqErr) { return nil, err
switch reqErr.Name { }
case "frontend.outline", "frontend.targets": if errors.As(err, &reqErr) {
isFallback = true switch reqErr.Name {
origErr = err case "frontend.outline", "frontend.targets":
continue
}
return nil, err
}
// buildkit v0.8 vendored in Docker 20.10 does not support typed errors
if strings.Contains(err.Error(), "unsupported request frontend.outline") || strings.Contains(err.Error(), "unsupported request frontend.targets") {
isFallback = true isFallback = true
origErr = err origErr = err
continue continue
} }
return nil, err
}
// buildkit v0.8 vendored in Docker 20.10 does not support typed errors
if strings.Contains(err.Error(), "unsupported request frontend.outline") || strings.Contains(err.Error(), "unsupported request frontend.targets") {
isFallback = true
origErr = err
continue
} }
return nil, err return nil, err
} }
@ -1199,40 +927,44 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s
} }
node := nodes[dp.driverIndex].Driver node := nodes[dp.driverIndex].Driver
if node.IsMobyDriver() { if !node.IsMobyDriver() {
for _, e := range so.Exports { return nil
if e.Type == "moby" && e.Attrs["push"] != "" { }
if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
pushNames = e.Attrs["name"] for _, e := range so.Exports {
if pushNames == "" { if e.Type == "moby" && e.Attrs["push"] != "" {
return errors.Errorf("tag is needed when pushing to registry") if ok, _ := strconv.ParseBool(e.Attrs["push"]); !ok {
} continue
pw := progress.ResetTime(pw) }
pushList := strings.Split(pushNames, ",") pushNames = e.Attrs["name"]
for _, name := range pushList { if pushNames == "" {
if err := progress.Wrap(fmt.Sprintf("pushing %s with docker", name), pw.Write, func(l progress.SubLogger) error { return errors.Errorf("tag is needed when pushing to registry")
return pushWithMoby(ctx, node, name, l) }
}); err != nil { pw := progress.ResetTime(pw)
return err pushList := strings.Split(pushNames, ",")
} for _, name := range pushList {
} if err := progress.Wrap(fmt.Sprintf("pushing %s with docker", name), pw.Write, func(l progress.SubLogger) error {
remoteDigest, err := remoteDigestWithMoby(ctx, node, pushList[0]) return pushWithMoby(ctx, node, name, l)
if err == nil && remoteDigest != "" { }); err != nil {
// old daemons might not have containerimage.config.digest set return err
// in response so use containerimage.digest value for it if available }
if _, ok := rr.ExporterResponse[exptypes.ExporterImageConfigDigestKey]; !ok { }
if v, ok := rr.ExporterResponse[exptypes.ExporterImageDigestKey]; ok { remoteDigest, err := remoteDigestWithMoby(ctx, node, pushList[0])
rr.ExporterResponse[exptypes.ExporterImageConfigDigestKey] = v if err == nil && remoteDigest != "" {
} // old daemons might not have containerimage.config.digest set
} // in response so use containerimage.digest value for it if available
rr.ExporterResponse[exptypes.ExporterImageDigestKey] = remoteDigest if _, ok := rr.ExporterResponse[exptypes.ExporterImageConfigDigestKey]; !ok {
} else if err != nil { if v, ok := rr.ExporterResponse[exptypes.ExporterImageDigestKey]; ok {
return err rr.ExporterResponse[exptypes.ExporterImageConfigDigestKey] = v
} }
} }
rr.ExporterResponse[exptypes.ExporterImageDigestKey] = remoteDigest
} else if err != nil {
return err
} }
} }
} }
return nil return nil
}) })
@ -1372,7 +1104,7 @@ func createTempDockerfile(r io.Reader) (string, error) {
return dir, err return dir, err
} }
func LoadInputs(ctx context.Context, d driver.Driver, inp Inputs, pw progress.Writer, target *client.SolveOpt) (func(), error) { func LoadInputs(ctx context.Context, d driver.Driver, inp options.Inputs, pw progress.Writer, target *client.SolveOpt) (func(), error) {
if inp.ContextPath == "" { if inp.ContextPath == "" {
return nil, errors.New("please specify build context (e.g. \".\" for the current directory)") return nil, errors.New("please specify build context (e.g. \".\" for the current directory)")
} }
@ -1606,27 +1338,30 @@ func waitContextDeps(ctx context.Context, index int, results *waitmap.Map, so *c
} }
delete(so.FrontendAttrs, v) delete(so.FrontendAttrs, v)
} }
if rr.Ref != nil {
st, err := rr.Ref.ToState() if rr.Ref == nil {
continue
}
st, err := rr.Ref.ToState()
if err != nil {
return err
}
so.FrontendInputs[k] = st
so.FrontendAttrs[v] = "input:" + k
metadata := make(map[string][]byte)
if dt, ok := rr.Metadata[exptypes.ExporterImageConfigKey]; ok {
metadata[exptypes.ExporterImageConfigKey] = dt
}
if dt, ok := rr.Metadata[exptypes.ExporterBuildInfo]; ok {
metadata[exptypes.ExporterBuildInfo] = dt
}
if len(metadata) > 0 {
dt, err := json.Marshal(metadata)
if err != nil { if err != nil {
return err return err
} }
so.FrontendInputs[k] = st so.FrontendAttrs["input-metadata:"+k] = string(dt)
so.FrontendAttrs[v] = "input:" + k
metadata := make(map[string][]byte)
if dt, ok := rr.Metadata[exptypes.ExporterImageConfigKey]; ok {
metadata[exptypes.ExporterImageConfigKey] = dt
}
if dt, ok := rr.Metadata[exptypes.ExporterBuildInfo]; ok {
metadata[exptypes.ExporterBuildInfo] = dt
}
if len(metadata) > 0 {
dt, err := json.Marshal(metadata)
if err != nil {
return err
}
so.FrontendAttrs["input-metadata:"+k] = string(dt)
}
} }
} }
return nil return nil
@ -1712,7 +1447,7 @@ func tryNodeIdentifier(configDir string) (out string) {
return return
} }
func noPrintFunc(opt map[string]Options) bool { func noPrintFunc(opt map[string]options.Options) bool {
for _, v := range opt { for _, v := range opt {
if v.PrintFunc != nil { if v.PrintFunc != nil {
return false return false

@ -0,0 +1,230 @@
package build
import (
"context"
_ "crypto/sha256" // ensure digests can be computed
"github.com/containerd/containerd/platforms"
"github.com/docker/buildx/builder"
"github.com/docker/buildx/options"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/util/progress"
"github.com/moby/buildkit/client"
gateway "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/util/tracing"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
)
type driverPair struct {
driverIndex int
platforms []specs.Platform
so *client.SolveOpt
bopts gateway.BuildOpts
}
func driverIndexes(m map[string][]driverPair) []int {
out := make([]int, 0, len(m))
visited := map[int]struct{}{}
for _, dp := range m {
for _, d := range dp {
if _, ok := visited[d.driverIndex]; ok {
continue
}
visited[d.driverIndex] = struct{}{}
out = append(out, d.driverIndex)
}
}
return out
}
func allIndexes(l int) []int {
out := make([]int, 0, l)
for i := 0; i < l; i++ {
out = append(out, i)
}
return out
}
func ensureBooted(ctx context.Context, nodes []builder.Node, idxs []int, pw progress.Writer) ([]*client.Client, error) {
clients := make([]*client.Client, len(nodes))
baseCtx := ctx
eg, ctx := errgroup.WithContext(ctx)
for _, i := range idxs {
func(i int) {
eg.Go(func() error {
c, err := driver.Boot(ctx, baseCtx, nodes[i].Driver, pw)
if err != nil {
return err
}
clients[i] = c
return nil
})
}(i)
}
if err := eg.Wait(); err != nil {
return nil, err
}
return clients, nil
}
func splitToDriverPairs(availablePlatforms map[string]int, opt map[string]options.Options) map[string][]driverPair {
m := map[string][]driverPair{}
for k, opt := range opt {
mm := map[int][]specs.Platform{}
for _, p := range opt.Platforms {
k := platforms.Format(p)
idx := availablePlatforms[k] // default 0
pp := mm[idx]
pp = append(pp, p)
mm[idx] = pp
}
// if no platform is specified, use first driver
if len(mm) == 0 {
mm[0] = nil
}
dps := make([]driverPair, 0, 2)
for idx, pp := range mm {
dps = append(dps, driverPair{driverIndex: idx, platforms: pp})
}
m[k] = dps
}
return m
}
func resolveDrivers(ctx context.Context, nodes []builder.Node, opt map[string]options.Options, pw progress.Writer) (map[string][]driverPair, []*client.Client, error) {
dps, clients, err := resolveDriversBase(ctx, nodes, opt, pw)
if err != nil {
return nil, nil, err
}
bopts := make([]gateway.BuildOpts, len(clients))
span, ctx := tracing.StartSpan(ctx, "load buildkit capabilities", trace.WithSpanKind(trace.SpanKindInternal))
eg, ctx := errgroup.WithContext(ctx)
for i, c := range clients {
if c == nil {
continue
}
func(i int, c *client.Client) {
eg.Go(func() error {
clients[i].Build(ctx, client.SolveOpt{}, "buildx", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
bopts[i] = c.BuildOpts()
return nil, nil
}, nil)
return nil
})
}(i, c)
}
err = eg.Wait()
tracing.FinishWithError(span, err)
if err != nil {
return nil, nil, err
}
for key := range dps {
for i, dp := range dps[key] {
dps[key][i].bopts = bopts[dp.driverIndex]
}
}
return dps, clients, nil
}
func resolveDriversBase(ctx context.Context, nodes []builder.Node, opt map[string]options.Options, pw progress.Writer) (map[string][]driverPair, []*client.Client, error) {
availablePlatforms := map[string]int{}
for i, node := range nodes {
for _, p := range node.Platforms {
availablePlatforms[platforms.Format(p)] = i
}
}
undetectedPlatform := false
allPlatforms := map[string]int{}
for _, opt := range opt {
for _, p := range opt.Platforms {
k := platforms.Format(p)
allPlatforms[k] = -1
if _, ok := availablePlatforms[k]; !ok {
undetectedPlatform = true
}
}
}
// fast path
if len(nodes) == 1 || len(allPlatforms) == 0 {
m := map[string][]driverPair{}
for k, opt := range opt {
m[k] = []driverPair{{driverIndex: 0, platforms: opt.Platforms}}
}
clients, err := ensureBooted(ctx, nodes, driverIndexes(m), pw)
if err != nil {
return nil, nil, err
}
return m, clients, nil
}
// map based on existing platforms
if !undetectedPlatform {
m := splitToDriverPairs(availablePlatforms, opt)
clients, err := ensureBooted(ctx, nodes, driverIndexes(m), pw)
if err != nil {
return nil, nil, err
}
return m, clients, nil
}
// boot all drivers in k
clients, err := ensureBooted(ctx, nodes, allIndexes(len(nodes)), pw)
if err != nil {
return nil, nil, err
}
eg, ctx := errgroup.WithContext(ctx)
workers := make([][]*client.WorkerInfo, len(clients))
for i, c := range clients {
if c == nil {
continue
}
func(i int) {
eg.Go(func() error {
ww, err := clients[i].ListWorkers(ctx)
if err != nil {
return errors.Wrap(err, "listing workers")
}
workers[i] = ww
return nil
})
}(i)
}
if err := eg.Wait(); err != nil {
return nil, nil, err
}
for i, ww := range workers {
for _, w := range ww {
for _, p := range w.Platforms {
p = platforms.Normalize(p)
ps := platforms.Format(p)
if _, ok := availablePlatforms[ps]; !ok {
availablePlatforms[ps] = i
}
}
}
}
return splitToDriverPairs(availablePlatforms, opt), clients, nil
}

@ -34,6 +34,24 @@ func (b *Builder) Nodes() []Node {
return b.nodes return b.nodes
} }
// AvailableNodes returns only nodes that are available.
func (b *Builder) AvailableNodes() ([]Node, error) {
out := make([]Node, 0, len(b.nodes))
err := errors.Errorf("no drivers found")
for _, n := range b.nodes {
if n.Err == nil && n.Driver != nil {
out = append(out, n)
}
if n.Err != nil {
err = n.Err
}
}
if len(out) > 0 {
return out, nil
}
return nil, err
}
// LoadNodes loads and returns nodes for this builder. // LoadNodes loads and returns nodes for this builder.
// TODO: this should be a method on a Node object and lazy load data for each driver. // TODO: this should be a method on a Node object and lazy load data for each driver.
func (b *Builder) LoadNodes(ctx context.Context, withData bool) (_ []Node, err error) { func (b *Builder) LoadNodes(ctx context.Context, withData bool) (_ []Node, err error) {
@ -163,6 +181,15 @@ func (b *Builder) LoadNodes(ctx context.Context, withData bool) (_ []Node, err e
return b.nodes, nil return b.nodes, nil
} }
func (b *Builder) LoadAvailableNodes(ctx context.Context, withData bool) (_ []Node, err error) {
_, err = b.LoadNodes(ctx, withData)
if err != nil {
return nil, err
}
return b.AvailableNodes()
}
func (n *Node) loadData(ctx context.Context) error { func (n *Node) loadData(ctx context.Context) error {
if n.Driver == nil { if n.Driver == nil {
return nil return nil

@ -114,7 +114,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
if err = updateLastActivity(dockerCli, b.NodeGroup); err != nil { if err = updateLastActivity(dockerCli, b.NodeGroup); err != nil {
return errors.Wrapf(err, "failed to update builder last activity time") return errors.Wrapf(err, "failed to update builder last activity time")
} }
nodes, err = b.LoadNodes(ctx, false) nodes, err = b.LoadAvailableNodes(ctx, false)
if err != nil { if err != nil {
return err return err
} }

@ -16,6 +16,8 @@ import (
"github.com/containerd/console" "github.com/containerd/console"
"github.com/docker/buildx/build" "github.com/docker/buildx/build"
"github.com/docker/buildx/options"
"github.com/docker/buildx/builder" "github.com/docker/buildx/builder"
"github.com/docker/buildx/monitor" "github.com/docker/buildx/monitor"
"github.com/docker/buildx/store" "github.com/docker/buildx/store"
@ -133,8 +135,8 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
return err return err
} }
opts := build.Options{ opts := options.Options{
Inputs: build.Inputs{ Inputs: options.Inputs{
ContextPath: in.contextPath, ContextPath: in.contextPath,
DockerfilePath: in.dockerfileName, DockerfilePath: in.dockerfileName,
InStream: os.Stdin, InStream: os.Stdin,
@ -266,12 +268,12 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
if err = updateLastActivity(dockerCli, b.NodeGroup); err != nil { if err = updateLastActivity(dockerCli, b.NodeGroup); err != nil {
return errors.Wrapf(err, "failed to update builder last activity time") return errors.Wrapf(err, "failed to update builder last activity time")
} }
nodes, err := b.LoadNodes(ctx, false) nodes, err := b.LoadAvailableNodes(ctx, false)
if err != nil { if err != nil {
return err return err
} }
imageID, res, err := buildTargets(ctx, dockerCli, nodes, map[string]build.Options{defaultTargetName: opts}, in.progress, in.metadataFile, in.invoke != "") imageID, res, err := buildTargets(ctx, dockerCli, nodes, map[string]options.Options{defaultTargetName: opts}, in.progress, in.metadataFile, in.invoke != "")
err = wrapBuildError(err, false) err = wrapBuildError(err, false)
if err != nil { if err != nil {
return err return err
@ -288,7 +290,7 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
return errors.Errorf("failed to configure terminal: %v", err) return errors.Errorf("failed to configure terminal: %v", err)
} }
err = monitor.RunMonitor(ctx, cfg, func(ctx context.Context) (*build.ResultContext, error) { err = monitor.RunMonitor(ctx, cfg, func(ctx context.Context) (*build.ResultContext, error) {
_, rr, err := buildTargets(ctx, dockerCli, nodes, map[string]build.Options{defaultTargetName: opts}, in.progress, in.metadataFile, true) _, rr, err := buildTargets(ctx, dockerCli, nodes, map[string]options.Options{defaultTargetName: opts}, in.progress, in.metadataFile, true)
return rr, err return rr, err
}, io.NopCloser(os.Stdin), nopCloser{os.Stdout}, nopCloser{os.Stderr}) }, io.NopCloser(os.Stdin), nopCloser{os.Stdout}, nopCloser{os.Stderr})
if err != nil { if err != nil {
@ -309,7 +311,7 @@ type nopCloser struct {
func (c nopCloser) Close() error { return nil } func (c nopCloser) Close() error { return nil }
func buildTargets(ctx context.Context, dockerCli command.Cli, nodes []builder.Node, opts map[string]build.Options, progressMode string, metadataFile string, allowNoOutput bool) (imageID string, res *build.ResultContext, err error) { func buildTargets(ctx context.Context, dockerCli command.Cli, nodes []builder.Node, opts map[string]options.Options, progressMode string, metadataFile string, allowNoOutput bool) (imageID string, res *build.ResultContext, err error) {
ctx2, cancel := context.WithCancel(context.TODO()) ctx2, cancel := context.WithCancel(context.TODO())
defer cancel() defer cancel()
@ -623,11 +625,11 @@ func listToMap(values []string, defaultEnv bool) map[string]string {
return result return result
} }
func parseContextNames(values []string) (map[string]build.NamedContext, error) { func parseContextNames(values []string) (map[string]options.NamedContext, error) {
if len(values) == 0 { if len(values) == 0 {
return nil, nil return nil, nil
} }
result := make(map[string]build.NamedContext, len(values)) result := make(map[string]options.NamedContext, len(values))
for _, value := range values { for _, value := range values {
kv := strings.SplitN(value, "=", 2) kv := strings.SplitN(value, "=", 2)
if len(kv) != 2 { if len(kv) != 2 {
@ -638,12 +640,12 @@ func parseContextNames(values []string) (map[string]build.NamedContext, error) {
return nil, errors.Wrapf(err, "invalid context name %s", kv[0]) return nil, errors.Wrapf(err, "invalid context name %s", kv[0])
} }
name := strings.TrimSuffix(reference.FamiliarString(named), ":latest") name := strings.TrimSuffix(reference.FamiliarString(named), ":latest")
result[name] = build.NamedContext{Path: kv[1]} result[name] = options.NamedContext{Path: kv[1]}
} }
return result, nil return result, nil
} }
func parsePrintFunc(str string) (*build.PrintFunc, error) { func parsePrintFunc(str string) (*options.PrintFunc, error) {
if str == "" { if str == "" {
return nil, nil return nil, nil
} }
@ -652,7 +654,7 @@ func parsePrintFunc(str string) (*build.PrintFunc, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
f := &build.PrintFunc{} f := &options.PrintFunc{}
for _, field := range fields { for _, field := range fields {
parts := strings.SplitN(field, "=", 2) parts := strings.SplitN(field, "=", 2)
if len(parts) == 2 { if len(parts) == 2 {

@ -38,15 +38,10 @@ func runDiskUsage(dockerCli command.Cli, opts duOptions) error {
return err return err
} }
nodes, err := b.LoadNodes(ctx, false) nodes, err := b.LoadAvailableNodes(ctx, false)
if err != nil { if err != nil {
return err return err
} }
for _, node := range nodes {
if node.Err != nil {
return node.Err
}
}
out := make([][]*client.UsageInfo, len(nodes)) out := make([][]*client.UsageInfo, len(nodes))

@ -6,14 +6,14 @@ import (
"log" "log"
"os" "os"
"github.com/docker/buildx/build" "github.com/docker/buildx/options"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
"github.com/moby/buildkit/frontend/subrequests" "github.com/moby/buildkit/frontend/subrequests"
"github.com/moby/buildkit/frontend/subrequests/outline" "github.com/moby/buildkit/frontend/subrequests/outline"
"github.com/moby/buildkit/frontend/subrequests/targets" "github.com/moby/buildkit/frontend/subrequests/targets"
) )
func printResult(f *build.PrintFunc, res map[string]string) error { func printResult(f *options.PrintFunc, res map[string]string) error {
switch f.Name { switch f.Name {
case "outline": case "outline":
return printValue(outline.PrintOutline, outline.SubrequestsOutlineDefinition.Version, f.Format, res) return printValue(outline.PrintOutline, outline.SubrequestsOutlineDefinition.Version, f.Format, res)

@ -59,15 +59,10 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
return err return err
} }
nodes, err := b.LoadNodes(ctx, false) nodes, err := b.LoadAvailableNodes(ctx, false)
if err != nil { if err != nil {
return err return err
} }
for _, node := range nodes {
if node.Err != nil {
return node.Err
}
}
ch := make(chan client.UsageInfo) ch := make(chan client.UsageInfo)
printed := make(chan struct{}) printed := make(chan struct{})

@ -0,0 +1,61 @@
package options
import (
_ "crypto/sha256" // ensure digests can be computed
"io"
"github.com/docker/cli/opts"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/util/entitlements"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
type Options struct {
Inputs Inputs
Allow []entitlements.Entitlement
Attests map[string]*string
BuildArgs map[string]string
CacheFrom []client.CacheOptionsEntry
CacheTo []client.CacheOptionsEntry
CgroupParent string
Exports []client.ExportEntry
ExtraHosts []string
ImageIDFile string
Labels map[string]string
NetworkMode string
NoCache bool
NoCacheFilter []string
Platforms []specs.Platform
Pull bool
Session []session.Attachable
ShmSize opts.MemBytes
Tags []string
Target string
Ulimits *opts.UlimitOpt
// Linked marks this target as exclusively linked (not requested by the user).
Linked bool
PrintFunc *PrintFunc
}
type PrintFunc struct {
Name string
Format string
}
type Inputs struct {
ContextPath string
DockerfilePath string
InStream io.Reader
ContextState *llb.State
DockerfileInline string
NamedContexts map[string]NamedContext
}
type NamedContext struct {
Path string
State *llb.State
}
Loading…
Cancel
Save