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
pull/6/head
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
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
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/wait",
"//vendor:k8s.io/apimachinery/pkg/watch",
"//vendor:k8s.io/client-go/dynamic",
"//vendor:k8s.io/client-go/rest",
"//vendor:k8s.io/client-go/util/integer",
"//vendor:k8s.io/client-go/util/jsonpath",

View File

@ -113,6 +113,7 @@ go_library(
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/errors",
"//vendor:k8s.io/apimachinery/pkg/util/json",
"//vendor:k8s.io/apimachinery/pkg/util/sets",
"//vendor:k8s.io/apimachinery/pkg/util/strategicpatch",
"//vendor:k8s.io/apimachinery/pkg/util/validation",

View File

@ -18,17 +18,20 @@ package cmd
import (
"bytes"
"encoding/json"
"fmt"
"io"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
@ -181,8 +184,12 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
}
changeCause := f.Command()
mapper, typer := f.Object()
b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
ContinueOnError().
NamespaceParam(namespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
@ -241,21 +248,21 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
if err != nil {
return err
}
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj)
patchBytes, err := jsonpatch.CreateMergePatch(oldData, newData)
createdPatch := err == nil
if err != nil {
glog.V(2).Infof("couldn't compute patch: %v", err)
}
mapping := info.ResourceMapping()
client, err := f.ClientForMapping(mapping)
client, err := f.UnstructuredClientForMapping(mapping)
if err != nil {
return err
}
helper := resource.NewHelper(client, mapping)
if createdPatch {
outputObj, err = helper.Patch(namespace, name, types.StrategicMergePatchType, patchBytes)
outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes)
} else {
outputObj, err = helper.Replace(namespace, name, false, obj)
}

View File

@ -424,11 +424,11 @@ func TestAnnotateErrors(t *testing.T) {
func TestAnnotateObject(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method {
case "GET":
@ -475,11 +475,11 @@ func TestAnnotateObject(t *testing.T) {
func TestAnnotateObjectFromFile(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method {
case "GET":
@ -525,10 +525,10 @@ func TestAnnotateObjectFromFile(t *testing.T) {
}
func TestAnnotateLocal(t *testing.T) {
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
@ -557,11 +557,11 @@ func TestAnnotateLocal(t *testing.T) {
func TestAnnotateMultipleObjects(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method {
case "GET":

View File

@ -22,12 +22,14 @@ import (
"strings"
"time"
"github.com/golang/glog"
"github.com/jonboulle/clockwork"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -182,8 +184,11 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti
}
}
mapper, typer := f.Object()
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
Schema(schema).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
@ -292,13 +297,10 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti
}
if cmdutil.ShouldRecord(cmd, info) {
patch, err := cmdutil.ChangeResourcePatch(info, f.Command())
if err != nil {
return err
}
_, err = helper.Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch)
if err != nil {
return cmdutil.AddSourceToErr(fmt.Sprintf("applying patch:\n%s\nto:\n%v\nfor:", patch, info), info.Source, err)
if patch, patchType, err := cmdutil.ChangeResourcePatch(info, f.Command()); err == nil {
if _, err = helper.Patch(info.Namespace, info.Name, patchType, patch); err != nil {
glog.V(4).Infof("error recording reason: %v", err)
}
}
}

View File

@ -197,11 +197,11 @@ func TestApplyObjectWithoutAnnotation(t *testing.T) {
nameRC, rcBytes := readReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
f, tf, _, ns := cmdtesting.NewAPIFactory()
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == pathRC && m == "GET":
@ -242,11 +242,11 @@ func TestApplyObject(t *testing.T) {
nameRC, currentRC := readAndAnnotateReplicationController(t, filenameRC)
pathRC := "/namespaces/test/replicationcontrollers/" + nameRC
f, tf, _, ns := cmdtesting.NewAPIFactory()
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == pathRC && m == "GET":
@ -286,11 +286,11 @@ func TestApplyRetry(t *testing.T) {
firstPatch := true
retry := false
getCount := 0
f, tf, _, ns := cmdtesting.NewAPIFactory()
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == pathRC && m == "GET":
@ -340,11 +340,11 @@ func TestApplyNonExistObject(t *testing.T) {
pathRC := "/namespaces/test/replicationcontrollers"
pathNameRC := pathRC + "/" + nameRC
f, tf, _, ns := cmdtesting.NewAPIFactory()
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/api/v1/namespaces/test" && m == "GET":
@ -391,11 +391,11 @@ func testApplyMultipleObjects(t *testing.T, asList bool) {
nameSVC, currentSVC := readAndAnnotateService(t, filenameSVC)
pathSVC := "/namespaces/test/services/" + nameSVC
f, tf, _, ns := cmdtesting.NewAPIFactory()
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == pathRC && m == "GET":

View File

@ -21,7 +21,6 @@ import (
"net/http"
"testing"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
@ -45,11 +44,10 @@ func TestCreateObject(t *testing.T) {
rc.Items[0].Name = "redis-master-controller"
f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:
@ -80,11 +78,10 @@ func TestCreateMultipleObject(t *testing.T) {
_, svc, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/services" && m == http.MethodPost:
@ -119,11 +116,10 @@ func TestCreateDirectory(t *testing.T) {
rc.Items[0].Name = "name"
f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers" && m == http.MethodPost:

View File

@ -43,7 +43,7 @@ func TestDeleteObjectByTuple(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -76,7 +76,7 @@ func TestDeleteNamedObject(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -109,7 +109,7 @@ func TestDeleteObject(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -167,7 +167,7 @@ func TestDeleteObjectGraceZero(t *testing.T) {
count := 0
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -216,7 +216,7 @@ func TestDeleteObjectGraceZero(t *testing.T) {
func TestDeleteObjectNotFound(t *testing.T) {
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -253,7 +253,7 @@ func TestDeleteObjectNotFound(t *testing.T) {
func TestDeleteObjectIgnoreNotFound(t *testing.T) {
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -290,7 +290,7 @@ func TestDeleteAllNotFound(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -339,7 +339,7 @@ func TestDeleteAllIgnoreNotFound(t *testing.T) {
notFoundError := &errors.NewNotFound(api.Resource("services"), "foo").ErrStatus
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -375,7 +375,7 @@ func TestDeleteMultipleObject(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -410,7 +410,7 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -454,7 +454,7 @@ func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) {
_, svc, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -492,7 +492,7 @@ func TestDeleteDirectory(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -524,7 +524,7 @@ func TestDeleteMultipleSelector(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {

View File

@ -25,6 +25,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
@ -111,8 +112,11 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a
return cmdutil.UsageError(cmd, "Required resource not specified.")
}
mapper, typer := f.Object()
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces).
FilenameParam(enforceNamespace, options).
@ -168,7 +172,11 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a
}
func DescribeMatchingResources(mapper meta.RESTMapper, typer runtime.ObjectTyper, f cmdutil.Factory, namespace, rsrc, prefix string, describerSettings *kubectl.DescriberSettings, out io.Writer, originalError error) error {
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
NamespaceParam(namespace).DefaultNamespace().
ResourceTypeOrNameArgs(true, rsrc).
SingleResourceType().

View File

@ -30,11 +30,11 @@ import (
// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get.
func TestDescribeUnknownSchemaObject(t *testing.T) {
d := &testDescriber{Output: "test output"}
f, tf, codec, ns := cmdtesting.NewTestFactory()
f, tf, codec, _ := cmdtesting.NewTestFactory()
tf.Describer = d
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))},
}
tf.Namespace = "non-default"
@ -43,6 +43,31 @@ func TestDescribeUnknownSchemaObject(t *testing.T) {
cmd := NewCmdDescribe(f, buf, buferr)
cmd.Run(cmd, []string{"type", "foo"})
if d.Name != "foo" || d.Namespace != "" {
t.Errorf("unexpected describer: %#v", d)
}
if buf.String() != fmt.Sprintf("%s", d.Output) {
t.Errorf("unexpected output: %s", buf.String())
}
}
// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get.
func TestDescribeUnknownNamespacedSchemaObject(t *testing.T) {
d := &testDescriber{Output: "test output"}
f, tf, codec, _ := cmdtesting.NewTestFactory()
tf.Describer = d
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalNamespacedType("", "", "foo", "non-default"))},
}
tf.Namespace = "non-default"
buf := bytes.NewBuffer([]byte{})
buferr := bytes.NewBuffer([]byte{})
cmd := NewCmdDescribe(f, buf, buferr)
cmd.Run(cmd, []string{"namespacedtype", "foo"})
if d.Name != "foo" || d.Namespace != "non-default" {
t.Errorf("unexpected describer: %#v", d)
}
@ -54,12 +79,12 @@ func TestDescribeUnknownSchemaObject(t *testing.T) {
func TestDescribeObject(t *testing.T) {
_, _, rc := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
d := &testDescriber{Output: "test output"}
tf.Describer = d
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "GET":
@ -88,12 +113,12 @@ func TestDescribeObject(t *testing.T) {
func TestDescribeListObjects(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
d := &testDescriber{Output: "test output"}
tf.Describer = d
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
}
@ -109,12 +134,12 @@ func TestDescribeListObjects(t *testing.T) {
func TestDescribeObjectShowEvents(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
d := &testDescriber{Output: "test output"}
tf.Describer = d
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
}
@ -131,12 +156,12 @@ func TestDescribeObjectShowEvents(t *testing.T) {
func TestDescribeObjectSkipEvents(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
d := &testDescriber{Output: "test output"}
tf.Describer = d
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
}

View File

@ -120,7 +120,7 @@ func TestGetUnknownSchemaObject(t *testing.T) {
f, tf, _, _ := cmdtesting.NewAPIFactory()
_, _, codec, _ := cmdtesting.NewTestFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))},
@ -165,7 +165,7 @@ func TestGetSchemaObject(t *testing.T) {
tf.Typer = api.Scheme
codec := testapi.Default.Codec()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.ReplicationController{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})},
@ -188,7 +188,7 @@ func TestGetObjects(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])},
@ -232,7 +232,7 @@ func TestGetSortedObjects(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
@ -291,7 +291,7 @@ func TestGetObjectsIdentifiedByFile(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])},
@ -318,7 +318,7 @@ func TestGetListObjects(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
@ -361,7 +361,7 @@ func TestGetAllListObjects(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)},
@ -391,7 +391,7 @@ func TestGetListComponentStatus(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, statuses)},
@ -420,7 +420,7 @@ func TestGetMultipleTypeObjects(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -459,7 +459,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -521,7 +521,7 @@ func TestGetMultipleTypeObjectsWithSelector(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -573,7 +573,7 @@ func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -610,7 +610,7 @@ func TestGetByNameForcesFlag(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])},
@ -710,7 +710,7 @@ func TestWatchSelector(t *testing.T) {
ResourceVersion: "10",
},
}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -752,7 +752,7 @@ func TestWatchResource(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -790,7 +790,7 @@ func TestWatchResourceIdentifiedByFile(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -829,7 +829,7 @@ func TestWatchOnlyResource(t *testing.T) {
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
@ -873,7 +873,7 @@ func TestWatchOnlyList(t *testing.T) {
ResourceVersion: "10",
},
}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {

View File

@ -17,21 +17,24 @@ limitations under the License.
package cmd
import (
"encoding/json"
"fmt"
"io"
"reflect"
"strings"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
@ -175,8 +178,12 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
}
changeCause := f.Command()
mapper, typer := f.Object()
b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
b := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &o.FilenameOptions).
@ -213,10 +220,7 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
}
outputObj = info.Object
} else {
obj, err := cmdutil.MaybeConvertObject(info.Object, info.Mapping.GroupVersionKind.GroupVersion(), info.Mapping)
if err != nil {
return err
}
obj := info.Object
name, namespace := info.Name, info.Namespace
oldData, err := json.Marshal(obj)
if err != nil {
@ -247,21 +251,21 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
if !reflect.DeepEqual(oldData, newData) {
dataChangeMsg = "labeled"
}
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj)
patchBytes, err := jsonpatch.CreateMergePatch(oldData, newData)
createdPatch := err == nil
if err != nil {
glog.V(2).Infof("couldn't compute patch: %v", err)
}
mapping := info.ResourceMapping()
client, err := f.ClientForMapping(mapping)
client, err := f.UnstructuredClientForMapping(mapping)
if err != nil {
return err
}
helper := resource.NewHelper(client, mapping)
if createdPatch {
outputObj, err = helper.Patch(namespace, name, types.StrategicMergePatchType, patchBytes)
outputObj, err = helper.Patch(namespace, name, types.MergePatchType, patchBytes)
} else {
outputObj, err = helper.Replace(namespace, name, false, obj)
}

View File

@ -347,10 +347,10 @@ func TestLabelErrors(t *testing.T) {
func TestLabelForResourceFromFile(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method {
case "GET":
@ -398,10 +398,10 @@ func TestLabelForResourceFromFile(t *testing.T) {
}
func TestLabelLocal(t *testing.T) {
f, tf, _, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
t.Fatalf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req)
return nil, nil
@ -432,10 +432,10 @@ func TestLabelLocal(t *testing.T) {
func TestLabelMultipleObjects(t *testing.T) {
pods, _, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
tf.Client = &fake.RESTClient{
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.Method {
case "GET":

View File

@ -17,17 +17,20 @@ limitations under the License.
package cmd
import (
"encoding/json"
"fmt"
"io"
"reflect"
"strings"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"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)
}
mapper, typer := f.Object()
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)).
mapper, typer, err := f.UnstructuredObject()
if err != nil {
return err
}
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, &options.FilenameOptions).
@ -164,7 +170,7 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
}
name, namespace := info.Name, info.Namespace
mapping := info.ResourceMapping()
client, err := f.ClientForMapping(mapping)
client, err := f.UnstructuredClientForMapping(mapping)
if err != nil {
return err
}
@ -176,14 +182,16 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
if err != nil {
return err
}
// Record the change as a second patch to avoid trying to merge with a user's patch data
if cmdutil.ShouldRecord(cmd, info) {
// don't return an error on failure. The patch itself succeeded, its only the hint for that change that failed
// don't bother checking for failures of this replace, because a failure to indicate the hint doesn't fail the command
// also, don't force the replacement. If the replacement fails on a resourceVersion conflict, then it means this
// record hint is likely to be invalid anyway, so avoid the bad hint
patch, err := cmdutil.ChangeResourcePatch(info, f.Command())
if err == nil {
helper.Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch)
// Copy the resource info and update with the result of applying the user's patch
infoCopy := *info
infoCopy.Object = patchedObj
infoCopy.VersionedObject = patchedObj
if patch, patchType, err := cmdutil.ChangeResourcePatch(&infoCopy, f.Command()); err == nil {
if _, err = helper.Patch(info.Namespace, info.Name, patchType, patch); err != nil {
glog.V(4).Infof("error recording reason: %v", err)
}
}
}
count++
@ -208,19 +216,15 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
count++
patchedObj, err := api.Scheme.DeepCopy(info.VersionedObject)
originalObjJS, err := runtime.Encode(unstructured.UnstructuredJSONScheme, info.VersionedObject)
if err != nil {
return err
}
originalObjJS, err := runtime.Encode(api.Codecs.LegacyCodec(mapping.GroupVersionKind.GroupVersion()), info.VersionedObject.(runtime.Object))
originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, mapping.GroupVersionKind, api.Scheme)
if err != nil {
return err
}
originalPatchedObjJS, err := getPatchedJSON(patchType, originalObjJS, patchBytes, patchedObj.(runtime.Object))
if err != nil {
return err
}
targetObj, err := runtime.Decode(api.Codecs.UniversalDecoder(), originalPatchedObjJS)
targetObj, err := runtime.Decode(unstructured.UnstructuredJSONScheme, originalPatchedObjJS)
if err != nil {
return err
}
@ -248,7 +252,7 @@ func RunPatch(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
return nil
}
func getPatchedJSON(patchType types.PatchType, originalJS, patchJS []byte, obj runtime.Object) ([]byte, error) {
func getPatchedJSON(patchType types.PatchType, originalJS, patchJS []byte, gvk schema.GroupVersionKind, creater runtime.ObjectCreater) ([]byte, error) {
switch patchType {
case types.JSONPatchType:
patchObj, err := jsonpatch.DecodePatch(patchJS)
@ -261,6 +265,11 @@ func getPatchedJSON(patchType types.PatchType, originalJS, patchJS []byte, obj r
return jsonpatch.MergePatch(originalJS, patchJS)
case types.StrategicMergePatchType:
// get a typed object for this GVK if we need to apply a strategic merge patch
obj, err := creater.New(gvk)
if err != nil {
return nil, fmt.Errorf("cannot apply strategic merge patch for %s locally, try --type merge", gvk.String())
}
return strategicpatch.StrategicMergePatch(originalJS, patchJS, obj)
default:

View File

@ -29,11 +29,11 @@ import (
func TestPatchObject(t *testing.T) {
_, svc, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"):
@ -62,11 +62,11 @@ func TestPatchObject(t *testing.T) {
func TestPatchObjectFromFile(t *testing.T) {
_, svc, _ := testData()
f, tf, codec, ns := cmdtesting.NewAPIFactory()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"):

View File

@ -22,7 +22,6 @@ import (
"strings"
"testing"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
@ -32,12 +31,11 @@ func TestReplaceObject(t *testing.T) {
_, _, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{}
deleted := false
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/api/v1/namespaces/test" && m == http.MethodGet:
@ -89,13 +87,12 @@ func TestReplaceMultipleObject(t *testing.T) {
_, svc, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{}
redisMasterDeleted := false
frontendDeleted := false
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/api/v1/namespaces/test" && m == http.MethodGet:
@ -160,12 +157,11 @@ func TestReplaceDirectory(t *testing.T) {
_, _, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{}
created := map[string]bool{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/api/v1/namespaces/test" && m == http.MethodGet:
@ -218,11 +214,10 @@ func TestForceReplaceObjectNotFound(t *testing.T) {
_, _, rc := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory()
ns := dynamic.ContentConfig().NegotiatedSerializer
tf.Printer = &testPrinter{}
tf.Client = &fake.RESTClient{
tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry,
NegotiatedSerializer: ns,
NegotiatedSerializer: unstructuredSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/api/v1/namespaces/test" && m == http.MethodGet:

View File

@ -23,7 +23,6 @@ import (
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
@ -164,7 +163,7 @@ func RunScale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
return err
}
if cmdutil.ShouldRecord(cmd, info) {
patchBytes, err := cmdutil.ChangeResourcePatch(info, f.Command())
patchBytes, patchType, err := cmdutil.ChangeResourcePatch(info, f.Command())
if err != nil {
return err
}
@ -174,7 +173,7 @@ func RunScale(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []strin
return err
}
helper := resource.NewHelper(client, mapping)
_, err = helper.Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patchBytes)
_, err = helper.Patch(info.Namespace, info.Name, patchType, patchBytes)
if err != nil {
return err
}

View File

@ -238,8 +238,8 @@ func (o *ImageOptions) Run() error {
// record this change (for rollout history)
if o.Record || cmdutil.ContainsChangeCause(info) {
if patch, err := cmdutil.ChangeResourcePatch(info, o.ChangeCause); err == nil {
if obj, err = resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch); err != nil {
if patch, patchType, err := cmdutil.ChangeResourcePatch(info, o.ChangeCause); err == nil {
if obj, err = resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, patchType, patch); err != nil {
fmt.Fprintf(o.Err, "WARNING: changes to %s/%s can't be recorded: %v\n", info.Mapping.Resource, info.Name, err)
}
}

View File

@ -93,6 +93,60 @@ func NewInternalType(kind, apiversion, name string) *InternalType {
return &item
}
type InternalNamespacedType struct {
Kind string
APIVersion string
Name string
Namespace string
}
type ExternalNamespacedType struct {
Kind string `json:"kind"`
APIVersion string `json:"apiVersion"`
Name string `json:"name"`
Namespace string `json:"namespace"`
}
type ExternalNamespacedType2 struct {
Kind string `json:"kind"`
APIVersion string `json:"apiVersion"`
Name string `json:"name"`
Namespace string `json:"namespace"`
}
func (obj *InternalNamespacedType) GetObjectKind() schema.ObjectKind { return obj }
func (obj *InternalNamespacedType) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
func (obj *InternalNamespacedType) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
func (obj *ExternalNamespacedType) GetObjectKind() schema.ObjectKind { return obj }
func (obj *ExternalNamespacedType) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
func (obj *ExternalNamespacedType) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
func (obj *ExternalNamespacedType2) GetObjectKind() schema.ObjectKind { return obj }
func (obj *ExternalNamespacedType2) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
func (obj *ExternalNamespacedType2) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
func NewInternalNamespacedType(kind, apiversion, name, namespace string) *InternalNamespacedType {
item := InternalNamespacedType{Kind: kind,
APIVersion: apiversion,
Name: name,
Namespace: namespace}
return &item
}
var versionErr = errors.New("not a version")
func versionErrIfFalse(b bool) error {
@ -109,11 +163,17 @@ var ValidVersionGV = schema.GroupVersion{Group: "apitest", Version: ValidVersion
func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) {
scheme := runtime.NewScheme()
scheme.AddKnownTypeWithName(InternalGV.WithKind("Type"), &InternalType{})
scheme.AddKnownTypeWithName(UnlikelyGV.WithKind("Type"), &ExternalType{})
//This tests that kubectl will not confuse the external scheme with the internal scheme, even when they accidentally have versions of the same name.
scheme.AddKnownTypeWithName(ValidVersionGV.WithKind("Type"), &ExternalType2{})
scheme.AddKnownTypeWithName(InternalGV.WithKind("NamespacedType"), &InternalNamespacedType{})
scheme.AddKnownTypeWithName(UnlikelyGV.WithKind("NamespacedType"), &ExternalNamespacedType{})
//This tests that kubectl will not confuse the external scheme with the internal scheme, even when they accidentally have versions of the same name.
scheme.AddKnownTypeWithName(ValidVersionGV.WithKind("NamespacedType"), &ExternalNamespacedType2{})
codecs := serializer.NewCodecFactory(scheme)
codec := codecs.LegacyCodec(UnlikelyGV)
mapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{UnlikelyGV, ValidVersionGV}, func(version schema.GroupVersion) (*meta.VersionInterfaces, error) {
@ -146,15 +206,16 @@ func (d *fakeCachedDiscoveryClient) Invalidate() {
}
type TestFactory struct {
Mapper meta.RESTMapper
Typer runtime.ObjectTyper
Client kubectl.RESTClient
Describer kubectl.Describer
Printer kubectl.ResourcePrinter
Validator validation.Schema
Namespace string
ClientConfig *restclient.Config
Err error
Mapper meta.RESTMapper
Typer runtime.ObjectTyper
Client kubectl.RESTClient
UnstructuredClient kubectl.RESTClient
Describer kubectl.Describer
Printer kubectl.ResourcePrinter
Validator validation.Schema
Namespace string
ClientConfig *restclient.Config
Err error
}
type FakeFactory struct {
@ -251,7 +312,7 @@ func (f *FakeFactory) ClientConfigForVersion(requiredVersion *schema.GroupVersio
}
func (f *FakeFactory) UnstructuredClientForMapping(*meta.RESTMapping) (resource.RESTClient, error) {
return nil, nil
return f.tf.UnstructuredClient, f.tf.Err
}
func (f *FakeFactory) Describer(*meta.RESTMapping) (kubectl.Describer, error) {
@ -497,7 +558,7 @@ func (f *fakeAPIFactory) ClientForMapping(*meta.RESTMapping) (resource.RESTClien
}
func (f *fakeAPIFactory) UnstructuredClientForMapping(*meta.RESTMapping) (resource.RESTClient, error) {
return f.tf.Client, f.tf.Err
return f.tf.UnstructuredClient, f.tf.Err
}
func (f *fakeAPIFactory) Describer(*meta.RESTMapping) (kubectl.Describer, error) {
@ -602,6 +663,7 @@ func testDynamicResources() []*discovery.APIGroupResources {
{Name: "componentstatuses", Namespaced: false, Kind: "ComponentStatus"},
{Name: "nodes", Namespaced: false, Kind: "Node"},
{Name: "type", Namespaced: false, Kind: "Type"},
{Name: "namespacedtype", Namespaced: true, Kind: "NamespacedType"},
},
},
},

View File

@ -38,7 +38,6 @@ go_library(
"//pkg/controller:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/resource:go_default_library",
"//pkg/registry/extensions/thirdpartyresourcedata:go_default_library",
"//pkg/util/exec:go_default_library",
"//pkg/version:go_default_library",
"//vendor:github.com/emicklei/go-restful/swagger",
@ -54,7 +53,9 @@ go_library(
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/json",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/errors",
"//vendor:k8s.io/apimachinery/pkg/util/json",
"//vendor:k8s.io/apimachinery/pkg/util/sets",
"//vendor:k8s.io/apimachinery/pkg/util/strategicpatch",
"//vendor:k8s.io/apimachinery/pkg/version",

View File

@ -51,7 +51,6 @@ import (
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata"
)
const (
@ -241,21 +240,6 @@ func getGroupVersionKinds(gvks []schema.GroupVersionKind, group string) []schema
return result
}
func makeInterfacesFor(versionList []schema.GroupVersion) func(version schema.GroupVersion) (*meta.VersionInterfaces, error) {
accessor := meta.NewAccessor()
return func(version schema.GroupVersion) (*meta.VersionInterfaces, error) {
for ix := range versionList {
if versionList[ix].String() == version.String() {
return &meta.VersionInterfaces{
ObjectConvertor: thirdpartyresourcedata.NewThirdPartyObjectConverter(api.Scheme),
MetadataAccessor: accessor,
}, nil
}
}
return nil, fmt.Errorf("unsupported storage version: %s (valid: %v)", version, versionList)
}
}
type factory struct {
ClientAccessFactory
ObjectMappingFactory

View File

@ -25,7 +25,6 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/kubectl/resource"
)
@ -44,7 +43,20 @@ func NewBuilderFactory(clientAccessFactory ClientAccessFactory, objectMappingFac
}
func (f *ring2Factory) PrintObject(cmd *cobra.Command, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error {
gvks, _, err := api.Scheme.ObjectKinds(obj)
// try to get a typed object
_, typer := f.objectMappingFactory.Object()
gvks, _, err := typer.ObjectKinds(obj)
// fall back to an unstructured object if we get something unregistered
if runtime.IsNotRegisteredError(err) {
_, typer, unstructuredErr := f.objectMappingFactory.UnstructuredObject()
if unstructuredErr != nil {
// if we can't get an unstructured typer, return the original error
return err
}
gvks, _, err = typer.ObjectKinds(obj)
}
if err != nil {
return err
}

View File

@ -49,7 +49,6 @@ import (
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata"
)
type ring0Factory struct {
@ -209,7 +208,7 @@ func (f *ring0Factory) Decoder(toInternal bool) runtime.Decoder {
} else {
decoder = api.Codecs.UniversalDeserializer()
}
return thirdpartyresourcedata.NewDecoder(decoder, "")
return decoder
}
func (f *ring0Factory) JSONEncoder() runtime.Encoder {

View File

@ -47,7 +47,6 @@ import (
"k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/registry/extensions/thirdpartyresourcedata"
)
type ring1Factory struct {
@ -127,9 +126,6 @@ func (f *ring1Factory) ClientForMapping(mapping *meta.RESTMapping) (resource.RES
}
gv := gvk.GroupVersion()
cfg.GroupVersion = &gv
if api.Registry.IsThirdPartyAPIGroupVersion(gvk.GroupVersion()) {
cfg.NegotiatedSerializer = thirdpartyresourcedata.NewNegotiatedSerializer(api.Codecs, gvk.Kind, gv, gv)
}
return restclient.RESTClientFor(cfg)
}
@ -162,14 +158,55 @@ func (f *ring1Factory) Describer(mapping *meta.RESTMapping) (kubectl.Describer,
return &kubectl.ClusterDescriber{Interface: fedClientSet}, nil
}
}
clientset, err := f.clientAccessFactory.ClientSetForVersion(&mappingVersion)
if err != nil {
// if we can't make a client for this group/version, go generic if possible
if genericDescriber, genericErr := genericDescriber(f.clientAccessFactory, mapping); genericErr == nil {
return genericDescriber, nil
}
// otherwise return the original error
return nil, err
}
// try to get a describer
if describer, ok := kubectl.DescriberFor(mapping.GroupVersionKind.GroupKind(), clientset); ok {
return describer, nil
}
return nil, fmt.Errorf("no description has been implemented for %q", mapping.GroupVersionKind.Kind)
// if this is a kind we don't have a describer for yet, go generic if possible
if genericDescriber, genericErr := genericDescriber(f.clientAccessFactory, mapping); genericErr == nil {
return genericDescriber, nil
}
// otherwise return an unregistered error
return nil, fmt.Errorf("no description has been implemented for %s", mapping.GroupVersionKind.String())
}
// helper function to make a generic describer, or return an error
func genericDescriber(clientAccessFactory ClientAccessFactory, mapping *meta.RESTMapping) (kubectl.Describer, error) {
clientConfig, err := clientAccessFactory.ClientConfig()
if err != nil {
return nil, err
}
clientConfigCopy := *clientConfig
clientConfigCopy.APIPath = dynamic.LegacyAPIPathResolverFunc(mapping.GroupVersionKind)
gv := mapping.GroupVersionKind.GroupVersion()
clientConfigCopy.GroupVersion = &gv
// used to fetch the resource
dynamicClient, err := dynamic.NewClient(&clientConfigCopy)
if err != nil {
return nil, err
}
// used to get events for the resource
clientSet, err := clientAccessFactory.ClientSet()
if err != nil {
return nil, err
}
eventsClient := clientSet.Core()
return kubectl.GenericDescriberFor(mapping, dynamicClient, eventsClient), nil
}
func (f *ring1Factory) LogsForObject(object, options runtime.Object) (*restclient.Request, error) {

View File

@ -18,7 +18,6 @@ package util
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
@ -38,7 +37,9 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/client-go/tools/clientcmd"
@ -520,27 +521,35 @@ func RecordChangeCause(obj runtime.Object, changeCause string) error {
return nil
}
// ChangeResourcePatch creates a strategic merge patch between the origin input resource info
// ChangeResourcePatch creates a patch between the origin input resource info
// and the annotated with change-cause input resource info.
func ChangeResourcePatch(info *resource.Info, changeCause string) ([]byte, error) {
func ChangeResourcePatch(info *resource.Info, changeCause string) ([]byte, types.PatchType, error) {
// Get a versioned object
obj, err := info.Mapping.ConvertToVersion(info.Object, info.Mapping.GroupVersionKind.GroupVersion())
if err != nil {
return nil, err
return nil, types.StrategicMergePatchType, err
}
oldData, err := json.Marshal(obj)
if err != nil {
return nil, err
return nil, types.StrategicMergePatchType, err
}
if err := RecordChangeCause(obj, changeCause); err != nil {
return nil, err
return nil, types.StrategicMergePatchType, err
}
newData, err := json.Marshal(obj)
if err != nil {
return nil, err
return nil, types.StrategicMergePatchType, err
}
switch obj := obj.(type) {
case *unstructured.Unstructured:
patch, err := jsonpatch.CreateMergePatch(oldData, newData)
return patch, types.MergePatchType, err
default:
patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj)
return patch, types.StrategicMergePatchType, err
}
return strategicpatch.CreateTwoWayMergePatch(oldData, newData, obj)
}
// containsChangeCause checks if input resource info contains change-cause annotation.

View File

@ -29,12 +29,14 @@ import (
"time"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/dynamic"
"k8s.io/kubernetes/federation/apis/federation"
fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset"
"k8s.io/kubernetes/pkg/api"
@ -171,6 +173,46 @@ func DescriberFor(kind schema.GroupKind, c clientset.Interface) (Describer, bool
return f, ok
}
// GenericDescriberFor returns a generic describer for the specified mapping
// that uses only information available from runtime.Unstructured
func GenericDescriberFor(mapping *meta.RESTMapping, dynamic *dynamic.Client, events coreclient.EventsGetter) Describer {
return &genericDescriber{mapping, dynamic, events}
}
type genericDescriber struct {
mapping *meta.RESTMapping
dynamic *dynamic.Client
events coreclient.EventsGetter
}
func (g *genericDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (output string, err error) {
apiResource := &metav1.APIResource{
Name: g.mapping.Resource,
Namespaced: g.mapping.Scope.Name() == meta.RESTScopeNameNamespace,
Kind: g.mapping.GroupVersionKind.Kind,
}
obj, err := g.dynamic.Resource(apiResource, namespace).Get(name)
if err != nil {
return "", err
}
var events *api.EventList
if describerSettings.ShowEvents {
events, _ = g.events.Events(namespace).Search(obj)
}
return tabbedString(func(out io.Writer) error {
w := &PrefixWriter{out}
w.Write(LEVEL_0, "Name:\t%s\n", obj.GetName())
w.Write(LEVEL_0, "Namespace:\t%s\n", obj.GetNamespace())
printLabelsMultiline(w, "Labels", obj.GetLabels())
if events != nil {
DescribeEvents(events, w)
}
return nil
})
}
// DefaultObjectDescriber can describe the default Kubernetes objects.
var DefaultObjectDescriber ObjectDescriber