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:
Clayton Coleman 2014-12-30 19:42:55 -05:00
parent b8333bdeef
commit 2151afe334
9 changed files with 672 additions and 166 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -35,66 +35,94 @@ import (
"github.com/golang/glog"
)
// GetPrinter takes a format type, an optional format argument, a version and a convertor
// to be used if the underlying printer requires the object to be in a specific schema (
// any of the generic formatters), and the default printer to use for this object.
func GetPrinter(format, formatArgument, version string, convertor runtime.ObjectConvertor, defaultPrinter ResourcePrinter) (ResourcePrinter, error) {
// GetPrinter takes a format type, an optional format argument. It will return true
// if the format is generic (untyped), otherwise it will return false. The printer
// is agnostic to schema versions, so you must send arguments to PrintObj in the
// version you wish them to be shown using a VersionedPrinter (typically when
// generic is true).
func GetPrinter(format, formatArgument string) (ResourcePrinter, bool, error) {
var printer ResourcePrinter
switch format {
case "json":
printer = &JSONPrinter{version, convertor}
printer = &JSONPrinter{}
case "yaml":
printer = &YAMLPrinter{version, convertor}
printer = &YAMLPrinter{}
case "template":
if len(formatArgument) == 0 {
return nil, fmt.Errorf("template format specified but no template given")
return nil, false, fmt.Errorf("template format specified but no template given")
}
var err error
printer, err = NewTemplatePrinter([]byte(formatArgument), version, convertor)
printer, err = NewTemplatePrinter([]byte(formatArgument))
if err != nil {
return nil, fmt.Errorf("error parsing template %s, %v\n", formatArgument, err)
return nil, false, fmt.Errorf("error parsing template %s, %v\n", formatArgument, err)
}
case "templatefile":
if len(formatArgument) == 0 {
return nil, fmt.Errorf("templatefile format specified but no template file given")
return nil, false, fmt.Errorf("templatefile format specified but no template file given")
}
data, err := ioutil.ReadFile(formatArgument)
if err != nil {
return nil, fmt.Errorf("error reading template %s, %v\n", formatArgument, err)
return nil, false, fmt.Errorf("error reading template %s, %v\n", formatArgument, err)
}
printer, err = NewTemplatePrinter(data, version, convertor)
printer, err = NewTemplatePrinter(data)
if err != nil {
return nil, fmt.Errorf("error parsing template %s, %v\n", string(data), err)
return nil, false, fmt.Errorf("error parsing template %s, %v\n", string(data), err)
}
case "":
printer = defaultPrinter
return nil, false, nil
default:
return nil, fmt.Errorf("output format %q not recognized", format)
return nil, false, fmt.Errorf("output format %q not recognized", format)
}
return printer, nil
return printer, true, nil
}
// ResourcePrinter is an interface that knows how to print runtime objects.
type ResourcePrinter interface {
// Print receives an arbitrary object, formats it and prints it to a writer.
// Print receives a runtime object, formats it and prints it to a writer.
PrintObj(runtime.Object, io.Writer) error
}
// JSONPrinter is an implementation of ResourcePrinter which outputs an object as JSON.
// The input object is assumed to be in the internal version of an API and is converted
// to the given version first.
type JSONPrinter struct {
version string
// ResourcePrinterFunc is a function that can print objects
type ResourcePrinterFunc func(runtime.Object, io.Writer) error
// PrintObj implements ResourcePrinter
func (fn ResourcePrinterFunc) PrintObj(obj runtime.Object, w io.Writer) error {
return fn(obj, w)
}
// VersionedPrinter takes runtime objects and ensures they are converted to a given API version
// prior to being passed to a nested printer.
type VersionedPrinter struct {
printer ResourcePrinter
convertor runtime.ObjectConvertor
version string
}
// NewVersionedPrinter wraps a printer to convert objects to a known API version prior to printing.
func NewVersionedPrinter(printer ResourcePrinter, convertor runtime.ObjectConvertor, version string) ResourcePrinter {
return &VersionedPrinter{
printer: printer,
convertor: convertor,
version: version,
}
}
// PrintObj implements ResourcePrinter
func (p *VersionedPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
converted, err := p.convertor.ConvertToVersion(obj, p.version)
if err != nil {
return err
}
return p.printer.PrintObj(converted, w)
}
// JSONPrinter is an implementation of ResourcePrinter which outputs an object as JSON.
type JSONPrinter struct {
}
// PrintObj is an implementation of ResourcePrinter.PrintObj which simply writes the object to the Writer.
func (p *JSONPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
outObj, err := p.convertor.ConvertToVersion(obj, p.version)
if err != nil {
return err
}
data, err := json.Marshal(outObj)
data, err := json.Marshal(obj)
if err != nil {
return err
}
@ -115,11 +143,7 @@ type YAMLPrinter struct {
// PrintObj prints the data as YAML.
func (p *YAMLPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
outObj, err := p.convertor.ConvertToVersion(obj, p.version)
if err != nil {
return err
}
output, err := yaml.Marshal(outObj)
output, err := yaml.Marshal(obj)
if err != nil {
return err
}
@ -387,11 +411,9 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
type TemplatePrinter struct {
rawTemplate string
template *template.Template
version string
convertor runtime.ObjectConvertor
}
func NewTemplatePrinter(tmpl []byte, asVersion string, convertor runtime.ObjectConvertor) (*TemplatePrinter, error) {
func NewTemplatePrinter(tmpl []byte) (*TemplatePrinter, error) {
t, err := template.New("output").
Funcs(template.FuncMap{"exists": exists}).
Parse(string(tmpl))
@ -401,18 +423,12 @@ func NewTemplatePrinter(tmpl []byte, asVersion string, convertor runtime.ObjectC
return &TemplatePrinter{
rawTemplate: string(tmpl),
template: t,
version: asVersion,
convertor: convertor,
}, nil
}
// PrintObj formats the obj with the Go Template.
func (p *TemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
outObj, err := p.convertor.ConvertToVersion(obj, p.version)
if err != nil {
return err
}
data, err := json.Marshal(outObj)
data, err := json.Marshal(obj)
if err != nil {
return err
}

View File

@ -27,6 +27,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
@ -56,38 +57,56 @@ var testData = testStruct{
IntList: []int{1, 2, 3},
}
func TestVersionedPrinter(t *testing.T) {
original := &testStruct{Key: "value"}
p := NewVersionedPrinter(
ResourcePrinterFunc(func(obj runtime.Object, w io.Writer) error {
if obj == original {
t.Fatalf("object should not be identical: %#v", obj)
}
if obj.(*testStruct).Key != "value" {
t.Fatalf("object was not converted: %#v", obj)
}
return nil
}),
api.Scheme,
testapi.Version(),
)
if err := p.PrintObj(original, nil); err != nil {
t.Errorf("unexpected error: %v", err)
}
}
func TestYAMLPrinter(t *testing.T) {
testPrinter(t, &YAMLPrinter{testapi.Version(), api.Scheme}, yaml.Unmarshal)
testPrinter(t, &YAMLPrinter{}, yaml.Unmarshal)
}
func TestJSONPrinter(t *testing.T) {
testPrinter(t, &JSONPrinter{testapi.Version(), api.Scheme}, json.Unmarshal)
testPrinter(t, &JSONPrinter{}, json.Unmarshal)
}
func TestPrintDefault(t *testing.T) {
printer, found, err := GetPrinter("", "")
if err != nil {
t.Fatalf("unexpected error: %#v", err)
}
if found {
t.Errorf("no printer should have been found: %#v / %v", printer, err)
}
}
type internalType struct {
Name string
Kind string
}
type externalType struct {
Name string
Kind string `json:"kind"`
func (*internalType) IsAnAPIObject() {
}
func (*internalType) IsAnAPIObject() {}
func (*externalType) IsAnAPIObject() {}
func newExternalScheme() *runtime.Scheme {
scheme := runtime.NewScheme()
scheme.AddKnownTypeWithName("", "Type", &internalType{})
scheme.AddKnownTypeWithName("unlikelyversion", "Type", &externalType{})
return scheme
}
func TestPrintJSONForUnknownSchema(t *testing.T) {
func TestPrintJSONForObject(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
printer, err := GetPrinter("json", "", "unlikelyversion", newExternalScheme(), nil)
if err != nil {
printer, found, err := GetPrinter("json", "")
if err != nil || !found {
t.Fatalf("unexpected error: %#v", err)
}
if err := printer.PrintObj(&internalType{Name: "foo"}, buf); err != nil {
@ -102,21 +121,10 @@ func TestPrintJSONForUnknownSchema(t *testing.T) {
}
}
func TestPrintJSONForUnknownSchemaAndWrongVersion(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
printer, err := GetPrinter("json", "", "badversion", newExternalScheme(), nil)
if err != nil {
t.Fatalf("unexpected error: %#v", err)
}
if err := printer.PrintObj(&internalType{Name: "foo"}, buf); err == nil {
t.Errorf("unexpected non-error")
}
}
func TestPrintJSON(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
printer, err := GetPrinter("json", "", testapi.Version(), api.Scheme, nil)
if err != nil {
printer, found, err := GetPrinter("json", "")
if err != nil || !found {
t.Fatalf("unexpected error: %#v", err)
}
printer.PrintObj(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, buf)
@ -128,8 +136,8 @@ func TestPrintJSON(t *testing.T) {
func TestPrintYAML(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
printer, err := GetPrinter("yaml", "", testapi.Version(), api.Scheme, nil)
if err != nil {
printer, found, err := GetPrinter("yaml", "")
if err != nil || !found {
t.Fatalf("unexpected error: %#v", err)
}
printer.PrintObj(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, buf)
@ -141,11 +149,11 @@ func TestPrintYAML(t *testing.T) {
func TestPrintTemplate(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
printer, err := GetPrinter("template", "{{.id}}", "v1beta1", api.Scheme, nil)
if err != nil {
printer, found, err := GetPrinter("template", "{{.id}}")
if err != nil || !found {
t.Fatalf("unexpected error: %#v", err)
}
err = printer.PrintObj(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, buf)
err = printer.PrintObj(&v1beta1.Pod{TypeMeta: v1beta1.TypeMeta{ID: "foo"}}, buf)
if err != nil {
t.Fatalf("unexpected error: %#v", err)
}
@ -155,19 +163,19 @@ func TestPrintTemplate(t *testing.T) {
}
func TestPrintEmptyTemplate(t *testing.T) {
if _, err := GetPrinter("template", "", testapi.Version(), api.Scheme, nil); err == nil {
if _, _, err := GetPrinter("template", ""); err == nil {
t.Errorf("unexpected non-error")
}
}
func TestPrintBadTemplate(t *testing.T) {
if _, err := GetPrinter("template", "{{ .Name", testapi.Version(), api.Scheme, nil); err == nil {
if _, _, err := GetPrinter("template", "{{ .Name"); err == nil {
t.Errorf("unexpected non-error")
}
}
func TestPrintBadTemplateFile(t *testing.T) {
if _, err := GetPrinter("templatefile", "", testapi.Version(), api.Scheme, nil); err == nil {
if _, _, err := GetPrinter("templatefile", ""); err == nil {
t.Errorf("unexpected non-error")
}
}
@ -276,12 +284,17 @@ func TestUnknownTypePrinting(t *testing.T) {
func TestTemplateEmitsVersionedObjects(t *testing.T) {
// kind is always blank in memory and set on the wire
printer, err := NewTemplatePrinter([]byte(`{{.kind}}`), testapi.Version(), api.Scheme)
printer, err := NewTemplatePrinter([]byte(`{{.kind}}`))
if err != nil {
t.Fatalf("tmpl fail: %v", err)
}
obj, err := api.Scheme.ConvertToVersion(&api.Pod{}, "v1beta1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
buffer := &bytes.Buffer{}
err = printer.PrintObj(&api.Pod{}, buffer)
err = printer.PrintObj(obj, buffer)
if err != nil {
t.Fatalf("print fail: %v", err)
}
@ -292,7 +305,7 @@ func TestTemplateEmitsVersionedObjects(t *testing.T) {
func TestTemplatePanic(t *testing.T) {
tmpl := `{{and ((index .currentState.info "update-demo").state.running.startedAt) .currentState.info.net.state.running.startedAt}}`
printer, err := NewTemplatePrinter([]byte(tmpl), testapi.Version(), api.Scheme)
printer, err := NewTemplatePrinter([]byte(tmpl))
if err != nil {
t.Fatalf("tmpl fail: %v", err)
}
@ -396,11 +409,13 @@ e: {{exists . "currentState" "info" "update-demo" "state" "running"}}
f: {{exists . "currentState" "info" "update-demo" "state" "running" "startedAt"}}`
_ = useThisToDebug // don't complain about unused var
printer, err := NewTemplatePrinter([]byte(tmpl), "v1beta1", api.Scheme)
p, err := NewTemplatePrinter([]byte(tmpl))
if err != nil {
t.Fatalf("tmpl fail: %v", err)
}
printer := NewVersionedPrinter(p, api.Scheme, "v1beta1")
for name, item := range table {
buffer := &bytes.Buffer{}
err = printer.PrintObj(&item.pod, buffer)
@ -416,19 +431,19 @@ f: {{exists . "currentState" "info" "update-demo" "state" "running" "startedAt"}
func TestPrinters(t *testing.T) {
om := func(name string) api.ObjectMeta { return api.ObjectMeta{Name: name} }
templatePrinter, err := NewTemplatePrinter([]byte("{{.name}}"), testapi.Version(), api.Scheme)
templatePrinter, err := NewTemplatePrinter([]byte("{{.name}}"))
if err != nil {
t.Fatal(err)
}
templatePrinter2, err := NewTemplatePrinter([]byte("{{len .items}}"), testapi.Version(), api.Scheme)
templatePrinter2, err := NewTemplatePrinter([]byte("{{len .items}}"))
if err != nil {
t.Fatal(err)
}
printers := map[string]ResourcePrinter{
"humanReadable": NewHumanReadablePrinter(true),
"humanReadableHeaders": NewHumanReadablePrinter(false),
"json": &JSONPrinter{testapi.Version(), api.Scheme},
"yaml": &YAMLPrinter{testapi.Version(), api.Scheme},
"json": &JSONPrinter{},
"yaml": &YAMLPrinter{},
"template": templatePrinter,
"template2": templatePrinter2,
}

View File

@ -17,16 +17,15 @@ limitations under the License.
package kubectl
import (
"io"
"os"
"os/signal"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
)
// WatchLoop loops, writing objects in the events from w to printer.
// WatchLoop loops, passing events in w to fn.
// If user sends interrupt signal, shut down cleanly. Otherwise, never return.
func WatchLoop(w watch.Interface, printer ResourcePrinter, out io.Writer) {
func WatchLoop(w watch.Interface, fn func(watch.Event) error) {
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt)
defer signal.Stop(signals)
@ -36,8 +35,7 @@ func WatchLoop(w watch.Interface, printer ResourcePrinter, out io.Writer) {
if !ok {
return
}
// TODO: need to print out added/modified/deleted!
if err := printer.PrintObj(event.Object, out); err != nil {
if err := fn(event); err != nil {
w.Stop()
}
case <-signals: