mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Merge pull request #30250 from krousey/kctl_dynamic
Automatic merge from submit-queue Change kubectl create to use dynamic client https://github.com/kubernetes/kubernetes/issues/16764 https://github.com/kubernetes/kubernetes/issues/3955 This is a series of changes to allow kubectl create to use discovery-based REST mapping and dynamic clients. cc @kubernetes/sig-api-machinery **Release note**: <!-- Steps to write your release note: 1. Use the release-note-* labels to set the release note state (if you have access) 2. Enter your extended release note in the below block; leaving it blank means using the PR title as the release note. If no release note is required, just write `NONE`. --> ```release-note kubectl will no longer do client-side defaulting on create and replace. ```
This commit is contained in:
commit
4b5fd43e24
31
pkg/api/meta/unstructured.go
Normal file
31
pkg/api/meta/unstructured.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
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 meta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InterfacesForUnstructured returns VersionInterfaces suitable for
|
||||||
|
// dealing with runtime.Unstructured objects.
|
||||||
|
func InterfacesForUnstructured(unversioned.GroupVersion) (*VersionInterfaces, error) {
|
||||||
|
return &VersionInterfaces{
|
||||||
|
ObjectConvertor: &runtime.UnstructuredObjectConverter{},
|
||||||
|
MetadataAccessor: NewAccessor(),
|
||||||
|
}, nil
|
||||||
|
}
|
@ -80,6 +80,8 @@ func NewRESTMapper(groupResources []*APIGroupResources, versionInterfaces meta.V
|
|||||||
// TODO only do this if it supports listing
|
// TODO only do this if it supports listing
|
||||||
versionMapper.Add(gv.WithKind(resource.Kind+"List"), scope)
|
versionMapper.Add(gv.WithKind(resource.Kind+"List"), scope)
|
||||||
}
|
}
|
||||||
|
// TODO why is this type not in discovery (at least for "v1")
|
||||||
|
versionMapper.Add(gv.WithKind("List"), meta.RESTScopeRoot)
|
||||||
unionMapper = append(unionMapper, versionMapper)
|
unionMapper = append(unionMapper, versionMapper)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
95
pkg/client/typed/discovery/unstructured.go
Normal file
95
pkg/client/typed/discovery/unstructured.go
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
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 discovery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnstructuredObjectTyper provides a runtime.ObjectTyper implmentation for
|
||||||
|
// runtime.Unstructured object based on discovery information.
|
||||||
|
type UnstructuredObjectTyper struct {
|
||||||
|
registered map[unversioned.GroupVersionKind]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUnstructuredObjectTyper returns a runtime.ObjectTyper for
|
||||||
|
// unstructred objects based on discovery information.
|
||||||
|
func NewUnstructuredObjectTyper(groupResources []*APIGroupResources) *UnstructuredObjectTyper {
|
||||||
|
dot := &UnstructuredObjectTyper{registered: make(map[unversioned.GroupVersionKind]bool)}
|
||||||
|
for _, group := range groupResources {
|
||||||
|
for _, discoveryVersion := range group.Group.Versions {
|
||||||
|
resources, ok := group.VersionedResources[discoveryVersion.Version]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
gv := unversioned.GroupVersion{Group: group.Group.Name, Version: discoveryVersion.Version}
|
||||||
|
for _, resource := range resources {
|
||||||
|
dot.registered[gv.WithKind(resource.Kind)] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dot
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (d *UnstructuredObjectTyper) 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. unversionedType will always be false
|
||||||
|
// because runtime.Unstructured object should always have group,version,kind
|
||||||
|
// information set.
|
||||||
|
func (d *UnstructuredObjectTyper) ObjectKinds(obj runtime.Object) (gvks []unversioned.GroupVersionKind, unversionedType bool, err error) {
|
||||||
|
gvk, err := d.ObjectKind(obj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []unversioned.GroupVersionKind{gvk}, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recognizes returns true if the provided group,version,kind was in the
|
||||||
|
// discovery information.
|
||||||
|
func (d *UnstructuredObjectTyper) Recognizes(gvk unversioned.GroupVersionKind) bool {
|
||||||
|
return d.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 (d *UnstructuredObjectTyper) IsUnversioned(obj runtime.Object) (unversioned bool, ok bool) {
|
||||||
|
gvk, err := d.ObjectKind(obj)
|
||||||
|
if err != nil {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, d.registered[gvk]
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ runtime.ObjectTyper = &UnstructuredObjectTyper{}
|
@ -40,11 +40,7 @@ import (
|
|||||||
// Client is a Kubernetes client that allows you to access metadata
|
// Client is a Kubernetes client that allows you to access metadata
|
||||||
// and manipulate metadata of a Kubernetes API group.
|
// and manipulate metadata of a Kubernetes API group.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
cl *restclient.RESTClient
|
cl *restclient.RESTClient
|
||||||
}
|
|
||||||
|
|
||||||
type ClientWithParameterCodec struct {
|
|
||||||
client *Client
|
|
||||||
parameterCodec runtime.ParameterCodec
|
parameterCodec runtime.ParameterCodec
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,9 +51,12 @@ func NewClient(conf *restclient.Config) (*Client, error) {
|
|||||||
confCopy := *conf
|
confCopy := *conf
|
||||||
conf = &confCopy
|
conf = &confCopy
|
||||||
|
|
||||||
// TODO: it's questionable that this should be using anything other than unstructured schema and JSON
|
contentConfig := ContentConfig()
|
||||||
conf.ContentType = runtime.ContentTypeJSON
|
contentConfig.GroupVersion = conf.GroupVersion
|
||||||
conf.AcceptContentTypes = runtime.ContentTypeJSON
|
if conf.NegotiatedSerializer != nil {
|
||||||
|
contentConfig.NegotiatedSerializer = conf.NegotiatedSerializer
|
||||||
|
}
|
||||||
|
conf.ContentConfig = contentConfig
|
||||||
|
|
||||||
if conf.APIPath == "" {
|
if conf.APIPath == "" {
|
||||||
conf.APIPath = "/api"
|
conf.APIPath = "/api"
|
||||||
@ -66,10 +65,6 @@ func NewClient(conf *restclient.Config) (*Client, error) {
|
|||||||
if len(conf.UserAgent) == 0 {
|
if len(conf.UserAgent) == 0 {
|
||||||
conf.UserAgent = restclient.DefaultKubernetesUserAgent()
|
conf.UserAgent = restclient.DefaultKubernetesUserAgent()
|
||||||
}
|
}
|
||||||
if conf.NegotiatedSerializer == nil {
|
|
||||||
streamingInfo, _ := api.Codecs.StreamingSerializerForMediaType("application/json;stream=watch", nil)
|
|
||||||
conf.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: dynamicCodec{}}, streamingInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
cl, err := restclient.RESTClientFor(conf)
|
cl, err := restclient.RESTClientFor(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -86,35 +81,24 @@ func (c *Client) GetRateLimiter() flowcontrol.RateLimiter {
|
|||||||
|
|
||||||
// Resource returns an API interface to the specified resource for this client's
|
// Resource returns an API interface to the specified resource for this client's
|
||||||
// group and version. If resource is not a namespaced resource, then namespace
|
// group and version. If resource is not a namespaced resource, then namespace
|
||||||
// is ignored.
|
// is ignored. The ResourceClient inherits the parameter codec of c.
|
||||||
func (c *Client) Resource(resource *unversioned.APIResource, namespace string) *ResourceClient {
|
func (c *Client) Resource(resource *unversioned.APIResource, namespace string) *ResourceClient {
|
||||||
return &ResourceClient{
|
return &ResourceClient{
|
||||||
cl: c.cl,
|
cl: c.cl,
|
||||||
resource: resource,
|
|
||||||
ns: namespace,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParameterCodec wraps a parameterCodec around the Client.
|
|
||||||
func (c *Client) ParameterCodec(parameterCodec runtime.ParameterCodec) *ClientWithParameterCodec {
|
|
||||||
return &ClientWithParameterCodec{
|
|
||||||
client: c,
|
|
||||||
parameterCodec: parameterCodec,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resource returns an API interface to the specified resource for this client's
|
|
||||||
// group and version. If resource is not a namespaced resource, then namespace
|
|
||||||
// is ignored. The ResourceClient inherits the parameter codec of c.
|
|
||||||
func (c *ClientWithParameterCodec) Resource(resource *unversioned.APIResource, namespace string) *ResourceClient {
|
|
||||||
return &ResourceClient{
|
|
||||||
cl: c.client.cl,
|
|
||||||
resource: resource,
|
resource: resource,
|
||||||
ns: namespace,
|
ns: namespace,
|
||||||
parameterCodec: c.parameterCodec,
|
parameterCodec: c.parameterCodec,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParameterCodec returns a client with the provided parameter codec.
|
||||||
|
func (c *Client) ParameterCodec(parameterCodec runtime.ParameterCodec) *Client {
|
||||||
|
return &Client{
|
||||||
|
cl: c.cl,
|
||||||
|
parameterCodec: parameterCodec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ResourceClient is an API interface to a specific resource under a
|
// ResourceClient is an API interface to a specific resource under a
|
||||||
// dynamic client.
|
// dynamic client.
|
||||||
type ResourceClient struct {
|
type ResourceClient struct {
|
||||||
@ -255,6 +239,18 @@ func (dynamicCodec) Encode(obj runtime.Object, w io.Writer) error {
|
|||||||
return runtime.UnstructuredJSONScheme.Encode(obj, w)
|
return runtime.UnstructuredJSONScheme.Encode(obj, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ContentConfig returns a restclient.ContentConfig for dynamic types.
|
||||||
|
func ContentConfig() restclient.ContentConfig {
|
||||||
|
// TODO: it's questionable that this should be using anything other than unstructured schema and JSON
|
||||||
|
codec := dynamicCodec{}
|
||||||
|
streamingInfo, _ := api.Codecs.StreamingSerializerForMediaType("application/json;stream=watch", nil)
|
||||||
|
return restclient.ContentConfig{
|
||||||
|
AcceptContentTypes: runtime.ContentTypeJSON,
|
||||||
|
ContentType: runtime.ContentTypeJSON,
|
||||||
|
NegotiatedSerializer: serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec}, streamingInfo),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// paramaterCodec is a codec converts an API object to query
|
// paramaterCodec is a codec converts an API object to query
|
||||||
// parameters without trying to convert to the target version.
|
// parameters without trying to convert to the target version.
|
||||||
type parameterCodec struct{}
|
type parameterCodec struct{}
|
||||||
|
@ -34,6 +34,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
"k8s.io/kubernetes/pkg/api/validation"
|
"k8s.io/kubernetes/pkg/api/validation"
|
||||||
"k8s.io/kubernetes/pkg/client/restclient"
|
"k8s.io/kubernetes/pkg/client/restclient"
|
||||||
|
"k8s.io/kubernetes/pkg/client/typed/discovery"
|
||||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||||
"k8s.io/kubernetes/pkg/client/unversioned/fake"
|
"k8s.io/kubernetes/pkg/client/unversioned/fake"
|
||||||
"k8s.io/kubernetes/pkg/kubectl"
|
"k8s.io/kubernetes/pkg/kubectl"
|
||||||
@ -279,6 +280,13 @@ func NewAPIFactory() (*cmdutil.Factory, *testFactory, runtime.Codec, runtime.Neg
|
|||||||
Object: func(discovery bool) (meta.RESTMapper, runtime.ObjectTyper) {
|
Object: func(discovery bool) (meta.RESTMapper, runtime.ObjectTyper) {
|
||||||
return testapi.Default.RESTMapper(), api.Scheme
|
return testapi.Default.RESTMapper(), api.Scheme
|
||||||
},
|
},
|
||||||
|
UnstructuredObject: func() (meta.RESTMapper, runtime.ObjectTyper, error) {
|
||||||
|
groupResources := testDynamicResources()
|
||||||
|
mapper := discovery.NewRESTMapper(groupResources, meta.InterfacesForUnstructured)
|
||||||
|
typer := discovery.NewUnstructuredObjectTyper(groupResources)
|
||||||
|
|
||||||
|
return kubectl.ShortcutExpander{RESTMapper: mapper}, typer, nil
|
||||||
|
},
|
||||||
Client: func() (*client.Client, error) {
|
Client: func() (*client.Client, error) {
|
||||||
// Swap out the HTTP client out of the client with the fake's version.
|
// Swap out the HTTP client out of the client with the fake's version.
|
||||||
fakeClient := t.Client.(*fake.RESTClient)
|
fakeClient := t.Client.(*fake.RESTClient)
|
||||||
@ -290,6 +298,9 @@ func NewAPIFactory() (*cmdutil.Factory, *testFactory, runtime.Codec, runtime.Neg
|
|||||||
ClientForMapping: func(*meta.RESTMapping) (resource.RESTClient, error) {
|
ClientForMapping: func(*meta.RESTMapping) (resource.RESTClient, error) {
|
||||||
return t.Client, t.Err
|
return t.Client, t.Err
|
||||||
},
|
},
|
||||||
|
UnstructuredClientForMapping: func(*meta.RESTMapping) (resource.RESTClient, error) {
|
||||||
|
return t.Client, t.Err
|
||||||
|
},
|
||||||
Decoder: func(bool) runtime.Decoder {
|
Decoder: func(bool) runtime.Decoder {
|
||||||
return testapi.Default.Codec()
|
return testapi.Default.Codec()
|
||||||
},
|
},
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/kubectl"
|
"k8s.io/kubernetes/pkg/kubectl"
|
||||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/resource"
|
"k8s.io/kubernetes/pkg/kubectl/resource"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
|
// CreateOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
|
||||||
@ -107,8 +108,11 @@ func RunCreate(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *C
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
|
mapper, typer, err := f.UnstructuredObject()
|
||||||
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), runtime.UnstructuredJSONScheme).
|
||||||
Schema(schema).
|
Schema(schema).
|
||||||
ContinueOnError().
|
ContinueOnError().
|
||||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/client/typed/dynamic"
|
||||||
"k8s.io/kubernetes/pkg/client/unversioned/fake"
|
"k8s.io/kubernetes/pkg/client/unversioned/fake"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,14 +41,15 @@ func TestCreateObject(t *testing.T) {
|
|||||||
_, _, rc := testData()
|
_, _, rc := testData()
|
||||||
rc.Items[0].Name = "redis-master-controller"
|
rc.Items[0].Name = "redis-master-controller"
|
||||||
|
|
||||||
f, tf, codec, ns := NewAPIFactory()
|
f, tf, codec, _ := NewAPIFactory()
|
||||||
|
ns := dynamic.ContentConfig().NegotiatedSerializer
|
||||||
tf.Printer = &testPrinter{}
|
tf.Printer = &testPrinter{}
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/namespaces/test/replicationcontrollers" && m == "POST":
|
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
|
||||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -72,16 +74,17 @@ func TestCreateMultipleObject(t *testing.T) {
|
|||||||
initTestErrorHandler(t)
|
initTestErrorHandler(t)
|
||||||
_, svc, rc := testData()
|
_, svc, rc := testData()
|
||||||
|
|
||||||
f, tf, codec, ns := NewAPIFactory()
|
f, tf, codec, _ := NewAPIFactory()
|
||||||
|
ns := dynamic.ContentConfig().NegotiatedSerializer
|
||||||
tf.Printer = &testPrinter{}
|
tf.Printer = &testPrinter{}
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/namespaces/test/services" && m == "POST":
|
case p == "/namespaces/test/services" && m == http.MethodPost:
|
||||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
|
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
|
||||||
case p == "/namespaces/test/replicationcontrollers" && m == "POST":
|
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
|
||||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -108,14 +111,15 @@ func TestCreateDirectory(t *testing.T) {
|
|||||||
_, _, rc := testData()
|
_, _, rc := testData()
|
||||||
rc.Items[0].Name = "name"
|
rc.Items[0].Name = "name"
|
||||||
|
|
||||||
f, tf, codec, ns := NewAPIFactory()
|
f, tf, codec, _ := NewAPIFactory()
|
||||||
|
ns := dynamic.ContentConfig().NegotiatedSerializer
|
||||||
tf.Printer = &testPrinter{}
|
tf.Printer = &testPrinter{}
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/namespaces/test/replicationcontrollers" && m == "POST":
|
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
|
||||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -33,6 +33,7 @@ import (
|
|||||||
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
"k8s.io/kubernetes/pkg/client/restclient"
|
"k8s.io/kubernetes/pkg/client/restclient"
|
||||||
|
"k8s.io/kubernetes/pkg/client/typed/discovery"
|
||||||
"k8s.io/kubernetes/pkg/client/unversioned/fake"
|
"k8s.io/kubernetes/pkg/client/unversioned/fake"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
"k8s.io/kubernetes/pkg/runtime/serializer"
|
"k8s.io/kubernetes/pkg/runtime/serializer"
|
||||||
@ -89,6 +90,26 @@ func testData() (*api.PodList, *api.ServiceList, *api.ReplicationControllerList)
|
|||||||
return pods, svc, rc
|
return pods, svc, rc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testDynamicResources() []*discovery.APIGroupResources {
|
||||||
|
return []*discovery.APIGroupResources{
|
||||||
|
{
|
||||||
|
Group: unversioned.APIGroup{
|
||||||
|
Versions: []unversioned.GroupVersionForDiscovery{
|
||||||
|
{Version: "v1"},
|
||||||
|
},
|
||||||
|
PreferredVersion: unversioned.GroupVersionForDiscovery{Version: "v1"},
|
||||||
|
},
|
||||||
|
VersionedResources: map[string][]unversioned.APIResource{
|
||||||
|
"v1": {
|
||||||
|
{Name: "pods", Namespaced: true, Kind: "Pod"},
|
||||||
|
{Name: "services", Namespaced: true, Kind: "Service"},
|
||||||
|
{Name: "replicationcontrollers", Namespaced: true, Kind: "ReplicationController"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testComponentStatusData() *api.ComponentStatusList {
|
func testComponentStatusData() *api.ComponentStatusList {
|
||||||
good := api.ComponentStatus{
|
good := api.ComponentStatus{
|
||||||
Conditions: []api.ComponentCondition{
|
Conditions: []api.ComponentCondition{
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/kubectl"
|
"k8s.io/kubernetes/pkg/kubectl"
|
||||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||||
"k8s.io/kubernetes/pkg/kubectl/resource"
|
"k8s.io/kubernetes/pkg/kubectl/resource"
|
||||||
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReplaceOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
|
// ReplaceOptions is the start of the data required to perform the operation. As new fields are added, add them here instead of
|
||||||
@ -187,8 +188,11 @@ func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
|
mapper, typer, err := f.UnstructuredObject()
|
||||||
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), runtime.UnstructuredJSONScheme).
|
||||||
ContinueOnError().
|
ContinueOnError().
|
||||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||||
FilenameParam(enforceNamespace, options.Recursive, options.Filenames...).
|
FilenameParam(enforceNamespace, options.Recursive, options.Filenames...).
|
||||||
@ -212,7 +216,7 @@ func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
r = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
|
r = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), runtime.UnstructuredJSONScheme).
|
||||||
Schema(schema).
|
Schema(schema).
|
||||||
ContinueOnError().
|
ContinueOnError().
|
||||||
NamespaceParam(cmdNamespace).DefaultNamespace().
|
NamespaceParam(cmdNamespace).DefaultNamespace().
|
||||||
|
@ -22,22 +22,24 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/client/typed/dynamic"
|
||||||
"k8s.io/kubernetes/pkg/client/unversioned/fake"
|
"k8s.io/kubernetes/pkg/client/unversioned/fake"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestReplaceObject(t *testing.T) {
|
func TestReplaceObject(t *testing.T) {
|
||||||
_, _, rc := testData()
|
_, _, rc := testData()
|
||||||
|
|
||||||
f, tf, codec, ns := NewAPIFactory()
|
f, tf, codec, _ := NewAPIFactory()
|
||||||
|
ns := dynamic.ContentConfig().NegotiatedSerializer
|
||||||
tf.Printer = &testPrinter{}
|
tf.Printer = &testPrinter{}
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/namespaces/test/replicationcontrollers/redis-master" && (m == "GET" || m == "PUT" || m == "DELETE"):
|
case p == "/namespaces/test/replicationcontrollers/redis-master" && (m == http.MethodGet || m == http.MethodPut || m == http.MethodDelete):
|
||||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||||
case p == "/namespaces/test/replicationcontrollers" && m == "POST":
|
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
|
||||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -71,20 +73,21 @@ func TestReplaceObject(t *testing.T) {
|
|||||||
func TestReplaceMultipleObject(t *testing.T) {
|
func TestReplaceMultipleObject(t *testing.T) {
|
||||||
_, svc, rc := testData()
|
_, svc, rc := testData()
|
||||||
|
|
||||||
f, tf, codec, ns := NewAPIFactory()
|
f, tf, codec, _ := NewAPIFactory()
|
||||||
|
ns := dynamic.ContentConfig().NegotiatedSerializer
|
||||||
tf.Printer = &testPrinter{}
|
tf.Printer = &testPrinter{}
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/namespaces/test/replicationcontrollers/redis-master" && (m == "GET" || m == "PUT" || m == "DELETE"):
|
case p == "/namespaces/test/replicationcontrollers/redis-master" && (m == http.MethodGet || m == http.MethodPut || m == http.MethodDelete):
|
||||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||||
case p == "/namespaces/test/replicationcontrollers" && m == "POST":
|
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
|
||||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||||
case p == "/namespaces/test/services/frontend" && (m == "GET" || m == "PUT" || m == "DELETE"):
|
case p == "/namespaces/test/services/frontend" && (m == http.MethodGet || m == http.MethodPut || m == http.MethodDelete):
|
||||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
|
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
|
||||||
case p == "/namespaces/test/services" && m == "POST":
|
case p == "/namespaces/test/services" && m == http.MethodPost:
|
||||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
|
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &svc.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -118,16 +121,17 @@ func TestReplaceMultipleObject(t *testing.T) {
|
|||||||
func TestReplaceDirectory(t *testing.T) {
|
func TestReplaceDirectory(t *testing.T) {
|
||||||
_, _, rc := testData()
|
_, _, rc := testData()
|
||||||
|
|
||||||
f, tf, codec, ns := NewAPIFactory()
|
f, tf, codec, _ := NewAPIFactory()
|
||||||
|
ns := dynamic.ContentConfig().NegotiatedSerializer
|
||||||
tf.Printer = &testPrinter{}
|
tf.Printer = &testPrinter{}
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && (m == "GET" || m == "PUT" || m == "DELETE"):
|
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers/") && (m == http.MethodGet || m == http.MethodPut || m == http.MethodDelete):
|
||||||
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
return &http.Response{StatusCode: http.StatusOK, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||||
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers") && m == "POST":
|
case strings.HasPrefix(p, "/namespaces/test/replicationcontrollers") && m == http.MethodPost:
|
||||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -161,16 +165,17 @@ func TestReplaceDirectory(t *testing.T) {
|
|||||||
func TestForceReplaceObjectNotFound(t *testing.T) {
|
func TestForceReplaceObjectNotFound(t *testing.T) {
|
||||||
_, _, rc := testData()
|
_, _, rc := testData()
|
||||||
|
|
||||||
f, tf, codec, ns := NewAPIFactory()
|
f, tf, codec, _ := NewAPIFactory()
|
||||||
|
ns := dynamic.ContentConfig().NegotiatedSerializer
|
||||||
tf.Printer = &testPrinter{}
|
tf.Printer = &testPrinter{}
|
||||||
tf.Client = &fake.RESTClient{
|
tf.Client = &fake.RESTClient{
|
||||||
NegotiatedSerializer: ns,
|
NegotiatedSerializer: ns,
|
||||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||||
switch p, m := req.URL.Path, req.Method; {
|
switch p, m := req.URL.Path, req.Method; {
|
||||||
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "DELETE":
|
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == http.MethodDelete:
|
||||||
return &http.Response{StatusCode: 404, Header: defaultHeader(), Body: stringBody("")}, nil
|
return &http.Response{StatusCode: http.StatusNotFound, Header: defaultHeader(), Body: stringBody("")}, nil
|
||||||
case p == "/namespaces/test/replicationcontrollers" && m == "POST":
|
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
|
||||||
return &http.Response{StatusCode: 201, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
return &http.Response{StatusCode: http.StatusCreated, Header: defaultHeader(), Body: objBody(codec, &rc.Items[0])}, nil
|
||||||
default:
|
default:
|
||||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -53,6 +53,8 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/apis/policy"
|
"k8s.io/kubernetes/pkg/apis/policy"
|
||||||
"k8s.io/kubernetes/pkg/apis/rbac"
|
"k8s.io/kubernetes/pkg/apis/rbac"
|
||||||
"k8s.io/kubernetes/pkg/client/restclient"
|
"k8s.io/kubernetes/pkg/client/restclient"
|
||||||
|
"k8s.io/kubernetes/pkg/client/typed/discovery"
|
||||||
|
"k8s.io/kubernetes/pkg/client/typed/dynamic"
|
||||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||||
clientset "k8s.io/kubernetes/pkg/client/unversioned/adapters/internalclientset"
|
clientset "k8s.io/kubernetes/pkg/client/unversioned/adapters/internalclientset"
|
||||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||||
@ -83,6 +85,9 @@ type Factory struct {
|
|||||||
// Returns interfaces for dealing with arbitrary runtime.Objects. If thirdPartyDiscovery is true, performs API calls
|
// Returns interfaces for dealing with arbitrary runtime.Objects. If thirdPartyDiscovery is true, performs API calls
|
||||||
// to discovery dynamic API objects registered by third parties.
|
// to discovery dynamic API objects registered by third parties.
|
||||||
Object func(thirdPartyDiscovery bool) (meta.RESTMapper, runtime.ObjectTyper)
|
Object func(thirdPartyDiscovery bool) (meta.RESTMapper, runtime.ObjectTyper)
|
||||||
|
// Returns interfaces for dealing with arbitrary
|
||||||
|
// runtime.Unstructured. This performs API calls to discover types.
|
||||||
|
UnstructuredObject func() (meta.RESTMapper, runtime.ObjectTyper, error)
|
||||||
// Returns interfaces for decoding objects - if toInternal is set, decoded objects will be converted
|
// Returns interfaces for decoding objects - if toInternal is set, decoded objects will be converted
|
||||||
// into their internal form (if possible). Eventually the internal form will be removed as an option,
|
// into their internal form (if possible). Eventually the internal form will be removed as an option,
|
||||||
// and only versioned objects will be returned.
|
// and only versioned objects will be returned.
|
||||||
@ -96,6 +101,8 @@ type Factory struct {
|
|||||||
// Returns a RESTClient for working with the specified RESTMapping or an error. This is intended
|
// Returns a RESTClient for working with the specified RESTMapping or an error. This is intended
|
||||||
// for working with arbitrary resources and is not guaranteed to point to a Kubernetes APIServer.
|
// for working with arbitrary resources and is not guaranteed to point to a Kubernetes APIServer.
|
||||||
ClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
|
ClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
|
||||||
|
// Returns a RESTClient for working with Unstructured objects.
|
||||||
|
UnstructuredClientForMapping func(mapping *meta.RESTMapping) (resource.RESTClient, error)
|
||||||
// Returns a Describer for displaying the specified RESTMapping type or an error.
|
// Returns a Describer for displaying the specified RESTMapping type or an error.
|
||||||
Describer func(mapping *meta.RESTMapping) (kubectl.Describer, error)
|
Describer func(mapping *meta.RESTMapping) (kubectl.Describer, error)
|
||||||
// Returns a Printer for formatting objects of the given type or an error.
|
// Returns a Printer for formatting objects of the given type or an error.
|
||||||
@ -360,6 +367,40 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
|
|||||||
}
|
}
|
||||||
return priorityRESTMapper, api.Scheme
|
return priorityRESTMapper, api.Scheme
|
||||||
},
|
},
|
||||||
|
UnstructuredObject: func() (meta.RESTMapper, runtime.ObjectTyper, error) {
|
||||||
|
cfg, err := clients.ClientConfigForVersion(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dc, err := discovery.NewDiscoveryClientForConfig(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
groupResources, err := discovery.GetAPIGroupResources(dc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register unknown APIs as third party for now to make
|
||||||
|
// validation happy. TODO perhaps make a dynamic schema
|
||||||
|
// validator to avoid this.
|
||||||
|
for _, group := range groupResources {
|
||||||
|
for _, version := range group.Group.Versions {
|
||||||
|
gv := unversioned.GroupVersion{Group: group.Group.Name, Version: version.Version}
|
||||||
|
if !registered.IsRegisteredVersion(gv) {
|
||||||
|
registered.AddThirdPartyAPIGroupVersions(gv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mapper := discovery.NewRESTMapper(groupResources, meta.InterfacesForUnstructured)
|
||||||
|
|
||||||
|
typer := discovery.NewUnstructuredObjectTyper(groupResources)
|
||||||
|
|
||||||
|
return kubectl.ShortcutExpander{RESTMapper: mapper}, typer, nil
|
||||||
|
},
|
||||||
Client: func() (*client.Client, error) {
|
Client: func() (*client.Client, error) {
|
||||||
return clients.ClientForVersion(nil)
|
return clients.ClientForVersion(nil)
|
||||||
},
|
},
|
||||||
@ -391,6 +432,23 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
|
|||||||
}
|
}
|
||||||
return restclient.RESTClientFor(cfg)
|
return restclient.RESTClientFor(cfg)
|
||||||
},
|
},
|
||||||
|
UnstructuredClientForMapping: func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
|
||||||
|
cfg, err := clientConfig.ClientConfig()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := restclient.SetKubernetesDefaults(cfg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cfg.APIPath = "/apis"
|
||||||
|
if mapping.GroupVersionKind.Group == api.GroupName {
|
||||||
|
cfg.APIPath = "/api"
|
||||||
|
}
|
||||||
|
gv := mapping.GroupVersionKind.GroupVersion()
|
||||||
|
cfg.ContentConfig = dynamic.ContentConfig()
|
||||||
|
cfg.GroupVersion = &gv
|
||||||
|
return restclient.RESTClientFor(cfg)
|
||||||
|
},
|
||||||
Describer: func(mapping *meta.RESTMapping) (kubectl.Describer, error) {
|
Describer: func(mapping *meta.RESTMapping) (kubectl.Describer, error) {
|
||||||
mappingVersion := mapping.GroupVersionKind.GroupVersion()
|
mappingVersion := mapping.GroupVersionKind.GroupVersion()
|
||||||
if mapping.GroupVersionKind.Group == federation.GroupName {
|
if mapping.GroupVersionKind.Group == federation.GroupName {
|
||||||
|
@ -22,7 +22,6 @@ import (
|
|||||||
|
|
||||||
"k8s.io/kubernetes/pkg/api/meta"
|
"k8s.io/kubernetes/pkg/api/meta"
|
||||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||||
"k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata"
|
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,20 +53,8 @@ func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to decode %q: %v", source, err)
|
return nil, fmt.Errorf("unable to decode %q: %v", source, err)
|
||||||
}
|
}
|
||||||
var obj runtime.Object
|
|
||||||
var versioned runtime.Object
|
obj, versioned := versions.Last(), versions.First()
|
||||||
if isThirdParty, gvkOut, err := thirdpartyresourcedata.IsThirdPartyObject(data, gvk); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if isThirdParty {
|
|
||||||
obj, err = runtime.Decode(thirdpartyresourcedata.NewDecoder(nil, gvkOut.Kind), data)
|
|
||||||
versioned = obj
|
|
||||||
gvk = gvkOut
|
|
||||||
} else {
|
|
||||||
obj, versioned = versions.Last(), versions.First()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to decode %q: %v [%v]", source, err, gvk)
|
|
||||||
}
|
|
||||||
mapping, err := m.RESTMapping(gvk.GroupKind(), gvk.Version)
|
mapping, err := m.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to recognize %q: %v", source, err)
|
return nil, fmt.Errorf("unable to recognize %q: %v", source, err)
|
||||||
|
@ -618,13 +618,7 @@ func RetrieveLatest(info *Info, err error) error {
|
|||||||
if info.Namespaced() && len(info.Namespace) == 0 {
|
if info.Namespaced() && len(info.Namespace) == 0 {
|
||||||
return fmt.Errorf("no namespace set on resource %s %q", info.Mapping.Resource, info.Name)
|
return fmt.Errorf("no namespace set on resource %s %q", info.Mapping.Resource, info.Name)
|
||||||
}
|
}
|
||||||
obj, err := NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name, info.Export)
|
return info.Get()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info.Object = obj
|
|
||||||
info.ResourceVersion, _ = info.Mapping.MetadataAccessor.ResourceVersion(obj)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RetrieveLazy updates the object if it has not been loaded yet.
|
// RetrieveLazy updates the object if it has not been loaded yet.
|
||||||
|
@ -269,35 +269,42 @@ func parseObject(data []byte) (map[string]interface{}, error) {
|
|||||||
return mapObj, nil
|
return mapObj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataDecoder) populate(data []byte) (runtime.Object, error) {
|
func (t *thirdPartyResourceDataDecoder) populate(data []byte) (runtime.Object, *unversioned.GroupVersionKind, error) {
|
||||||
mapObj, err := parseObject(data)
|
mapObj, err := parseObject(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return t.populateFromObject(mapObj, data)
|
return t.populateFromObject(mapObj, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *thirdPartyResourceDataDecoder) populateFromObject(mapObj map[string]interface{}, data []byte) (runtime.Object, error) {
|
func (t *thirdPartyResourceDataDecoder) populateFromObject(mapObj map[string]interface{}, data []byte) (runtime.Object, *unversioned.GroupVersionKind, error) {
|
||||||
typeMeta := unversioned.TypeMeta{}
|
typeMeta := unversioned.TypeMeta{}
|
||||||
if err := json.Unmarshal(data, &typeMeta); err != nil {
|
if err := json.Unmarshal(data, &typeMeta); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gv, err := unversioned.ParseGroupVersion(typeMeta.APIVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
gvk := gv.WithKind(typeMeta.Kind)
|
||||||
|
|
||||||
isList := strings.HasSuffix(typeMeta.Kind, "List")
|
isList := strings.HasSuffix(typeMeta.Kind, "List")
|
||||||
switch {
|
switch {
|
||||||
case !isList && (len(t.kind) == 0 || typeMeta.Kind == t.kind):
|
case !isList && (len(t.kind) == 0 || typeMeta.Kind == t.kind):
|
||||||
result := &extensions.ThirdPartyResourceData{}
|
result := &extensions.ThirdPartyResourceData{}
|
||||||
if err := t.populateResource(result, mapObj, data); err != nil {
|
if err := t.populateResource(result, mapObj, data); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, &gvk, nil
|
||||||
case isList && (len(t.kind) == 0 || typeMeta.Kind == t.kind+"List"):
|
case isList && (len(t.kind) == 0 || typeMeta.Kind == t.kind+"List"):
|
||||||
list := &extensions.ThirdPartyResourceDataList{}
|
list := &extensions.ThirdPartyResourceDataList{}
|
||||||
if err := t.populateListResource(list, mapObj); err != nil {
|
if err := t.populateListResource(list, mapObj); err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return list, nil
|
return list, &gvk, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unexpected kind: %s, expected %s", typeMeta.Kind, t.kind)
|
return nil, nil, fmt.Errorf("unexpected kind: %s, expected %s", typeMeta.Kind, t.kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,11 +366,7 @@ func (t *thirdPartyResourceDataDecoder) Decode(data []byte, gvk *unversioned.Gro
|
|||||||
return t.delegate.Decode(data, gvk, into)
|
return t.delegate.Decode(data, gvk, into)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
obj, err := t.populate(data)
|
return t.populate(data)
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return obj, gvk, nil
|
|
||||||
}
|
}
|
||||||
switch o := into.(type) {
|
switch o := into.(type) {
|
||||||
case *extensions.ThirdPartyResourceData:
|
case *extensions.ThirdPartyResourceData:
|
||||||
@ -377,14 +380,14 @@ func (t *thirdPartyResourceDataDecoder) Decode(data []byte, gvk *unversioned.Gro
|
|||||||
return t.delegate.Decode(data, gvk, into)
|
return t.delegate.Decode(data, gvk, into)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
obj, err := t.populate(data)
|
obj, outGVK, err := t.populate(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
o.Objects = []runtime.Object{
|
o.Objects = []runtime.Object{
|
||||||
obj,
|
obj,
|
||||||
}
|
}
|
||||||
return o, gvk, nil
|
return o, outGVK, nil
|
||||||
default:
|
default:
|
||||||
return t.delegate.Decode(data, gvk, into)
|
return t.delegate.Decode(data, gvk, into)
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
@ -138,6 +139,21 @@ type Unstructured struct {
|
|||||||
Object map[string]interface{}
|
Object map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON ensures that the unstructured object produces proper
|
||||||
|
// JSON when passed to Go's standard JSON library.
|
||||||
|
func (u *Unstructured) MarshalJSON() ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := UnstructuredJSONScheme.Encode(u, &buf)
|
||||||
|
return buf.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON ensures that the unstructured object properly decodes
|
||||||
|
// JSON when passed to Go's standard JSON library.
|
||||||
|
func (u *Unstructured) UnmarshalJSON(b []byte) error {
|
||||||
|
_, _, err := UnstructuredJSONScheme.Decode(b, nil, u)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func getNestedField(obj map[string]interface{}, fields ...string) interface{} {
|
func getNestedField(obj map[string]interface{}, fields ...string) interface{} {
|
||||||
var val interface{} = obj
|
var val interface{} = obj
|
||||||
for _, field := range fields {
|
for _, field := range fields {
|
||||||
@ -450,6 +466,21 @@ type UnstructuredList struct {
|
|||||||
Items []*Unstructured `json:"items"`
|
Items []*Unstructured `json:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalJSON ensures that the unstructured list object produces proper
|
||||||
|
// JSON when passed to Go's standard JSON library.
|
||||||
|
func (u *UnstructuredList) MarshalJSON() ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := UnstructuredJSONScheme.Encode(u, &buf)
|
||||||
|
return buf.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON ensures that the unstructured list object properly
|
||||||
|
// decodes JSON when passed to Go's standard JSON library.
|
||||||
|
func (u *UnstructuredList) UnmarshalJSON(b []byte) error {
|
||||||
|
_, _, err := UnstructuredJSONScheme.Decode(b, nil, u)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (u *UnstructuredList) setNestedField(value interface{}, fields ...string) {
|
func (u *UnstructuredList) setNestedField(value interface{}, fields ...string) {
|
||||||
if u.Object == nil {
|
if u.Object == nil {
|
||||||
u.Object = make(map[string]interface{})
|
u.Object = make(map[string]interface{})
|
||||||
|
@ -103,10 +103,9 @@ func (s unstructuredJSONScheme) decodeInto(data []byte, obj Object) error {
|
|||||||
case *UnstructuredList:
|
case *UnstructuredList:
|
||||||
return s.decodeToList(data, x)
|
return s.decodeToList(data, x)
|
||||||
case *VersionedObjects:
|
case *VersionedObjects:
|
||||||
u := new(Unstructured)
|
o, err := s.decode(data)
|
||||||
err := s.decodeToUnstructured(data, u)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
x.Objects = []Object{u}
|
x.Objects = []Object{o}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
default:
|
default:
|
||||||
|
Loading…
Reference in New Issue
Block a user