@@ -2,6 +2,7 @@ package kubernetes
22
33import (
44 "context"
5+ "encoding/json"
56 "os"
67 "strconv"
78 "strings"
@@ -13,6 +14,7 @@ import (
1314 "github.com/docker/buildx/driver/kubernetes/kubeclient"
1415 "github.com/docker/buildx/driver/kubernetes/manifest"
1516 "github.com/docker/buildx/driver/kubernetes/podchooser"
17+ "github.com/itchyny/gojq"
1618 dockerclient "github.com/moby/moby/client"
1719 "github.com/pkg/errors"
1820 "github.com/sirupsen/logrus"
@@ -123,7 +125,7 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
123125 InitConfig : cfg ,
124126 }
125127
126- deploymentOpt , loadbalance , namespace , defaultLoad , timeout , err := f .processDriverOpts (deploymentName , namespace , cfg )
128+ deploymentOpt , loadbalance , namespace , defaultLoad , timeout , manifestPatch , err := f .processDriverOpts (deploymentName , namespace , cfg )
127129 if nil != err {
128130 return nil , err
129131 }
@@ -136,6 +138,19 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
136138 return nil , err
137139 }
138140
141+ if manifestPatch != "" {
142+ if d .deployment != nil {
143+ if d .deployment , err = applyManifestPatch (d .deployment , manifestPatch ); err != nil {
144+ return nil , errors .Wrap (err , "failed to apply manifest-patch to Deployment" )
145+ }
146+ }
147+ if d .statefulSet != nil {
148+ if d .statefulSet , err = applyManifestPatch (d .statefulSet , manifestPatch ); err != nil {
149+ return nil , errors .Wrap (err , "failed to apply manifest-patch to StatefulSet" )
150+ }
151+ }
152+ }
153+
139154 d .minReplicas = int (deploymentOpt .Replicas )
140155
141156 clients , err := kubeclient .New (restClientConfig , namespace )
@@ -165,7 +180,7 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
165180 return d , nil
166181}
167182
168- func (f * factory ) processDriverOpts (deploymentName string , namespace string , cfg driver.InitConfig ) (* manifest.DeploymentOpt , string , string , bool , time.Duration , error ) {
183+ func (f * factory ) processDriverOpts (deploymentName string , namespace string , cfg driver.InitConfig ) (* manifest.DeploymentOpt , string , string , bool , time.Duration , string , error ) {
169184 deploymentOpt := & manifest.DeploymentOpt {
170185 Name : deploymentName ,
171186 Image : bkimage .DefaultImage ,
@@ -180,6 +195,7 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
180195 timeout := defaultTimeout
181196 deploymentOpt .Qemu .Image = bkimage .QemuImage
182197 loadbalance := LoadbalanceSticky
198+ manifestPatch := ""
183199 var err error
184200
185201 for k , v := range cfg .DriverOpts {
@@ -193,7 +209,7 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
193209 case k == "replicas" :
194210 r , err := strconv .ParseInt (v , 10 , 32 )
195211 if err != nil {
196- return nil , "" , "" , false , 0 , err
212+ return nil , "" , "" , false , 0 , "" , err
197213 }
198214 deploymentOpt .Replicas = int32 (r )
199215 case k == "requests.cpu" :
@@ -213,7 +229,7 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
213229 case k == "rootless" :
214230 deploymentOpt .Rootless , err = strconv .ParseBool (v )
215231 if err != nil {
216- return nil , "" , "" , false , 0 , err
232+ return nil , "" , "" , false , 0 , "" , err
217233 }
218234 if _ , isImage := cfg .DriverOpts ["image" ]; ! isImage {
219235 deploymentOpt .Image = bkimage .DefaultRootlessImage
@@ -225,17 +241,17 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
225241 case k == "nodeselector" :
226242 deploymentOpt .NodeSelector , err = splitMultiValues (v , "," , "=" )
227243 if err != nil {
228- return nil , "" , "" , false , 0 , errors .Wrap (err , "cannot parse node selector" )
244+ return nil , "" , "" , false , 0 , "" , errors .Wrap (err , "cannot parse node selector" )
229245 }
230246 case k == "annotations" :
231247 deploymentOpt .CustomAnnotations , err = splitMultiValues (v , "," , "=" )
232248 if err != nil {
233- return nil , "" , "" , false , 0 , errors .Wrap (err , "cannot parse annotations" )
249+ return nil , "" , "" , false , 0 , "" , errors .Wrap (err , "cannot parse annotations" )
234250 }
235251 case k == "labels" :
236252 deploymentOpt .CustomLabels , err = splitMultiValues (v , "," , "=" )
237253 if err != nil {
238- return nil , "" , "" , false , 0 , errors .Wrap (err , "cannot parse labels" )
254+ return nil , "" , "" , false , 0 , "" , errors .Wrap (err , "cannot parse labels" )
239255 }
240256 case k == "tolerations" :
241257 ts := strings .Split (v , ";" )
@@ -260,29 +276,34 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
260276 case "tolerationSeconds" :
261277 c , err := strconv .Atoi (kv [1 ])
262278 if nil != err {
263- return nil , "" , "" , false , 0 , err
279+ return nil , "" , "" , false , 0 , "" , err
264280 }
265281 c64 := int64 (c )
266282 t .TolerationSeconds = & c64
267283 default :
268- return nil , "" , "" , false , 0 , errors .Errorf ("invalid tolaration %q" , v )
284+ return nil , "" , "" , false , 0 , "" , errors .Errorf ("invalid tolaration %q" , v )
269285 }
270286 }
271287 }
272288
273289 deploymentOpt .Tolerations = append (deploymentOpt .Tolerations , t )
274290 }
291+ case k == "manifest-patch" :
292+ if _ , err := gojq .Parse (v ); err != nil {
293+ return nil , "" , "" , false , 0 , "" , errors .Wrap (err , "invalid manifest-patch jq expression" )
294+ }
295+ manifestPatch = v
275296 case k == "loadbalance" :
276297 switch v {
277298 case LoadbalanceSticky , LoadbalanceRandom :
278299 loadbalance = v
279300 default :
280- return nil , "" , "" , false , 0 , errors .Errorf ("invalid loadbalance %q" , v )
301+ return nil , "" , "" , false , 0 , "" , errors .Errorf ("invalid loadbalance %q" , v )
281302 }
282303 case k == "qemu.install" :
283304 deploymentOpt .Qemu .Install , err = strconv .ParseBool (v )
284305 if err != nil {
285- return nil , "" , "" , false , 0 , err
306+ return nil , "" , "" , false , 0 , "" , err
286307 }
287308 case k == "qemu.image" :
288309 if v != "" {
@@ -295,24 +316,65 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
295316 case k == "default-load" :
296317 defaultLoad , err = strconv .ParseBool (v )
297318 if err != nil {
298- return nil , "" , "" , false , 0 , err
319+ return nil , "" , "" , false , 0 , "" , err
299320 }
300321 case k == "timeout" :
301322 timeout , err = time .ParseDuration (v )
302323 if err != nil {
303- return nil , "" , "" , false , 0 , errors .Wrap (err , "cannot parse timeout" )
324+ return nil , "" , "" , false , 0 , "" , errors .Wrap (err , "cannot parse timeout" )
304325 }
305326 case strings .HasPrefix (k , "env." ):
306327 envName := strings .TrimPrefix (k , "env." )
307328 if envName == "" {
308- return nil , "" , "" , false , 0 , errors .Errorf ("invalid env option %q, expecting env.FOO=bar" , k )
329+ return nil , "" , "" , false , 0 , "" , errors .Errorf ("invalid env option %q, expecting env.FOO=bar" , k )
309330 }
310331 deploymentOpt .Env = append (deploymentOpt .Env , corev1.EnvVar {Name : envName , Value : v })
311332 default :
312- return nil , "" , "" , false , 0 , errors .Errorf ("invalid driver option %s for driver %s" , k , DriverName )
333+ return nil , "" , "" , false , 0 , "" , errors .Errorf ("invalid driver option %s for driver %s" , k , DriverName )
313334 }
314335 }
315- return deploymentOpt , loadbalance , namespace , defaultLoad , timeout , nil
336+ return deploymentOpt , loadbalance , namespace , defaultLoad , timeout , manifestPatch , nil
337+ }
338+
339+ // applyManifestPatch applies a jq expression to patch a Kubernetes manifest object.
340+ // The expression receives the manifest as input and must produce a single object as output.
341+ func applyManifestPatch [T any ](obj * T , patch string ) (* T , error ) {
342+ query , err := gojq .Parse (patch )
343+ if err != nil {
344+ return nil , errors .Wrap (err , "failed to parse manifest-patch expression" )
345+ }
346+ code , err := gojq .Compile (query )
347+ if err != nil {
348+ return nil , errors .Wrap (err , "failed to compile manifest-patch expression" )
349+ }
350+
351+ b , err := json .Marshal (obj )
352+ if err != nil {
353+ return nil , err
354+ }
355+ var input interface {}
356+ if err := json .Unmarshal (b , & input ); err != nil {
357+ return nil , err
358+ }
359+
360+ iter := code .Run (input )
361+ v , ok := iter .Next ()
362+ if ! ok {
363+ return nil , errors .New ("manifest-patch expression produced no output" )
364+ }
365+ if err , ok := v .(error ); ok {
366+ return nil , errors .Wrap (err , "manifest-patch expression failed" )
367+ }
368+
369+ b , err = json .Marshal (v )
370+ if err != nil {
371+ return nil , err
372+ }
373+ var result T
374+ if err := json .Unmarshal (b , & result ); err != nil {
375+ return nil , err
376+ }
377+ return & result , nil
316378}
317379
318380func splitMultiValues (in string , itemsep string , kvsep string ) (map [string ]string , error ) {
0 commit comments