diff options
Diffstat (limited to 'vendor/k8s.io/apimachinery/pkg/runtime/serializer')
10 files changed, 1639 insertions, 0 deletions
diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/codec_factory.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/codec_factory.go new file mode 100644 index 000000000..65f451124 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/codec_factory.go @@ -0,0 +1,237 @@ +/* +Copyright 2014 The Kubernetes Authors. + +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 serializer + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/json" + "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" + "k8s.io/apimachinery/pkg/runtime/serializer/versioning" +) + +// serializerExtensions are for serializers that are conditionally compiled in +var serializerExtensions = []func(*runtime.Scheme) (serializerType, bool){} + +type serializerType struct { + AcceptContentTypes []string + ContentType string + FileExtensions []string + // EncodesAsText should be true if this content type can be represented safely in UTF-8 + EncodesAsText bool + + Serializer runtime.Serializer + PrettySerializer runtime.Serializer + + AcceptStreamContentTypes []string + StreamContentType string + + Framer runtime.Framer + StreamSerializer runtime.Serializer +} + +func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory) []serializerType { + jsonSerializer := json.NewSerializer(mf, scheme, scheme, false) + jsonPrettySerializer := json.NewSerializer(mf, scheme, scheme, true) + yamlSerializer := json.NewYAMLSerializer(mf, scheme, scheme) + + serializers := []serializerType{ + { + AcceptContentTypes: []string{"application/json"}, + ContentType: "application/json", + FileExtensions: []string{"json"}, + EncodesAsText: true, + Serializer: jsonSerializer, + PrettySerializer: jsonPrettySerializer, + + Framer: json.Framer, + StreamSerializer: jsonSerializer, + }, + { + AcceptContentTypes: []string{"application/yaml"}, + ContentType: "application/yaml", + FileExtensions: []string{"yaml"}, + EncodesAsText: true, + Serializer: yamlSerializer, + }, + } + + for _, fn := range serializerExtensions { + if serializer, ok := fn(scheme); ok { + serializers = append(serializers, serializer) + } + } + return serializers +} + +// CodecFactory provides methods for retrieving codecs and serializers for specific +// versions and content types. +type CodecFactory struct { + scheme *runtime.Scheme + serializers []serializerType + universal runtime.Decoder + accepts []runtime.SerializerInfo + + legacySerializer runtime.Serializer +} + +// NewCodecFactory provides methods for retrieving serializers for the supported wire formats +// and conversion wrappers to define preferred internal and external versions. In the future, +// as the internal version is used less, callers may instead use a defaulting serializer and +// only convert objects which are shared internally (Status, common API machinery). +// TODO: allow other codecs to be compiled in? +// TODO: accept a scheme interface +func NewCodecFactory(scheme *runtime.Scheme) CodecFactory { + serializers := newSerializersForScheme(scheme, json.DefaultMetaFactory) + return newCodecFactory(scheme, serializers) +} + +// newCodecFactory is a helper for testing that allows a different metafactory to be specified. +func newCodecFactory(scheme *runtime.Scheme, serializers []serializerType) CodecFactory { + decoders := make([]runtime.Decoder, 0, len(serializers)) + var accepts []runtime.SerializerInfo + alreadyAccepted := make(map[string]struct{}) + + var legacySerializer runtime.Serializer + for _, d := range serializers { + decoders = append(decoders, d.Serializer) + for _, mediaType := range d.AcceptContentTypes { + if _, ok := alreadyAccepted[mediaType]; ok { + continue + } + alreadyAccepted[mediaType] = struct{}{} + info := runtime.SerializerInfo{ + MediaType: d.ContentType, + EncodesAsText: d.EncodesAsText, + Serializer: d.Serializer, + PrettySerializer: d.PrettySerializer, + } + if d.StreamSerializer != nil { + info.StreamSerializer = &runtime.StreamSerializerInfo{ + Serializer: d.StreamSerializer, + EncodesAsText: d.EncodesAsText, + Framer: d.Framer, + } + } + accepts = append(accepts, info) + if mediaType == runtime.ContentTypeJSON { + legacySerializer = d.Serializer + } + } + } + if legacySerializer == nil { + legacySerializer = serializers[0].Serializer + } + + return CodecFactory{ + scheme: scheme, + serializers: serializers, + universal: recognizer.NewDecoder(decoders...), + + accepts: accepts, + + legacySerializer: legacySerializer, + } +} + +// SupportedMediaTypes returns the RFC2046 media types that this factory has serializers for. +func (f CodecFactory) SupportedMediaTypes() []runtime.SerializerInfo { + return f.accepts +} + +// LegacyCodec encodes output to a given API versions, and decodes output into the internal form from +// any recognized source. The returned codec will always encode output to JSON. If a type is not +// found in the list of versions an error will be returned. +// +// This method is deprecated - clients and servers should negotiate a serializer by mime-type and +// invoke CodecForVersions. Callers that need only to read data should use UniversalDecoder(). +// +// TODO: make this call exist only in pkg/api, and initialize it with the set of default versions. +// All other callers will be forced to request a Codec directly. +func (f CodecFactory) LegacyCodec(version ...schema.GroupVersion) runtime.Codec { + return versioning.NewDefaultingCodecForScheme(f.scheme, f.legacySerializer, f.universal, schema.GroupVersions(version), runtime.InternalGroupVersioner) +} + +// UniversalDeserializer can convert any stored data recognized by this factory into a Go object that satisfies +// runtime.Object. It does not perform conversion. It does not perform defaulting. +func (f CodecFactory) UniversalDeserializer() runtime.Decoder { + return f.universal +} + +// UniversalDecoder returns a runtime.Decoder capable of decoding all known API objects in all known formats. Used +// by clients that do not need to encode objects but want to deserialize API objects stored on disk. Only decodes +// objects in groups registered with the scheme. The GroupVersions passed may be used to select alternate +// versions of objects to return - by default, runtime.APIVersionInternal is used. If any versions are specified, +// unrecognized groups will be returned in the version they are encoded as (no conversion). This decoder performs +// defaulting. +// +// TODO: the decoder will eventually be removed in favor of dealing with objects in their versioned form +// TODO: only accept a group versioner +func (f CodecFactory) UniversalDecoder(versions ...schema.GroupVersion) runtime.Decoder { + var versioner runtime.GroupVersioner + if len(versions) == 0 { + versioner = runtime.InternalGroupVersioner + } else { + versioner = schema.GroupVersions(versions) + } + return f.CodecForVersions(nil, f.universal, nil, versioner) +} + +// CodecForVersions creates a codec with the provided serializer. If an object is decoded and its group is not in the list, +// it will default to runtime.APIVersionInternal. If encode is not specified for an object's group, the object is not +// converted. If encode or decode are nil, no conversion is performed. +func (f CodecFactory) CodecForVersions(encoder runtime.Encoder, decoder runtime.Decoder, encode runtime.GroupVersioner, decode runtime.GroupVersioner) runtime.Codec { + // TODO: these are for backcompat, remove them in the future + if encode == nil { + encode = runtime.DisabledGroupVersioner + } + if decode == nil { + decode = runtime.InternalGroupVersioner + } + return versioning.NewDefaultingCodecForScheme(f.scheme, encoder, decoder, encode, decode) +} + +// DecoderToVersion returns a decoder that targets the provided group version. +func (f CodecFactory) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { + return f.CodecForVersions(nil, decoder, nil, gv) +} + +// EncoderForVersion returns an encoder that targets the provided group version. +func (f CodecFactory) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { + return f.CodecForVersions(encoder, nil, gv, nil) +} + +// DirectCodecFactory provides methods for retrieving "DirectCodec"s, which do not do conversion. +type DirectCodecFactory struct { + CodecFactory +} + +// EncoderForVersion returns an encoder that does not do conversion. +func (f DirectCodecFactory) EncoderForVersion(serializer runtime.Encoder, version runtime.GroupVersioner) runtime.Encoder { + return versioning.DirectEncoder{ + Version: version, + Encoder: serializer, + ObjectTyper: f.CodecFactory.scheme, + } +} + +// DecoderToVersion returns an decoder that does not do conversion. gv is ignored. +func (f DirectCodecFactory) DecoderToVersion(serializer runtime.Decoder, _ runtime.GroupVersioner) runtime.Decoder { + return versioning.DirectDecoder{ + Decoder: serializer, + } +} diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go new file mode 100644 index 000000000..28bb91b53 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go @@ -0,0 +1,245 @@ +/* +Copyright 2014 The Kubernetes Authors. + +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 json + +import ( + "encoding/json" + "io" + + "github.com/ghodss/yaml" + "github.com/ugorji/go/codec" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" + "k8s.io/apimachinery/pkg/util/framer" + utilyaml "k8s.io/apimachinery/pkg/util/yaml" +) + +// NewSerializer creates a JSON serializer that handles encoding versioned objects into the proper JSON form. If typer +// is not nil, the object has the group, version, and kind fields set. +func NewSerializer(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper, pretty bool) *Serializer { + return &Serializer{ + meta: meta, + creater: creater, + typer: typer, + yaml: false, + pretty: pretty, + } +} + +// NewYAMLSerializer creates a YAML serializer that handles encoding versioned objects into the proper YAML form. If typer +// is not nil, the object has the group, version, and kind fields set. This serializer supports only the subset of YAML that +// matches JSON, and will error if constructs are used that do not serialize to JSON. +func NewYAMLSerializer(meta MetaFactory, creater runtime.ObjectCreater, typer runtime.ObjectTyper) *Serializer { + return &Serializer{ + meta: meta, + creater: creater, + typer: typer, + yaml: true, + } +} + +type Serializer struct { + meta MetaFactory + creater runtime.ObjectCreater + typer runtime.ObjectTyper + yaml bool + pretty bool +} + +// Serializer implements Serializer +var _ runtime.Serializer = &Serializer{} +var _ recognizer.RecognizingDecoder = &Serializer{} + +// Decode attempts to convert the provided data into YAML or JSON, extract the stored schema kind, apply the provided default gvk, and then +// load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, the raw data will be +// extracted and no decoding will be performed. If into is not registered with the typer, then the object will be straight decoded using +// normal JSON/YAML unmarshalling. If into is provided and the original data is not fully qualified with kind/version/group, the type of +// the into will be used to alter the returned gvk. On success or most errors, the method will return the calculated schema kind. +func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + if versioned, ok := into.(*runtime.VersionedObjects); ok { + into = versioned.Last() + obj, actual, err := s.Decode(originalData, gvk, into) + if err != nil { + return nil, actual, err + } + versioned.Objects = []runtime.Object{obj} + return versioned, actual, nil + } + + data := originalData + if s.yaml { + altered, err := yaml.YAMLToJSON(data) + if err != nil { + return nil, nil, err + } + data = altered + } + + actual, err := s.meta.Interpret(data) + if err != nil { + return nil, nil, err + } + + if gvk != nil { + // apply kind and version defaulting from provided default + if len(actual.Kind) == 0 { + actual.Kind = gvk.Kind + } + if len(actual.Version) == 0 && len(actual.Group) == 0 { + actual.Group = gvk.Group + actual.Version = gvk.Version + } + if len(actual.Version) == 0 && actual.Group == gvk.Group { + actual.Version = gvk.Version + } + } + + if unk, ok := into.(*runtime.Unknown); ok && unk != nil { + unk.Raw = originalData + unk.ContentType = runtime.ContentTypeJSON + unk.GetObjectKind().SetGroupVersionKind(*actual) + return unk, actual, nil + } + + if into != nil { + types, _, err := s.typer.ObjectKinds(into) + switch { + case runtime.IsNotRegisteredError(err): + if err := codec.NewDecoderBytes(data, new(codec.JsonHandle)).Decode(into); err != nil { + return nil, actual, err + } + return into, actual, nil + case err != nil: + return nil, actual, err + default: + typed := types[0] + if len(actual.Kind) == 0 { + actual.Kind = typed.Kind + } + if len(actual.Version) == 0 && len(actual.Group) == 0 { + actual.Group = typed.Group + actual.Version = typed.Version + } + if len(actual.Version) == 0 && actual.Group == typed.Group { + actual.Version = typed.Version + } + } + } + + if len(actual.Kind) == 0 { + return nil, actual, runtime.NewMissingKindErr(string(originalData)) + } + if len(actual.Version) == 0 { + return nil, actual, runtime.NewMissingVersionErr(string(originalData)) + } + + // use the target if necessary + obj, err := runtime.UseOrCreateObject(s.typer, s.creater, *actual, into) + if err != nil { + return nil, actual, err + } + + if err := codec.NewDecoderBytes(data, new(codec.JsonHandle)).Decode(obj); err != nil { + return nil, actual, err + } + return obj, actual, nil +} + +// Encode serializes the provided object to the given writer. +func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error { + if s.yaml { + json, err := json.Marshal(obj) + if err != nil { + return err + } + data, err := yaml.JSONToYAML(json) + if err != nil { + return err + } + _, err = w.Write(data) + return err + } + + if s.pretty { + data, err := json.MarshalIndent(obj, "", " ") + if err != nil { + return err + } + _, err = w.Write(data) + return err + } + encoder := json.NewEncoder(w) + return encoder.Encode(obj) +} + +// RecognizesData implements the RecognizingDecoder interface. +func (s *Serializer) RecognizesData(peek io.Reader) (ok, unknown bool, err error) { + if s.yaml { + // we could potentially look for '---' + return false, true, nil + } + _, _, ok = utilyaml.GuessJSONStream(peek, 2048) + return ok, false, nil +} + +// Framer is the default JSON framing behavior, with newlines delimiting individual objects. +var Framer = jsonFramer{} + +type jsonFramer struct{} + +// NewFrameWriter implements stream framing for this serializer +func (jsonFramer) NewFrameWriter(w io.Writer) io.Writer { + // we can write JSON objects directly to the writer, because they are self-framing + return w +} + +// NewFrameReader implements stream framing for this serializer +func (jsonFramer) NewFrameReader(r io.ReadCloser) io.ReadCloser { + // we need to extract the JSON chunks of data to pass to Decode() + return framer.NewJSONFramedReader(r) +} + +// Framer is the default JSON framing behavior, with newlines delimiting individual objects. +var YAMLFramer = yamlFramer{} + +type yamlFramer struct{} + +// NewFrameWriter implements stream framing for this serializer +func (yamlFramer) NewFrameWriter(w io.Writer) io.Writer { + return yamlFrameWriter{w} +} + +// NewFrameReader implements stream framing for this serializer +func (yamlFramer) NewFrameReader(r io.ReadCloser) io.ReadCloser { + // extract the YAML document chunks directly + return utilyaml.NewDocumentDecoder(r) +} + +type yamlFrameWriter struct { + w io.Writer +} + +// Write separates each document with the YAML document separator (`---` followed by line +// break). Writers must write well formed YAML documents (include a final line break). +func (w yamlFrameWriter) Write(data []byte) (n int, err error) { + if _, err := w.w.Write([]byte("---\n")); err != nil { + return 0, err + } + return w.w.Write(data) +} diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/json/meta.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/json/meta.go new file mode 100644 index 000000000..df3f5f989 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/json/meta.go @@ -0,0 +1,63 @@ +/* +Copyright 2014 The Kubernetes Authors. + +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 json + +import ( + "encoding/json" + "fmt" + + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// MetaFactory is used to store and retrieve the version and kind +// information for JSON objects in a serializer. +type MetaFactory interface { + // Interpret should return the version and kind of the wire-format of + // the object. + Interpret(data []byte) (*schema.GroupVersionKind, error) +} + +// DefaultMetaFactory is a default factory for versioning objects in JSON. The object +// in memory and in the default JSON serialization will use the "kind" and "apiVersion" +// fields. +var DefaultMetaFactory = SimpleMetaFactory{} + +// SimpleMetaFactory provides default methods for retrieving the type and version of objects +// that are identified with an "apiVersion" and "kind" fields in their JSON +// serialization. It may be parameterized with the names of the fields in memory, or an +// optional list of base structs to search for those fields in memory. +type SimpleMetaFactory struct { +} + +// Interpret will return the APIVersion and Kind of the JSON wire-format +// encoding of an object, or an error. +func (SimpleMetaFactory) Interpret(data []byte) (*schema.GroupVersionKind, error) { + findKind := struct { + // +optional + APIVersion string `json:"apiVersion,omitempty"` + // +optional + Kind string `json:"kind,omitempty"` + }{} + if err := json.Unmarshal(data, &findKind); err != nil { + return nil, fmt.Errorf("couldn't get version/kind; json parse error: %v", err) + } + gv, err := schema.ParseGroupVersion(findKind.APIVersion) + if err != nil { + return nil, err + } + return &schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: findKind.Kind}, nil +} diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/negotiated_codec.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/negotiated_codec.go new file mode 100644 index 000000000..a42b4a41a --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/negotiated_codec.go @@ -0,0 +1,43 @@ +/* +Copyright 2016 The Kubernetes Authors. + +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 serializer + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +// TODO: We should split negotiated serializers that we can change versions on from those we can change +// serialization formats on +type negotiatedSerializerWrapper struct { + info runtime.SerializerInfo +} + +func NegotiatedSerializerWrapper(info runtime.SerializerInfo) runtime.NegotiatedSerializer { + return &negotiatedSerializerWrapper{info} +} + +func (n *negotiatedSerializerWrapper) SupportedMediaTypes() []runtime.SerializerInfo { + return []runtime.SerializerInfo{n.info} +} + +func (n *negotiatedSerializerWrapper) EncoderForVersion(e runtime.Encoder, _ runtime.GroupVersioner) runtime.Encoder { + return e +} + +func (n *negotiatedSerializerWrapper) DecoderToVersion(d runtime.Decoder, _gv runtime.GroupVersioner) runtime.Decoder { + return d +} diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/doc.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/doc.go new file mode 100644 index 000000000..72d0ac79b --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2015 The Kubernetes Authors. + +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 provides a Kubernetes serializer for the protobuf format. +package protobuf // import "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf.go new file mode 100644 index 000000000..8d4ea7118 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf.go @@ -0,0 +1,448 @@ +/* +Copyright 2015 The Kubernetes Authors. + +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 ( + "bytes" + "fmt" + "io" + "reflect" + + "github.com/gogo/protobuf/proto" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer/recognizer" + "k8s.io/apimachinery/pkg/util/framer" +) + +var ( + // protoEncodingPrefix serves as a magic number for an encoded protobuf message on this serializer. All + // proto messages serialized by this schema will be preceded by the bytes 0x6b 0x38 0x73, with the fourth + // byte being reserved for the encoding style. The only encoding style defined is 0x00, which means that + // the rest of the byte stream is a message of type k8s.io.kubernetes.pkg.runtime.Unknown (proto2). + // + // See k8s.io/apimachinery/pkg/runtime/generated.proto for details of the runtime.Unknown message. + // + // This encoding scheme is experimental, and is subject to change at any time. + protoEncodingPrefix = []byte{0x6b, 0x38, 0x73, 0x00} +) + +type errNotMarshalable struct { + t reflect.Type +} + +func (e errNotMarshalable) Error() string { + return fmt.Sprintf("object %v does not implement the protobuf marshalling interface and cannot be encoded to a protobuf message", e.t) +} + +func IsNotMarshalable(err error) bool { + _, ok := err.(errNotMarshalable) + return err != nil && ok +} + +// NewSerializer creates a Protobuf serializer that handles encoding versioned objects into the proper wire form. If a typer +// is passed, the encoded object will have group, version, and kind fields set. If typer is nil, the objects will be written +// as-is (any type info passed with the object will be used). +// +// This encoding scheme is experimental, and is subject to change at any time. +func NewSerializer(creater runtime.ObjectCreater, typer runtime.ObjectTyper, defaultContentType string) *Serializer { + return &Serializer{ + prefix: protoEncodingPrefix, + creater: creater, + typer: typer, + contentType: defaultContentType, + } +} + +type Serializer struct { + prefix []byte + creater runtime.ObjectCreater + typer runtime.ObjectTyper + contentType string +} + +var _ runtime.Serializer = &Serializer{} +var _ recognizer.RecognizingDecoder = &Serializer{} + +// Decode attempts to convert the provided data into a protobuf message, extract the stored schema kind, apply the provided default +// gvk, and then load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, +// the raw data will be extracted and no decoding will be performed. If into is not registered with the typer, then the object will +// be straight decoded using normal protobuf unmarshalling (the MarshalTo interface). If into is provided and the original data is +// not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk. On success or most +// errors, the method will return the calculated schema kind. +func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + if versioned, ok := into.(*runtime.VersionedObjects); ok { + into = versioned.Last() + obj, actual, err := s.Decode(originalData, gvk, into) + if err != nil { + return nil, actual, err + } + // the last item in versioned becomes into, so if versioned was not originally empty we reset the object + // array so the first position is the decoded object and the second position is the outermost object. + // if there were no objects in the versioned list passed to us, only add ourselves. + if into != nil && into != obj { + versioned.Objects = []runtime.Object{obj, into} + } else { + versioned.Objects = []runtime.Object{obj} + } + return versioned, actual, err + } + + prefixLen := len(s.prefix) + switch { + case len(originalData) == 0: + // TODO: treat like decoding {} from JSON with defaulting + return nil, nil, fmt.Errorf("empty data") + case len(originalData) < prefixLen || !bytes.Equal(s.prefix, originalData[:prefixLen]): + return nil, nil, fmt.Errorf("provided data does not appear to be a protobuf message, expected prefix %v", s.prefix) + case len(originalData) == prefixLen: + // TODO: treat like decoding {} from JSON with defaulting + return nil, nil, fmt.Errorf("empty body") + } + + data := originalData[prefixLen:] + unk := runtime.Unknown{} + if err := unk.Unmarshal(data); err != nil { + return nil, nil, err + } + + actual := unk.GroupVersionKind() + copyKindDefaults(&actual, gvk) + + if intoUnknown, ok := into.(*runtime.Unknown); ok && intoUnknown != nil { + *intoUnknown = unk + if ok, _, _ := s.RecognizesData(bytes.NewBuffer(unk.Raw)); ok { + intoUnknown.ContentType = s.contentType + } + return intoUnknown, &actual, nil + } + + if into != nil { + types, _, err := s.typer.ObjectKinds(into) + switch { + case runtime.IsNotRegisteredError(err): + pb, ok := into.(proto.Message) + if !ok { + return nil, &actual, errNotMarshalable{reflect.TypeOf(into)} + } + if err := proto.Unmarshal(unk.Raw, pb); err != nil { + return nil, &actual, err + } + return into, &actual, nil + case err != nil: + return nil, &actual, err + default: + copyKindDefaults(&actual, &types[0]) + // if the result of defaulting did not set a version or group, ensure that at least group is set + // (copyKindDefaults will not assign Group if version is already set). This guarantees that the group + // of into is set if there is no better information from the caller or object. + if len(actual.Version) == 0 && len(actual.Group) == 0 { + actual.Group = types[0].Group + } + } + } + + if len(actual.Kind) == 0 { + return nil, &actual, runtime.NewMissingKindErr(fmt.Sprintf("%#v", unk.TypeMeta)) + } + if len(actual.Version) == 0 { + return nil, &actual, runtime.NewMissingVersionErr(fmt.Sprintf("%#v", unk.TypeMeta)) + } + + return unmarshalToObject(s.typer, s.creater, &actual, into, unk.Raw) +} + +// Encode serializes the provided object to the given writer. +func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error { + prefixSize := uint64(len(s.prefix)) + + var unk runtime.Unknown + switch t := obj.(type) { + case *runtime.Unknown: + estimatedSize := prefixSize + uint64(t.Size()) + data := make([]byte, estimatedSize) + i, err := t.MarshalTo(data[prefixSize:]) + if err != nil { + return err + } + copy(data, s.prefix) + _, err = w.Write(data[:prefixSize+uint64(i)]) + return err + default: + kind := obj.GetObjectKind().GroupVersionKind() + unk = runtime.Unknown{ + TypeMeta: runtime.TypeMeta{ + Kind: kind.Kind, + APIVersion: kind.GroupVersion().String(), + }, + } + } + + switch t := obj.(type) { + case bufferedMarshaller: + // this path performs a single allocation during write but requires the caller to implement + // the more efficient Size and MarshalTo methods + encodedSize := uint64(t.Size()) + estimatedSize := prefixSize + estimateUnknownSize(&unk, encodedSize) + data := make([]byte, estimatedSize) + + i, err := unk.NestedMarshalTo(data[prefixSize:], t, encodedSize) + if err != nil { + return err + } + + copy(data, s.prefix) + + _, err = w.Write(data[:prefixSize+uint64(i)]) + return err + + case proto.Marshaler: + // this path performs extra allocations + data, err := t.Marshal() + if err != nil { + return err + } + unk.Raw = data + + estimatedSize := prefixSize + uint64(unk.Size()) + data = make([]byte, estimatedSize) + + i, err := unk.MarshalTo(data[prefixSize:]) + if err != nil { + return err + } + + copy(data, s.prefix) + + _, err = w.Write(data[:prefixSize+uint64(i)]) + return err + + default: + // TODO: marshal with a different content type and serializer (JSON for third party objects) + return errNotMarshalable{reflect.TypeOf(obj)} + } +} + +// RecognizesData implements the RecognizingDecoder interface. +func (s *Serializer) RecognizesData(peek io.Reader) (bool, bool, error) { + prefix := make([]byte, 4) + n, err := peek.Read(prefix) + if err != nil { + if err == io.EOF { + return false, false, nil + } + return false, false, err + } + if n != 4 { + return false, false, nil + } + return bytes.Equal(s.prefix, prefix), false, nil +} + +// copyKindDefaults defaults dst to the value in src if dst does not have a value set. +func copyKindDefaults(dst, src *schema.GroupVersionKind) { + if src == nil { + return + } + // apply kind and version defaulting from provided default + if len(dst.Kind) == 0 { + dst.Kind = src.Kind + } + if len(dst.Version) == 0 && len(src.Version) > 0 { + dst.Group = src.Group + dst.Version = src.Version + } +} + +// bufferedMarshaller describes a more efficient marshalling interface that can avoid allocating multiple +// byte buffers by pre-calculating the size of the final buffer needed. +type bufferedMarshaller interface { + proto.Sizer + runtime.ProtobufMarshaller +} + +// estimateUnknownSize returns the expected bytes consumed by a given runtime.Unknown +// object with a nil RawJSON struct and the expected size of the provided buffer. The +// returned size will not be correct if RawJSOn is set on unk. +func estimateUnknownSize(unk *runtime.Unknown, byteSize uint64) uint64 { + size := uint64(unk.Size()) + // protobuf uses 1 byte for the tag, a varint for the length of the array (at most 8 bytes - uint64 - here), + // and the size of the array. + size += 1 + 8 + byteSize + return size +} + +// NewRawSerializer creates a Protobuf serializer that handles encoding versioned objects into the proper wire form. If typer +// is not nil, the object has the group, version, and kind fields set. This serializer does not provide type information for the +// encoded object, and thus is not self describing (callers must know what type is being described in order to decode). +// +// This encoding scheme is experimental, and is subject to change at any time. +func NewRawSerializer(creater runtime.ObjectCreater, typer runtime.ObjectTyper, defaultContentType string) *RawSerializer { + return &RawSerializer{ + creater: creater, + typer: typer, + contentType: defaultContentType, + } +} + +// RawSerializer encodes and decodes objects without adding a runtime.Unknown wrapper (objects are encoded without identifying +// type). +type RawSerializer struct { + creater runtime.ObjectCreater + typer runtime.ObjectTyper + contentType string +} + +var _ runtime.Serializer = &RawSerializer{} + +// Decode attempts to convert the provided data into a protobuf message, extract the stored schema kind, apply the provided default +// gvk, and then load that data into an object matching the desired schema kind or the provided into. If into is *runtime.Unknown, +// the raw data will be extracted and no decoding will be performed. If into is not registered with the typer, then the object will +// be straight decoded using normal protobuf unmarshalling (the MarshalTo interface). If into is provided and the original data is +// not fully qualified with kind/version/group, the type of the into will be used to alter the returned gvk. On success or most +// errors, the method will return the calculated schema kind. +func (s *RawSerializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + if into == nil { + return nil, nil, fmt.Errorf("this serializer requires an object to decode into: %#v", s) + } + + if versioned, ok := into.(*runtime.VersionedObjects); ok { + into = versioned.Last() + obj, actual, err := s.Decode(originalData, gvk, into) + if err != nil { + return nil, actual, err + } + if into != nil && into != obj { + versioned.Objects = []runtime.Object{obj, into} + } else { + versioned.Objects = []runtime.Object{obj} + } + return versioned, actual, err + } + + if len(originalData) == 0 { + // TODO: treat like decoding {} from JSON with defaulting + return nil, nil, fmt.Errorf("empty data") + } + data := originalData + + actual := &schema.GroupVersionKind{} + copyKindDefaults(actual, gvk) + + if intoUnknown, ok := into.(*runtime.Unknown); ok && intoUnknown != nil { + intoUnknown.Raw = data + intoUnknown.ContentEncoding = "" + intoUnknown.ContentType = s.contentType + intoUnknown.SetGroupVersionKind(*actual) + return intoUnknown, actual, nil + } + + types, _, err := s.typer.ObjectKinds(into) + switch { + case runtime.IsNotRegisteredError(err): + pb, ok := into.(proto.Message) + if !ok { + return nil, actual, errNotMarshalable{reflect.TypeOf(into)} + } + if err := proto.Unmarshal(data, pb); err != nil { + return nil, actual, err + } + return into, actual, nil + case err != nil: + return nil, actual, err + default: + copyKindDefaults(actual, &types[0]) + // if the result of defaulting did not set a version or group, ensure that at least group is set + // (copyKindDefaults will not assign Group if version is already set). This guarantees that the group + // of into is set if there is no better information from the caller or object. + if len(actual.Version) == 0 && len(actual.Group) == 0 { + actual.Group = types[0].Group + } + } + + if len(actual.Kind) == 0 { + return nil, actual, runtime.NewMissingKindErr("<protobuf encoded body - must provide default type>") + } + if len(actual.Version) == 0 { + return nil, actual, runtime.NewMissingVersionErr("<protobuf encoded body - must provide default type>") + } + + return unmarshalToObject(s.typer, s.creater, actual, into, data) +} + +// unmarshalToObject is the common code between decode in the raw and normal serializer. +func unmarshalToObject(typer runtime.ObjectTyper, creater runtime.ObjectCreater, actual *schema.GroupVersionKind, into runtime.Object, data []byte) (runtime.Object, *schema.GroupVersionKind, error) { + // use the target if necessary + obj, err := runtime.UseOrCreateObject(typer, creater, *actual, into) + if err != nil { + return nil, actual, err + } + + pb, ok := obj.(proto.Message) + if !ok { + return nil, actual, errNotMarshalable{reflect.TypeOf(obj)} + } + if err := proto.Unmarshal(data, pb); err != nil { + return nil, actual, err + } + return obj, actual, nil +} + +// Encode serializes the provided object to the given writer. Overrides is ignored. +func (s *RawSerializer) Encode(obj runtime.Object, w io.Writer) error { + switch t := obj.(type) { + case bufferedMarshaller: + // this path performs a single allocation during write but requires the caller to implement + // the more efficient Size and MarshalTo methods + encodedSize := uint64(t.Size()) + data := make([]byte, encodedSize) + + n, err := t.MarshalTo(data) + if err != nil { + return err + } + _, err = w.Write(data[:n]) + return err + + case proto.Marshaler: + // this path performs extra allocations + data, err := t.Marshal() + if err != nil { + return err + } + _, err = w.Write(data) + return err + + default: + return errNotMarshalable{reflect.TypeOf(obj)} + } +} + +var LengthDelimitedFramer = lengthDelimitedFramer{} + +type lengthDelimitedFramer struct{} + +// NewFrameWriter implements stream framing for this serializer +func (lengthDelimitedFramer) NewFrameWriter(w io.Writer) io.Writer { + return framer.NewLengthDelimitedFrameWriter(w) +} + +// NewFrameReader implements stream framing for this serializer +func (lengthDelimitedFramer) NewFrameReader(r io.ReadCloser) io.ReadCloser { + return framer.NewLengthDelimitedFrameReader(r) +} diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf_extension.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf_extension.go new file mode 100644 index 000000000..545cf78df --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf_extension.go @@ -0,0 +1,48 @@ +/* +Copyright 2014 The Kubernetes Authors. + +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 serializer + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer/protobuf" +) + +const ( + // contentTypeProtobuf is the protobuf type exposed for Kubernetes. It is private to prevent others from + // depending on it unintentionally. + // TODO: potentially move to pkg/api (since it's part of the Kube public API) and pass it in to the + // CodecFactory on initialization. + contentTypeProtobuf = "application/vnd.kubernetes.protobuf" +) + +func protobufSerializer(scheme *runtime.Scheme) (serializerType, bool) { + serializer := protobuf.NewSerializer(scheme, scheme, contentTypeProtobuf) + raw := protobuf.NewRawSerializer(scheme, scheme, contentTypeProtobuf) + return serializerType{ + AcceptContentTypes: []string{contentTypeProtobuf}, + ContentType: contentTypeProtobuf, + FileExtensions: []string{"pb"}, + Serializer: serializer, + + Framer: protobuf.LengthDelimitedFramer, + StreamSerializer: raw, + }, true +} + +func init() { + serializerExtensions = append(serializerExtensions, protobufSerializer) +} diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/recognizer/recognizer.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/recognizer/recognizer.go new file mode 100644 index 000000000..38497ab53 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/recognizer/recognizer.go @@ -0,0 +1,127 @@ +/* +Copyright 2014 The Kubernetes Authors. + +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 recognizer + +import ( + "bufio" + "bytes" + "fmt" + "io" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type RecognizingDecoder interface { + runtime.Decoder + // RecognizesData should return true if the input provided in the provided reader + // belongs to this decoder, or an error if the data could not be read or is ambiguous. + // Unknown is true if the data could not be determined to match the decoder type. + // Decoders should assume that they can read as much of peek as they need (as the caller + // provides) and may return unknown if the data provided is not sufficient to make a + // a determination. When peek returns EOF that may mean the end of the input or the + // end of buffered input - recognizers should return the best guess at that time. + RecognizesData(peek io.Reader) (ok, unknown bool, err error) +} + +// NewDecoder creates a decoder that will attempt multiple decoders in an order defined +// by: +// +// 1. The decoder implements RecognizingDecoder and identifies the data +// 2. All other decoders, and any decoder that returned true for unknown. +// +// The order passed to the constructor is preserved within those priorities. +func NewDecoder(decoders ...runtime.Decoder) runtime.Decoder { + return &decoder{ + decoders: decoders, + } +} + +type decoder struct { + decoders []runtime.Decoder +} + +var _ RecognizingDecoder = &decoder{} + +func (d *decoder) RecognizesData(peek io.Reader) (bool, bool, error) { + var ( + lastErr error + anyUnknown bool + ) + data, _ := bufio.NewReaderSize(peek, 1024).Peek(1024) + for _, r := range d.decoders { + switch t := r.(type) { + case RecognizingDecoder: + ok, unknown, err := t.RecognizesData(bytes.NewBuffer(data)) + if err != nil { + lastErr = err + continue + } + anyUnknown = anyUnknown || unknown + if !ok { + continue + } + return true, false, nil + } + } + return false, anyUnknown, lastErr +} + +func (d *decoder) Decode(data []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + var ( + lastErr error + skipped []runtime.Decoder + ) + + // try recognizers, record any decoders we need to give a chance later + for _, r := range d.decoders { + switch t := r.(type) { + case RecognizingDecoder: + buf := bytes.NewBuffer(data) + ok, unknown, err := t.RecognizesData(buf) + if err != nil { + lastErr = err + continue + } + if unknown { + skipped = append(skipped, t) + continue + } + if !ok { + continue + } + return r.Decode(data, gvk, into) + default: + skipped = append(skipped, t) + } + } + + // try recognizers that returned unknown or didn't recognize their data + for _, r := range skipped { + out, actual, err := r.Decode(data, gvk, into) + if err != nil { + lastErr = err + continue + } + return out, actual, nil + } + + if lastErr == nil { + lastErr = fmt.Errorf("no serialization format matched the provided data") + } + return nil, nil, lastErr +} diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/streaming/streaming.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/streaming/streaming.go new file mode 100644 index 000000000..91fd4ed4f --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/streaming/streaming.go @@ -0,0 +1,137 @@ +/* +Copyright 2015 The Kubernetes Authors. + +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 streaming implements encoder and decoder for streams +// of runtime.Objects over io.Writer/Readers. +package streaming + +import ( + "bytes" + "fmt" + "io" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// Encoder is a runtime.Encoder on a stream. +type Encoder interface { + // Encode will write the provided object to the stream or return an error. It obeys the same + // contract as runtime.VersionedEncoder. + Encode(obj runtime.Object) error +} + +// Decoder is a runtime.Decoder from a stream. +type Decoder interface { + // Decode will return io.EOF when no more objects are available. + Decode(defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) + // Close closes the underlying stream. + Close() error +} + +// Serializer is a factory for creating encoders and decoders that work over streams. +type Serializer interface { + NewEncoder(w io.Writer) Encoder + NewDecoder(r io.ReadCloser) Decoder +} + +type decoder struct { + reader io.ReadCloser + decoder runtime.Decoder + buf []byte + maxBytes int + resetRead bool +} + +// NewDecoder creates a streaming decoder that reads object chunks from r and decodes them with d. +// The reader is expected to return ErrShortRead if the provided buffer is not large enough to read +// an entire object. +func NewDecoder(r io.ReadCloser, d runtime.Decoder) Decoder { + return &decoder{ + reader: r, + decoder: d, + buf: make([]byte, 1024), + maxBytes: 1024 * 1024, + } +} + +var ErrObjectTooLarge = fmt.Errorf("object to decode was longer than maximum allowed size") + +// Decode reads the next object from the stream and decodes it. +func (d *decoder) Decode(defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + base := 0 + for { + n, err := d.reader.Read(d.buf[base:]) + if err == io.ErrShortBuffer { + if n == 0 { + return nil, nil, fmt.Errorf("got short buffer with n=0, base=%d, cap=%d", base, cap(d.buf)) + } + if d.resetRead { + continue + } + // double the buffer size up to maxBytes + if len(d.buf) < d.maxBytes { + base += n + d.buf = append(d.buf, make([]byte, len(d.buf))...) + continue + } + // must read the rest of the frame (until we stop getting ErrShortBuffer) + d.resetRead = true + base = 0 + return nil, nil, ErrObjectTooLarge + } + if err != nil { + return nil, nil, err + } + if d.resetRead { + // now that we have drained the large read, continue + d.resetRead = false + continue + } + base += n + break + } + return d.decoder.Decode(d.buf[:base], defaults, into) +} + +func (d *decoder) Close() error { + return d.reader.Close() +} + +type encoder struct { + writer io.Writer + encoder runtime.Encoder + buf *bytes.Buffer +} + +// NewEncoder returns a new streaming encoder. +func NewEncoder(w io.Writer, e runtime.Encoder) Encoder { + return &encoder{ + writer: w, + encoder: e, + buf: &bytes.Buffer{}, + } +} + +// Encode writes the provided object to the nested writer. +func (e *encoder) Encode(obj runtime.Object) error { + if err := e.encoder.Encode(obj, e.buf); err != nil { + return err + } + _, err := e.writer.Write(e.buf.Bytes()) + e.buf.Reset() + return err +} diff --git a/vendor/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning.go b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning.go new file mode 100644 index 000000000..829d4fa89 --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/runtime/serializer/versioning/versioning.go @@ -0,0 +1,273 @@ +/* +Copyright 2014 The Kubernetes Authors. + +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 versioning + +import ( + "io" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +// NewCodecForScheme is a convenience method for callers that are using a scheme. +func NewCodecForScheme( + // TODO: I should be a scheme interface? + scheme *runtime.Scheme, + encoder runtime.Encoder, + decoder runtime.Decoder, + encodeVersion runtime.GroupVersioner, + decodeVersion runtime.GroupVersioner, +) runtime.Codec { + return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, scheme, nil, encodeVersion, decodeVersion) +} + +// NewDefaultingCodecForScheme is a convenience method for callers that are using a scheme. +func NewDefaultingCodecForScheme( + // TODO: I should be a scheme interface? + scheme *runtime.Scheme, + encoder runtime.Encoder, + decoder runtime.Decoder, + encodeVersion runtime.GroupVersioner, + decodeVersion runtime.GroupVersioner, +) runtime.Codec { + return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, scheme, scheme, encodeVersion, decodeVersion) +} + +// NewCodec takes objects in their internal versions and converts them to external versions before +// serializing them. It assumes the serializer provided to it only deals with external versions. +// This class is also a serializer, but is generally used with a specific version. +func NewCodec( + encoder runtime.Encoder, + decoder runtime.Decoder, + convertor runtime.ObjectConvertor, + creater runtime.ObjectCreater, + copier runtime.ObjectCopier, + typer runtime.ObjectTyper, + defaulter runtime.ObjectDefaulter, + encodeVersion runtime.GroupVersioner, + decodeVersion runtime.GroupVersioner, +) runtime.Codec { + internal := &codec{ + encoder: encoder, + decoder: decoder, + convertor: convertor, + creater: creater, + copier: copier, + typer: typer, + defaulter: defaulter, + + encodeVersion: encodeVersion, + decodeVersion: decodeVersion, + } + return internal +} + +type codec struct { + encoder runtime.Encoder + decoder runtime.Decoder + convertor runtime.ObjectConvertor + creater runtime.ObjectCreater + copier runtime.ObjectCopier + typer runtime.ObjectTyper + defaulter runtime.ObjectDefaulter + + encodeVersion runtime.GroupVersioner + decodeVersion runtime.GroupVersioner +} + +// Decode attempts a decode of the object, then tries to convert it to the internal version. If into is provided and the decoding is +// successful, the returned runtime.Object will be the value passed as into. Note that this may bypass conversion if you pass an +// into that matches the serialized version. +func (c *codec) Decode(data []byte, defaultGVK *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + versioned, isVersioned := into.(*runtime.VersionedObjects) + if isVersioned { + into = versioned.Last() + } + + obj, gvk, err := c.decoder.Decode(data, defaultGVK, into) + if err != nil { + return nil, gvk, err + } + + if d, ok := obj.(runtime.NestedObjectDecoder); ok { + if err := d.DecodeNestedObjects(DirectDecoder{c.decoder}); err != nil { + return nil, gvk, err + } + } + + // if we specify a target, use generic conversion. + if into != nil { + if into == obj { + if isVersioned { + return versioned, gvk, nil + } + return into, gvk, nil + } + + // perform defaulting if requested + if c.defaulter != nil { + // create a copy to ensure defaulting is not applied to the original versioned objects + if isVersioned { + copied, err := c.copier.Copy(obj) + if err != nil { + utilruntime.HandleError(err) + copied = obj + } + versioned.Objects = []runtime.Object{copied} + } + c.defaulter.Default(obj) + } else { + if isVersioned { + versioned.Objects = []runtime.Object{obj} + } + } + + if err := c.convertor.Convert(obj, into, c.decodeVersion); err != nil { + return nil, gvk, err + } + + if isVersioned { + versioned.Objects = append(versioned.Objects, into) + return versioned, gvk, nil + } + return into, gvk, nil + } + + // Convert if needed. + if isVersioned { + // create a copy, because ConvertToVersion does not guarantee non-mutation of objects + copied, err := c.copier.Copy(obj) + if err != nil { + utilruntime.HandleError(err) + copied = obj + } + versioned.Objects = []runtime.Object{copied} + } + + // perform defaulting if requested + if c.defaulter != nil { + c.defaulter.Default(obj) + } + + out, err := c.convertor.ConvertToVersion(obj, c.decodeVersion) + if err != nil { + return nil, gvk, err + } + if isVersioned { + if versioned.Last() != out { + versioned.Objects = append(versioned.Objects, out) + } + return versioned, gvk, nil + } + return out, gvk, nil +} + +// 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: + return c.encoder.Encode(obj, w) + } + + gvks, isUnversioned, err := c.typer.ObjectKinds(obj) + if err != nil { + return err + } + + if c.encodeVersion == nil || isUnversioned { + if e, ok := obj.(runtime.NestedObjectEncoder); ok { + if err := e.EncodeNestedObjects(DirectEncoder{Encoder: c.encoder, ObjectTyper: c.typer}); err != nil { + return err + } + } + objectKind := obj.GetObjectKind() + old := objectKind.GroupVersionKind() + objectKind.SetGroupVersionKind(gvks[0]) + err = c.encoder.Encode(obj, w) + objectKind.SetGroupVersionKind(old) + return err + } + + // Perform a conversion if necessary + objectKind := obj.GetObjectKind() + old := objectKind.GroupVersionKind() + out, err := c.convertor.ConvertToVersion(obj, c.encodeVersion) + if err != nil { + return err + } + + if e, ok := out.(runtime.NestedObjectEncoder); ok { + if err := e.EncodeNestedObjects(DirectEncoder{Encoder: c.encoder, ObjectTyper: c.typer}); err != nil { + return err + } + } + + // Conversion is responsible for setting the proper group, version, and kind onto the outgoing object + err = c.encoder.Encode(out, w) + // restore the old GVK, in case conversion returned the same object + objectKind.SetGroupVersionKind(old) + return err +} + +// DirectEncoder serializes an object and ensures the GVK is set. +type DirectEncoder struct { + Version runtime.GroupVersioner + runtime.Encoder + runtime.ObjectTyper +} + +// Encode does not do conversion. It sets the gvk during serialization. +func (e DirectEncoder) Encode(obj runtime.Object, stream io.Writer) error { + gvks, _, err := e.ObjectTyper.ObjectKinds(obj) + if err != nil { + if runtime.IsNotRegisteredError(err) { + return e.Encoder.Encode(obj, stream) + } + return err + } + kind := obj.GetObjectKind() + oldGVK := kind.GroupVersionKind() + gvk := gvks[0] + if e.Version != nil { + preferredGVK, ok := e.Version.KindForGroupVersionKinds(gvks) + if ok { + gvk = preferredGVK + } + } + kind.SetGroupVersionKind(gvk) + err = e.Encoder.Encode(obj, stream) + kind.SetGroupVersionKind(oldGVK) + return err +} + +// DirectDecoder clears the group version kind of a deserialized object. +type DirectDecoder struct { + runtime.Decoder +} + +// Decode does not do conversion. It removes the gvk during deserialization. +func (d DirectDecoder) Decode(data []byte, defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) { + obj, gvk, err := d.Decoder.Decode(data, defaults, into) + if obj != nil { + kind := obj.GetObjectKind() + // clearing the gvk is just a convention of a codec + kind.SetGroupVersionKind(schema.GroupVersionKind{}) + } + return obj, gvk, err +} |