@@ -99,6 +99,14 @@ tags, and then generate with `hack/update-toc.sh`.
99
99
- [ Encoding Determinism] ( #encoding-determinism )
100
100
- [ Unicode] ( #unicode )
101
101
- [ Libraries] ( #libraries )
102
+ - [ RawExtension] ( #rawextension )
103
+ - [ Usage] ( #usage )
104
+ - [ Transient External Types] ( #transient-external-types )
105
+ - [ Stored External Types] ( #stored-external-types )
106
+ - [ Types as Canonical Definition of Custom Resources] ( #types-as-canonical-definition-of-custom-resources )
107
+ - [ Scenarios] ( #scenarios )
108
+ - [ Compatibility] ( #compatibility )
109
+ - [ Migration] ( #migration )
102
110
- [ Test Plan] ( #test-plan )
103
111
- [ Prerequisite testing updates] ( #prerequisite-testing-updates )
104
112
- [ Unit tests] ( #unit-tests )
@@ -614,7 +622,204 @@ sequence that it will not successfully decode.
614
622
615
623
[ Benchmarks] ( https://docs.google.com/spreadsheets/d/1yi8cHrnlbmCUY2Vo7Sknrf87WDOuGUswYsyqJfEUwls/edit#gid=0 ) TODO: inline
616
624
625
+ ### RawExtension
617
626
627
+ The ` RawExtension ` type in ` k8s.io/apimachinery/pkg/runtime ` allows extension types to be handled
628
+ opaquely within external versioned types, as long as they are syntactically valid.
629
+
630
+ The [ type
631
+ declaration] ( https://github.com/kubernetes/kubernetes/blob/169a952720ebd75fcbcb4f3f5cc64e82fdd3ec45/staging/src/k8s.io/apimachinery/pkg/runtime/types.go#L51-L109 )
632
+ is:
633
+
634
+ ``` go
635
+ type RawExtension struct {
636
+ Raw []byte
637
+ Object Object
638
+ }
639
+ ```
640
+
641
+ Using JSON, marshalling and unmarshalling of ` RawExtension ` is comparable to that of the standard
642
+ library's [ RawMessage] ( https://pkg.go.dev/encoding/json#RawMessage ) type. For unmarshalling, if the
643
+ input serialized JSON value is ` null ` , the destination ` RawExtension ` is not modified. Otherwise,
644
+ its ` Raw ` field is set to a verbatim copy of the provided serialized JSON value. The contract of
645
+ json.Unmarshaler states that implementations can assume that the input is valid encoding of a JSON
646
+ value. Absent a bug in the caller (typically via ` json.Marshal ` or ` (*json.Decoder).Decode ` ), a
647
+ ` RawExtension ` 's Raw field will contain a valid JSON text after unmarshaling.
648
+
649
+ In general, for an encoding that supports Unstructured, the encoding of a RawExtension value must
650
+ always be the same as the overall encoding of the request or response body. This is not the case for
651
+ Protobuf. Protobuf can encode RawExtension fields with any encoding since both the writer and reader
652
+ of a Protobuf message have the type information to know that they are serializing or deserializing a
653
+ RawExtension message.
654
+
655
+ There are three cases when marshalling ` RawExtension ` to JSON:
656
+
657
+ 1 . If both Raw and Object are ` nil ` , ` null ` is returned.
658
+ 1 . If Raw is not ` nil ` , return it verbatim.
659
+ 1 . Otherwise (Raw is ` nil ` and Object is not ` nil ` ), return the result of marshalling Object.
660
+
661
+ Note that, in the second case, the bytes of the Raw field must be a valid JSON text in order to
662
+ successfully serialize an object containing a ` RawExtension ` to JSON.
663
+
664
+ #### Usage
665
+
666
+ ##### Transient External Types
667
+
668
+ External versioned types may use ` RawExtension ` to exchange arbitrary objects and plugins without
669
+ persisting them to storage. In these cases, only a single object encoding is involved. When
670
+ preparing to send, or handle a received object containing ` RawExtension ` , callers can assume that
671
+ the Raw bytes are in the same encoding as the negotiated request or response encoding.
672
+
673
+ ##### Stored External Types
674
+
675
+ Storing the verbatim Raw bytes of a ` RawExtension ` received from a client introduces additional
676
+ considerations on top of the transient (transmit-only) case. The encoding of the Raw bytes is
677
+ determined by encoding of the request that wrote the value of the RawExtension, which may or may not
678
+ be the same as the object's storage encoding.
679
+
680
+ ##### Types as Canonical Definition of Custom Resources
681
+
682
+ Throughout the ecosystem, it is common practice to maintain Go structs as the canonical definition
683
+ for API extensions. In many cases, ` controller-gen ` is used to mechanically translate such types
684
+ from Go sources to CustomResourceDefinition manifests. Similarly, ` client-gen ` can produce typed Go
685
+ clients that use the canonical Go types directly. These Go struct types can and sometimes do include
686
+ fields of type ` RawExtension `
687
+ ([ example] ( https://github.com/openshift/api/blob/944467d2cc3b03225ccc24c4e88b876396202d5a/operator/v1/types.go#L91 ) ).
688
+
689
+ #### Scenarios
690
+
691
+ The following tables enumerate API request and response flows that can involve ` RawExtension ` .
692
+
693
+ The * Client* and * Server* columns indicate the types the named component uses to processes API
694
+ objects. If "dynamic", it uses Unstructured (e.g. a custom resource handler or a dynamic client). If
695
+ "typed", it uses API-specific Go types that may include ` RawExtension ` (e.g. clients generated by
696
+ ` client-gen ` , kube-apiserver built-in types, aggregated apiservers). The table omits cases where
697
+ both the client and the server are dynamic (e.g. a dynamic client and a custom resource handler),
698
+ since neither side should be dealing with ` RawExtension ` values. The edge case where a client
699
+ program makes a ` RawExtension ` a child of an Unstructured value's ` map[string]interface{} ` can be
700
+ considered a static client case for the purposes of this evaluation.
701
+
702
+ The * Encoding* column is the client's encoding of the request body (for requests) or the server's
703
+ encoding of the response body (for responses).
704
+
705
+ ** Marshalled Unstructured**
706
+
707
+ | N | Client | Server | Direction | Encoding |
708
+ | ---| ---------| ---------| -----------| ----------|
709
+ | 1 | dynamic | typed | request | json |
710
+ | 2 | dynamic | typed | request | cbor |
711
+ | 3 | typed | dynamic | response | json |
712
+ | 4 | typed | dynamic | response | cbor |
713
+
714
+ In these cases, the marshalling side acts on an Unstructured object and is not aware that the
715
+ unmarshalling side may decode some of the payload into a ` RawExtension ` . The bytes stored in the
716
+ ` RawExtension ` by unmarshalling ultimately * depend on the negotiated content type, which can vary*
717
+ with the enablement of the CBOR serializer. Existing programs have so far been able to assume that
718
+ unmarshalled RawExtensions always have either nil or a valid JSON text in their Raw field.
719
+
720
+ ** Marshalled RawExtension**
721
+
722
+ | N | Client | Server | Direction | Encoding |
723
+ | ----| ---------| ---------| -----------| ----------|
724
+ | 1 | typed | typed | request | json |
725
+ | 2 | typed | typed | request | cbor |
726
+ | 3 | typed | typed | response | json |
727
+ | 4 | typed | typed | response | cbor |
728
+ | 5 | dynamic | typed | response | json |
729
+ | 6 | dynamic | typed | response | cbor |
730
+ | 7 | typed | dynamic | request | json |
731
+ | 8 | typed | dynamic | request | cbor |
732
+ | 9 | typed | typed | request | protobuf |
733
+ | 10 | typed | typed | response | protobuf |
734
+
735
+ In these cases, if the marshalling side populates Raw with a non-nil slice, it is responsible for
736
+ ensuring that that encoding of the slice contents matches the encoding that will be used to
737
+ serialize the object containing the ` RawExtension ` . This is trivially ensured in cases 9 and 10
738
+ because Protobuf is capable of representing ` RawExtension ` values containing arbitrary
739
+ bytes. Protobuf is not a supported encoding for Unstructured objects. Existing programs have in
740
+ practice stored JSON in the Raw field of ` RawExtension ` .
741
+
742
+ #### Compatibility
743
+
744
+ If the ` RawExtension ` marshalling and unmarshalling behavior for CBOR were to be implemented in
745
+ exactly the same way as the existing JSON behaviors, the assumptions in many existing programs that
746
+ the Raw field can be assigned to a slice of JSON bytes, or that the Raw bytes of an unmarshalled
747
+ ` RawExtension ` are valid JSON, would be broken.
748
+
749
+ The simple approach of automatically transcoding JSON to CBOR during CBOR marshalling, and
750
+ transcoding CBOR to JSON during CBOR unmarshalling, would avoid breaking existing programs. However,
751
+ the expense of transcoding to or from JSON would negate any performance advantage of a binary
752
+ encoding. This expense would not be limited to a few API types: significant examples include the use
753
+ of a ` RawExtension ` field in ` metav1.WatchEvent ` to represent each watch event's object state, or
754
+ the arbitrary objects embedded in ` admissionv1.AdmissionRequest ` .
755
+
756
+ A new ` ContentType string ` field will be added to ` RawExtension ` to indicate the IANA media type of
757
+ the Raw bytes. If empty, the assumed content type is "application/json". In existing usage, if a
758
+ RawExtension's Raw field does not contain valid JSON, the RawExtension itself cannot be marshalled
759
+ to JSON.
760
+
761
+ ContentType will not be serialized to JSON or CBOR, but it will be serialized to Protobuf. When
762
+ unmarshalling either JSON or CBOR into a RawExtension, the content type is implicitly the same as
763
+ that of the input. This is not true for Protobuf, which is capable of embedding RawExtensions using
764
+ any encoding, since in all cases both the writer and reader of a Protobuf message are aware that
765
+ they are handling an extension.
766
+
767
+ The proposed behavior for both MarshalJSON and MarshalCBOR is:
768
+
769
+ 1 . If both Raw and Object are ` nil ` , ` null ` is returned.
770
+ 1 . If Object is not ` nil ` , return the result of marshalling Object to the target encoding.
771
+ 1 . If the ContentType matches the media type of the target encoding (or if ContentType is the empty
772
+ string and the target encoding is JSON), return the Raw bytes verbatim.
773
+ 1 . Otherwise, return the result of transcoding the Raw bytes from the encoding indicated by
774
+ ContentType to the target encoding.
775
+
776
+ Unmarshalling will behave the same for CBOR as it currently does for JSON and the input bytes will
777
+ be copied verbatim to the Raw field. The ContentType will be set to "application/json" by a
778
+ successful call to UnmarshalJSON and to "application/cbor" by a successful call to UnmarshalCBOR.
779
+
780
+ Additionally, by default, the Raw bytes of a decoded ` RawExtension ` will be automatically transcoded
781
+ to JSON to preserve compatibility with programs that assume an unmarshalled RawExtension contains
782
+ valid JSON. The CBOR serializer available through ` serializer.CodecFactory ` will be wired to use
783
+ this, allowing existing programs to continue to assume that unmarshalled Raw bytes contain JSON. The
784
+ stream serializer will not. In practice, the watch decoder assumes that the non-stream serializer
785
+ can directly decode the Raw bytes of a ` metav1.WatchEvent ` decoded by the stream serializer.
786
+
787
+ There will be a migration period during which it will remain possible to disable automatic
788
+ transcoding of RawExtension via feature gate.
789
+
790
+ ##### Migration
791
+
792
+ ** GA**
793
+
794
+ * Naive Clients*
795
+
796
+ 1 . Client assumes received RawExtension is JSON.
797
+ 1 . Client receives CBOR response body. The response bytes that represent the RawExtension are CBOR.
798
+ 1 . During decoding, the RawExtension's Raw field is transcoded from CBOR to JSON.
799
+ 1 . Client continues processing RawExtension bytes as JSON.
800
+
801
+ * Advanced Clients*
802
+
803
+ 1 . Client tolerates RawExtensions containing either JSON or CBOR.
804
+ 1 . Client receives CBOR response body. The response bytes that represent the RawExtension are CBOR.
805
+ 1 . No transcoding is performed during decoding.
806
+ 1 . Client detects the format of the RawExtension bytes and processes it accordingly. RawExtension
807
+ will implement UnstructuredConverter, providing a one-liner to get an Unstructured from a
808
+ RawExtension.
809
+
810
+ ** Post-GA, CBOR as Default Preferred Request/Response Encoding for One Year**
811
+
812
+ Automatic transcoding client feature gate becomes disabled by default. The feature gate is unlocked
813
+ and transcoding can be re-enabled without code changes using the existing client feature gate
814
+ environment variable mechanism.
815
+
816
+ ** Post-GA, CBOR as Default Preferred Request/Response Encoding for Two Years**
817
+
818
+ Automatic transcoding client feature gate is removed and requires code changes to enable.
819
+
820
+ All existent clusters will support CBOR. Existing programs continue to work unmodified. Updating
821
+ client libraries in existing programs may cause them to break if they have not changed how they are
822
+ handling RawExtensions.
618
823
619
824
### Test Plan
620
825
0 commit comments