/
common.go
120 lines (104 loc) · 3.37 KB
/
common.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
package controllers
import (
"context"
"errors"
"fmt"
"strings"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/client-go/util/retry"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"
"github.com/grafana/tempo-operator/internal/manifests"
)
func isNamespaceScoped(obj client.Object) bool {
switch obj.(type) {
case *rbacv1.ClusterRole, *rbacv1.ClusterRoleBinding:
return false
default:
return true
}
}
// reconcileManagedObjects creates or updates all managed objects.
// If immutable fields are changed, the object will be deleted and re-created.
func reconcileManagedObjects(
ctx context.Context,
k8sclient client.Client,
owner metav1.Object,
scheme *runtime.Scheme,
managedObjects []client.Object,
ownedObjects map[types.UID]client.Object,
) error {
log := log.FromContext(ctx)
pruneObjects := ownedObjects
// Create or update all objects managed by the operator
errs := []error{}
for _, obj := range managedObjects {
l := log.WithValues(
"objectName", obj.GetName(),
"objectKind", obj.GetObjectKind().GroupVersionKind(),
)
if isNamespaceScoped(obj) {
if err := ctrl.SetControllerReference(owner, obj, scheme); err != nil {
l.Error(err, "failed to set controller owner reference to resource")
errs = append(errs, err)
continue
}
}
desired := obj.DeepCopyObject().(client.Object)
mutateFn := manifests.MutateFuncFor(obj, desired)
var op controllerutil.OperationResult
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
var err error
op, err = ctrl.CreateOrUpdate(ctx, k8sclient, obj, mutateFn)
return err
})
var immutableErr *manifests.ImmutableErr
if err != nil && errors.As(err, &immutableErr) {
l.Error(err, "detected a change in an immutable field. The object will be deleted, and re-created on next reconcile", "obj", obj.GetName())
err = k8sclient.Delete(ctx, desired)
}
if err != nil {
l.Error(err, "failed to configure resource")
errs = append(errs, err)
} else {
l.V(1).Info(fmt.Sprintf("resource has been %s", op))
}
// This object is still managed by the operator, remove it from the list of objects to prune
delete(pruneObjects, obj.GetUID())
}
if len(errs) > 0 {
return fmt.Errorf("failed to create objects for %s: %w", owner.GetName(), errors.Join(errs...))
}
// Prune owned objects in the cluster which are not managed anymore
pruneErrs := []error{}
for _, obj := range pruneObjects {
l := log.WithValues(
"objectName", obj.GetName(),
"objectKind", obj.GetObjectKind(),
)
l.Info("pruning unmanaged resource")
err := k8sclient.Delete(ctx, obj)
if err != nil {
l.Error(err, "failed to delete resource")
pruneErrs = append(pruneErrs, err)
}
}
if len(pruneErrs) > 0 {
return fmt.Errorf("failed to prune objects for %s: %w", owner.GetName(), errors.Join(pruneErrs...))
}
return nil
}
// listFieldErrors converts field.ErrorList to a comma separated string of errors.
func listFieldErrors(fieldErrs field.ErrorList) string {
msgs := make([]string, len(fieldErrs))
for i, fieldErr := range fieldErrs {
msgs[i] = fieldErr.Detail
}
return strings.Join(msgs, ", ")
}