mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 12:43:23 +00:00
Merge pull request #3004 from smarterclayton/allow_get_to_span_resources
Allow kubectl get to fetch multiple resource types by label
This commit is contained in:
commit
bb140d636a
@ -28,6 +28,7 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/clientcmd"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"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
|
// Factory provides abstractions that allow the Kubectl command to be extended across multiple types
|
||||||
// of resources and different API sets.
|
// of resources and different API sets.
|
||||||
|
// TODO: make the functions interfaces
|
||||||
type Factory struct {
|
type Factory struct {
|
||||||
clients *clientCache
|
clients *clientCache
|
||||||
flags *pflag.FlagSet
|
flags *pflag.FlagSet
|
||||||
@ -218,6 +220,13 @@ func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
|
|||||||
return 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) {
|
func checkErr(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.FatalDepth(1, err)
|
glog.FatalDepth(1, err)
|
||||||
|
@ -22,6 +22,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"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/api/meta"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl"
|
||||||
. "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
|
. "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
|
||||||
@ -66,12 +68,12 @@ func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type testPrinter struct {
|
type testPrinter struct {
|
||||||
Obj runtime.Object
|
Objects []runtime.Object
|
||||||
Err error
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *testPrinter) PrintObj(obj runtime.Object, out io.Writer) 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)
|
fmt.Fprintf(out, "%#v", obj)
|
||||||
return t.Err
|
return t.Err
|
||||||
}
|
}
|
||||||
@ -112,6 +114,23 @@ func NewTestFactory() (*Factory, *testFactory, runtime.Codec) {
|
|||||||
}, t, 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 {
|
func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
|
||||||
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
|
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ Examples:
|
|||||||
checkErr(err)
|
checkErr(err)
|
||||||
selector := GetFlagString(cmd, "selector")
|
selector := GetFlagString(cmd, "selector")
|
||||||
found := 0
|
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++
|
found++
|
||||||
if err := resource.NewHelper(r.Client, r.Mapping).Delete(r.Namespace, r.Name); err != nil {
|
if err := resource.NewHelper(r.Client, r.Mapping).Delete(r.Namespace, r.Name); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -20,14 +20,17 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"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"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"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 {
|
func (f *Factory) NewCmdGet(out io.Writer) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "get [(-o|--output=)json|yaml|...] <resource> [<id>]",
|
Use: "get [(-o|--output=)json|yaml|...] <resource> [<id>]",
|
||||||
@ -48,66 +51,134 @@ Examples:
|
|||||||
<list single replication controller in ps output format>
|
<list single replication controller in ps output format>
|
||||||
|
|
||||||
$ kubectl get -o json pod 1234-56-7890-234234-456456
|
$ 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) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
mapping, namespace, name := ResourceOrTypeFromArgs(cmd, args, f.Mapper)
|
RunGet(f, out, cmd, args)
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.Flags().StringP("output", "o", "", "Output format: json|yaml|template|templatefile")
|
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().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().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().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().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.")
|
cmd.Flags().Bool("watch-only", false, "Watch for changes to the requseted object(s), without listing/getting first.")
|
||||||
return cmd
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
@ -27,9 +29,38 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
"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.
|
// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get.
|
||||||
func TestGetUnknownSchemaObject(t *testing.T) {
|
func TestGetUnknownSchemaObject(t *testing.T) {
|
||||||
f, tf, codec := NewTestFactory()
|
f, tf, codec := NewTestFactory()
|
||||||
@ -41,12 +72,13 @@ func TestGetUnknownSchemaObject(t *testing.T) {
|
|||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
|
||||||
cmd := f.NewCmdGet(buf)
|
cmd := f.NewCmdGet(buf)
|
||||||
|
cmd.SetOutput(buf)
|
||||||
cmd.Flags().String("api-version", "default", "")
|
cmd.Flags().String("api-version", "default", "")
|
||||||
cmd.Flags().String("namespace", "test", "")
|
cmd.Flags().String("namespace", "test", "")
|
||||||
cmd.Run(cmd, []string{"type", "foo"})
|
cmd.Run(cmd, []string{"type", "foo"})
|
||||||
|
|
||||||
expected := &internalType{Name: "foo"}
|
expected := &internalType{Name: "foo"}
|
||||||
actual := tf.Printer.(*testPrinter).Obj
|
actual := tf.Printer.(*testPrinter).Objects[0]
|
||||||
if !reflect.DeepEqual(expected, actual) {
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
t.Errorf("unexpected object: %#v", actual)
|
t.Errorf("unexpected object: %#v", actual)
|
||||||
}
|
}
|
||||||
@ -73,12 +105,347 @@ func TestGetSchemaObject(t *testing.T) {
|
|||||||
cmd.Flags().String("namespace", "test", "")
|
cmd.Flags().String("namespace", "test", "")
|
||||||
cmd.Run(cmd, []string{"replicationcontrollers", "foo"})
|
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\"") {
|
if !strings.Contains(buf.String(), "\"foo\"") {
|
||||||
t.Errorf("unexpected output: %s", buf.String())
|
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,
|
mapper meta.RESTMapper,
|
||||||
clientBuilder func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error),
|
clientBuilder func(cmd *cobra.Command, mapping *meta.RESTMapping) (kubectl.RESTClient, error),
|
||||||
schema validation.Schema,
|
schema validation.Schema,
|
||||||
|
requireNames bool,
|
||||||
) resource.Visitor {
|
) resource.Visitor {
|
||||||
|
|
||||||
// handling filename & resource id
|
// handling filename & resource id
|
||||||
if len(selector) == 0 {
|
if len(selector) == 0 {
|
||||||
mapping, namespace, name := ResourceFromArgsOrFile(cmd, args, filename, typer, mapper, schema)
|
if requireNames || len(filename) > 0 {
|
||||||
client, err := clientBuilder(cmd, mapping)
|
mapping, namespace, name := ResourceFromArgsOrFile(cmd, args, filename, typer, mapper, schema)
|
||||||
checkErr(err)
|
client, err := clientBuilder(cmd, mapping)
|
||||||
|
checkErr(err)
|
||||||
return resource.NewInfo(client, mapping, namespace, name)
|
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)
|
labelSelector, err := labels.ParseSelector(selector)
|
||||||
@ -58,9 +66,12 @@ func ResourcesFromArgsOrFile(
|
|||||||
namespace := GetKubeNamespace(cmd)
|
namespace := GetKubeNamespace(cmd)
|
||||||
visitors := resource.VisitorList{}
|
visitors := resource.VisitorList{}
|
||||||
|
|
||||||
if len(args) != 1 {
|
if len(args) < 1 {
|
||||||
usageError(cmd, "Must specify the type of resource")
|
usageError(cmd, "Must specify the type of resource")
|
||||||
}
|
}
|
||||||
|
if len(args) > 1 {
|
||||||
|
usageError(cmd, "Too many arguments")
|
||||||
|
}
|
||||||
types := SplitResourceArgument(args[0])
|
types := SplitResourceArgument(args[0])
|
||||||
for _, arg := range types {
|
for _, arg := range types {
|
||||||
resourceName := arg
|
resourceName := arg
|
||||||
|
@ -35,66 +35,94 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetPrinter takes a format type, an optional format argument, a version and a convertor
|
// GetPrinter takes a format type, an optional format argument. It will return true
|
||||||
// to be used if the underlying printer requires the object to be in a specific schema (
|
// if the format is generic (untyped), otherwise it will return false. The printer
|
||||||
// any of the generic formatters), and the default printer to use for this object.
|
// is agnostic to schema versions, so you must send arguments to PrintObj in the
|
||||||
func GetPrinter(format, formatArgument, version string, convertor runtime.ObjectConvertor, defaultPrinter ResourcePrinter) (ResourcePrinter, error) {
|
// 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
|
var printer ResourcePrinter
|
||||||
switch format {
|
switch format {
|
||||||
case "json":
|
case "json":
|
||||||
printer = &JSONPrinter{version, convertor}
|
printer = &JSONPrinter{}
|
||||||
case "yaml":
|
case "yaml":
|
||||||
printer = &YAMLPrinter{version, convertor}
|
printer = &YAMLPrinter{}
|
||||||
case "template":
|
case "template":
|
||||||
if len(formatArgument) == 0 {
|
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
|
var err error
|
||||||
printer, err = NewTemplatePrinter([]byte(formatArgument), version, convertor)
|
printer, err = NewTemplatePrinter([]byte(formatArgument))
|
||||||
if err != nil {
|
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":
|
case "templatefile":
|
||||||
if len(formatArgument) == 0 {
|
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)
|
data, err := ioutil.ReadFile(formatArgument)
|
||||||
if err != nil {
|
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 {
|
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 "":
|
case "":
|
||||||
printer = defaultPrinter
|
return nil, false, nil
|
||||||
default:
|
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.
|
// ResourcePrinter is an interface that knows how to print runtime objects.
|
||||||
type ResourcePrinter interface {
|
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
|
PrintObj(runtime.Object, io.Writer) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSONPrinter is an implementation of ResourcePrinter which outputs an object as JSON.
|
// ResourcePrinterFunc is a function that can print objects
|
||||||
// The input object is assumed to be in the internal version of an API and is converted
|
type ResourcePrinterFunc func(runtime.Object, io.Writer) error
|
||||||
// to the given version first.
|
|
||||||
type JSONPrinter struct {
|
// PrintObj implements ResourcePrinter
|
||||||
version string
|
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
|
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.
|
// 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 {
|
func (p *JSONPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
|
||||||
outObj, err := p.convertor.ConvertToVersion(obj, p.version)
|
data, err := json.Marshal(obj)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(outObj)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -115,11 +143,7 @@ type YAMLPrinter struct {
|
|||||||
|
|
||||||
// PrintObj prints the data as YAML.
|
// PrintObj prints the data as YAML.
|
||||||
func (p *YAMLPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
|
func (p *YAMLPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
|
||||||
outObj, err := p.convertor.ConvertToVersion(obj, p.version)
|
output, err := yaml.Marshal(obj)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
output, err := yaml.Marshal(outObj)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -388,11 +412,9 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
|
|||||||
type TemplatePrinter struct {
|
type TemplatePrinter struct {
|
||||||
rawTemplate string
|
rawTemplate string
|
||||||
template *template.Template
|
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").
|
t, err := template.New("output").
|
||||||
Funcs(template.FuncMap{"exists": exists}).
|
Funcs(template.FuncMap{"exists": exists}).
|
||||||
Parse(string(tmpl))
|
Parse(string(tmpl))
|
||||||
@ -402,18 +424,12 @@ func NewTemplatePrinter(tmpl []byte, asVersion string, convertor runtime.ObjectC
|
|||||||
return &TemplatePrinter{
|
return &TemplatePrinter{
|
||||||
rawTemplate: string(tmpl),
|
rawTemplate: string(tmpl),
|
||||||
template: t,
|
template: t,
|
||||||
version: asVersion,
|
|
||||||
convertor: convertor,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintObj formats the obj with the Go Template.
|
// PrintObj formats the obj with the Go Template.
|
||||||
func (p *TemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
|
func (p *TemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
|
||||||
outObj, err := p.convertor.ConvertToVersion(obj, p.version)
|
data, err := json.Marshal(obj)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(outObj)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
"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/runtime"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
|
|
||||||
@ -56,38 +57,56 @@ var testData = testStruct{
|
|||||||
IntList: []int{1, 2, 3},
|
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) {
|
func TestYAMLPrinter(t *testing.T) {
|
||||||
testPrinter(t, &YAMLPrinter{testapi.Version(), api.Scheme}, yaml.Unmarshal)
|
testPrinter(t, &YAMLPrinter{}, yaml.Unmarshal)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONPrinter(t *testing.T) {
|
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 {
|
type internalType struct {
|
||||||
Name string
|
Name string
|
||||||
Kind string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type externalType struct {
|
func (*internalType) IsAnAPIObject() {
|
||||||
Name string
|
|
||||||
Kind string `json:"kind"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*internalType) IsAnAPIObject() {}
|
func TestPrintJSONForObject(t *testing.T) {
|
||||||
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) {
|
|
||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
printer, err := GetPrinter("json", "", "unlikelyversion", newExternalScheme(), nil)
|
printer, found, err := GetPrinter("json", "")
|
||||||
if err != nil {
|
if err != nil || !found {
|
||||||
t.Fatalf("unexpected error: %#v", err)
|
t.Fatalf("unexpected error: %#v", err)
|
||||||
}
|
}
|
||||||
if err := printer.PrintObj(&internalType{Name: "foo"}, buf); err != nil {
|
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) {
|
func TestPrintJSON(t *testing.T) {
|
||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
printer, err := GetPrinter("json", "", testapi.Version(), api.Scheme, nil)
|
printer, found, err := GetPrinter("json", "")
|
||||||
if err != nil {
|
if err != nil || !found {
|
||||||
t.Fatalf("unexpected error: %#v", err)
|
t.Fatalf("unexpected error: %#v", err)
|
||||||
}
|
}
|
||||||
printer.PrintObj(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, buf)
|
printer.PrintObj(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, buf)
|
||||||
@ -128,8 +136,8 @@ func TestPrintJSON(t *testing.T) {
|
|||||||
|
|
||||||
func TestPrintYAML(t *testing.T) {
|
func TestPrintYAML(t *testing.T) {
|
||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
printer, err := GetPrinter("yaml", "", testapi.Version(), api.Scheme, nil)
|
printer, found, err := GetPrinter("yaml", "")
|
||||||
if err != nil {
|
if err != nil || !found {
|
||||||
t.Fatalf("unexpected error: %#v", err)
|
t.Fatalf("unexpected error: %#v", err)
|
||||||
}
|
}
|
||||||
printer.PrintObj(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, buf)
|
printer.PrintObj(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}, buf)
|
||||||
@ -141,11 +149,11 @@ func TestPrintYAML(t *testing.T) {
|
|||||||
|
|
||||||
func TestPrintTemplate(t *testing.T) {
|
func TestPrintTemplate(t *testing.T) {
|
||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
printer, err := GetPrinter("template", "{{.id}}", "v1beta1", api.Scheme, nil)
|
printer, found, err := GetPrinter("template", "{{.id}}")
|
||||||
if err != nil {
|
if err != nil || !found {
|
||||||
t.Fatalf("unexpected error: %#v", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %#v", err)
|
t.Fatalf("unexpected error: %#v", err)
|
||||||
}
|
}
|
||||||
@ -155,19 +163,19 @@ func TestPrintTemplate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPrintEmptyTemplate(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")
|
t.Errorf("unexpected non-error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrintBadTemplate(t *testing.T) {
|
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")
|
t.Errorf("unexpected non-error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrintBadTemplateFile(t *testing.T) {
|
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")
|
t.Errorf("unexpected non-error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -276,12 +284,17 @@ func TestUnknownTypePrinting(t *testing.T) {
|
|||||||
|
|
||||||
func TestTemplateEmitsVersionedObjects(t *testing.T) {
|
func TestTemplateEmitsVersionedObjects(t *testing.T) {
|
||||||
// kind is always blank in memory and set on the wire
|
// 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 {
|
if err != nil {
|
||||||
t.Fatalf("tmpl fail: %v", err)
|
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{}
|
buffer := &bytes.Buffer{}
|
||||||
err = printer.PrintObj(&api.Pod{}, buffer)
|
err = printer.PrintObj(obj, buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("print fail: %v", err)
|
t.Fatalf("print fail: %v", err)
|
||||||
}
|
}
|
||||||
@ -292,7 +305,7 @@ func TestTemplateEmitsVersionedObjects(t *testing.T) {
|
|||||||
|
|
||||||
func TestTemplatePanic(t *testing.T) {
|
func TestTemplatePanic(t *testing.T) {
|
||||||
tmpl := `{{and ((index .currentState.info "update-demo").state.running.startedAt) .currentState.info.net.state.running.startedAt}}`
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("tmpl fail: %v", err)
|
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"}}`
|
f: {{exists . "currentState" "info" "update-demo" "state" "running" "startedAt"}}`
|
||||||
_ = useThisToDebug // don't complain about unused var
|
_ = useThisToDebug // don't complain about unused var
|
||||||
|
|
||||||
printer, err := NewTemplatePrinter([]byte(tmpl), "v1beta1", api.Scheme)
|
p, err := NewTemplatePrinter([]byte(tmpl))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("tmpl fail: %v", err)
|
t.Fatalf("tmpl fail: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printer := NewVersionedPrinter(p, api.Scheme, "v1beta1")
|
||||||
|
|
||||||
for name, item := range table {
|
for name, item := range table {
|
||||||
buffer := &bytes.Buffer{}
|
buffer := &bytes.Buffer{}
|
||||||
err = printer.PrintObj(&item.pod, 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) {
|
func TestPrinters(t *testing.T) {
|
||||||
om := func(name string) api.ObjectMeta { return api.ObjectMeta{Name: name} }
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
templatePrinter2, err := NewTemplatePrinter([]byte("{{len .items}}"), testapi.Version(), api.Scheme)
|
templatePrinter2, err := NewTemplatePrinter([]byte("{{len .items}}"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
printers := map[string]ResourcePrinter{
|
printers := map[string]ResourcePrinter{
|
||||||
"humanReadable": NewHumanReadablePrinter(true),
|
"humanReadable": NewHumanReadablePrinter(true),
|
||||||
"humanReadableHeaders": NewHumanReadablePrinter(false),
|
"humanReadableHeaders": NewHumanReadablePrinter(false),
|
||||||
"json": &JSONPrinter{testapi.Version(), api.Scheme},
|
"json": &JSONPrinter{},
|
||||||
"yaml": &YAMLPrinter{testapi.Version(), api.Scheme},
|
"yaml": &YAMLPrinter{},
|
||||||
"template": templatePrinter,
|
"template": templatePrinter,
|
||||||
"template2": templatePrinter2,
|
"template2": templatePrinter2,
|
||||||
}
|
}
|
||||||
|
@ -17,16 +17,15 @@ limitations under the License.
|
|||||||
package kubectl
|
package kubectl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
"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.
|
// 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)
|
signals := make(chan os.Signal, 1)
|
||||||
signal.Notify(signals, os.Interrupt)
|
signal.Notify(signals, os.Interrupt)
|
||||||
defer signal.Stop(signals)
|
defer signal.Stop(signals)
|
||||||
@ -36,8 +35,7 @@ func WatchLoop(w watch.Interface, printer ResourcePrinter, out io.Writer) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// TODO: need to print out added/modified/deleted!
|
if err := fn(event); err != nil {
|
||||||
if err := printer.PrintObj(event.Object, out); err != nil {
|
|
||||||
w.Stop()
|
w.Stop()
|
||||||
}
|
}
|
||||||
case <-signals:
|
case <-signals:
|
||||||
|
Loading…
Reference in New Issue
Block a user