github.com/docker/buildx@v0.14.1-0.20240514123050-afcb609966dc/store/nodegroup.go (about)

     1  package store
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/containerd/containerd/platforms"
     8  	"github.com/docker/buildx/util/confutil"
     9  	"github.com/docker/buildx/util/platformutil"
    10  	specs "github.com/opencontainers/image-spec/specs-go/v1"
    11  	"github.com/pkg/errors"
    12  	"github.com/sirupsen/logrus"
    13  )
    14  
    15  type NodeGroup struct {
    16  	Name    string
    17  	Driver  string
    18  	Nodes   []Node
    19  	Dynamic bool
    20  
    21  	// skip the following fields from being saved in the store
    22  	DockerContext bool      `json:"-"`
    23  	LastActivity  time.Time `json:"-"`
    24  }
    25  
    26  type Node struct {
    27  	Name           string
    28  	Endpoint       string
    29  	Platforms      []specs.Platform
    30  	DriverOpts     map[string]string
    31  	BuildkitdFlags []string `json:"Flags"` // keep the field name for backward compatibility
    32  
    33  	Files map[string][]byte
    34  }
    35  
    36  func (ng *NodeGroup) Leave(name string) error {
    37  	if ng.Dynamic {
    38  		return errors.New("dynamic node group does not support Leave")
    39  	}
    40  	i := ng.findNode(name)
    41  	if i == -1 {
    42  		return errors.Errorf("node %q not found for %s", name, ng.Name)
    43  	}
    44  	if len(ng.Nodes) == 1 {
    45  		return errors.Errorf("can not leave last node, do you want to rm instance instead?")
    46  	}
    47  	ng.Nodes = append(ng.Nodes[:i], ng.Nodes[i+1:]...)
    48  	return nil
    49  }
    50  
    51  func (ng *NodeGroup) Update(name, endpoint string, platforms []string, endpointsSet bool, actionAppend bool, buildkitdFlags []string, buildkitdConfigFile string, do map[string]string) error {
    52  	if ng.Dynamic {
    53  		return errors.New("dynamic node group does not support Update")
    54  	}
    55  	i := ng.findNode(name)
    56  	if i == -1 && !actionAppend {
    57  		if len(ng.Nodes) > 0 {
    58  			return errors.Errorf("node %s not found, did you mean to append?", name)
    59  		}
    60  		ng.Nodes = nil
    61  	}
    62  
    63  	pp, err := platformutil.Parse(platforms)
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	var files map[string][]byte
    69  	if buildkitdConfigFile != "" {
    70  		files, err = confutil.LoadConfigFiles(buildkitdConfigFile)
    71  		if err != nil {
    72  			return err
    73  		}
    74  	}
    75  
    76  	if i != -1 {
    77  		n := ng.Nodes[i]
    78  		needsRestart := false
    79  		if endpointsSet {
    80  			n.Endpoint = endpoint
    81  			needsRestart = true
    82  		}
    83  		if len(platforms) > 0 {
    84  			n.Platforms = pp
    85  		}
    86  		if buildkitdFlags != nil {
    87  			n.BuildkitdFlags = buildkitdFlags
    88  			needsRestart = true
    89  		}
    90  		if do != nil {
    91  			n.DriverOpts = do
    92  			needsRestart = true
    93  		}
    94  		if buildkitdConfigFile != "" {
    95  			for k, v := range files {
    96  				n.Files[k] = v
    97  			}
    98  			needsRestart = true
    99  		}
   100  		if needsRestart {
   101  			logrus.Warn("new settings may not be used until builder is restarted")
   102  		}
   103  
   104  		ng.Nodes[i] = n
   105  		return ng.validateDuplicates(endpoint, i)
   106  	}
   107  
   108  	if name == "" {
   109  		name = ng.nextNodeName()
   110  	}
   111  
   112  	name, err = ValidateName(name)
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	n := Node{
   118  		Name:           name,
   119  		Endpoint:       endpoint,
   120  		Platforms:      pp,
   121  		DriverOpts:     do,
   122  		BuildkitdFlags: buildkitdFlags,
   123  		Files:          files,
   124  	}
   125  
   126  	ng.Nodes = append(ng.Nodes, n)
   127  	return ng.validateDuplicates(endpoint, len(ng.Nodes)-1)
   128  }
   129  
   130  func (ng *NodeGroup) Copy() *NodeGroup {
   131  	nodes := make([]Node, len(ng.Nodes))
   132  	for i, node := range ng.Nodes {
   133  		nodes[i] = *node.Copy()
   134  	}
   135  	return &NodeGroup{
   136  		Name:    ng.Name,
   137  		Driver:  ng.Driver,
   138  		Nodes:   nodes,
   139  		Dynamic: ng.Dynamic,
   140  	}
   141  }
   142  
   143  func (n *Node) Copy() *Node {
   144  	platforms := []specs.Platform{}
   145  	copy(platforms, n.Platforms)
   146  	buildkitdFlags := []string{}
   147  	copy(buildkitdFlags, n.BuildkitdFlags)
   148  	driverOpts := map[string]string{}
   149  	for k, v := range n.DriverOpts {
   150  		driverOpts[k] = v
   151  	}
   152  	files := map[string][]byte{}
   153  	for k, v := range n.Files {
   154  		vv := []byte{}
   155  		copy(vv, v)
   156  		files[k] = vv
   157  	}
   158  	return &Node{
   159  		Name:           n.Name,
   160  		Endpoint:       n.Endpoint,
   161  		Platforms:      platforms,
   162  		BuildkitdFlags: buildkitdFlags,
   163  		DriverOpts:     driverOpts,
   164  		Files:          files,
   165  	}
   166  }
   167  
   168  func (ng *NodeGroup) validateDuplicates(ep string, idx int) error {
   169  	i := 0
   170  	for _, n := range ng.Nodes {
   171  		if n.Endpoint == ep {
   172  			i++
   173  		}
   174  	}
   175  	if i > 1 {
   176  		return errors.Errorf("invalid duplicate endpoint %s", ep)
   177  	}
   178  
   179  	m := map[string]struct{}{}
   180  	for _, p := range ng.Nodes[idx].Platforms {
   181  		m[platforms.Format(p)] = struct{}{}
   182  	}
   183  
   184  	for i := range ng.Nodes {
   185  		if i == idx {
   186  			continue
   187  		}
   188  		ng.Nodes[i].Platforms = filterPlatforms(ng.Nodes[i].Platforms, m)
   189  	}
   190  
   191  	return nil
   192  }
   193  
   194  func (ng *NodeGroup) findNode(name string) int {
   195  	for i, n := range ng.Nodes {
   196  		if n.Name == name {
   197  			return i
   198  		}
   199  	}
   200  	return -1
   201  }
   202  
   203  func (ng *NodeGroup) nextNodeName() string {
   204  	i := 0
   205  	for {
   206  		name := fmt.Sprintf("%s%d", ng.Name, i)
   207  		if ii := ng.findNode(name); ii != -1 {
   208  			i++
   209  			continue
   210  		}
   211  		return name
   212  	}
   213  }
   214  
   215  func filterPlatforms(in []specs.Platform, m map[string]struct{}) []specs.Platform {
   216  	out := make([]specs.Platform, 0, len(in))
   217  	for _, p := range in {
   218  		if _, ok := m[platforms.Format(p)]; !ok {
   219  			out = append(out, p)
   220  		}
   221  	}
   222  	return out
   223  }