@@ -25,12 +25,11 @@ import (
25
25
"k8s.io/apimachinery/pkg/api/meta"
26
26
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27
27
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28
- "k8s.io/apimachinery/pkg/runtime/schema"
29
28
"k8s.io/apimachinery/pkg/types"
30
29
"k8s.io/apimachinery/pkg/util/sets"
31
30
"k8s.io/client-go/dynamic"
32
31
"k8s.io/klog/v2"
33
- "sigs.k8s.io/kubebuilder-declarative-pattern/applylib/applyset /forked/kubectlapply "
32
+ kubectlapply "sigs.k8s.io/kubebuilder-declarative-pattern/applylib/third_party /forked/github.com/kubernetes/kubectl/pkg/cmd/apply "
34
33
)
35
34
36
35
// ApplySet is a set of objects that we want to apply to the cluster.
@@ -65,10 +64,6 @@ type ApplySet struct {
65
64
// Parent provides the necessary methods to determine a ApplySet parent object, which can be used to find out all the on-track
66
65
// deployment manifests.
67
66
parent Parent
68
- // Leveraging the applyset from kubectl. Eventually, we expect to use a tool-neutral library that can provide the key ApplySet
69
- // methods. Nowadays, k8s.io/kubectl/pkg/cmd/apply provides the most complete ApplySet library. However, it is bundled
70
- // with kubectl and can hardly be used directly by kubebuilder-declarative-pattern.
71
- innerApplySet * kubectlapply.ApplySet
72
67
// If not given, the tooling value will be the `Parent` Kind.
73
68
tooling string
74
69
}
@@ -90,7 +85,7 @@ type Options struct {
90
85
// New constructs a new ApplySet
91
86
func New (options Options ) (* ApplySet , error ) {
92
87
parent := options .Parent
93
- parentRef := & kubectlapply.ApplySetParentRef {Name : parent .Name (), RESTMapping : parent .RESTMapping ()}
88
+ parentRef := & kubectlapply.ApplySetParentRef {Name : parent .Name (), Namespace : parent . Namespace (), RESTMapping : parent .RESTMapping ()}
94
89
kapplyset := kubectlapply .NewApplySet (parentRef , kubectlapply.ApplySetTooling {Name : options .Tooling }, options .RESTMapper )
95
90
options .PatchOptions .FieldManager = kapplyset .FieldManager ()
96
91
a := & ApplySet {
@@ -99,7 +94,6 @@ func New(options Options) (*ApplySet, error) {
99
94
patchOptions : options .PatchOptions ,
100
95
deleteOptions : options .DeleteOptions ,
101
96
prune : options .Prune ,
102
- innerApplySet : kapplyset ,
103
97
parent : parent ,
104
98
tooling : options .Tooling ,
105
99
}
@@ -134,9 +128,13 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
134
128
a .mutex .Unlock ()
135
129
136
130
results := & ApplyResults {total : len (trackers .items )}
137
- if err := a .BeforeApply (ctx ); err != nil {
138
- return nil , err
139
- }
131
+ visitedUids := sets .New [types.UID ]()
132
+
133
+ // We initialize a new Kubectl ApplySet for each ApplyOnce run. This is because kubectl Applyset is designed for
134
+ // single actuation and not for reconciliation.
135
+ // Note: The Kubectl ApplySet will share the RESTMapper with k-d-p/ApplySet, which caches all the manifests in the past.
136
+ parentRef := & kubectlapply.ApplySetParentRef {Name : a .parent .Name (), Namespace : a .parent .Namespace (), RESTMapping : a .parent .RESTMapping ()}
137
+ kapplyset := kubectlapply .NewApplySet (parentRef , kubectlapply.ApplySetTooling {Name : a .tooling }, a .restMapper )
140
138
for i := range trackers .items {
141
139
tracker := & trackers .items [i ]
142
140
obj := tracker .desired
@@ -151,6 +149,14 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
151
149
results .applyError (gvk , nn , fmt .Errorf ("error getting rest mapping for %v: %w" , gvk , err ))
152
150
continue
153
151
}
152
+ // cache the GVK in kubectlapply. kubectlapply will use this to calculate
153
+ // the latest parent "applyset.kubernetes.io/contains-group-resources" annotation after applying.
154
+ kapplyset .AddResource (restMapping , obj .GetNamespace ())
155
+ if err = a .updateManifestLabel (obj , kapplyset .LabelsForMember ()); err != nil {
156
+ klog .Errorf ("unable to update label for %v/%v %v: %v" , obj .GetName (), obj .GetNamespace (), gvk , err )
157
+ continue
158
+ }
159
+
154
160
gvr := restMapping .Resource
155
161
156
162
var dynamicResource dynamic.ResourceInterface
@@ -188,131 +194,83 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
188
194
results .applyError (gvk , nn , fmt .Errorf ("error from apply: %w" , err ))
189
195
continue
190
196
}
191
-
197
+ visitedUids . Insert ( lastApplied . GetUID ())
192
198
tracker .lastApplied = lastApplied
193
199
results .applySuccess (gvk , nn )
194
200
tracker .isHealthy = isHealthy (lastApplied )
195
201
results .reportHealth (gvk , nn , tracker .isHealthy )
196
202
}
197
203
198
204
if a .prune {
199
- klog .Info ("prune is enabled" )
205
+ klog .V (4 ).Infof ("Prune is enabled" )
206
+ if err := a .WithParent (kapplyset ); err != nil {
207
+ return results , err
208
+ }
200
209
err := func () error {
201
- allObjects , err := a . innerApplySet . FindAllObjectsToPrune (ctx , a .client , sets . New [types. UID ]() )
210
+ pruneObjects , err := kapplyset . FindAllObjectsToPrune (ctx , a .client , visitedUids )
202
211
if err != nil {
203
212
return err
204
213
}
205
- pruneObjects := a .excludeCurrent (allObjects )
206
214
if err = a .deleteObjects (ctx , pruneObjects , results ); err != nil {
207
215
return err
208
216
}
209
217
return nil
210
218
}()
211
219
if err != nil {
212
- klog .Errorf ("prune failed %v" , err )
213
- if e := a .updateParent ( ctx , "superset" ); e != nil {
214
- klog . Errorf ( "update parent failed %v" , e )
220
+ klog .Errorf ("encounter error on pruning %v" , err )
221
+ if e := a .updateParentLabelsAndAnnotations ( kapplyset , "superset" ); e != nil {
222
+ return results , e
215
223
}
216
- return results , nil
217
224
}
218
- klog .Info ("prune succeed" )
219
225
}
220
- if err := a .updateParent ( ctx , "latest" ); err != nil {
226
+ if err := a .updateParentLabelsAndAnnotations ( kapplyset , "latest" ); err != nil {
221
227
klog .Errorf ("update parent failed %v" , err )
222
228
}
223
229
return results , nil
224
230
}
225
231
226
- // BeforeApply updates the parent and manifests labels and annotations.
227
- // This method is adjusted for kubectlapply which requests
228
- // 1. the manifests with its RESTMappings. This is from the ControllerRESTMapper cache.
229
- // 2. the parent in the cluster must already have labels and annotations. This means the `FetchParent` will fail at first
230
- // until the `updateParent` updates the parent labels and annotations in the cluster. This is why the FetchParent continue wth error.
231
- func (a * ApplySet ) BeforeApply (ctx context.Context ) error {
232
- if err := a .FetchParent (ctx ); err != nil {
233
- klog .Errorf ("fetch parent error %v" , err .Error ())
232
+ func mergeMap (from , to map [string ]string ) map [string ]string {
233
+ if to == nil {
234
+ to = make (map [string ]string )
234
235
}
235
- for _ , obj := range a .trackers .items {
236
- gvk := obj .desired .GroupVersionKind ()
237
- restmapping , err := a .restMapper .RESTMapping (gvk .GroupKind (), gvk .Version )
238
- if err != nil {
239
- klog .Errorf ("unable to get restmapping for %v: %v" , gvk , err )
240
- continue
241
- }
242
- a .innerApplySet .AddResource (restmapping , obj .desired .GetNamespace ())
243
- if err = a .updateLabel (obj .desired ); err != nil {
244
- klog .Errorf ("unable to update label for %v: %w" , gvk , err )
245
- }
236
+ for k , v := range from {
237
+ to [k ] = v
246
238
}
247
- if err := a .updateParent (ctx , "superset" ); err != nil {
248
- klog .Errorf ("before apply: update parent error %v" , err .Error ())
249
- }
250
- return nil
239
+ return to
251
240
}
252
241
253
242
// updateLabel adds the "applyset.kubernetes.io/part-of: Parent-ID" label to the manifest.
254
- func (a * ApplySet ) updateLabel (obj ApplyableObject ) error {
255
- applysetLabels := a .innerApplySet .LabelsForMember ()
256
- data , err := obj .MarshalJSON ()
257
- if err != nil {
258
- return err
259
- }
260
- var u unstructured.Unstructured
261
- if err = u .UnmarshalJSON (data ); err != nil {
262
- return err
243
+ func (a * ApplySet ) updateManifestLabel (obj ApplyableObject , applysetLabels map [string ]string ) error {
244
+ u , ok := obj .(* unstructured.Unstructured )
245
+ if ! ok {
246
+ return fmt .Errorf ("unable to convert `ApplyableObject` to `unstructured.Unstructured` %v/%v %v" ,
247
+ obj .GetName (), obj .GetNamespace (), obj .GroupVersionKind ().String ())
263
248
}
264
249
labels := u .GetLabels ()
265
250
if labels == nil {
266
251
labels = make (map [string ]string )
267
252
}
268
- for k , v := range applysetLabels {
269
- labels [k ] = v
270
- }
271
- u .SetLabels (labels )
272
- newData , err := u .MarshalJSON ()
273
- if err != nil {
274
- return err
275
- }
276
- json .Unmarshal (newData , obj )
253
+ newLabels := mergeMap (applysetLabels , labels )
254
+ u .SetLabels (newLabels )
277
255
return nil
278
256
}
279
257
280
- // updateParent updates the parent labels and annotations.
258
+ // updateParentLabelsAndAnnotations updates the parent labels and annotations.
281
259
// This method leverages the kubectlapply to build the parent labels and annotations, but avoid using the
282
260
// `resource.NewHelper` and cmdutil to patch the parent.
283
- func (a * ApplySet ) updateParent ( ctx context. Context , mode kubectlapply.ApplySetUpdateMode ) error {
284
- data , err := json . Marshal (a .innerApplySet . BuildParentPatch ( mode ))
261
+ func (a * ApplySet ) updateParentLabelsAndAnnotations ( kapplyset * kubectlapply. ApplySet , mode kubectlapply.ApplySetUpdateMode ) error {
262
+ parent , err := meta . Accessor (a .parent . GetSubject ( ))
285
263
if err != nil {
286
- return fmt .Errorf ("failed to encode patch for ApplySet parent: %w" , err )
287
- }
288
- if _ , err = a .client .Resource (a .parent .RESTMapping ().Resource ).Patch (ctx , a .parent .Name (), types .ApplyPatchType , data , a .patchOptions ); err != nil {
289
- klog .Warningf ("unable to patch parent before apply: %v" , err )
290
264
return err
291
265
}
266
+ partialParent := kapplyset .BuildParentPatch (mode )
267
+ newAnnotations := mergeMap (partialParent .Annotations , parent .GetAnnotations ())
268
+ parent .SetAnnotations (newAnnotations )
269
+ newLabels := mergeMap (partialParent .Labels , parent .GetLabels ())
270
+ parent .SetLabels (newLabels )
292
271
return nil
293
272
}
294
273
295
- func (a * ApplySet ) excludeCurrent (allObjects []kubectlapply.PruneObject ) []kubectlapply.PruneObject {
296
- gvknnKey := func (gvk schema.GroupVersionKind , name , namespace string ) string {
297
- return gvk .String () + name + namespace
298
- }
299
- desiredObj := make (map [string ]struct {})
300
- for _ , obj := range a .trackers .items {
301
- gvk := obj .desired .GroupVersionKind ()
302
- name := obj .desired .GetName ()
303
- ns := obj .desired .GetNamespace ()
304
- desiredObj [gvknnKey (gvk , name , ns )] = struct {}{}
305
- }
306
- var pruneList []kubectlapply.PruneObject
307
- for _ , p := range allObjects {
308
- gvk := p .Object .GetObjectKind ().GroupVersionKind ()
309
- if _ , ok := desiredObj [gvknnKey (gvk , p .Name , p .Namespace )]; ! ok {
310
- pruneList = append (pruneList , p )
311
- }
312
- }
313
- return pruneList
314
- }
315
-
316
274
func (a * ApplySet ) deleteObjects (ctx context.Context , pruneObjects []kubectlapply.PruneObject , results * ApplyResults ) error {
317
275
for i := range pruneObjects {
318
276
pruneObject := & pruneObjects [i ]
@@ -334,9 +292,8 @@ func (a *ApplySet) deleteObjects(ctx context.Context, pruneObjects []kubectlappl
334
292
return nil
335
293
}
336
294
337
- func (a * ApplySet ) FetchParent (ctx context.Context ) error {
338
- object , err := a .client .Resource (a .parent .RESTMapping ().Resource ).Namespace (a .parent .Namespace ()).Get (
339
- ctx , a .parent .Name (), metav1.GetOptions {})
295
+ func (a * ApplySet ) WithParent (kapplyset * kubectlapply.ApplySet ) error {
296
+ object , err := meta .Accessor (a .parent .GetSubject ())
340
297
if err != nil {
341
298
return err
342
299
}
@@ -356,8 +313,9 @@ func (a *ApplySet) FetchParent(ctx context.Context) error {
356
313
if labels == nil {
357
314
labels = make (map [string ]string )
358
315
}
359
- labels [kubectlapply .ApplySetParentIDLabel ] = a . innerApplySet .ID ()
316
+ labels [kubectlapply .ApplySetParentIDLabel ] = kapplyset .ID ()
360
317
object .SetLabels (labels )
361
318
}
362
- return a .innerApplySet .FetchParent (object )
319
+ // This is not a cluster fetch. It builds up the parents labels and annotations information in kapplyset.
320
+ return kapplyset .FetchParent (a .parent .GetSubject ())
363
321
}
0 commit comments