add function to upgrade managedfields CSA to SSA

Kubernetes-commit: 27cd307e23df3a2d508d52ff10ac8f46bf3bcea3
This commit is contained in:
Alexander Zielenski 2022-08-22 16:42:18 -07:00 committed by Kubernetes Publisher
parent 27c67e708a
commit c364b639fb
2 changed files with 255 additions and 0 deletions

134
util/csaupgrade/upgrade.go Normal file
View File

@ -0,0 +1,134 @@
package csaupgrade
import (
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// Upgrades the Manager information for fields managed with CSA
// Prepares fields owned by `csaManager` for 'Update' operations for use now
// with the given `ssaManager` for `Apply` operations
//
// csaManager - Name of FieldManager formerly used for `Update` operations
// ssaManager - Name of FieldManager formerly used for `Apply` operations
// subResource - Name of subresource used for api calls or empty string for main resource
func UpgradeManagedFields(
obj runtime.Object,
csaManager string,
ssaManager string,
subResource string,
) (runtime.Object, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, fmt.Errorf("error accessing object metadata: %w", err)
}
managed, error := fieldmanager.DecodeManagedFields(accessor.GetManagedFields())
if error != nil {
return nil, fmt.Errorf("failed to decode managed fields: %w", error)
}
// If SSA manager exists:
// find CSA manager of same version, union. discard the rest
// Else SSA manager does not exist:
// find most recent CSA manager. convert to Apply operation
ssaIdentifier, err := fieldmanager.BuildManagerIdentifier(&metav1.ManagedFieldsEntry{
Manager: ssaManager,
Operation: metav1.ManagedFieldsOperationApply,
Subresource: subResource,
})
if err != nil {
return nil, fmt.Errorf("failed to build manager identifier for ssa manager")
}
ssaMan, ssaExists := managed.Fields()[ssaIdentifier]
// Collect all relevant CSA managers before operating on them
csaManagers := map[string]fieldpath.VersionedSet{}
for name, entry := range managed.Fields() {
if entry.Applied() {
// Not interested in SSA managed fields entries
continue
}
// Manager string is a JSON representation of encoded entry
// Pull manager name and subresource from it
encodedVersionedSet := &metav1.ManagedFieldsEntry{}
err = json.Unmarshal([]byte(name), encodedVersionedSet)
if err != nil {
return nil, fmt.Errorf("error unmarshalling manager identifier %v: %v", name, err)
}
if encodedVersionedSet.Manager != csaManager ||
encodedVersionedSet.Subresource != subResource {
continue
}
csaManagers[name] = entry
}
if len(csaManagers) == 0 {
return obj, nil
}
if ssaExists {
for name, entry := range csaManagers {
if entry.APIVersion() == ssaMan.APIVersion() {
// Merge entries if they are compatible versions
ssaMan = fieldpath.NewVersionedSet(
ssaMan.Set().Union(entry.Set()),
entry.APIVersion(),
true,
)
managed.Fields()[ssaIdentifier] = ssaMan
}
// Discard entry in all cases:
// if it has the wrong version we discard since managed fields versions
// cannot be converted
// if it has the correct version its fields were moved into the
// ssaManager's fieldSet
delete(managed.Fields(), name)
}
} else {
// Loop through sorted CSA managers. Take the first one we care about
firstName := ""
for _, entry := range accessor.GetManagedFields() {
if entry.Manager == csaManager &&
entry.Subresource == subResource &&
entry.Operation == metav1.ManagedFieldsOperationUpdate {
if len(firstName) == 0 {
ident, err := fieldmanager.BuildManagerIdentifier(&entry)
if err != nil {
return nil, fmt.Errorf("failed to build manager identifier: %w", err)
}
firstName = ident
break
}
}
}
managed.Fields()[ssaIdentifier] = csaManagers[firstName]
for name := range csaManagers {
delete(managed.Fields(), name)
}
}
now := metav1.Now()
managed.Times()[ssaIdentifier] = &now
copied := obj.DeepCopyObject()
if err := fieldmanager.EncodeObjectManagedFields(copied, managed); err != nil {
return nil, err
}
return copied, nil
}

View File

@ -0,0 +1,121 @@
package csaupgrade_test
import (
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/util/csaupgrade"
)
var avoidTimestampEqualities = func() conversion.Equalities {
var eqs = equality.Semantic.Copy()
err := eqs.AddFunc(
func(a, b metav1.ManagedFieldsEntry) bool {
// Two objects' managed fields are equivalent if, ignoring timestamp,
// the objects are deeply equal.
a.Time = nil
b.Time = nil
return reflect.DeepEqual(a, b)
},
)
if err != nil {
panic(err)
}
return eqs
}()
func TestUpgradeCSA(t *testing.T) {
// Initial object has managed fields from using CSA
originalYAML := []byte(`
apiVersion: v1
data:
key: value
legacy: unused
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
creationTimestamp: "2022-08-22T23:08:23Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:key: {}
f:legacy: {}
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
manager: kubectl-client-side-apply
operation: Update
time: "2022-08-22T23:08:23Z"
name: test
namespace: default
resourceVersion: "12502"
uid: 1f186675-58e6-4d7b-8bc5-7ece581e3013
`)
initialObject := unstructured.Unstructured{}
err := yaml.Unmarshal(originalYAML, &initialObject)
if err != nil {
t.Fatal(err)
}
upgraded, err := csaupgrade.UpgradeManagedFields(&initialObject, "kubectl-client-side-apply", "kubectl", "")
if err != nil {
t.Fatal(err)
}
expectedYAML := []byte(`
apiVersion: v1
data:
key: value
legacy: unused
kind: ConfigMap
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"key":"value","legacy":"unused"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"test","namespace":"default"}}
creationTimestamp: "2022-08-22T23:08:23Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:key: {}
f:legacy: {}
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
manager: kubectl
operation: Apply
time: "2022-08-22T23:08:23Z"
name: test
namespace: default
resourceVersion: "12502"
uid: 1f186675-58e6-4d7b-8bc5-7ece581e3013
`)
expectedObject := unstructured.Unstructured{}
err = yaml.Unmarshal(expectedYAML, &expectedObject)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(&expectedObject, upgraded) {
t.Fatal(cmp.Diff(&expectedObject, upgraded))
}
}