From 387cc633c71d1fed57d12907a8e57d390bd911b0 Mon Sep 17 00:00:00 2001 From: Ilya Dmitrichenko Date: Tue, 13 Dec 2022 14:22:06 +0000 Subject: [PATCH] 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 --- build/build.go | 210 ------------------------------------------- build/drivers.go | 230 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+), 210 deletions(-) create mode 100644 build/drivers.go diff --git a/build/build.go b/build/build.go index bf61e4e0..4418d912 100644 --- a/build/build.go +++ b/build/build.go @@ -64,216 +64,6 @@ const ( printFallbackImage = "docker/dockerfile-upstream:1.4-outline@sha256:627443ff4e2d0f635d429cfc1da5388bcd5a70949c38adcd3cd7c4e5df67c73c" ) -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 -} - func toRepoOnly(in string) (string, error) { m := map[string]struct{}{} p := strings.Split(in, ",") diff --git a/build/drivers.go b/build/drivers.go new file mode 100644 index 00000000..60df0129 --- /dev/null +++ b/build/drivers.go @@ -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 +}