Add protobuf preparation objects, guarded by proto tag

This commit is contained in:
Clayton Coleman 2015-11-26 18:45:43 -05:00
parent e2679abdb8
commit 9fea762917
13 changed files with 594 additions and 48 deletions

View File

@ -83,6 +83,11 @@ import (
// This format is intended to make it difficult to use these numbers without
// writing some sort of special handling code in the hopes that that will
// cause implementors to also use a fixed point implementation.
//
// +protobuf=true
// +protobuf.embed=QuantityProto
// +protobuf.options.marshal=false
// +protobuf.options.(gogoproto.goproto_stringer)=false
type Quantity struct {
// Amount is public, so you can manipulate it if the accessor
// functions are not sufficient.

View File

@ -0,0 +1,80 @@
// +build proto
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resource
import (
"math/big"
"speter.net/go/exp/math/dec/inf"
)
// QuantityProto is a struct that is equivalent to Quantity, but intended for
// protobuf marshalling/unmarshalling. It is generated into a serialization
// that matches Quantity. Do not use in Go structs.
//
// +protobuf=true
type QuantityProto struct {
// The format of the quantity
Format Format
// The scale dimension of the value
Scale int32
// Bigint is serialized as a raw bytes array
Bigint []byte
}
// ProtoTime returns the Time as a new ProtoTime value.
func (q *Quantity) QuantityProto() *QuantityProto {
if q == nil {
return &QuantityProto{}
}
p := &QuantityProto{
Format: q.Format,
}
if q.Amount != nil {
p.Scale = int32(q.Amount.Scale())
p.Bigint = q.Amount.UnscaledBig().Bytes()
}
return p
}
// Size implements the protobuf marshalling interface.
func (q *Quantity) Size() (n int) { return q.QuantityProto().Size() }
// Reset implements the protobuf marshalling interface.
func (q *Quantity) Unmarshal(data []byte) error {
p := QuantityProto{}
if err := p.Unmarshal(data); err != nil {
return err
}
q.Format = p.Format
b := big.NewInt(0)
b.SetBytes(p.Bigint)
q.Amount = inf.NewDecBig(b, inf.Scale(p.Scale))
return nil
}
// Marshal implements the protobuf marshalling interface.
func (q *Quantity) Marshal() (data []byte, err error) {
return q.QuantityProto().Marshal()
}
// MarshalTo implements the protobuf marshalling interface.
func (q *Quantity) MarshalTo(data []byte) (int, error) {
return q.QuantityProto().MarshalTo(data)
}

View File

@ -0,0 +1,95 @@
// +build proto
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package api_test
import (
"encoding/hex"
"math/rand"
"testing"
"github.com/gogo/protobuf/proto"
"k8s.io/kubernetes/pkg/api"
apitesting "k8s.io/kubernetes/pkg/api/testing"
"k8s.io/kubernetes/pkg/api/v1"
_ "k8s.io/kubernetes/pkg/apis/extensions"
_ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/runtime/protobuf"
"k8s.io/kubernetes/pkg/util"
)
func init() {
codecsToTest = append(codecsToTest, func(version string, item runtime.Object) (runtime.Codec, error) {
return protobuf.NewCodec(version, api.Scheme, api.Scheme, api.Scheme), nil
})
}
func TestProtobufRoundTrip(t *testing.T) {
obj := &v1.Pod{}
apitesting.FuzzerFor(t, "v1", rand.NewSource(benchmarkSeed)).Fuzz(obj)
data, err := obj.Marshal()
if err != nil {
t.Fatal(err)
}
out := &v1.Pod{}
if err := out.Unmarshal(data); err != nil {
t.Fatal(err)
}
if !api.Semantic.Equalities.DeepEqual(out, obj) {
t.Logf("marshal\n%s", hex.Dump(data))
t.Fatalf("Unmarshal is unequal\n%s", util.ObjectGoPrintSideBySide(out, obj))
}
}
func BenchmarkEncodeProtobufGeneratedMarshal(b *testing.B) {
items := benchmarkItems()
width := len(items)
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err := items[i%width].Marshal(); err != nil {
b.Fatal(err)
}
}
b.StopTimer()
}
// BenchmarkDecodeJSON provides a baseline for regular JSON decode performance
func BenchmarkDecodeIntoProtobuf(b *testing.B) {
items := benchmarkItems()
width := len(items)
encoded := make([][]byte, width)
for i := range items {
data, err := (&items[i]).Marshal()
if err != nil {
b.Fatal(err)
}
encoded[i] = data
validate := &v1.Pod{}
if err := proto.Unmarshal(data, validate); err != nil {
b.Fatalf("Failed to unmarshal %d: %v\n%#v", i, err, items[i])
}
}
for i := 0; i < b.N; i++ {
obj := v1.Pod{}
if err := proto.Unmarshal(encoded[i%width], &obj); err != nil {
b.Fatal(err)
}
}
}

View File

@ -18,29 +18,35 @@ package api_test
import (
"encoding/json"
"math/rand"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
flag "github.com/spf13/pflag"
"github.com/ugorji/go/codec"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/testapi"
apitesting "k8s.io/kubernetes/pkg/api/testing"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/v1"
_ "k8s.io/kubernetes/pkg/apis/extensions"
_ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util/sets"
_ "k8s.io/kubernetes/pkg/apis/extensions"
_ "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
flag "github.com/spf13/pflag"
)
var fuzzIters = flag.Int("fuzz-iters", 20, "How many fuzzing iterations to do.")
var codecsToTest = []func(version string, item runtime.Object) (runtime.Codec, error){
func(version string, item runtime.Object) (runtime.Codec, error) {
return testapi.GetCodecForObject(item)
},
}
func fuzzInternalObject(t *testing.T, forVersion string, item runtime.Object, seed int64) runtime.Object {
apitesting.FuzzerFor(t, forVersion, rand.NewSource(seed)).Fuzz(item)
@ -92,16 +98,22 @@ func roundTripSame(t *testing.T, item runtime.Object, except ...string) {
seed := rand.Int63()
fuzzInternalObject(t, "", item, seed)
codec, err := testapi.GetCodecForObject(item)
if err != nil {
t.Errorf("unexpected error: %v", err)
return
version := testapi.Default.VersionUnderTest
codecs := []runtime.Codec{}
for _, fn := range codecsToTest {
codec, err := fn(version, item)
if err != nil {
t.Errorf("unable to get codec: %v", err)
return
}
codecs = append(codecs, codec)
}
version := testapi.Default.Version()
if !set.Has(version) {
fuzzInternalObject(t, version, item, seed)
roundTrip(t, codec, item)
for _, codec := range codecs {
roundTrip(t, codec, item)
}
}
}
@ -163,7 +175,9 @@ func doRoundTripTest(kind string, t *testing.T) {
if _, err := meta.TypeAccessor(item); err != nil {
t.Fatalf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableTypes: %v", kind, err)
}
roundTripSame(t, item, nonRoundTrippableTypesByVersion[kind]...)
if api.Scheme.Recognizes(testapi.Default.VersionUnderTest, kind) {
roundTripSame(t, item, nonRoundTrippableTypesByVersion[kind]...)
}
if !nonInternalRoundTrippableTypes.Has(kind) {
roundTrip(t, api.Codec, fuzzInternalObject(t, "", item, rand.Int63()))
}
@ -247,54 +261,132 @@ func TestUnversionedTypes(t *testing.T) {
const benchmarkSeed = 100
func BenchmarkEncode(b *testing.B) {
pod := api.Pod{}
func benchmarkItems() []v1.Pod {
apiObjectFuzzer := apitesting.FuzzerFor(nil, "", rand.NewSource(benchmarkSeed))
apiObjectFuzzer.Fuzz(&pod)
for i := 0; i < b.N; i++ {
testapi.Default.Codec().Encode(&pod)
items := make([]v1.Pod, 2)
for i := range items {
apiObjectFuzzer.Fuzz(&items[i])
}
return items
}
// BenchmarkEncodeJSON provides a baseline for regular JSON encode performance
func BenchmarkEncodeJSON(b *testing.B) {
pod := api.Pod{}
apiObjectFuzzer := apitesting.FuzzerFor(nil, "", rand.NewSource(benchmarkSeed))
apiObjectFuzzer.Fuzz(&pod)
// BenchmarkEncodeCodec measures the cost of performing a codec encode, which includes
// reflection (to clear APIVersion and Kind)
func BenchmarkEncodeCodec(b *testing.B) {
items := benchmarkItems()
width := len(items)
b.ResetTimer()
for i := 0; i < b.N; i++ {
json.Marshal(&pod)
if _, err := testapi.Default.Codec().Encode(&items[i%width]); err != nil {
b.Fatal(err)
}
}
b.StopTimer()
}
func BenchmarkDecode(b *testing.B) {
pod := api.Pod{}
apiObjectFuzzer := apitesting.FuzzerFor(nil, "", rand.NewSource(benchmarkSeed))
apiObjectFuzzer.Fuzz(&pod)
data, _ := testapi.Default.Codec().Encode(&pod)
// BenchmarkEncodeJSONMarshal provides a baseline for regular JSON encode performance
func BenchmarkEncodeJSONMarshal(b *testing.B) {
items := benchmarkItems()
width := len(items)
b.ResetTimer()
for i := 0; i < b.N; i++ {
testapi.Default.Codec().Decode(data)
if _, err := json.Marshal(&items[i%width]); err != nil {
b.Fatal(err)
}
}
b.StopTimer()
}
func BenchmarkDecodeInto(b *testing.B) {
pod := api.Pod{}
apiObjectFuzzer := apitesting.FuzzerFor(nil, "", rand.NewSource(benchmarkSeed))
apiObjectFuzzer.Fuzz(&pod)
data, _ := testapi.Default.Codec().Encode(&pod)
for i := 0; i < b.N; i++ {
obj := api.Pod{}
testapi.Default.Codec().DecodeInto(data, &obj)
func BenchmarkDecodeCodec(b *testing.B) {
codec := testapi.Default.Codec()
items := benchmarkItems()
width := len(items)
encoded := make([][]byte, width)
for i := range items {
data, err := codec.Encode(&items[i])
if err != nil {
b.Fatal(err)
}
encoded[i] = data
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err := codec.Decode(encoded[i%width]); err != nil {
b.Fatal(err)
}
}
b.StopTimer()
}
func BenchmarkDecodeIntoCodec(b *testing.B) {
codec := testapi.Default.Codec()
items := benchmarkItems()
width := len(items)
encoded := make([][]byte, width)
for i := range items {
data, err := codec.Encode(&items[i])
if err != nil {
b.Fatal(err)
}
encoded[i] = data
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
obj := v1.Pod{}
if err := codec.DecodeInto(encoded[i%width], &obj); err != nil {
b.Fatal(err)
}
}
b.StopTimer()
}
// BenchmarkDecodeJSON provides a baseline for regular JSON decode performance
func BenchmarkDecodeJSON(b *testing.B) {
pod := api.Pod{}
apiObjectFuzzer := apitesting.FuzzerFor(nil, "", rand.NewSource(benchmarkSeed))
apiObjectFuzzer.Fuzz(&pod)
data, _ := testapi.Default.Codec().Encode(&pod)
for i := 0; i < b.N; i++ {
obj := api.Pod{}
json.Unmarshal(data, &obj)
func BenchmarkDecodeIntoJSON(b *testing.B) {
codec := testapi.Default.Codec()
items := benchmarkItems()
width := len(items)
encoded := make([][]byte, width)
for i := range items {
data, err := codec.Encode(&items[i])
if err != nil {
b.Fatal(err)
}
encoded[i] = data
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
obj := v1.Pod{}
if err := json.Unmarshal(encoded[i%width], &obj); err != nil {
b.Fatal(err)
}
}
b.StopTimer()
}
// BenchmarkDecodeJSON provides a baseline for codecgen JSON decode performance
func BenchmarkDecodeIntoJSONCodecGen(b *testing.B) {
kcodec := testapi.Default.Codec()
items := benchmarkItems()
width := len(items)
encoded := make([][]byte, width)
for i := range items {
data, err := kcodec.Encode(&items[i])
if err != nil {
b.Fatal(err)
}
encoded[i] = data
}
handler := &codec.JsonHandle{}
b.ResetTimer()
for i := 0; i < b.N; i++ {
obj := v1.Pod{}
if err := codec.NewDecoderBytes(encoded[i%width], handler).Decode(&obj); err != nil {
b.Fatal(err)
}
}
b.StopTimer()
}

View File

@ -43,5 +43,5 @@ func (d *Duration) UnmarshalJSON(b []byte) error {
// MarshalJSON implements the json.Marshaler interface.
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
return json.Marshal(d.Duration.String())
}

View File

@ -40,6 +40,8 @@ func (gvr *GroupVersionResource) String() string {
// GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying
// concepts during lookup stages without having partially valid types
//
// +protobuf.options.(gogoproto.goproto_stringer)=false
type GroupKind struct {
Group string
Kind string
@ -55,6 +57,8 @@ func (gk *GroupKind) String() string {
// GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion
// to avoid automatic coersion. It doesn't use a GroupVersion to avoid custom marshalling
//
// +protobuf.options.(gogoproto.goproto_stringer)=false
type GroupVersionKind struct {
Group string
Version string
@ -79,6 +83,8 @@ func (gvk *GroupVersionKind) String() string {
}
// GroupVersion contains the "group" and the "version", which uniquely identifies the API.
//
// +protobuf.options.(gogoproto.goproto_stringer)=false
type GroupVersion struct {
Group string
Version string

View File

@ -26,8 +26,10 @@ import (
// Time is a wrapper around time.Time which supports correct
// marshaling to YAML and JSON. Wrappers are provided for many
// of the factory methods that the time package offers.
//
// +protobuf.options.marshal=false
type Time struct {
time.Time
time.Time `protobuf:"Timestamp,1,req,name=time"`
}
// NewTime returns a wrapped instance of the provided time

View File

@ -0,0 +1,80 @@
// +build proto
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package unversioned
import (
"time"
)
// ProtoTime is a struct that is equivalent to Time, but intended for
// protobuf marshalling/unmarshalling. It is generated into a serialization
// that matches Time. Do not use in Go structs.
type ProtoTime struct {
// Represents the time of an event.
Timestamp Timestamp `json:"timestamp"`
}
// Timestamp is a protobuf Timestamp compatible representation of time.Time
type Timestamp struct {
// Represents seconds of UTC time since Unix epoch
// 1970-01-01T00:00:00Z. Must be from from 0001-01-01T00:00:00Z to
// 9999-12-31T23:59:59Z inclusive.
Seconds int64 `json:"seconds"`
// Non-negative fractions of a second at nanosecond resolution. Negative
// second values with fractions must still have non-negative nanos values
// that count forward in time. Must be from 0 to 999,999,999
// inclusive.
Nanos int32 `json:"nanos"`
}
// ProtoTime returns the Time as a new ProtoTime value.
func (m *Time) ProtoTime() *ProtoTime {
if m == nil {
return &ProtoTime{}
}
return &ProtoTime{
Timestamp: Timestamp{
Seconds: m.Time.Unix(),
Nanos: int32(m.Time.Nanosecond()),
},
}
}
// Size implements the protobuf marshalling interface.
func (m *Time) Size() (n int) { return m.ProtoTime().Size() }
// Reset implements the protobuf marshalling interface.
func (m *Time) Unmarshal(data []byte) error {
p := ProtoTime{}
if err := p.Unmarshal(data); err != nil {
return err
}
m.Time = time.Unix(p.Timestamp.Seconds, int64(p.Timestamp.Nanos))
return nil
}
// Marshal implements the protobuf marshalling interface.
func (m *Time) Marshal() (data []byte, err error) {
return m.ProtoTime().Marshal()
}
// MarshalTo implements the protobuf marshalling interface.
func (m *Time) MarshalTo(data []byte) (int, error) {
return m.ProtoTime().MarshalTo(data)
}

View File

@ -311,6 +311,8 @@ func (*APIResourceList) IsAnAPIObject() {}
// APIVersions lists the versions that are available, to allow clients to
// discover the API at /api, which is the root path of the legacy v1 API.
//
// +protobuf.options.(gogoproto.goproto_stringer)=false
type APIVersions struct {
TypeMeta `json:",inline"`
// versions are the api versions that are available.

View File

@ -0,0 +1,18 @@
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package protobuf implements ProtoBuf serialization and deserialization.
package protobuf

View File

@ -0,0 +1,158 @@
// +build proto
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package protobuf
import (
"fmt"
"io"
"net/url"
"reflect"
"github.com/gogo/protobuf/proto"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/runtime"
)
// NewCodec
func NewCodec(version string, creater runtime.ObjectCreater, typer runtime.ObjectTyper, convertor runtime.ObjectConvertor) runtime.Codec {
return &codec{
version: version,
creater: creater,
typer: typer,
convertor: convertor,
}
}
// codec decodes protobuf objects
type codec struct {
version string
outputVersion string
creater runtime.ObjectCreater
typer runtime.ObjectTyper
convertor runtime.ObjectConvertor
}
var _ runtime.Codec = codec{}
func (c codec) Decode(data []byte) (runtime.Object, error) {
unknown := &runtime.Unknown{}
if err := proto.Unmarshal(data, unknown); err != nil {
return nil, err
}
obj, err := c.creater.New(unknown.APIVersion, unknown.Kind)
if err != nil {
return nil, err
}
pobj, ok := obj.(proto.Message)
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 {
return nil, err
}
if unknown.APIVersion != c.outputVersion {
out, err := c.convertor.ConvertToVersion(obj, c.outputVersion)
if err != nil {
return nil, err
}
obj = out
}
return obj, nil
}
func (c codec) DecodeToVersion(data []byte, version unversioned.GroupVersion) (runtime.Object, error) {
return nil, fmt.Errorf("unimplemented")
}
func (c codec) DecodeInto(data []byte, obj runtime.Object) error {
version, kind, err := c.typer.ObjectVersionAndKind(obj)
if err != nil {
return err
}
unknown := &runtime.Unknown{}
if err := proto.Unmarshal(data, unknown); err != nil {
return err
}
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)
}
versioned, err := c.creater.New(unknown.APIVersion, unknown.Kind)
if err != nil {
return err
}
pobj, ok := versioned.(proto.Message)
if !ok {
return fmt.Errorf("runtime object is not a proto.Message: %v", reflect.TypeOf(obj))
}
if err := proto.Unmarshal(unknown.RawJSON, pobj); err != nil {
return err
}
return c.convertor.Convert(versioned, obj)
}
func (c codec) DecodeIntoWithSpecifiedVersionKind(data []byte, obj runtime.Object, kind unversioned.GroupVersionKind) error {
return fmt.Errorf("unimplemented")
}
func (c codec) DecodeParametersInto(parameters url.Values, obj runtime.Object) error {
return fmt.Errorf("unimplemented")
}
func (c codec) Encode(obj runtime.Object) (data []byte, err error) {
version, kind, err := c.typer.ObjectVersionAndKind(obj)
if err != nil {
return nil, err
}
if len(version) == 0 {
version = c.version
converted, err := c.convertor.ConvertToVersion(obj, version)
if err != nil {
return nil, err
}
obj = converted
}
m, ok := obj.(proto.Marshaler)
if !ok {
return nil, fmt.Errorf("object %v (kind: %s in version: %s) does not implement ProtoBuf marshalling", reflect.TypeOf(obj), kind, c.version)
}
b, err := m.Marshal()
if err != nil {
return nil, err
}
return (&runtime.Unknown{
TypeMeta: runtime.TypeMeta{
Kind: kind,
APIVersion: version,
},
RawJSON: b,
}).Marshal()
}
func (c codec) EncodeToStream(obj runtime.Object, stream io.Writer) error {
return fmt.Errorf("unimplemented")
}

View File

@ -30,6 +30,7 @@ package runtime
// TypeMeta is provided here for convenience. You may use it directly from this package or define
// your own with the same fields.
//
// +protobuf=true
type TypeMeta struct {
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
@ -98,6 +99,8 @@ type EmbeddedObject struct {
// JSON stored in RawExtension, turning it into the correct object type, and storing it
// in the EmbeddedObject. (TODO: In the case where the object is of an unknown type, a
// runtime.Unknown object will be created and stored.)
//
// +protobuf=true
type RawExtension struct {
RawJSON []byte
}
@ -107,6 +110,8 @@ type RawExtension struct {
// TypeMeta features-- kind, version, etc.
// TODO: Make this object have easy access to field based accessors and settors for
// metadata and field mutatation.
//
// +protobuf=true
type Unknown struct {
TypeMeta `json:",inline"`
// RawJSON will hold the complete JSON of the object which couldn't be matched

View File

@ -29,6 +29,9 @@ import (
// inner type. This allows you to have, for example, a JSON field that can
// accept a name or number.
// TODO: Rename to Int32OrString
//
// +protobuf=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
type IntOrString struct {
Type Type
IntVal int32