mirror of https://github.com/k3s-io/k3s
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 GVKpull/6/head
commit
1625150de8
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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().
|
||||
|
|
|
@ -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)},
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue