sigs.k8s.io/cluster-api@v1.7.1/internal/hooks/tracking.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package hooks has helper functions for Runtime Hooks. 18 package hooks 19 20 import ( 21 "context" 22 "strings" 23 24 "github.com/pkg/errors" 25 "k8s.io/apimachinery/pkg/util/sets" 26 "sigs.k8s.io/controller-runtime/pkg/client" 27 28 runtimev1 "sigs.k8s.io/cluster-api/exp/runtime/api/v1alpha1" 29 runtimecatalog "sigs.k8s.io/cluster-api/exp/runtime/catalog" 30 tlog "sigs.k8s.io/cluster-api/internal/log" 31 "sigs.k8s.io/cluster-api/util/patch" 32 ) 33 34 // MarkAsPending adds to the object's PendingHooksAnnotation the intent to execute a hook after an operation completes. 35 // Usually this function is called when an operation is starting in order to track the intent to call an After<operation> hook later in the process. 36 func MarkAsPending(ctx context.Context, c client.Client, obj client.Object, hooks ...runtimecatalog.Hook) error { 37 hookNames := []string{} 38 for _, hook := range hooks { 39 hookNames = append(hookNames, runtimecatalog.HookName(hook)) 40 } 41 42 patchHelper, err := patch.NewHelper(obj, c) 43 if err != nil { 44 return errors.Wrapf(err, "failed to mark %q hook(s) as pending", strings.Join(hookNames, ",")) 45 } 46 47 // Read the annotation of the objects and add the hook to the comma separated list 48 annotations := obj.GetAnnotations() 49 if annotations == nil { 50 annotations = map[string]string{} 51 } 52 annotations[runtimev1.PendingHooksAnnotation] = addToCommaSeparatedList(annotations[runtimev1.PendingHooksAnnotation], hookNames...) 53 obj.SetAnnotations(annotations) 54 55 if err := patchHelper.Patch(ctx, obj); err != nil { 56 return errors.Wrapf(err, "failed to mark %q hook(s) as pending", strings.Join(hookNames, ",")) 57 } 58 59 return nil 60 } 61 62 // IsPending returns true if there is an intent to call a hook being tracked in the object's PendingHooksAnnotation. 63 func IsPending(hook runtimecatalog.Hook, obj client.Object) bool { 64 hookName := runtimecatalog.HookName(hook) 65 annotations := obj.GetAnnotations() 66 if annotations == nil { 67 return false 68 } 69 return isInCommaSeparatedList(annotations[runtimev1.PendingHooksAnnotation], hookName) 70 } 71 72 // MarkAsDone removes the intent to call a Hook from the object's PendingHooksAnnotation. 73 // Usually this func is called after all the registered extensions for the Hook returned an answer without requests 74 // to hold on to the object's lifecycle (retryAfterSeconds). 75 func MarkAsDone(ctx context.Context, c client.Client, obj client.Object, hooks ...runtimecatalog.Hook) error { 76 hookNames := []string{} 77 for _, hook := range hooks { 78 hookNames = append(hookNames, runtimecatalog.HookName(hook)) 79 } 80 81 patchHelper, err := patch.NewHelper(obj, c) 82 if err != nil { 83 return errors.Wrapf(err, "failed to mark %q hook(s) as done", strings.Join(hookNames, ",")) 84 } 85 86 // Read the annotation of the objects and add the hook to the comma separated list 87 annotations := obj.GetAnnotations() 88 if annotations == nil { 89 annotations = map[string]string{} 90 } 91 annotations[runtimev1.PendingHooksAnnotation] = removeFromCommaSeparatedList(annotations[runtimev1.PendingHooksAnnotation], hookNames...) 92 if annotations[runtimev1.PendingHooksAnnotation] == "" { 93 delete(annotations, runtimev1.PendingHooksAnnotation) 94 } 95 obj.SetAnnotations(annotations) 96 97 if err := patchHelper.Patch(ctx, obj); err != nil { 98 return errors.Wrapf(err, "failed to mark %q hook(s) as done", strings.Join(hookNames, ",")) 99 } 100 101 return nil 102 } 103 104 // IsOkToDelete returns true if object has the OkToDeleteAnnotation in the annotations of the object, false otherwise. 105 func IsOkToDelete(obj client.Object) bool { 106 annotations := obj.GetAnnotations() 107 if annotations == nil { 108 return false 109 } 110 if _, ok := annotations[runtimev1.OkToDeleteAnnotation]; ok { 111 return true 112 } 113 return false 114 } 115 116 // MarkAsOkToDelete adds the OkToDeleteAnnotation annotation to the object and patches it. 117 func MarkAsOkToDelete(ctx context.Context, c client.Client, obj client.Object) error { 118 patchHelper, err := patch.NewHelper(obj, c) 119 if err != nil { 120 return errors.Wrapf(err, "failed to mark %s as ok to delete", tlog.KObj{Obj: obj}) 121 } 122 123 annotations := obj.GetAnnotations() 124 if annotations == nil { 125 annotations = map[string]string{} 126 } 127 annotations[runtimev1.OkToDeleteAnnotation] = "" 128 obj.SetAnnotations(annotations) 129 130 if err := patchHelper.Patch(ctx, obj); err != nil { 131 return errors.Wrapf(err, "failed to mark %s as ok to delete", tlog.KObj{Obj: obj}) 132 } 133 134 return nil 135 } 136 137 func addToCommaSeparatedList(list string, items ...string) string { 138 set := sets.Set[string]{}.Insert(strings.Split(list, ",")...) 139 140 // Remove empty strings (that might have been introduced by splitting an empty list) 141 // from the hook list 142 set.Delete("") 143 144 set.Insert(items...) 145 146 return strings.Join(sets.List(set), ",") 147 } 148 149 func isInCommaSeparatedList(list, item string) bool { 150 set := sets.Set[string]{}.Insert(strings.Split(list, ",")...) 151 return set.Has(item) 152 } 153 154 func removeFromCommaSeparatedList(list string, items ...string) string { 155 set := sets.Set[string]{}.Insert(strings.Split(list, ",")...) 156 157 // Remove empty strings (that might have been introduced by splitting an empty list) 158 // from the hook list 159 set.Delete("") 160 161 set.Delete(items...) 162 return strings.Join(sets.List(set), ",") 163 }