mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Add "fieldManager" to flag to PATCH/CREATE/UPDATE
And add a corresponding flag in kubectl (for apply), even though the value is defaulted in kubectl with "kubectl". The flag is required for Apply patch-type, and optional for other PATCH, CREATE and UPDATE (in which case we fallback on the user-agent).
This commit is contained in:
		@@ -68,6 +68,7 @@ type ApplyOptions struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	ServerSideApply bool
 | 
						ServerSideApply bool
 | 
				
			||||||
	ForceConflicts  bool
 | 
						ForceConflicts  bool
 | 
				
			||||||
 | 
						FieldManager    string
 | 
				
			||||||
	Selector        string
 | 
						Selector        string
 | 
				
			||||||
	DryRun          bool
 | 
						DryRun          bool
 | 
				
			||||||
	ServerDryRun    bool
 | 
						ServerDryRun    bool
 | 
				
			||||||
@@ -196,14 +197,15 @@ func NewCmdApply(baseName string, f cmdutil.Factory, ioStreams genericclioptions
 | 
				
			|||||||
func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
 | 
					func (o *ApplyOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
 | 
				
			||||||
	o.ServerSideApply = cmdutil.GetServerSideApplyFlag(cmd)
 | 
						o.ServerSideApply = cmdutil.GetServerSideApplyFlag(cmd)
 | 
				
			||||||
	o.ForceConflicts = cmdutil.GetForceConflictsFlag(cmd)
 | 
						o.ForceConflicts = cmdutil.GetForceConflictsFlag(cmd)
 | 
				
			||||||
 | 
						o.FieldManager = cmdutil.GetFieldManagerFlag(cmd)
 | 
				
			||||||
	o.DryRun = cmdutil.GetDryRunFlag(cmd)
 | 
						o.DryRun = cmdutil.GetDryRunFlag(cmd)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if o.ForceConflicts && !o.ServerSideApply {
 | 
						if o.ForceConflicts && !o.ServerSideApply {
 | 
				
			||||||
		return fmt.Errorf("--force-conflicts only works with --server-side")
 | 
							return fmt.Errorf("--experimental-force-conflicts only works with --experimental-server-side")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if o.DryRun && o.ServerSideApply {
 | 
						if o.DryRun && o.ServerSideApply {
 | 
				
			||||||
		return fmt.Errorf("--dry-run doesn't work with --server-side")
 | 
							return fmt.Errorf("--dry-run doesn't work with --experimental-server-side (did you mean --server-dry-run instead?)")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if o.DryRun && o.ServerDryRun {
 | 
						if o.DryRun && o.ServerDryRun {
 | 
				
			||||||
@@ -394,6 +396,7 @@ func (o *ApplyOptions) Run() error {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
			options := metav1.PatchOptions{
 | 
								options := metav1.PatchOptions{
 | 
				
			||||||
				Force:        &o.ForceConflicts,
 | 
									Force:        &o.ForceConflicts,
 | 
				
			||||||
 | 
									FieldManager: o.FieldManager,
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if o.ServerDryRun {
 | 
								if o.ServerDryRun {
 | 
				
			||||||
				options.DryRun = []string{metav1.DryRunAll}
 | 
									options.DryRun = []string{metav1.DryRunAll}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -402,7 +402,7 @@ func (o *DiffOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
 | 
				
			|||||||
	o.ServerSideApply = cmdutil.GetServerSideApplyFlag(cmd)
 | 
						o.ServerSideApply = cmdutil.GetServerSideApplyFlag(cmd)
 | 
				
			||||||
	o.ForceConflicts = cmdutil.GetForceConflictsFlag(cmd)
 | 
						o.ForceConflicts = cmdutil.GetForceConflictsFlag(cmd)
 | 
				
			||||||
	if o.ForceConflicts && !o.ServerSideApply {
 | 
						if o.ForceConflicts && !o.ServerSideApply {
 | 
				
			||||||
		return fmt.Errorf("--force-conflicts only works with --server-side")
 | 
							return fmt.Errorf("--experimental-force-conflicts only works with --experimental-server-side")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !o.ServerSideApply {
 | 
						if !o.ServerSideApply {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -406,8 +406,9 @@ func AddDryRunFlag(cmd *cobra.Command) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func AddServerSideApplyFlags(cmd *cobra.Command) {
 | 
					func AddServerSideApplyFlags(cmd *cobra.Command) {
 | 
				
			||||||
	cmd.Flags().Bool("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-server-side", false, "If true, apply runs in the server instead of the client. This is an alpha feature and flag.")
 | 
				
			||||||
	cmd.Flags().Bool("force-conflicts", false, "If true, server-side apply will force the changes against conflicts. 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) {
 | 
					func AddIncludeUninitializedFlag(cmd *cobra.Command) {
 | 
				
			||||||
@@ -484,11 +485,15 @@ func DumpReaderToFile(reader io.Reader, filename string) error {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetServerSideApplyFlag(cmd *cobra.Command) bool {
 | 
					func GetServerSideApplyFlag(cmd *cobra.Command) bool {
 | 
				
			||||||
	return GetFlagBool(cmd, "server-side")
 | 
						return GetFlagBool(cmd, "experimental-server-side")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetForceConflictsFlag(cmd *cobra.Command) bool {
 | 
					func GetForceConflictsFlag(cmd *cobra.Command) bool {
 | 
				
			||||||
	return GetFlagBool(cmd, "force-conflicts")
 | 
						return GetFlagBool(cmd, "experimental-force-conflicts")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetFieldManagerFlag(cmd *cobra.Command) string {
 | 
				
			||||||
 | 
						return GetFlagString(cmd, "experimental-field-manager")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetDryRunFlag(cmd *cobra.Command) bool {
 | 
					func GetDryRunFlag(cmd *cobra.Command) bool {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -71,6 +71,7 @@ values:
 | 
				
			|||||||
	result, err := rest.Patch(types.ApplyPatchType).
 | 
						result, err := rest.Patch(types.ApplyPatchType).
 | 
				
			||||||
		AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
 | 
							AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
 | 
				
			||||||
		Name("mytest").
 | 
							Name("mytest").
 | 
				
			||||||
 | 
							Param("fieldManager", "apply_test").
 | 
				
			||||||
		Body(yamlBody).
 | 
							Body(yamlBody).
 | 
				
			||||||
		DoRaw()
 | 
							DoRaw()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -90,6 +91,7 @@ values:
 | 
				
			|||||||
	result, err = rest.Patch(types.ApplyPatchType).
 | 
						result, err = rest.Patch(types.ApplyPatchType).
 | 
				
			||||||
		AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
 | 
							AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
 | 
				
			||||||
		Name("mytest").
 | 
							Name("mytest").
 | 
				
			||||||
 | 
							Param("fieldManager", "apply_test").
 | 
				
			||||||
		Body(yamlBody).
 | 
							Body(yamlBody).
 | 
				
			||||||
		DoRaw()
 | 
							DoRaw()
 | 
				
			||||||
	if err == nil {
 | 
						if err == nil {
 | 
				
			||||||
@@ -108,6 +110,7 @@ values:
 | 
				
			|||||||
		AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
 | 
							AbsPath("/apis", noxuDefinition.Spec.Group, noxuDefinition.Spec.Version, noxuDefinition.Spec.Names.Plural).
 | 
				
			||||||
		Name("mytest").
 | 
							Name("mytest").
 | 
				
			||||||
		Param("force", "true").
 | 
							Param("force", "true").
 | 
				
			||||||
 | 
							Param("fieldManager", "apply_test").
 | 
				
			||||||
		Body(yamlBody).
 | 
							Body(yamlBody).
 | 
				
			||||||
		DoRaw()
 | 
							DoRaw()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -176,6 +176,9 @@ func ValidateObjectMetaAccessor(meta metav1.Object, requiresNamespace bool, name
 | 
				
			|||||||
			allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterName"), meta.GetClusterName(), msg))
 | 
								allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterName"), meta.GetClusterName(), msg))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						for _, entry := range meta.GetManagedFields() {
 | 
				
			||||||
 | 
							allErrs = append(allErrs, v1validation.ValidateFieldManager(entry.Manager, fldPath.Child("fieldManager"))...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	allErrs = append(allErrs, ValidateNonnegativeField(meta.GetGeneration(), fldPath.Child("generation"))...)
 | 
						allErrs = append(allErrs, ValidateNonnegativeField(meta.GetGeneration(), fldPath.Child("generation"))...)
 | 
				
			||||||
	allErrs = append(allErrs, v1validation.ValidateLabels(meta.GetLabels(), fldPath.Child("labels"))...)
 | 
						allErrs = append(allErrs, v1validation.ValidateLabels(meta.GetLabels(), fldPath.Child("labels"))...)
 | 
				
			||||||
	allErrs = append(allErrs, ValidateAnnotations(meta.GetAnnotations(), fldPath.Child("annotations"))...)
 | 
						allErrs = append(allErrs, ValidateAnnotations(meta.GetAnnotations(), fldPath.Child("annotations"))...)
 | 
				
			||||||
@@ -239,6 +242,9 @@ func ValidateObjectMetaAccessorUpdate(newMeta, oldMeta metav1.Object, fldPath *f
 | 
				
			|||||||
		allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.GetGeneration(), "must not be decremented"))
 | 
							allErrs = append(allErrs, field.Invalid(fldPath.Child("generation"), newMeta.GetGeneration(), "must not be decremented"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, entry := range newMeta.GetManagedFields() {
 | 
				
			||||||
 | 
							allErrs = append(allErrs, v1validation.ValidateFieldManager(entry.Manager, fldPath.Child("fieldManager"))...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	allErrs = append(allErrs, ValidateImmutableField(newMeta.GetName(), oldMeta.GetName(), fldPath.Child("name"))...)
 | 
						allErrs = append(allErrs, ValidateImmutableField(newMeta.GetName(), oldMeta.GetName(), fldPath.Child("name"))...)
 | 
				
			||||||
	allErrs = append(allErrs, ValidateImmutableField(newMeta.GetNamespace(), oldMeta.GetNamespace(), fldPath.Child("namespace"))...)
 | 
						allErrs = append(allErrs, ValidateImmutableField(newMeta.GetNamespace(), oldMeta.GetNamespace(), fldPath.Child("namespace"))...)
 | 
				
			||||||
	allErrs = append(allErrs, ValidateImmutableField(newMeta.GetUID(), oldMeta.GetUID(), fldPath.Child("uid"))...)
 | 
						allErrs = append(allErrs, ValidateImmutableField(newMeta.GetUID(), oldMeta.GetUID(), fldPath.Child("uid"))...)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -506,6 +506,13 @@ type CreateOptions struct {
 | 
				
			|||||||
	// +optional
 | 
						// +optional
 | 
				
			||||||
	DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,1,rep,name=dryRun"`
 | 
						DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,1,rep,name=dryRun"`
 | 
				
			||||||
	// +k8s:deprecated=includeUninitialized,protobuf=2
 | 
						// +k8s:deprecated=includeUninitialized,protobuf=2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// fieldManager is a name associated with the actor or entity
 | 
				
			||||||
 | 
						// that is making these changes. The value must be less than or
 | 
				
			||||||
 | 
						// 128 characters long, and only contain printable characters,
 | 
				
			||||||
 | 
						// as defined by https://golang.org/pkg/unicode/#IsPrint.
 | 
				
			||||||
 | 
						// +optional
 | 
				
			||||||
 | 
						FieldManager string `json:"fieldManager,omitempty" protobuf:"bytes,3,name=fieldManager"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 | 
					// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 | 
				
			||||||
@@ -528,6 +535,16 @@ type PatchOptions struct {
 | 
				
			|||||||
	// flag must be unset for non-apply patch requests.
 | 
						// flag must be unset for non-apply patch requests.
 | 
				
			||||||
	// +optional
 | 
						// +optional
 | 
				
			||||||
	Force *bool `json:"force,omitempty" protobuf:"varint,2,opt,name=force"`
 | 
						Force *bool `json:"force,omitempty" protobuf:"varint,2,opt,name=force"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// fieldManager is a name associated with the actor or entity
 | 
				
			||||||
 | 
						// that is making these changes. The value must be less than or
 | 
				
			||||||
 | 
						// 128 characters long, and only contain printable characters,
 | 
				
			||||||
 | 
						// as defined by https://golang.org/pkg/unicode/#IsPrint. This
 | 
				
			||||||
 | 
						// field is required for apply requests
 | 
				
			||||||
 | 
						// (application/apply-patch) but optional for non-apply patch
 | 
				
			||||||
 | 
						// types (JsonPatch, MergePatch, StrategicMergePatch).
 | 
				
			||||||
 | 
						// +optional
 | 
				
			||||||
 | 
						FieldManager string `json:"fieldManager,omitempty" protobuf:"bytes,3,name=fieldManager"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 | 
					// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 | 
				
			||||||
@@ -544,6 +561,13 @@ type UpdateOptions struct {
 | 
				
			|||||||
	// - All: all dry run stages will be processed
 | 
						// - All: all dry run stages will be processed
 | 
				
			||||||
	// +optional
 | 
						// +optional
 | 
				
			||||||
	DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,1,rep,name=dryRun"`
 | 
						DryRun []string `json:"dryRun,omitempty" protobuf:"bytes,1,rep,name=dryRun"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// fieldManager is a name associated with the actor or entity
 | 
				
			||||||
 | 
						// that is making these changes. The value must be less than or
 | 
				
			||||||
 | 
						// 128 characters long, and only contain printable characters,
 | 
				
			||||||
 | 
						// as defined by https://golang.org/pkg/unicode/#IsPrint.
 | 
				
			||||||
 | 
						// +optional
 | 
				
			||||||
 | 
						FieldManager string `json:"fieldManager,omitempty" protobuf:"bytes,2,name=fieldManager"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.
 | 
					// Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,11 @@ go_test(
 | 
				
			|||||||
    name = "go_default_test",
 | 
					    name = "go_default_test",
 | 
				
			||||||
    srcs = ["validation_test.go"],
 | 
					    srcs = ["validation_test.go"],
 | 
				
			||||||
    embed = [":go_default_library"],
 | 
					    embed = [":go_default_library"],
 | 
				
			||||||
    deps = ["//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library"],
 | 
					    deps = [
 | 
				
			||||||
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
 | 
				
			||||||
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
 | 
				
			||||||
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
go_library(
 | 
					go_library(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,9 @@ limitations under the License.
 | 
				
			|||||||
package validation
 | 
					package validation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"unicode"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/types"
 | 
						"k8s.io/apimachinery/pkg/types"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/sets"
 | 
						"k8s.io/apimachinery/pkg/util/sets"
 | 
				
			||||||
@@ -91,22 +94,58 @@ func ValidateDeleteOptions(options *metav1.DeleteOptions) field.ErrorList {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ValidateCreateOptions(options *metav1.CreateOptions) field.ErrorList {
 | 
					func ValidateCreateOptions(options *metav1.CreateOptions) field.ErrorList {
 | 
				
			||||||
	return ValidateDryRun(field.NewPath("dryRun"), options.DryRun)
 | 
						return append(
 | 
				
			||||||
 | 
							ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager")),
 | 
				
			||||||
 | 
							ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ValidateUpdateOptions(options *metav1.UpdateOptions) field.ErrorList {
 | 
					func ValidateUpdateOptions(options *metav1.UpdateOptions) field.ErrorList {
 | 
				
			||||||
	return ValidateDryRun(field.NewPath("dryRun"), options.DryRun)
 | 
						return append(
 | 
				
			||||||
 | 
							ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager")),
 | 
				
			||||||
 | 
							ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ValidatePatchOptions(options *metav1.PatchOptions, patchType types.PatchType) field.ErrorList {
 | 
					func ValidatePatchOptions(options *metav1.PatchOptions, patchType types.PatchType) field.ErrorList {
 | 
				
			||||||
	allErrs := field.ErrorList{}
 | 
						allErrs := field.ErrorList{}
 | 
				
			||||||
	if patchType != types.ApplyPatchType && options.Force != nil {
 | 
						if patchType != types.ApplyPatchType {
 | 
				
			||||||
 | 
							if options.Force != nil {
 | 
				
			||||||
			allErrs = append(allErrs, field.Forbidden(field.NewPath("force"), "may not be specified for non-apply patch"))
 | 
								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"))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...)
 | 
				
			||||||
	allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...)
 | 
						allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...)
 | 
				
			||||||
	return allErrs
 | 
						return allErrs
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var FieldManagerMaxLength = 128
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ValidateFieldManager valides that the fieldManager is the proper length and
 | 
				
			||||||
 | 
					// only has printable characters.
 | 
				
			||||||
 | 
					func ValidateFieldManager(fieldManager string, fldPath *field.Path) field.ErrorList {
 | 
				
			||||||
 | 
						allErrs := field.ErrorList{}
 | 
				
			||||||
 | 
						// the field can not be set as a `*string`, so a empty string ("") is
 | 
				
			||||||
 | 
						// considered as not set and is defaulted by the rest of the process
 | 
				
			||||||
 | 
						// (unless apply is used, in which case it is required).
 | 
				
			||||||
 | 
						if len(fieldManager) > FieldManagerMaxLength {
 | 
				
			||||||
 | 
							allErrs = append(allErrs, field.TooLong(fldPath, fieldManager, FieldManagerMaxLength))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Verify that all characters are printable.
 | 
				
			||||||
 | 
						for i, r := range fieldManager {
 | 
				
			||||||
 | 
							if !unicode.IsPrint(r) {
 | 
				
			||||||
 | 
								allErrs = append(allErrs, field.Invalid(fldPath, fieldManager, fmt.Sprintf("invalid character %#U (at position %d)", r, i)))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return allErrs
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var allowedDryRunValues = sets.NewString(metav1.DryRunAll)
 | 
					var allowedDryRunValues = sets.NewString(metav1.DryRunAll)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ValidateDryRun validates that a dryRun query param only contains allowed values.
 | 
					// ValidateDryRun validates that a dryRun query param only contains allowed values.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,8 @@ import (
 | 
				
			|||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/types"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/validation/field"
 | 
						"k8s.io/apimachinery/pkg/util/validation/field"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -125,3 +127,117 @@ func TestInvalidDryRun(t *testing.T) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func boolPtr(b bool) *bool {
 | 
				
			||||||
 | 
						return &b
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestValidPatchOptions(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							opts      metav1.PatchOptions
 | 
				
			||||||
 | 
							patchType types.PatchType
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								opts: metav1.PatchOptions{
 | 
				
			||||||
 | 
									Force:        boolPtr(true),
 | 
				
			||||||
 | 
									FieldManager: "kubectl",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								patchType: types.ApplyPatchType,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								opts: metav1.PatchOptions{
 | 
				
			||||||
 | 
									FieldManager: "kubectl",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								patchType: types.ApplyPatchType,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								opts:      metav1.PatchOptions{},
 | 
				
			||||||
 | 
								patchType: types.MergePatchType,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								opts: metav1.PatchOptions{
 | 
				
			||||||
 | 
									FieldManager: "patcher",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								patchType: types.MergePatchType,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, test := range tests {
 | 
				
			||||||
 | 
							t.Run(fmt.Sprintf("%v", test.opts), func(t *testing.T) {
 | 
				
			||||||
 | 
								errs := ValidatePatchOptions(&test.opts, test.patchType)
 | 
				
			||||||
 | 
								if len(errs) != 0 {
 | 
				
			||||||
 | 
									t.Fatalf("Expected no failures, got: %v", errs)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestInvalidPatchOptions(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							opts      metav1.PatchOptions
 | 
				
			||||||
 | 
							patchType types.PatchType
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							// missing manager
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								opts:      metav1.PatchOptions{},
 | 
				
			||||||
 | 
								patchType: types.ApplyPatchType,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// force on non-apply
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								opts: metav1.PatchOptions{
 | 
				
			||||||
 | 
									Force: boolPtr(true),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								patchType: types.MergePatchType,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							// manager and force on non-apply
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								opts: metav1.PatchOptions{
 | 
				
			||||||
 | 
									FieldManager: "kubectl",
 | 
				
			||||||
 | 
									Force:        boolPtr(false),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								patchType: types.MergePatchType,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, test := range tests {
 | 
				
			||||||
 | 
							t.Run(fmt.Sprintf("%v", test.opts), func(t *testing.T) {
 | 
				
			||||||
 | 
								errs := ValidatePatchOptions(&test.opts, test.patchType)
 | 
				
			||||||
 | 
								if len(errs) == 0 {
 | 
				
			||||||
 | 
									t.Fatal("Expected failures, got none.")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestValidateFieldManagerValid(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []string{
 | 
				
			||||||
 | 
							"filedManager",
 | 
				
			||||||
 | 
							"你好", // Hello
 | 
				
			||||||
 | 
							"🍔",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, test := range tests {
 | 
				
			||||||
 | 
							t.Run(test, func(t *testing.T) {
 | 
				
			||||||
 | 
								errs := ValidateFieldManager(test, field.NewPath("fieldManager"))
 | 
				
			||||||
 | 
								if len(errs) != 0 {
 | 
				
			||||||
 | 
									t.Errorf("Validation failed: %v", errs)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestValidateFieldManagerInvalid(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []string{
 | 
				
			||||||
 | 
							"field\nmanager", // Contains invalid character \n
 | 
				
			||||||
 | 
							"fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", // Has 129 chars
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, test := range tests {
 | 
				
			||||||
 | 
							t.Run(test, func(t *testing.T) {
 | 
				
			||||||
 | 
								errs := ValidateFieldManager(test, field.NewPath("fieldManager"))
 | 
				
			||||||
 | 
								if len(errs) == 0 {
 | 
				
			||||||
 | 
									t.Errorf("Validation should have failed")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ load(
 | 
				
			|||||||
go_test(
 | 
					go_test(
 | 
				
			||||||
    name = "go_default_test",
 | 
					    name = "go_default_test",
 | 
				
			||||||
    srcs = [
 | 
					    srcs = [
 | 
				
			||||||
 | 
					        "create_test.go",
 | 
				
			||||||
        "namer_test.go",
 | 
					        "namer_test.go",
 | 
				
			||||||
        "rest_test.go",
 | 
					        "rest_test.go",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,11 +17,14 @@ limitations under the License.
 | 
				
			|||||||
package handlers
 | 
					package handlers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
						"unicode"
 | 
				
			||||||
 | 
						"unicode/utf8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/api/errors"
 | 
						"k8s.io/apimachinery/pkg/api/errors"
 | 
				
			||||||
	metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
 | 
						metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
 | 
				
			||||||
@@ -141,7 +144,7 @@ func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Inte
 | 
				
			|||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			obj, err = scope.FieldManager.Update(liveObj, obj, prefixFromUserAgent(req.UserAgent()))
 | 
								obj, err = scope.FieldManager.Update(liveObj, obj, managerOrUserAgent(options.FieldManager, req.UserAgent()))
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				scope.err(fmt.Errorf("failed to update object (Create for %v) managed fields: %v", scope.Kind, err), w, req)
 | 
									scope.err(fmt.Errorf("failed to update object (Create for %v) managed fields: %v", scope.Kind, err), w, req)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
@@ -193,6 +196,31 @@ func (c *namedCreaterAdapter) Create(ctx context.Context, name string, obj runti
 | 
				
			|||||||
	return c.Creater.Create(ctx, obj, createValidatingAdmission, options)
 | 
						return c.Creater.Create(ctx, obj, createValidatingAdmission, options)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func prefixFromUserAgent(u string) string {
 | 
					// manager is assumed to be already a valid value, we need to make
 | 
				
			||||||
	return strings.Split(u, "/")[0]
 | 
					// userAgent into a valid value too.
 | 
				
			||||||
 | 
					func managerOrUserAgent(manager, userAgent string) string {
 | 
				
			||||||
 | 
						if manager != "" {
 | 
				
			||||||
 | 
							return manager
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return prefixFromUserAgent(userAgent)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// prefixFromUserAgent takes the characters preceding the first /, quote
 | 
				
			||||||
 | 
					// unprintable character and then trim what's beyond the
 | 
				
			||||||
 | 
					// FieldManagerMaxLength limit.
 | 
				
			||||||
 | 
					func prefixFromUserAgent(u string) string {
 | 
				
			||||||
 | 
						m := strings.Split(u, "/")[0]
 | 
				
			||||||
 | 
						buf := bytes.NewBuffer(nil)
 | 
				
			||||||
 | 
						for _, r := range m {
 | 
				
			||||||
 | 
							// Ignore non-printable characters
 | 
				
			||||||
 | 
							if !unicode.IsPrint(r) {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Only append if we have room for it
 | 
				
			||||||
 | 
							if buf.Len()+utf8.RuneLen(r) > validation.FieldManagerMaxLength {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							buf.WriteRune(r)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return buf.String()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					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 handlers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestManagerOrUserAgent(t *testing.T) {
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							manager   string
 | 
				
			||||||
 | 
							userAgent string
 | 
				
			||||||
 | 
							expected  string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								manager:   "",
 | 
				
			||||||
 | 
								userAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
 | 
				
			||||||
 | 
								expected:  "Mozilla",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								manager:   "",
 | 
				
			||||||
 | 
								userAgent: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff/Something",
 | 
				
			||||||
 | 
								expected:  "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								manager:   "",
 | 
				
			||||||
 | 
								userAgent: "🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔",
 | 
				
			||||||
 | 
								expected:  "🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔🍔",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								manager:   "manager",
 | 
				
			||||||
 | 
								userAgent: "userAgent",
 | 
				
			||||||
 | 
								expected:  "manager",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, test := range tests {
 | 
				
			||||||
 | 
							t.Run(fmt.Sprintf("%v-%v", test.manager, test.userAgent), func(t *testing.T) {
 | 
				
			||||||
 | 
								got := managerOrUserAgent(test.manager, test.userAgent)
 | 
				
			||||||
 | 
								if got != test.expected {
 | 
				
			||||||
 | 
									t.Errorf("Wanted %#v, got %#v", test.expected, got)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -30,8 +30,6 @@ import (
 | 
				
			|||||||
	"sigs.k8s.io/structured-merge-diff/merge"
 | 
						"sigs.k8s.io/structured-merge-diff/merge"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const applyManager = "apply"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// FieldManager updates the managed fields and merge applied
 | 
					// FieldManager updates the managed fields and merge applied
 | 
				
			||||||
// configurations.
 | 
					// configurations.
 | 
				
			||||||
type FieldManager struct {
 | 
					type FieldManager struct {
 | 
				
			||||||
@@ -141,7 +139,7 @@ func (f *FieldManager) Update(liveObj, newObj runtime.Object, manager string) (r
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Apply is used when server-side apply is called, as it merges the
 | 
					// Apply is used when server-side apply is called, as it merges the
 | 
				
			||||||
// object and update the managed fields.
 | 
					// object and update the managed fields.
 | 
				
			||||||
func (f *FieldManager) Apply(liveObj runtime.Object, patch []byte, force bool) (runtime.Object, error) {
 | 
					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 the object doesn't have metadata, apply isn't allowed.
 | 
				
			||||||
	if _, err := meta.Accessor(liveObj); err != nil {
 | 
						if _, err := meta.Accessor(liveObj); err != nil {
 | 
				
			||||||
		return nil, fmt.Errorf("couldn't get accessor: %v", err)
 | 
							return nil, fmt.Errorf("couldn't get accessor: %v", err)
 | 
				
			||||||
@@ -168,7 +166,7 @@ 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)
 | 
						manager, err := f.buildManagerInfo(fieldManager, metav1.ManagedFieldsOperationApply)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, fmt.Errorf("failed to build manager identifier: %v", err)
 | 
							return nil, fmt.Errorf("failed to build manager identifier: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -96,7 +96,7 @@ func TestApplyStripsFields(t *testing.T) {
 | 
				
			|||||||
				}],
 | 
									}],
 | 
				
			||||||
			"resourceVersion": "b"
 | 
								"resourceVersion": "b"
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}`), false)
 | 
						}`), "fieldmanager_test", false)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatalf("failed to apply object: %v", err)
 | 
							t.Fatalf("failed to apply object: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -110,6 +110,7 @@ func TestApplyStripsFields(t *testing.T) {
 | 
				
			|||||||
		t.Fatalf("fields did not get stripped on apply: %v", m)
 | 
							t.Fatalf("fields did not get stripped on apply: %v", m)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestApplyDoesNotStripLabels(t *testing.T) {
 | 
					func TestApplyDoesNotStripLabels(t *testing.T) {
 | 
				
			||||||
	f := NewTestFieldManager(t)
 | 
						f := NewTestFieldManager(t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -123,7 +124,7 @@ func TestApplyDoesNotStripLabels(t *testing.T) {
 | 
				
			|||||||
				"a": "b"
 | 
									"a": "b"
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}`), false)
 | 
						}`), "fieldmanager_test", false)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatalf("failed to apply object: %v", err)
 | 
							t.Fatalf("failed to apply object: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -320,7 +320,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, prefixFromUserAgent(p.userAgent)); err != 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 nil, fmt.Errorf("failed to update object (json PATCH for %v) managed fields: %v", p.kind, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -387,7 +387,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, prefixFromUserAgent(p.userAgent)); err != 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 nil, fmt.Errorf("failed to update object (smp PATCH for %v) managed fields: %v", p.kind, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -414,7 +414,7 @@ func (p *applyPatcher) applyPatchToCurrentObject(obj runtime.Object) (runtime.Ob
 | 
				
			|||||||
	if p.fieldManager == nil {
 | 
						if p.fieldManager == nil {
 | 
				
			||||||
		panic("FieldManager must be installed to run apply")
 | 
							panic("FieldManager must be installed to run apply")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return p.fieldManager.Apply(obj, p.patch, force)
 | 
						return p.fieldManager.Apply(obj, p.patch, p.options.FieldManager, force)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (p *applyPatcher) createNewObject() (runtime.Object, error) {
 | 
					func (p *applyPatcher) createNewObject() (runtime.Object, error) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -124,7 +124,7 @@ func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interfac
 | 
				
			|||||||
		transformers := []rest.TransformFunc{}
 | 
							transformers := []rest.TransformFunc{}
 | 
				
			||||||
		if scope.FieldManager != nil {
 | 
							if scope.FieldManager != nil {
 | 
				
			||||||
			transformers = append(transformers, func(_ context.Context, newObj, liveObj runtime.Object) (runtime.Object, error) {
 | 
								transformers = append(transformers, func(_ context.Context, newObj, liveObj runtime.Object) (runtime.Object, error) {
 | 
				
			||||||
				obj, err := scope.FieldManager.Update(liveObj, newObj, prefixFromUserAgent(req.UserAgent()))
 | 
									obj, err := scope.FieldManager.Update(liveObj, newObj, managerOrUserAgent(options.FieldManager, req.UserAgent()))
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					return nil, fmt.Errorf("failed to update object (Update for %v) managed fields: %v", scope.Kind, err)
 | 
										return nil, fmt.Errorf("failed to update object (Update for %v) managed fields: %v", scope.Kind, err)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -249,12 +249,12 @@ run_kubectl_apply_tests() {
 | 
				
			|||||||
  set -o errexit
 | 
					  set -o errexit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  create_and_use_new_namespace
 | 
					  create_and_use_new_namespace
 | 
				
			||||||
  kube::log::status "Testing kubectl apply --server-side"
 | 
					  kube::log::status "Testing kubectl apply --experimental-server-side"
 | 
				
			||||||
  ## kubectl apply should create the resource that doesn't exist yet
 | 
					  ## kubectl apply should create the resource that doesn't exist yet
 | 
				
			||||||
  # Pre-Condition: no POD exists
 | 
					  # Pre-Condition: no POD exists
 | 
				
			||||||
  kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
 | 
					  kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
 | 
				
			||||||
  # Command: apply a pod "test-pod" (doesn't exist) should create this pod
 | 
					  # Command: apply a pod "test-pod" (doesn't exist) should create this pod
 | 
				
			||||||
  kubectl apply --server-side -f hack/testdata/pod.yaml "${kube_flags[@]}"
 | 
					  kubectl apply --experimental-server-side -f hack/testdata/pod.yaml "${kube_flags[@]}"
 | 
				
			||||||
  # Post-Condition: pod "test-pod" is created
 | 
					  # Post-Condition: pod "test-pod" is created
 | 
				
			||||||
  kube::test::get_object_assert 'pods test-pod' "{{${labels_field}.name}}" 'test-pod-label'
 | 
					  kube::test::get_object_assert 'pods test-pod' "{{${labels_field}.name}}" 'test-pod-label'
 | 
				
			||||||
  # Clean up
 | 
					  # Clean up
 | 
				
			||||||
@@ -265,13 +265,13 @@ run_kubectl_apply_tests() {
 | 
				
			|||||||
  kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
 | 
					  kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # apply dry-run
 | 
					  # apply dry-run
 | 
				
			||||||
  kubectl apply --server-side --server-dry-run -f hack/testdata/pod.yaml "${kube_flags[@]}"
 | 
					  kubectl apply --experimental-server-side --server-dry-run -f hack/testdata/pod.yaml "${kube_flags[@]}"
 | 
				
			||||||
  # No pod exists
 | 
					  # No pod exists
 | 
				
			||||||
  kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
 | 
					  kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
 | 
				
			||||||
  # apply non dry-run creates the pod
 | 
					  # apply non dry-run creates the pod
 | 
				
			||||||
  kubectl apply --server-side -f hack/testdata/pod.yaml "${kube_flags[@]}"
 | 
					  kubectl apply --experimental-server-side -f hack/testdata/pod.yaml "${kube_flags[@]}"
 | 
				
			||||||
  # apply changes
 | 
					  # apply changes
 | 
				
			||||||
  kubectl apply --server-side --server-dry-run -f hack/testdata/pod-apply.yaml "${kube_flags[@]}"
 | 
					  kubectl apply --experimental-server-side --server-dry-run -f hack/testdata/pod-apply.yaml "${kube_flags[@]}"
 | 
				
			||||||
  # Post-Condition: label still has initial value
 | 
					  # Post-Condition: label still has initial value
 | 
				
			||||||
  kube::test::get_object_assert 'pods test-pod' "{{${labels_field}.name}}" 'test-pod-label'
 | 
					  kube::test::get_object_assert 'pods test-pod' "{{${labels_field}.name}}" 'test-pod-label'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -302,7 +302,7 @@ run_kubectl_apply_tests() {
 | 
				
			|||||||
__EOF__
 | 
					__EOF__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Dry-run create the CR
 | 
					  # Dry-run create the CR
 | 
				
			||||||
  kubectl "${kube_flags[@]}" apply --server-side --server-dry-run -f hack/testdata/CRD/resource.yaml "${kube_flags[@]}"
 | 
					  kubectl "${kube_flags[@]}" apply --experimental-server-side --server-dry-run -f hack/testdata/CRD/resource.yaml "${kube_flags[@]}"
 | 
				
			||||||
  # Make sure that the CR doesn't exist
 | 
					  # Make sure that the CR doesn't exist
 | 
				
			||||||
  ! kubectl "${kube_flags[@]}" get resource/myobj
 | 
					  ! kubectl "${kube_flags[@]}" get resource/myobj
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,10 +2,15 @@ load("@io_bazel_rules_go//go:def.bzl", "go_test")
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
go_test(
 | 
					go_test(
 | 
				
			||||||
    name = "go_default_test",
 | 
					    name = "go_default_test",
 | 
				
			||||||
 | 
					    size = "large",
 | 
				
			||||||
    srcs = [
 | 
					    srcs = [
 | 
				
			||||||
        "apply_test.go",
 | 
					        "apply_test.go",
 | 
				
			||||||
        "main_test.go",
 | 
					        "main_test.go",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					    tags = [
 | 
				
			||||||
 | 
					        "etcd",
 | 
				
			||||||
 | 
					        "integration",
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
    deps = [
 | 
					    deps = [
 | 
				
			||||||
        "//pkg/master:go_default_library",
 | 
					        "//pkg/master:go_default_library",
 | 
				
			||||||
        "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
 | 
					        "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -106,6 +106,7 @@ func TestApplyAlsoCreates(t *testing.T) {
 | 
				
			|||||||
			Namespace("default").
 | 
								Namespace("default").
 | 
				
			||||||
			Resource(tc.resource).
 | 
								Resource(tc.resource).
 | 
				
			||||||
			Name(tc.name).
 | 
								Name(tc.name).
 | 
				
			||||||
 | 
								Param("fieldManager", "apply_test").
 | 
				
			||||||
			Body([]byte(tc.body)).
 | 
								Body([]byte(tc.body)).
 | 
				
			||||||
			Do().
 | 
								Do().
 | 
				
			||||||
			Get()
 | 
								Get()
 | 
				
			||||||
@@ -132,6 +133,7 @@ func TestCreateOnApplyFailsWithUID(t *testing.T) {
 | 
				
			|||||||
		Namespace("default").
 | 
							Namespace("default").
 | 
				
			||||||
		Resource("pods").
 | 
							Resource("pods").
 | 
				
			||||||
		Name("test-pod-uid").
 | 
							Name("test-pod-uid").
 | 
				
			||||||
 | 
							Param("fieldManager", "apply_test").
 | 
				
			||||||
		Body([]byte(`{
 | 
							Body([]byte(`{
 | 
				
			||||||
			"apiVersion": "v1",
 | 
								"apiVersion": "v1",
 | 
				
			||||||
			"kind": "Pod",
 | 
								"kind": "Pod",
 | 
				
			||||||
@@ -194,6 +196,7 @@ func TestApplyUpdateApplyConflictForced(t *testing.T) {
 | 
				
			|||||||
		Namespace("default").
 | 
							Namespace("default").
 | 
				
			||||||
		Resource("deployments").
 | 
							Resource("deployments").
 | 
				
			||||||
		Name("deployment").
 | 
							Name("deployment").
 | 
				
			||||||
 | 
							Param("fieldManager", "apply_test").
 | 
				
			||||||
		Body(obj).Do().Get()
 | 
							Body(obj).Do().Get()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatalf("Failed to create object using Apply patch: %v", err)
 | 
							t.Fatalf("Failed to create object using Apply patch: %v", err)
 | 
				
			||||||
@@ -214,6 +217,7 @@ func TestApplyUpdateApplyConflictForced(t *testing.T) {
 | 
				
			|||||||
		Namespace("default").
 | 
							Namespace("default").
 | 
				
			||||||
		Resource("deployments").
 | 
							Resource("deployments").
 | 
				
			||||||
		Name("deployment").
 | 
							Name("deployment").
 | 
				
			||||||
 | 
							Param("fieldManager", "apply_test").
 | 
				
			||||||
		Body([]byte(obj)).Do().Get()
 | 
							Body([]byte(obj)).Do().Get()
 | 
				
			||||||
	if err == nil {
 | 
						if err == nil {
 | 
				
			||||||
		t.Fatalf("Expecting to get conflicts when applying object")
 | 
							t.Fatalf("Expecting to get conflicts when applying object")
 | 
				
			||||||
@@ -232,6 +236,7 @@ func TestApplyUpdateApplyConflictForced(t *testing.T) {
 | 
				
			|||||||
		Resource("deployments").
 | 
							Resource("deployments").
 | 
				
			||||||
		Name("deployment").
 | 
							Name("deployment").
 | 
				
			||||||
		Param("force", "true").
 | 
							Param("force", "true").
 | 
				
			||||||
 | 
							Param("fieldManager", "apply_test").
 | 
				
			||||||
		Body([]byte(obj)).Do().Get()
 | 
							Body([]byte(obj)).Do().Get()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatalf("Failed to apply object with force: %v", err)
 | 
							t.Fatalf("Failed to apply object with force: %v", err)
 | 
				
			||||||
@@ -249,6 +254,7 @@ func TestApplyManagedFields(t *testing.T) {
 | 
				
			|||||||
		Namespace("default").
 | 
							Namespace("default").
 | 
				
			||||||
		Resource("configmaps").
 | 
							Resource("configmaps").
 | 
				
			||||||
		Name("test-cm").
 | 
							Name("test-cm").
 | 
				
			||||||
 | 
							Param("fieldManager", "apply_test").
 | 
				
			||||||
		Body([]byte(`{
 | 
							Body([]byte(`{
 | 
				
			||||||
			"apiVersion": "v1",
 | 
								"apiVersion": "v1",
 | 
				
			||||||
			"kind": "ConfigMap",
 | 
								"kind": "ConfigMap",
 | 
				
			||||||
@@ -273,6 +279,7 @@ func TestApplyManagedFields(t *testing.T) {
 | 
				
			|||||||
		Namespace("default").
 | 
							Namespace("default").
 | 
				
			||||||
		Resource("configmaps").
 | 
							Resource("configmaps").
 | 
				
			||||||
		Name("test-cm").
 | 
							Name("test-cm").
 | 
				
			||||||
 | 
							Param("fieldManager", "updater").
 | 
				
			||||||
		Body([]byte(`{"data":{"key": "new value"}}`)).Do().Get()
 | 
							Body([]byte(`{"data":{"key": "new value"}}`)).Do().Get()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatalf("Failed to patch object: %v", err)
 | 
							t.Fatalf("Failed to patch object: %v", err)
 | 
				
			||||||
@@ -306,7 +313,7 @@ func TestApplyManagedFields(t *testing.T) {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
			"managedFields": [
 | 
								"managedFields": [
 | 
				
			||||||
				{
 | 
									{
 | 
				
			||||||
					"manager": "apply",
 | 
										"manager": "apply_test",
 | 
				
			||||||
					"operation": "Apply",
 | 
										"operation": "Apply",
 | 
				
			||||||
					"apiVersion": "v1",
 | 
										"apiVersion": "v1",
 | 
				
			||||||
					"fields": {
 | 
										"fields": {
 | 
				
			||||||
@@ -318,7 +325,7 @@ func TestApplyManagedFields(t *testing.T) {
 | 
				
			|||||||
					}
 | 
										}
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
				{
 | 
									{
 | 
				
			||||||
					"manager": "` + accessor.GetManagedFields()[1].Manager + `",
 | 
										"manager": "updater",
 | 
				
			||||||
					"operation": "Update",
 | 
										"operation": "Update",
 | 
				
			||||||
					"apiVersion": "v1",
 | 
										"apiVersion": "v1",
 | 
				
			||||||
					"time": "` + accessor.GetManagedFields()[1].Time.UTC().Format(time.RFC3339) + `",
 | 
										"time": "` + accessor.GetManagedFields()[1].Time.UTC().Format(time.RFC3339) + `",
 | 
				
			||||||
@@ -360,6 +367,7 @@ func TestApplyRemovesEmptyManagedFields(t *testing.T) {
 | 
				
			|||||||
		Namespace("default").
 | 
							Namespace("default").
 | 
				
			||||||
		Resource("configmaps").
 | 
							Resource("configmaps").
 | 
				
			||||||
		Name("test-cm").
 | 
							Name("test-cm").
 | 
				
			||||||
 | 
							Param("fieldManager", "apply_test").
 | 
				
			||||||
		Body(obj).
 | 
							Body(obj).
 | 
				
			||||||
		Do().
 | 
							Do().
 | 
				
			||||||
		Get()
 | 
							Get()
 | 
				
			||||||
@@ -371,6 +379,7 @@ func TestApplyRemovesEmptyManagedFields(t *testing.T) {
 | 
				
			|||||||
		Namespace("default").
 | 
							Namespace("default").
 | 
				
			||||||
		Resource("configmaps").
 | 
							Resource("configmaps").
 | 
				
			||||||
		Name("test-cm").
 | 
							Name("test-cm").
 | 
				
			||||||
 | 
							Param("fieldManager", "apply_test").
 | 
				
			||||||
		Body(obj).Do().Get()
 | 
							Body(obj).Do().Get()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		t.Fatalf("Failed to patch object: %v", err)
 | 
							t.Fatalf("Failed to patch object: %v", err)
 | 
				
			||||||
@@ -390,3 +399,42 @@ func TestApplyRemovesEmptyManagedFields(t *testing.T) {
 | 
				
			|||||||
		t.Fatalf("Object contains unexpected managedFields: %v", managed)
 | 
							t.Fatalf("Object contains unexpected managedFields: %v", managed)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestApplyRequiresFieldManager(t *testing.T) {
 | 
				
			||||||
 | 
						defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, client, closeFn := setup(t)
 | 
				
			||||||
 | 
						defer closeFn()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						obj := []byte(`{
 | 
				
			||||||
 | 
							"apiVersion": "v1",
 | 
				
			||||||
 | 
							"kind": "ConfigMap",
 | 
				
			||||||
 | 
							"metadata": {
 | 
				
			||||||
 | 
								"name": "test-cm",
 | 
				
			||||||
 | 
								"namespace": "default"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
 | 
				
			||||||
 | 
							Namespace("default").
 | 
				
			||||||
 | 
							Resource("configmaps").
 | 
				
			||||||
 | 
							Name("test-cm").
 | 
				
			||||||
 | 
							Body(obj).
 | 
				
			||||||
 | 
							Do().
 | 
				
			||||||
 | 
							Get()
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							t.Fatalf("Apply should fail to create without fieldManager")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
 | 
				
			||||||
 | 
							Namespace("default").
 | 
				
			||||||
 | 
							Resource("configmaps").
 | 
				
			||||||
 | 
							Name("test-cm").
 | 
				
			||||||
 | 
							Param("fieldManager", "apply_test").
 | 
				
			||||||
 | 
							Body(obj).
 | 
				
			||||||
 | 
							Do().
 | 
				
			||||||
 | 
							Get()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("Apply failed to create with fieldManager: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -567,6 +567,9 @@ func TestRBAC(t *testing.T) {
 | 
				
			|||||||
			if r.verb == "PATCH" {
 | 
								if r.verb == "PATCH" {
 | 
				
			||||||
				// For patch operations, use the apply content type
 | 
									// For patch operations, use the apply content type
 | 
				
			||||||
				req.Header.Add("Content-Type", string(types.ApplyPatchType))
 | 
									req.Header.Add("Content-Type", string(types.ApplyPatchType))
 | 
				
			||||||
 | 
									q := req.URL.Query()
 | 
				
			||||||
 | 
									q.Add("fieldManager", "rbac_test")
 | 
				
			||||||
 | 
									req.URL.RawQuery = q.Encode()
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user