Skip to content

Commit

Permalink
Replace automatic YAML decoding with opt-in YAML decoding
Browse files Browse the repository at this point in the history
Base codecs no longer automically handle YAML.  Instead, clients
must convert to JSON first via yaml.ToJSON and runtime.YAMLDecoder.
  • Loading branch information
smarterclayton committed Mar 20, 2015
1 parent 71abc99 commit 6918a4d
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 40 deletions.
18 changes: 16 additions & 2 deletions examples/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/yaml"
"github.com/golang/glog"
)

Expand Down Expand Up @@ -88,6 +89,15 @@ func walkJSONFiles(inDir string, fn func(name, path string, data []byte)) error
return err
}
name := strings.TrimSuffix(file, ext)

if ext == ".yaml" {
out, err := yaml.ToJSON(data)
if err != nil {
return err
}
data = out
}

fn(name, path, data)
}
return nil
Expand Down Expand Up @@ -215,14 +225,18 @@ func TestReadme(t *testing.T) {

//t.Logf("testing (%s): \n%s", subtype, content)
expectedType := &api.Pod{}
if err := latest.Codec.DecodeInto([]byte(content), expectedType); err != nil {
json, err := yaml.ToJSON([]byte(content))
if err != nil {
t.Errorf("%s could not be converted to JSON: %v\n%s", path, err, string(content))
}
if err := latest.Codec.DecodeInto(json, expectedType); err != nil {
t.Errorf("%s did not decode correctly: %v\n%s", path, err, string(content))
continue
}
if errors := validateObject(expectedType); len(errors) > 0 {
t.Errorf("%s did not validate correctly: %v", path, errors)
}
_, err := latest.Codec.Encode(expectedType)
_, err = latest.Codec.Encode(expectedType)
if err != nil {
t.Errorf("Could not encode object: %v", err)
continue
Expand Down
16 changes: 10 additions & 6 deletions pkg/api/validation/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ import (
"reflect"
"strings"

"github.com/GoogleCloudPlatform/kubernetes/pkg/util/yaml"
"github.com/emicklei/go-restful/swagger"
"github.com/golang/glog"
"gopkg.in/yaml.v2"
)

type InvalidTypeError struct {
Expand Down Expand Up @@ -65,11 +65,15 @@ func NewSwaggerSchemaFromBytes(data []byte) (Schema, error) {

func (s *SwaggerSchema) ValidateBytes(data []byte) error {
var obj interface{}
err := yaml.Unmarshal(data, &obj)
out, err := yaml.ToJSON(data)
if err != nil {
return err
}
fields := obj.(map[interface{}]interface{})
data = out
if err := json.Unmarshal(data, &obj); err != nil {
return err
}
fields := obj.(map[string]interface{})
apiVersion := fields["apiVersion"].(string)
kind := fields["kind"].(string)
return s.ValidateObject(obj, apiVersion, "", apiVersion+"."+kind)
Expand All @@ -84,12 +88,12 @@ func (s *SwaggerSchema) ValidateObject(obj interface{}, apiVersion, fieldName, t
return nil
}
properties := model.Properties
fields := obj.(map[interface{}]interface{})
fields := obj.(map[string]interface{})
if len(fieldName) > 0 {
fieldName = fieldName + "."
}
for key, value := range fields {
details, ok := properties[key.(string)]
details, ok := properties[key]
if !ok {
glog.V(2).Infof("couldn't find properties for %s, skipping", key)
continue
Expand All @@ -99,7 +103,7 @@ func (s *SwaggerSchema) ValidateObject(obj interface{}, apiVersion, fieldName, t
glog.V(2).Infof("Skipping nil field: %s", key)
continue
}
err := s.validateField(value, apiVersion, fieldName+key.(string), fieldType, &details)
err := s.validateField(value, apiVersion, fieldName+key, fieldType, &details)
if err != nil {
glog.Errorf("Validation failed for: %s, %v", key, value)
return err
Expand Down
3 changes: 2 additions & 1 deletion pkg/client/clientcmd/api/latest/latest.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package latest

import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd/api/v1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)

// Version is the string that represents the current external default version.
Expand All @@ -37,4 +38,4 @@ var Versions = []string{"v1"}
// the latest supported version. Use this Codec when writing to
// disk, a data store that is not dynamically versioned, or in tests.
// This codec can decode any object that Kubernetes is aware of.
var Codec = v1.Codec
var Codec = runtime.YAMLDecoder(v1.Codec)
29 changes: 8 additions & 21 deletions pkg/conversion/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,12 @@ limitations under the License.
package conversion

import (
"encoding/json"
"errors"
"fmt"

"github.com/ghodss/yaml"
)

// Decode converts a YAML or JSON string back into a pointer to an api object.
// Decode converts a JSON string back into a pointer to an api object.
// Deduces the type based upon the fields added by the MetaInsertionFactory
// technique. The object will be converted, if necessary, into the
// s.InternalVersion type before being returned. Decode will not decode
Expand All @@ -44,16 +43,12 @@ func (s *Scheme) Decode(data []byte) (interface{}, error) {
return nil, err
}

// yaml is a superset of json, so we use it to decode here. That way,
// we understand both.
err = yaml.Unmarshal(data, obj)
if err != nil {
if err := json.Unmarshal(data, obj); err != nil {
return nil, err
}

// Version and Kind should be blank in memory.
err = s.SetVersionAndKind("", "", obj)
if err != nil {
if err := s.SetVersionAndKind("", "", obj); err != nil {
return nil, err
}

Expand All @@ -63,25 +58,21 @@ func (s *Scheme) Decode(data []byte) (interface{}, error) {
if err != nil {
return nil, err
}
err = s.converter.Convert(obj, objOut, 0, s.generateConvertMeta(version, s.InternalVersion))
if err != nil {
if err := s.converter.Convert(obj, objOut, 0, s.generateConvertMeta(version, s.InternalVersion)); err != nil {
return nil, err
}
obj = objOut
}
return obj, nil
}

// DecodeInto parses a YAML or JSON string and stores it in obj. Returns an error
// DecodeInto parses a JSON string and stores it in obj. Returns an error
// if data.Kind is set and doesn't match the type of obj. Obj should be a
// pointer to an api type.
// If obj's version doesn't match that in data, an attempt will be made to convert
// data into obj's version.
func (s *Scheme) DecodeInto(data []byte, obj interface{}) error {
if len(data) == 0 {
// This is valid YAML, but it's a bad idea not to return an error
// for an empty string-- that's almost certainly not what the caller
// was expecting.
return errors.New("empty input")
}
dataVersion, dataKind, err := s.DataVersionAndKind(data)
Expand All @@ -107,14 +98,10 @@ func (s *Scheme) DecodeInto(data []byte, obj interface{}) error {
if err != nil {
return err
}
// yaml is a superset of json, so we use it to decode here. That way,
// we understand both.
err = yaml.Unmarshal(data, external)
if err != nil {
if err := json.Unmarshal(data, external); err != nil {
return err
}
err = s.converter.Convert(external, obj, 0, s.generateConvertMeta(dataVersion, objVersion))
if err != nil {
if err := s.converter.Convert(external, obj, 0, s.generateConvertMeta(dataVersion, objVersion)); err != nil {
return err
}

Expand Down
13 changes: 5 additions & 8 deletions pkg/conversion/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@ limitations under the License.
package conversion

import (
"encoding/json"
"fmt"
"reflect"

"github.com/ghodss/yaml"
)

// MetaFactory is used to store and retrieve the version and kind
Expand All @@ -33,13 +32,13 @@ type MetaFactory interface {
Interpret(data []byte) (version, kind string, err error)
}

// DefaultMetaFactory is a default factory for versioning objects in JSON/YAML. The object
// 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{KindField: "Kind", VersionField: "APIVersion"}

// SimpleMetaFactory provides default methods for retrieving the type and version of objects
// that are identified with an "apiVersion" and "kind" fields in their JSON/YAML
// 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 {
Expand All @@ -51,16 +50,14 @@ type SimpleMetaFactory struct {
BaseFields []string
}

// Interpret will return the APIVersion and Kind of the JSON/YAML wire-format
// Interpret will return the APIVersion and Kind of the JSON wire-format
// encoding of an object, or an error.
func (SimpleMetaFactory) Interpret(data []byte) (version, kind string, err error) {
findKind := struct {
APIVersion string `json:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"`
}{}
// yaml is a superset of json, so we use it to decode here. That way,
// we understand both.
err = yaml.Unmarshal(data, &findKind)
err = json.Unmarshal(data, &findKind)
if err != nil {
return "", "", fmt.Errorf("couldn't get version/kind: %v", err)
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/kubectl/resource/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/yaml"
)

// Mapper is a convenience struct for holding references to the three interfaces
Expand All @@ -36,6 +37,11 @@ type Mapper struct {
// if any of the decoding or client lookup steps fail. Name and namespace will be
// set into Info if the mapping's MetadataAccessor can retrieve them.
func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) {
json, err := yaml.ToJSON(data)
if err != nil {
return nil, fmt.Errorf("unable to parse %q: %v", err)
}
data = json
version, kind, err := m.DataVersionAndKind(data)
if err != nil {
return nil, fmt.Errorf("unable to get type info from %q: %v", source, err)
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 @@ -196,7 +196,7 @@ func testPrinter(t *testing.T, printer ResourcePrinter, unmarshalFunc func(data
}
// Use real decode function to undo the versioning process.
poutput = testStruct{}
err = testapi.Codec().DecodeInto(buf.Bytes(), &poutput)
err = runtime.YAMLDecoder(testapi.Codec()).DecodeInto(buf.Bytes(), &poutput)
if err != nil {
t.Fatal(err)
}
Expand All @@ -217,7 +217,7 @@ func testPrinter(t *testing.T, printer ResourcePrinter, unmarshalFunc func(data
}
// Use real decode function to undo the versioning process.
objOut = api.Pod{}
err = testapi.Codec().DecodeInto(buf.Bytes(), &objOut)
err = runtime.YAMLDecoder(testapi.Codec()).DecodeInto(buf.Bytes(), &objOut)
if err != nil {
t.Fatal(err)
}
Expand Down

0 comments on commit 6918a4d

Please sign in to comment.