Commit 1625150d authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #40260 from liggitt/kubectl-tpr

Automatic merge from submit-queue (batch tested with PRs 39223, 40260, 40082, 40389) make kubectl generic commands work with unstructured objects part of making apply, edit, label, annotate, and patch work with third party resources fixes #35149 fixes #34413 prereq of: https://github.com/kubernetes/kubernetes/issues/35496 https://github.com/kubernetes/kubernetes/pull/40096 related to: https://github.com/kubernetes/kubernetes/issues/39906 https://github.com/kubernetes/kubernetes/issues/40119 kubectl is currently decoding any resource it doesn't have compiled-in to a ThirdPartyResourceData struct, which means it computes patches using that struct, and would try to send a ThirdPartyResourceData object to the API server when running `apply` This PR removes the behavior that decodes unknown objects into ThirdPartyResourceData structs internally, and fixes up the following generic commands to work with unstructured objects - [x] apply - [x] decode into runtime.Unstructured objects - [x] successfully use `--record` with unregistered objects - [x] patch - [x] decode into runtime.Unstructured objects - [x] successfully use `--record` with unregistered objects - [x] describe - [x] decode into runtime.Unstructured objects - [x] implement generic describer - [x] fix other generic kubectl commands to work with unstructured objects - [x] label - [x] annotate follow-ups for pre-existing issues: - [ ] `explain` doesn't work with unregistered resources - [ ] remove special casing of federation group in clientset lookups, etc - [ ] `patch` - [ ] doesn't honor output formats when persisting to server (`kubectl patch -f svc.json --type merge -p '{}' -o json` doesn't output json) - [ ] --local throws exception (`kubectl patch -f svc.json --type merge -p '{}' --local`) - [ ] `apply` - [ ] fall back to generic JSON patch computation if no go struct is registered for the target GVK (e.g. https://github.com/kubernetes/kubernetes/pull/40096) - [ ] ensure subkey deletion works in CreateThreeWayJSONMergePatch - [ ] ensure type stomping works in CreateThreeWayJSONMergePatch - [ ] lots of tests for generic json patch computation - [ ] prevent generic apply patch computation among different versions - [ ] reconcile treatment of nulls with https://github.com/kubernetes/kubernetes/pull/35496 - [ ] `edit` - [ ] decode into runtime.Unstructured objects - [ ] fall back to generic JSON patch computation if no go struct is registered for the target GVK
parents 10e3edca 17168aaa
...@@ -1202,6 +1202,73 @@ __EOF__ ...@@ -1202,6 +1202,73 @@ __EOF__
# Test that we can list this new third party resource # Test that we can list this new third party resource
kube::test::get_object_assert foos "{{range.items}}{{$id_field}}:{{end}}" 'test:' 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}}" '<no value>'
# 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 # Delete the resource
kubectl "${kube_flags[@]}" delete foos test kubectl "${kube_flags[@]}" delete foos test
......
...@@ -109,6 +109,7 @@ go_library( ...@@ -109,6 +109,7 @@ go_library(
"//vendor:k8s.io/apimachinery/pkg/util/validation", "//vendor:k8s.io/apimachinery/pkg/util/validation",
"//vendor:k8s.io/apimachinery/pkg/util/wait", "//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/apimachinery/pkg/watch", "//vendor:k8s.io/apimachinery/pkg/watch",
"//vendor:k8s.io/client-go/dynamic",
"//vendor:k8s.io/client-go/rest", "//vendor:k8s.io/client-go/rest",
"//vendor:k8s.io/client-go/util/integer", "//vendor:k8s.io/client-go/util/integer",
"//vendor:k8s.io/client-go/util/jsonpath", "//vendor:k8s.io/client-go/util/jsonpath",
......
...@@ -113,6 +113,7 @@ go_library( ...@@ -113,6 +113,7 @@ go_library(
"//vendor:k8s.io/apimachinery/pkg/runtime/schema", "//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/types", "//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/errors", "//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/sets",
"//vendor:k8s.io/apimachinery/pkg/util/strategicpatch", "//vendor:k8s.io/apimachinery/pkg/util/strategicpatch",
"//vendor:k8s.io/apimachinery/pkg/util/validation", "//vendor:k8s.io/apimachinery/pkg/util/validation",
......
...@@ -18,17 +18,20 @@ package cmd ...@@ -18,17 +18,20 @@ package cmd
import ( import (
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"io" "io"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 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/runtime"
"k8s.io/apimachinery/pkg/types" "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"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
...@@ -181,8 +184,12 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro ...@@ -181,8 +184,12 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
} }
changeCause := f.Command() 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(). ContinueOnError().
NamespaceParam(namespace).DefaultNamespace(). NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions). FilenameParam(enforceNamespace, &o.FilenameOptions).
...@@ -241,21 +248,21 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro ...@@ -241,21 +248,21 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
if err != nil { if err != nil {
return err return err
} }
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) patchBytes, err := jsonpatch.CreateMergePatch(oldData, newData)
createdPatch := err == nil createdPatch := err == nil
if err != nil { if err != nil {
glog.V(2).Infof("couldn't compute patch: %v", err) glog.V(2).Infof("couldn't compute patch: %v", err)
} }
mapping := info.ResourceMapping() mapping := info.ResourceMapping()
client, err := f.ClientForMapping(mapping) client, err := f.UnstructuredClientForMapping(mapping)
if err != nil { if err != nil {
return err return err
} }
helper := resource.NewHelper(client, mapping) helper := resource.NewHelper(client, mapping)
if createdPatch { if createdPatch {
outputObj, err = helper.Patch(namespace, name, types.StrategicMergePatchType, patchBytes) outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes)
} else { } else {
outputObj, err = helper.Replace(namespace, name, false, obj) outputObj, err = helper.Replace(namespace, name, false, obj)
} }
......
...@@ -424,11 +424,11 @@ func TestAnnotateErrors(t *testing.T) { ...@@ -424,11 +424,11 @@ func TestAnnotateErrors(t *testing.T) {
func TestAnnotateObject(t *testing.T) { func TestAnnotateObject(t *testing.T) {
pods, _, _ := testData() pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method { switch req.Method {
case "GET": case "GET":
...@@ -475,11 +475,11 @@ func TestAnnotateObject(t *testing.T) { ...@@ -475,11 +475,11 @@ func TestAnnotateObject(t *testing.T) {
func TestAnnotateObjectFromFile(t *testing.T) { func TestAnnotateObjectFromFile(t *testing.T) {
pods, _, _ := testData() pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method { switch req.Method {
case "GET": case "GET":
...@@ -525,10 +525,10 @@ func TestAnnotateObjectFromFile(t *testing.T) { ...@@ -525,10 +525,10 @@ func TestAnnotateObjectFromFile(t *testing.T) {
} }
func TestAnnotateLocal(t *testing.T) { func TestAnnotateLocal(t *testing.T) {
f, tf, _, ns := cmdtesting.NewAPIFactory() f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil return nil, nil
...@@ -557,11 +557,11 @@ func TestAnnotateLocal(t *testing.T) { ...@@ -557,11 +557,11 @@ func TestAnnotateLocal(t *testing.T) {
func TestAnnotateMultipleObjects(t *testing.T) { func TestAnnotateMultipleObjects(t *testing.T) {
pods, _, _ := testData() pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method { switch req.Method {
case "GET": case "GET":
......
...@@ -22,12 +22,14 @@ import ( ...@@ -22,12 +22,14 @@ import (
"strings" "strings"
"time" "time"
"github.com/golang/glog"
"github.com/jonboulle/clockwork" "github.com/jonboulle/clockwork"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 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/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
...@@ -182,8 +184,11 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti ...@@ -182,8 +184,11 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti
} }
} }
mapper, typer := f.Object() 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), unstructured.UnstructuredJSONScheme).
Schema(schema). Schema(schema).
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
...@@ -292,13 +297,10 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti ...@@ -292,13 +297,10 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti
} }
if cmdutil.ShouldRecord(cmd, info) { if cmdutil.ShouldRecord(cmd, info) {
patch, err := cmdutil.ChangeResourcePatch(info, f.Command()) if patch, patchType, err := cmdutil.ChangeResourcePatch(info, f.Command()); err == nil {
if err != nil { if _, err = helper.Patch(info.Namespace, info.Name, patchType, patch); err != nil {
return err glog.V(4).Infof("error recording reason: %v", 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)
} }
} }
......
...@@ -197,11 +197,11 @@ func TestApplyObjectWithoutAnnotation(t *testing.T) { ...@@ -197,11 +197,11 @@ func TestApplyObjectWithoutAnnotation(t *testing.T) {
nameRC, rcBytes := readReplicationController(t, filenameRC) nameRC, rcBytes := readReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
f, tf, _, ns := cmdtesting.NewAPIFactory() f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
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 == pathRC && m == "GET": case p == pathRC && m == "GET":
...@@ -242,11 +242,11 @@ func TestApplyObject(t *testing.T) { ...@@ -242,11 +242,11 @@ func TestApplyObject(t *testing.T) {
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC) nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
f, tf, _, ns := cmdtesting.NewAPIFactory() f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
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 == pathRC && m == "GET": case p == pathRC && m == "GET":
...@@ -286,11 +286,11 @@ func TestApplyRetry(t *testing.T) { ...@@ -286,11 +286,11 @@ func TestApplyRetry(t *testing.T) {
firstPatch := true firstPatch := true
retry := false retry := false
getCount := 0 getCount := 0
f, tf, _, ns := cmdtesting.NewAPIFactory() f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
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 == pathRC && m == "GET": case p == pathRC && m == "GET":
...@@ -340,11 +340,11 @@ func TestApplyNonExistObject(t *testing.T) { ...@@ -340,11 +340,11 @@ func TestApplyNonExistObject(t *testing.T) {
pathRC := "/namespaces/test/replicationcontrollers" pathRC := "/namespaces/test/replicationcontrollers"
pathNameRC := pathRC + "/" + nameRC pathNameRC := pathRC + "/" + nameRC
f, tf, _, ns := cmdtesting.NewAPIFactory() f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
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 == "/api/v1/namespaces/test" && m == "GET": case p == "/api/v1/namespaces/test" && m == "GET":
...@@ -391,11 +391,11 @@ func testApplyMultipleObjects(t *testing.T, asList bool) { ...@@ -391,11 +391,11 @@ func testApplyMultipleObjects(t *testing.T, asList bool) {
nameSVC, currentSVC := readAndAnnotateService(t, filenameSVC) nameSVC, currentSVC := readAndAnnotateService(t, filenameSVC)
pathSVC := "/namespaces/test/services/" + nameSVC pathSVC := "/namespaces/test/services/" + nameSVC
f, tf, _, ns := cmdtesting.NewAPIFactory() f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
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 == pathRC && m == "GET": case p == pathRC && m == "GET":
......
...@@ -21,7 +21,6 @@ import ( ...@@ -21,7 +21,6 @@ import (
"net/http" "net/http"
"testing" "testing"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest/fake" "k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
...@@ -45,11 +44,10 @@ func TestCreateObject(t *testing.T) { ...@@ -45,11 +44,10 @@ func TestCreateObject(t *testing.T) {
rc.Items[0].Name = "redis-master-controller" rc.Items[0].Name = "redis-master-controller"
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
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 == http.MethodPost: case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
...@@ -80,11 +78,10 @@ func TestCreateMultipleObject(t *testing.T) { ...@@ -80,11 +78,10 @@ func TestCreateMultipleObject(t *testing.T) {
_, svc, rc := testData() _, svc, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
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 == http.MethodPost: case p == "/namespaces/test/services" && m == http.MethodPost:
...@@ -119,11 +116,10 @@ func TestCreateDirectory(t *testing.T) { ...@@ -119,11 +116,10 @@ func TestCreateDirectory(t *testing.T) {
rc.Items[0].Name = "name" rc.Items[0].Name = "name"
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
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 == http.MethodPost: case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
......
...@@ -43,7 +43,7 @@ func TestDeleteObjectByTuple(t *testing.T) { ...@@ -43,7 +43,7 @@ func TestDeleteObjectByTuple(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -76,7 +76,7 @@ func TestDeleteNamedObject(t *testing.T) { ...@@ -76,7 +76,7 @@ func TestDeleteNamedObject(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -109,7 +109,7 @@ func TestDeleteObject(t *testing.T) { ...@@ -109,7 +109,7 @@ func TestDeleteObject(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -167,7 +167,7 @@ func TestDeleteObjectGraceZero(t *testing.T) { ...@@ -167,7 +167,7 @@ func TestDeleteObjectGraceZero(t *testing.T) {
count := 0 count := 0
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -216,7 +216,7 @@ func TestDeleteObjectGraceZero(t *testing.T) { ...@@ -216,7 +216,7 @@ func TestDeleteObjectGraceZero(t *testing.T) {
func TestDeleteObjectNotFound(t *testing.T) { func TestDeleteObjectNotFound(t *testing.T) {
f, tf, _, _ := cmdtesting.NewAPIFactory() f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -253,7 +253,7 @@ func TestDeleteObjectNotFound(t *testing.T) { ...@@ -253,7 +253,7 @@ func TestDeleteObjectNotFound(t *testing.T) {
func TestDeleteObjectIgnoreNotFound(t *testing.T) { func TestDeleteObjectIgnoreNotFound(t *testing.T) {
f, tf, _, _ := cmdtesting.NewAPIFactory() f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -290,7 +290,7 @@ func TestDeleteAllNotFound(t *testing.T) { ...@@ -290,7 +290,7 @@ func TestDeleteAllNotFound(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -339,7 +339,7 @@ func TestDeleteAllIgnoreNotFound(t *testing.T) { ...@@ -339,7 +339,7 @@ func TestDeleteAllIgnoreNotFound(t *testing.T) {
notFoundError := &errors.NewNotFound(api.Resource("services"), "foo").ErrStatus notFoundError := &errors.NewNotFound(api.Resource("services"), "foo").ErrStatus
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -375,7 +375,7 @@ func TestDeleteMultipleObject(t *testing.T) { ...@@ -375,7 +375,7 @@ func TestDeleteMultipleObject(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -410,7 +410,7 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) { ...@@ -410,7 +410,7 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -454,7 +454,7 @@ func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) { ...@@ -454,7 +454,7 @@ func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) {
_, svc, rc := testData() _, svc, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -492,7 +492,7 @@ func TestDeleteDirectory(t *testing.T) { ...@@ -492,7 +492,7 @@ func TestDeleteDirectory(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -524,7 +524,7 @@ func TestDeleteMultipleSelector(t *testing.T) { ...@@ -524,7 +524,7 @@ func TestDeleteMultipleSelector(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
......
...@@ -25,6 +25,7 @@ import ( ...@@ -25,6 +25,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
...@@ -111,8 +112,11 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a ...@@ -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.") return cmdutil.UsageError(cmd, "Required resource not specified.")
} }
mapper, typer := f.Object() 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), unstructured.UnstructuredJSONScheme).
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces).
FilenameParam(enforceNamespace, options). FilenameParam(enforceNamespace, options).
...@@ -168,7 +172,11 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a ...@@ -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 { 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(). NamespaceParam(namespace).DefaultNamespace().
ResourceTypeOrNameArgs(true, rsrc). ResourceTypeOrNameArgs(true, rsrc).
SingleResourceType(). SingleResourceType().
......
...@@ -30,11 +30,11 @@ import ( ...@@ -30,11 +30,11 @@ import (
// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get. // Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get.
func TestDescribeUnknownSchemaObject(t *testing.T) { func TestDescribeUnknownSchemaObject(t *testing.T) {
d := &testDescriber{Output: "test output"} d := &testDescriber{Output: "test output"}
f, tf, codec, ns := cmdtesting.NewTestFactory() f, tf, codec, _ := cmdtesting.NewTestFactory()
tf.Describer = d tf.Describer = d
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))}, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))},
} }
tf.Namespace = "non-default" tf.Namespace = "non-default"
...@@ -43,6 +43,31 @@ func TestDescribeUnknownSchemaObject(t *testing.T) { ...@@ -43,6 +43,31 @@ func TestDescribeUnknownSchemaObject(t *testing.T) {
cmd := NewCmdDescribe(f, buf, buferr) cmd := NewCmdDescribe(f, buf, buferr)
cmd.Run(cmd, []string{"type", "foo"}) 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" { if d.Name != "foo" || d.Namespace != "non-default" {
t.Errorf("unexpected describer: %#v", d) t.Errorf("unexpected describer: %#v", d)
} }
...@@ -54,12 +79,12 @@ func TestDescribeUnknownSchemaObject(t *testing.T) { ...@@ -54,12 +79,12 @@ func TestDescribeUnknownSchemaObject(t *testing.T) {
func TestDescribeObject(t *testing.T) { func TestDescribeObject(t *testing.T) {
_, _, rc := testData() _, _, rc := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
d := &testDescriber{Output: "test output"} d := &testDescriber{Output: "test output"}
tf.Describer = d tf.Describer = d
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
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": case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "GET":
...@@ -88,12 +113,12 @@ func TestDescribeObject(t *testing.T) { ...@@ -88,12 +113,12 @@ func TestDescribeObject(t *testing.T) {
func TestDescribeListObjects(t *testing.T) { func TestDescribeListObjects(t *testing.T) {
pods, _, _ := testData() pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
d := &testDescriber{Output: "test output"} d := &testDescriber{Output: "test output"}
tf.Describer = d tf.Describer = d
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
} }
...@@ -109,12 +134,12 @@ func TestDescribeListObjects(t *testing.T) { ...@@ -109,12 +134,12 @@ func TestDescribeListObjects(t *testing.T) {
func TestDescribeObjectShowEvents(t *testing.T) { func TestDescribeObjectShowEvents(t *testing.T) {
pods, _, _ := testData() pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
d := &testDescriber{Output: "test output"} d := &testDescriber{Output: "test output"}
tf.Describer = d tf.Describer = d
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
} }
...@@ -131,12 +156,12 @@ func TestDescribeObjectShowEvents(t *testing.T) { ...@@ -131,12 +156,12 @@ func TestDescribeObjectShowEvents(t *testing.T) {
func TestDescribeObjectSkipEvents(t *testing.T) { func TestDescribeObjectSkipEvents(t *testing.T) {
pods, _, _ := testData() pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
d := &testDescriber{Output: "test output"} d := &testDescriber{Output: "test output"}
tf.Describer = d tf.Describer = d
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
} }
......
...@@ -120,7 +120,7 @@ func TestGetUnknownSchemaObject(t *testing.T) { ...@@ -120,7 +120,7 @@ func TestGetUnknownSchemaObject(t *testing.T) {
f, tf, _, _ := cmdtesting.NewAPIFactory() f, tf, _, _ := cmdtesting.NewAPIFactory()
_, _, codec, _ := cmdtesting.NewTestFactory() _, _, codec, _ := cmdtesting.NewTestFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))}, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))},
...@@ -165,7 +165,7 @@ func TestGetSchemaObject(t *testing.T) { ...@@ -165,7 +165,7 @@ func TestGetSchemaObject(t *testing.T) {
tf.Typer = api.Scheme tf.Typer = api.Scheme
codec := testapi.Default.Codec() codec := testapi.Default.Codec()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.ReplicationController{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})}, 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) { ...@@ -188,7 +188,7 @@ func TestGetObjects(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])},
...@@ -232,7 +232,7 @@ func TestGetSortedObjects(t *testing.T) { ...@@ -232,7 +232,7 @@ func TestGetSortedObjects(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
...@@ -291,7 +291,7 @@ func TestGetObjectsIdentifiedByFile(t *testing.T) { ...@@ -291,7 +291,7 @@ func TestGetObjectsIdentifiedByFile(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])},
...@@ -318,7 +318,7 @@ func TestGetListObjects(t *testing.T) { ...@@ -318,7 +318,7 @@ func TestGetListObjects(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
...@@ -361,7 +361,7 @@ func TestGetAllListObjects(t *testing.T) { ...@@ -361,7 +361,7 @@ func TestGetAllListObjects(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
...@@ -391,7 +391,7 @@ func TestGetListComponentStatus(t *testing.T) { ...@@ -391,7 +391,7 @@ func TestGetListComponentStatus(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, statuses)}, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, statuses)},
...@@ -420,7 +420,7 @@ func TestGetMultipleTypeObjects(t *testing.T) { ...@@ -420,7 +420,7 @@ func TestGetMultipleTypeObjects(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -459,7 +459,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) { ...@@ -459,7 +459,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -521,7 +521,7 @@ func TestGetMultipleTypeObjectsWithSelector(t *testing.T) { ...@@ -521,7 +521,7 @@ func TestGetMultipleTypeObjectsWithSelector(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -573,7 +573,7 @@ func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) { ...@@ -573,7 +573,7 @@ func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -610,7 +610,7 @@ func TestGetByNameForcesFlag(t *testing.T) { ...@@ -610,7 +610,7 @@ func TestGetByNameForcesFlag(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])},
...@@ -710,7 +710,7 @@ func TestWatchSelector(t *testing.T) { ...@@ -710,7 +710,7 @@ func TestWatchSelector(t *testing.T) {
ResourceVersion: "10", ResourceVersion: "10",
}, },
} }
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -752,7 +752,7 @@ func TestWatchResource(t *testing.T) { ...@@ -752,7 +752,7 @@ func TestWatchResource(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -790,7 +790,7 @@ func TestWatchResourceIdentifiedByFile(t *testing.T) { ...@@ -790,7 +790,7 @@ func TestWatchResourceIdentifiedByFile(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -829,7 +829,7 @@ func TestWatchOnlyResource(t *testing.T) { ...@@ -829,7 +829,7 @@ func TestWatchOnlyResource(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
...@@ -873,7 +873,7 @@ func TestWatchOnlyList(t *testing.T) { ...@@ -873,7 +873,7 @@ func TestWatchOnlyList(t *testing.T) {
ResourceVersion: "10", ResourceVersion: "10",
}, },
} }
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
......
...@@ -17,21 +17,24 @@ limitations under the License. ...@@ -17,21 +17,24 @@ limitations under the License.
package cmd package cmd
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"reflect" "reflect"
"strings" "strings"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 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/runtime"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors" 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/apimachinery/pkg/util/validation"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
...@@ -175,8 +178,12 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { ...@@ -175,8 +178,12 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
} }
changeCause := f.Command() 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(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions). FilenameParam(enforceNamespace, &o.FilenameOptions).
...@@ -213,10 +220,7 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { ...@@ -213,10 +220,7 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
} }
outputObj = info.Object outputObj = info.Object
} else { } else {
obj, err := cmdutil.MaybeConvertObject(info.Object, info.Mapping.GroupVersionKind.GroupVersion(), info.Mapping) obj := info.Object
if err != nil {
return err
}
name, namespace := info.Name, info.Namespace name, namespace := info.Name, info.Namespace
oldData, err := json.Marshal(obj) oldData, err := json.Marshal(obj)
if err != nil { if err != nil {
...@@ -247,21 +251,21 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { ...@@ -247,21 +251,21 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
if !reflect.DeepEqual(oldData, newData) { if !reflect.DeepEqual(oldData, newData) {
dataChangeMsg = "labeled" dataChangeMsg = "labeled"
} }
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj) patchBytes, err := jsonpatch.CreateMergePatch(oldData, newData)
createdPatch := err == nil createdPatch := err == nil
if err != nil { if err != nil {
glog.V(2).Infof("couldn't compute patch: %v", err) glog.V(2).Infof("couldn't compute patch: %v", err)
} }
mapping := info.ResourceMapping() mapping := info.ResourceMapping()
client, err := f.ClientForMapping(mapping) client, err := f.UnstructuredClientForMapping(mapping)
if err != nil { if err != nil {
return err return err
} }
helper := resource.NewHelper(client, mapping) helper := resource.NewHelper(client, mapping)
if createdPatch { if createdPatch {
outputObj, err = helper.Patch(namespace, name, types.StrategicMergePatchType, patchBytes) outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes)
} else { } else {
outputObj, err = helper.Replace(namespace, name, false, obj) outputObj, err = helper.Replace(namespace, name, false, obj)
} }
......
...@@ -347,10 +347,10 @@ func TestLabelErrors(t *testing.T) { ...@@ -347,10 +347,10 @@ func TestLabelErrors(t *testing.T) {
func TestLabelForResourceFromFile(t *testing.T) { func TestLabelForResourceFromFile(t *testing.T) {
pods, _, _ := testData() pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method { switch req.Method {
case "GET": case "GET":
...@@ -398,10 +398,10 @@ func TestLabelForResourceFromFile(t *testing.T) { ...@@ -398,10 +398,10 @@ func TestLabelForResourceFromFile(t *testing.T) {
} }
func TestLabelLocal(t *testing.T) { func TestLabelLocal(t *testing.T) {
f, tf, _, ns := cmdtesting.NewAPIFactory() f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil return nil, nil
...@@ -432,10 +432,10 @@ func TestLabelLocal(t *testing.T) { ...@@ -432,10 +432,10 @@ func TestLabelLocal(t *testing.T) {
func TestLabelMultipleObjects(t *testing.T) { func TestLabelMultipleObjects(t *testing.T) {
pods, _, _ := testData() pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method { switch req.Method {
case "GET": case "GET":
......
...@@ -17,17 +17,20 @@ limitations under the License. ...@@ -17,17 +17,20 @@ limitations under the License.
package cmd package cmd
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"reflect" "reflect"
"strings" "strings"
jsonpatch "github.com/evanphx/json-patch" jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/yaml" "k8s.io/apimachinery/pkg/util/yaml"
...@@ -144,8 +147,11 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin ...@@ -144,8 +147,11 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
return fmt.Errorf("unable to parse %q: %v", patch, err) return fmt.Errorf("unable to parse %q: %v", patch, err)
} }
mapper, typer := f.Object() 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), unstructured.UnstructuredJSONScheme).
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &options.FilenameOptions). FilenameParam(enforceNamespace, &options.FilenameOptions).
...@@ -164,7 +170,7 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin ...@@ -164,7 +170,7 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
} }
name, namespace := info.Name, info.Namespace name, namespace := info.Name, info.Namespace
mapping := info.ResourceMapping() mapping := info.ResourceMapping()
client, err := f.ClientForMapping(mapping) client, err := f.UnstructuredClientForMapping(mapping)
if err != nil { if err != nil {
return err return err
} }
...@@ -176,14 +182,16 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin ...@@ -176,14 +182,16 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
if err != nil { if err != nil {
return err 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) { 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 // Copy the resource info and update with the result of applying the user's patch
// don't bother checking for failures of this replace, because a failure to indicate the hint doesn't fail the command infoCopy := *info
// also, don't force the replacement. If the replacement fails on a resourceVersion conflict, then it means this infoCopy.Object = patchedObj
// record hint is likely to be invalid anyway, so avoid the bad hint infoCopy.VersionedObject = patchedObj
patch, err := cmdutil.ChangeResourcePatch(info, f.Command()) if patch, patchType, err := cmdutil.ChangeResourcePatch(&infoCopy, f.Command()); err == nil {
if err == nil { if _, err = helper.Patch(info.Namespace, info.Name, patchType, patch); err != nil {
helper.Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch) glog.V(4).Infof("error recording reason: %v", err)
}
} }
} }
count++ count++
...@@ -208,19 +216,15 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin ...@@ -208,19 +216,15 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
count++ 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))
if err != nil { if err != nil {
return err return err
} }
originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, patchedObj.(runtime.Object)) originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, mapping.GroupVersionKind, api.Scheme)
if err != nil { if err != nil {
return err return err
} }
targetObj, err := runtime.Decode(api.Codecs.UniversalDecoder(), originalPatchedObjJS) targetObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, originalPatchedObjJS)
if err != nil { if err != nil {
return err return err
} }
...@@ -248,7 +252,7 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin ...@@ -248,7 +252,7 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
return nil 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 { switch patchType {
case types.JSONPatchType: case types.JSONPatchType:
patchObj, err := jsonpatch.DecodePatch(patchJS) patchObj, err := jsonpatch.DecodePatch(patchJS)
...@@ -261,6 +265,11 @@ func getPatchedJSON(patchType types.PatchType, originalJS, patchJS []byte, obj r ...@@ -261,6 +265,11 @@ func getPatchedJSON(patchType types.PatchType, originalJS, patchJS []byte, obj r
return jsonpatch.MergePatch(originalJS, patchJS) return jsonpatch.MergePatch(originalJS, patchJS)
case types.StrategicMergePatchType: 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) return strategicpatch.StrategicMergePatch(originalJS, patchJS, obj)
default: default:
......
...@@ -29,11 +29,11 @@ import ( ...@@ -29,11 +29,11 @@ import (
func TestPatchObject(t *testing.T) { func TestPatchObject(t *testing.T) {
_, svc, _ := testData() _, svc, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
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/frontend" && (m == "PATCH" || m == "GET"): case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"):
...@@ -62,11 +62,11 @@ func TestPatchObject(t *testing.T) { ...@@ -62,11 +62,11 @@ func TestPatchObject(t *testing.T) {
func TestPatchObjectFromFile(t *testing.T) { func TestPatchObjectFromFile(t *testing.T) {
_, svc, _ := testData() _, svc, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
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/frontend" && (m == "PATCH" || m == "GET"): case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"):
......
...@@ -22,7 +22,6 @@ import ( ...@@ -22,7 +22,6 @@ import (
"strings" "strings"
"testing" "testing"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest/fake" "k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
...@@ -32,12 +31,11 @@ func TestReplaceObject(t *testing.T) { ...@@ -32,12 +31,11 @@ func TestReplaceObject(t *testing.T) {
_, _, rc := testData() _, _, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
deleted := false deleted := false
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
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 == "/api/v1/namespaces/test" && m == http.MethodGet: case p == "/api/v1/namespaces/test" && m == http.MethodGet:
...@@ -89,13 +87,12 @@ func TestReplaceMultipleObject(t *testing.T) { ...@@ -89,13 +87,12 @@ func TestReplaceMultipleObject(t *testing.T) {
_, svc, rc := testData() _, svc, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
redisMasterDeleted := false redisMasterDeleted := false
frontendDeleted := false frontendDeleted := false
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
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 == "/api/v1/namespaces/test" && m == http.MethodGet: case p == "/api/v1/namespaces/test" && m == http.MethodGet:
...@@ -160,12 +157,11 @@ func TestReplaceDirectory(t *testing.T) { ...@@ -160,12 +157,11 @@ func TestReplaceDirectory(t *testing.T) {
_, _, rc := testData() _, _, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
created := map[string]bool{} created := map[string]bool{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
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 == "/api/v1/namespaces/test" && m == http.MethodGet: case p == "/api/v1/namespaces/test" && m == http.MethodGet:
...@@ -218,11 +214,10 @@ func TestForceReplaceObjectNotFound(t *testing.T) { ...@@ -218,11 +214,10 @@ func TestForceReplaceObjectNotFound(t *testing.T) {
_, _, rc := testData() _, _, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: ns, NegotiatedSerializer: unstructuredSerializer,
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 == "/api/v1/namespaces/test" && m == http.MethodGet: case p == "/api/v1/namespaces/test" && m == http.MethodGet:
......
...@@ -23,7 +23,6 @@ import ( ...@@ -23,7 +23,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" 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 ...@@ -164,7 +163,7 @@ func RunScale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
return err return err
} }
if cmdutil.ShouldRecord(cmd, info) { if cmdutil.ShouldRecord(cmd, info) {
patchBytes, err := cmdutil.ChangeResourcePatch(info, f.Command()) patchBytes, patchType, err := cmdutil.ChangeResourcePatch(info, f.Command())
if err != nil { if err != nil {
return err return err
} }
...@@ -174,7 +173,7 @@ func RunScale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin ...@@ -174,7 +173,7 @@ func RunScale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
return err return err
} }
helper := resource.NewHelper(client, mapping) 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 { if err != nil {
return err return err
} }
......
...@@ -238,8 +238,8 @@ func (o *ImageOptions) Run() error { ...@@ -238,8 +238,8 @@ func (o *ImageOptions) Run() error {
// record this change (for rollout history) // record this change (for rollout history)
if o.Record || cmdutil.ContainsChangeCause(info) { if o.Record || cmdutil.ContainsChangeCause(info) {
if patch, err := cmdutil.ChangeResourcePatch(info, o.ChangeCause); 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, types.StrategicMergePatchType, patch); 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) fmt.Fprintf(o.Err, "WARNING: changes to %s/%s can't be recorded: %v\n", info.Mapping.Resource, info.Name, err)
} }
} }
......
...@@ -93,6 +93,60 @@ func NewInternalType(kind, apiversion, name string) *InternalType { ...@@ -93,6 +93,60 @@ func NewInternalType(kind, apiversion, name string) *InternalType {
return &item 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") var versionErr = errors.New("not a version")
func versionErrIfFalse(b bool) error { func versionErrIfFalse(b bool) error {
...@@ -109,11 +163,17 @@ var ValidVersionGV = schema.GroupVersion{Group: "apitest", Version: ValidVersion ...@@ -109,11 +163,17 @@ var ValidVersionGV = schema.GroupVersion{Group: "apitest", Version: ValidVersion
func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) { func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) {
scheme := runtime.NewScheme() scheme := runtime.NewScheme()
scheme.AddKnownTypeWithName(InternalGV.WithKind("Type"), &InternalType{}) scheme.AddKnownTypeWithName(InternalGV.WithKind("Type"), &InternalType{})
scheme.AddKnownTypeWithName(UnlikelyGV.WithKind("Type"), &ExternalType{}) 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. //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(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) codecs := serializer.NewCodecFactory(scheme)
codec := codecs.LegacyCodec(UnlikelyGV) codec := codecs.LegacyCodec(UnlikelyGV)
mapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{UnlikelyGV, ValidVersionGV}, func(version schema.GroupVersion) (*meta.VersionInterfaces, error) { mapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{UnlikelyGV, ValidVersionGV}, func(version schema.GroupVersion) (*meta.VersionInterfaces, error) {
...@@ -146,15 +206,16 @@ func (d *fakeCachedDiscoveryClient) Invalidate() { ...@@ -146,15 +206,16 @@ func (d *fakeCachedDiscoveryClient) Invalidate() {
} }
type TestFactory struct { type TestFactory struct {
Mapper meta.RESTMapper Mapper meta.RESTMapper
Typer runtime.ObjectTyper Typer runtime.ObjectTyper
Client kubectl.RESTClient Client kubectl.RESTClient
Describer kubectl.Describer UnstructuredClient kubectl.RESTClient
Printer kubectl.ResourcePrinter Describer kubectl.Describer
Validator validation.Schema Printer kubectl.ResourcePrinter
Namespace string Validator validation.Schema
ClientConfig *restclient.Config Namespace string
Err error ClientConfig *restclient.Config
Err error
} }
type FakeFactory struct { type FakeFactory struct {
...@@ -251,7 +312,7 @@ func (f *FakeFactory) ClientConfigForVersion(requiredVersion *schema.GroupVersio ...@@ -251,7 +312,7 @@ func (f *FakeFactory) ClientConfigForVersion(requiredVersion *schema.GroupVersio
} }
func (f *FakeFactory) UnstructuredClientForMapping(*meta.RESTMapping) (resource.RESTClient, error) { 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) { func (f *FakeFactory) Describer(*meta.RESTMapping) (kubectl.Describer, error) {
...@@ -497,7 +558,7 @@ func (f *fakeAPIFactory) ClientForMapping(*meta.RESTMapping) (resource.RESTClien ...@@ -497,7 +558,7 @@ func (f *fakeAPIFactory) ClientForMapping(*meta.RESTMapping) (resource.RESTClien
} }
func (f *fakeAPIFactory) UnstructuredClientForMapping(*meta.RESTMapping) (resource.RESTClient, error) { 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) { func (f *fakeAPIFactory) Describer(*meta.RESTMapping) (kubectl.Describer, error) {
...@@ -602,6 +663,7 @@ func testDynamicResources() []*discovery.APIGroupResources { ...@@ -602,6 +663,7 @@ func testDynamicResources() []*discovery.APIGroupResources {
{Name: "componentstatuses", Namespaced: false, Kind: "ComponentStatus"}, {Name: "componentstatuses", Namespaced: false, Kind: "ComponentStatus"},
{Name: "nodes", Namespaced: false, Kind: "Node"}, {Name: "nodes", Namespaced: false, Kind: "Node"},
{Name: "type", Namespaced: false, Kind: "Type"}, {Name: "type", Namespaced: false, Kind: "Type"},
{Name: "namespacedtype", Namespaced: true, Kind: "NamespacedType"},
}, },
}, },
}, },
......
...@@ -38,7 +38,6 @@ go_library( ...@@ -38,7 +38,6 @@ go_library(
"//pkg/controller:go_default_library", "//pkg/controller:go_default_library",
"//pkg/kubectl:go_default_library", "//pkg/kubectl:go_default_library",
"//pkg/kubectl/resource:go_default_library", "//pkg/kubectl/resource:go_default_library",
"//pkg/registry/extensions/thirdpartyresourcedata:go_default_library",
"//pkg/util/exec:go_default_library", "//pkg/util/exec:go_default_library",
"//pkg/version:go_default_library", "//pkg/version:go_default_library",
"//vendor:github.com/emicklei/go-restful/swagger", "//vendor:github.com/emicklei/go-restful/swagger",
...@@ -54,7 +53,9 @@ go_library( ...@@ -54,7 +53,9 @@ go_library(
"//vendor:k8s.io/apimachinery/pkg/runtime", "//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema", "//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/json", "//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/errors",
"//vendor:k8s.io/apimachinery/pkg/util/json",
"//vendor:k8s.io/apimachinery/pkg/util/sets", "//vendor:k8s.io/apimachinery/pkg/util/sets",
"//vendor:k8s.io/apimachinery/pkg/util/strategicpatch", "//vendor:k8s.io/apimachinery/pkg/util/strategicpatch",
"//vendor:k8s.io/apimachinery/pkg/version", "//vendor:k8s.io/apimachinery/pkg/version",
......
...@@ -51,7 +51,6 @@ import ( ...@@ -51,7 +51,6 @@ import (
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata"
) )
const ( const (
...@@ -241,21 +240,6 @@ func getGroupVersionKinds(gvks []schema.GroupVersionKind, group string) []schema ...@@ -241,21 +240,6 @@ func getGroupVersionKinds(gvks []schema.GroupVersionKind, group string) []schema
return result 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 { type factory struct {
ClientAccessFactory ClientAccessFactory
ObjectMappingFactory ObjectMappingFactory
......
...@@ -25,7 +25,6 @@ import ( ...@@ -25,7 +25,6 @@ import (
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
) )
...@@ -44,7 +43,20 @@ func NewBuilderFactory(clientAccessFactory ClientAccessFactory, objectMappingFac ...@@ -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 { 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 { if err != nil {
return err return err
} }
......
...@@ -49,7 +49,6 @@ import ( ...@@ -49,7 +49,6 @@ import (
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata"
) )
type ring0Factory struct { type ring0Factory struct {
...@@ -209,7 +208,7 @@ func (f *ring0Factory) Decoder(toInternal bool) runtime.Decoder { ...@@ -209,7 +208,7 @@ func (f *ring0Factory) Decoder(toInternal bool) runtime.Decoder {
} else { } else {
decoder = api.Codecs.UniversalDeserializer() decoder = api.Codecs.UniversalDeserializer()
} }
return thirdpartyresourcedata.NewDecoder(decoder, "") return decoder
} }
func (f *ring0Factory) JSONEncoder() runtime.Encoder { func (f *ring0Factory) JSONEncoder() runtime.Encoder {
......
...@@ -47,7 +47,6 @@ import ( ...@@ -47,7 +47,6 @@ import (
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata"
) )
type ring1Factory struct { type ring1Factory struct {
...@@ -127,9 +126,6 @@ func (f *ring1Factory) ClientForMapping(mapping *meta.RESTMapping) (resource.RES ...@@ -127,9 +126,6 @@ func (f *ring1Factory) ClientForMapping(mapping *meta.RESTMapping) (resource.RES
} }
gv := gvk.GroupVersion() gv := gvk.GroupVersion()
cfg.GroupVersion = &gv cfg.GroupVersion = &gv
if api.Registry.IsThirdPartyAPIGroupVersion(gvk.GroupVersion()) {
cfg.NegotiatedSerializer = thirdpartyresourcedata.NewNegotiatedSerializer(api.Codecs, gvk.Kind, gv, gv)
}
return restclient.RESTClientFor(cfg) return restclient.RESTClientFor(cfg)
} }
...@@ -162,14 +158,55 @@ func (f *ring1Factory) Describer(mapping *meta.RESTMapping) (kubectl.Describer, ...@@ -162,14 +158,55 @@ func (f *ring1Factory) Describer(mapping *meta.RESTMapping) (kubectl.Describer,
return &kubectl.ClusterDescriber{Interface: fedClientSet}, nil return &kubectl.ClusterDescriber{Interface: fedClientSet}, nil
} }
} }
clientset, err := f.clientAccessFactory.ClientSetForVersion(&mappingVersion) clientset, err := f.clientAccessFactory.ClientSetForVersion(&mappingVersion)
if err != nil { 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 return nil, err
} }
// try to get a describer
if describer, ok := kubectl.DescriberFor(mapping.GroupVersionKind.GroupKind(), clientset); ok { if describer, ok := kubectl.DescriberFor(mapping.GroupVersionKind.GroupKind(), clientset); ok {
return describer, nil 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) { func (f *ring1Factory) LogsForObject(object, options runtime.Object) (*restclient.Request, error) {
......
...@@ -18,7 +18,6 @@ package util ...@@ -18,7 +18,6 @@ package util
import ( import (
"bytes" "bytes"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
...@@ -38,7 +37,9 @@ import ( ...@@ -38,7 +37,9 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
...@@ -520,27 +521,35 @@ func RecordChangeCause(obj runtime.Object, changeCause string) error { ...@@ -520,27 +521,35 @@ func RecordChangeCause(obj runtime.Object, changeCause string) error {
return nil 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. // 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 // Get a versioned object
obj, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion()) obj, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion())
if err != nil { if err != nil {
return nil, err return nil, types.StrategicMergePatchType, err
} }
oldData, err := json.Marshal(obj) oldData, err := json.Marshal(obj)
if err != nil { if err != nil {
return nil, err return nil, types.StrategicMergePatchType, err
} }
if err := RecordChangeCause(obj, changeCause); err != nil { if err := RecordChangeCause(obj, changeCause); err != nil {
return nil, err return nil, types.StrategicMergePatchType, err
} }
newData, err := json.Marshal(obj) newData, err := json.Marshal(obj)
if err != nil { 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. // containsChangeCause checks if input resource info contains change-cause annotation.
......
...@@ -29,12 +29,14 @@ import ( ...@@ -29,12 +29,14 @@ import (
"time" "time"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/dynamic"
"k8s.io/kubernetes/federation/apis/federation" "k8s.io/kubernetes/federation/apis/federation"
fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset" fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
...@@ -171,6 +173,46 @@ func DescriberFor(kind schema.GroupKind, c clientset.Interface) (Describer, bool ...@@ -171,6 +173,46 @@ func DescriberFor(kind schema.GroupKind, c clientset.Interface) (Describer, bool
return f, ok 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. // DefaultObjectDescriber can describe the default Kubernetes objects.
var DefaultObjectDescriber ObjectDescriber var DefaultObjectDescriber ObjectDescriber
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment