From a57307a4291b07e551d910445db3a1f456033a8d Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Sat, 21 Jan 2017 16:17:40 -0500 Subject: [PATCH 01/13] bazel --- pkg/kubectl/BUILD | 1 + pkg/kubectl/cmd/BUILD | 1 + pkg/kubectl/cmd/util/BUILD | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/kubectl/BUILD b/pkg/kubectl/BUILD index ac87b25c25..1b7f7150f8 100644 --- a/pkg/kubectl/BUILD +++ b/pkg/kubectl/BUILD @@ -109,6 +109,7 @@ go_library( "//vendor:k8s.io/apimachinery/pkg/util/validation", "//vendor:k8s.io/apimachinery/pkg/util/wait", "//vendor:k8s.io/apimachinery/pkg/watch", + "//vendor:k8s.io/client-go/dynamic", "//vendor:k8s.io/client-go/rest", "//vendor:k8s.io/client-go/util/integer", "//vendor:k8s.io/client-go/util/jsonpath", diff --git a/pkg/kubectl/cmd/BUILD b/pkg/kubectl/cmd/BUILD index cf2cd2bae4..a1782ae9f9 100644 --- a/pkg/kubectl/cmd/BUILD +++ b/pkg/kubectl/cmd/BUILD @@ -113,6 +113,7 @@ go_library( "//vendor:k8s.io/apimachinery/pkg/runtime/schema", "//vendor:k8s.io/apimachinery/pkg/types", "//vendor:k8s.io/apimachinery/pkg/util/errors", + "//vendor:k8s.io/apimachinery/pkg/util/json", "//vendor:k8s.io/apimachinery/pkg/util/sets", "//vendor:k8s.io/apimachinery/pkg/util/strategicpatch", "//vendor:k8s.io/apimachinery/pkg/util/validation", diff --git a/pkg/kubectl/cmd/util/BUILD b/pkg/kubectl/cmd/util/BUILD index c95f814981..7f70692b42 100644 --- a/pkg/kubectl/cmd/util/BUILD +++ b/pkg/kubectl/cmd/util/BUILD @@ -38,7 +38,6 @@ go_library( "//pkg/controller:go_default_library", "//pkg/kubectl:go_default_library", "//pkg/kubectl/resource:go_default_library", - "//pkg/registry/extensions/thirdpartyresourcedata:go_default_library", "//pkg/util/exec:go_default_library", "//pkg/version:go_default_library", "//vendor:github.com/emicklei/go-restful/swagger", From a82c4481f30e5877edd614f6dc7a323b5e4caf67 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Sun, 22 Jan 2017 13:45:30 -0500 Subject: [PATCH 02/13] Distinguish between client and unstructuredclient in fake factory --- pkg/kubectl/cmd/create_test.go | 16 ++++++--------- pkg/kubectl/cmd/delete_test.go | 26 ++++++++++++------------ pkg/kubectl/cmd/get_test.go | 36 ++++++++++++++++----------------- pkg/kubectl/cmd/replace_test.go | 21 ++++++++----------- pkg/kubectl/cmd/testing/fake.go | 23 +++++++++++---------- 5 files changed, 57 insertions(+), 65 deletions(-) diff --git a/pkg/kubectl/cmd/create_test.go b/pkg/kubectl/cmd/create_test.go index 1cbf2fe881..b68670afcc 100644 --- a/pkg/kubectl/cmd/create_test.go +++ b/pkg/kubectl/cmd/create_test.go @@ -21,7 +21,6 @@ import ( "net/http" "testing" - "k8s.io/client-go/dynamic" "k8s.io/client-go/rest/fake" "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" @@ -45,11 +44,10 @@ func TestCreateObject(t *testing.T) { rc.Items[0].Name = "redis-master-controller" f, tf, codec, _ := cmdtesting.NewAPIFactory() - ns := dynamic.ContentConfig().NegotiatedSerializer tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost: @@ -80,11 +78,10 @@ func TestCreateMultipleObject(t *testing.T) { _, svc, rc := testData() f, tf, codec, _ := cmdtesting.NewAPIFactory() - ns := dynamic.ContentConfig().NegotiatedSerializer tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == "/namespaces/test/services" && m == http.MethodPost: @@ -119,11 +116,10 @@ func TestCreateDirectory(t *testing.T) { rc.Items[0].Name = "name" f, tf, codec, _ := cmdtesting.NewAPIFactory() - ns := dynamic.ContentConfig().NegotiatedSerializer tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost: diff --git a/pkg/kubectl/cmd/delete_test.go b/pkg/kubectl/cmd/delete_test.go index a63d5f1502..748fb96173 100644 --- a/pkg/kubectl/cmd/delete_test.go +++ b/pkg/kubectl/cmd/delete_test.go @@ -43,7 +43,7 @@ func TestDeleteObjectByTuple(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -76,7 +76,7 @@ func TestDeleteNamedObject(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -109,7 +109,7 @@ func TestDeleteObject(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -167,7 +167,7 @@ func TestDeleteObjectGraceZero(t *testing.T) { count := 0 f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -216,7 +216,7 @@ func TestDeleteObjectGraceZero(t *testing.T) { func TestDeleteObjectNotFound(t *testing.T) { f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -253,7 +253,7 @@ func TestDeleteObjectNotFound(t *testing.T) { func TestDeleteObjectIgnoreNotFound(t *testing.T) { f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -290,7 +290,7 @@ func TestDeleteAllNotFound(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -339,7 +339,7 @@ func TestDeleteAllIgnoreNotFound(t *testing.T) { notFoundError := &errors.NewNotFound(api.Resource("services"), "foo").ErrStatus tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -375,7 +375,7 @@ func TestDeleteMultipleObject(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -410,7 +410,7 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -454,7 +454,7 @@ func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) { _, svc, rc := testData() f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -492,7 +492,7 @@ func TestDeleteDirectory(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -524,7 +524,7 @@ func TestDeleteMultipleSelector(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { diff --git a/pkg/kubectl/cmd/get_test.go b/pkg/kubectl/cmd/get_test.go index 72c5c3ccc6..b917f27165 100644 --- a/pkg/kubectl/cmd/get_test.go +++ b/pkg/kubectl/cmd/get_test.go @@ -120,7 +120,7 @@ func TestGetUnknownSchemaObject(t *testing.T) { f, tf, _, _ := cmdtesting.NewAPIFactory() _, _, codec, _ := cmdtesting.NewTestFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))}, @@ -165,7 +165,7 @@ func TestGetSchemaObject(t *testing.T) { tf.Typer = api.Scheme codec := testapi.Default.Codec() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.ReplicationController{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})}, @@ -188,7 +188,7 @@ func TestGetObjects(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, @@ -232,7 +232,7 @@ func TestGetSortedObjects(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, @@ -291,7 +291,7 @@ func TestGetObjectsIdentifiedByFile(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, @@ -318,7 +318,7 @@ func TestGetListObjects(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, @@ -361,7 +361,7 @@ func TestGetAllListObjects(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, @@ -391,7 +391,7 @@ func TestGetListComponentStatus(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, statuses)}, @@ -420,7 +420,7 @@ func TestGetMultipleTypeObjects(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -459,7 +459,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -521,7 +521,7 @@ func TestGetMultipleTypeObjectsWithSelector(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -573,7 +573,7 @@ func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -610,7 +610,7 @@ func TestGetByNameForcesFlag(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, @@ -710,7 +710,7 @@ func TestWatchSelector(t *testing.T) { ResourceVersion: "10", }, } - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -752,7 +752,7 @@ func TestWatchResource(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -790,7 +790,7 @@ func TestWatchResourceIdentifiedByFile(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -829,7 +829,7 @@ func TestWatchOnlyResource(t *testing.T) { f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { @@ -873,7 +873,7 @@ func TestWatchOnlyList(t *testing.T) { ResourceVersion: "10", }, } - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { diff --git a/pkg/kubectl/cmd/replace_test.go b/pkg/kubectl/cmd/replace_test.go index 40c903b036..cb54fddf81 100644 --- a/pkg/kubectl/cmd/replace_test.go +++ b/pkg/kubectl/cmd/replace_test.go @@ -22,7 +22,6 @@ import ( "strings" "testing" - "k8s.io/client-go/dynamic" "k8s.io/client-go/rest/fake" "k8s.io/kubernetes/pkg/api" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" @@ -32,12 +31,11 @@ func TestReplaceObject(t *testing.T) { _, _, rc := testData() f, tf, codec, _ := cmdtesting.NewAPIFactory() - ns := dynamic.ContentConfig().NegotiatedSerializer tf.Printer = &testPrinter{} deleted := false - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == "/api/v1/namespaces/test" && m == http.MethodGet: @@ -89,13 +87,12 @@ func TestReplaceMultipleObject(t *testing.T) { _, svc, rc := testData() f, tf, codec, _ := cmdtesting.NewAPIFactory() - ns := dynamic.ContentConfig().NegotiatedSerializer tf.Printer = &testPrinter{} redisMasterDeleted := false frontendDeleted := false - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == "/api/v1/namespaces/test" && m == http.MethodGet: @@ -160,12 +157,11 @@ func TestReplaceDirectory(t *testing.T) { _, _, rc := testData() f, tf, codec, _ := cmdtesting.NewAPIFactory() - ns := dynamic.ContentConfig().NegotiatedSerializer tf.Printer = &testPrinter{} created := map[string]bool{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == "/api/v1/namespaces/test" && m == http.MethodGet: @@ -218,11 +214,10 @@ func TestForceReplaceObjectNotFound(t *testing.T) { _, _, rc := testData() f, tf, codec, _ := cmdtesting.NewAPIFactory() - ns := dynamic.ContentConfig().NegotiatedSerializer tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == "/api/v1/namespaces/test" && m == http.MethodGet: diff --git a/pkg/kubectl/cmd/testing/fake.go b/pkg/kubectl/cmd/testing/fake.go index 387fe01cf3..035b9276fe 100644 --- a/pkg/kubectl/cmd/testing/fake.go +++ b/pkg/kubectl/cmd/testing/fake.go @@ -146,15 +146,16 @@ func (d *fakeCachedDiscoveryClient) Invalidate() { } type TestFactory struct { - Mapper meta.RESTMapper - Typer runtime.ObjectTyper - Client kubectl.RESTClient - Describer kubectl.Describer - Printer kubectl.ResourcePrinter - Validator validation.Schema - Namespace string - ClientConfig *restclient.Config - Err error + Mapper meta.RESTMapper + Typer runtime.ObjectTyper + Client kubectl.RESTClient + UnstructuredClient kubectl.RESTClient + Describer kubectl.Describer + Printer kubectl.ResourcePrinter + Validator validation.Schema + Namespace string + ClientConfig *restclient.Config + Err error } type FakeFactory struct { @@ -251,7 +252,7 @@ func (f *FakeFactory) ClientConfigForVersion(requiredVersion *schema.GroupVersio } func (f *FakeFactory) UnstructuredClientForMapping(*meta.RESTMapping) (resource.RESTClient, error) { - return nil, nil + return f.tf.UnstructuredClient, f.tf.Err } func (f *FakeFactory) Describer(*meta.RESTMapping) (kubectl.Describer, error) { @@ -497,7 +498,7 @@ func (f *fakeAPIFactory) ClientForMapping(*meta.RESTMapping) (resource.RESTClien } func (f *fakeAPIFactory) UnstructuredClientForMapping(*meta.RESTMapping) (resource.RESTClient, error) { - return f.tf.Client, f.tf.Err + return f.tf.UnstructuredClient, f.tf.Err } func (f *fakeAPIFactory) Describer(*meta.RESTMapping) (kubectl.Describer, error) { From dd6d4548488acaab77c99c5efe01de16d287ca0c Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Sat, 21 Jan 2017 15:48:41 -0500 Subject: [PATCH 03/13] kubectl: remove ThirdPartyResourceData behavior for unregistered objects --- pkg/kubectl/cmd/util/factory.go | 16 ---------------- pkg/kubectl/cmd/util/factory_client_access.go | 3 +-- pkg/kubectl/cmd/util/factory_object_mapping.go | 4 ---- 3 files changed, 1 insertion(+), 22 deletions(-) diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index 8411f2eacd..6e213a2038 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -51,7 +51,6 @@ import ( coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/resource" - "k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata" ) const ( @@ -241,21 +240,6 @@ func getGroupVersionKinds(gvks []schema.GroupVersionKind, group string) []schema return result } -func makeInterfacesFor(versionList []schema.GroupVersion) func(version schema.GroupVersion) (*meta.VersionInterfaces, error) { - accessor := meta.NewAccessor() - return func(version schema.GroupVersion) (*meta.VersionInterfaces, error) { - for ix := range versionList { - if versionList[ix].String() == version.String() { - return &meta.VersionInterfaces{ - ObjectConvertor: thirdpartyresourcedata.NewThirdPartyObjectConverter(api.Scheme), - MetadataAccessor: accessor, - }, nil - } - } - return nil, fmt.Errorf("unsupported storage version: %s (valid: %v)", version, versionList) - } -} - type factory struct { ClientAccessFactory ObjectMappingFactory diff --git a/pkg/kubectl/cmd/util/factory_client_access.go b/pkg/kubectl/cmd/util/factory_client_access.go index 93185177f5..3dd42c458e 100644 --- a/pkg/kubectl/cmd/util/factory_client_access.go +++ b/pkg/kubectl/cmd/util/factory_client_access.go @@ -49,7 +49,6 @@ import ( "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/resource" - "k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata" ) type ring0Factory struct { @@ -209,7 +208,7 @@ func (f *ring0Factory) Decoder(toInternal bool) runtime.Decoder { } else { decoder = api.Codecs.UniversalDeserializer() } - return thirdpartyresourcedata.NewDecoder(decoder, "") + return decoder } func (f *ring0Factory) JSONEncoder() runtime.Encoder { diff --git a/pkg/kubectl/cmd/util/factory_object_mapping.go b/pkg/kubectl/cmd/util/factory_object_mapping.go index a9fe71b52a..abf162f668 100644 --- a/pkg/kubectl/cmd/util/factory_object_mapping.go +++ b/pkg/kubectl/cmd/util/factory_object_mapping.go @@ -47,7 +47,6 @@ import ( "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/resource" - "k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata" ) type ring1Factory struct { @@ -127,9 +126,6 @@ func (f *ring1Factory) ClientForMapping(mapping *meta.RESTMapping) (resource.RES } gv := gvk.GroupVersion() cfg.GroupVersion = &gv - if api.Registry.IsThirdPartyAPIGroupVersion(gvk.GroupVersion()) { - cfg.NegotiatedSerializer = thirdpartyresourcedata.NewNegotiatedSerializer(api.Codecs, gvk.Kind, gv, gv) - } return restclient.RESTClientFor(cfg) } From 9b60d7fd848a490b5d9b36ab2b327331b0375bf4 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Sun, 22 Jan 2017 13:54:39 -0500 Subject: [PATCH 04/13] annotate: use unstructured objects --- pkg/kubectl/cmd/annotate.go | 21 ++++++++++++++------- pkg/kubectl/cmd/annotate_test.go | 24 ++++++++++++------------ 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/pkg/kubectl/cmd/annotate.go b/pkg/kubectl/cmd/annotate.go index 60e6a06f44..c85da240ca 100644 --- a/pkg/kubectl/cmd/annotate.go +++ b/pkg/kubectl/cmd/annotate.go @@ -18,17 +18,20 @@ package cmd import ( "bytes" - "encoding/json" "fmt" "io" + jsonpatch "github.com/evanphx/json-patch" "github.com/golang/glog" "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/apimachinery/pkg/util/json" + "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -181,8 +184,12 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro } changeCause := f.Command() - mapper, typer := f.Object() - b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). + + mapper, typer, err := f.UnstructuredObject() + if err != nil { + return err + } + b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme). ContinueOnError(). NamespaceParam(namespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). @@ -241,21 +248,21 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro if err != nil { return err } - patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) + patchBytes, err := jsonpatch.CreateMergePatch(oldData, newData) createdPatch := err == nil if err != nil { glog.V(2).Infof("couldn't compute patch: %v", err) } mapping := info.ResourceMapping() - client, err := f.ClientForMapping(mapping) + client, err := f.UnstructuredClientForMapping(mapping) if err != nil { return err } helper := resource.NewHelper(client, mapping) if createdPatch { - outputObj, err = helper.Patch(namespace, name, types.StrategicMergePatchType, patchBytes) + outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes) } else { outputObj, err = helper.Replace(namespace, name, false, obj) } diff --git a/pkg/kubectl/cmd/annotate_test.go b/pkg/kubectl/cmd/annotate_test.go index 470ae2abeb..57aa7a892a 100644 --- a/pkg/kubectl/cmd/annotate_test.go +++ b/pkg/kubectl/cmd/annotate_test.go @@ -424,11 +424,11 @@ func TestAnnotateErrors(t *testing.T) { func TestAnnotateObject(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.Method { case "GET": @@ -475,11 +475,11 @@ func TestAnnotateObject(t *testing.T) { func TestAnnotateObjectFromFile(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.Method { case "GET": @@ -525,10 +525,10 @@ func TestAnnotateObjectFromFile(t *testing.T) { } func TestAnnotateLocal(t *testing.T) { - f, tf, _, ns := cmdtesting.NewAPIFactory() - tf.Client = &fake.RESTClient{ + f, tf, _, _ := cmdtesting.NewAPIFactory() + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) return nil, nil @@ -557,11 +557,11 @@ func TestAnnotateLocal(t *testing.T) { func TestAnnotateMultipleObjects(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.Method { case "GET": From 89266588d1eed0290c0654ff358e4dabd33e2ee6 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Sun, 22 Jan 2017 13:54:46 -0500 Subject: [PATCH 05/13] label: use unstructured objects --- pkg/kubectl/cmd/label.go | 26 +++++++++++++++----------- pkg/kubectl/cmd/label_test.go | 18 +++++++++--------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/pkg/kubectl/cmd/label.go b/pkg/kubectl/cmd/label.go index 8a3d795185..681b06ec37 100644 --- a/pkg/kubectl/cmd/label.go +++ b/pkg/kubectl/cmd/label.go @@ -17,21 +17,24 @@ limitations under the License. package cmd import ( - "encoding/json" "fmt" "io" "reflect" "strings" + jsonpatch "github.com/evanphx/json-patch" "github.com/golang/glog" "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -175,8 +178,12 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { } changeCause := f.Command() - mapper, typer := f.Object() - b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). + + mapper, typer, err := f.UnstructuredObject() + if err != nil { + return err + } + b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &o.FilenameOptions). @@ -213,10 +220,7 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { } outputObj = info.Object } else { - obj, err := cmdutil.MaybeConvertObject(info.Object, info.Mapping.GroupVersionKind.GroupVersion(), info.Mapping) - if err != nil { - return err - } + obj := info.Object name, namespace := info.Name, info.Namespace oldData, err := json.Marshal(obj) if err != nil { @@ -247,21 +251,21 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { if !reflect.DeepEqual(oldData, newData) { dataChangeMsg = "labeled" } - patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) + patchBytes, err := jsonpatch.CreateMergePatch(oldData, newData) createdPatch := err == nil if err != nil { glog.V(2).Infof("couldn't compute patch: %v", err) } mapping := info.ResourceMapping() - client, err := f.ClientForMapping(mapping) + client, err := f.UnstructuredClientForMapping(mapping) if err != nil { return err } helper := resource.NewHelper(client, mapping) if createdPatch { - outputObj, err = helper.Patch(namespace, name, types.StrategicMergePatchType, patchBytes) + outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes) } else { outputObj, err = helper.Replace(namespace, name, false, obj) } diff --git a/pkg/kubectl/cmd/label_test.go b/pkg/kubectl/cmd/label_test.go index 8d051f3add..40590cf72e 100644 --- a/pkg/kubectl/cmd/label_test.go +++ b/pkg/kubectl/cmd/label_test.go @@ -347,10 +347,10 @@ func TestLabelErrors(t *testing.T) { func TestLabelForResourceFromFile(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() - tf.Client = &fake.RESTClient{ + f, tf, codec, _ := cmdtesting.NewAPIFactory() + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.Method { case "GET": @@ -398,10 +398,10 @@ func TestLabelForResourceFromFile(t *testing.T) { } func TestLabelLocal(t *testing.T) { - f, tf, _, ns := cmdtesting.NewAPIFactory() - tf.Client = &fake.RESTClient{ + f, tf, _, _ := cmdtesting.NewAPIFactory() + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) return nil, nil @@ -432,10 +432,10 @@ func TestLabelLocal(t *testing.T) { func TestLabelMultipleObjects(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() - tf.Client = &fake.RESTClient{ + f, tf, codec, _ := cmdtesting.NewAPIFactory() + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.Method { case "GET": From b6d4f371cd65d32d2281ad92c58c1f39d695b566 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Sun, 22 Jan 2017 11:38:05 -0500 Subject: [PATCH 06/13] patch: use unstructured objects --- pkg/kubectl/cmd/patch.go | 12 ++++++++---- pkg/kubectl/cmd/patch_test.go | 12 ++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/pkg/kubectl/cmd/patch.go b/pkg/kubectl/cmd/patch.go index dd42779f3a..eb847cbca2 100644 --- a/pkg/kubectl/cmd/patch.go +++ b/pkg/kubectl/cmd/patch.go @@ -17,7 +17,6 @@ limitations under the License. package cmd import ( - "encoding/json" "fmt" "io" "reflect" @@ -26,8 +25,10 @@ import ( jsonpatch "github.com/evanphx/json-patch" "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/yaml" @@ -144,8 +145,11 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin return fmt.Errorf("unable to parse %q: %v", patch, err) } - mapper, typer := f.Object() - r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). + mapper, typer, err := f.UnstructuredObject() + if err != nil { + return err + } + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, &options.FilenameOptions). @@ -164,7 +168,7 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin } name, namespace := info.Name, info.Namespace mapping := info.ResourceMapping() - client, err := f.ClientForMapping(mapping) + client, err := f.UnstructuredClientForMapping(mapping) if err != nil { return err } diff --git a/pkg/kubectl/cmd/patch_test.go b/pkg/kubectl/cmd/patch_test.go index c7fc8ef82c..401a642990 100644 --- a/pkg/kubectl/cmd/patch_test.go +++ b/pkg/kubectl/cmd/patch_test.go @@ -29,11 +29,11 @@ import ( func TestPatchObject(t *testing.T) { _, svc, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"): @@ -62,11 +62,11 @@ func TestPatchObject(t *testing.T) { func TestPatchObjectFromFile(t *testing.T) { _, svc, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"): From 06c12f6716b55f5782c83530ce2a67ad65619500 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Sun, 22 Jan 2017 17:11:33 -0500 Subject: [PATCH 07/13] describe: use unstructured objects --- pkg/kubectl/cmd/describe.go | 14 ++++++-- pkg/kubectl/cmd/describe_test.go | 55 ++++++++++++++++++++-------- pkg/kubectl/cmd/testing/fake.go | 61 ++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 18 deletions(-) diff --git a/pkg/kubectl/cmd/describe.go b/pkg/kubectl/cmd/describe.go index 08cfd5de0e..5c398a4dc7 100644 --- a/pkg/kubectl/cmd/describe.go +++ b/pkg/kubectl/cmd/describe.go @@ -25,6 +25,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" @@ -111,8 +112,11 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a return cmdutil.UsageError(cmd, "Required resource not specified.") } - mapper, typer := f.Object() - r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). + mapper, typer, err := f.UnstructuredObject() + if err != nil { + return err + } + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). FilenameParam(enforceNamespace, options). @@ -168,7 +172,11 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a } func DescribeMatchingResources(mapper meta.RESTMapper, typer runtime.ObjectTyper, f cmdutil.Factory, namespace, rsrc, prefix string, describerSettings *kubectl.DescriberSettings, out io.Writer, originalError error) error { - r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). + mapper, typer, err := f.UnstructuredObject() + if err != nil { + return err + } + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme). NamespaceParam(namespace).DefaultNamespace(). ResourceTypeOrNameArgs(true, rsrc). SingleResourceType(). diff --git a/pkg/kubectl/cmd/describe_test.go b/pkg/kubectl/cmd/describe_test.go index e36673308d..1064d981f3 100644 --- a/pkg/kubectl/cmd/describe_test.go +++ b/pkg/kubectl/cmd/describe_test.go @@ -30,11 +30,11 @@ import ( // Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get. func TestDescribeUnknownSchemaObject(t *testing.T) { d := &testDescriber{Output: "test output"} - f, tf, codec, ns := cmdtesting.NewTestFactory() + f, tf, codec, _ := cmdtesting.NewTestFactory() tf.Describer = d - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))}, } tf.Namespace = "non-default" @@ -43,6 +43,31 @@ func TestDescribeUnknownSchemaObject(t *testing.T) { cmd := NewCmdDescribe(f, buf, buferr) cmd.Run(cmd, []string{"type", "foo"}) + if d.Name != "foo" || d.Namespace != "" { + t.Errorf("unexpected describer: %#v", d) + } + + if buf.String() != fmt.Sprintf("%s", d.Output) { + t.Errorf("unexpected output: %s", buf.String()) + } +} + +// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get. +func TestDescribeUnknownNamespacedSchemaObject(t *testing.T) { + d := &testDescriber{Output: "test output"} + f, tf, codec, _ := cmdtesting.NewTestFactory() + tf.Describer = d + tf.UnstructuredClient = &fake.RESTClient{ + APIRegistry: api.Registry, + NegotiatedSerializer: unstructuredSerializer, + Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalNamespacedType("", "", "foo", "non-default"))}, + } + tf.Namespace = "non-default" + buf := bytes.NewBuffer([]byte{}) + buferr := bytes.NewBuffer([]byte{}) + cmd := NewCmdDescribe(f, buf, buferr) + cmd.Run(cmd, []string{"namespacedtype", "foo"}) + if d.Name != "foo" || d.Namespace != "non-default" { t.Errorf("unexpected describer: %#v", d) } @@ -54,12 +79,12 @@ func TestDescribeUnknownSchemaObject(t *testing.T) { func TestDescribeObject(t *testing.T) { _, _, rc := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() d := &testDescriber{Output: "test output"} tf.Describer = d - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "GET": @@ -88,12 +113,12 @@ func TestDescribeObject(t *testing.T) { func TestDescribeListObjects(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() d := &testDescriber{Output: "test output"} tf.Describer = d - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } @@ -109,12 +134,12 @@ func TestDescribeListObjects(t *testing.T) { func TestDescribeObjectShowEvents(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() d := &testDescriber{Output: "test output"} tf.Describer = d - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } @@ -131,12 +156,12 @@ func TestDescribeObjectShowEvents(t *testing.T) { func TestDescribeObjectSkipEvents(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() d := &testDescriber{Output: "test output"} tf.Describer = d - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } diff --git a/pkg/kubectl/cmd/testing/fake.go b/pkg/kubectl/cmd/testing/fake.go index 035b9276fe..58e2088ac4 100644 --- a/pkg/kubectl/cmd/testing/fake.go +++ b/pkg/kubectl/cmd/testing/fake.go @@ -93,6 +93,60 @@ func NewInternalType(kind, apiversion, name string) *InternalType { return &item } +type InternalNamespacedType struct { + Kind string + APIVersion string + + Name string + Namespace string +} + +type ExternalNamespacedType struct { + Kind string `json:"kind"` + APIVersion string `json:"apiVersion"` + + Name string `json:"name"` + Namespace string `json:"namespace"` +} + +type ExternalNamespacedType2 struct { + Kind string `json:"kind"` + APIVersion string `json:"apiVersion"` + + Name string `json:"name"` + Namespace string `json:"namespace"` +} + +func (obj *InternalNamespacedType) GetObjectKind() schema.ObjectKind { return obj } +func (obj *InternalNamespacedType) SetGroupVersionKind(gvk schema.GroupVersionKind) { + obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind() +} +func (obj *InternalNamespacedType) GroupVersionKind() schema.GroupVersionKind { + return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind) +} +func (obj *ExternalNamespacedType) GetObjectKind() schema.ObjectKind { return obj } +func (obj *ExternalNamespacedType) SetGroupVersionKind(gvk schema.GroupVersionKind) { + obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind() +} +func (obj *ExternalNamespacedType) GroupVersionKind() schema.GroupVersionKind { + return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind) +} +func (obj *ExternalNamespacedType2) GetObjectKind() schema.ObjectKind { return obj } +func (obj *ExternalNamespacedType2) SetGroupVersionKind(gvk schema.GroupVersionKind) { + obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind() +} +func (obj *ExternalNamespacedType2) GroupVersionKind() schema.GroupVersionKind { + return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind) +} + +func NewInternalNamespacedType(kind, apiversion, name, namespace string) *InternalNamespacedType { + item := InternalNamespacedType{Kind: kind, + APIVersion: apiversion, + Name: name, + Namespace: namespace} + return &item +} + var versionErr = errors.New("not a version") func versionErrIfFalse(b bool) error { @@ -109,11 +163,17 @@ var ValidVersionGV = schema.GroupVersion{Group: "apitest", Version: ValidVersion func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) { scheme := runtime.NewScheme() + scheme.AddKnownTypeWithName(InternalGV.WithKind("Type"), &InternalType{}) scheme.AddKnownTypeWithName(UnlikelyGV.WithKind("Type"), &ExternalType{}) //This tests that kubectl will not confuse the external scheme with the internal scheme, even when they accidentally have versions of the same name. scheme.AddKnownTypeWithName(ValidVersionGV.WithKind("Type"), &ExternalType2{}) + scheme.AddKnownTypeWithName(InternalGV.WithKind("NamespacedType"), &InternalNamespacedType{}) + scheme.AddKnownTypeWithName(UnlikelyGV.WithKind("NamespacedType"), &ExternalNamespacedType{}) + //This tests that kubectl will not confuse the external scheme with the internal scheme, even when they accidentally have versions of the same name. + scheme.AddKnownTypeWithName(ValidVersionGV.WithKind("NamespacedType"), &ExternalNamespacedType2{}) + codecs := serializer.NewCodecFactory(scheme) codec := codecs.LegacyCodec(UnlikelyGV) mapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{UnlikelyGV, ValidVersionGV}, func(version schema.GroupVersion) (*meta.VersionInterfaces, error) { @@ -603,6 +663,7 @@ func testDynamicResources() []*discovery.APIGroupResources { {Name: "componentstatuses", Namespaced: false, Kind: "ComponentStatus"}, {Name: "nodes", Namespaced: false, Kind: "Node"}, {Name: "type", Namespaced: false, Kind: "Type"}, + {Name: "namespacedtype", Namespaced: true, Kind: "NamespacedType"}, }, }, }, From d41e391799b97f84081dd286c8a4562fd7c70832 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Sun, 22 Jan 2017 22:12:42 -0500 Subject: [PATCH 08/13] describe: allow describing generic api objects --- .../cmd/util/factory_object_mapping.go | 43 ++++++++++++++++++- pkg/kubectl/describe.go | 42 ++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/pkg/kubectl/cmd/util/factory_object_mapping.go b/pkg/kubectl/cmd/util/factory_object_mapping.go index abf162f668..9bdb07b0a6 100644 --- a/pkg/kubectl/cmd/util/factory_object_mapping.go +++ b/pkg/kubectl/cmd/util/factory_object_mapping.go @@ -158,14 +158,55 @@ func (f *ring1Factory) Describer(mapping *meta.RESTMapping) (kubectl.Describer, return &kubectl.ClusterDescriber{Interface: fedClientSet}, nil } } + clientset, err := f.clientAccessFactory.ClientSetForVersion(&mappingVersion) if err != nil { + // if we can't make a client for this group/version, go generic if possible + if genericDescriber, genericErr := genericDescriber(f.clientAccessFactory, mapping); genericErr == nil { + return genericDescriber, nil + } + // otherwise return the original error return nil, err } + + // try to get a describer if describer, ok := kubectl.DescriberFor(mapping.GroupVersionKind.GroupKind(), clientset); ok { return describer, nil } - return nil, fmt.Errorf("no description has been implemented for %q", mapping.GroupVersionKind.Kind) + // if this is a kind we don't have a describer for yet, go generic if possible + if genericDescriber, genericErr := genericDescriber(f.clientAccessFactory, mapping); genericErr == nil { + return genericDescriber, nil + } + // otherwise return an unregistered error + return nil, fmt.Errorf("no description has been implemented for %s", mapping.GroupVersionKind.String()) +} + +// helper function to make a generic describer, or return an error +func genericDescriber(clientAccessFactory ClientAccessFactory, mapping *meta.RESTMapping) (kubectl.Describer, error) { + clientConfig, err := clientAccessFactory.ClientConfig() + if err != nil { + return nil, err + } + + clientConfigCopy := *clientConfig + clientConfigCopy.APIPath = dynamic.LegacyAPIPathResolverFunc(mapping.GroupVersionKind) + gv := mapping.GroupVersionKind.GroupVersion() + clientConfigCopy.GroupVersion = &gv + + // used to fetch the resource + dynamicClient, err := dynamic.NewClient(&clientConfigCopy) + if err != nil { + return nil, err + } + + // used to get events for the resource + clientSet, err := clientAccessFactory.ClientSet() + if err != nil { + return nil, err + } + eventsClient := clientSet.Core() + + return kubectl.GenericDescriberFor(mapping, dynamicClient, eventsClient), nil } func (f *ring1Factory) LogsForObject(object, options runtime.Object) (*restclient.Request, error) { diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index 01dea417b5..f6a11414a7 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -29,12 +29,14 @@ import ( "time" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/dynamic" "k8s.io/kubernetes/federation/apis/federation" fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset" "k8s.io/kubernetes/pkg/api" @@ -171,6 +173,46 @@ func DescriberFor(kind schema.GroupKind, c clientset.Interface) (Describer, bool return f, ok } +// GenericDescriberFor returns a generic describer for the specified mapping +// that uses only information available from runtime.Unstructured +func GenericDescriberFor(mapping *meta.RESTMapping, dynamic *dynamic.Client, events coreclient.EventsGetter) Describer { + return &genericDescriber{mapping, dynamic, events} +} + +type genericDescriber struct { + mapping *meta.RESTMapping + dynamic *dynamic.Client + events coreclient.EventsGetter +} + +func (g *genericDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (output string, err error) { + apiResource := &metav1.APIResource{ + Name: g.mapping.Resource, + Namespaced: g.mapping.Scope.Name() == meta.RESTScopeNameNamespace, + Kind: g.mapping.GroupVersionKind.Kind, + } + obj, err := g.dynamic.Resource(apiResource, namespace).Get(name) + if err != nil { + return "", err + } + + var events *api.EventList + if describerSettings.ShowEvents { + events, _ = g.events.Events(namespace).Search(obj) + } + + return tabbedString(func(out io.Writer) error { + w := &PrefixWriter{out} + w.Write(LEVEL_0, "Name:\t%s\n", obj.GetName()) + w.Write(LEVEL_0, "Namespace:\t%s\n", obj.GetNamespace()) + printLabelsMultiline(w, "Labels", obj.GetLabels()) + if events != nil { + DescribeEvents(events, w) + } + return nil + }) +} + // DefaultObjectDescriber can describe the default Kubernetes objects. var DefaultObjectDescriber ObjectDescriber From aaf96a10486d99717f96faa4e08dd8fb0deca21c Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Sun, 22 Jan 2017 13:59:48 -0500 Subject: [PATCH 09/13] apply: use unstructured objects --- pkg/kubectl/cmd/apply.go | 8 ++++++-- pkg/kubectl/cmd/apply_test.go | 30 +++++++++++++++--------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/pkg/kubectl/cmd/apply.go b/pkg/kubectl/cmd/apply.go index 4afcfcb5fa..2c69cf1cbc 100644 --- a/pkg/kubectl/cmd/apply.go +++ b/pkg/kubectl/cmd/apply.go @@ -28,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -181,8 +182,11 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti } } - mapper, typer := f.Object() - r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). + mapper, typer, err := f.UnstructuredObject() + if err != nil { + return err + } + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme). Schema(schema). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). diff --git a/pkg/kubectl/cmd/apply_test.go b/pkg/kubectl/cmd/apply_test.go index 89a4d425f9..c41de7aa15 100644 --- a/pkg/kubectl/cmd/apply_test.go +++ b/pkg/kubectl/cmd/apply_test.go @@ -197,11 +197,11 @@ func TestApplyObjectWithoutAnnotation(t *testing.T) { nameRC, rcBytes := readReplicationController(t, filenameRC) pathRC := "/namespaces/test/replicationcontrollers/" + nameRC - f, tf, _, ns := cmdtesting.NewAPIFactory() + f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == pathRC && m == "GET": @@ -242,11 +242,11 @@ func TestApplyObject(t *testing.T) { nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC) pathRC := "/namespaces/test/replicationcontrollers/" + nameRC - f, tf, _, ns := cmdtesting.NewAPIFactory() + f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == pathRC && m == "GET": @@ -286,11 +286,11 @@ func TestApplyRetry(t *testing.T) { firstPatch := true retry := false getCount := 0 - f, tf, _, ns := cmdtesting.NewAPIFactory() + f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == pathRC && m == "GET": @@ -340,11 +340,11 @@ func TestApplyNonExistObject(t *testing.T) { pathRC := "/namespaces/test/replicationcontrollers" pathNameRC := pathRC + "/" + nameRC - f, tf, _, ns := cmdtesting.NewAPIFactory() + f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == "/api/v1/namespaces/test" && m == "GET": @@ -391,11 +391,11 @@ func testApplyMultipleObjects(t *testing.T, asList bool) { nameSVC, currentSVC := readAndAnnotateService(t, filenameSVC) pathSVC := "/namespaces/test/services/" + nameSVC - f, tf, _, ns := cmdtesting.NewAPIFactory() + f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == pathRC && m == "GET": From 424dac921f2cc3f295f4842fb7ebe522b36cb7d4 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Wed, 25 Jan 2017 20:48:18 -0500 Subject: [PATCH 10/13] Fall back to unstructured typer retrieving GVKs for printing --- pkg/kubectl/cmd/util/factory_builder.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pkg/kubectl/cmd/util/factory_builder.go b/pkg/kubectl/cmd/util/factory_builder.go index d6d718fa42..c11652d13e 100644 --- a/pkg/kubectl/cmd/util/factory_builder.go +++ b/pkg/kubectl/cmd/util/factory_builder.go @@ -25,7 +25,6 @@ import ( "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl/resource" ) @@ -44,7 +43,20 @@ func NewBuilderFactory(clientAccessFactory ClientAccessFactory, objectMappingFac } func (f *ring2Factory) PrintObject(cmd *cobra.Command, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error { - gvks, _, err := api.Scheme.ObjectKinds(obj) + // try to get a typed object + _, typer := f.objectMappingFactory.Object() + gvks, _, err := typer.ObjectKinds(obj) + + // fall back to an unstructured object if we get something unregistered + if runtime.IsNotRegisteredError(err) { + _, typer, unstructuredErr := f.objectMappingFactory.UnstructuredObject() + if unstructuredErr != nil { + // if we can't get an unstructured typer, return the original error + return err + } + gvks, _, err = typer.ObjectKinds(obj) + } + if err != nil { return err } From c317f929907bc292ddf29cf4558d1663f1456b08 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Wed, 25 Jan 2017 21:21:47 -0500 Subject: [PATCH 11/13] patch: allow --local to use structs for strategic merge, warn about unknown objects --- pkg/kubectl/cmd/patch.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/kubectl/cmd/patch.go b/pkg/kubectl/cmd/patch.go index eb847cbca2..19b2c0b425 100644 --- a/pkg/kubectl/cmd/patch.go +++ b/pkg/kubectl/cmd/patch.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/sets" @@ -212,19 +213,15 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin count++ - patchedObj, err := api.Scheme.DeepCopy(info.VersionedObject) + originalObjJS, err := runtime.Encode(unstructured.UnstructuredJSONScheme, info.VersionedObject) if err != nil { return err } - originalObjJS, err := runtime.Encode(api.Codecs.LegacyCodec(mapping.GroupVersionKind.GroupVersion()), info.VersionedObject.(runtime.Object)) + originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, mapping.GroupVersionKind, api.Scheme) if err != nil { return err } - originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, patchedObj.(runtime.Object)) - if err != nil { - return err - } - targetObj, err := runtime.Decode(api.Codecs.UniversalDecoder(), originalPatchedObjJS) + targetObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, originalPatchedObjJS) if err != nil { return err } @@ -252,7 +249,7 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin return nil } -func getPatchedJSON(patchType types.PatchType, originalJS, patchJS []byte, obj runtime.Object) ([]byte, error) { +func getPatchedJSON(patchType types.PatchType, originalJS, patchJS []byte, gvk schema.GroupVersionKind, creater runtime.ObjectCreater) ([]byte, error) { switch patchType { case types.JSONPatchType: patchObj, err := jsonpatch.DecodePatch(patchJS) @@ -265,6 +262,11 @@ func getPatchedJSON(patchType types.PatchType, originalJS, patchJS []byte, obj r return jsonpatch.MergePatch(originalJS, patchJS) case types.StrategicMergePatchType: + // get a typed object for this GVK if we need to apply a strategic merge patch + obj, err := creater.New(gvk) + if err != nil { + return nil, fmt.Errorf("cannot apply strategic merge patch for %s locally, try --type merge", gvk.String()) + } return strategicpatch.StrategicMergePatch(originalJS, patchJS, obj) default: From 408a978f26bc3996cadc4450810d7116aecb6266 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Sun, 22 Jan 2017 21:15:10 -0500 Subject: [PATCH 12/13] fix --record to work with unstructured objects --- pkg/kubectl/cmd/apply.go | 12 +++++------- pkg/kubectl/cmd/patch.go | 17 ++++++++++------- pkg/kubectl/cmd/scale.go | 5 ++--- pkg/kubectl/cmd/set/set_image.go | 4 ++-- pkg/kubectl/cmd/util/BUILD | 2 ++ pkg/kubectl/cmd/util/helpers.go | 25 +++++++++++++++++-------- 6 files changed, 38 insertions(+), 27 deletions(-) diff --git a/pkg/kubectl/cmd/apply.go b/pkg/kubectl/cmd/apply.go index 2c69cf1cbc..07c2393023 100644 --- a/pkg/kubectl/cmd/apply.go +++ b/pkg/kubectl/cmd/apply.go @@ -22,6 +22,7 @@ import ( "strings" "time" + "github.com/golang/glog" "github.com/jonboulle/clockwork" "github.com/spf13/cobra" @@ -295,13 +296,10 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti } if cmdutil.ShouldRecord(cmd, info) { - patch, err := cmdutil.ChangeResourcePatch(info, f.Command()) - if err != nil { - return err - } - _, err = helper.Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch) - if err != nil { - return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patch, info), info.Source, err) + if patch, patchType, err := cmdutil.ChangeResourcePatch(info, f.Command()); err == nil { + if _, err = helper.Patch(info.Namespace, info.Name, patchType, patch); err != nil { + glog.V(4).Infof("error recording reason: %v", err) + } } } diff --git a/pkg/kubectl/cmd/patch.go b/pkg/kubectl/cmd/patch.go index 19b2c0b425..3e0f55342f 100644 --- a/pkg/kubectl/cmd/patch.go +++ b/pkg/kubectl/cmd/patch.go @@ -23,6 +23,7 @@ import ( "strings" jsonpatch "github.com/evanphx/json-patch" + "github.com/golang/glog" "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -181,14 +182,16 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin if err != nil { return err } + // Record the change as a second patch to avoid trying to merge with a user's patch data if cmdutil.ShouldRecord(cmd, info) { - // don't return an error on failure. The patch itself succeeded, its only the hint for that change that failed - // don't bother checking for failures of this replace, because a failure to indicate the hint doesn't fail the command - // also, don't force the replacement. If the replacement fails on a resourceVersion conflict, then it means this - // record hint is likely to be invalid anyway, so avoid the bad hint - patch, err := cmdutil.ChangeResourcePatch(info, f.Command()) - if err == nil { - helper.Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch) + // Copy the resource info and update with the result of applying the user's patch + infoCopy := *info + infoCopy.Object = patchedObj + infoCopy.VersionedObject = patchedObj + if patch, patchType, err := cmdutil.ChangeResourcePatch(&infoCopy, f.Command()); err == nil { + if _, err = helper.Patch(info.Namespace, info.Name, patchType, patch); err != nil { + glog.V(4).Infof("error recording reason: %v", err) + } } } count++ diff --git a/pkg/kubectl/cmd/scale.go b/pkg/kubectl/cmd/scale.go index ff34ec50b8..ef811f04fa 100644 --- a/pkg/kubectl/cmd/scale.go +++ b/pkg/kubectl/cmd/scale.go @@ -23,7 +23,6 @@ import ( "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/types" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -164,7 +163,7 @@ func RunScale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin return err } if cmdutil.ShouldRecord(cmd, info) { - patchBytes, err := cmdutil.ChangeResourcePatch(info, f.Command()) + patchBytes, patchType, err := cmdutil.ChangeResourcePatch(info, f.Command()) if err != nil { return err } @@ -174,7 +173,7 @@ func RunScale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin return err } helper := resource.NewHelper(client, mapping) - _, err = helper.Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patchBytes) + _, err = helper.Patch(info.Namespace, info.Name, patchType, patchBytes) if err != nil { return err } diff --git a/pkg/kubectl/cmd/set/set_image.go b/pkg/kubectl/cmd/set/set_image.go index b079418edc..8b43902353 100644 --- a/pkg/kubectl/cmd/set/set_image.go +++ b/pkg/kubectl/cmd/set/set_image.go @@ -238,8 +238,8 @@ func (o *ImageOptions) Run() error { // record this change (for rollout history) if o.Record || cmdutil.ContainsChangeCause(info) { - if patch, err := cmdutil.ChangeResourcePatch(info, o.ChangeCause); err == nil { - if obj, err = resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch); err != nil { + if patch, patchType, err := cmdutil.ChangeResourcePatch(info, o.ChangeCause); err == nil { + if obj, err = resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, patchType, patch); err != nil { fmt.Fprintf(o.Err, "WARNING: changes to %s/%s can't be recorded: %v\n", info.Mapping.Resource, info.Name, err) } } diff --git a/pkg/kubectl/cmd/util/BUILD b/pkg/kubectl/cmd/util/BUILD index 7f70692b42..7df57bacab 100644 --- a/pkg/kubectl/cmd/util/BUILD +++ b/pkg/kubectl/cmd/util/BUILD @@ -53,7 +53,9 @@ go_library( "//vendor:k8s.io/apimachinery/pkg/runtime", "//vendor:k8s.io/apimachinery/pkg/runtime/schema", "//vendor:k8s.io/apimachinery/pkg/runtime/serializer/json", + "//vendor:k8s.io/apimachinery/pkg/types", "//vendor:k8s.io/apimachinery/pkg/util/errors", + "//vendor:k8s.io/apimachinery/pkg/util/json", "//vendor:k8s.io/apimachinery/pkg/util/sets", "//vendor:k8s.io/apimachinery/pkg/util/strategicpatch", "//vendor:k8s.io/apimachinery/pkg/version", diff --git a/pkg/kubectl/cmd/util/helpers.go b/pkg/kubectl/cmd/util/helpers.go index 5d4e0c8336..bc92403cc2 100644 --- a/pkg/kubectl/cmd/util/helpers.go +++ b/pkg/kubectl/cmd/util/helpers.go @@ -18,7 +18,6 @@ package util import ( "bytes" - "encoding/json" "errors" "fmt" "io" @@ -38,7 +37,9 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/client-go/tools/clientcmd" @@ -520,27 +521,35 @@ func RecordChangeCause(obj runtime.Object, changeCause string) error { return nil } -// ChangeResourcePatch creates a strategic merge patch between the origin input resource info +// ChangeResourcePatch creates a patch between the origin input resource info // and the annotated with change-cause input resource info. -func ChangeResourcePatch(info *resource.Info, changeCause string) ([]byte, error) { +func ChangeResourcePatch(info *resource.Info, changeCause string) ([]byte, types.PatchType, error) { // Get a versioned object obj, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion()) if err != nil { - return nil, err + return nil, types.StrategicMergePatchType, err } oldData, err := json.Marshal(obj) if err != nil { - return nil, err + return nil, types.StrategicMergePatchType, err } if err := RecordChangeCause(obj, changeCause); err != nil { - return nil, err + return nil, types.StrategicMergePatchType, err } newData, err := json.Marshal(obj) if err != nil { - return nil, err + return nil, types.StrategicMergePatchType, err + } + + switch obj := obj.(type) { + case *unstructured.Unstructured: + patch, err := jsonpatch.CreateMergePatch(oldData, newData) + return patch, types.MergePatchType, err + default: + patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) + return patch, types.StrategicMergePatchType, err } - return strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) } // containsChangeCause checks if input resource info contains change-cause annotation. From 17168aaacc574a870bb02cb5ae60bc91c074b197 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Wed, 25 Jan 2017 22:46:54 -0500 Subject: [PATCH 13/13] Test generic operations with thirdpartyresources --- hack/make-rules/test-cmd-util.sh | 67 ++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/hack/make-rules/test-cmd-util.sh b/hack/make-rules/test-cmd-util.sh index a4808b5c76..0adcc72b83 100644 --- a/hack/make-rules/test-cmd-util.sh +++ b/hack/make-rules/test-cmd-util.sh @@ -1202,6 +1202,73 @@ __EOF__ # Test that we can list this new third party resource kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" 'test:' + # Test alternate forms + kube::test::get_object_assert foo "{{range.items}}{{$id_field}}:{{end}}" 'test:' + kube::test::get_object_assert foos.company.com "{{range.items}}{{$id_field}}:{{end}}" 'test:' + kube::test::get_object_assert foos.v1.company.com "{{range.items}}{{$id_field}}:{{end}}" 'test:' + + # Test all printers, with lists and individual items + kube::log::status "Testing ThirdPartyResource printing" + kubectl "${kube_flags[@]}" get foos + kubectl "${kube_flags[@]}" get foos/test + kubectl "${kube_flags[@]}" get foos -o name + kubectl "${kube_flags[@]}" get foos/test -o name + kubectl "${kube_flags[@]}" get foos -o wide + kubectl "${kube_flags[@]}" get foos/test -o wide + kubectl "${kube_flags[@]}" get foos -o json + kubectl "${kube_flags[@]}" get foos/test -o json + kubectl "${kube_flags[@]}" get foos -o yaml + kubectl "${kube_flags[@]}" get foos/test -o yaml + kubectl "${kube_flags[@]}" get foos -o "jsonpath={.items[*].some-field}" --allow-missing-template-keys=false + kubectl "${kube_flags[@]}" get foos/test -o "jsonpath={.some-field}" --allow-missing-template-keys=false + kubectl "${kube_flags[@]}" get foos -o "go-template={{range .items}}{{index . \"some-field\"}}{{end}}" --allow-missing-template-keys=false + kubectl "${kube_flags[@]}" get foos/test -o "go-template={{index . \"some-field\"}}" --allow-missing-template-keys=false + + # Test patching + kube::log::status "Testing ThirdPartyResource patching" + kubectl "${kube_flags[@]}" patch foos/test -p '{"patched":"value1"}' --type=merge + kube::test::get_object_assert foos/test "{{.patched}}" 'value1' + kubectl "${kube_flags[@]}" patch foos/test -p '{"patched":"value2"}' --type=merge --record + kube::test::get_object_assert foos/test "{{.patched}}" 'value2' + kubectl "${kube_flags[@]}" patch foos/test -p '{"patched":null}' --type=merge --record + kube::test::get_object_assert foos/test "{{.patched}}" '' + # Get local version + TPR_RESOURCE_FILE="${KUBE_TEMP}/tpr-foos-test.json" + kubectl "${kube_flags[@]}" get foos/test -o json > "${TPR_RESOURCE_FILE}" + # cannot apply strategic patch locally + TPR_PATCH_ERROR_FILE="${KUBE_TEMP}/tpr-foos-test-error" + ! kubectl "${kube_flags[@]}" patch --local -f "${TPR_RESOURCE_FILE}" -p '{"patched":"value3"}' 2> "${TPR_PATCH_ERROR_FILE}" + if grep -q "try --type merge" "${TPR_PATCH_ERROR_FILE}"; then + kube::log::status "\"kubectl patch --local\" returns error as expected for ThirdPartyResource: $(cat ${TPR_PATCH_ERROR_FILE})" + else + kube::log::status "\"kubectl patch --local\" returns unexpected error or non-error: $(cat ${TPR_PATCH_ERROR_FILE})" + exit 1 + fi + # can apply merge patch locally + kubectl "${kube_flags[@]}" patch --local -f "${TPR_RESOURCE_FILE}" -p '{"patched":"value3"}' --type=merge -o json + # can apply merge patch remotely + kubectl "${kube_flags[@]}" patch --record -f "${TPR_RESOURCE_FILE}" -p '{"patched":"value3"}' --type=merge -o json + kube::test::get_object_assert foos/test "{{.patched}}" 'value3' + rm "${TPR_RESOURCE_FILE}" + rm "${TPR_PATCH_ERROR_FILE}" + + # Test labeling + kube::log::status "Testing ThirdPartyResource labeling" + kubectl "${kube_flags[@]}" label foos --all listlabel=true + kubectl "${kube_flags[@]}" label foo/test itemlabel=true + + # Test annotating + kube::log::status "Testing ThirdPartyResource annotating" + kubectl "${kube_flags[@]}" annotate foos --all listannotation=true + kubectl "${kube_flags[@]}" annotate foo/test itemannotation=true + + # Test describing + kube::log::status "Testing ThirdPartyResource describing" + kubectl "${kube_flags[@]}" describe foos + kubectl "${kube_flags[@]}" describe foos/test + kubectl "${kube_flags[@]}" describe foos | grep listlabel=true + kubectl "${kube_flags[@]}" describe foos | grep itemlabel=true + # Delete the resource kubectl "${kube_flags[@]}" delete foos test