Denote if a printer is generic.

This fixes #38779.

This allows us to avoid case in which printers.GetStandardPrinter
returns nil for both printer and err removing any potential panics that
may arise throughout kubectl commands.

Please see #38779 and #38112 for complete context.

Add comment explaining adding handlers to printers.HumanReadablePrinter
also remove an unnecessary conversion of printers.HumanReadablePrinter
to printers.ResourcePrinter.
pull/6/head
Waseem Ahmad 2017-05-23 08:17:20 +05:30
parent 77a8c25839
commit 8442a118ea
24 changed files with 182 additions and 96 deletions

View File

@ -456,8 +456,7 @@ func TestApplyObjectOutput(t *testing.T) {
} }
f, tf, _, _ := cmdtesting.NewAPIFactory() f, tf, _, _ := cmdtesting.NewAPIFactory()
tf.CommandPrinter = &printers.YAMLPrinter{} tf.Printer = &printers.YAMLPrinter{}
tf.GenericPrinter = true
tf.UnstructuredClient = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,

View File

@ -80,8 +80,9 @@ func defaultClientConfigForVersion(version *schema.GroupVersion) *restclient.Con
} }
type testPrinter struct { type testPrinter struct {
Objects []runtime.Object Objects []runtime.Object
Err error Err error
GenericPrinter bool
} }
func (t *testPrinter) PrintObj(obj runtime.Object, out io.Writer) error { func (t *testPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
@ -99,6 +100,10 @@ func (t *testPrinter) AfterPrint(output io.Writer, res string) error {
return nil return nil
} }
func (t *testPrinter) IsGeneric() bool {
return t.GenericPrinter
}
type testDescriber struct { type testDescriber struct {
Name, Namespace string Name, Namespace string
Settings printers.DescriberSettings Settings printers.DescriberSettings

View File

@ -81,7 +81,7 @@ func NewCmdConfigView(out, errOut io.Writer, ConfigAccess clientcmd.ConfigAccess
cmd.Flags().Set("output", defaultOutputFormat) cmd.Flags().Set("output", defaultOutputFormat)
} }
printer, _, err := cmdutil.PrinterForCommand(cmd, meta.NewDefaultRESTMapper(nil, nil), latest.Scheme, []runtime.Decoder{latest.Codec}) printer, err := cmdutil.PrinterForCommand(cmd, meta.NewDefaultRESTMapper(nil, nil), latest.Scheme, nil, []runtime.Decoder{latest.Codec}, printers.PrintOptions{})
cmdutil.CheckErr(err) cmdutil.CheckErr(err)
printer = printers.NewVersionedPrinter(printer, latest.Scheme, latest.ExternalVersion) printer = printers.NewVersionedPrinter(printer, latest.Scheme, latest.ExternalVersion)

View File

@ -164,7 +164,7 @@ func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.C
cmd.Flags().Set("output", outputFormat) cmd.Flags().Set("output", outputFormat)
} }
o.encoder = f.JSONEncoder() o.encoder = f.JSONEncoder()
o.printer, _, err = f.PrinterForCommand(cmd) o.printer, err = f.PrinterForCommand(cmd, printers.PrintOptions{})
if err != nil { if err != nil {
return err return err
} }

View File

@ -46,6 +46,10 @@ func (t *testClusterRolePrinter) HandledResources() []string {
return []string{} return []string{}
} }
func (t *testClusterRolePrinter) IsGeneric() bool {
return true
}
func TestCreateClusterRole(t *testing.T) { func TestCreateClusterRole(t *testing.T) {
clusterRoleName := "my-cluster-role" clusterRoleName := "my-cluster-role"

View File

@ -46,6 +46,10 @@ func (t *testRolePrinter) HandledResources() []string {
return []string{} return []string{}
} }
func (t *testRolePrinter) IsGeneric() bool {
return true
}
func TestCreateRole(t *testing.T) { func TestCreateRole(t *testing.T) {
roleName := "my-role" roleName := "my-role"

View File

@ -302,7 +302,7 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
return err return err
} }
printer, generic, err := f.PrinterForCommand(cmd) printer, err := f.PrinterForCommand(cmd, printers.PrintOptions{})
if err != nil { if err != nil {
return err return err
} }
@ -313,7 +313,7 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
r.IgnoreErrors(kapierrors.IsNotFound) r.IgnoreErrors(kapierrors.IsNotFound)
} }
if generic { if printer.IsGeneric() {
// we flattened the data from the builder, so we have individual items, but now we'd like to either: // we flattened the data from the builder, so we have individual items, but now we'd like to either:
// 1. if there is more than one item, combine them all into a single list // 1. if there is more than one item, combine them all into a single list
// 2. if there is a single item and that item is a list, leave it as its specific list // 2. if there is a single item and that item is a list, leave it as its specific list
@ -436,7 +436,7 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
mapping = infos[ix].Mapping mapping = infos[ix].Mapping
original = infos[ix].Object original = infos[ix].Object
} }
if printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource { if shouldGetNewPrinterForMapping(printer, lastMapping, mapping) {
if printer != nil { if printer != nil {
w.Flush() w.Flush()
} }
@ -513,3 +513,7 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
cmdutil.PrintFilterCount(errOut, len(objs), filteredResourceCount, len(allErrs), filterOpts, options.IgnoreNotFound) cmdutil.PrintFilterCount(errOut, len(objs), filteredResourceCount, len(allErrs), filterOpts, options.IgnoreNotFound)
return utilerrors.NewAggregate(allErrs) return utilerrors.NewAggregate(allErrs)
} }
func shouldGetNewPrinterForMapping(printer printers.ResourcePrinter, lastMapping, mapping *meta.RESTMapping) bool {
return printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource
}

View File

@ -220,15 +220,16 @@ func TestGetObjectsFiltered(t *testing.T) {
second := &pods.Items[1] second := &pods.Items[1]
testCases := []struct { testCases := []struct {
args []string args []string
resp runtime.Object resp runtime.Object
flags map[string]string flags map[string]string
expect []runtime.Object expect []runtime.Object
genericPrinter bool
}{ }{
{args: []string{"pods", "foo"}, resp: first, expect: []runtime.Object{first}}, {args: []string{"pods", "foo"}, resp: first, expect: []runtime.Object{first}, genericPrinter: true},
{args: []string{"pods", "foo"}, flags: map[string]string{"show-all": "false"}, resp: first, expect: []runtime.Object{first}}, {args: []string{"pods", "foo"}, flags: map[string]string{"show-all": "false"}, resp: first, expect: []runtime.Object{first}, genericPrinter: true},
{args: []string{"pods"}, flags: map[string]string{"show-all": "true"}, resp: pods, expect: []runtime.Object{first, second}}, {args: []string{"pods"}, flags: map[string]string{"show-all": "true"}, resp: pods, expect: []runtime.Object{first, second}},
{args: []string{"pods/foo"}, resp: first, expect: []runtime.Object{first}}, {args: []string{"pods/foo"}, resp: first, expect: []runtime.Object{first}, genericPrinter: true},
{args: []string{"pods"}, flags: map[string]string{"output": "yaml"}, resp: pods, expect: []runtime.Object{second}}, {args: []string{"pods"}, flags: map[string]string{"output": "yaml"}, resp: pods, expect: []runtime.Object{second}},
{args: []string{}, flags: map[string]string{"filename": "../../../examples/storage/cassandra/cassandra-controller.yaml"}, resp: pods, expect: []runtime.Object{first, second}}, {args: []string{}, flags: map[string]string{"filename": "../../../examples/storage/cassandra/cassandra-controller.yaml"}, resp: pods, expect: []runtime.Object{first, second}},
@ -240,7 +241,7 @@ func TestGetObjectsFiltered(t *testing.T) {
for i, test := range testCases { for i, test := range testCases {
t.Logf("%d", i) t.Logf("%d", i)
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{GenericPrinter: test.genericPrinter}
tf.UnstructuredClient = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
@ -281,7 +282,7 @@ func TestGetObjectIgnoreNotFound(t *testing.T) {
} }
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{GenericPrinter: true}
tf.UnstructuredClient = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
@ -393,7 +394,7 @@ func TestGetObjectsIdentifiedByFile(t *testing.T) {
pods, _, _ := testData() pods, _, _ := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{GenericPrinter: true}
tf.UnstructuredClient = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
@ -561,8 +562,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
pods, svc, _ := testData() pods, svc, _ := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.CommandPrinter = &testPrinter{} tf.Printer = &testPrinter{GenericPrinter: true}
tf.GenericPrinter = true
tf.UnstructuredClient = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,
@ -589,7 +589,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
cmd.Flags().Set("output", "json") cmd.Flags().Set("output", "json")
cmd.Run(cmd, []string{"pods,services"}) cmd.Run(cmd, []string{"pods,services"})
actual := tf.CommandPrinter.(*testPrinter).Objects actual := tf.Printer.(*testPrinter).Objects
fn := func(obj runtime.Object) unstructured.Unstructured { fn := func(obj runtime.Object) unstructured.Unstructured {
data, err := runtime.Encode(api.Codecs.LegacyCodec(schema.GroupVersion{Version: "v1"}), obj) data, err := runtime.Encode(api.Codecs.LegacyCodec(schema.GroupVersion{Version: "v1"}), obj)
if err != nil { if err != nil {
@ -716,7 +716,7 @@ func TestGetByFormatForcesFlag(t *testing.T) {
pods, _, _ := testData() pods, _, _ := testData()
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.Printer = &testPrinter{} tf.Printer = &testPrinter{GenericPrinter: true}
tf.UnstructuredClient = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,

View File

@ -161,8 +161,7 @@ func TestPatchObjectFromFileOutput(t *testing.T) {
svcCopy.Labels["post-patch"] = "post-patch-value" svcCopy.Labels["post-patch"] = "post-patch-value"
f, tf, codec, _ := cmdtesting.NewAPIFactory() f, tf, codec, _ := cmdtesting.NewAPIFactory()
tf.CommandPrinter = &printers.YAMLPrinter{} tf.Printer = &printers.YAMLPrinter{}
tf.GenericPrinter = true
tf.UnstructuredClient = &fake.RESTClient{ tf.UnstructuredClient = &fake.RESTClient{
APIRegistry: api.Registry, APIRegistry: api.Registry,
NegotiatedSerializer: unstructuredSerializer, NegotiatedSerializer: unstructuredSerializer,

View File

@ -221,13 +221,11 @@ type TestFactory struct {
UnstructuredClient kubectl.RESTClient UnstructuredClient kubectl.RESTClient
Describer printers.Describer Describer printers.Describer
Printer printers.ResourcePrinter Printer printers.ResourcePrinter
CommandPrinter printers.ResourcePrinter
Validator validation.Schema Validator validation.Schema
Namespace string Namespace string
ClientConfig *restclient.Config ClientConfig *restclient.Config
Err error Err error
Command string Command string
GenericPrinter bool
TmpDir string TmpDir string
ClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error) ClientForMappingFunc func(mapping *meta.RESTMapping) (resource.RESTClient, error)
@ -338,8 +336,8 @@ func (f *FakeFactory) Describer(*meta.RESTMapping) (printers.Describer, error) {
return f.tf.Describer, f.tf.Err return f.tf.Describer, f.tf.Err
} }
func (f *FakeFactory) PrinterForCommand(cmd *cobra.Command) (printers.ResourcePrinter, bool, error) { func (f *FakeFactory) PrinterForCommand(cmd *cobra.Command, options printers.PrintOptions) (printers.ResourcePrinter, error) {
return f.tf.CommandPrinter, f.tf.GenericPrinter, f.tf.Err return f.tf.Printer, f.tf.Err
} }
func (f *FakeFactory) Printer(mapping *meta.RESTMapping, options printers.PrintOptions) (printers.ResourcePrinter, error) { func (f *FakeFactory) Printer(mapping *meta.RESTMapping, options printers.PrintOptions) (printers.ResourcePrinter, error) {
@ -619,8 +617,8 @@ func (f *fakeAPIFactory) UnstructuredClientForMapping(m *meta.RESTMapping) (reso
return f.tf.UnstructuredClient, f.tf.Err return f.tf.UnstructuredClient, f.tf.Err
} }
func (f *fakeAPIFactory) PrinterForCommand(cmd *cobra.Command) (printers.ResourcePrinter, bool, error) { func (f *fakeAPIFactory) PrinterForCommand(cmd *cobra.Command, options printers.PrintOptions) (printers.ResourcePrinter, error) {
return f.tf.CommandPrinter, f.tf.GenericPrinter, f.tf.Err return f.tf.Printer, f.tf.Err
} }
func (f *fakeAPIFactory) Describer(*meta.RESTMapping) (printers.Describer, error) { func (f *fakeAPIFactory) Describer(*meta.RESTMapping) (printers.Describer, error) {

View File

@ -230,10 +230,10 @@ type ObjectMappingFactory interface {
// Generally they depend upon client mapper functions // Generally they depend upon client mapper functions
type BuilderFactory interface { type BuilderFactory interface {
// PrinterForCommand returns the default printer for the command. It requires that certain options // PrinterForCommand returns the default printer for the command. It requires that certain options
// are declared on the command (see AddPrinterFlags). Returns a printer, true if the printer is // are declared on the command (see AddPrinterFlags). Returns a printer, or an error if a printer
// generic (is not internal), or an error if a printer could not be found. // could not be found.
// TODO: Break the dependency on cmd here. // TODO: Break the dependency on cmd here.
PrinterForCommand(cmd *cobra.Command) (printers.ResourcePrinter, bool, error) PrinterForCommand(cmd *cobra.Command, options printers.PrintOptions) (printers.ResourcePrinter, error)
// PrinterForMapping returns a printer suitable for displaying the provided resource type. // PrinterForMapping returns a printer suitable for displaying the provided resource type.
// Requires that printer flags have been added to cmd (see AddPrinterFlags). // Requires that printer flags have been added to cmd (see AddPrinterFlags).
PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinter, error) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinter, error)

View File

@ -31,6 +31,7 @@ import (
"k8s.io/kubernetes/pkg/kubectl/plugins" "k8s.io/kubernetes/pkg/kubectl/plugins"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
) )
type ring2Factory struct { type ring2Factory struct {
@ -47,24 +48,41 @@ func NewBuilderFactory(clientAccessFactory ClientAccessFactory, objectMappingFac
return f return f
} }
func (f *ring2Factory) PrinterForCommand(cmd *cobra.Command) (printers.ResourcePrinter, bool, error) { func (f *ring2Factory) PrinterForCommand(cmd *cobra.Command, options printers.PrintOptions) (printers.ResourcePrinter, error) {
mapper, typer, err := f.objectMappingFactory.UnstructuredObject() mapper, typer, err := f.objectMappingFactory.UnstructuredObject()
if err != nil { if err != nil {
return nil, false, err return nil, err
} }
// TODO: used by the custom column implementation and the name implementation, break this dependency // TODO: used by the custom column implementation and the name implementation, break this dependency
decoders := []runtime.Decoder{f.clientAccessFactory.Decoder(true), unstructured.UnstructuredJSONScheme} decoders := []runtime.Decoder{f.clientAccessFactory.Decoder(true), unstructured.UnstructuredJSONScheme}
return PrinterForCommand(cmd, mapper, typer, decoders) encoder := f.clientAccessFactory.JSONEncoder()
return PrinterForCommand(cmd, mapper, typer, encoder, decoders, options)
} }
func (f *ring2Factory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinter, error) { func (f *ring2Factory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTMapping, withNamespace bool) (printers.ResourcePrinter, error) {
printer, generic, err := f.PrinterForCommand(cmd) // Some callers do not have "label-columns" so we can't use the GetFlagStringSlice() helper
columnLabel, err := cmd.Flags().GetStringSlice("label-columns")
if err != nil {
columnLabel = []string{}
}
options := printers.PrintOptions{
NoHeaders: GetFlagBool(cmd, "no-headers"),
WithNamespace: withNamespace,
Wide: GetWideFlag(cmd),
ShowAll: GetFlagBool(cmd, "show-all"),
ShowLabels: GetFlagBool(cmd, "show-labels"),
AbsoluteTimestamps: isWatch(cmd),
ColumnLabels: columnLabel,
}
printer, err := f.PrinterForCommand(cmd, options)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Make sure we output versioned data for generic printers // Make sure we output versioned data for generic printers
if generic { if printer.IsGeneric() {
if mapping == nil { if mapping == nil {
return nil, fmt.Errorf("no serialization format found") return nil, fmt.Errorf("no serialization format found")
} }
@ -74,25 +92,17 @@ func (f *ring2Factory) PrinterForMapping(cmd *cobra.Command, mapping *meta.RESTM
} }
printer = printers.NewVersionedPrinter(printer, mapping.ObjectConvertor, version, mapping.GroupVersionKind.GroupVersion()) printer = printers.NewVersionedPrinter(printer, mapping.ObjectConvertor, version, mapping.GroupVersionKind.GroupVersion())
} else { } else {
// Some callers do not have "label-columns" so we can't use the GetFlagStringSlice() helper // We add handlers to the printer in case it is printers.HumanReadablePrinter.
columnLabel, err := cmd.Flags().GetStringSlice("label-columns") // printers.AddHandlers expects concrete type of printers.HumanReadablePrinter
if err != nil { // as its parameter because of this we have to do a type check on printer and
columnLabel = []string{} // extract out concrete HumanReadablePrinter from it. We are then able to attach
// handlers on it.
if humanReadablePrinter, ok := printer.(*printers.HumanReadablePrinter); ok {
printersinternal.AddHandlers(humanReadablePrinter)
printer = humanReadablePrinter
} }
printer, err = f.clientAccessFactory.Printer(mapping, printers.PrintOptions{
NoHeaders: GetFlagBool(cmd, "no-headers"),
WithNamespace: withNamespace,
Wide: GetWideFlag(cmd),
ShowAll: GetFlagBool(cmd, "show-all"),
ShowLabels: GetFlagBool(cmd, "show-labels"),
AbsoluteTimestamps: isWatch(cmd),
ColumnLabels: columnLabel,
})
if err != nil {
return nil, err
}
printer = maybeWrapSortingPrinter(cmd, printer)
} }
return printer, nil return printer, nil

View File

@ -107,7 +107,7 @@ func ValidateOutputArgs(cmd *cobra.Command) error {
// PrinterForCommand returns the default printer for this command. // PrinterForCommand returns the default printer for this command.
// Requires that printer flags have been added to cmd (see AddPrinterFlags). // Requires that printer flags have been added to cmd (see AddPrinterFlags).
func PrinterForCommand(cmd *cobra.Command, mapper meta.RESTMapper, typer runtime.ObjectTyper, decoders []runtime.Decoder) (printers.ResourcePrinter, bool, error) { func PrinterForCommand(cmd *cobra.Command, mapper meta.RESTMapper, typer runtime.ObjectTyper, encoder runtime.Encoder, decoders []runtime.Decoder, options printers.PrintOptions) (printers.ResourcePrinter, error) {
outputFormat := GetFlagString(cmd, "output") outputFormat := GetFlagString(cmd, "output")
// templates are logically optional for specifying a format. // templates are logically optional for specifying a format.
@ -133,15 +133,15 @@ func PrinterForCommand(cmd *cobra.Command, mapper meta.RESTMapper, typer runtime
if cmd.Flags().Lookup("allow-missing-template-keys") != nil { if cmd.Flags().Lookup("allow-missing-template-keys") != nil {
allowMissingTemplateKeys = GetFlagBool(cmd, "allow-missing-template-keys") allowMissingTemplateKeys = GetFlagBool(cmd, "allow-missing-template-keys")
} }
printer, generic, err := printers.GetStandardPrinter( printer, err := printers.GetStandardPrinter(
outputFormat, templateFile, GetFlagBool(cmd, "no-headers"), allowMissingTemplateKeys, outputFormat, templateFile, GetFlagBool(cmd, "no-headers"), allowMissingTemplateKeys,
mapper, typer, decoders, mapper, typer, encoder, decoders, options,
) )
if err != nil { if err != nil {
return nil, generic, err return nil, err
} }
return maybeWrapSortingPrinter(cmd, printer), generic, nil return maybeWrapSortingPrinter(cmd, printer), nil
} }
// PrintResourceInfoForCommand receives a *cobra.Command and a *resource.Info and // PrintResourceInfoForCommand receives a *cobra.Command and a *resource.Info and
@ -149,11 +149,11 @@ func PrinterForCommand(cmd *cobra.Command, mapper meta.RESTMapper, typer runtime
// object passed is non-generic, it attempts to print the object using a HumanReadablePrinter. // object passed is non-generic, it attempts to print the object using a HumanReadablePrinter.
// Requires that printer flags have been added to cmd (see AddPrinterFlags). // Requires that printer flags have been added to cmd (see AddPrinterFlags).
func PrintResourceInfoForCommand(cmd *cobra.Command, info *resource.Info, f Factory, out io.Writer) error { func PrintResourceInfoForCommand(cmd *cobra.Command, info *resource.Info, f Factory, out io.Writer) error {
printer, generic, err := f.PrinterForCommand(cmd) printer, err := f.PrinterForCommand(cmd, printers.PrintOptions{})
if err != nil { if err != nil {
return err return err
} }
if !generic || printer == nil { if !printer.IsGeneric() {
printer, err = f.PrinterForMapping(cmd, nil, false) printer, err = f.PrinterForMapping(cmd, nil, false)
if err != nil { if err != nil {
return err return err

View File

@ -60,10 +60,14 @@ func (s *SortingPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
} }
// TODO: implement HandledResources() // TODO: implement HandledResources()
func (p *SortingPrinter) HandledResources() []string { func (s *SortingPrinter) HandledResources() []string {
return []string{} return []string{}
} }
func (s *SortingPrinter) IsGeneric() bool {
return s.Delegate.IsGeneric()
}
func (s *SortingPrinter) sortObj(obj runtime.Object) error { func (s *SortingPrinter) sortObj(obj runtime.Object) error {
objs, err := meta.ExtractList(obj) objs, err := meta.ExtractList(obj)
if err != nil { if err != nil {

View File

@ -239,3 +239,7 @@ func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jso
func (s *CustomColumnsPrinter) HandledResources() []string { func (s *CustomColumnsPrinter) HandledResources() []string {
return []string{} return []string{}
} }
func (s *CustomColumnsPrinter) IsGeneric() bool {
return true
}

View File

@ -152,6 +152,10 @@ func (h *HumanReadablePrinter) AfterPrint(output io.Writer, res string) error {
return nil return nil
} }
func (h *HumanReadablePrinter) IsGeneric() bool {
return false
}
func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error { func (h *HumanReadablePrinter) unknown(data []byte, w io.Writer) error {
_, err := fmt.Fprintf(w, "Unknown object: %s", string(data)) _, err := fmt.Fprintf(w, "Unknown object: %s", string(data))
return err return err

View File

@ -31,6 +31,8 @@ type ResourcePrinter interface {
//Can be used to print out warning/clarifications if needed //Can be used to print out warning/clarifications if needed
//after all objects were printed //after all objects were printed
AfterPrint(io.Writer, string) error AfterPrint(io.Writer, string) error
// Identify if it is a generic printer
IsGeneric() bool
} }
// ResourcePrinterFunc is a function that can print objects // ResourcePrinterFunc is a function that can print objects
@ -50,6 +52,10 @@ func (fn ResourcePrinterFunc) AfterPrint(io.Writer, string) error {
return nil return nil
} }
func (fn ResourcePrinterFunc) IsGeneric() bool {
return true
}
type PrintOptions struct { type PrintOptions struct {
NoHeaders bool NoHeaders bool
WithNamespace bool WithNamespace bool

View File

@ -81,12 +81,22 @@ func TestVersionedPrinter(t *testing.T) {
} }
func TestPrintDefault(t *testing.T) { func TestPrintDefault(t *testing.T) {
printer, found, err := printers.GetStandardPrinter("", "", false, false, nil, nil, nil) printerTests := []struct {
if err != nil { Name string
t.Fatalf("unexpected error: %#v", err) Format string
}{
{"test wide", "wide"},
{"test blank format", ""},
} }
if found {
t.Errorf("no printer should have been found: %#v / %v", printer, err) for _, test := range printerTests {
printer, err := printers.GetStandardPrinter(test.Format, "", false, false, nil, nil, api.Codecs.LegacyCodec(api.Registry.EnabledVersions()...), []runtime.Decoder{api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, printers.PrintOptions{})
if err != nil {
t.Errorf("in %s, unexpected error: %#v", test.Name, err)
}
if printer.IsGeneric() {
t.Errorf("in %s, printer should not be generic: %#v", test.Name, printer)
}
} }
} }
@ -136,11 +146,11 @@ func TestPrinter(t *testing.T) {
} }
for _, test := range printerTests { for _, test := range printerTests {
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
printer, generic, err := printers.GetStandardPrinter(test.Format, test.FormatArgument, false, true, api.Registry.RESTMapper(api.Registry.EnabledVersions()...), api.Scheme, []runtime.Decoder{api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}) printer, err := printers.GetStandardPrinter(test.Format, test.FormatArgument, false, true, api.Registry.RESTMapper(api.Registry.EnabledVersions()...), api.Scheme, api.Codecs.LegacyCodec(api.Registry.EnabledVersions()...), []runtime.Decoder{api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, printers.PrintOptions{})
if err != nil { if err != nil {
t.Errorf("in %s, unexpected error: %#v", test.Name, err) t.Errorf("in %s, unexpected error: %#v", test.Name, err)
} }
if generic && len(test.OutputVersions) > 0 { if printer.IsGeneric() && len(test.OutputVersions) > 0 {
printer = printers.NewVersionedPrinter(printer, api.Scheme, test.OutputVersions...) printer = printers.NewVersionedPrinter(printer, api.Scheme, test.OutputVersions...)
} }
if err := printer.PrintObj(test.Input, buf); err != nil { if err := printer.PrintObj(test.Input, buf); err != nil {
@ -164,9 +174,10 @@ func TestBadPrinter(t *testing.T) {
{"bad template", "template", "{{ .Name", fmt.Errorf("error parsing template {{ .Name, template: output:1: unclosed action\n")}, {"bad template", "template", "{{ .Name", fmt.Errorf("error parsing template {{ .Name, template: output:1: unclosed action\n")},
{"bad templatefile", "templatefile", "", fmt.Errorf("templatefile format specified but no template file given")}, {"bad templatefile", "templatefile", "", fmt.Errorf("templatefile format specified but no template file given")},
{"bad jsonpath", "jsonpath", "{.Name", fmt.Errorf("error parsing jsonpath {.Name, unclosed action\n")}, {"bad jsonpath", "jsonpath", "{.Name", fmt.Errorf("error parsing jsonpath {.Name, unclosed action\n")},
{"unknown format", "anUnknownFormat", "", fmt.Errorf("output format \"anUnknownFormat\" not recognized")},
} }
for _, test := range badPrinterTests { for _, test := range badPrinterTests {
_, _, err := printers.GetStandardPrinter(test.Format, test.FormatArgument, false, false, api.Registry.RESTMapper(api.Registry.EnabledVersions()...), api.Scheme, []runtime.Decoder{api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}) _, err := printers.GetStandardPrinter(test.Format, test.FormatArgument, false, false, api.Registry.RESTMapper(api.Registry.EnabledVersions()...), api.Scheme, api.Codecs.LegacyCodec(api.Registry.EnabledVersions()...), []runtime.Decoder{api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, printers.PrintOptions{})
if err == nil || err.Error() != test.Error.Error() { if err == nil || err.Error() != test.Error.Error() {
t.Errorf("in %s, expect %s, got %s", test.Name, test.Error, err) t.Errorf("in %s, expect %s, got %s", test.Name, test.Error, err)
} }
@ -359,7 +370,7 @@ func TestNamePrinter(t *testing.T) {
}, },
"pods/foo\npods/bar\n"}, "pods/foo\npods/bar\n"},
} }
printer, _, _ := printers.GetStandardPrinter("name", "", false, false, api.Registry.RESTMapper(api.Registry.EnabledVersions()...), api.Scheme, []runtime.Decoder{api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}) printer, _ := printers.GetStandardPrinter("name", "", false, false, api.Registry.RESTMapper(api.Registry.EnabledVersions()...), api.Scheme, api.Codecs.LegacyCodec(api.Registry.EnabledVersions()...), []runtime.Decoder{api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, printers.PrintOptions{})
for name, item := range tests { for name, item := range tests {
buff := &bytes.Buffer{} buff := &bytes.Buffer{}
err := printer.PrintObj(item.obj, buff) err := printer.PrintObj(item.obj, buff)
@ -2235,7 +2246,7 @@ func TestAllowMissingKeys(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
printer, _, err := printers.GetStandardPrinter(test.Format, test.Template, false, test.AllowMissingTemplateKeys, api.Registry.RESTMapper(api.Registry.EnabledVersions()...), api.Scheme, []runtime.Decoder{api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}) printer, err := printers.GetStandardPrinter(test.Format, test.Template, false, test.AllowMissingTemplateKeys, api.Registry.RESTMapper(api.Registry.EnabledVersions()...), api.Scheme, api.Codecs.LegacyCodec(api.Registry.EnabledVersions()...), []runtime.Decoder{api.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme}, printers.PrintOptions{})
if err != nil { if err != nil {
t.Errorf("in %s, unexpected error: %#v", test.Name, err) t.Errorf("in %s, unexpected error: %#v", test.Name, err)
} }

View File

@ -63,6 +63,10 @@ func (p *JSONPrinter) HandledResources() []string {
return []string{} return []string{}
} }
func (p *JSONPrinter) IsGeneric() bool {
return true
}
// YAMLPrinter is an implementation of ResourcePrinter which outputs an object as YAML. // YAMLPrinter is an implementation of ResourcePrinter which outputs an object as YAML.
// The input object is assumed to be in the internal version of an API and is converted // The input object is assumed to be in the internal version of an API and is converted
// to the given version first. // to the given version first.
@ -99,3 +103,7 @@ func (p *YAMLPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
func (p *YAMLPrinter) HandledResources() []string { func (p *YAMLPrinter) HandledResources() []string {
return []string{} return []string{}
} }
func (p *YAMLPrinter) IsGeneric() bool {
return true
}

View File

@ -155,3 +155,7 @@ func (j *JSONPathPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
func (p *JSONPathPrinter) HandledResources() []string { func (p *JSONPathPrinter) HandledResources() []string {
return []string{} return []string{}
} }
func (p *JSONPathPrinter) IsGeneric() bool {
return true
}

View File

@ -87,3 +87,7 @@ func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
func (p *NamePrinter) HandledResources() []string { func (p *NamePrinter) HandledResources() []string {
return []string{} return []string{}
} }
func (p *NamePrinter) IsGeneric() bool {
return true
}

View File

@ -25,92 +25,102 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
// GetStandardPrinter takes a format type, an optional format argument. It will return true // GetStandardPrinter takes a format type, an optional format argument. It will return
// if the format is generic (untyped), otherwise it will return false. The printer // a printer or an error. The printer is agnostic to schema versions, so you must
// is agnostic to schema versions, so you must send arguments to PrintObj in the // send arguments to PrintObj in the version you wish them to be shown using a
// version you wish them to be shown using a VersionedPrinter (typically when // VersionedPrinter (typically when generic is true).
// generic is true). func GetStandardPrinter(format, formatArgument string, noHeaders, allowMissingTemplateKeys bool, mapper meta.RESTMapper, typer runtime.ObjectTyper, encoder runtime.Encoder, decoders []runtime.Decoder, options PrintOptions) (ResourcePrinter, error) {
func GetStandardPrinter(format, formatArgument string, noHeaders, allowMissingTemplateKeys bool, mapper meta.RESTMapper, typer runtime.ObjectTyper, decoders []runtime.Decoder) (ResourcePrinter, bool, error) {
var printer ResourcePrinter var printer ResourcePrinter
switch format { switch format {
case "json": case "json":
printer = &JSONPrinter{} printer = &JSONPrinter{}
case "yaml": case "yaml":
printer = &YAMLPrinter{} printer = &YAMLPrinter{}
case "name": case "name":
printer = &NamePrinter{ printer = &NamePrinter{
Typer: typer, Typer: typer,
Decoders: decoders, Decoders: decoders,
Mapper: mapper, Mapper: mapper,
} }
case "template", "go-template": case "template", "go-template":
if len(formatArgument) == 0 { if len(formatArgument) == 0 {
return nil, false, fmt.Errorf("template format specified but no template given") return nil, fmt.Errorf("template format specified but no template given")
} }
templatePrinter, err := NewTemplatePrinter([]byte(formatArgument)) templatePrinter, err := NewTemplatePrinter([]byte(formatArgument))
if err != nil { if err != nil {
return nil, false, fmt.Errorf("error parsing template %s, %v\n", formatArgument, err) return nil, fmt.Errorf("error parsing template %s, %v\n", formatArgument, err)
} }
templatePrinter.AllowMissingKeys(allowMissingTemplateKeys) templatePrinter.AllowMissingKeys(allowMissingTemplateKeys)
printer = templatePrinter printer = templatePrinter
case "templatefile", "go-template-file": case "templatefile", "go-template-file":
if len(formatArgument) == 0 { if len(formatArgument) == 0 {
return nil, false, fmt.Errorf("templatefile format specified but no template file given") return nil, 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, false, fmt.Errorf("error reading template %s, %v\n", formatArgument, err) return nil, fmt.Errorf("error reading template %s, %v\n", formatArgument, err)
} }
templatePrinter, err := NewTemplatePrinter(data) templatePrinter, err := NewTemplatePrinter(data)
if err != nil { if err != nil {
return nil, false, fmt.Errorf("error parsing template %s, %v\n", string(data), err) return nil, fmt.Errorf("error parsing template %s, %v\n", string(data), err)
} }
templatePrinter.AllowMissingKeys(allowMissingTemplateKeys) templatePrinter.AllowMissingKeys(allowMissingTemplateKeys)
printer = templatePrinter printer = templatePrinter
case "jsonpath": case "jsonpath":
if len(formatArgument) == 0 { if len(formatArgument) == 0 {
return nil, false, fmt.Errorf("jsonpath template format specified but no template given") return nil, fmt.Errorf("jsonpath template format specified but no template given")
} }
jsonpathPrinter, err := NewJSONPathPrinter(formatArgument) jsonpathPrinter, err := NewJSONPathPrinter(formatArgument)
if err != nil { if err != nil {
return nil, false, fmt.Errorf("error parsing jsonpath %s, %v\n", formatArgument, err) return nil, fmt.Errorf("error parsing jsonpath %s, %v\n", formatArgument, err)
} }
jsonpathPrinter.AllowMissingKeys(allowMissingTemplateKeys) jsonpathPrinter.AllowMissingKeys(allowMissingTemplateKeys)
printer = jsonpathPrinter printer = jsonpathPrinter
case "jsonpath-file": case "jsonpath-file":
if len(formatArgument) == 0 { if len(formatArgument) == 0 {
return nil, false, fmt.Errorf("jsonpath file format specified but no template file file given") return nil, fmt.Errorf("jsonpath file format specified but no template file file given")
} }
data, err := ioutil.ReadFile(formatArgument) data, err := ioutil.ReadFile(formatArgument)
if err != nil { if err != nil {
return nil, false, fmt.Errorf("error reading template %s, %v\n", formatArgument, err) return nil, fmt.Errorf("error reading template %s, %v\n", formatArgument, err)
} }
jsonpathPrinter, err := NewJSONPathPrinter(string(data)) jsonpathPrinter, err := NewJSONPathPrinter(string(data))
if err != nil { if err != nil {
return nil, false, fmt.Errorf("error parsing template %s, %v\n", string(data), err) return nil, fmt.Errorf("error parsing template %s, %v\n", string(data), err)
} }
jsonpathPrinter.AllowMissingKeys(allowMissingTemplateKeys) jsonpathPrinter.AllowMissingKeys(allowMissingTemplateKeys)
printer = jsonpathPrinter printer = jsonpathPrinter
case "custom-columns": case "custom-columns":
var err error var err error
if printer, err = NewCustomColumnsPrinterFromSpec(formatArgument, decoders[0], noHeaders); err != nil { if printer, err = NewCustomColumnsPrinterFromSpec(formatArgument, decoders[0], noHeaders); err != nil {
return nil, false, err return nil, err
} }
case "custom-columns-file": case "custom-columns-file":
file, err := os.Open(formatArgument) file, err := os.Open(formatArgument)
if err != nil { if err != nil {
return nil, false, fmt.Errorf("error reading template %s, %v\n", formatArgument, err) return nil, fmt.Errorf("error reading template %s, %v\n", formatArgument, err)
} }
defer file.Close() defer file.Close()
if printer, err = NewCustomColumnsPrinterFromTemplate(file, decoders[0]); err != nil { if printer, err = NewCustomColumnsPrinterFromTemplate(file, decoders[0]); err != nil {
return nil, false, err return nil, err
} }
case "wide": case "wide":
fallthrough fallthrough
case "": case "":
return nil, false, nil
printer = NewHumanReadablePrinter(encoder, decoders[0], options)
default: default:
return nil, false, fmt.Errorf("output format %q not recognized", format) return nil, fmt.Errorf("output format %q not recognized", format)
} }
return printer, true, nil return printer, nil
} }

View File

@ -88,6 +88,10 @@ func (p *TemplatePrinter) HandledResources() []string {
return []string{} return []string{}
} }
func (p *TemplatePrinter) IsGeneric() bool {
return true
}
// safeExecute tries to execute the template, but catches panics and returns an error // safeExecute tries to execute the template, but catches panics and returns an error
// should the template engine panic. // should the template engine panic.
func (p *TemplatePrinter) safeExecute(w io.Writer, obj interface{}) error { func (p *TemplatePrinter) safeExecute(w io.Writer, obj interface{}) error {

View File

@ -61,3 +61,7 @@ func (p *VersionedPrinter) PrintObj(obj runtime.Object, w io.Writer) error {
func (p *VersionedPrinter) HandledResources() []string { func (p *VersionedPrinter) HandledResources() []string {
return []string{} return []string{}
} }
func (p *VersionedPrinter) IsGeneric() bool {
return p.printer.IsGeneric()
}