From 6cd2fa6b4254de6705973370d301ab5335dc47db Mon Sep 17 00:00:00 2001 From: Kris Date: Wed, 11 May 2016 15:36:08 -0700 Subject: [PATCH 1/3] Make unstructured scheme aware of VersionedObjects UnstructuredJSONScheme now handles decoding into VersionedObjects properly by decoding into Unstructured and putting it in the object list. --- pkg/runtime/unstructured.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/runtime/unstructured.go b/pkg/runtime/unstructured.go index 24fe1357ac3..0617ae303ed 100644 --- a/pkg/runtime/unstructured.go +++ b/pkg/runtime/unstructured.go @@ -98,6 +98,13 @@ func (s unstructuredJSONScheme) decodeInto(data []byte, obj Object) error { return s.decodeToUnstructured(data, x) case *UnstructuredList: return s.decodeToList(data, x) + case *VersionedObjects: + u := new(Unstructured) + err := s.decodeToUnstructured(data, u) + if err == nil { + x.Objects = []Object{u} + } + return err default: return json.Unmarshal(data, x) } From db515f47bd16f54aea40a70ee10b671ff3abb765 Mon Sep 17 00:00:00 2001 From: Kris Date: Wed, 11 May 2016 15:39:56 -0700 Subject: [PATCH 2/3] Provide a dumb ObjectConverter for Unstructured --- pkg/runtime/unstructured.go | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pkg/runtime/unstructured.go b/pkg/runtime/unstructured.go index 0617ae303ed..3b746c9a9ca 100644 --- a/pkg/runtime/unstructured.go +++ b/pkg/runtime/unstructured.go @@ -18,6 +18,8 @@ package runtime import ( gojson "encoding/json" + "errors" + "fmt" "io" "k8s.io/kubernetes/pkg/api/unversioned" @@ -146,3 +148,39 @@ func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList } return nil } + +// UnstructuredObjectConverter is an ObjectConverter for use with +// Unstructured objects. Since it has no schema or type information, +// it will only succeed for no-op conversions. This is provided as a +// sane implementation for APIs that require an object converter. +type UnstructuredObjectConverter struct{} + +func (UnstructuredObjectConverter) Convert(in, out interface{}) error { + unstructIn, ok := in.(*Unstructured) + if !ok { + return fmt.Errorf("input type %T in not valid for unstructured conversion", in) + } + + unstructOut, ok := out.(*Unstructured) + if !ok { + return fmt.Errorf("output type %T in not valid for unstructured conversion", out) + } + + // maybe deep copy the map? It is documented in the + // ObjectConverter interface that this function is not + // guaranteeed to not mutate the input. Or maybe set the input + // object to nil. + unstructOut.Object = unstructIn.Object + return nil +} + +func (UnstructuredObjectConverter) ConvertToVersion(in Object, outVersion unversioned.GroupVersion) (Object, error) { + if gvk := in.GetObjectKind().GroupVersionKind(); gvk.GroupVersion() != outVersion { + return nil, errors.New("unstructured converter cannot convert versions") + } + return in, nil +} + +func (UnstructuredObjectConverter) ConvertFieldLabel(version, kind, label, value string) (string, string, error) { + return "", "", errors.New("unstructured cannot convert field labels") +} From 925003b4bc943a43d1418bb43967fd88acba6b02 Mon Sep 17 00:00:00 2001 From: Kris Date: Thu, 12 May 2016 10:17:19 -0700 Subject: [PATCH 3/3] Add mapper and typer based on discovery info --- pkg/client/typed/dynamic/dynamic_util.go | 119 ++++++++++++++++++ pkg/client/typed/dynamic/dynamic_util_test.go | 78 ++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 pkg/client/typed/dynamic/dynamic_util.go create mode 100644 pkg/client/typed/dynamic/dynamic_util_test.go diff --git a/pkg/client/typed/dynamic/dynamic_util.go b/pkg/client/typed/dynamic/dynamic_util.go new file mode 100644 index 00000000000..bcd18b2c6d2 --- /dev/null +++ b/pkg/client/typed/dynamic/dynamic_util.go @@ -0,0 +1,119 @@ +/* +Copyright 2016 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 dynamic + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api/meta" + "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/runtime" +) + +// VersionInterfaces provides an object converter and metadata +// accessor appropriate for use with unstructured objects. +func VersionInterfaces(unversioned.GroupVersion) (*meta.VersionInterfaces, error) { + return &meta.VersionInterfaces{ + ObjectConvertor: &runtime.UnstructuredObjectConverter{}, + MetadataAccessor: meta.NewAccessor(), + }, nil +} + +// NewDiscoveryRESTMapper returns a RESTMapper based on discovery information. +func NewDiscoveryRESTMapper(resources []*unversioned.APIResourceList, versionFunc meta.VersionInterfacesFunc) (*meta.DefaultRESTMapper, error) { + rm := meta.NewDefaultRESTMapper(nil, versionFunc) + for _, resourceList := range resources { + gv, err := unversioned.ParseGroupVersion(resourceList.GroupVersion) + if err != nil { + return nil, err + } + + for _, resource := range resourceList.APIResources { + gvk := gv.WithKind(resource.Kind) + scope := meta.RESTScopeRoot + if resource.Namespaced { + scope = meta.RESTScopeNamespace + } + rm.Add(gvk, scope) + } + } + return rm, nil +} + +// ObjectTyper provides an ObjectTyper implmentation for +// runtime.Unstructured object based on discovery information. +type ObjectTyper struct { + registered map[unversioned.GroupVersionKind]bool +} + +// NewObjectTyper constructs an ObjectTyper from discovery information. +func NewObjectTyper(resources []*unversioned.APIResourceList) (runtime.ObjectTyper, error) { + ot := &ObjectTyper{registered: make(map[unversioned.GroupVersionKind]bool)} + for _, resourceList := range resources { + gv, err := unversioned.ParseGroupVersion(resourceList.GroupVersion) + if err != nil { + return nil, err + } + + for _, resource := range resourceList.APIResources { + ot.registered[gv.WithKind(resource.Kind)] = true + } + } + return ot, nil +} + +// ObjectKind returns the group,version,kind of the provided object, +// or an error if the object in not *runtime.Unstructured or has no +// group,version,kind information. +func (ot *ObjectTyper) ObjectKind(obj runtime.Object) (unversioned.GroupVersionKind, error) { + if _, ok := obj.(*runtime.Unstructured); !ok { + return unversioned.GroupVersionKind{}, fmt.Errorf("type %T is invalid for dynamic object typer", obj) + } + + return obj.GetObjectKind().GroupVersionKind(), nil +} + +// ObjectKinds returns a slice of one element with the +// group,version,kind of the provided object, or an error if the +// object is not *runtime.Unstructured or has no group,version,kind +// information. +func (ot *ObjectTyper) ObjectKinds(obj runtime.Object) ([]unversioned.GroupVersionKind, error) { + gvk, err := ot.ObjectKind(obj) + if err != nil { + return nil, err + } + + return []unversioned.GroupVersionKind{gvk}, nil +} + +// Recognizes returns true if the provided group,version,kind was in +// the discovery information. +func (ot *ObjectTyper) Recognizes(gvk unversioned.GroupVersionKind) bool { + return ot.registered[gvk] +} + +// IsUnversioned returns false always because *runtime.Unstructured +// objects should always have group,version,kind information set. ok +// will be true if the object's group,version,kind is registered. +func (ot *ObjectTyper) IsUnversioned(obj runtime.Object) (unversioned bool, ok bool) { + gvk, err := ot.ObjectKind(obj) + if err != nil { + return false, false + } + + return false, ot.registered[gvk] +} diff --git a/pkg/client/typed/dynamic/dynamic_util_test.go b/pkg/client/typed/dynamic/dynamic_util_test.go new file mode 100644 index 00000000000..c6c315a8a7e --- /dev/null +++ b/pkg/client/typed/dynamic/dynamic_util_test.go @@ -0,0 +1,78 @@ +/* +Copyright 2016 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 dynamic + +import ( + "testing" + + "k8s.io/kubernetes/pkg/api/unversioned" +) + +func TestDiscoveryRESTMapper(t *testing.T) { + resources := []*unversioned.APIResourceList{ + { + GroupVersion: "test/beta1", + APIResources: []unversioned.APIResource{ + { + Name: "test_kinds", + Namespaced: true, + Kind: "test_kind", + }, + }, + }, + } + + gvk := unversioned.GroupVersionKind{ + Group: "test", + Version: "beta1", + Kind: "test_kind", + } + + mapper, err := NewDiscoveryRESTMapper(resources, VersionInterfaces) + if err != nil { + t.Fatalf("unexpected error creating mapper: %s", err) + } + + for _, res := range []unversioned.GroupVersionResource{ + { + Group: "test", + Version: "beta1", + Resource: "test_kinds", + }, + { + Version: "beta1", + Resource: "test_kinds", + }, + { + Group: "test", + Resource: "test_kinds", + }, + { + Resource: "test_kinds", + }, + } { + got, err := mapper.KindFor(res) + if err != nil { + t.Errorf("KindFor(%#v) unexpected error: %s", res, err) + continue + } + + if got != gvk { + t.Errorf("KindFor(%#v) = %#v; want %#v", res, got, gvk) + } + } +}