mirror of
https://github.com/kubernetes/client-go.git
synced 2025-06-23 13:47:19 +00:00
add function to upgrade managedfields CSA to SSA
Kubernetes-commit: 27cd307e23df3a2d508d52ff10ac8f46bf3bcea3
This commit is contained in:
parent
27c67e708a
commit
c364b639fb
134
util/csaupgrade/upgrade.go
Normal file
134
util/csaupgrade/upgrade.go
Normal 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
|
||||
}
|
121
util/csaupgrade/upgrade_test.go
Normal file
121
util/csaupgrade/upgrade_test.go
Normal 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))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user