-
Notifications
You must be signed in to change notification settings - Fork 462
/
object.go
174 lines (147 loc) · 5.5 KB
/
object.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Gardener contributors
//
// SPDX-License-Identifier: Apache-2.0
package kubernetes
import (
"context"
"fmt"
"strings"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/cache"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"github.com/gardener/gardener/pkg/resourcemanager/controller/garbagecollector/references"
"github.com/gardener/gardener/pkg/utils"
"github.com/gardener/gardener/pkg/utils/flow"
)
// ObjectName returns the name of the given object in the format <namespace>/<name>
func ObjectName(obj client.Object) string {
if obj.GetNamespace() == "" {
return obj.GetName()
}
return client.ObjectKeyFromObject(obj).String()
}
// ParseObjectName parses the given object name (in the format <namespace>/<name>) to its constituent namespace and name.
// If the given object name is not namespaced, an empty namespace is returned.
func ParseObjectName(objectName string) (string, string) {
if parts := strings.Split(objectName, string(types.Separator)); len(parts) == 2 {
return parts[0], parts[1]
}
return "", objectName
}
// DeleteObjects deletes a list of Kubernetes objects.
func DeleteObjects(ctx context.Context, c client.Writer, objects ...client.Object) error {
for _, obj := range objects {
if err := DeleteObject(ctx, c, obj); err != nil {
return err
}
}
return nil
}
// DeleteObject deletes a Kubernetes object. It ignores 'not found' and 'no match' errors.
func DeleteObject(ctx context.Context, c client.Writer, object client.Object) error {
if err := c.Delete(ctx, object); client.IgnoreNotFound(err) != nil && !meta.IsNoMatchError(err) {
return err
}
return nil
}
// DeleteObjectsFromListConditionally takes a Kubernetes List object. It iterates over its items and, if provided,
// executes the predicate function. If it evaluates to true then the object will be deleted.
func DeleteObjectsFromListConditionally(ctx context.Context, c client.Client, listObj client.ObjectList, predicateFn func(runtime.Object) bool) error {
fns := make([]flow.TaskFn, 0, meta.LenList(listObj))
if err := meta.EachListItem(listObj, func(obj runtime.Object) error {
if predicateFn == nil || predicateFn(obj) {
fns = append(fns, func(ctx context.Context) error {
return client.IgnoreNotFound(c.Delete(ctx, obj.(client.Object)))
})
}
return nil
}); err != nil {
return err
}
return flow.Parallel(fns...)(ctx)
}
// ResourcesExist checks if there is at least one object of the given objList.
func ResourcesExist(ctx context.Context, reader client.Reader, objList client.ObjectList, scheme *runtime.Scheme, listOpts ...client.ListOption) (bool, error) {
objects := objList
// Use `PartialObjectMetadata` if no or metadata only field selectors are passed (informer's indexers only have access to metadata fields).
if hasNoOrMetadataOnlyFieldSelector(listOpts...) {
gvk, err := apiutil.GVKForObject(objList, scheme)
if err != nil {
return false, err
}
objects = &metav1.PartialObjectMetadataList{}
objects.(*metav1.PartialObjectMetadataList).SetGroupVersionKind(gvk)
}
if err := reader.List(ctx, objects, append(listOpts, client.Limit(1))...); err != nil {
return true, err
}
switch o := objects.(type) {
case *metav1.PartialObjectMetadataList:
return len(o.Items) > 0, nil
default:
items, err := meta.ExtractList(objList)
if err != nil {
return false, err
}
return len(items) > 0, err
}
}
func hasNoOrMetadataOnlyFieldSelector(listOpts ...client.ListOption) bool {
listOptions := &client.ListOptions{}
for _, opt := range listOpts {
opt.ApplyToList(listOptions)
}
if listOptions.FieldSelector == nil {
return true
}
for _, req := range listOptions.FieldSelector.Requirements() {
if !strings.HasPrefix(req.Field, "metadata") && req.Field != cache.NamespaceIndex {
return false
}
}
return true
}
// MakeUnique takes either a *corev1.ConfigMap or a *corev1.Secret object and makes it immutable, i.e., it sets
// .immutable=true, computes a checksum based on .data, and appends the first 8 characters of the computed checksum
// to the name of the object. Additionally, it injects the `resources.gardener.cloud/garbage-collectable-reference=true`
// label.
func MakeUnique(obj runtime.Object) error {
var (
numberOfChecksumChars = 8
prependHyphen = func(name string) string {
if strings.HasSuffix(name, "-") {
return ""
}
return "-"
}
mergeMaps = func(a map[string]string, b map[string][]byte) map[string][]byte {
out := make(map[string][]byte, len(a)+len(b))
for k, v := range a {
out[k] = []byte(v)
}
for k, v := range b {
out[k] = v
}
return out
}
)
switch o := obj.(type) {
case *corev1.Secret:
o.Immutable = ptr.To(true)
o.Name += prependHyphen(o.Name) + utils.ComputeSecretChecksum(mergeMaps(o.StringData, o.Data))[:numberOfChecksumChars]
metav1.SetMetaDataLabel(&o.ObjectMeta, references.LabelKeyGarbageCollectable, references.LabelValueGarbageCollectable)
case *corev1.ConfigMap:
o.Immutable = ptr.To(true)
o.Name += prependHyphen(o.Name) + utils.ComputeSecretChecksum(mergeMaps(o.Data, o.BinaryData))[:numberOfChecksumChars]
metav1.SetMetaDataLabel(&o.ObjectMeta, references.LabelKeyGarbageCollectable, references.LabelValueGarbageCollectable)
default:
return fmt.Errorf("unhandled object type: %T", obj)
}
return nil
}