Skip to content

Commit 4fe2a88

Browse files
committed
driver/kubernetes: generalize manifest patching
Signed-off-by: abhay1999 <abhaychaurasiya19@gmail.com>
1 parent 0bfadd2 commit 4fe2a88

File tree

4 files changed

+164
-89
lines changed

4 files changed

+164
-89
lines changed

driver/kubernetes/factory.go

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ import (
1717
"github.com/pkg/errors"
1818
"github.com/sirupsen/logrus"
1919
corev1 "k8s.io/api/core/v1"
20-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21-
"k8s.io/apimachinery/pkg/types"
2220
"k8s.io/client-go/rest"
2321
)
2422

@@ -239,6 +237,8 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
239237
if err != nil {
240238
return nil, "", "", false, 0, errors.Wrap(err, "cannot parse labels")
241239
}
240+
case k == "manifest-patch":
241+
deploymentOpt.ManifestPatch = v
242242
case k == "tolerations":
243243
ts := strings.Split(v, ";")
244244
deploymentOpt.Tolerations = []corev1.Toleration{}
@@ -274,46 +274,6 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
274274

275275
deploymentOpt.Tolerations = append(deploymentOpt.Tolerations, t)
276276
}
277-
case k == "ownerrefs":
278-
refs := strings.Split(v, ";")
279-
deploymentOpt.OwnerReferences = []metav1.OwnerReference{}
280-
for i := range refs {
281-
kvs := strings.Split(refs[i], ",")
282-
283-
ref := metav1.OwnerReference{}
284-
285-
for j := range kvs {
286-
kv := strings.SplitN(kvs[j], "=", 2)
287-
if len(kv) == 2 {
288-
switch kv[0] {
289-
case "apiVersion":
290-
ref.APIVersion = kv[1]
291-
case "kind":
292-
ref.Kind = kv[1]
293-
case "name":
294-
ref.Name = kv[1]
295-
case "uid":
296-
ref.UID = types.UID(kv[1])
297-
case "controller":
298-
b, err := strconv.ParseBool(kv[1])
299-
if err != nil {
300-
return nil, "", "", false, 0, errors.Wrap(err, "invalid ownerrefs controller value")
301-
}
302-
ref.Controller = &b
303-
case "blockOwnerDeletion":
304-
b, err := strconv.ParseBool(kv[1])
305-
if err != nil {
306-
return nil, "", "", false, 0, errors.Wrap(err, "invalid ownerrefs blockOwnerDeletion value")
307-
}
308-
ref.BlockOwnerDeletion = &b
309-
default:
310-
return nil, "", "", false, 0, errors.Errorf("invalid ownerrefs key %q", kv[0])
311-
}
312-
}
313-
}
314-
315-
deploymentOpt.OwnerReferences = append(deploymentOpt.OwnerReferences, ref)
316-
}
317277
case k == "loadbalance":
318278
switch v {
319279
case LoadbalanceSticky, LoadbalanceRandom:

driver/kubernetes/factory_test.go

Lines changed: 7 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import (
88
"github.com/docker/buildx/driver/bkimage"
99
"github.com/stretchr/testify/require"
1010
v1 "k8s.io/api/core/v1"
11-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12-
"k8s.io/apimachinery/pkg/types"
1311
"k8s.io/client-go/rest"
1412
)
1513

@@ -271,57 +269,25 @@ func TestFactory_processDriverOpts(t *testing.T) {
271269
)
272270

273271
t.Run(
274-
"OwnerRefs", func(t *testing.T) {
275-
controller := true
276-
blockOwnerDeletion := false
272+
"ManifestPatch", func(t *testing.T) {
273+
patch := `.metadata.ownerReferences = [{apiVersion: "actions.github.com/v1alpha1", kind: "EphemeralRunner", name: "runner-xyz", uid: "b636330d-26b7-417a-8464-c2641438feed", controller: true, blockOwnerDeletion: false}]`
277274
cfg.DriverOpts = map[string]string{
278-
"ownerrefs": "apiVersion=actions.github.com/v1alpha1,kind=EphemeralRunner,name=runner-xyz,uid=b636330d-26b7-417a-8464-c2641438feed,controller=true,blockOwnerDeletion=false",
275+
"manifest-patch": patch,
279276
}
280277
r, _, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
281278
require.NoError(t, err)
282-
require.Equal(t, []metav1.OwnerReference{
283-
{
284-
APIVersion: "actions.github.com/v1alpha1",
285-
Kind: "EphemeralRunner",
286-
Name: "runner-xyz",
287-
UID: types.UID("b636330d-26b7-417a-8464-c2641438feed"),
288-
Controller: &controller,
289-
BlockOwnerDeletion: &blockOwnerDeletion,
290-
},
291-
}, r.OwnerReferences)
279+
require.Equal(t, patch, r.ManifestPatch)
292280
},
293281
)
294282

295283
t.Run(
296-
"MultipleOwnerRefs", func(t *testing.T) {
284+
"EmptyManifestPatch", func(t *testing.T) {
297285
cfg.DriverOpts = map[string]string{
298-
"ownerrefs": "apiVersion=v1,kind=Pod,name=pod1,uid=uid-1;apiVersion=v1,kind=Pod,name=pod2,uid=uid-2",
286+
"manifest-patch": "",
299287
}
300288
r, _, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
301289
require.NoError(t, err)
302-
require.Len(t, r.OwnerReferences, 2)
303-
require.Equal(t, types.UID("uid-1"), r.OwnerReferences[0].UID)
304-
require.Equal(t, types.UID("uid-2"), r.OwnerReferences[1].UID)
305-
},
306-
)
307-
308-
t.Run(
309-
"InvalidOwnerRefKey", func(t *testing.T) {
310-
cfg.DriverOpts = map[string]string{
311-
"ownerrefs": "apiVersion=v1,invalid=foo",
312-
}
313-
_, _, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
314-
require.Error(t, err)
315-
},
316-
)
317-
318-
t.Run(
319-
"InvalidOwnerRefController", func(t *testing.T) {
320-
cfg.DriverOpts = map[string]string{
321-
"ownerrefs": "apiVersion=v1,controller=notabool",
322-
}
323-
_, _, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
324-
require.Error(t, err)
290+
require.Empty(t, r.ManifestPatch)
325291
},
326292
)
327293
}

driver/kubernetes/manifest/manifest.go

Lines changed: 108 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package manifest
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"path"
67
"strings"
78

89
"github.com/docker/buildx/util/platformutil"
910
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
11+
"github.com/pkg/errors"
12+
yaml "go.yaml.in/yaml/v3"
1013
appsv1 "k8s.io/api/apps/v1"
1114
corev1 "k8s.io/api/core/v1"
1215
"k8s.io/apimachinery/pkg/api/resource"
@@ -38,7 +41,7 @@ type DeploymentOpt struct {
3841
CustomAnnotations map[string]string
3942
CustomLabels map[string]string
4043
Tolerations []corev1.Toleration
41-
OwnerReferences []metav1.OwnerReference
44+
ManifestPatch string
4245
RequestsCPU string
4346
RequestsMemory string
4447
RequestsEphemeralStorage string
@@ -155,11 +158,10 @@ func NewDeployment(opt *DeploymentOpt) (d *appsv1.Deployment, s *appsv1.Stateful
155158
}
156159

157160
meta := metav1.ObjectMeta{
158-
Namespace: opt.Namespace,
159-
Name: opt.Name,
160-
Labels: labels,
161-
Annotations: annotations,
162-
OwnerReferences: opt.OwnerReferences,
161+
Namespace: opt.Namespace,
162+
Name: opt.Name,
163+
Labels: labels,
164+
Annotations: annotations,
163165
}
164166

165167
for _, cfg := range splitConfigFiles(opt.ConfigFiles) {
@@ -331,9 +333,109 @@ func NewDeployment(opt *DeploymentOpt) (d *appsv1.Deployment, s *appsv1.Stateful
331333
}
332334
}
333335

336+
if opt.ManifestPatch != "" {
337+
if d != nil {
338+
if err := applyManifestPatch(d, opt.ManifestPatch); err != nil {
339+
return nil, nil, nil, err
340+
}
341+
}
342+
if s != nil {
343+
if err := applyManifestPatch(s, opt.ManifestPatch); err != nil {
344+
return nil, nil, nil, err
345+
}
346+
}
347+
for _, cfgMap := range c {
348+
if err := applyManifestPatch(cfgMap, opt.ManifestPatch); err != nil {
349+
return nil, nil, nil, err
350+
}
351+
}
352+
}
353+
334354
return
335355
}
336356

357+
func applyManifestPatch(obj any, patch string) error {
358+
path, value, err := parseManifestPatch(patch)
359+
if err != nil {
360+
return err
361+
}
362+
363+
dt, err := json.Marshal(obj)
364+
if err != nil {
365+
return errors.Wrap(err, "marshal manifest")
366+
}
367+
368+
var doc map[string]any
369+
if err := json.Unmarshal(dt, &doc); err != nil {
370+
return errors.Wrap(err, "unmarshal manifest")
371+
}
372+
373+
if err := setManifestPatchValue(doc, path, value); err != nil {
374+
return err
375+
}
376+
377+
dt, err = json.Marshal(doc)
378+
if err != nil {
379+
return errors.Wrap(err, "marshal patched manifest")
380+
}
381+
if err := json.Unmarshal(dt, obj); err != nil {
382+
return errors.Wrap(err, "unmarshal patched manifest")
383+
}
384+
return nil
385+
}
386+
387+
func parseManifestPatch(patch string) ([]string, any, error) {
388+
lhs, rhs, ok := strings.Cut(patch, "=")
389+
if !ok {
390+
return nil, nil, errors.Errorf("invalid manifest-patch %q: expected assignment expression", patch)
391+
}
392+
393+
lhs = strings.TrimSpace(lhs)
394+
rhs = strings.TrimSpace(rhs)
395+
if lhs == "" || rhs == "" {
396+
return nil, nil, errors.Errorf("invalid manifest-patch %q: expected non-empty assignment", patch)
397+
}
398+
if !strings.HasPrefix(lhs, ".") {
399+
return nil, nil, errors.Errorf("invalid manifest-patch %q: path must start with '.'", patch)
400+
}
401+
402+
path := strings.Split(strings.TrimPrefix(lhs, "."), ".")
403+
for _, segment := range path {
404+
if segment == "" {
405+
return nil, nil, errors.Errorf("invalid manifest-patch %q: empty path segment", patch)
406+
}
407+
}
408+
409+
var value any
410+
if err := yaml.Unmarshal([]byte(rhs), &value); err != nil {
411+
return nil, nil, errors.Wrapf(err, "invalid manifest-patch %q", patch)
412+
}
413+
return path, value, nil
414+
}
415+
416+
func setManifestPatchValue(doc map[string]any, path []string, value any) error {
417+
current := doc
418+
for i, segment := range path {
419+
if i == len(path)-1 {
420+
current[segment] = value
421+
return nil
422+
}
423+
next, ok := current[segment]
424+
if !ok {
425+
child := map[string]any{}
426+
current[segment] = child
427+
current = child
428+
continue
429+
}
430+
child, ok := next.(map[string]any)
431+
if !ok {
432+
return errors.Errorf("invalid manifest-patch path %q: %q is not an object", strings.Join(path, "."), strings.Join(path[:i+1], "."))
433+
}
434+
current = child
435+
}
436+
return nil
437+
}
438+
337439
func (opt *DeploymentOpt) IsPersistentStorage() bool {
338440
return opt.RequestsPersistentStorage != ""
339441
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package manifest
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
"k8s.io/apimachinery/pkg/types"
9+
)
10+
11+
func TestNewDeploymentManifestPatch(t *testing.T) {
12+
controller := true
13+
blockOwnerDeletion := false
14+
15+
d, s, c, err := NewDeployment(&DeploymentOpt{
16+
Namespace: "test-ns",
17+
Name: "test-builder",
18+
Image: "buildkit:test",
19+
Replicas: 1,
20+
ManifestPatch: `.metadata.ownerReferences = [{apiVersion: "actions.github.com/v1alpha1", kind: "EphemeralRunner", name: "runner-xyz", uid: "b636330d-26b7-417a-8464-c2641438feed", controller: true, blockOwnerDeletion: false}]`,
21+
})
22+
require.NoError(t, err)
23+
require.NotNil(t, d)
24+
require.Nil(t, s)
25+
require.Empty(t, c)
26+
require.Equal(t, []metav1.OwnerReference{
27+
{
28+
APIVersion: "actions.github.com/v1alpha1",
29+
Kind: "EphemeralRunner",
30+
Name: "runner-xyz",
31+
UID: types.UID("b636330d-26b7-417a-8464-c2641438feed"),
32+
Controller: &controller,
33+
BlockOwnerDeletion: &blockOwnerDeletion,
34+
},
35+
}, d.OwnerReferences)
36+
}
37+
38+
func TestNewDeploymentManifestPatchInvalid(t *testing.T) {
39+
_, _, _, err := NewDeployment(&DeploymentOpt{
40+
Namespace: "test-ns",
41+
Name: "test-builder",
42+
Image: "buildkit:test",
43+
Replicas: 1,
44+
ManifestPatch: `.metadata.ownerReferences = [`,
45+
})
46+
require.Error(t, err)
47+
}

0 commit comments

Comments
 (0)