build: move driver helpers to a separate file
This code is very tightly coupled with the rest of the build package, and is not useful elsewhere, so it makes sense to keep it where it is. Separating into another package would require more significant changes to how `driverPair` struct is used in various functions. Signed-off-by: Ilya Dmitrichenko <errordeveloper@gmail.com>pull/1466/head
parent
61adfa576a
commit
387cc633c7
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue