-
Notifications
You must be signed in to change notification settings - Fork 49
/
managedfields.go
170 lines (150 loc) · 7.64 KB
/
managedfields.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
// Copyright 2022 Lingfei Kong <colin404@foxmail.com>. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. The original repo for
// this file is https://github.com/superproj/onex.
//
// Package ssa contains utils related to Server-Side-Apply.
package ssa
import (
"context"
"encoding/json"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"github.com/superproj/onex/internal/pkg/contract"
)
const classicManager = "manager"
// DropManagedFields modifies the managedFields entries on the object that belong to "manager" (Operation=Update)
// to drop ownership of the given paths if there is no field yet that is managed by `ssaManager`.
//
// If we want to be able to drop fields that were previously owned by the "manager" we have to ensure that
// fields are not co-owned by "manager" and `ssaManager`. Otherwise, when we drop the fields with SSA
// (i.e. `ssaManager`) the fields would remain as they are still owned by "manager".
// The following code will do a one-time update on the managed fields.
// We won't do this on subsequent reconciles. This case will be identified by checking if `ssaManager` owns any fields.
// Dropping ownership in paths for existing "manager" entries (which could also be from other controllers) is safe,
// as we assume that if other controllers are still writing fields on the object they will just do it again and thus
// gain ownership again.
func DropManagedFields(ctx context.Context, c client.Client, obj client.Object, ssaManager string, paths []contract.Path) error {
// Return if `ssaManager` already owns any fields.
if hasFieldsManagedBy(obj, ssaManager) {
return nil
}
// Since there is no field managed by `ssaManager` it means that
// this is the first time this object is being processed after the controller calling this function
// started to use SSA patches.
// It is required to clean-up managedFields from entries created by the regular patches.
// This will ensure that `ssaManager` will be able to modify the fields that
// were originally owned by "manager".
base := obj.DeepCopyObject().(client.Object)
// Modify managedFieldEntry for manager=manager and operation=update to drop ownership
// for the given paths to avoid having two managers holding values.
originalManagedFields := obj.GetManagedFields()
managedFields := make([]metav1.ManagedFieldsEntry, 0, len(originalManagedFields))
for _, managedField := range originalManagedFields {
if managedField.Manager == classicManager &&
managedField.Operation == metav1.ManagedFieldsOperationUpdate {
// Unmarshal the managed fields into a map[string]any
fieldsV1 := map[string]any{}
if err := json.Unmarshal(managedField.FieldsV1.Raw, &fieldsV1); err != nil {
return errors.Wrap(err, "failed to unmarshal managed fields")
}
// Filter out the ownership for the given paths.
FilterIntent(&FilterIntentInput{
Path: contract.Path{},
Value: fieldsV1,
ShouldFilter: IsPathIgnored(paths),
})
fieldsV1Raw, err := json.Marshal(fieldsV1)
if err != nil {
return errors.Wrap(err, "failed to marshal managed fields")
}
managedField.FieldsV1.Raw = fieldsV1Raw
managedFields = append(managedFields, managedField)
} else {
// Do not modify the entry. Use as is.
managedFields = append(managedFields, managedField)
}
}
obj.SetManagedFields(managedFields)
return c.Patch(ctx, obj, client.MergeFrom(base))
}
// CleanUpManagedFieldsForSSAAdoption deletes the managedFields entries on the object that belong to "manager" (Operation=Update)
// if there is no field yet that is managed by `ssaManager`.
// It adds an "empty" entry in managedFields of the object if no field is currently managed by `ssaManager`.
//
// In previous versions of Cluster API (< v1.4.0) we were writing objects with Create and Patch which resulted in fields
// being owned by the "manager". After switching to Server-Side-Apply (SSA), fields will be owned by `ssaManager`.
//
// If we want to be able to drop fields that were previously owned by the "manager" we have to ensure that
// fields are not co-owned by "manager" and `ssaManager`. Otherwise, when we drop the fields with SSA
// (i.e. `ssaManager`) the fields would remain as they are still owned by "manager".
// The following code will do a one-time update on the managed fields to drop all entries for "manager".
// We won't do this on subsequent reconciles. This case will be identified by checking if `ssaManager` owns any fields.
// Dropping all existing "manager" entries (which could also be from other controllers) is safe, as we assume that if
// other controllers are still writing fields on the object they will just do it again and thus gain ownership again.
func CleanUpManagedFieldsForSSAAdoption(ctx context.Context, c client.Client, obj client.Object, ssaManager string) error {
// Return if `ssaManager` already owns any fields.
if hasFieldsManagedBy(obj, ssaManager) {
return nil
}
// Since there is no field managed by `ssaManager` it means that
// this is the first time this object is being processed after the controller calling this function
// started to use SSA patches.
// It is required to clean-up managedFields from entries created by the regular patches.
// This will ensure that `ssaManager` will be able to modify the fields that
// were originally owned by "manager".
base := obj.DeepCopyObject().(client.Object)
// Remove managedFieldEntry for manager=manager and operation=update to prevent having two managers holding values.
originalManagedFields := obj.GetManagedFields()
managedFields := make([]metav1.ManagedFieldsEntry, 0, len(originalManagedFields))
for i := range originalManagedFields {
if originalManagedFields[i].Manager == classicManager &&
originalManagedFields[i].Operation == metav1.ManagedFieldsOperationUpdate {
continue
}
managedFields = append(managedFields, originalManagedFields[i])
}
// Add a seeding managedFieldEntry for SSA executed by the management controller, to prevent SSA to create/infer
// a default managedFieldEntry when the first SSA is applied.
// More specifically, if an existing object doesn't have managedFields when applying the first SSA the API server
// creates an entry with operation=Update (kind of guessing where the object comes from), but this entry ends up
// acting as a co-ownership and we want to prevent this.
// NOTE: fieldV1Map cannot be empty, so we add metadata.name which will be cleaned up at the first SSA patch.
fieldV1Map := map[string]any{
"f:metadata": map[string]any{
"f:name": map[string]any{},
},
}
fieldV1, err := json.Marshal(fieldV1Map)
if err != nil {
return errors.Wrap(err, "failed to create seeding fieldV1Map for cleaning up legacy managed fields")
}
now := metav1.Now()
gvk, err := apiutil.GVKForObject(obj, c.Scheme())
if err != nil {
return errors.Wrapf(err, "failed to get GroupVersionKind of object %s", klog.KObj(obj))
}
managedFields = append(managedFields, metav1.ManagedFieldsEntry{
Manager: ssaManager,
Operation: metav1.ManagedFieldsOperationApply,
APIVersion: gvk.GroupVersion().String(),
Time: &now,
FieldsType: "FieldsV1",
FieldsV1: &metav1.FieldsV1{Raw: fieldV1},
})
obj.SetManagedFields(managedFields)
return c.Patch(ctx, obj, client.MergeFrom(base))
}
// hasFieldsManagedBy returns true if any of the fields in obj are managed by manager.
func hasFieldsManagedBy(obj client.Object, manager string) bool {
managedFields := obj.GetManagedFields()
for _, mf := range managedFields {
if mf.Manager == manager {
return true
}
}
return false
}