-
Notifications
You must be signed in to change notification settings - Fork 147
/
util.go
286 lines (245 loc) · 8.84 KB
/
util.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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
package util
import (
"context"
"errors"
"fmt"
"os"
"reflect"
"strings"
"time"
"github.com/go-logr/logr"
objectreferencesv1 "github.com/openshift/custom-resource-status/objectreferences/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/discovery"
"k8s.io/client-go/tools/reference"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)
// ForceRunModeEnv indicates if the operator should be forced to run in either local
// or cluster mode (currently only used for local mode)
var ForceRunModeEnv = "OSDK_FORCE_RUN_MODE"
type RunModeType string
const (
LocalRunMode RunModeType = "local"
ClusterRunMode RunModeType = "cluster"
// PodNameEnvVar is the constant for env variable POD_NAME
// which is the name of the current pod.
PodNameEnvVar = "POD_NAME"
)
// ErrNoNamespace indicates that a namespace could not be found for the current
// environment
var ErrNoNamespace = fmt.Errorf("namespace not found for current environment")
// ErrRunLocal indicates that the operator is set to run in local mode (this error
// is returned by functions that only work on operators running in cluster mode)
var ErrRunLocal = fmt.Errorf("operator run mode forced to local")
func GetOperatorNamespaceFromEnv() (string, error) {
if namespace, ok := os.LookupEnv(OperatorNamespaceEnv); ok {
return namespace, nil
}
return "", fmt.Errorf("%s unset or empty in environment", OperatorNamespaceEnv)
}
func IsRunModeLocal() bool {
return os.Getenv(ForceRunModeEnv) == string(LocalRunMode)
}
// GetOperatorNamespace returns the namespace the operator should be running in.
var GetOperatorNamespace = func(logger logr.Logger) (string, error) {
if IsRunModeLocal() {
return "", ErrRunLocal
}
nsBytes, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
if err != nil {
if os.IsNotExist(err) {
return "", ErrNoNamespace
}
return "", err
}
ns := strings.TrimSpace(string(nsBytes))
logger.Info("Found namespace", "Namespace", ns)
return ns, nil
}
// ToUnstructured converts an arbitrary object (which MUST obey the
// k8s object conventions) to an Unstructured
func ToUnstructured(obj runtime.Object, c client.Client) (*unstructured.Unstructured, error) {
apiVersion, kind := obj.GetObjectKind().GroupVersionKind().ToAPIVersionAndKind()
if apiVersion == "" || kind == "" {
gvk, err := apiutil.GVKForObject(obj, c.Scheme())
if err != nil {
return nil, err
}
ta, err := meta.TypeAccessor(obj)
if err != nil {
return nil, err
}
ta.SetKind(gvk.Kind)
ta.SetAPIVersion(gvk.GroupVersion().String())
}
u := &unstructured.Unstructured{}
var err error
u.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, err
}
return u, nil
}
// GetRuntimeObject will query the apiserver for the object
func GetRuntimeObject(ctx context.Context, c client.Client, obj client.Object) error {
key := client.ObjectKeyFromObject(obj)
return c.Get(ctx, key, obj)
}
// ComponentResourceRemoval removes the resource `obj` if it exists and belongs to the HCO
// with wait=true it will wait, (util ctx timeout, please set it!) for the resource to be effectively deleted
func ComponentResourceRemoval(ctx context.Context, c client.Client, obj client.Object, hcoName string, logger logr.Logger, dryRun bool, wait bool, protectNonHCOObjects bool) (bool, error) {
logger.Info("Removing resource", "name", obj.GetName(), "namespace", obj.GetNamespace(), "GVK", obj.GetObjectKind().GroupVersionKind(), "dryRun", dryRun)
resource, err := ToUnstructured(obj, c)
if err != nil {
logger.Error(err, "Failed to convert object to Unstructured")
return false, err
}
ok, err := getResourceForDeletion(ctx, c, resource, logger)
if !ok {
return false, err
}
if protectNonHCOObjects && !shouldDeleteResource(resource, hcoName, logger) {
return false, nil
}
opts := getDeletionOption(dryRun, wait)
if deleted, err := doDeleteResource(ctx, c, resource, opts); !deleted {
return deleted, err
}
if !wait || dryRun { // no need to wait
return !dryRun, nil
}
return validateDeletion(ctx, c, resource)
}
func doDeleteResource(ctx context.Context, c client.Client, resource *unstructured.Unstructured, opts *client.DeleteOptions) (bool, error) {
err := c.Delete(ctx, resource, opts)
if err != nil {
if apierrors.IsNotFound(err) {
// to be idempotent if called on a object that was
// already marked for deletion in a previous reconciliation loop
return false, nil
}
// failure
return false, err
}
return true, err
}
func shouldDeleteResource(resource *unstructured.Unstructured, hcoName string, logger logr.Logger) bool {
labels := resource.GetLabels()
if app, labelExists := labels[AppLabel]; !labelExists || app != hcoName {
logger.Info("Existing resource wasn't deployed by HCO, ignoring", "Kind", resource.GetObjectKind())
return false
}
return true
}
func getDeletionOption(dryRun bool, wait bool) *client.DeleteOptions {
opts := &client.DeleteOptions{}
if dryRun {
opts.DryRun = []string{metav1.DryRunAll}
}
if wait {
opts.PropagationPolicy = ptr.To(metav1.DeletePropagationForeground)
}
return opts
}
func getResourceForDeletion(ctx context.Context, c client.Client, resource *unstructured.Unstructured, logger logr.Logger) (bool, error) {
err := c.Get(ctx, types.NamespacedName{Name: resource.GetName(), Namespace: resource.GetNamespace()}, resource)
if err != nil {
if apierrors.IsNotFound(err) {
logger.Info("Resource doesn't exist, there is nothing to remove", "Kind", resource.GetObjectKind())
return false, nil
}
return false, err
}
return true, nil
}
func validateDeletion(ctx context.Context, c client.Client, resource *unstructured.Unstructured) (bool, error) {
for {
err := c.Get(ctx, types.NamespacedName{Name: resource.GetName(), Namespace: resource.GetNamespace()}, resource)
if apierrors.IsNotFound(err) {
// success!
return true, nil
}
select {
case <-ctx.Done():
// failed to delete in time
return false, fmt.Errorf("timed out waiting for %q - %q to be deleted", resource.GetObjectKind(), resource.GetName())
case <-time.After(100 * time.Millisecond):
// do nothing, try again
}
}
}
// EnsureDeleted calls ComponentResourceRemoval if the runtime object exists
// with wait=true it will wait, (util ctx timeout, please set it!) for the resource to be effectively deleted
func EnsureDeleted(ctx context.Context, c client.Client, obj client.Object, hcoName string, logger logr.Logger, dryRun bool, wait bool, protectNonHCOObjects bool) (bool, error) {
err := GetRuntimeObject(ctx, c, obj)
if err != nil {
var gdferr *discovery.ErrGroupDiscoveryFailed
if apierrors.IsNotFound(err) || meta.IsNoMatchError(err) || errors.As(err, &gdferr) {
logger.Info("Resource doesn't exist, there is nothing to remove", "Kind", obj.GetObjectKind())
return false, nil
}
logger.Error(err, "failed to get object from kubernetes", "Kind", obj.GetObjectKind())
return false, err
}
return ComponentResourceRemoval(ctx, c, obj, hcoName, logger, dryRun, wait, protectNonHCOObjects)
}
func ContainsString(s []string, word string) bool {
for _, w := range s {
if w == word {
return true
}
}
return false
}
var hcoKvIoVersion string
func GetHcoKvIoVersion() string {
if hcoKvIoVersion == "" {
hcoKvIoVersion = os.Getenv(HcoKvIoVersionName)
}
return hcoKvIoVersion
}
func AddCrToTheRelatedObjectList(relatedObjects *[]corev1.ObjectReference, found client.Object, scheme *runtime.Scheme) (bool, error) {
// Add it to the list of RelatedObjects if found
objectRef, err := reference.GetReference(scheme, found)
if err != nil {
return false, err
}
existingRef, err := objectreferencesv1.FindObjectReference(*relatedObjects, *objectRef)
if err != nil {
return false, err
}
if existingRef == nil || !reflect.DeepEqual(existingRef, objectRef) {
err = objectreferencesv1.SetObjectReference(relatedObjects, *objectRef)
if err != nil {
return false, err
}
// Eventually remove outdated reference with a different APIVersion
for _, ref := range *relatedObjects {
if ref.Kind == objectRef.Kind && ref.Namespace == objectRef.Namespace && ref.Name == objectRef.Name && ref.APIVersion != objectRef.APIVersion {
err = objectreferencesv1.RemoveObjectReference(relatedObjects, ref)
if err != nil {
return false, err
}
}
}
return true, nil
}
return false, nil
}
func GetLabels(hcName string, component AppComponent) map[string]string {
return map[string]string{
AppLabel: hcName,
AppLabelManagedBy: OperatorName,
AppLabelVersion: GetHcoKvIoVersion(),
AppLabelPartOf: HyperConvergedCluster,
AppLabelComponent: string(component),
}
}