mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 21:47:07 +00:00
Merge pull request #25540 from krousey/discovery_mapper
Automatic merge from submit-queue Adding support objects for integrating dynamic client the kubectl builder Kubectl will try to decode into `runtime.VersionedObjects`, so the `UnstructuredJSONScheme` needs to handle that intelligently. Kubectl's builder also needs a `meta.RESTMapper` and `runtime.Typer`. The `meta.RESTMapper` requires a `runtime.ObjectConvertor` (spelling?) that works with `runtime.Unstructured`. The mapper and typer required discovery info, so I just put that in the kubectl util package since it didn't really seem to fit anywhere else. Subsequent PRs will be using these in kubectl. cc @kubernetes/sig-api-machinery @smarterclayton @liggitt @lavalamp
This commit is contained in:
commit
c05c1d24ae
119
pkg/client/typed/dynamic/dynamic_util.go
Normal file
119
pkg/client/typed/dynamic/dynamic_util.go
Normal file
@ -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]
|
||||||
|
}
|
78
pkg/client/typed/dynamic/dynamic_util_test.go
Normal file
78
pkg/client/typed/dynamic/dynamic_util_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,8 @@ package runtime
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
gojson "encoding/json"
|
gojson "encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -99,6 +101,13 @@ func (s unstructuredJSONScheme) decodeInto(data []byte, obj Object) error {
|
|||||||
return s.decodeToUnstructured(data, x)
|
return s.decodeToUnstructured(data, x)
|
||||||
case *UnstructuredList:
|
case *UnstructuredList:
|
||||||
return s.decodeToList(data, x)
|
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:
|
default:
|
||||||
return json.Unmarshal(data, x)
|
return json.Unmarshal(data, x)
|
||||||
}
|
}
|
||||||
@ -152,3 +161,39 @@ func (s unstructuredJSONScheme) decodeToList(data []byte, list *UnstructuredList
|
|||||||
}
|
}
|
||||||
return nil
|
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")
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user