Change:
} } <ins> func TestValidateObjectMetaUpdatePreventsDeletionFieldMutation(t *testing.T) { now := unversioned.NewTime(time.Unix(1000, 0).UTC()) later := unversioned.NewTime(time.Unix(2000, 0).UTC()) gracePeriodShort := int64(30) gracePeriodLong := int64(40) testcases := map[string]struct { Old          api.ObjectMeta New          api.ObjectMeta ExpectedNew  api.ObjectMeta ExpectedErrs []string }{ "valid without deletion fields": { Old:          api.ObjectMeta{Name: "test", ResourceVersion: "1"}, New:          api.ObjectMeta{Name: "test", ResourceVersion: "1"}, ExpectedNew:  api.ObjectMeta{Name: "test", ResourceVersion: "1"}, ExpectedErrs: []string{}, }, "valid with deletion fields": { Old:          api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort}, New:          api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort}, ExpectedNew:  api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now, DeletionGracePeriodSeconds: &gracePeriodShort}, ExpectedErrs: []string{}, }, "invalid set deletionTimestamp": { Old:          api.ObjectMeta{Name: "test", ResourceVersion: "1"}, New:          api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, ExpectedNew:  api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, ExpectedErrs: []string{"field.deletionTimestamp: Invalid value: "1970-01-01T00:16:40Z": field is immutable; may only be changed via deletion"}, }, "invalid clear deletionTimestamp": { Old:          api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, New:          api.ObjectMeta{Name: "test", ResourceVersion: "1"}, ExpectedNew:  api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, ExpectedErrs: []string{}, // no errors, validation copies the old value }, "invalid change deletionTimestamp": { Old:          api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, New:          api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &later}, ExpectedNew:  api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionTimestamp: &now}, ExpectedErrs: []string{}, // no errors, validation copies the old value }, "invalid set deletionGracePeriodSeconds": { Old:          api.ObjectMeta{Name: "test", ResourceVersion: "1"}, New:          api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, ExpectedNew:  api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 30: field is immutable; may only be changed via deletion"}, }, "invalid clear deletionGracePeriodSeconds": { Old:          api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, New:          api.ObjectMeta{Name: "test", ResourceVersion: "1"}, ExpectedNew:  api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, ExpectedErrs: []string{}, // no errors, validation copies the old value }, "invalid change deletionGracePeriodSeconds": { Old:          api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodShort}, New:          api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong}, ExpectedNew:  api.ObjectMeta{Name: "test", ResourceVersion: "1", DeletionGracePeriodSeconds: &gracePeriodLong}, ExpectedErrs: []string{"field.deletionGracePeriodSeconds: Invalid value: 40: field is immutable; may only be changed via deletion"}, }, } for k, tc := range testcases { errs := ValidateObjectMetaUpdate(&tc.New, &tc.Old, field.NewPath("field")) if len(errs) != len(tc.ExpectedErrs) { t.Logf("%s: Expected: %#v", k, tc.ExpectedErrs) t.Logf("%s: Got: %#v", k, errs) t.Errorf("%s: expected %d errors, got %d", k, len(tc.ExpectedErrs), len(errs)) continue } for i := range errs { if errs[i].Error() != tc.ExpectedErrs[i] { t.Errorf("%s: error #%d: expected %q, got %q", k, i, tc.ExpectedErrs[i], errs[i].Error()) } } if !reflect.DeepEqual(tc.New, tc.ExpectedNew) { t.Errorf("%s: Expected after validation:n%#vngotn%#v", k, tc.ExpectedNew, tc.New) } } } </ins> // Ensure trailing slash is allowed in generate name func TestValidateObjectMetaTrimsTrailingSlash(t *testing.T) { errs := ValidateObjectMeta(

Discussion:
Honestly I'm not sure if this is a bug or a feature, but didn't expect it. Filing this to track.
I think it's a bug. Nothing in the code base depends on it that I can see (edit: namespace deletion does, but only via an in-server call which I'd like to see fixed), and we don't let other delete-related fields mutate during update (deleteGracePeriodSeconds)