Remove ServerSideApply

k3s-v1.14.0
Darren Shepherd 2019-04-06 18:11:50 -07:00
parent c8c0c66b49
commit 958fd3a605
34 changed files with 10 additions and 2632 deletions

View File

@ -374,7 +374,6 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
genericfeatures.AdvancedAuditing: {Default: true, PreRelease: utilfeature.GA},
genericfeatures.APIListChunking: {Default: true, PreRelease: utilfeature.Beta},
genericfeatures.DryRun: {Default: true, PreRelease: utilfeature.Beta},
genericfeatures.ServerSideApply: {Default: false, PreRelease: utilfeature.Alpha},
// inherited features from apiextensions-apiserver, relisted here to get a conflict if it is changed
// unintentionally on either side:

View File

@ -66,9 +66,6 @@ type ApplyOptions struct {
DeleteFlags *delete.DeleteFlags
DeleteOptions *delete.DeleteOptions
ServerSideApply bool
ForceConflicts bool
FieldManager string
Selector string
DryRun bool
ServerDryRun bool
@ -184,7 +181,6 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions
cmd.Flags().BoolVar(&o.ServerDryRun, "server-dry-run", o.ServerDryRun, "If true, request will be sent to server with dry-run flag, which means the modifications won't be persisted. This is an alpha feature and flag.")
cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it. Warning: --dry-run cannot accurately output the result of merging the local manifest and the server-side data. Use --server-dry-run to get the merged result instead.")
cmdutil.AddIncludeUninitializedFlag(cmd)
cmdutil.AddServerSideApplyFlags(cmd)
// apply subcommands
cmd.AddCommand(NewCmdApplyViewLastApplied(f, ioStreams))
@ -195,19 +191,8 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions
}
func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
o.ServerSideApply = cmdutil.GetServerSideApplyFlag(cmd)
o.ForceConflicts = cmdutil.GetForceConflictsFlag(cmd)
o.FieldManager = cmdutil.GetFieldManagerFlag(cmd)
o.DryRun = cmdutil.GetDryRunFlag(cmd)
if o.ForceConflicts && !o.ServerSideApply {
return fmt.Errorf("--experimental-force-conflicts only works with --experimental-server-side")
}
if o.DryRun && o.ServerSideApply {
return fmt.Errorf("--dry-run doesn't work with --experimental-server-side (did you mean --server-dry-run instead?)")
}
if o.DryRun && o.ServerDryRun {
return fmt.Errorf("--dry-run and --server-dry-run can't be used together")
}
@ -388,51 +373,6 @@ func (o *ApplyOptions) Run() error {
klog.V(4).Infof("error recording current command: %v", err)
}
if o.ServerSideApply {
// Send the full object to be applied on the server side.
data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, info.Object)
if err != nil {
return cmdutil.AddSourceToErr("serverside-apply", info.Source, err)
}
options := metav1.PatchOptions{
Force: &o.ForceConflicts,
FieldManager: o.FieldManager,
}
if o.ServerDryRun {
options.DryRun = []string{metav1.DryRunAll}
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(
info.Namespace,
info.Name,
types.ApplyPatchType,
data,
&options,
)
if err == nil {
info.Refresh(obj, true)
metadata, err := meta.Accessor(info.Object)
if err != nil {
return err
}
visitedUids.Insert(string(metadata.GetUID()))
count++
if len(output) > 0 && !shortOutput {
objs = append(objs, info.Object)
return nil
}
printer, err := o.ToPrinter("serverside-applied")
if err != nil {
return err
}
return printer.PrintObj(info.Object, o.Out)
} else if !isIncompatibleServerError(err) {
return err
}
// If we're talking to a server which does not implement server-side apply,
// continue with the client side apply after this block.
klog.Warningf("serverside-apply incompatible server: %v", err)
}
// Get the modified configuration of the object. Embed the result
// as an annotation in the modified configuration, so that it will appear
// in the patch sent to the server.

View File

@ -30,7 +30,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/discovery"
@ -71,9 +70,6 @@ const maxRetries = 4
type DiffOptions struct {
FilenameOptions resource.FilenameOptions
ServerSideApply bool
ForceConflicts bool
OpenAPISchema openapi.Resources
DiscoveryClient discovery.DiscoveryInterface
DynamicClient dynamic.Interface
@ -117,7 +113,6 @@ func NewCmdDiff(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C
usage := "contains the configuration to diff"
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmdutil.AddServerSideApplyFlags(cmd)
return cmd
}
@ -255,8 +250,6 @@ type InfoObject struct {
Encoder runtime.Encoder
OpenAPI openapi.Resources
Force bool
ServerSideApply bool
ForceConflicts bool
}
var _ Object = &InfoObject{}
@ -269,24 +262,6 @@ func (obj InfoObject) Live() runtime.Object {
// Returns the "merged" object, as it would look like if applied or
// created.
func (obj InfoObject) Merged() (runtime.Object, error) {
if obj.ServerSideApply {
data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj.LocalObj)
if err != nil {
return nil, err
}
options := metav1.PatchOptions{
Force: &obj.ForceConflicts,
DryRun: []string{metav1.DryRunAll},
}
return resource.NewHelper(obj.Info.Client, obj.Info.Mapping).Patch(
obj.Info.Namespace,
obj.Info.Name,
types.ApplyPatchType,
data,
&options,
)
}
// Build the patcher, and then apply the patch with dry-run, unless the object doesn't exist, in which case we need to create it.
if obj.Live() == nil {
// Dry-run create if the object doesn't exist.
@ -399,17 +374,9 @@ func (o *DiffOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
return err
}
o.ServerSideApply = cmdutil.GetServerSideApplyFlag(cmd)
o.ForceConflicts = cmdutil.GetForceConflictsFlag(cmd)
if o.ForceConflicts && !o.ServerSideApply {
return fmt.Errorf("--experimental-force-conflicts only works with --experimental-server-side")
}
if !o.ServerSideApply {
o.OpenAPISchema, err = f.OpenAPISchema()
if err != nil {
return err
}
o.OpenAPISchema, err = f.OpenAPISchema()
if err != nil {
return err
}
o.DiscoveryClient, err = f.ToDiscoveryClient()
@ -490,8 +457,6 @@ func (o *DiffOptions) Run() error {
Encoder: scheme.DefaultJSONEncoder(),
OpenAPI: o.OpenAPISchema,
Force: force,
ServerSideApply: o.ServerSideApply,
ForceConflicts: o.ForceConflicts,
}
err = differ.Diff(obj, printer)

View File

@ -405,12 +405,6 @@ func AddDryRunFlag(cmd *cobra.Command) {
cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it.")
}
func AddServerSideApplyFlags(cmd *cobra.Command) {
cmd.Flags().Bool("experimental-server-side", false, "If true, apply runs in the server instead of the client. This is an alpha feature and flag.")
cmd.Flags().Bool("experimental-force-conflicts", false, "If true, server-side apply will force the changes against conflicts. This is an alpha feature and flag.")
cmd.Flags().String("experimental-field-manager", "kubectl", "Name of the manager used to track field ownership. This is an alpha feature and flag.")
}
func AddIncludeUninitializedFlag(cmd *cobra.Command) {
cmd.Flags().Bool("include-uninitialized", false, `If true, the kubectl command applies to uninitialized objects. If explicitly set to false, this flag overrides other flags that make the kubectl commands apply to uninitialized objects, e.g., "--all". Objects with empty metadata.initializers are regarded as initialized.`)
cmd.Flags().MarkDeprecated("include-uninitialized", "The Initializers feature has been removed. This flag is now a no-op, and will be removed in v1.15")
@ -484,18 +478,6 @@ func DumpReaderToFile(reader io.Reader, filename string) error {
return nil
}
func GetServerSideApplyFlag(cmd *cobra.Command) bool {
return GetFlagBool(cmd, "experimental-server-side")
}
func GetForceConflictsFlag(cmd *cobra.Command) bool {
return GetFlagBool(cmd, "experimental-force-conflicts")
}
func GetFieldManagerFlag(cmd *cobra.Command) string {
return GetFlagString(cmd, "experimental-field-manager")
}
func GetDryRunFlag(cmd *cobra.Command) bool {
return GetFlagBool(cmd, "dry-run")
}

View File

@ -55,11 +55,9 @@ import (
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/handlers"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/apiserver/pkg/endpoints/metrics"
apirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/storage/storagebackend"
@ -234,9 +232,6 @@ func (r *crdHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
string(types.JSONPatchType),
string(types.MergePatchType),
}
if utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
supportedTypes = append(supportedTypes, string(types.ApplyPatchType))
}
var handler http.HandlerFunc
subresources, err := apiextensions.GetSubresourcesForVersion(crd, requestInfo.APIVersion)
@ -575,16 +570,6 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
Authorizer: r.authorizer,
}
if utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
reqScope := requestScopes[v.Name]
reqScope.FieldManager = fieldmanager.NewCRDFieldManager(
reqScope.Convertor,
reqScope.Defaulter,
reqScope.Kind.GroupVersion(),
reqScope.HubGroupVersion,
)
requestScopes[v.Name] = reqScope
}
// override scaleSpec subresource values
// shallow copy

View File

@ -109,15 +109,8 @@ func ValidateUpdateOptions(options *metav1.UpdateOptions) field.ErrorList {
func ValidatePatchOptions(options *metav1.PatchOptions, patchType types.PatchType) field.ErrorList {
allErrs := field.ErrorList{}
if patchType != types.ApplyPatchType {
if options.Force != nil {
allErrs = append(allErrs, field.Forbidden(field.NewPath("force"), "may not be specified for non-apply patch"))
}
} else {
if options.FieldManager == "" {
// This field is defaulted to "kubectl" by kubectl, but HAS TO be explicitly set by controllers.
allErrs = append(allErrs, field.Required(field.NewPath("fieldManager"), "is required for apply patch"))
}
if options.Force != nil {
allErrs = append(allErrs, field.Forbidden(field.NewPath("force"), "may not be specified for non-apply patch"))
}
allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...)
allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...)

View File

@ -25,5 +25,4 @@ const (
JSONPatchType PatchType = "application/json-patch+json"
MergePatchType PatchType = "application/merge-patch+json"
StrategicMergePatchType PatchType = "application/strategic-merge-patch+json"
ApplyPatchType PatchType = "application/apply-patch+yaml"
)

View File

@ -137,20 +137,6 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
}
}
if scope.FieldManager != nil {
liveObj, err := scope.Creater.New(scope.Kind)
if err != nil {
scope.err(fmt.Errorf("failed to create new object (Create for %v): %v", scope.Kind, err), w, req)
return
}
obj, err = scope.FieldManager.Update(liveObj, obj, managerOrUserAgent(options.FieldManager, req.UserAgent()))
if err != nil {
scope.err(fmt.Errorf("failed to update object (Create for %v) managed fields: %v", scope.Kind, err), w, req)
return
}
}
trace.Step("About to store object in database")
result, err := finishRequest(timeout, func() (runtime.Object, error) {
return r.Create(

View File

@ -1,52 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["fieldmanager.go"],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager",
importpath = "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/merge:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["fieldmanager_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
],
)

View File

@ -1,5 +0,0 @@
approvers:
- jennybuckley
- apelisse
reviewers:
- kwiesmueller

View File

@ -1,270 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fieldmanager
import (
"fmt"
"time"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
openapiproto "k8s.io/kube-openapi/pkg/util/proto"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/merge"
"sigs.k8s.io/yaml"
)
// FieldManager updates the managed fields and merge applied
// configurations.
type FieldManager struct {
typeConverter internal.TypeConverter
objectConverter runtime.ObjectConvertor
objectDefaulter runtime.ObjectDefaulter
groupVersion schema.GroupVersion
hubVersion schema.GroupVersion
updater merge.Updater
}
// NewFieldManager creates a new FieldManager that merges apply requests
// and update managed fields for other types of requests.
func NewFieldManager(models openapiproto.Models, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion) (*FieldManager, error) {
typeConverter, err := internal.NewTypeConverter(models)
if err != nil {
return nil, err
}
return &FieldManager{
typeConverter: typeConverter,
objectConverter: objectConverter,
objectDefaulter: objectDefaulter,
groupVersion: gv,
hubVersion: hub,
updater: merge.Updater{
Converter: internal.NewVersionConverter(typeConverter, objectConverter, hub),
},
}, 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.NewCRDVersionConverter(internal.DeducedTypeConverter{}, objectConverter, hub),
},
}
}
// Update is used when the object has already been merged (non-apply
// use-case), and simply updates the managed fields in the output
// object.
func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (runtime.Object, error) {
// If the object doesn't have metadata, we should just return without trying to
// set the managedFields at all, so creates/updates/patches will work normally.
if _, err := meta.Accessor(newObj); err != nil {
return newObj, nil
}
// First try to decode the managed fields provided in the update,
// This is necessary to allow directly updating managed fields.
managed, err := internal.DecodeObjectManagedFields(newObj)
// If the managed field is empty or we failed to decode it,
// let's try the live object. This is to prevent clients who
// don't understand managedFields from deleting it accidentally.
if err != nil || len(managed) == 0 {
managed, err = internal.DecodeObjectManagedFields(liveObj)
if err != nil {
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
}
}
newObjVersioned, err := f.toVersioned(newObj)
if err != nil {
return nil, fmt.Errorf("failed to convert new object to proper version: %v", err)
}
liveObjVersioned, err := f.toVersioned(liveObj)
if err != nil {
return nil, fmt.Errorf("failed to convert live object to proper version: %v", err)
}
internal.RemoveObjectManagedFields(liveObjVersioned)
internal.RemoveObjectManagedFields(newObjVersioned)
newObjTyped, err := f.typeConverter.ObjectToTyped(newObjVersioned)
if err != nil {
return nil, fmt.Errorf("failed to create typed new object: %v", err)
}
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
if err != nil {
return nil, fmt.Errorf("failed to create typed live object: %v", err)
}
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)
if err != nil {
return nil, fmt.Errorf("failed to update ManagedFields: %v", err)
}
managed = f.stripFields(managed, manager)
if err := internal.EncodeObjectManagedFields(newObj, managed); err != nil {
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
}
return newObj, nil
}
// Apply is used when server-side apply is called, as it merges the
// object and update the managed fields.
func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, fieldManager string, force bool) (runtime.Object, error) {
// If the object doesn't have metadata, apply isn't allowed.
if _, err := meta.Accessor(liveObj); err != nil {
return nil, fmt.Errorf("couldn't get accessor: %v", err)
}
managed, err := internal.DecodeObjectManagedFields(liveObj)
if err != nil {
return nil, fmt.Errorf("failed to decode managed fields: %v", err)
}
// Check that the patch object has the same version as the live object
patchObj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal(patch, &patchObj.Object); err != nil {
return nil, fmt.Errorf("error decoding YAML: %v", err)
}
if patchObj.GetAPIVersion() != f.groupVersion.String() {
return nil,
errors.NewBadRequest(
fmt.Sprintf("Incorrect version specified in apply patch. "+
"Specified patch version: %s, expected: %s",
patchObj.GetAPIVersion(), f.groupVersion.String()))
}
liveObjVersioned, err := f.toVersioned(liveObj)
if err != nil {
return nil, fmt.Errorf("failed to convert live object to proper version: %v", err)
}
internal.RemoveObjectManagedFields(liveObjVersioned)
patchObjTyped, err := f.typeConverter.YAMLToTyped(patch)
if err != nil {
return nil, fmt.Errorf("failed to create typed patch object: %v", err)
}
liveObjTyped, err := f.typeConverter.ObjectToTyped(liveObjVersioned)
if err != nil {
return nil, fmt.Errorf("failed to create typed live object: %v", err)
}
manager, err := f.buildManagerInfo(fieldManager, metav1.ManagedFieldsOperationApply)
if err != nil {
return nil, fmt.Errorf("failed to build manager identifier: %v", err)
}
apiVersion := fieldpath.APIVersion(f.groupVersion.String())
newObjTyped, managed, err := f.updater.Apply(liveObjTyped, patchObjTyped, apiVersion, managed, manager, force)
if err != nil {
if conflicts, ok := err.(merge.Conflicts); ok {
return nil, internal.NewConflictError(conflicts)
}
return nil, err
}
managed = f.stripFields(managed, manager)
newObj, err := f.typeConverter.TypedToObject(newObjTyped)
if err != nil {
return nil, fmt.Errorf("failed to convert new typed object to object: %v", err)
}
if err := internal.EncodeObjectManagedFields(newObj, managed); err != nil {
return nil, fmt.Errorf("failed to encode managed fields: %v", err)
}
newObjVersioned, err := f.toVersioned(newObj)
if err != nil {
return nil, fmt.Errorf("failed to convert new object to proper version: %v", err)
}
f.objectDefaulter.Default(newObjVersioned)
newObjUnversioned, err := f.toUnversioned(newObjVersioned)
if err != nil {
return nil, fmt.Errorf("failed to convert to unversioned: %v", err)
}
return newObjUnversioned, nil
}
func (f *FieldManager) toVersioned(obj runtime.Object) (runtime.Object, error) {
return f.objectConverter.ConvertToVersion(obj, f.groupVersion)
}
func (f *FieldManager) toUnversioned(obj runtime.Object) (runtime.Object, error) {
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.BuildManagerIdentifier(&managerInfo)
}
// stripSet is the list of fields that should never be part of a mangedFields.
var stripSet = fieldpath.NewSet(
fieldpath.MakePathOrDie("apiVersion"),
fieldpath.MakePathOrDie("kind"),
fieldpath.MakePathOrDie("metadata", "name"),
fieldpath.MakePathOrDie("metadata", "namespace"),
fieldpath.MakePathOrDie("metadata", "creationTimestamp"),
fieldpath.MakePathOrDie("metadata", "selfLink"),
fieldpath.MakePathOrDie("metadata", "uid"),
fieldpath.MakePathOrDie("metadata", "clusterName"),
fieldpath.MakePathOrDie("metadata", "generation"),
fieldpath.MakePathOrDie("metadata", "managedFields"),
fieldpath.MakePathOrDie("metadata", "resourceVersion"),
)
// stripFields removes a predefined set of paths found in typed from managed and returns the updated ManagedFields
func (f *FieldManager) stripFields(managed fieldpath.ManagedFields, manager string) fieldpath.ManagedFields {
vs, ok := managed[manager]
if ok {
if vs == nil {
panic(fmt.Sprintf("Found unexpected nil manager which should never happen: %s", manager))
}
vs.Set = vs.Set.Difference(stripSet)
if vs.Set.Empty() {
delete(managed, manager)
}
}
return managed
}

View File

@ -1,175 +0,0 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package fieldmanager_test
import (
"errors"
"net/http"
"testing"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
)
type fakeObjectConvertor struct{}
func (c *fakeObjectConvertor) Convert(in, out, context interface{}) error {
out = in
return nil
}
func (c *fakeObjectConvertor) ConvertToVersion(in runtime.Object, _ runtime.GroupVersioner) (runtime.Object, error) {
return in, nil
}
func (c *fakeObjectConvertor) ConvertFieldLabel(_ schema.GroupVersionKind, _, _ string) (string, string, error) {
return "", "", errors.New("not implemented")
}
type fakeObjectDefaulter struct{}
func (d *fakeObjectDefaulter) Default(in runtime.Object) {}
func NewTestFieldManager(t *testing.T) *fieldmanager.FieldManager {
gv := schema.GroupVersion{
Group: "apps",
Version: "v1",
}
return fieldmanager.NewCRDFieldManager(
&fakeObjectConvertor{},
&fakeObjectDefaulter{},
gv,
gv,
)
}
func TestFieldManagerCreation(t *testing.T) {
if NewTestFieldManager(t) == nil {
t.Fatal("failed to create FieldManager")
}
}
func TestApplyStripsFields(t *testing.T) {
f := NewTestFieldManager(t)
obj := &corev1.Pod{}
newObj, err := f.Apply(obj, []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "b",
"namespace": "b",
"creationTimestamp": "2016-05-19T09:59:00Z",
"selfLink": "b",
"uid": "b",
"clusterName": "b",
"generation": 0,
"managedFields": [{
"manager": "apply",
"operation": "Apply",
"apiVersion": "apps/v1",
"fields": {
"f:metadata": {
"f:labels": {
"f:test-label": {}
}
}
}
}],
"resourceVersion": "b"
}
}`), "fieldmanager_test", false)
if err != nil {
t.Fatalf("failed to apply object: %v", err)
}
accessor, err := meta.Accessor(newObj)
if err != nil {
t.Fatalf("couldn't get accessor: %v", err)
}
if m := accessor.GetManagedFields(); len(m) != 0 {
t.Fatalf("fields did not get stripped on apply: %v", m)
}
}
func TestVersionCheck(t *testing.T) {
f := NewTestFieldManager(t)
obj := &corev1.Pod{}
// patch has 'apiVersion: apps/v1' and live version is apps/v1 -> no errors
_, err := f.Apply(obj, []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
}`), "fieldmanager_test", false)
if err != nil {
t.Fatalf("failed to apply object: %v", err)
}
// patch has 'apiVersion: apps/v2' but live version is apps/v1 -> error
_, err = f.Apply(obj, []byte(`{
"apiVersion": "apps/v2",
"kind": "Deployment",
}`), "fieldmanager_test", false)
if err == nil {
t.Fatalf("expected an error from mismatched patch and live versions")
}
switch typ := err.(type) {
default:
t.Fatalf("expected error to be of type %T was %T", apierrors.StatusError{}, typ)
case apierrors.APIStatus:
if typ.Status().Code != http.StatusBadRequest {
t.Fatalf("expected status code to be %d but was %d",
http.StatusBadRequest, typ.Status().Code)
}
}
}
func TestApplyDoesNotStripLabels(t *testing.T) {
f := NewTestFieldManager(t)
obj := &corev1.Pod{}
newObj, err := f.Apply(obj, []byte(`{
"apiVersion": "apps/v1",
"kind": "Pod",
"metadata": {
"labels": {
"a": "b"
},
}
}`), "fieldmanager_test", false)
if err != nil {
t.Fatalf("failed to apply object: %v", err)
}
accessor, err := meta.Accessor(newObj)
if err != nil {
t.Fatalf("couldn't get accessor: %v", err)
}
if m := accessor.GetManagedFields(); len(m) != 1 {
t.Fatalf("labels shouldn't get stripped on apply: %v", m)
}
}

View File

@ -1,72 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"conflict.go",
"fields.go",
"gvkparser.go",
"managedfields.go",
"pathelement.go",
"typeconverter.go",
"versionconverter.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal",
importpath = "k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/schemaconv:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/merge:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/typed:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/value:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"conflict_test.go",
"fields_test.go",
"managedfields_test.go",
"pathelement_test.go",
"typeconverter_test.go",
"versionconverter_test.go",
],
data = ["//api/openapi-spec"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/util/proto/testing:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/fieldpath:go_default_library",
"//vendor/sigs.k8s.io/structured-merge-diff/merge:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -1,82 +0,0 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
import (
"encoding/json"
"fmt"
"sort"
"strings"
"time"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/merge"
)
// NewConflictError returns an error including details on the requests apply conflicts
func NewConflictError(conflicts merge.Conflicts) *errors.StatusError {
causes := []metav1.StatusCause{}
for _, conflict := range conflicts {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldManagerConflict,
Message: fmt.Sprintf("conflict with %v", printManager(conflict.Manager)),
Field: conflict.Path.String(),
})
}
return errors.NewApplyConflict(causes, getConflictMessage(conflicts))
}
func getConflictMessage(conflicts merge.Conflicts) string {
if len(conflicts) == 1 {
return fmt.Sprintf("Apply failed with 1 conflict: conflict with %v: %v", printManager(conflicts[0].Manager), conflicts[0].Path)
}
m := map[string][]fieldpath.Path{}
for _, conflict := range conflicts {
m[conflict.Manager] = append(m[conflict.Manager], conflict.Path)
}
uniqueManagers := []string{}
for manager := range m {
uniqueManagers = append(uniqueManagers, manager)
}
// Print conflicts by sorted managers.
sort.Strings(uniqueManagers)
messages := []string{}
for _, manager := range uniqueManagers {
messages = append(messages, fmt.Sprintf("conflicts with %v:", printManager(manager)))
for _, path := range m[manager] {
messages = append(messages, fmt.Sprintf("- %v", path))
}
}
return fmt.Sprintf("Apply failed with %d conflicts: %s", len(conflicts), strings.Join(messages, "\n"))
}
func printManager(manager string) string {
encodedManager := &metav1.ManagedFieldsEntry{}
if err := json.Unmarshal([]byte(manager), encodedManager); err != nil {
return fmt.Sprintf("%q", manager)
}
if encodedManager.Operation == metav1.ManagedFieldsOperationUpdate {
return fmt.Sprintf("%q using %v at %v", encodedManager.Manager, encodedManager.APIVersion, encodedManager.Time.UTC().Format(time.RFC3339))
}
return fmt.Sprintf("%q", encodedManager.Manager)
}

View File

@ -1,106 +0,0 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal_test
import (
"net/http"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/merge"
)
// TestNewConflictError tests that NewConflictError creates the correct StatusError for a given smd Conflicts
func TestNewConflictError(t *testing.T) {
testCases := []struct {
conflict merge.Conflicts
expected *errors.StatusError
}{
{
conflict: merge.Conflicts{
merge.Conflict{
Manager: `{"manager":"foo","operation":"Update","apiVersion":"v1","time":"2001-02-03T04:05:06Z"}`,
Path: fieldpath.MakePathOrDie("spec", "replicas"),
},
},
expected: &errors.StatusError{
ErrStatus: metav1.Status{
Status: metav1.StatusFailure,
Code: http.StatusConflict,
Reason: metav1.StatusReasonConflict,
Details: &metav1.StatusDetails{
Causes: []metav1.StatusCause{
{
Type: metav1.CauseTypeFieldManagerConflict,
Message: `conflict with "foo" using v1 at 2001-02-03T04:05:06Z`,
Field: ".spec.replicas",
},
},
},
Message: `Apply failed with 1 conflict: conflict with "foo" using v1 at 2001-02-03T04:05:06Z: .spec.replicas`,
},
},
},
{
conflict: merge.Conflicts{
merge.Conflict{
Manager: `{"manager":"foo","operation":"Update","apiVersion":"v1","time":"2001-02-03T04:05:06Z"}`,
Path: fieldpath.MakePathOrDie("spec", "replicas"),
},
merge.Conflict{
Manager: `{"manager":"bar","operation":"Apply"}`,
Path: fieldpath.MakePathOrDie("metadata", "labels", "app"),
},
},
expected: &errors.StatusError{
ErrStatus: metav1.Status{
Status: metav1.StatusFailure,
Code: http.StatusConflict,
Reason: metav1.StatusReasonConflict,
Details: &metav1.StatusDetails{
Causes: []metav1.StatusCause{
{
Type: metav1.CauseTypeFieldManagerConflict,
Message: `conflict with "foo" using v1 at 2001-02-03T04:05:06Z`,
Field: ".spec.replicas",
},
{
Type: metav1.CauseTypeFieldManagerConflict,
Message: `conflict with "bar"`,
Field: ".metadata.labels.app",
},
},
},
Message: `Apply failed with 2 conflicts: conflicts with "bar":
- .metadata.labels.app
conflicts with "foo" using v1 at 2001-02-03T04:05:06Z:
- .spec.replicas`,
},
},
},
}
for _, tc := range testCases {
actual := internal.NewConflictError(tc.conflict)
if !reflect.DeepEqual(tc.expected, actual) {
t.Errorf("Expected to get\n%+v\nbut got\n%+v", tc.expected.ErrStatus, actual.ErrStatus)
}
}
}

View File

@ -1,95 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/structured-merge-diff/fieldpath"
)
func newFields() metav1.Fields {
return metav1.Fields{Map: map[string]metav1.Fields{}}
}
func fieldsSet(f metav1.Fields, path fieldpath.Path, set *fieldpath.Set) error {
if len(f.Map) == 0 {
set.Insert(path)
}
for k := range f.Map {
if k == "." {
set.Insert(path)
continue
}
pe, err := NewPathElement(k)
if err != nil {
return err
}
path = append(path, pe)
err = fieldsSet(f.Map[k], path, set)
if err != nil {
return err
}
path = path[:len(path)-1]
}
return nil
}
// FieldsToSet creates a set paths from an input trie of fields
func FieldsToSet(f metav1.Fields) (fieldpath.Set, error) {
set := fieldpath.Set{}
return set, fieldsSet(f, fieldpath.Path{}, &set)
}
func removeUselessDots(f metav1.Fields) metav1.Fields {
if _, ok := f.Map["."]; ok && len(f.Map) == 1 {
delete(f.Map, ".")
return f
}
for k, tf := range f.Map {
f.Map[k] = removeUselessDots(tf)
}
return f
}
// SetToFields creates a trie of fields from an input set of paths
func SetToFields(s fieldpath.Set) (metav1.Fields, error) {
var err error
f := newFields()
s.Iterate(func(path fieldpath.Path) {
if err != nil {
return
}
tf := f
for _, pe := range path {
var str string
str, err = PathElementString(pe)
if err != nil {
break
}
if _, ok := tf.Map[str]; ok {
tf = tf.Map[str]
} else {
tf.Map[str] = newFields()
tf = tf.Map[str]
}
}
tf.Map["."] = newFields()
})
f = removeUselessDots(f)
return f, err
}

View File

@ -1,109 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
import (
"reflect"
"strings"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/structured-merge-diff/fieldpath"
)
// TestFieldsRoundTrip tests that a fields trie can be round tripped as a path set
func TestFieldsRoundTrip(t *testing.T) {
tests := []metav1.Fields{
{
Map: map[string]metav1.Fields{
"f:metadata": {
Map: map[string]metav1.Fields{
".": newFields(),
"f:name": newFields(),
},
},
},
},
}
for _, test := range tests {
set, err := FieldsToSet(test)
if err != nil {
t.Fatalf("Failed to create path set: %v", err)
}
output, err := SetToFields(set)
if err != nil {
t.Fatalf("Failed to create fields trie from path set: %v", err)
}
if !reflect.DeepEqual(test, output) {
t.Fatalf("Expected round-trip:\ninput: %v\noutput: %v", test, output)
}
}
}
// TestFieldsToSetError tests that errors are picked up by FieldsToSet
func TestFieldsToSetError(t *testing.T) {
tests := []struct {
fields metav1.Fields
errString string
}{
{
fields: metav1.Fields{
Map: map[string]metav1.Fields{
"k:{invalid json}": {
Map: map[string]metav1.Fields{
".": newFields(),
"f:name": newFields(),
},
},
},
},
errString: "invalid character",
},
}
for _, test := range tests {
_, err := FieldsToSet(test.fields)
if err == nil || !strings.Contains(err.Error(), test.errString) {
t.Fatalf("Expected error to contain %q but got: %v", test.errString, err)
}
}
}
// TestSetToFieldsError tests that errors are picked up by SetToFields
func TestSetToFieldsError(t *testing.T) {
validName := "ok"
invalidPath := fieldpath.Path([]fieldpath.PathElement{{}, {FieldName: &validName}})
tests := []struct {
set fieldpath.Set
errString string
}{
{
set: *fieldpath.NewSet(invalidPath),
errString: "Invalid type of path element",
},
}
for _, test := range tests {
_, err := SetToFields(test.set)
if err == nil || !strings.Contains(err.Error(), test.errString) {
t.Fatalf("Expected error to contain %q but got: %v", test.errString, err)
}
}
}

View File

@ -1,120 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
import (
"fmt"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/schemaconv"
"k8s.io/kube-openapi/pkg/util/proto"
"sigs.k8s.io/structured-merge-diff/typed"
)
// groupVersionKindExtensionKey is the key used to lookup the
// GroupVersionKind value for an object definition from the
// definition's "extensions" map.
const groupVersionKindExtensionKey = "x-kubernetes-group-version-kind"
type gvkParser struct {
gvks map[schema.GroupVersionKind]string
parser typed.Parser
}
func (p *gvkParser) Type(gvk schema.GroupVersionKind) typed.ParseableType {
typeName, ok := p.gvks[gvk]
if !ok {
return nil
}
return p.parser.Type(typeName)
}
func newGVKParser(models proto.Models) (*gvkParser, error) {
typeSchema, err := schemaconv.ToSchema(models)
if err != nil {
return nil, fmt.Errorf("failed to convert models to schema: %v", err)
}
parser := gvkParser{
gvks: map[schema.GroupVersionKind]string{},
}
parser.parser = typed.Parser{Schema: *typeSchema}
for _, modelName := range models.ListModels() {
model := models.LookupModel(modelName)
if model == nil {
panic(fmt.Sprintf("ListModels returns a model that can't be looked-up for: %v", modelName))
}
gvkList := parseGroupVersionKind(model)
for _, gvk := range gvkList {
if len(gvk.Kind) > 0 {
_, ok := parser.gvks[gvk]
if ok {
return nil, fmt.Errorf("duplicate entry for %v", gvk)
}
parser.gvks[gvk] = modelName
}
}
}
return &parser, nil
}
// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one.
func parseGroupVersionKind(s proto.Schema) []schema.GroupVersionKind {
extensions := s.GetExtensions()
gvkListResult := []schema.GroupVersionKind{}
// Get the extensions
gvkExtension, ok := extensions[groupVersionKindExtensionKey]
if !ok {
return []schema.GroupVersionKind{}
}
// gvk extension must be a list of at least 1 element.
gvkList, ok := gvkExtension.([]interface{})
if !ok {
return []schema.GroupVersionKind{}
}
for _, gvk := range gvkList {
// gvk extension list must be a map with group, version, and
// kind fields
gvkMap, ok := gvk.(map[interface{}]interface{})
if !ok {
continue
}
group, ok := gvkMap["group"].(string)
if !ok {
continue
}
version, ok := gvkMap["version"].(string)
if !ok {
continue
}
kind, ok := gvkMap["kind"].(string)
if !ok {
continue
}
gvkListResult = append(gvkListResult, schema.GroupVersionKind{
Group: group,
Version: version,
Kind: kind,
})
}
return gvkListResult
}

View File

@ -1,202 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
import (
"encoding/json"
"fmt"
"sort"
"time"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/structured-merge-diff/fieldpath"
)
// RemoveObjectManagedFields removes the ManagedFields from the object
// before we merge so that it doesn't appear in the ManagedFields
// recursively.
func RemoveObjectManagedFields(obj runtime.Object) {
accessor, err := meta.Accessor(obj)
if err != nil {
panic(fmt.Sprintf("couldn't get accessor: %v", err))
}
accessor.SetManagedFields(nil)
}
// DecodeObjectManagedFields extracts and converts the objects ManagedFields into a fieldpath.ManagedFields.
func DecodeObjectManagedFields(from runtime.Object) (fieldpath.ManagedFields, error) {
if from == nil {
return make(map[string]*fieldpath.VersionedSet), nil
}
accessor, err := meta.Accessor(from)
if err != nil {
panic(fmt.Sprintf("couldn't get accessor: %v", err))
}
managed, err := decodeManagedFields(accessor.GetManagedFields())
if err != nil {
return nil, fmt.Errorf("failed to convert managed fields from API: %v", err)
}
return managed, err
}
// EncodeObjectManagedFields converts and stores the fieldpathManagedFields into the objects ManagedFields
func EncodeObjectManagedFields(obj runtime.Object, fields fieldpath.ManagedFields) error {
accessor, err := meta.Accessor(obj)
if err != nil {
panic(fmt.Sprintf("couldn't get accessor: %v", err))
}
managed, err := encodeManagedFields(fields)
if err != nil {
return fmt.Errorf("failed to convert back managed fields to API: %v", err)
}
accessor.SetManagedFields(managed)
return nil
}
// decodeManagedFields converts ManagedFields from the wire format (api format)
// to the format used by sigs.k8s.io/structured-merge-diff
func decodeManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (managedFields fieldpath.ManagedFields, err error) {
managedFields = make(map[string]*fieldpath.VersionedSet, len(encodedManagedFields))
for _, encodedVersionedSet := range encodedManagedFields {
manager, err := BuildManagerIdentifier(&encodedVersionedSet)
if err != nil {
return nil, fmt.Errorf("error decoding manager from %v: %v", encodedVersionedSet, err)
}
managedFields[manager], err = decodeVersionedSet(&encodedVersionedSet)
if err != nil {
return nil, fmt.Errorf("error decoding versioned set from %v: %v", encodedVersionedSet, err)
}
}
return managedFields, nil
}
// BuildManagerIdentifier creates a manager identifier string from a ManagedFieldsEntry
func BuildManagerIdentifier(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.APIVersion = fieldpath.APIVersion(encodedVersionedSet.APIVersion)
if encodedVersionedSet.Operation == metav1.ManagedFieldsOperationApply {
versionedSet.Applied = true
}
fields := metav1.Fields{}
if encodedVersionedSet.Fields != nil {
fields = *encodedVersionedSet.Fields
}
set, err := FieldsToSet(fields)
if err != nil {
return nil, fmt.Errorf("error decoding set: %v", err)
}
versionedSet.Set = &set
return versionedSet, nil
}
// encodeManagedFields converts ManagedFields from the the format used by
// sigs.k8s.io/structured-merge-diff to the the wire format (api format)
func encodeManagedFields(managedFields fieldpath.ManagedFields) (encodedManagedFields []metav1.ManagedFieldsEntry, err error) {
// Sort the keys so a predictable order will be used.
managers := []string{}
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 {
return nil, fmt.Errorf("error encoding versioned set for %v: %v", manager, err)
}
encodedManagedFields = append(encodedManagedFields, *v)
}
return sortEncodedManagedFields(encodedManagedFields)
}
func sortEncodedManagedFields(encodedManagedFields []metav1.ManagedFieldsEntry) (sortedManagedFields []metav1.ManagedFieldsEntry, err error) {
sort.Slice(encodedManagedFields, func(i, j int) bool {
p, q := encodedManagedFields[i], encodedManagedFields[j]
if p.Operation != q.Operation {
return p.Operation < q.Operation
}
ntime := &metav1.Time{Time: time.Time{}}
if p.Time == nil {
p.Time = ntime
}
if q.Time == nil {
q.Time = ntime
}
if !p.Time.Equal(q.Time) {
return p.Time.Before(q.Time)
}
return p.Manager < q.Manager
})
return encodedManagedFields, nil
}
func encodeManagerVersionedSet(manager string, versionedSet *fieldpath.VersionedSet) (encodedVersionedSet *metav1.ManagedFieldsEntry, err error) {
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, Operation, and Fields from the VersionedSet
encodedVersionedSet.APIVersion = string(versionedSet.APIVersion)
if versionedSet.Applied {
encodedVersionedSet.Operation = metav1.ManagedFieldsOperationApply
}
fields, err := SetToFields(*versionedSet.Set)
if err != nil {
return nil, fmt.Errorf("error encoding set: %v", err)
}
encodedVersionedSet.Fields = &fields
return encodedVersionedSet, nil
}

View File

@ -1,342 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
import (
"fmt"
"reflect"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
)
// TestRoundTripManagedFields will roundtrip ManagedFields from the wire format
// (api format) to the format used by sigs.k8s.io/structured-merge-diff and back
func TestRoundTripManagedFields(t *testing.T) {
tests := []string{
`- apiVersion: v1
fields:
v:3:
f:alsoPi: {}
v:3.1415:
f:pi: {}
v:false:
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"
`,
`- apiVersion: v1
fields:
f:spec:
f:containers:
k:{"name":"c"}:
f:image: {}
f:name: {}
manager: foo
operation: Apply
`,
`- apiVersion: v1
fields:
f:apiVersion: {}
f:kind: {}
f:metadata:
f:labels:
f:app: {}
f:name: {}
f:spec:
f:replicas: {}
f:selector:
f:matchLabels:
f:app: {}
f:template:
f:medatada:
f:labels:
f:app: {}
f:spec:
f:containers:
k:{"name":"nginx"}:
.: {}
f:image: {}
f:name: {}
f:ports:
i:0:
f:containerPort: {}
manager: foo
operation: Update
`,
`- apiVersion: v1
fields:
f:allowVolumeExpansion: {}
f:apiVersion: {}
f:kind: {}
f:metadata:
f:name: {}
f:parameters:
f:resturl: {}
f:restuser: {}
f:secretName: {}
f:secretNamespace: {}
f:provisioner: {}
manager: foo
operation: Apply
`,
`- apiVersion: v1
fields:
f:apiVersion: {}
f:kind: {}
f:metadata:
f:name: {}
f:spec:
f:group: {}
f:names:
f:kind: {}
f:plural: {}
f:shortNames:
i:0: {}
f:singular: {}
f:scope: {}
f:versions:
k:{"name":"v1"}:
f:name: {}
f:served: {}
f:storage: {}
manager: foo
operation: Update
`,
}
for _, test := range tests {
t.Run(test, func(t *testing.T) {
var unmarshaled []metav1.ManagedFieldsEntry
if err := yaml.Unmarshal([]byte(test), &unmarshaled); err != nil {
t.Fatalf("did not expect yaml unmarshalling error but got: %v", err)
}
decoded, err := decodeManagedFields(unmarshaled)
if err != nil {
t.Fatalf("did not expect decoding error but got: %v", err)
}
encoded, err := encodeManagedFields(decoded)
if err != nil {
t.Fatalf("did not expect encoding error but got: %v", err)
}
marshaled, err := yaml.Marshal(&encoded)
if err != nil {
t.Fatalf("did not expect yaml marshalling error but got: %v", err)
}
if !reflect.DeepEqual(string(marshaled), test) {
t.Fatalf("expected:\n%v\nbut got:\n%v", test, string(marshaled))
}
})
}
}
func TestBuildManagerIdentifier(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 := BuildManagerIdentifier(&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)
}
})
}
}
func TestSortEncodedManagedFields(t *testing.T) {
tests := []struct {
name string
managedFields []metav1.ManagedFieldsEntry
expected []metav1.ManagedFieldsEntry
}{
{
name: "empty",
managedFields: []metav1.ManagedFieldsEntry{},
expected: []metav1.ManagedFieldsEntry{},
},
{
name: "nil",
managedFields: nil,
expected: nil,
},
{
name: "remains untouched",
managedFields: []metav1.ManagedFieldsEntry{
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
},
expected: []metav1.ManagedFieldsEntry{
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
},
},
{
name: "manager without time first",
managedFields: []metav1.ManagedFieldsEntry{
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
},
expected: []metav1.ManagedFieldsEntry{
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
},
},
{
name: "manager without time first name last",
managedFields: []metav1.ManagedFieldsEntry{
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
{Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
},
expected: []metav1.ManagedFieldsEntry{
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
},
},
{
name: "apply first",
managedFields: []metav1.ManagedFieldsEntry{
{Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
},
expected: []metav1.ManagedFieldsEntry{
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
},
},
{
name: "newest last",
managedFields: []metav1.ManagedFieldsEntry{
{Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")},
{Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
},
expected: []metav1.ManagedFieldsEntry{
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
{Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")},
},
},
{
name: "manager last",
managedFields: []metav1.ManagedFieldsEntry{
{Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "d", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
{Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
},
expected: []metav1.ManagedFieldsEntry{
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
{Manager: "d", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
},
},
{
name: "manager sorted",
managedFields: []metav1.ManagedFieldsEntry{
{Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "g", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "f", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")},
{Manager: "i", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "d", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")},
{Manager: "h", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "e", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2003-01-01T01:00:00Z")},
{Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
},
expected: []metav1.ManagedFieldsEntry{
{Manager: "a", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "b", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "g", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "h", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "i", Operation: metav1.ManagedFieldsOperationApply, Time: nil},
{Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2001-01-01T01:00:00Z")},
{Manager: "d", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")},
{Manager: "f", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2002-01-01T01:00:00Z")},
{Manager: "e", Operation: metav1.ManagedFieldsOperationUpdate, Time: parseTimeOrPanic("2003-01-01T01:00:00Z")},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
sorted, err := sortEncodedManagedFields(test.managedFields)
if err != nil {
t.Fatalf("did not expect error when sorting but got: %v", err)
}
if !reflect.DeepEqual(sorted, test.expected) {
t.Fatalf("expected:\n%v\nbut got:\n%v", test.expected, sorted)
}
})
}
}
func parseTimeOrPanic(s string) *metav1.Time {
t, err := time.Parse(time.RFC3339, s)
if err != nil {
panic(fmt.Sprintf("failed to parse time %s, got: %v", s, err))
}
return &metav1.Time{Time: t.UTC()}
}

View File

@ -1,140 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/value"
)
const (
// Field indicates that the content of this path element is a field's name
Field = "f"
// Value indicates that the content of this path element is a field's value
Value = "v"
// Index indicates that the content of this path element is an index in an array
Index = "i"
// Key indicates that the content of this path element is a key value map
Key = "k"
// Separator separates the type of a path element from the contents
Separator = ":"
)
// NewPathElement parses a serialized path element
func NewPathElement(s string) (fieldpath.PathElement, error) {
split := strings.SplitN(s, Separator, 2)
if len(split) < 2 {
return fieldpath.PathElement{}, fmt.Errorf("missing colon: %v", s)
}
switch split[0] {
case Field:
return fieldpath.PathElement{
FieldName: &split[1],
}, nil
case Value:
val, err := value.FromJSON([]byte(split[1]))
if err != nil {
return fieldpath.PathElement{}, err
}
return fieldpath.PathElement{
Value: &val,
}, nil
case Index:
i, err := strconv.Atoi(split[1])
if err != nil {
return fieldpath.PathElement{}, err
}
return fieldpath.PathElement{
Index: &i,
}, nil
case Key:
kv := map[string]json.RawMessage{}
err := json.Unmarshal([]byte(split[1]), &kv)
if err != nil {
return fieldpath.PathElement{}, err
}
fields := []value.Field{}
for k, v := range kv {
b, err := json.Marshal(v)
if err != nil {
return fieldpath.PathElement{}, err
}
val, err := value.FromJSON(b)
if err != nil {
return fieldpath.PathElement{}, err
}
fields = append(fields, value.Field{
Name: k,
Value: val,
})
}
return fieldpath.PathElement{
Key: fields,
}, nil
default:
// Ignore unknown key types
return fieldpath.PathElement{}, nil
}
}
// PathElementString serializes a path element
func PathElementString(pe fieldpath.PathElement) (string, error) {
switch {
case pe.FieldName != nil:
return Field + Separator + *pe.FieldName, nil
case len(pe.Key) > 0:
kv := map[string]json.RawMessage{}
for _, k := range pe.Key {
b, err := k.Value.ToJSON()
if err != nil {
return "", err
}
m := json.RawMessage{}
err = json.Unmarshal(b, &m)
if err != nil {
return "", err
}
kv[k.Name] = m
}
b, err := json.Marshal(kv)
if err != nil {
return "", err
}
return Key + ":" + string(b), nil
case pe.Value != nil:
b, err := pe.Value.ToJSON()
if err != nil {
return "", err
}
return Value + ":" + string(b), nil
case pe.Index != nil:
return Index + ":" + strconv.Itoa(*pe.Index), nil
default:
return "", errors.New("Invalid type of path element")
}
}

View File

@ -1,84 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
import "testing"
func TestPathElementRoundTrip(t *testing.T) {
tests := []string{
`i:0`,
`i:1234`,
`f:`,
`f:spec`,
`f:more-complicated-string`,
`k:{"name":"my-container"}`,
`k:{"port":"8080","protocol":"TCP"}`,
`k:{"optionalField":null}`,
`k:{"jsonField":{"A":1,"B":null,"C":"D","E":{"F":"G"}}}`,
`k:{"listField":["1","2","3"]}`,
`v:null`,
`v:"some-string"`,
`v:1234`,
`v:{"some":"json"}`,
}
for _, test := range tests {
t.Run(test, func(t *testing.T) {
pe, err := NewPathElement(test)
if err != nil {
t.Fatalf("Failed to create path element: %v", err)
}
output, err := PathElementString(pe)
if err != nil {
t.Fatalf("Failed to create string from path element: %v", err)
}
if test != output {
t.Fatalf("Expected round-trip:\ninput: %v\noutput: %v", test, output)
}
})
}
}
func TestPathElementIgnoreUnknown(t *testing.T) {
_, err := NewPathElement("r:Hello")
if err != nil {
t.Fatalf("Unknown qualifiers should be ignored")
}
}
func TestNewPathElementError(t *testing.T) {
tests := []string{
``,
`no-colon`,
`i:index is not a number`,
`i:1.23`,
`i:`,
`v:invalid json`,
`v:`,
`k:invalid json`,
`k:{"name":invalid}`,
}
for _, test := range tests {
t.Run(test, func(t *testing.T) {
_, err := NewPathElement(test)
if err == nil {
t.Fatalf("Expected error, no error found")
}
})
}
}

View File

@ -1,127 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
import (
"fmt"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kube-openapi/pkg/util/proto"
"sigs.k8s.io/structured-merge-diff/typed"
"sigs.k8s.io/structured-merge-diff/value"
"sigs.k8s.io/yaml"
)
// TypeConverter allows you to convert from runtime.Object to
// typed.TypedValue and the other way around.
type TypeConverter interface {
ObjectToTyped(runtime.Object) (typed.TypedValue, error)
YAMLToTyped([]byte) (typed.TypedValue, 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).
// TODO(jennybuckley): Use the schema provided by a CRD if it exists.
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 {
parser *gvkParser
}
var _ TypeConverter = &typeConverter{}
// NewTypeConverter builds a TypeConverter from a proto.Models. This
// will automatically find the proper version of the object, and the
// corresponding schema information.
func NewTypeConverter(models proto.Models) (TypeConverter, error) {
parser, err := newGVKParser(models)
if err != nil {
return nil, err
}
return &typeConverter{parser: parser}, nil
}
func (c *typeConverter) ObjectToTyped(obj runtime.Object) (typed.TypedValue, error) {
u, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, err
}
gvk := obj.GetObjectKind().GroupVersionKind()
t := c.parser.Type(gvk)
if t == nil {
return nil, fmt.Errorf("no corresponding type for %v", gvk)
}
return t.FromUnstructured(u)
}
func (c *typeConverter) YAMLToTyped(from []byte) (typed.TypedValue, error) {
unstructured := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal(from, &unstructured.Object); err != nil {
return nil, fmt.Errorf("error decoding YAML: %v", err)
}
gvk := unstructured.GetObjectKind().GroupVersionKind()
t := c.parser.Type(gvk)
if t == nil {
return nil, fmt.Errorf("no corresponding type for %v", gvk)
}
return t.FromYAML(typed.YAMLObject(string(from)))
}
func (c *typeConverter) TypedToObject(value typed.TypedValue) (runtime.Object, error) {
return valueToObject(value.AsValue())
}
func valueToObject(value *value.Value) (runtime.Object, error) {
vu := value.ToUnstructured(false)
u, ok := vu.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("failed to convert typed to unstructured: want map, got %T", vu)
}
return &unstructured.Unstructured{Object: u}, nil
}

View File

@ -1,177 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal_test
import (
"fmt"
"path/filepath"
"reflect"
"strings"
"testing"
"sigs.k8s.io/yaml"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"k8s.io/kube-openapi/pkg/util/proto"
prototesting "k8s.io/kube-openapi/pkg/util/proto/testing"
)
var fakeSchema = prototesting.Fake{
Path: filepath.Join(
strings.Repeat(".."+string(filepath.Separator), 9),
"api", "openapi-spec", "swagger.json"),
}
func TestTypeConverter(t *testing.T) {
d, err := fakeSchema.OpenAPISchema()
if err != nil {
t.Fatalf("Failed to parse OpenAPI schema: %v", err)
}
m, err := proto.NewOpenAPIData(d)
if err != nil {
t.Fatalf("Failed to build OpenAPI models: %v", err)
}
tc, err := internal.NewTypeConverter(m)
if err != nil {
t.Fatalf("Failed to build TypeConverter: %v", err)
}
dtc := internal.DeducedTypeConverter{}
testCases := []struct {
name string
yaml string
}{
{
name: "apps/v1.Deployment",
yaml: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.15.4
`,
}, {
name: "extensions/v1beta1.Deployment",
yaml: `
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.15.4
`,
}, {
name: "v1.Pod",
yaml: `
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.15.4
`,
},
}
for _, testCase := range testCases {
t.Run(fmt.Sprintf("%v ObjectToTyped with TypeConverter", testCase.name), func(t *testing.T) {
testObjectToTyped(t, tc, testCase.yaml)
})
t.Run(fmt.Sprintf("%v YAMLToTyped with TypeConverter", testCase.name), func(t *testing.T) {
testYAMLToTyped(t, tc, testCase.yaml)
})
t.Run(fmt.Sprintf("%v ObjectToTyped with DeducedTypeConverter", testCase.name), func(t *testing.T) {
testObjectToTyped(t, dtc, testCase.yaml)
})
}
}
func testObjectToTyped(t *testing.T, tc internal.TypeConverter, y string) {
obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil {
t.Fatalf("Failed to parse yaml object: %v", err)
}
typed, err := tc.ObjectToTyped(obj)
if err != nil {
t.Fatalf("Failed to convert object to typed: %v", err)
}
newObj, err := tc.TypedToObject(typed)
if err != nil {
t.Fatalf("Failed to convert typed to object: %v", err)
}
if !reflect.DeepEqual(obj, newObj) {
t.Errorf(`Round-trip failed:
Original object:
%#v
Final object:
%#v`, obj, newObj)
}
}
func testYAMLToTyped(t *testing.T, tc internal.TypeConverter, y string) {
obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil {
t.Fatalf("Failed to parse yaml object: %v", err)
}
yamlTyped, err := tc.YAMLToTyped([]byte(y))
if err != nil {
t.Fatalf("Failed to convert yaml to typed: %v", err)
}
newObj, err := tc.TypedToObject(yamlTyped)
if err != nil {
t.Fatalf("Failed to convert typed to object: %v", err)
}
if !reflect.DeepEqual(obj, newObj) {
t.Errorf(`YAML conversion resulted in different object failed:
Original object:
%#v
Final object:
%#v`, obj, newObj)
}
}

View File

@ -1,101 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/structured-merge-diff/fieldpath"
"sigs.k8s.io/structured-merge-diff/merge"
"sigs.k8s.io/structured-merge-diff/typed"
)
// versionConverter is an implementation of
// sigs.k8s.io/structured-merge-diff/merge.Converter
type versionConverter struct {
typeConverter TypeConverter
objectConvertor runtime.ObjectConvertor
hubGetter func(from schema.GroupVersion) schema.GroupVersion
}
var _ merge.Converter = &versionConverter{}
// NewVersionConverter builds a VersionConverter from a TypeConverter and an ObjectConvertor.
func NewVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter {
return &versionConverter{
typeConverter: t,
objectConvertor: o,
hubGetter: func(from schema.GroupVersion) schema.GroupVersion {
return schema.GroupVersion{
Group: from.Group,
Version: h.Version,
}
},
}
}
// NewCRDVersionConverter builds a VersionConverter for CRDs from a TypeConverter and an ObjectConvertor.
func NewCRDVersionConverter(t TypeConverter, o runtime.ObjectConvertor, h schema.GroupVersion) merge.Converter {
return &versionConverter{
typeConverter: t,
objectConvertor: o,
hubGetter: func(from schema.GroupVersion) schema.GroupVersion {
return h
},
}
}
// Convert implements sigs.k8s.io/structured-merge-diff/merge.Converter
func (v *versionConverter) Convert(object typed.TypedValue, version fieldpath.APIVersion) (typed.TypedValue, error) {
// Convert the smd typed value to a kubernetes object.
objectToConvert, err := v.typeConverter.TypedToObject(object)
if err != nil {
return object, err
}
// Parse the target groupVersion.
groupVersion, err := schema.ParseGroupVersion(string(version))
if err != nil {
return object, err
}
// If attempting to convert to the same version as we already have, just return it.
fromVersion := objectToConvert.GetObjectKind().GroupVersionKind().GroupVersion()
if fromVersion == groupVersion {
return object, nil
}
// Convert to internal
internalObject, err := v.objectConvertor.ConvertToVersion(objectToConvert, v.hubGetter(fromVersion))
if err != nil {
return object, err
}
// Convert the object into the target version
convertedObject, err := v.objectConvertor.ConvertToVersion(internalObject, groupVersion)
if err != nil {
return object, err
}
// Convert the object back to a smd typed value and return it.
return v.typeConverter.ObjectToTyped(convertedObject)
}
// IsMissingVersionError
func (v *versionConverter) IsMissingVersionError(err error) bool {
return runtime.IsNotRegisteredError(err)
}

View File

@ -1,107 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal_test
import (
"fmt"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
"k8s.io/kube-openapi/pkg/util/proto"
"sigs.k8s.io/structured-merge-diff/fieldpath"
)
// TestVersionConverter tests the version converter
func TestVersionConverter(t *testing.T) {
d, err := fakeSchema.OpenAPISchema()
if err != nil {
t.Fatalf("Failed to parse OpenAPI schema: %v", err)
}
m, err := proto.NewOpenAPIData(d)
if err != nil {
t.Fatalf("Failed to build OpenAPI models: %v", err)
}
tc, err := internal.NewTypeConverter(m)
if err != nil {
t.Fatalf("Failed to build TypeConverter: %v", err)
}
oc := fakeObjectConvertor{
gvkForVersion("v1beta1"): objForGroupVersion("apps/v1beta1"),
gvkForVersion("v1"): objForGroupVersion("apps/v1"),
}
vc := internal.NewVersionConverter(tc, oc, schema.GroupVersion{Group: "apps", Version: runtime.APIVersionInternal})
input, err := tc.ObjectToTyped(objForGroupVersion("apps/v1beta1"))
if err != nil {
t.Fatalf("error creating converting input object to a typed value: %v", err)
}
expected := objForGroupVersion("apps/v1")
output, err := vc.Convert(input, fieldpath.APIVersion("apps/v1"))
if err != nil {
t.Fatalf("expected err to be nil but got %v", err)
}
actual, err := tc.TypedToObject(output)
if err != nil {
t.Fatalf("error converting output typed value to an object %v", err)
}
if !reflect.DeepEqual(expected, actual) {
t.Fatalf("expected to get %v but got %v", expected, actual)
}
}
func gvkForVersion(v string) schema.GroupVersionKind {
return schema.GroupVersionKind{
Group: "apps",
Version: v,
Kind: "Deployment",
}
}
func objForGroupVersion(gv string) runtime.Object {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": gv,
"kind": "Deployment",
},
}
}
type fakeObjectConvertor map[schema.GroupVersionKind]runtime.Object
var _ runtime.ObjectConvertor = fakeObjectConvertor{}
func (c fakeObjectConvertor) ConvertToVersion(_ runtime.Object, gv runtime.GroupVersioner) (runtime.Object, error) {
allKinds := make([]schema.GroupVersionKind, 0)
for kind := range c {
allKinds = append(allKinds, kind)
}
gvk, _ := gv.KindForGroupVersionKinds(allKinds)
return c[gvk], nil
}
func (fakeObjectConvertor) Convert(_, _, _ interface{}) error {
return fmt.Errorf("function not implemented")
}
func (fakeObjectConvertor) ConvertFieldLabel(_ schema.GroupVersionKind, _, _ string) (string, string, error) {
return "", "", fmt.Errorf("function not implemented")
}

View File

@ -39,7 +39,6 @@ import (
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/audit"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/features"
@ -126,9 +125,6 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
trace.Step("Recorded the audit event")
baseContentType := runtime.ContentTypeJSON
if patchType == types.ApplyPatchType {
baseContentType = runtime.ContentTypeYAML
}
s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), baseContentType)
if !ok {
scope.err(fmt.Errorf("no serializer defined for %v", baseContentType), w, req)
@ -296,8 +292,6 @@ type patchMechanism interface {
type jsonPatcher struct {
*patcher
fieldManager *fieldmanager.FieldManager
}
func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) {
@ -319,11 +313,6 @@ func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (r
return nil, err
}
if p.fieldManager != nil {
if objToUpdate, err = p.fieldManager.Update(currentObject, objToUpdate, managerOrUserAgent(p.options.FieldManager, p.userAgent)); err != nil {
return nil, fmt.Errorf("failed to update object (json PATCH for %v) managed fields: %v", p.kind, err)
}
}
return objToUpdate, nil
}
@ -363,7 +352,6 @@ type smpPatcher struct {
// Schema
schemaReferenceObj runtime.Object
fieldManager *fieldmanager.FieldManager
}
func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) {
@ -386,11 +374,6 @@ func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (ru
return nil, err
}
if p.fieldManager != nil {
if newObj, err = p.fieldManager.Update(currentObject, newObj, managerOrUserAgent(p.options.FieldManager, p.userAgent)); err != nil {
return nil, fmt.Errorf("failed to update object (smp PATCH for %v) managed fields: %v", p.kind, err)
}
}
return newObj, nil
}
@ -398,33 +381,6 @@ func (p *smpPatcher) createNewObject() (runtime.Object, error) {
return nil, errors.NewNotFound(p.resource.GroupResource(), p.name)
}
type applyPatcher struct {
patch []byte
options *metav1.PatchOptions
creater runtime.ObjectCreater
kind schema.GroupVersionKind
fieldManager *fieldmanager.FieldManager
}
func (p *applyPatcher) applyPatchToCurrentObject(obj runtime.Object) (runtime.Object, error) {
force := false
if p.options.Force != nil {
force = *p.options.Force
}
if p.fieldManager == nil {
panic("FieldManager must be installed to run apply")
}
return p.fieldManager.Apply(obj, p.patch, p.options.FieldManager, force)
}
func (p *applyPatcher) createNewObject() (runtime.Object, error) {
obj, err := p.creater.New(p.kind)
if err != nil {
return nil, fmt.Errorf("failed to create new object: %v", obj)
}
return p.applyPatchToCurrentObject(obj)
}
// strategicPatchObject applies a strategic merge patch of <patchBytes> to
// <originalObject> and stores the result in <objToUpdate>.
// It additionally returns the map[string]interface{} representation of the
@ -523,7 +479,6 @@ func (p *patcher) patchResource(ctx context.Context, scope RequestScope) (runtim
case types.JSONPatchType, types.MergePatchType:
p.mechanism = &jsonPatcher{
patcher: p,
fieldManager: scope.FieldManager,
}
case types.StrategicMergePatchType:
schemaReferenceObj, err := p.unsafeConvertor.ConvertToVersion(p.restPatcher.New(), p.kind.GroupVersion())
@ -533,18 +488,8 @@ func (p *patcher) patchResource(ctx context.Context, scope RequestScope) (runtim
p.mechanism = &smpPatcher{
patcher: p,
schemaReferenceObj: schemaReferenceObj,
fieldManager: scope.FieldManager,
}
// this case is unreachable if ServerSideApply is not enabled because we will have already rejected the content type
case types.ApplyPatchType:
p.mechanism = &applyPatcher{
fieldManager: scope.FieldManager,
patch: p.patchBytes,
options: p.options,
creater: p.creater,
kind: p.kind,
}
p.forceAllowCreate = true
default:
return nil, false, fmt.Errorf("%v: unimplemented patch type", p.patchType)
}

View File

@ -36,7 +36,6 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/apiserver/pkg/endpoints/metrics"
"k8s.io/apiserver/pkg/endpoints/request"
@ -61,7 +60,6 @@ type RequestScope struct {
Trace *utiltrace.Trace
TableConvertor rest.TableConvertor
FieldManager *fieldmanager.FieldManager
Resource schema.GroupVersionResource
Kind schema.GroupVersionKind

View File

@ -122,15 +122,6 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
userInfo, _ := request.UserFrom(ctx)
transformers := []rest.TransformFunc{}
if scope.FieldManager != nil {
transformers = append(transformers, func(_ context.Context, newObj, liveObj runtime.Object) (runtime.Object, error) {
obj, err := scope.FieldManager.Update(liveObj, newObj, managerOrUserAgent(options.FieldManager, req.UserAgent()))
if err != nil {
return nil, fmt.Errorf("failed to update object (Update for %v) managed fields: %v", scope.Kind, err)
}
return obj, nil
})
}
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
transformers = append(transformers, func(ctx context.Context, newObj, oldObj runtime.Object) (runtime.Object, error) {
isNotZeroObject, err := hasUID(oldObj)

View File

@ -35,7 +35,6 @@ import (
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/endpoints/discovery"
"k8s.io/apiserver/pkg/endpoints/handlers"
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager"
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
"k8s.io/apiserver/pkg/endpoints/metrics"
"k8s.io/apiserver/pkg/features"
@ -542,19 +541,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
if a.group.MetaGroupVersion != nil {
reqScope.MetaGroupVersion = *a.group.MetaGroupVersion
}
if a.group.OpenAPIModels != nil && utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
fm, err := fieldmanager.NewFieldManager(
a.group.OpenAPIModels,
a.group.UnsafeConvertor,
a.group.Defaulter,
fqKindToRegister.GroupVersion(),
reqScope.HubGroupVersion,
)
if err != nil {
return nil, fmt.Errorf("failed to create field manager: %v", err)
}
reqScope.FieldManager = fm
}
for _, action := range actions {
producedObject := storageMeta.ProducesObject(action.Verb)
if producedObject == nil {
@ -708,9 +694,6 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
string(types.MergePatchType),
string(types.StrategicMergePatchType),
}
if utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
supportedTypes = append(supportedTypes, string(types.ApplyPatchType))
}
handler := metrics.InstrumentRouteFunc(action.Verb, group, version, resource, subresource, requestScope, metrics.APIServerComponent, restfulPatchResource(patcher, reqScope, admit, supportedTypes))
route := ws.PATCH(action.Path).To(handler).
Doc(doc).

View File

@ -26,16 +26,12 @@ import (
"sync"
"time"
"github.com/emicklei/go-restful"
"github.com/prometheus/client_golang/prometheus"
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/types"
utilnet "k8s.io/apimachinery/pkg/util/net"
utilsets "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"github.com/emicklei/go-restful"
"github.com/prometheus/client_golang/prometheus"
)
// resettableCollector is the interface implemented by prometheus.MetricVec
@ -325,9 +321,6 @@ func cleanVerb(verb string, request *http.Request) string {
if verb == "WATCHLIST" {
reportedVerb = "WATCH"
}
if verb == "PATCH" && request.Header.Get("Content-Type") == string(types.ApplyPatchType) && utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
reportedVerb = "APPLY"
}
return reportedVerb
}

View File

@ -70,12 +70,6 @@ const (
// committing.
DryRun utilfeature.Feature = "DryRun"
// owner: @apelisse, @lavalamp
// alpha: v1.14
//
// Server-side apply. Merging happens on the server.
ServerSideApply utilfeature.Feature = "ServerSideApply"
// owner: @caesarxuchao
// alpha: v1.14
//
@ -109,7 +103,6 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
AdvancedAuditing: {Default: true, PreRelease: utilfeature.GA},
APIListChunking: {Default: true, PreRelease: utilfeature.Beta},
DryRun: {Default: true, PreRelease: utilfeature.Beta},
ServerSideApply: {Default: false, PreRelease: utilfeature.Alpha},
StorageVersionHash: {Default: false, PreRelease: utilfeature.Alpha},
WinOverlay: {Default: false, PreRelease: utilfeature.Alpha},
WinDSR: {Default: false, PreRelease: utilfeature.Alpha},

View File

@ -28,9 +28,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/storage/names"
utilfeature "k8s.io/apiserver/pkg/util/feature"
)
// RESTCreateStrategy defines the minimum validation, accepted input, and
@ -96,9 +94,7 @@ func BeforeCreate(strategy RESTCreateStrategy, ctx context.Context, obj runtime.
objectMeta.SetInitializers(nil)
// Ensure managedFields is not set unless the feature is enabled
if !utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
objectMeta.SetManagedFields(nil)
}
objectMeta.SetManagedFields(nil)
// ClusterName is ignored and should not be saved
if len(objectMeta.GetClusterName()) > 0 {

View File

@ -28,8 +28,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
)
// RESTUpdateStrategy defines the minimum validation, accepted input, and
@ -109,10 +107,8 @@ func BeforeUpdate(strategy RESTUpdateStrategy, ctx context.Context, obj, old run
objectMeta.SetInitializers(nil)
// Ensure managedFields state is removed unless ServerSideApply is enabled
if !utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
oldMeta.SetManagedFields(nil)
objectMeta.SetManagedFields(nil)
}
oldMeta.SetManagedFields(nil)
objectMeta.SetManagedFields(nil)
strategy.PrepareForUpdate(ctx, obj, old)