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
This commit is contained in:
Kubernetes Submit Queue 2017-01-27 05:41:45 -08:00 committed by GitHub
commit 1625150de8
27 changed files with 470 additions and 210 deletions

View File

@ -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

View File

@ -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",

View File

@ -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",

View File

@ -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
} }
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
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)
} }

View File

@ -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) {
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) {
} }
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) {
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":

View File

@ -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
} }
} }
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
} }
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)
} }
} }

View File

@ -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) {
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) {
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) {
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) {
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":

View File

@ -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) {
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) {
_, 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) {
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:

View File

@ -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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
_, 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) {
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) {
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) {

View File

@ -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
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
} }
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().

View File

@ -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) {
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) {
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) {
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) {
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) {
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)},
} }

View File

@ -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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {

View File

@ -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 {
} }
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 {
} }
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 {
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)
} }

View File

@ -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) {
} }
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) {
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":

View File

@ -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
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
} }
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
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
count++ count++
patchedObj, err := api.Scheme.DeepCopy(info.VersionedObject) originalObjJS, err := runtime.Encode(unstructured.UnstructuredJSONScheme, info.VersionedObject)
if err != nil { if err != nil {
return err return err
} }
originalObjJS, err := runtime.Encode(api.Codecs.LegacyCodec(mapping.GroupVersionKind.GroupVersion()), info.VersionedObject.(runtime.Object)) originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, mapping.GroupVersionKind, api.Scheme)
if err != nil { if err != nil {
return err return err
} }
originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, patchedObj.(runtime.Object)) targetObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, originalPatchedObjJS)
if err != nil {
return err
}
targetObj, err := runtime.Decode(api.Codecs.UniversalDecoder(), 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
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
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:

View File

@ -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) {
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"):

View File

@ -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) {
_, _, 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) {
_, 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) {
_, _, 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) {
_, _, 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:

View File

@ -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
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
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
} }

View File

@ -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)
} }
} }

View File

@ -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
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() {
} }
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
} }
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
} }
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 {
{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"},
}, },
}, },
}, },

View File

@ -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(
"//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",

View File

@ -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
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

View File

@ -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
} }
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
} }

View File

@ -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 {
} 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 {

View File

@ -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
} }
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,
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) {

View File

@ -18,7 +18,6 @@ package util
import ( import (
"bytes" "bytes"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -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 {
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.

View File

@ -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
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