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 }