diff --git a/pkg/kubectl/cmd/apply_test.go b/pkg/kubectl/cmd/apply_test.go index 84a5b3da782..536329b7b0a 100644 --- a/pkg/kubectl/cmd/apply_test.go +++ b/pkg/kubectl/cmd/apply_test.go @@ -178,7 +178,7 @@ func annotateRuntimeObject(t *testing.T, originalObj, currentObj runtime.Object, originalLabels := originalAccessor.GetLabels() originalLabels["DELETE_ME"] = "DELETE_ME" originalAccessor.SetLabels(originalLabels) - original, err := runtime.Encode(testapi.Default.Codec(), originalObj) + original, err := runtime.Encode(unstructured.JSONFallbackEncoder{Encoder: testapi.Default.Codec()}, originalObj) if err != nil { t.Fatal(err) } @@ -194,7 +194,7 @@ func annotateRuntimeObject(t *testing.T, originalObj, currentObj runtime.Object, } currentAnnotations[api.LastAppliedConfigAnnotation] = string(original) currentAccessor.SetAnnotations(currentAnnotations) - current, err := runtime.Encode(testapi.Default.Codec(), currentObj) + current, err := runtime.Encode(unstructured.JSONFallbackEncoder{Encoder: testapi.Default.Codec()}, currentObj) if err != nil { t.Fatal(err) } @@ -971,7 +971,7 @@ func TestUnstructuredIdempotentApply(t *testing.T) { initTestErrorHandler(t) serversideObject := readUnstructuredFromFile(t, filenameWidgetServerside) - serversideData, err := runtime.Encode(testapi.Default.Codec(), serversideObject) + serversideData, err := runtime.Encode(unstructured.JSONFallbackEncoder{Encoder: testapi.Default.Codec()}, serversideObject) if err != nil { t.Fatal(err) } diff --git a/pkg/kubectl/cmd/util/factory_client_access.go b/pkg/kubectl/cmd/util/factory_client_access.go index e44bfc71b3e..cac3efa453c 100644 --- a/pkg/kubectl/cmd/util/factory_client_access.go +++ b/pkg/kubectl/cmd/util/factory_client_access.go @@ -45,6 +45,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" utilflag "k8s.io/apiserver/pkg/util/flag" @@ -707,5 +708,6 @@ func InternalVersionDecoder() runtime.Decoder { } func InternalVersionJSONEncoder() runtime.Encoder { - return legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...) + encoder := legacyscheme.Codecs.LegacyCodec(legacyscheme.Registry.EnabledVersions()...) + return unstructured.JSONFallbackEncoder{Encoder: encoder} } diff --git a/pkg/kubectl/scheme/BUILD b/pkg/kubectl/scheme/BUILD index cdd9333404b..c22cb880898 100644 --- a/pkg/kubectl/scheme/BUILD +++ b/pkg/kubectl/scheme/BUILD @@ -39,6 +39,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apimachinery/registered: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/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", diff --git a/pkg/kubectl/scheme/scheme.go b/pkg/kubectl/scheme/scheme.go index d2e7ca978cb..1140f609045 100644 --- a/pkg/kubectl/scheme/scheme.go +++ b/pkg/kubectl/scheme/scheme.go @@ -21,6 +21,7 @@ import ( "k8s.io/apimachinery/pkg/apimachinery/announced" "k8s.io/apimachinery/pkg/apimachinery/registered" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" @@ -50,5 +51,5 @@ var Versions = []schema.GroupVersion{} // DefaultJSONEncoder returns a default encoder for our scheme func DefaultJSONEncoder() runtime.Encoder { - return Codecs.LegacyCodec(Versions...) + return unstructured.JSONFallbackEncoder{Encoder: Codecs.LegacyCodec(Versions...)} } diff --git a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers.go b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers.go index bbaef03c171..60f0573623a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers.go +++ b/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers.go @@ -436,6 +436,21 @@ func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList return nil } +type JSONFallbackEncoder struct { + runtime.Encoder +} + +func (c JSONFallbackEncoder) Encode(obj runtime.Object, w io.Writer) error { + err := c.Encoder.Encode(obj, w) + if runtime.IsNotRegisteredError(err) { + switch obj.(type) { + case *Unstructured, *UnstructuredList: + return UnstructuredJSONScheme.Encode(obj, w) + } + } + return err +} + // UnstructuredObjectConverter is an ObjectConverter for use with // Unstructured objects. Since it has no schema or type information, // it will only succeed for no-op conversions. This is provided as a diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/error.go b/staging/src/k8s.io/apimachinery/pkg/runtime/error.go index 86b24840f0a..77879660213 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/error.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/error.go @@ -41,10 +41,18 @@ func NewNotRegisteredErrForTarget(t reflect.Type, target GroupVersioner) error { return ¬RegisteredErr{t: t, target: target} } +func NewNotRegisteredGVKErrForTarget(gvk schema.GroupVersionKind, target GroupVersioner) error { + return ¬RegisteredErr{gvk: gvk, target: target} +} + func (k *notRegisteredErr) Error() string { if k.t != nil && k.target != nil { return fmt.Sprintf("%v is not suitable for converting to %q", k.t, k.target) } + nullGVK := schema.GroupVersionKind{} + if k.gvk != nullGVK && k.target != nil { + return fmt.Sprintf("%q is not suitable for converting to %q", k.gvk.GroupVersion(), k.target) + } if k.t != nil { return fmt.Sprintf("no kind is registered for the type %v", k.t) } diff --git a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning.go b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning.go index b717fe8fe6c..48df6b5dd18 100644 --- a/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning.go +++ b/staging/src/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning.go @@ -166,9 +166,22 @@ func (c *codec) Decode(data []byte, defaultGVK *schema.GroupVersionKind, into ru // Encode ensures the provided object is output in the appropriate group and version, invoking // conversion if necessary. Unversioned objects (according to the ObjectTyper) are output as is. func (c *codec) Encode(obj runtime.Object, w io.Writer) error { - switch obj.(type) { - case *runtime.Unknown, runtime.Unstructured: + switch obj := obj.(type) { + case *runtime.Unknown: return c.encoder.Encode(obj, w) + case runtime.Unstructured: + // avoid conversion roundtrip if GVK is the right one already or is empty (yes, this is a hack, but the old behaviour we rely on in kubectl) + objGVK := obj.GetObjectKind().GroupVersionKind() + if len(objGVK.Version) == 0 { + return c.encoder.Encode(obj, w) + } + targetGVK, ok := c.encodeVersion.KindForGroupVersionKinds([]schema.GroupVersionKind{objGVK}) + if !ok { + return runtime.NewNotRegisteredGVKErrForTarget(objGVK, c.encodeVersion) + } + if targetGVK == objGVK { + return c.encoder.Encode(obj, w) + } } gvks, isUnversioned, err := c.typer.ObjectKinds(obj)