diff --git a/pkg/api/testing/BUILD b/pkg/api/testing/BUILD index e628fc9e37e..81585c39fe0 100644 --- a/pkg/api/testing/BUILD +++ b/pkg/api/testing/BUILD @@ -95,6 +95,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/api/testing/fuzzer:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion/unstructured:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", diff --git a/pkg/api/testing/unstructured_test.go b/pkg/api/testing/unstructured_test.go index 32eaedc06c0..8894bf4d38b 100644 --- a/pkg/api/testing/unstructured_test.go +++ b/pkg/api/testing/unstructured_test.go @@ -25,7 +25,10 @@ import ( "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/testing/fuzzer" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metaunstruct "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/conversion/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/diff" @@ -131,6 +134,43 @@ func TestRoundTrip(t *testing.T) { } } +func TestRoundTripWithEmptyCreationTimestamp(t *testing.T) { + for groupKey, group := range testapi.Groups { + for kind := range group.ExternalTypes() { + if nonRoundTrippableTypes.Has(kind) { + continue + } + item, err := legacyscheme.Scheme.New(group.GroupVersion().WithKind(kind)) + if err != nil { + t.Fatalf("Couldn't create external object %v: %v", kind, err) + } + t.Logf("Testing: %v in %v", kind, groupKey) + + unstrBody, err := unstructured.DefaultConverter.ToUnstructured(item) + if err != nil { + t.Fatalf("ToUnstructured failed: %v", err) + } + + unstructObj := &metaunstruct.Unstructured{} + unstructObj.Object = unstrBody + + if meta, err := meta.Accessor(unstructObj); err == nil { + meta.SetCreationTimestamp(metav1.Time{}) + } else { + t.Fatalf("Unable to set creation timestamp: %v", err) + } + + // attempt to re-convert unstructured object - conversion should not fail + // based on empty metadata fields, such as creationTimestamp + newObj := reflect.New(reflect.TypeOf(item).Elem()).Interface().(runtime.Object) + err = unstructured.DefaultConverter.FromUnstructured(unstructObj.Object, newObj) + if err != nil { + t.Fatalf("FromUnstructured failed: %v", err) + } + } + } +} + func BenchmarkToFromUnstructured(b *testing.B) { items := benchmarkItems(b) size := len(items) diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go index e95a5397d43..56d18a32e85 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go @@ -258,6 +258,10 @@ func (u *Unstructured) GetCreationTimestamp() metav1.Time { func (u *Unstructured) SetCreationTimestamp(timestamp metav1.Time) { ts, _ := timestamp.MarshalQueryParameter() + if len(ts) == 0 || timestamp.Time.IsZero() { + RemoveNestedField(u.Object, "metadata", "creationTimestamp") + return + } u.setNestedField(ts, "metadata", "creationTimestamp") } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list_test.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list_test.go index e6f0cf8e9a9..db935774a79 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured_list_test.go @@ -62,3 +62,24 @@ func TestNilDeletionTimestamp(t *testing.T) { _, ok = metadata["deletionTimestamp"] assert.False(t, ok) } + +func TestEmptyCreationTimestampIsOmitted(t *testing.T) { + var u Unstructured + now := metav1.Now() + + // set an initial creationTimestamp and ensure the field exists + u.SetCreationTimestamp(now) + metadata := u.Object["metadata"].(map[string]interface{}) + creationTimestamp, exists := metadata["creationTimestamp"] + if !exists { + t.Fatalf("unexpected missing creationTimestamp") + } + + // set an empty timestamp and ensure the field no longer exists + u.SetCreationTimestamp(metav1.Time{}) + metadata = u.Object["metadata"].(map[string]interface{}) + creationTimestamp, exists = metadata["creationTimestamp"] + if exists { + t.Errorf("unexpected creation timestamp field: %q", creationTimestamp) + } +}