Plugin FieldManager in CRD handler, change to API
Kubernetes-commit: b55417f429353e1109df8b3bfa2afc8dbd9f240b
This commit is contained in:
parent
337fc9ccde
commit
d1a2d7fd8d
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
|
@ -140,7 +141,7 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
obj, err = scope.FieldManager.Update(liveObj, obj, "create")
|
obj, err = scope.FieldManager.Update(liveObj, obj, prefixFromUserAgent(req.UserAgent()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
scope.err(fmt.Errorf("failed to update object managed fields: %v", err), w, req)
|
scope.err(fmt.Errorf("failed to update object managed fields: %v", err), w, req)
|
||||||
return
|
return
|
||||||
|
|
@ -191,3 +192,7 @@ type namedCreaterAdapter struct {
|
||||||
func (c *namedCreaterAdapter) Create(ctx context.Context, name string, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
|
func (c *namedCreaterAdapter) Create(ctx context.Context, name string, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
|
||||||
return c.Creater.Create(ctx, obj, createValidatingAdmission, options)
|
return c.Creater.Create(ctx, obj, createValidatingAdmission, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prefixFromUserAgent(u string) string {
|
||||||
|
return strings.Split(u, "/")[0]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,10 @@ package fieldmanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
||||||
|
|
@ -60,6 +62,22 @@ func NewFieldManager(models openapiproto.Models, objectConverter runtime.ObjectC
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewCRDFieldManager creates a new FieldManager specifically for
|
||||||
|
// CRDs. This doesn't use openapi models (and it doesn't support the
|
||||||
|
// validation field right now).
|
||||||
|
func NewCRDFieldManager(objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion) *FieldManager {
|
||||||
|
return &FieldManager{
|
||||||
|
typeConverter: internal.DeducedTypeConverter{},
|
||||||
|
objectConverter: objectConverter,
|
||||||
|
objectDefaulter: objectDefaulter,
|
||||||
|
groupVersion: gv,
|
||||||
|
hubVersion: hub,
|
||||||
|
updater: merge.Updater{
|
||||||
|
Converter: internal.NewVersionConverter(internal.DeducedTypeConverter{}, objectConverter, hub),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update is used when the object has already been merged (non-apply
|
// Update is used when the object has already been merged (non-apply
|
||||||
// use-case), and simply updates the managed fields in the output
|
// use-case), and simply updates the managed fields in the output
|
||||||
// object.
|
// object.
|
||||||
|
|
@ -97,6 +115,11 @@ func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (r
|
||||||
return nil, fmt.Errorf("failed to create typed live object: %v", err)
|
return nil, fmt.Errorf("failed to create typed live object: %v", err)
|
||||||
}
|
}
|
||||||
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
||||||
|
manager, err = f.buildManagerInfo(manager, metav1.ManagedFieldsOperationUpdate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to build manager identifier: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
managed, err = f.updater.Update(liveObjTyped, newObjTyped, apiVersion, managed, manager)
|
managed, err = f.updater.Update(liveObjTyped, newObjTyped, apiVersion, managed, manager)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to update ManagedFields: %v", err)
|
return nil, fmt.Errorf("failed to update ManagedFields: %v", err)
|
||||||
|
|
@ -134,8 +157,13 @@ func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, force bool) (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create typed live object: %v", err)
|
return nil, fmt.Errorf("failed to create typed live object: %v", err)
|
||||||
}
|
}
|
||||||
|
manager, err := f.buildManagerInfo(applyManager, metav1.ManagedFieldsOperationApply)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to build manager identifier: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
|
||||||
newObjTyped, managed, err := f.updater.Apply(liveObjTyped, patchObjTyped, apiVersion, managed, applyManager, force)
|
newObjTyped, managed, err := f.updater.Apply(liveObjTyped, patchObjTyped, apiVersion, managed, manager, force)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if conflicts, ok := err.(merge.Conflicts); ok {
|
if conflicts, ok := err.(merge.Conflicts); ok {
|
||||||
return nil, errors.NewApplyConflict(conflicts)
|
return nil, errors.NewApplyConflict(conflicts)
|
||||||
|
|
@ -172,3 +200,16 @@ func (f *FieldManager) toVersioned(obj runtime.Object) (runtime.Object, error) {
|
||||||
func (f *FieldManager) toUnversioned(obj runtime.Object) (runtime.Object, error) {
|
func (f *FieldManager) toUnversioned(obj runtime.Object) (runtime.Object, error) {
|
||||||
return f.objectConverter.ConvertToVersion(obj, f.hubVersion)
|
return f.objectConverter.ConvertToVersion(obj, f.hubVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FieldManager) buildManagerInfo(prefix string, operation metav1.ManagedFieldsOperationType) (string, error) {
|
||||||
|
managerInfo := metav1.ManagedFieldsEntry{
|
||||||
|
Manager: prefix,
|
||||||
|
Operation: operation,
|
||||||
|
APIVersion: f.groupVersion.String(),
|
||||||
|
Time: &metav1.Time{Time: time.Now().UTC()},
|
||||||
|
}
|
||||||
|
if managerInfo.Manager == "" {
|
||||||
|
managerInfo.Manager = "unknown"
|
||||||
|
}
|
||||||
|
return internal.DecodeManager(&managerInfo)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ type gvkParser struct {
|
||||||
parser typed.Parser
|
parser typed.Parser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *gvkParser) Type(gvk schema.GroupVersionKind) *typed.ParseableType {
|
func (p *gvkParser) Type(gvk schema.GroupVersionKind) typed.ParseableType {
|
||||||
typeName, ok := p.gvks[gvk]
|
typeName, ok := p.gvks[gvk]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@ limitations under the License.
|
||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
@ -72,21 +74,53 @@ func EncodeObjectManagedFields(obj runtime.Object, fields fieldpath.ManagedField
|
||||||
|
|
||||||
// decodeManagedFields converts ManagedFields from the wire format (api format)
|
// decodeManagedFields converts ManagedFields from the wire format (api format)
|
||||||
// to the format used by sigs.k8s.io/structured-merge-diff
|
// to the format used by sigs.k8s.io/structured-merge-diff
|
||||||
func decodeManagedFields(encodedManagedFields map[string]metav1.VersionedFields) (managedFields fieldpath.ManagedFields, err error) {
|
func decodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (managedFields fieldpath.ManagedFields, err error) {
|
||||||
managedFields = make(map[string]*fieldpath.VersionedSet, len(encodedManagedFields))
|
managedFields = make(map[string]*fieldpath.VersionedSet, len(encodedManagedFields))
|
||||||
for manager, encodedVersionedSet := range encodedManagedFields {
|
for _, encodedVersionedSet := range encodedManagedFields {
|
||||||
|
manager, err := DecodeManager(&encodedVersionedSet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error decoding manager from %v: %v", encodedVersionedSet, err)
|
||||||
|
}
|
||||||
managedFields[manager], err = decodeVersionedSet(&encodedVersionedSet)
|
managedFields[manager], err = decodeVersionedSet(&encodedVersionedSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error decoding versioned set for %v: %v", manager, err)
|
return nil, fmt.Errorf("error decoding versioned set from %v: %v", encodedVersionedSet, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return managedFields, nil
|
return managedFields, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeVersionedSet(encodedVersionedSet *metav1.VersionedFields) (versionedSet *fieldpath.VersionedSet, err error) {
|
// DecodeManager creates a manager identifier string from a ManagedFieldsEntry
|
||||||
|
func DecodeManager(encodedManager *metav1.ManagedFieldsEntry) (manager string, err error) {
|
||||||
|
encodedManagerCopy := *encodedManager
|
||||||
|
|
||||||
|
// Never include the fields in the manager identifier
|
||||||
|
encodedManagerCopy.Fields = nil
|
||||||
|
|
||||||
|
// For appliers, don't include the APIVersion or Time in the manager identifier,
|
||||||
|
// so it will always have the same manager identifier each time it applied.
|
||||||
|
if encodedManager.Operation == metav1.ManagedFieldsOperationApply {
|
||||||
|
encodedManagerCopy.APIVersion = ""
|
||||||
|
encodedManagerCopy.Time = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the remaining fields to build the manager identifier
|
||||||
|
b, err := json.Marshal(&encodedManagerCopy)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error marshalling manager identifier: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeVersionedSet(encodedVersionedSet *metav1.ManagedFieldsEntry) (versionedSet *fieldpath.VersionedSet, err error) {
|
||||||
versionedSet = &fieldpath.VersionedSet{}
|
versionedSet = &fieldpath.VersionedSet{}
|
||||||
versionedSet.APIVersion = fieldpath.APIVersion(encodedVersionedSet.APIVersion)
|
versionedSet.APIVersion = fieldpath.APIVersion(encodedVersionedSet.APIVersion)
|
||||||
set, err := FieldsToSet(encodedVersionedSet.Fields)
|
|
||||||
|
fields := metav1.Fields{}
|
||||||
|
if encodedVersionedSet.Fields != nil {
|
||||||
|
fields = *encodedVersionedSet.Fields
|
||||||
|
}
|
||||||
|
set, err := FieldsToSet(fields)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error decoding set: %v", err)
|
return nil, fmt.Errorf("error decoding set: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -96,24 +130,42 @@ func decodeVersionedSet(encodedVersionedSet *metav1.VersionedFields) (versionedS
|
||||||
|
|
||||||
// encodeManagedFields converts ManagedFields from the the format used by
|
// encodeManagedFields converts ManagedFields from the the format used by
|
||||||
// sigs.k8s.io/structured-merge-diff to the the wire format (api format)
|
// sigs.k8s.io/structured-merge-diff to the the wire format (api format)
|
||||||
func encodeManagedFields(managedFields fieldpath.ManagedFields) (encodedManagedFields map[string]metav1.VersionedFields, err error) {
|
func encodeManagedFields(managedFields fieldpath.ManagedFields) (encodedManagedFields []metav1.ManagedFieldsEntry, err error) {
|
||||||
encodedManagedFields = make(map[string]metav1.VersionedFields, len(managedFields))
|
// Sort the keys so a predictable order will be used.
|
||||||
for manager, versionedSet := range managedFields {
|
managers := []string{}
|
||||||
v, err := encodeVersionedSet(versionedSet)
|
for manager := range managedFields {
|
||||||
|
managers = append(managers, manager)
|
||||||
|
}
|
||||||
|
sort.Strings(managers)
|
||||||
|
|
||||||
|
encodedManagedFields = []metav1.ManagedFieldsEntry{}
|
||||||
|
for _, manager := range managers {
|
||||||
|
versionedSet := managedFields[manager]
|
||||||
|
v, err := encodeManagerVersionedSet(manager, versionedSet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error encoding versioned set for %v: %v", manager, err)
|
return nil, fmt.Errorf("error encoding versioned set for %v: %v", manager, err)
|
||||||
}
|
}
|
||||||
encodedManagedFields[manager] = *v
|
encodedManagedFields = append(encodedManagedFields, *v)
|
||||||
}
|
}
|
||||||
return encodedManagedFields, nil
|
return encodedManagedFields, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func encodeVersionedSet(versionedSet *fieldpath.VersionedSet) (encodedVersionedSet *metav1.VersionedFields, err error) {
|
func encodeManagerVersionedSet(manager string, versionedSet *fieldpath.VersionedSet) (encodedVersionedSet *metav1.ManagedFieldsEntry, err error) {
|
||||||
encodedVersionedSet = &metav1.VersionedFields{}
|
encodedVersionedSet = &metav1.ManagedFieldsEntry{}
|
||||||
|
|
||||||
|
// Get as many fields as we can from the manager identifier
|
||||||
|
err = json.Unmarshal([]byte(manager), encodedVersionedSet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error unmarshalling manager identifier %v: %v", manager, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the APIVersion and Fields from the VersionedSet
|
||||||
encodedVersionedSet.APIVersion = string(versionedSet.APIVersion)
|
encodedVersionedSet.APIVersion = string(versionedSet.APIVersion)
|
||||||
encodedVersionedSet.Fields, err = SetToFields(*versionedSet.Set)
|
fields, err := SetToFields(*versionedSet.Set)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error encoding set: %v", err)
|
return nil, fmt.Errorf("error encoding set: %v", err)
|
||||||
}
|
}
|
||||||
|
encodedVersionedSet.Fields = &fields
|
||||||
|
|
||||||
return encodedVersionedSet, nil
|
return encodedVersionedSet, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,29 +29,36 @@ import (
|
||||||
// sigs.k8s.io/structured-merge-diff to the wire format (api format) and back
|
// sigs.k8s.io/structured-merge-diff to the wire format (api format) and back
|
||||||
func TestRoundTripManagedFields(t *testing.T) {
|
func TestRoundTripManagedFields(t *testing.T) {
|
||||||
tests := []string{
|
tests := []string{
|
||||||
`foo:
|
`- apiVersion: v1
|
||||||
apiVersion: v1
|
|
||||||
fields:
|
fields:
|
||||||
i:5:
|
|
||||||
f:i: {}
|
|
||||||
v:3:
|
v:3:
|
||||||
f:alsoPi: {}
|
f:alsoPi: {}
|
||||||
v:3.1415:
|
v:3.1415:
|
||||||
f:pi: {}
|
f:pi: {}
|
||||||
v:false:
|
v:false:
|
||||||
f:notTrue: {}
|
f:notTrue: {}
|
||||||
|
manager: foo
|
||||||
|
operation: Update
|
||||||
|
time: "2001-02-03T04:05:06Z"
|
||||||
|
- apiVersion: v1beta1
|
||||||
|
fields:
|
||||||
|
i:5:
|
||||||
|
f:i: {}
|
||||||
|
manager: foo
|
||||||
|
operation: Update
|
||||||
|
time: "2011-12-13T14:15:16Z"
|
||||||
`,
|
`,
|
||||||
`foo:
|
`- apiVersion: v1
|
||||||
apiVersion: v1
|
|
||||||
fields:
|
fields:
|
||||||
f:spec:
|
f:spec:
|
||||||
f:containers:
|
f:containers:
|
||||||
k:{"name":"c"}:
|
k:{"name":"c"}:
|
||||||
f:image: {}
|
f:image: {}
|
||||||
f:name: {}
|
f:name: {}
|
||||||
|
manager: foo
|
||||||
|
operation: Apply
|
||||||
`,
|
`,
|
||||||
`foo:
|
`- apiVersion: v1
|
||||||
apiVersion: v1
|
|
||||||
fields:
|
fields:
|
||||||
f:apiVersion: {}
|
f:apiVersion: {}
|
||||||
f:kind: {}
|
f:kind: {}
|
||||||
|
|
@ -77,9 +84,10 @@ func TestRoundTripManagedFields(t *testing.T) {
|
||||||
f:ports:
|
f:ports:
|
||||||
i:0:
|
i:0:
|
||||||
f:containerPort: {}
|
f:containerPort: {}
|
||||||
|
manager: foo
|
||||||
|
operation: Update
|
||||||
`,
|
`,
|
||||||
`foo:
|
`- apiVersion: v1
|
||||||
apiVersion: v1
|
|
||||||
fields:
|
fields:
|
||||||
f:allowVolumeExpansion: {}
|
f:allowVolumeExpansion: {}
|
||||||
f:apiVersion: {}
|
f:apiVersion: {}
|
||||||
|
|
@ -92,9 +100,10 @@ func TestRoundTripManagedFields(t *testing.T) {
|
||||||
f:secretName: {}
|
f:secretName: {}
|
||||||
f:secretNamespace: {}
|
f:secretNamespace: {}
|
||||||
f:provisioner: {}
|
f:provisioner: {}
|
||||||
|
manager: foo
|
||||||
|
operation: Apply
|
||||||
`,
|
`,
|
||||||
`foo:
|
`- apiVersion: v1
|
||||||
apiVersion: v1
|
|
||||||
fields:
|
fields:
|
||||||
f:apiVersion: {}
|
f:apiVersion: {}
|
||||||
f:kind: {}
|
f:kind: {}
|
||||||
|
|
@ -114,12 +123,14 @@ func TestRoundTripManagedFields(t *testing.T) {
|
||||||
f:name: {}
|
f:name: {}
|
||||||
f:served: {}
|
f:served: {}
|
||||||
f:storage: {}
|
f:storage: {}
|
||||||
|
manager: foo
|
||||||
|
operation: Update
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test, func(t *testing.T) {
|
t.Run(test, func(t *testing.T) {
|
||||||
var unmarshaled map[string]metav1.VersionedFields
|
var unmarshaled []metav1.ManagedFieldsEntry
|
||||||
if err := yaml.Unmarshal([]byte(test), &unmarshaled); err != nil {
|
if err := yaml.Unmarshal([]byte(test), &unmarshaled); err != nil {
|
||||||
t.Fatalf("did not expect yaml unmarshalling error but got: %v", err)
|
t.Fatalf("did not expect yaml unmarshalling error but got: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -141,3 +152,49 @@ func TestRoundTripManagedFields(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDecodeManager(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
managedFieldsEntry string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
managedFieldsEntry: `
|
||||||
|
apiVersion: v1
|
||||||
|
fields:
|
||||||
|
f:apiVersion: {}
|
||||||
|
manager: foo
|
||||||
|
operation: Update
|
||||||
|
time: "2001-02-03T04:05:06Z"
|
||||||
|
`,
|
||||||
|
expected: "{\"manager\":\"foo\",\"operation\":\"Update\",\"apiVersion\":\"v1\",\"time\":\"2001-02-03T04:05:06Z\"}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
managedFieldsEntry: `
|
||||||
|
apiVersion: v1
|
||||||
|
fields:
|
||||||
|
f:apiVersion: {}
|
||||||
|
manager: foo
|
||||||
|
operation: Apply
|
||||||
|
time: "2001-02-03T04:05:06Z"
|
||||||
|
`,
|
||||||
|
expected: "{\"manager\":\"foo\",\"operation\":\"Apply\"}",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.managedFieldsEntry, func(t *testing.T) {
|
||||||
|
var unmarshaled metav1.ManagedFieldsEntry
|
||||||
|
if err := yaml.Unmarshal([]byte(test.managedFieldsEntry), &unmarshaled); err != nil {
|
||||||
|
t.Fatalf("did not expect yaml unmarshalling error but got: %v", err)
|
||||||
|
}
|
||||||
|
decoded, err := DecodeManager(&unmarshaled)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("did not expect decoding error but got: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(decoded, test.expected) {
|
||||||
|
t.Fatalf("expected:\n%v\nbut got:\n%v", test.expected, decoded)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,21 +21,52 @@ import (
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"k8s.io/kube-openapi/pkg/util/proto"
|
"k8s.io/kube-openapi/pkg/util/proto"
|
||||||
"sigs.k8s.io/structured-merge-diff/typed"
|
"sigs.k8s.io/structured-merge-diff/typed"
|
||||||
|
"sigs.k8s.io/structured-merge-diff/value"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TypeConverter allows you to convert from runtime.Object to
|
// TypeConverter allows you to convert from runtime.Object to
|
||||||
// typed.TypedValue and the other way around.
|
// typed.TypedValue and the other way around.
|
||||||
type TypeConverter interface {
|
type TypeConverter interface {
|
||||||
NewTyped(schema.GroupVersionKind) (typed.TypedValue, error)
|
|
||||||
ObjectToTyped(runtime.Object) (typed.TypedValue, error)
|
ObjectToTyped(runtime.Object) (typed.TypedValue, error)
|
||||||
YAMLToTyped([]byte) (typed.TypedValue, error)
|
YAMLToTyped([]byte) (typed.TypedValue, error)
|
||||||
TypedToObject(typed.TypedValue) (runtime.Object, error)
|
TypedToObject(typed.TypedValue) (runtime.Object, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeducedTypeConverter is a TypeConverter for CRDs that don't have a
|
||||||
|
// schema. It does implement the same interface though (and create the
|
||||||
|
// same types of objects), so that everything can still work the same.
|
||||||
|
// CRDs are merged with all their fields being "atomic" (lists
|
||||||
|
// included).
|
||||||
|
//
|
||||||
|
// Note that this is not going to be sufficient for converting to/from
|
||||||
|
// CRDs that have a schema defined (we don't support that schema yet).
|
||||||
|
type DeducedTypeConverter struct{}
|
||||||
|
|
||||||
|
var _ TypeConverter = DeducedTypeConverter{}
|
||||||
|
|
||||||
|
// ObjectToTyped converts an object into a TypedValue with a "deduced type".
|
||||||
|
func (DeducedTypeConverter) ObjectToTyped(obj runtime.Object) (typed.TypedValue, error) {
|
||||||
|
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return typed.DeducedParseableType{}.FromUnstructured(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// YAMLToTyped parses a yaml object into a TypedValue with a "deduced type".
|
||||||
|
func (DeducedTypeConverter) YAMLToTyped(from []byte) (typed.TypedValue, error) {
|
||||||
|
return typed.DeducedParseableType{}.FromYAML(typed.YAMLObject(from))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypedToObject transforms the typed value into a runtime.Object. That
|
||||||
|
// is not specific to deduced type.
|
||||||
|
func (DeducedTypeConverter) TypedToObject(value typed.TypedValue) (runtime.Object, error) {
|
||||||
|
return valueToObject(value.AsValue())
|
||||||
|
}
|
||||||
|
|
||||||
type typeConverter struct {
|
type typeConverter struct {
|
||||||
parser *gvkParser
|
parser *gvkParser
|
||||||
}
|
}
|
||||||
|
|
@ -53,28 +84,15 @@ func NewTypeConverter(models proto.Models) (TypeConverter, error) {
|
||||||
return &typeConverter{parser: parser}, nil
|
return &typeConverter{parser: parser}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *typeConverter) NewTyped(gvk schema.GroupVersionKind) (typed.TypedValue, error) {
|
|
||||||
t := c.parser.Type(gvk)
|
|
||||||
if t == nil {
|
|
||||||
return typed.TypedValue{}, fmt.Errorf("no corresponding type for %v", gvk)
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := t.New()
|
|
||||||
if err != nil {
|
|
||||||
return typed.TypedValue{}, fmt.Errorf("new typed: %v", err)
|
|
||||||
}
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *typeConverter) ObjectToTyped(obj runtime.Object) (typed.TypedValue, error) {
|
func (c *typeConverter) ObjectToTyped(obj runtime.Object) (typed.TypedValue, error) {
|
||||||
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return typed.TypedValue{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
gvk := obj.GetObjectKind().GroupVersionKind()
|
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||||
t := c.parser.Type(gvk)
|
t := c.parser.Type(gvk)
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return typed.TypedValue{}, fmt.Errorf("no corresponding type for %v", gvk)
|
return nil, fmt.Errorf("no corresponding type for %v", gvk)
|
||||||
}
|
}
|
||||||
return t.FromUnstructured(u)
|
return t.FromUnstructured(u)
|
||||||
}
|
}
|
||||||
|
|
@ -83,14 +101,18 @@ func (c *typeConverter) YAMLToTyped(from []byte) (typed.TypedValue, error) {
|
||||||
unstructured := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
unstructured := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||||
|
|
||||||
if err := yaml.Unmarshal(from, &unstructured.Object); err != nil {
|
if err := yaml.Unmarshal(from, &unstructured.Object); err != nil {
|
||||||
return typed.TypedValue{}, fmt.Errorf("error decoding YAML: %v", err)
|
return nil, fmt.Errorf("error decoding YAML: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.ObjectToTyped(unstructured)
|
return c.ObjectToTyped(unstructured)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *typeConverter) TypedToObject(value typed.TypedValue) (runtime.Object, error) {
|
func (c *typeConverter) TypedToObject(value typed.TypedValue) (runtime.Object, error) {
|
||||||
vu := value.AsValue().ToUnstructured(false)
|
return valueToObject(value.AsValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
func valueToObject(value *value.Value) (runtime.Object, error) {
|
||||||
|
vu := value.ToUnstructured(false)
|
||||||
u, ok := vu.(map[string]interface{})
|
u, ok := vu.(map[string]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("failed to convert typed to unstructured: want map, got %T", vu)
|
return nil, fmt.Errorf("failed to convert typed to unstructured: want map, got %T", vu)
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,7 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
||||||
name: name,
|
name: name,
|
||||||
patchType: patchType,
|
patchType: patchType,
|
||||||
patchBytes: patchBytes,
|
patchBytes: patchBytes,
|
||||||
|
userAgent: req.UserAgent(),
|
||||||
|
|
||||||
trace: trace,
|
trace: trace,
|
||||||
}
|
}
|
||||||
|
|
@ -268,6 +269,7 @@ type patcher struct {
|
||||||
name string
|
name string
|
||||||
patchType types.PatchType
|
patchType types.PatchType
|
||||||
patchBytes []byte
|
patchBytes []byte
|
||||||
|
userAgent string
|
||||||
|
|
||||||
trace *utiltrace.Trace
|
trace *utiltrace.Trace
|
||||||
|
|
||||||
|
|
@ -309,7 +311,7 @@ func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (r
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.fieldManager != nil {
|
if p.fieldManager != nil {
|
||||||
if objToUpdate, err = p.fieldManager.Update(currentObject, objToUpdate, "jsonPatcher"); err != nil {
|
if objToUpdate, err = p.fieldManager.Update(currentObject, objToUpdate, prefixFromUserAgent(p.userAgent)); err != nil {
|
||||||
return nil, fmt.Errorf("failed to update object managed fields: %v", err)
|
return nil, fmt.Errorf("failed to update object managed fields: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -371,7 +373,7 @@ func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (ru
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.fieldManager != nil {
|
if p.fieldManager != nil {
|
||||||
if newObj, err = p.fieldManager.Update(currentObject, newObj, "smPatcher"); err != nil {
|
if newObj, err = p.fieldManager.Update(currentObject, newObj, prefixFromUserAgent(p.userAgent)); err != nil {
|
||||||
return nil, fmt.Errorf("failed to update object managed fields: %v", err)
|
return nil, fmt.Errorf("failed to update object managed fields: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -395,6 +397,9 @@ func (p *applyPatcher) applyPatchToCurrentObject(obj runtime.Object) (runtime.Ob
|
||||||
if p.options.Force != nil {
|
if p.options.Force != nil {
|
||||||
force = *p.options.Force
|
force = *p.options.Force
|
||||||
}
|
}
|
||||||
|
if p.fieldManager == nil {
|
||||||
|
panic("FieldManager must be installed to run apply")
|
||||||
|
}
|
||||||
return p.fieldManager.Apply(obj, p.patch, force)
|
return p.fieldManager.Apply(obj, p.patch, force)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -123,8 +123,8 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
|
||||||
userInfo, _ := request.UserFrom(ctx)
|
userInfo, _ := request.UserFrom(ctx)
|
||||||
transformers := []rest.TransformFunc{}
|
transformers := []rest.TransformFunc{}
|
||||||
if scope.FieldManager != nil {
|
if scope.FieldManager != nil {
|
||||||
transformers = append(transformers, func(_ context.Context, liveObj, newObj runtime.Object) (runtime.Object, error) {
|
transformers = append(transformers, func(_ context.Context, newObj, liveObj runtime.Object) (runtime.Object, error) {
|
||||||
if obj, err = scope.FieldManager.Update(liveObj, newObj, "update"); err != nil {
|
if obj, err = scope.FieldManager.Update(liveObj, newObj, prefixFromUserAgent(req.UserAgent())); err != nil {
|
||||||
return nil, fmt.Errorf("failed to update object managed fields: %v", err)
|
return nil, fmt.Errorf("failed to update object managed fields: %v", err)
|
||||||
}
|
}
|
||||||
return obj, nil
|
return obj, nil
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,7 @@ func TestPatchApply(t *testing.T) {
|
||||||
if simpleStorage.updated.Other != "bar" {
|
if simpleStorage.updated.Other != "bar" {
|
||||||
t.Errorf(`Merge should have kept initial "bar" value for Other: %v`, simpleStorage.updated.Other)
|
t.Errorf(`Merge should have kept initial "bar" value for Other: %v`, simpleStorage.updated.Other)
|
||||||
}
|
}
|
||||||
if _, ok := simpleStorage.updated.ObjectMeta.ManagedFields["default"]; !ok {
|
if len(simpleStorage.updated.ObjectMeta.ManagedFields) == 0 {
|
||||||
t.Errorf(`Expected managedFields field to be set, but is empty`)
|
t.Errorf(`Expected managedFields field to be set, but is empty`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -230,11 +230,11 @@ func TestApplyAddsGVK(t *testing.T) {
|
||||||
}
|
}
|
||||||
// TODO: Need to fix this
|
// TODO: Need to fix this
|
||||||
expected := `{"apiVersion":"test.group/version","kind":"Simple","labels":{"test":"yes"},"metadata":{"name":"id"}}`
|
expected := `{"apiVersion":"test.group/version","kind":"Simple","labels":{"test":"yes"},"metadata":{"name":"id"}}`
|
||||||
if simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion != expected {
|
if simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion != expected {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
`Expected managedFields field to be %q, got %q`,
|
`Expected managedFields field to be %q, got %q`,
|
||||||
expected,
|
expected,
|
||||||
simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion,
|
simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -265,11 +265,11 @@ func TestApplyCreatesWithManagedFields(t *testing.T) {
|
||||||
}
|
}
|
||||||
// TODO: Need to fix this
|
// TODO: Need to fix this
|
||||||
expected := `{"apiVersion":"test.group/version","kind":"Simple","labels":{"test":"yes"},"metadata":{"name":"id"}}`
|
expected := `{"apiVersion":"test.group/version","kind":"Simple","labels":{"test":"yes"},"metadata":{"name":"id"}}`
|
||||||
if simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion != expected {
|
if simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion != expected {
|
||||||
t.Errorf(
|
t.Errorf(
|
||||||
`Expected managedFields field to be %q, got %q`,
|
`Expected managedFields field to be %q, got %q`,
|
||||||
expected,
|
expected,
|
||||||
simpleStorage.updated.ObjectMeta.ManagedFields["default"].APIVersion,
|
simpleStorage.updated.ObjectMeta.ManagedFields[0].APIVersion,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue