mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-17 15:13:08 +00:00
Allow kubectl get to fetch multiple resource types
Like Delete, which can now run over multiple types: kubectl delete pods,services -l name=foo Get should be able to span items for label selection kubectl get pods,services -l name=foo
This commit is contained in:
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
|
||||
@@ -42,6 +43,7 @@ const (
|
||||
|
||||
// Factory provides abstractions that allow the Kubectl command to be extended across multiple types
|
||||
// of resources and different API sets.
|
||||
// TODO: make the functions interfaces
|
||||
type Factory struct {
|
||||
clients *clientCache
|
||||
flags *pflag.FlagSet
|
||||
@@ -218,6 +220,13 @@ func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
|
||||
return clientConfig
|
||||
}
|
||||
|
||||
// ClientMapperForCommand returns a ClientMapper for the given command and factory.
|
||||
func ClientMapperForCommand(cmd *cobra.Command, f *Factory) resource.ClientMapper {
|
||||
return resource.ClientMapperFunc(func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
|
||||
return f.RESTClient(cmd, mapping)
|
||||
})
|
||||
}
|
||||
|
||||
func checkErr(err error) {
|
||||
if err != nil {
|
||||
glog.FatalDepth(1, err)
|
||||
|
@@ -22,6 +22,8 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
||||
. "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
|
||||
@@ -66,12 +68,12 @@ func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) {
|
||||
}
|
||||
|
||||
type testPrinter struct {
|
||||
Obj runtime.Object
|
||||
Err error
|
||||
Objects []runtime.Object
|
||||
Err error
|
||||
}
|
||||
|
||||
func (t *testPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
|
||||
t.Obj = obj
|
||||
t.Objects = append(t.Objects, obj)
|
||||
fmt.Fprintf(out, "%#v", obj)
|
||||
return t.Err
|
||||
}
|
||||
@@ -112,6 +114,23 @@ func NewTestFactory() (*Factory, *testFactory, runtime.Codec) {
|
||||
}, t, codec
|
||||
}
|
||||
|
||||
func NewAPIFactory() (*Factory, *testFactory, runtime.Codec) {
|
||||
t := &testFactory{}
|
||||
return &Factory{
|
||||
Mapper: latest.RESTMapper,
|
||||
Typer: api.Scheme,
|
||||
RESTClient: func(*cobra.Command, *meta.RESTMapping) (kubectl.RESTClient, error) {
|
||||
return t.Client, t.Err
|
||||
},
|
||||
Describer: func(*cobra.Command, *meta.RESTMapping) (kubectl.Describer, error) {
|
||||
return t.Describer, t.Err
|
||||
},
|
||||
Printer: func(cmd *cobra.Command, mapping *meta.RESTMapping, noHeaders bool) (kubectl.ResourcePrinter, error) {
|
||||
return t.Printer, t.Err
|
||||
},
|
||||
}, t, latest.Codec
|
||||
}
|
||||
|
||||
func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
|
||||
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
|
||||
}
|
||||
|
@@ -59,7 +59,7 @@ Examples:
|
||||
checkErr(err)
|
||||
selector := GetFlagString(cmd, "selector")
|
||||
found := 0
|
||||
ResourcesFromArgsOrFile(cmd, args, filename, selector, f.Typer, f.Mapper, f.RESTClient, schema).Visit(func(r *resource.Info) error {
|
||||
ResourcesFromArgsOrFile(cmd, args, filename, selector, f.Typer, f.Mapper, f.RESTClient, schema, true).Visit(func(r *resource.Info) error {
|
||||
found++
|
||||
if err := resource.NewHelper(r.Client, r.Mapping).Delete(r.Namespace, r.Name); err != nil {
|
||||
return err
|
||||
|
@@ -20,14 +20,17 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewCmdGet creates a command object for the generic "get" action, which
|
||||
// retrieves one or more resources from a server.
|
||||
func (f *Factory) NewCmdGet(out io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "get [(-o|--output=)json|yaml|...] <resource> [<id>]",
|
||||
@@ -48,66 +51,134 @@ Examples:
|
||||
<list single replication controller in ps output format>
|
||||
|
||||
$ kubectl get -o json pod 1234-56-7890-234234-456456
|
||||
<list single pod in json output format>`,
|
||||
<list single pod in json output format>
|
||||
|
||||
$ kubectl get rc,services
|
||||
<list replication controllers and services together in ps output format>`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
mapping, namespace, name := ResourceOrTypeFromArgs(cmd, args, f.Mapper)
|
||||
|
||||
selector := GetFlagString(cmd, "selector")
|
||||
labelSelector, err := labels.ParseSelector(selector)
|
||||
checkErr(err)
|
||||
|
||||
client, err := f.RESTClient(cmd, mapping)
|
||||
checkErr(err)
|
||||
|
||||
outputFormat := GetFlagString(cmd, "output")
|
||||
templateFile := GetFlagString(cmd, "template")
|
||||
defaultPrinter, err := f.Printer(cmd, mapping, GetFlagBool(cmd, "no-headers"))
|
||||
checkErr(err)
|
||||
|
||||
outputVersion := GetFlagString(cmd, "output-version")
|
||||
if len(outputVersion) == 0 {
|
||||
outputVersion = mapping.APIVersion
|
||||
}
|
||||
|
||||
printer, err := kubectl.GetPrinter(outputFormat, templateFile, outputVersion, mapping.ObjectConvertor, defaultPrinter)
|
||||
checkErr(err)
|
||||
|
||||
restHelper := resource.NewHelper(client, mapping)
|
||||
var obj runtime.Object
|
||||
if len(name) == 0 {
|
||||
obj, err = restHelper.List(namespace, labelSelector)
|
||||
} else {
|
||||
obj, err = restHelper.Get(namespace, name)
|
||||
}
|
||||
checkErr(err)
|
||||
|
||||
isWatch, isWatchOnly := GetFlagBool(cmd, "watch"), GetFlagBool(cmd, "watch-only")
|
||||
|
||||
// print the current object
|
||||
if !isWatchOnly {
|
||||
if err := printer.PrintObj(obj, out); err != nil {
|
||||
checkErr(fmt.Errorf("unable to output the provided object: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// print watched changes
|
||||
if isWatch || isWatchOnly {
|
||||
rv, err := mapping.MetadataAccessor.ResourceVersion(obj)
|
||||
checkErr(err)
|
||||
|
||||
w, err := restHelper.Watch(namespace, rv, labelSelector, labels.Everything())
|
||||
checkErr(err)
|
||||
|
||||
kubectl.WatchLoop(w, printer, out)
|
||||
}
|
||||
RunGet(f, out, cmd, args)
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringP("output", "o", "", "Output format: json|yaml|template|templatefile")
|
||||
cmd.Flags().String("output-version", "", "Output the formatted object with the given version (default api-version)")
|
||||
cmd.Flags().Bool("no-headers", false, "When using the default output, don't print headers")
|
||||
cmd.Flags().StringP("template", "t", "", "Template string or path to template file to use when --output=template or --output=templatefile")
|
||||
cmd.Flags().StringP("template", "t", "", "Template string or path to template file to use when -o=template or -o=templatefile.")
|
||||
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on")
|
||||
cmd.Flags().BoolP("watch", "w", false, "After listing/getting the requested object, watch for changes.")
|
||||
cmd.Flags().Bool("watch-only", false, "Watch for changes to the requseted object(s), without listing/getting first.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RunGet implements the generic Get command
|
||||
// TODO: convert all direct flag accessors to a struct and pass that instead of cmd
|
||||
// TODO: return an error instead of using glog.Fatal and checkErr
|
||||
func RunGet(f *Factory, out io.Writer, cmd *cobra.Command, args []string) {
|
||||
selector := GetFlagString(cmd, "selector")
|
||||
|
||||
// handle watch separately since we cannot watch multiple resource types
|
||||
isWatch, isWatchOnly := GetFlagBool(cmd, "watch"), GetFlagBool(cmd, "watch-only")
|
||||
if isWatch || isWatchOnly {
|
||||
r := resource.NewBuilder(f.Mapper, f.Typer, ClientMapperForCommand(cmd, f)).
|
||||
NamespaceParam(GetKubeNamespace(cmd)).DefaultNamespace().
|
||||
SelectorParam(selector).
|
||||
ResourceTypeOrNameArgs(args...).
|
||||
SingleResourceType().
|
||||
Do()
|
||||
|
||||
mapping, err := r.ResourceMapping()
|
||||
checkErr(err)
|
||||
|
||||
printer, err := printerForMapping(f, cmd, mapping)
|
||||
checkErr(err)
|
||||
|
||||
obj, err := r.Object()
|
||||
checkErr(err)
|
||||
|
||||
rv, err := mapping.MetadataAccessor.ResourceVersion(obj)
|
||||
checkErr(err)
|
||||
|
||||
// print the current object
|
||||
if !isWatchOnly {
|
||||
if err := printer.PrintObj(obj, out); err != nil {
|
||||
checkErr(fmt.Errorf("unable to output the provided object: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// print watched changes
|
||||
w, err := r.Watch(rv)
|
||||
checkErr(err)
|
||||
|
||||
kubectl.WatchLoop(w, func(e watch.Event) error {
|
||||
return printer.PrintObj(e.Object, out)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
printer, generic, err := printerForCommand(cmd)
|
||||
checkErr(err)
|
||||
|
||||
b := resource.NewBuilder(f.Mapper, f.Typer, ClientMapperForCommand(cmd, f)).
|
||||
NamespaceParam(GetKubeNamespace(cmd)).DefaultNamespace().
|
||||
SelectorParam(selector).
|
||||
ResourceTypeOrNameArgs(args...).
|
||||
Latest()
|
||||
|
||||
if generic {
|
||||
// the outermost object will be converted to the output-version
|
||||
printer = kubectl.NewVersionedPrinter(printer, api.Scheme, outputVersion(cmd))
|
||||
|
||||
obj, err := b.Flatten().Do().Object()
|
||||
checkErr(err)
|
||||
|
||||
err = printer.PrintObj(obj, out)
|
||||
checkErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
// use the default printer for each object
|
||||
err = b.Do().Visit(func(r *resource.Info) error {
|
||||
printer, err := printerForMapping(f, cmd, r.Mapping)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return printer.PrintObj(r.Object, out)
|
||||
})
|
||||
checkErr(err)
|
||||
}
|
||||
|
||||
// outputVersion returns the preferred output version for generic content (JSON, YAML, or templates)
|
||||
func outputVersion(cmd *cobra.Command) string {
|
||||
outputVersion := GetFlagString(cmd, "output-version")
|
||||
if len(outputVersion) == 0 {
|
||||
outputVersion = GetFlagString(cmd, "api-version")
|
||||
}
|
||||
return outputVersion
|
||||
}
|
||||
|
||||
// printerForCommand returns the default printer for this command.
|
||||
func printerForCommand(cmd *cobra.Command) (kubectl.ResourcePrinter, bool, error) {
|
||||
outputFormat := GetFlagString(cmd, "output")
|
||||
templateFile := GetFlagString(cmd, "template")
|
||||
if len(outputFormat) == 0 && len(templateFile) != 0 {
|
||||
outputFormat = "template"
|
||||
}
|
||||
|
||||
return kubectl.GetPrinter(outputFormat, templateFile)
|
||||
}
|
||||
|
||||
// printerForMapping returns a printer suitable for displaying the provided resource type.
|
||||
func printerForMapping(f *Factory, cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.ResourcePrinter, error) {
|
||||
printer, ok, err := printerForCommand(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ok {
|
||||
printer = kubectl.NewVersionedPrinter(printer, mapping.ObjectConvertor, outputVersion(cmd))
|
||||
} else {
|
||||
printer, err = f.Printer(cmd, mapping, GetFlagBool(cmd, "no-headers"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return printer, nil
|
||||
}
|
||||
|
@@ -19,6 +19,8 @@ package cmd_test
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -27,9 +29,38 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch/json"
|
||||
)
|
||||
|
||||
func testData() (*api.PodList, *api.ServiceList) {
|
||||
pods := &api.PodList{
|
||||
ListMeta: api.ListMeta{
|
||||
ResourceVersion: "15",
|
||||
},
|
||||
Items: []api.Pod{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
|
||||
},
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"},
|
||||
},
|
||||
},
|
||||
}
|
||||
svc := &api.ServiceList{
|
||||
ListMeta: api.ListMeta{
|
||||
ResourceVersion: "16",
|
||||
},
|
||||
Items: []api.Service{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
|
||||
},
|
||||
},
|
||||
}
|
||||
return pods, svc
|
||||
}
|
||||
|
||||
// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get.
|
||||
func TestGetUnknownSchemaObject(t *testing.T) {
|
||||
f, tf, codec := NewTestFactory()
|
||||
@@ -41,12 +72,13 @@ func TestGetUnknownSchemaObject(t *testing.T) {
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
|
||||
cmd := f.NewCmdGet(buf)
|
||||
cmd.SetOutput(buf)
|
||||
cmd.Flags().String("api-version", "default", "")
|
||||
cmd.Flags().String("namespace", "test", "")
|
||||
cmd.Run(cmd, []string{"type", "foo"})
|
||||
|
||||
expected := &internalType{Name: "foo"}
|
||||
actual := tf.Printer.(*testPrinter).Obj
|
||||
actual := tf.Printer.(*testPrinter).Objects[0]
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("unexpected object: %#v", actual)
|
||||
}
|
||||
@@ -73,12 +105,347 @@ func TestGetSchemaObject(t *testing.T) {
|
||||
cmd.Flags().String("namespace", "test", "")
|
||||
cmd.Run(cmd, []string{"replicationcontrollers", "foo"})
|
||||
|
||||
expected := &api.ReplicationController{ObjectMeta: api.ObjectMeta{Name: "foo"}, Spec: api.ReplicationControllerSpec{Template: &api.PodTemplateSpec{}}}
|
||||
actual := tf.Printer.(*testPrinter).Obj
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("unexpected object: %s", util.ObjectGoPrintDiff(expected, actual))
|
||||
}
|
||||
if !strings.Contains(buf.String(), "\"foo\"") {
|
||||
t.Errorf("unexpected output: %s", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetObjects(t *testing.T) {
|
||||
pods, _ := testData()
|
||||
|
||||
f, tf, codec := NewAPIFactory()
|
||||
tf.Printer = &testPrinter{}
|
||||
tf.Client = &client.FakeRESTClient{
|
||||
Codec: codec,
|
||||
Resp: &http.Response{StatusCode: 200, Body: objBody(codec, &pods.Items[0])},
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
|
||||
cmd := f.NewCmdGet(buf)
|
||||
cmd.SetOutput(buf)
|
||||
cmd.Flags().String("namespace", "test", "")
|
||||
cmd.Run(cmd, []string{"pods", "foo"})
|
||||
|
||||
expected := []runtime.Object{&pods.Items[0]}
|
||||
actual := tf.Printer.(*testPrinter).Objects
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("unexpected object: %#v", actual)
|
||||
}
|
||||
if len(buf.String()) == 0 {
|
||||
t.Errorf("unexpected empty output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetListObjects(t *testing.T) {
|
||||
pods, _ := testData()
|
||||
|
||||
f, tf, codec := NewAPIFactory()
|
||||
tf.Printer = &testPrinter{}
|
||||
tf.Client = &client.FakeRESTClient{
|
||||
Codec: codec,
|
||||
Resp: &http.Response{StatusCode: 200, Body: objBody(codec, pods)},
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
|
||||
cmd := f.NewCmdGet(buf)
|
||||
cmd.SetOutput(buf)
|
||||
cmd.Flags().String("namespace", "test", "")
|
||||
cmd.Run(cmd, []string{"pods"})
|
||||
|
||||
expected := []runtime.Object{pods}
|
||||
actual := tf.Printer.(*testPrinter).Objects
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("unexpected object: %#v %#v", expected, actual)
|
||||
}
|
||||
if len(buf.String()) == 0 {
|
||||
t.Errorf("unexpected empty output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMultipleTypeObjects(t *testing.T) {
|
||||
pods, svc := testData()
|
||||
|
||||
f, tf, codec := NewAPIFactory()
|
||||
tf.Printer = &testPrinter{}
|
||||
tf.Client = &client.FakeRESTClient{
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch req.URL.Path {
|
||||
case "/ns/test/pods":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
|
||||
case "/ns/test/services":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, svc)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
|
||||
cmd := f.NewCmdGet(buf)
|
||||
cmd.SetOutput(buf)
|
||||
cmd.Flags().String("namespace", "test", "")
|
||||
cmd.Run(cmd, []string{"pods,services"})
|
||||
|
||||
expected := []runtime.Object{pods, svc}
|
||||
actual := tf.Printer.(*testPrinter).Objects
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("unexpected object: %#v", actual)
|
||||
}
|
||||
if len(buf.String()) == 0 {
|
||||
t.Errorf("unexpected empty output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMultipleTypeObjectsAsList(t *testing.T) {
|
||||
pods, svc := testData()
|
||||
|
||||
f, tf, codec := NewAPIFactory()
|
||||
tf.Printer = &testPrinter{}
|
||||
tf.Client = &client.FakeRESTClient{
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch req.URL.Path {
|
||||
case "/ns/test/pods":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
|
||||
case "/ns/test/services":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, svc)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
|
||||
cmd := f.NewCmdGet(buf)
|
||||
cmd.SetOutput(buf)
|
||||
cmd.Flags().String("namespace", "test", "")
|
||||
cmd.Flags().String("api-version", "v1beta1", "")
|
||||
|
||||
cmd.Flags().Set("output", "json")
|
||||
cmd.Run(cmd, []string{"pods,services"})
|
||||
|
||||
if tf.Printer.(*testPrinter).Objects != nil {
|
||||
t.Errorf("unexpected print to default printer")
|
||||
}
|
||||
|
||||
out, err := codec.Decode(buf.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
expected := &api.List{
|
||||
Items: []runtime.Object{
|
||||
&pods.Items[0],
|
||||
&pods.Items[1],
|
||||
&svc.Items[0],
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(expected, out) {
|
||||
t.Errorf("unexpected output: %#v", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMultipleTypeObjectsWithSelector(t *testing.T) {
|
||||
pods, svc := testData()
|
||||
|
||||
f, tf, codec := NewAPIFactory()
|
||||
tf.Printer = &testPrinter{}
|
||||
tf.Client = &client.FakeRESTClient{
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Query().Get("labels") != "a=b" {
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
}
|
||||
switch req.URL.Path {
|
||||
case "/ns/test/pods":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, pods)}, nil
|
||||
case "/ns/test/services":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, svc)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
|
||||
cmd := f.NewCmdGet(buf)
|
||||
cmd.SetOutput(buf)
|
||||
cmd.Flags().String("namespace", "test", "")
|
||||
|
||||
cmd.Flags().Set("selector", "a=b")
|
||||
cmd.Run(cmd, []string{"pods,services"})
|
||||
|
||||
expected := []runtime.Object{pods, svc}
|
||||
actual := tf.Printer.(*testPrinter).Objects
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("unexpected object: %#v", actual)
|
||||
}
|
||||
if len(buf.String()) == 0 {
|
||||
t.Errorf("unexpected empty output")
|
||||
}
|
||||
}
|
||||
|
||||
func watchTestData() ([]api.Pod, []watch.Event) {
|
||||
pods := []api.Pod{
|
||||
{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: "test",
|
||||
ResourceVersion: "10",
|
||||
},
|
||||
},
|
||||
}
|
||||
events := []watch.Event{
|
||||
{
|
||||
Type: watch.Modified,
|
||||
Object: &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: "test",
|
||||
ResourceVersion: "11",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: watch.Deleted,
|
||||
Object: &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: "test",
|
||||
ResourceVersion: "12",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return pods, events
|
||||
}
|
||||
|
||||
func TestWatchSelector(t *testing.T) {
|
||||
pods, events := watchTestData()
|
||||
|
||||
f, tf, codec := NewAPIFactory()
|
||||
tf.Printer = &testPrinter{}
|
||||
tf.Client = &client.FakeRESTClient{
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Query().Get("labels") != "a=b" {
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
}
|
||||
switch req.URL.Path {
|
||||
case "/ns/test/pods":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &api.PodList{Items: pods})}, nil
|
||||
case "/watch/ns/test/pods":
|
||||
return &http.Response{StatusCode: 200, Body: watchBody(codec, events)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
|
||||
cmd := f.NewCmdGet(buf)
|
||||
cmd.SetOutput(buf)
|
||||
cmd.Flags().String("namespace", "test", "")
|
||||
|
||||
cmd.Flags().Set("watch", "true")
|
||||
cmd.Flags().Set("selector", "a=b")
|
||||
cmd.Run(cmd, []string{"pods"})
|
||||
|
||||
expected := []runtime.Object{&api.PodList{Items: pods}, events[0].Object, events[1].Object}
|
||||
actual := tf.Printer.(*testPrinter).Objects
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("unexpected object: %#v %#v", expected[0], actual[0])
|
||||
}
|
||||
if len(buf.String()) == 0 {
|
||||
t.Errorf("unexpected empty output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchResource(t *testing.T) {
|
||||
pods, events := watchTestData()
|
||||
|
||||
f, tf, codec := NewAPIFactory()
|
||||
tf.Printer = &testPrinter{}
|
||||
tf.Client = &client.FakeRESTClient{
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch req.URL.Path {
|
||||
case "/ns/test/pods/foo":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods[0])}, nil
|
||||
case "/watch/ns/test/pods/foo":
|
||||
return &http.Response{StatusCode: 200, Body: watchBody(codec, events)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
|
||||
cmd := f.NewCmdGet(buf)
|
||||
cmd.SetOutput(buf)
|
||||
cmd.Flags().String("namespace", "test", "")
|
||||
|
||||
cmd.Flags().Set("watch", "true")
|
||||
cmd.Run(cmd, []string{"pods", "foo"})
|
||||
|
||||
expected := []runtime.Object{&pods[0], events[0].Object, events[1].Object}
|
||||
actual := tf.Printer.(*testPrinter).Objects
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("unexpected object: %#v", actual)
|
||||
}
|
||||
if len(buf.String()) == 0 {
|
||||
t.Errorf("unexpected empty output")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchOnlyResource(t *testing.T) {
|
||||
pods, events := watchTestData()
|
||||
|
||||
f, tf, codec := NewAPIFactory()
|
||||
tf.Printer = &testPrinter{}
|
||||
tf.Client = &client.FakeRESTClient{
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch req.URL.Path {
|
||||
case "/ns/test/pods/foo":
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &pods[0])}, nil
|
||||
case "/watch/ns/test/pods/foo":
|
||||
return &http.Response{StatusCode: 200, Body: watchBody(codec, events)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
|
||||
cmd := f.NewCmdGet(buf)
|
||||
cmd.SetOutput(buf)
|
||||
cmd.Flags().String("namespace", "test", "")
|
||||
|
||||
cmd.Flags().Set("watch-only", "true")
|
||||
cmd.Run(cmd, []string{"pods", "foo"})
|
||||
|
||||
expected := []runtime.Object{events[0].Object, events[1].Object}
|
||||
actual := tf.Printer.(*testPrinter).Objects
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Errorf("unexpected object: %#v", actual)
|
||||
}
|
||||
if len(buf.String()) == 0 {
|
||||
t.Errorf("unexpected empty output")
|
||||
}
|
||||
}
|
||||
|
||||
func watchBody(codec runtime.Codec, events []watch.Event) io.ReadCloser {
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
enc := json.NewEncoder(buf, codec)
|
||||
for i := range events {
|
||||
enc.Encode(&events[i])
|
||||
}
|
||||
return ioutil.NopCloser(buf)
|
||||
}
|
||||
|
@@ -41,15 +41,23 @@ func ResourcesFromArgsOrFile(
|
||||
mapper meta.RESTMapper,
|
||||
clientBuilder func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error),
|
||||
schema validation.Schema,
|
||||
requireNames bool,
|
||||
) resource.Visitor {
|
||||
|
||||
// handling filename & resource id
|
||||
if len(selector) == 0 {
|
||||
mapping, namespace, name := ResourceFromArgsOrFile(cmd, args, filename, typer, mapper, schema)
|
||||
client, err := clientBuilder(cmd, mapping)
|
||||
checkErr(err)
|
||||
|
||||
return resource.NewInfo(client, mapping, namespace, name)
|
||||
if requireNames || len(filename) > 0 {
|
||||
mapping, namespace, name := ResourceFromArgsOrFile(cmd, args, filename, typer, mapper, schema)
|
||||
client, err := clientBuilder(cmd, mapping)
|
||||
checkErr(err)
|
||||
return resource.NewInfo(client, mapping, namespace, name)
|
||||
}
|
||||
if len(args) == 2 {
|
||||
mapping, namespace, name := ResourceOrTypeFromArgs(cmd, args, mapper)
|
||||
client, err := clientBuilder(cmd, mapping)
|
||||
checkErr(err)
|
||||
return resource.NewInfo(client, mapping, namespace, name)
|
||||
}
|
||||
}
|
||||
|
||||
labelSelector, err := labels.ParseSelector(selector)
|
||||
@@ -58,9 +66,12 @@ func ResourcesFromArgsOrFile(
|
||||
namespace := GetKubeNamespace(cmd)
|
||||
visitors := resource.VisitorList{}
|
||||
|
||||
if len(args) != 1 {
|
||||
if len(args) < 1 {
|
||||
usageError(cmd, "Must specify the type of resource")
|
||||
}
|
||||
if len(args) > 1 {
|
||||
usageError(cmd, "Too many arguments")
|
||||
}
|
||||
types := SplitResourceArgument(args[0])
|
||||
for _, arg := range types {
|
||||
resourceName := arg
|
||||
|
Reference in New Issue
Block a user