github.com/pdmccormick/importable-docker-buildx@v0.0.0-20240426161518-e47091289030/driver/kubernetes/factory.go (about)

     1  package kubernetes
     2  
     3  import (
     4  	"context"
     5  	"strconv"
     6  	"strings"
     7  
     8  	corev1 "k8s.io/api/core/v1"
     9  
    10  	"github.com/docker/buildx/driver"
    11  	"github.com/docker/buildx/driver/bkimage"
    12  	"github.com/docker/buildx/driver/kubernetes/manifest"
    13  	"github.com/docker/buildx/driver/kubernetes/podchooser"
    14  	dockerclient "github.com/docker/docker/client"
    15  	"github.com/pkg/errors"
    16  	"k8s.io/client-go/kubernetes"
    17  )
    18  
    19  const prioritySupported = 40
    20  const priorityUnsupported = 80
    21  
    22  func init() {
    23  	driver.Register(&factory{})
    24  }
    25  
    26  type factory struct {
    27  }
    28  
    29  func (*factory) Name() string {
    30  	return DriverName
    31  }
    32  
    33  func (*factory) Usage() string {
    34  	return DriverName
    35  }
    36  
    37  func (*factory) Priority(ctx context.Context, endpoint string, api dockerclient.APIClient, dialMeta map[string][]string) int {
    38  	if api == nil {
    39  		return priorityUnsupported
    40  	}
    41  	return prioritySupported
    42  }
    43  
    44  func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver, error) {
    45  	if cfg.KubeClientConfig == nil {
    46  		return nil, errors.Errorf("%s driver requires kubernetes API access", DriverName)
    47  	}
    48  	deploymentName, err := buildxNameToDeploymentName(cfg.Name)
    49  	if err != nil {
    50  		return nil, err
    51  	}
    52  	namespace, _, err := cfg.KubeClientConfig.Namespace()
    53  	if err != nil {
    54  		return nil, errors.Wrap(err, "cannot determine Kubernetes namespace, specify manually")
    55  	}
    56  	restClientConfig, err := cfg.KubeClientConfig.ClientConfig()
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  	clientset, err := kubernetes.NewForConfig(restClientConfig)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	d := &Driver{
    66  		factory:    f,
    67  		InitConfig: cfg,
    68  		clientset:  clientset,
    69  	}
    70  
    71  	deploymentOpt, loadbalance, namespace, defaultLoad, err := f.processDriverOpts(deploymentName, namespace, cfg)
    72  	if nil != err {
    73  		return nil, err
    74  	}
    75  
    76  	d.defaultLoad = defaultLoad
    77  
    78  	d.deployment, d.configMaps, err = manifest.NewDeployment(deploymentOpt)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	d.minReplicas = deploymentOpt.Replicas
    84  
    85  	d.deploymentClient = clientset.AppsV1().Deployments(namespace)
    86  	d.podClient = clientset.CoreV1().Pods(namespace)
    87  	d.configMapClient = clientset.CoreV1().ConfigMaps(namespace)
    88  
    89  	switch loadbalance {
    90  	case LoadbalanceSticky:
    91  		d.podChooser = &podchooser.StickyPodChooser{
    92  			Key:        cfg.ContextPathHash,
    93  			PodClient:  d.podClient,
    94  			Deployment: d.deployment,
    95  		}
    96  	case LoadbalanceRandom:
    97  		d.podChooser = &podchooser.RandomPodChooser{
    98  			PodClient:  d.podClient,
    99  			Deployment: d.deployment,
   100  		}
   101  	}
   102  	return d, nil
   103  }
   104  
   105  func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg driver.InitConfig) (*manifest.DeploymentOpt, string, string, bool, error) {
   106  	deploymentOpt := &manifest.DeploymentOpt{
   107  		Name:          deploymentName,
   108  		Image:         bkimage.DefaultImage,
   109  		Replicas:      1,
   110  		BuildkitFlags: cfg.BuildkitdFlags,
   111  		Rootless:      false,
   112  		Platforms:     cfg.Platforms,
   113  		ConfigFiles:   cfg.Files,
   114  	}
   115  
   116  	defaultLoad := false
   117  
   118  	deploymentOpt.Qemu.Image = bkimage.QemuImage
   119  
   120  	loadbalance := LoadbalanceSticky
   121  	var err error
   122  
   123  	for k, v := range cfg.DriverOpts {
   124  		switch k {
   125  		case "image":
   126  			if v != "" {
   127  				deploymentOpt.Image = v
   128  			}
   129  		case "namespace":
   130  			namespace = v
   131  		case "replicas":
   132  			deploymentOpt.Replicas, err = strconv.Atoi(v)
   133  			if err != nil {
   134  				return nil, "", "", false, err
   135  			}
   136  		case "requests.cpu":
   137  			deploymentOpt.RequestsCPU = v
   138  		case "requests.memory":
   139  			deploymentOpt.RequestsMemory = v
   140  		case "requests.ephemeral-storage":
   141  			deploymentOpt.RequestsEphemeralStorage = v
   142  		case "limits.cpu":
   143  			deploymentOpt.LimitsCPU = v
   144  		case "limits.memory":
   145  			deploymentOpt.LimitsMemory = v
   146  		case "limits.ephemeral-storage":
   147  			deploymentOpt.LimitsEphemeralStorage = v
   148  		case "rootless":
   149  			deploymentOpt.Rootless, err = strconv.ParseBool(v)
   150  			if err != nil {
   151  				return nil, "", "", false, err
   152  			}
   153  			if _, isImage := cfg.DriverOpts["image"]; !isImage {
   154  				deploymentOpt.Image = bkimage.DefaultRootlessImage
   155  			}
   156  		case "schedulername":
   157  			deploymentOpt.SchedulerName = v
   158  		case "serviceaccount":
   159  			deploymentOpt.ServiceAccountName = v
   160  		case "nodeselector":
   161  			deploymentOpt.NodeSelector, err = splitMultiValues(v, ",", "=")
   162  			if err != nil {
   163  				return nil, "", "", false, errors.Wrap(err, "cannot parse node selector")
   164  			}
   165  		case "annotations":
   166  			deploymentOpt.CustomAnnotations, err = splitMultiValues(v, ",", "=")
   167  			if err != nil {
   168  				return nil, "", "", false, errors.Wrap(err, "cannot parse annotations")
   169  			}
   170  		case "labels":
   171  			deploymentOpt.CustomLabels, err = splitMultiValues(v, ",", "=")
   172  			if err != nil {
   173  				return nil, "", "", false, errors.Wrap(err, "cannot parse labels")
   174  			}
   175  		case "tolerations":
   176  			ts := strings.Split(v, ";")
   177  			deploymentOpt.Tolerations = []corev1.Toleration{}
   178  			for i := range ts {
   179  				kvs := strings.Split(ts[i], ",")
   180  
   181  				t := corev1.Toleration{}
   182  
   183  				for j := range kvs {
   184  					kv := strings.Split(kvs[j], "=")
   185  					if len(kv) == 2 {
   186  						switch kv[0] {
   187  						case "key":
   188  							t.Key = kv[1]
   189  						case "operator":
   190  							t.Operator = corev1.TolerationOperator(kv[1])
   191  						case "value":
   192  							t.Value = kv[1]
   193  						case "effect":
   194  							t.Effect = corev1.TaintEffect(kv[1])
   195  						case "tolerationSeconds":
   196  							c, err := strconv.Atoi(kv[1])
   197  							if nil != err {
   198  								return nil, "", "", false, err
   199  							}
   200  							c64 := int64(c)
   201  							t.TolerationSeconds = &c64
   202  						default:
   203  							return nil, "", "", false, errors.Errorf("invalid tolaration %q", v)
   204  						}
   205  					}
   206  				}
   207  
   208  				deploymentOpt.Tolerations = append(deploymentOpt.Tolerations, t)
   209  			}
   210  		case "loadbalance":
   211  			switch v {
   212  			case LoadbalanceSticky:
   213  			case LoadbalanceRandom:
   214  			default:
   215  				return nil, "", "", false, errors.Errorf("invalid loadbalance %q", v)
   216  			}
   217  			loadbalance = v
   218  		case "qemu.install":
   219  			deploymentOpt.Qemu.Install, err = strconv.ParseBool(v)
   220  			if err != nil {
   221  				return nil, "", "", false, err
   222  			}
   223  		case "qemu.image":
   224  			if v != "" {
   225  				deploymentOpt.Qemu.Image = v
   226  			}
   227  		case "default-load":
   228  			defaultLoad, err = strconv.ParseBool(v)
   229  			if err != nil {
   230  				return nil, "", "", false, err
   231  			}
   232  		default:
   233  			return nil, "", "", false, errors.Errorf("invalid driver option %s for driver %s", k, DriverName)
   234  		}
   235  	}
   236  
   237  	return deploymentOpt, loadbalance, namespace, defaultLoad, nil
   238  }
   239  
   240  func splitMultiValues(in string, itemsep string, kvsep string) (map[string]string, error) {
   241  	kvs := strings.Split(strings.Trim(in, `"`), itemsep)
   242  	s := map[string]string{}
   243  	for i := range kvs {
   244  		kv := strings.Split(kvs[i], kvsep)
   245  		if len(kv) != 2 {
   246  			return nil, errors.Errorf("invalid key-value pair: %s", kvs[i])
   247  		}
   248  		s[kv[0]] = kv[1]
   249  	}
   250  	return s, nil
   251  }
   252  
   253  func (f *factory) AllowsInstances() bool {
   254  	return true
   255  }
   256  
   257  // buildxNameToDeploymentName converts buildx name to Kubernetes Deployment name.
   258  //
   259  // eg. "buildx_buildkit_loving_mendeleev0" -> "loving-mendeleev0"
   260  func buildxNameToDeploymentName(bx string) (string, error) {
   261  	// TODO: commands.util.go should not pass "buildx_buildkit_" prefix to drivers
   262  	s, err := driver.ParseBuilderName(bx)
   263  	if err != nil {
   264  		return "", err
   265  	}
   266  	s = strings.ReplaceAll(s, "_", "-")
   267  	return s, nil
   268  }