package builder import ( "context" "github.com/docker/buildx/driver" ctxkube "github.com/docker/buildx/driver/kubernetes/context" "github.com/docker/buildx/store" "github.com/docker/buildx/store/storeutil" "github.com/docker/buildx/util/dockerutil" "github.com/docker/buildx/util/imagetools" "github.com/docker/buildx/util/platformutil" "github.com/moby/buildkit/client" "github.com/moby/buildkit/util/grpcerrors" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sync/errgroup" "google.golang.org/grpc/codes" ) type Node struct { store.Node Builder string Driver *driver.DriverHandle DriverInfo *driver.Info Platforms []ocispecs.Platform GCPolicy []client.PruneInfo Labels map[string]string ImageOpt imagetools.Opt ProxyConfig map[string]string Version string Err error } // Nodes returns nodes for this builder. func (b *Builder) Nodes() []Node { return b.nodes } // 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. func (b *Builder) LoadNodes(ctx context.Context, withData bool) (_ []Node, err error) { eg, _ := errgroup.WithContext(ctx) b.nodes = make([]Node, len(b.NodeGroup.Nodes)) defer func() { if b.err == nil && err != nil { b.err = err } }() factory, err := b.Factory(ctx) if err != nil { return nil, err } imageopt, err := b.ImageOpt() if err != nil { return nil, err } for i, n := range b.NodeGroup.Nodes { func(i int, n store.Node) { eg.Go(func() error { node := Node{ Node: n, ProxyConfig: storeutil.GetProxyConfig(b.opts.dockerCli), Platforms: n.Platforms, Builder: b.Name, } defer func() { b.nodes[i] = node }() dockerapi, err := dockerutil.NewClientAPI(b.opts.dockerCli, n.Endpoint) if err != nil { node.Err = err return nil } contextStore := b.opts.dockerCli.ContextStore() var kcc driver.KubeClientConfig kcc, err = ctxkube.ConfigFromEndpoint(n.Endpoint, contextStore) if err != nil { // err is returned if n.Endpoint is non-context name like "unix:///var/run/docker.sock". // try again with name="default". // FIXME(@AkihiroSuda): n should retain real context name. kcc, err = ctxkube.ConfigFromEndpoint("default", contextStore) if err != nil { logrus.Error(err) } } tryToUseKubeConfigInCluster := false if kcc == nil { tryToUseKubeConfigInCluster = true } else { if _, err := kcc.ClientConfig(); err != nil { tryToUseKubeConfigInCluster = true } } if tryToUseKubeConfigInCluster { kccInCluster := driver.KubeClientConfigInCluster{} if _, err := kccInCluster.ClientConfig(); err == nil { logrus.Debug("using kube config in cluster") kcc = kccInCluster } } d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, factory, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.SecurityOpts, n.Platforms, b.opts.contextPathHash) if err != nil { node.Err = err return nil } node.Driver = d node.ImageOpt = imageopt if withData { if err := node.loadData(ctx); err != nil { node.Err = err } } return nil }) }(i, n) } if err := eg.Wait(); err != nil { return nil, err } // TODO: This should be done in the routine loading driver data if withData { kubernetesDriverCount := 0 for _, d := range b.nodes { if d.DriverInfo != nil && len(d.DriverInfo.DynamicNodes) > 0 { kubernetesDriverCount++ } } isAllKubernetesDrivers := len(b.nodes) == kubernetesDriverCount if isAllKubernetesDrivers { var nodes []Node var dynamicNodes []store.Node for _, di := range b.nodes { // dynamic nodes are used in Kubernetes driver. // Kubernetes' pods are dynamically mapped to BuildKit Nodes. if di.DriverInfo != nil && len(di.DriverInfo.DynamicNodes) > 0 { for i := 0; i < len(di.DriverInfo.DynamicNodes); i++ { diClone := di if pl := di.DriverInfo.DynamicNodes[i].Platforms; len(pl) > 0 { diClone.Platforms = pl } nodes = append(nodes, di) } dynamicNodes = append(dynamicNodes, di.DriverInfo.DynamicNodes...) } } // not append (remove the static nodes in the store) b.NodeGroup.Nodes = dynamicNodes b.nodes = nodes b.NodeGroup.Dynamic = true } } return b.nodes, nil } func (n *Node) loadData(ctx context.Context) error { if n.Driver == nil { return nil } info, err := n.Driver.Info(ctx) if err != nil { return err } n.DriverInfo = info if n.DriverInfo.Status == driver.Running { driverClient, err := n.Driver.Client(ctx) if err != nil { return err } workers, err := driverClient.ListWorkers(ctx) if err != nil { return errors.Wrap(err, "listing workers") } for idx, w := range workers { n.Platforms = append(n.Platforms, w.Platforms...) if idx == 0 { n.GCPolicy = w.GCPolicy n.Labels = w.Labels } } n.Platforms = platformutil.Dedupe(n.Platforms) inf, err := driverClient.Info(ctx) if err != nil { if st, ok := grpcerrors.AsGRPCStatus(err); ok && st.Code() == codes.Unimplemented { n.Version, err = n.Driver.Version(ctx) if err != nil { return errors.Wrap(err, "getting version") } } } else { n.Version = inf.BuildkitVersion.Version } } return nil }