@@ -20,6 +20,7 @@ import (
20
20
"context"
21
21
"encoding/json"
22
22
"fmt"
23
+ "reflect"
23
24
"sync"
24
25
25
26
"k8s.io/apimachinery/pkg/api/meta"
@@ -30,6 +31,7 @@ import (
30
31
"k8s.io/apimachinery/pkg/util/sets"
31
32
"k8s.io/client-go/dynamic"
32
33
"k8s.io/klog/v2"
34
+ "sigs.k8s.io/controller-runtime/pkg/client"
33
35
kubectlapply "sigs.k8s.io/kubebuilder-declarative-pattern/applylib/third_party/forked/github.com/kubernetes/kubectl/pkg/cmd/apply"
34
36
)
35
37
@@ -45,6 +47,8 @@ import (
45
47
type ApplySet struct {
46
48
// client is the dynamic kubernetes client used to apply objects to the k8s cluster.
47
49
client dynamic.Interface
50
+ // ParentClient is the controller runtime client used to apply parent.
51
+ parentClient client.Client
48
52
// restMapper is used to map object kind to resources, and to know if objects are cluster-scoped.
49
53
restMapper meta.RESTMapper
50
54
// patchOptions holds the options used when applying, in particular the fieldManager
@@ -73,6 +77,8 @@ type ApplySet struct {
73
77
type Options struct {
74
78
// Client is the dynamic kubernetes client used to apply objects to the k8s cluster.
75
79
Client dynamic.Interface
80
+ // ParentClient is the controller runtime client used to apply parent.
81
+ ParentClient client.Client
76
82
// RESTMapper is used to map object kind to resources, and to know if objects are cluster-scoped.
77
83
RESTMapper meta.RESTMapper
78
84
// PatchOptions holds the options used when applying, in particular the fieldManager
@@ -90,6 +96,7 @@ func New(options Options) (*ApplySet, error) {
90
96
kapplyset := kubectlapply .NewApplySet (parentRef , kubectlapply.ApplySetTooling {Name : options .Tooling }, options .RESTMapper )
91
97
options .PatchOptions .FieldManager = kapplyset .FieldManager ()
92
98
a := & ApplySet {
99
+ parentClient : options .ParentClient ,
93
100
client : options .Client ,
94
101
restMapper : options .RESTMapper ,
95
102
patchOptions : options .PatchOptions ,
@@ -142,6 +149,7 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
142
149
parentRef := & kubectlapply.ApplySetParentRef {Name : a .parent .Name (), Namespace : a .parent .Namespace (), RESTMapping : a .parent .RESTMapping ()}
143
150
kapplyset := kubectlapply .NewApplySet (parentRef , kubectlapply.ApplySetTooling {Name : a .tooling }, a .restMapper )
144
151
152
+ // Cache the current RESTMappings to avoid re-fetching the bad ones.
145
153
restMappings := make (map [schema.GroupVersionKind ]restMappingResult )
146
154
for i := range trackers .items {
147
155
tracker := & trackers .items [i ]
@@ -160,15 +168,16 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
160
168
}
161
169
162
170
// TODO: Check error is NotFound and not some transient error?
163
- // TODO: Make sure we don't prune if there were errors
164
-
165
171
restMapping := result .restMapping
166
172
if restMapping != nil {
167
173
// cache the GVK in kubectlapply. kubectlapply will use this to calculate
168
174
// the latest parent "applyset.kubernetes.io/contains-group-resources" annotation after applying.
169
175
kapplyset .AddResource (restMapping , obj .GetNamespace ())
170
176
}
171
177
}
178
+ if err := a .WithParent (ctx , kapplyset ); err != nil {
179
+ return results , fmt .Errorf ("unable to update Parent %v" , err )
180
+ }
172
181
173
182
for i := range trackers .items {
174
183
tracker := & trackers .items [i ]
@@ -240,11 +249,9 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
240
249
results .reportHealth (gvk , nn , tracker .isHealthy )
241
250
}
242
251
243
- if a .prune {
252
+ // We want to be more cautions on pruning and only do it if all manifests are applied.
253
+ if a .prune && results .applyFailCount == 0 {
244
254
klog .V (4 ).Infof ("Prune is enabled" )
245
- if err := a .WithParent (kapplyset ); err != nil {
246
- return results , err
247
- }
248
255
err := func () error {
249
256
pruneObjects , err := kapplyset .FindAllObjectsToPrune (ctx , a .client , visitedUids )
250
257
if err != nil {
@@ -257,12 +264,12 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
257
264
}()
258
265
if err != nil {
259
266
klog .Errorf ("encounter error on pruning %v" , err )
260
- if e := a .updateParentLabelsAndAnnotations (kapplyset , "superset" ); e != nil {
267
+ if e := a .updateParentLabelsAndAnnotations (ctx , kapplyset , "superset" ); e != nil {
261
268
return results , e
262
269
}
263
270
}
264
271
}
265
- if err := a .updateParentLabelsAndAnnotations (kapplyset , "latest" ); err != nil {
272
+ if err := a .updateParentLabelsAndAnnotations (ctx , kapplyset , "latest" ); err != nil {
266
273
klog .Errorf ("update parent failed %v" , err )
267
274
}
268
275
return results , nil
@@ -297,17 +304,23 @@ func (a *ApplySet) updateManifestLabel(obj ApplyableObject, applysetLabels map[s
297
304
// updateParentLabelsAndAnnotations updates the parent labels and annotations.
298
305
// This method leverages the kubectlapply to build the parent labels and annotations, but avoid using the
299
306
// `resource.NewHelper` and cmdutil to patch the parent.
300
- func (a * ApplySet ) updateParentLabelsAndAnnotations (kapplyset * kubectlapply.ApplySet , mode kubectlapply.ApplySetUpdateMode ) error {
307
+ func (a * ApplySet ) updateParentLabelsAndAnnotations (ctx context. Context , kapplyset * kubectlapply.ApplySet , mode kubectlapply.ApplySetUpdateMode ) error {
301
308
parent , err := meta .Accessor (a .parent .GetSubject ())
302
309
if err != nil {
303
310
return err
304
311
}
312
+
313
+ original , err := meta .Accessor (a .parent .GetSubject ().DeepCopyObject ())
314
+ if err != nil {
315
+ return err
316
+ }
305
317
partialParent := kapplyset .BuildParentPatch (mode )
306
318
newAnnotations := mergeMap (partialParent .Annotations , parent .GetAnnotations ())
307
319
parent .SetAnnotations (newAnnotations )
308
320
newLabels := mergeMap (partialParent .Labels , parent .GetLabels ())
309
321
parent .SetLabels (newLabels )
310
- return nil
322
+
323
+ return a .updateParent (ctx , original , parent )
311
324
}
312
325
313
326
func (a * ApplySet ) deleteObjects (ctx context.Context , pruneObjects []kubectlapply.PruneObject , results * ApplyResults ) error {
@@ -331,8 +344,13 @@ func (a *ApplySet) deleteObjects(ctx context.Context, pruneObjects []kubectlappl
331
344
return nil
332
345
}
333
346
334
- func (a * ApplySet ) WithParent (kapplyset * kubectlapply.ApplySet ) error {
335
- object , err := meta .Accessor (a .parent .GetSubject ())
347
+ func (a * ApplySet ) WithParent (ctx context.Context , kapplyset * kubectlapply.ApplySet ) error {
348
+ parent := a .parent .GetSubject ()
349
+ object , err := meta .Accessor (parent )
350
+ if err != nil {
351
+ return err
352
+ }
353
+ original , err := meta .Accessor (parent .DeepCopyObject ())
336
354
if err != nil {
337
355
return err
338
356
}
@@ -356,5 +374,19 @@ func (a *ApplySet) WithParent(kapplyset *kubectlapply.ApplySet) error {
356
374
object .SetLabels (labels )
357
375
}
358
376
// This is not a cluster fetch. It builds up the parents labels and annotations information in kapplyset.
359
- return kapplyset .FetchParent (a .parent .GetSubject ())
377
+ if err := kapplyset .FetchParent (a .parent .GetSubject ()); err != nil {
378
+ return err
379
+ }
380
+
381
+ return a .updateParent (ctx , original , object )
382
+ }
383
+
384
+ func (a * ApplySet ) updateParent (ctx context.Context , original , current metav1.Object ) error {
385
+ if ! reflect .DeepEqual (original .GetLabels (), current .GetLabels ()) || ! reflect .DeepEqual (original .GetAnnotations (), current .GetAnnotations ()) {
386
+ if err := a .parentClient .Update (ctx , current .(client.Object )); err != nil {
387
+ return fmt .Errorf ("error updating parent %v" , err )
388
+ }
389
+ }
390
+ klog .V (4 ).Infof ("parent labels and annotations are not changed, skip updating" )
391
+ return nil
360
392
}
0 commit comments