mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +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:
parent
8bde75e63f
commit
eb904d8fa8
@ -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 {
|
||||||
@ -393,7 +395,8 @@ func (o *ApplyOptions) Run() error {
|
|||||||
return cmdutil.AddSourceToErr("serverside-apply", info.Source, err)
|
return cmdutil.AddSourceToErr("serverside-apply", info.Source, err)
|
||||||
}
|
}
|
||||||
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 {
|
||||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("force"), "may not be specified for non-apply patch"))
|
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"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user