Skip to content

Commit f23e528

Browse files
committed
Better kubectlapply usage; pass Parent to applyset; better RESTMapping logic.
1 parent 030d840 commit f23e528

File tree

18 files changed

+200
-2220
lines changed

18 files changed

+200
-2220
lines changed

applylib/applyset/applyset.go

Lines changed: 53 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,11 @@ import (
2525
"k8s.io/apimachinery/pkg/api/meta"
2626
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2727
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
28-
"k8s.io/apimachinery/pkg/runtime/schema"
2928
"k8s.io/apimachinery/pkg/types"
3029
"k8s.io/apimachinery/pkg/util/sets"
3130
"k8s.io/client-go/dynamic"
3231
"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"
3433
)
3534

3635
// ApplySet is a set of objects that we want to apply to the cluster.
@@ -65,10 +64,6 @@ type ApplySet struct {
6564
// Parent provides the necessary methods to determine a ApplySet parent object, which can be used to find out all the on-track
6665
// deployment manifests.
6766
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
7267
// If not given, the tooling value will be the `Parent` Kind.
7368
tooling string
7469
}
@@ -90,7 +85,7 @@ type Options struct {
9085
// New constructs a new ApplySet
9186
func New(options Options) (*ApplySet, error) {
9287
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()}
9489
kapplyset := kubectlapply.NewApplySet(parentRef, kubectlapply.ApplySetTooling{Name: options.Tooling}, options.RESTMapper)
9590
options.PatchOptions.FieldManager = kapplyset.FieldManager()
9691
a := &ApplySet{
@@ -99,7 +94,6 @@ func New(options Options) (*ApplySet, error) {
9994
patchOptions: options.PatchOptions,
10095
deleteOptions: options.DeleteOptions,
10196
prune: options.Prune,
102-
innerApplySet: kapplyset,
10397
parent: parent,
10498
tooling: options.Tooling,
10599
}
@@ -134,9 +128,13 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
134128
a.mutex.Unlock()
135129

136130
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)
140138
for i := range trackers.items {
141139
tracker := &trackers.items[i]
142140
obj := tracker.desired
@@ -151,6 +149,14 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
151149
results.applyError(gvk, nn, fmt.Errorf("error getting rest mapping for %v: %w", gvk, err))
152150
continue
153151
}
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+
154160
gvr := restMapping.Resource
155161

156162
var dynamicResource dynamic.ResourceInterface
@@ -188,131 +194,83 @@ func (a *ApplySet) ApplyOnce(ctx context.Context) (*ApplyResults, error) {
188194
results.applyError(gvk, nn, fmt.Errorf("error from apply: %w", err))
189195
continue
190196
}
191-
197+
visitedUids.Insert(lastApplied.GetUID())
192198
tracker.lastApplied = lastApplied
193199
results.applySuccess(gvk, nn)
194200
tracker.isHealthy = isHealthy(lastApplied)
195201
results.reportHealth(gvk, nn, tracker.isHealthy)
196202
}
197203

198204
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+
}
200209
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)
202211
if err != nil {
203212
return err
204213
}
205-
pruneObjects := a.excludeCurrent(allObjects)
206214
if err = a.deleteObjects(ctx, pruneObjects, results); err != nil {
207215
return err
208216
}
209217
return nil
210218
}()
211219
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
215223
}
216-
return results, nil
217224
}
218-
klog.Info("prune succeed")
219225
}
220-
if err := a.updateParent(ctx, "latest"); err != nil {
226+
if err := a.updateParentLabelsAndAnnotations(kapplyset, "latest"); err != nil {
221227
klog.Errorf("update parent failed %v", err)
222228
}
223229
return results, nil
224230
}
225231

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)
234235
}
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
246238
}
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
251240
}
252241

253242
// 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())
263248
}
264249
labels := u.GetLabels()
265250
if labels == nil {
266251
labels = make(map[string]string)
267252
}
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)
277255
return nil
278256
}
279257

280-
// updateParent updates the parent labels and annotations.
258+
// updateParentLabelsAndAnnotations updates the parent labels and annotations.
281259
// This method leverages the kubectlapply to build the parent labels and annotations, but avoid using the
282260
// `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())
285263
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)
290264
return err
291265
}
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)
292271
return nil
293272
}
294273

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-
316274
func (a *ApplySet) deleteObjects(ctx context.Context, pruneObjects []kubectlapply.PruneObject, results *ApplyResults) error {
317275
for i := range pruneObjects {
318276
pruneObject := &pruneObjects[i]
@@ -334,9 +292,8 @@ func (a *ApplySet) deleteObjects(ctx context.Context, pruneObjects []kubectlappl
334292
return nil
335293
}
336294

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())
340297
if err != nil {
341298
return err
342299
}
@@ -356,8 +313,9 @@ func (a *ApplySet) FetchParent(ctx context.Context) error {
356313
if labels == nil {
357314
labels = make(map[string]string)
358315
}
359-
labels[kubectlapply.ApplySetParentIDLabel] = a.innerApplySet.ID()
316+
labels[kubectlapply.ApplySetParentIDLabel] = kapplyset.ID()
360317
object.SetLabels(labels)
361318
}
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())
363321
}

0 commit comments

Comments
 (0)