Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename RawJSON to Raw in runtime.RawExtension and add ContentType & C… #23112

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions pkg/api/meta/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ func ExtractList(obj runtime.Object) ([]runtime.Object, error) {
switch {
case item.Object != nil:
list[i] = item.Object
case item.RawJSON != nil:
// TODO: Set ContentEncoding and ContentType.
list[i] = &runtime.Unknown{Raw: item.RawJSON}
case item.Raw != nil:
// TODO: Set ContentEncoding and ContentType correctly.
list[i] = &runtime.Unknown{Raw: item.Raw}
default:
list[i] = nil
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/api/meta/help_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ func TestExtractListGeneric(t *testing.T) {
func TestExtractListGenericV1(t *testing.T) {
pl := &v1.List{
Items: []runtime.RawExtension{
{RawJSON: []byte("foo")},
{RawJSON: []byte("bar")},
{Raw: []byte("foo")},
{Raw: []byte("bar")},
{Object: &v1.Pod{ObjectMeta: v1.ObjectMeta{Name: "other"}}},
},
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/api/rest/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ type Storage interface {

// KindProvider specifies a different kind for its API than for its internal storage. This is necessary for external
// objects that are not compiled into the api server. For such objects, there is no in-memory representation for
// the object, so they must be represented as generic objects (e.g. RawJSON), but when we present the object as part of
// the object, so they must be represented as generic objects (e.g. runtime.Unknown), but when we present the object as part of
// API discovery we want to present the specific kind, not the generic internal representation.
type KindProvider interface {
Kind() string
Expand Down
9 changes: 5 additions & 4 deletions pkg/kubectl/resource/visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,14 +474,15 @@ func (v *StreamVisitor) Visit(fn VisitorFunc) error {
}
return err
}
ext.RawJSON = bytes.TrimSpace(ext.RawJSON)
if len(ext.RawJSON) == 0 || bytes.Equal(ext.RawJSON, []byte("null")) {
// TODO: This needs to be able to handle object in other encodings and schemas.
ext.Raw = bytes.TrimSpace(ext.Raw)
if len(ext.Raw) == 0 || bytes.Equal(ext.Raw, []byte("null")) {
continue
}
if err := ValidateSchema(ext.RawJSON, v.Schema); err != nil {
if err := ValidateSchema(ext.Raw, v.Schema); err != nil {
return fmt.Errorf("error validating %q: %v", v.Source, err)
}
info, err := v.InfoForData(ext.RawJSON, v.Source)
info, err := v.InfoForData(ext.Raw, v.Source)
if err != nil {
if fnErr := fn(info, err); fnErr != nil {
return fnErr
Expand Down
4 changes: 2 additions & 2 deletions pkg/kubectl/resource_printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,10 +299,10 @@ func TestNamePrinter(t *testing.T) {
},
Items: []runtime.RawExtension{
{
RawJSON: []byte(`{"kind": "Pod", "apiVersion": "v1", "metadata": { "name": "foo"}}`),
Raw: []byte(`{"kind": "Pod", "apiVersion": "v1", "metadata": { "name": "foo"}}`),
},
{
RawJSON: []byte(`{"kind": "Pod", "apiVersion": "v1", "metadata": { "name": "bar"}}`),
Raw: []byte(`{"kind": "Pod", "apiVersion": "v1", "metadata": { "name": "bar"}}`),
},
},
},
Expand Down
24 changes: 12 additions & 12 deletions pkg/kubectl/sorting_printer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,16 +190,16 @@ func TestSortingPrinter(t *testing.T) {
name: "v1.List in order",
obj: &api.List{
Items: []runtime.RawExtension{
{RawJSON: encodeOrDie(a)},
{RawJSON: encodeOrDie(b)},
{RawJSON: encodeOrDie(c)},
{Raw: encodeOrDie(a)},
{Raw: encodeOrDie(b)},
{Raw: encodeOrDie(c)},
},
},
sort: &api.List{
Items: []runtime.RawExtension{
{RawJSON: encodeOrDie(a)},
{RawJSON: encodeOrDie(b)},
{RawJSON: encodeOrDie(c)},
{Raw: encodeOrDie(a)},
{Raw: encodeOrDie(b)},
{Raw: encodeOrDie(c)},
},
},
field: "{.metadata.name}",
Expand All @@ -208,16 +208,16 @@ func TestSortingPrinter(t *testing.T) {
name: "v1.List in reverse",
obj: &api.List{
Items: []runtime.RawExtension{
{RawJSON: encodeOrDie(c)},
{RawJSON: encodeOrDie(b)},
{RawJSON: encodeOrDie(a)},
{Raw: encodeOrDie(c)},
{Raw: encodeOrDie(b)},
{Raw: encodeOrDie(a)},
},
},
sort: &api.List{
Items: []runtime.RawExtension{
{RawJSON: encodeOrDie(a)},
{RawJSON: encodeOrDie(b)},
{RawJSON: encodeOrDie(c)},
{Raw: encodeOrDie(a)},
{Raw: encodeOrDie(b)},
{Raw: encodeOrDie(c)},
},
},
field: "{.metadata.name}",
Expand Down
6 changes: 3 additions & 3 deletions pkg/runtime/deep_copy_generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ import (
)

func DeepCopy_runtime_RawExtension(in RawExtension, out *RawExtension, c *conversion.Cloner) error {
if in.RawJSON != nil {
in, out := in.RawJSON, &out.RawJSON
if in.Raw != nil {
in, out := in.Raw, &out.Raw
*out = make([]byte, len(in))
copy(*out, in)
} else {
out.RawJSON = nil
out.Raw = nil
}
if in.Object == nil {
out.Object = nil
Expand Down
8 changes: 4 additions & 4 deletions pkg/runtime/embedded.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,19 +92,19 @@ func DefaultEmbeddedConversions() []interface{} {
return []interface{}{
func(in *Object, out *RawExtension, s conversion.Scope) error {
if in == nil {
out.RawJSON = []byte("null")
out.Raw = []byte("null")
return nil
}
obj := *in
if unk, ok := obj.(*Unknown); ok {
if unk.Raw != nil {
out.RawJSON = unk.Raw
out.Raw = unk.Raw
return nil
}
obj = out.Object
}
if obj == nil {
out.RawJSON = nil
out.Raw = nil
return nil
}
out.Object = obj
Expand All @@ -116,7 +116,7 @@ func DefaultEmbeddedConversions() []interface{} {
*out = in.Object
return nil
}
data := in.RawJSON
data := in.Raw
if len(data) == 0 || (len(data) == 4 && string(data) == "null") {
*out = nil
return nil
Expand Down
4 changes: 2 additions & 2 deletions pkg/runtime/embedded_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func TestArrayOfRuntimeObject(t *testing.T) {
if err := json.Unmarshal(wire, obj); err != nil {
t.Fatalf("unexpected error: %v", err)
}
t.Logf("exact wire is: %s", string(obj.Items[0].RawJSON))
t.Logf("exact wire is: %s", string(obj.Items[0].Raw))

items[3] = &ObjectTest{Items: innerItems}
internal.Items = items
Expand Down Expand Up @@ -230,7 +230,7 @@ func TestNestedObject(t *testing.T) {
if externalViaJSON.Kind == "" || externalViaJSON.APIVersion == "" || externalViaJSON.ID != "outer" {
t.Errorf("Expected objects to have type info set, got %#v", externalViaJSON)
}
if !reflect.DeepEqual(externalViaJSON.EmptyObject.RawJSON, []byte("null")) || len(externalViaJSON.Object.RawJSON) == 0 {
if !reflect.DeepEqual(externalViaJSON.EmptyObject.Raw, []byte("null")) || len(externalViaJSON.Object.Raw) == 0 {
t.Errorf("Expected deserialization of nested objects into bytes, got %#v", externalViaJSON)
}

Expand Down
7 changes: 4 additions & 3 deletions pkg/runtime/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ func (re *RawExtension) UnmarshalJSON(in []byte) error {
if re == nil {
return errors.New("runtime.RawExtension: UnmarshalJSON on nil pointer")
}
re.RawJSON = append(re.RawJSON[0:0], in...)
re.Raw = append(re.Raw[0:0], in...)
return nil
}

// Marshal may get called on pointers or values, so implement MarshalJSON on value.
// http://stackoverflow.com/questions/21390979/custom-marshaljson-never-gets-called-in-go
func (re RawExtension) MarshalJSON() ([]byte, error) {
if re.RawJSON == nil {
if re.Raw == nil {
// TODO: this is to support legacy behavior of JSONPrinter and YAMLPrinter, which
// expect to call json.Marshal on arbitrary versioned objects (even those not in
// the scheme). pkg/kubectl/resource#AsVersionedObjects and its interaction with
Expand All @@ -43,5 +43,6 @@ func (re RawExtension) MarshalJSON() ([]byte, error) {
}
return []byte("null"), nil
}
return re.RawJSON, nil
// TODO: Check whether ContentType is actually JSON before returning it.
return re.Raw, nil
}
2 changes: 1 addition & 1 deletion pkg/runtime/extension_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestEmbeddedRawExtensionMarshal(t *testing.T) {
Ext runtime.RawExtension
}

extension := test{Ext: runtime.RawExtension{RawJSON: []byte(`{"foo":"bar"}`)}}
extension := test{Ext: runtime.RawExtension{Raw: []byte(`{"foo":"bar"}`)}}
data, err := json.Marshal(extension)
if err != nil {
t.Fatalf("unexpected error: %v", err)
Expand Down
15 changes: 11 additions & 4 deletions pkg/runtime/protobuf/protobuf.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ func (c codec) Decode(data []byte) (runtime.Object, error) {
if !ok {
return nil, fmt.Errorf("runtime object is not a proto.Message: %v", reflect.TypeOf(obj))
}
if err := proto.Unmarshal(unknown.RawJSON, pobj); err != nil {
if unknown.ContentType != runtime.ContentTypeProtobuf {
return nil, fmt.Errorf("unmarshal non-protobuf object with protobuf decoder")
}
if err := proto.Unmarshal(unknown.Raw, pobj); err != nil {
return nil, err
}
if unknown.APIVersion != c.outputVersion {
Expand All @@ -90,13 +93,16 @@ func (c codec) DecodeInto(data []byte, obj runtime.Object) error {
if err := proto.Unmarshal(data, unknown); err != nil {
return err
}
if unknown.ContentType != runtime.ContentTypeProtobuf {
return nil, fmt.Errorf("unmarshal non-protobuf object with protobuf decoder")
}
if unknown.APIVersion == version && unknown.Kind == kind {
pobj, ok := obj.(proto.Message)
if !ok {
return fmt.Errorf("runtime object is not a proto.Message: %v", reflect.TypeOf(obj))
}

return proto.Unmarshal(unknown.RawJSON, pobj)
return proto.Unmarshal(unknown.Raw, pobj)
}

versioned, err := c.creater.New(unknown.APIVersion, unknown.Kind)
Expand All @@ -109,7 +115,7 @@ func (c codec) DecodeInto(data []byte, obj runtime.Object) error {
return fmt.Errorf("runtime object is not a proto.Message: %v", reflect.TypeOf(obj))
}

if err := proto.Unmarshal(unknown.RawJSON, pobj); err != nil {
if err := proto.Unmarshal(unknown.Raw, pobj); err != nil {
return err
}
return c.convertor.Convert(versioned, obj)
Expand Down Expand Up @@ -149,7 +155,8 @@ func (c codec) Encode(obj runtime.Object) (data []byte, err error) {
Kind: kind,
APIVersion: version,
},
RawJSON: b,
Raw: b,
ContentType: runtime.ContentTypeProtobuf,
}).Marshal()
}

Expand Down
20 changes: 12 additions & 8 deletions pkg/runtime/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ type TypeMeta struct {
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
}

const (
ContentTypeJSON string = "application/json"
// TODO: Fix the value.
ContentTypeProtobuf string = "application/protobuf"
)

// RawExtension is used to hold extensions in external versions.
//
// To use this, make a field which has RawExtension as its type in your external, versioned
Expand Down Expand Up @@ -80,17 +86,15 @@ type TypeMeta struct {
//
// +protobuf=true
type RawExtension struct {
// RawJSON is the underlying serialization of this object.
RawJSON []byte
// Raw is the underlying serialization of this object.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So adding ContentEncoding / Type is interesting - when this object is included in an external type for JSON, it's assumed to be JSON (and there is no way to set encoding or content type). For Protobuf, it will be slightly different (the nested object would be a runtime.Unknown, but ContentEncoding and ContentType would be inside that object, not this one). For serializing nested objects (like List) we've said that that has to be something that serializer knows how to do, so the serializer is responsible for transforming Object -> Raw and vice versa.

Do we need to add them here? Maybe I didn't catch why they were required.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So maybe I don't fully understand it.
My rationale for having it is that it seemed to be that in some cases there wasn't any way to infer that information if we don't have them in RawExtension - as examples see above:

  • pkg/api/meta/help.go
  • pkg/runtime/embedded.go (conversions between RawExtension and Object)

But maybe I'm missing how RawExtensions generally works...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're trying to remove the conversion logic in pkg/runtime/embedded.go (conversions doesn't know enough about the context that a serialization is happening in to properly convert arbitrary objects to bytes). In OpenShift we have more cases where we use embedded RawExtensions, so we hit the use cases more. @deads2k and I ended up having an extension to the serializer that gave objects the chance to encode their nested objects with a copy of the serializer, prior to actually writing out the actual object. In that case, the encoding and type would be handled by the serializer code.

Example - we have a List object that has runtime.Objects. Those objects don't have to be in the same API group, or be targeted to the same API version. When we read the List, we don't decode them immediately (we just move them to unknown). The serializer that reads those knows what content type is the default - conversion does not. Likewise, when we write those to protobuf, the protobuf serializer knows that the destination content type is protobuf, not conversion. So the hooks in the serializer have to go and give each object a chance to serialize using protobuf.

If we get rid of internal versions, we'd need a way to pass around objects that have been decoded (that's what Object is for). I'm not sure that we'd need content encoding or type.

I think line 102 of embedded up above is technically wrong (where we convert unknown to raw extension), because we don't want conversion doing encoding. The problem is we need to formalize how we do encoding of nested objects, probably as part of this chain of work. Since Unknown is a wrapper, rather than an object on its own, we should probably do nothing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for detailed explanation - that helps a lot!

So I guess, we should remove ContentEncoding and ContentType from here, right?
Will do that tomorrow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, let's remove and add a TODO: here and a follow up issue to determine
what the solution is. I'll open a PR on the "let callers encode themselves
from serializers" side.

On Thu, Mar 17, 2016 at 4:43 PM, Wojciech Tyczynski <
notifications@github.com> wrote:

In pkg/runtime/types.go
#23112 (comment)
:

@@ -80,17 +86,19 @@ type TypeMeta struct {
//
// +protobuf=true
type RawExtension struct {

  • // RawJSON is the underlying serialization of this object.
  • RawJSON []byte
  • // Raw is the underlying serialization of this object.

Thanks for detailed explanation - that helps a lot!

So I guess, we should remove ContentEncoding and ContentType from here,
right?
Will do that tomorrow.


You are receiving this because you were assigned.
Reply to this email directly or view it on GitHub
https://github.com/kubernetes/kubernetes/pull/23112/files/edfb4acc188fbb26529993e02d82fd72faa247d7#r56574323

//
// TODO: Determine how to detect ContentType and ContentEncoding of 'Raw' data.
Raw []byte
// Object can hold a representation of this extension - useful for working with versioned
// structs.
Object Object `json:"-"`
}

// TODO; Where should it be?
// TODO; What value it should have?
const ContentTypeJSON string = "application/json"

// Unknown allows api objects with unknown types to be passed-through. This can be used
// to deal with the API objects from a plug-in. Unknown objects still have functioning
// TypeMeta features-- kind, version, etc.
Expand All @@ -105,10 +109,10 @@ type Unknown struct {
// except for passing it through the system.
Raw []byte
// ContentEncoding is encoding used to encode 'Raw' data.
// Unspecified mean no encoding.
// Unspecified means no encoding.
ContentEncoding string
// ContentType is serialization method used to serialize 'Raw'.
// TODO: Define what unspecified means.
// Unspecified means ContentTypeJSON.
ContentType string
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/watch/json/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (d *Decoder) Decode() (watch.EventType, runtime.Object, error) {
return "", nil, fmt.Errorf("got invalid watch event type: %v", got.Type)
}

obj, err := runtime.Decode(d.codec, got.Object.RawJSON)
obj, err := runtime.Decode(d.codec, got.Object.Raw)
if err != nil {
return "", nil, fmt.Errorf("unable to decode watch event: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/watch/json/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestDecoder(t *testing.T) {
if err != nil {
t.Fatalf("Unexpected error %v", err)
}
if err := encoder.Encode(&WatchEvent{eventType, runtime.RawExtension{RawJSON: json.RawMessage(data)}}); err != nil {
if err := encoder.Encode(&WatchEvent{eventType, runtime.RawExtension{Raw: json.RawMessage(data)}}); err != nil {
t.Errorf("Unexpected error %v", err)
}
in.Close()
Expand Down
2 changes: 1 addition & 1 deletion pkg/watch/json/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,5 @@ func Object(encoder runtime.Encoder, event *watch.Event) (interface{}, error) {
if err != nil {
return nil, err
}
return &WatchEvent{event.Type, runtime.RawExtension{RawJSON: json.RawMessage(data)}}, nil
return &WatchEvent{event.Type, runtime.RawExtension{Raw: json.RawMessage(data)}}, nil
}