From aa38cea4d145321e405707056f300664e3d3992b Mon Sep 17 00:00:00 2001 From: juanvallejo Date: Wed, 21 Mar 2018 16:46:34 -0400 Subject: [PATCH] wire through name/success print flags --- pkg/printers/BUILD | 3 + pkg/printers/name.go | 25 +++++- pkg/printers/name_flags.go | 80 ++++++++++++++++++++ pkg/printers/name_flags_test.go | 130 ++++++++++++++++++++++++++++++++ pkg/printers/printers.go | 12 ++- 5 files changed, 243 insertions(+), 7 deletions(-) create mode 100644 pkg/printers/name_flags.go create mode 100644 pkg/printers/name_flags_test.go diff --git a/pkg/printers/BUILD b/pkg/printers/BUILD index 89fb01796a4..a2dca85be43 100644 --- a/pkg/printers/BUILD +++ b/pkg/printers/BUILD @@ -16,6 +16,7 @@ go_library( "json_yaml_flags.go", "jsonpath.go", "name.go", + "name_flags.go", "printers.go", "tabwriter.go", "template.go", @@ -23,6 +24,7 @@ go_library( ], importpath = "k8s.io/kubernetes/pkg/printers", deps = [ + "//pkg/kubectl/scheme:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/github.com/golang/glog:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", @@ -44,6 +46,7 @@ go_test( srcs = [ "customcolumn_test.go", "json_yaml_flags_test.go", + "name_flags_test.go", ], deps = [ ":go_default_library", diff --git a/pkg/printers/name.go b/pkg/printers/name.go index 4d8903e4ad6..9e0d84ae91d 100644 --- a/pkg/printers/name.go +++ b/pkg/printers/name.go @@ -30,6 +30,15 @@ import ( // NamePrinter is an implementation of ResourcePrinter which outputs "resource/name" pair of an object. type NamePrinter struct { + // DryRun indicates whether the "(dry run)" message + // should be appended to the finalized "successful" + // message printed about an action on an object. + DryRun bool + // Operation describes the name of the action that + // took place on an object, to be included in the + // finalized "successful" message. + Operation string + Decoders []runtime.Decoder Typer runtime.ObjectTyper } @@ -64,7 +73,7 @@ func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error { } } - return printObj(w, name, GetObjectGroupKind(obj, p.Typer)) + return printObj(w, name, p.Operation, p.DryRun, GetObjectGroupKind(obj, p.Typer)) } func GetObjectGroupKind(obj runtime.Object, typer runtime.ObjectTyper) schema.GroupKind { @@ -94,17 +103,25 @@ func GetObjectGroupKind(obj runtime.Object, typer runtime.ObjectTyper) schema.Gr return schema.GroupKind{Kind: ""} } -func printObj(w io.Writer, name string, groupKind schema.GroupKind) error { +func printObj(w io.Writer, name string, operation string, dryRun bool, groupKind schema.GroupKind) error { if len(groupKind.Kind) == 0 { return fmt.Errorf("missing kind for resource with name %v", name) } + dryRunMsg := "" + if dryRun { + dryRunMsg = " (dry run)" + } + if len(operation) > 0 { + operation = " " + operation + } + if len(groupKind.Group) == 0 { - fmt.Fprintf(w, "%s/%s\n", strings.ToLower(groupKind.Kind), name) + fmt.Fprintf(w, "%s/%s%s%s\n", strings.ToLower(groupKind.Kind), name, operation, dryRunMsg) return nil } - fmt.Fprintf(w, "%s.%s/%s\n", strings.ToLower(groupKind.Kind), groupKind.Group, name) + fmt.Fprintf(w, "%s.%s/%s%s%s\n", strings.ToLower(groupKind.Kind), groupKind.Group, name, operation, dryRunMsg) return nil } diff --git a/pkg/printers/name_flags.go b/pkg/printers/name_flags.go new file mode 100644 index 00000000000..ebe8f7e4d34 --- /dev/null +++ b/pkg/printers/name_flags.go @@ -0,0 +1,80 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package printers + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + + "k8s.io/kubernetes/pkg/kubectl/scheme" +) + +// NamePrintFlags provides default flags necessary for printing +// a resource's fully-qualified Kind.group/name, or a successful +// message about that resource if an Operation is provided. +type NamePrintFlags struct { + // DryRun indicates whether the "(dry run)" message + // should be appended to the finalized "successful" + // message printed about an action on an object. + DryRun bool + // Operation describes the name of the action that + // took place on an object, to be included in the + // finalized "successful" message. + Operation string +} + +// ToPrinter receives an outputFormat and returns a printer capable of +// handling --output=name printing. +// Returns false if the specified outputFormat does not match a supported format. +// Supported format types can be found in pkg/printers/printers.go +func (f *NamePrintFlags) ToPrinter(outputFormat string) (ResourcePrinter, bool, error) { + decoders := []runtime.Decoder{scheme.Codecs.UniversalDecoder(), unstructured.UnstructuredJSONScheme} + namePrinter := &NamePrinter{ + Operation: f.Operation, + DryRun: f.DryRun, + Typer: scheme.Scheme, + Decoders: decoders, + } + + outputFormat = strings.ToLower(outputFormat) + switch outputFormat { + case "name": + return namePrinter, true, nil + case "": + return nil, false, fmt.Errorf("missing output format") + default: + return nil, false, nil + } +} + +// AddFlags receives a *cobra.Command reference and binds +// flags related to name printing to it +func (f *NamePrintFlags) AddFlags(c *cobra.Command) {} + +// NewNamePrintFlags returns flags associated with +// --name printing, with default values set. +func NewNamePrintFlags(operation string, dryRun bool) *NamePrintFlags { + return &NamePrintFlags{ + Operation: operation, + DryRun: dryRun, + } +} diff --git a/pkg/printers/name_flags_test.go b/pkg/printers/name_flags_test.go new file mode 100644 index 00000000000..7e5dca13b0b --- /dev/null +++ b/pkg/printers/name_flags_test.go @@ -0,0 +1,130 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package printers_test + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "strings" + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/printers" +) + +func TestNamePrinterSupportsExpectedFormats(t *testing.T) { + testObject := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}} + + customColumnsFile, err := ioutil.TempFile("", "printers_jsonpath_flags") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + defer func(tempFile *os.File) { + tempFile.Close() + os.Remove(tempFile.Name()) + }(customColumnsFile) + + fmt.Fprintf(customColumnsFile, "NAME\n.metadata.name") + + testCases := []struct { + name string + outputFormat string + operation string + dryRun bool + expectedError string + expectedOutput string + expectNoMatch bool + }{ + { + name: "valid \"name\" output format with no operation prints resource name", + outputFormat: "name", + expectedOutput: "pod/foo", + }, + { + name: "valid \"name\" output format and an operation prints success message", + outputFormat: "name", + operation: "patched", + expectedOutput: "pod/foo patched", + }, + { + name: "valid \"name\" output format and an operation prints success message with dry run", + outputFormat: "name", + operation: "patched", + dryRun: true, + expectedOutput: "pod/foo patched (dry run)", + }, + { + name: "operation and no valid \"name\" output does not match a printer", + operation: "patched", + dryRun: true, + expectNoMatch: true, + }, + { + name: "no printer is matched on an invalid outputFormat", + outputFormat: "invalid", + expectNoMatch: true, + }, + { + name: "printer should not match on any other format supported by another printer", + outputFormat: "go-template", + expectNoMatch: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + printFlags := printers.NamePrintFlags{ + Operation: tc.operation, + DryRun: tc.dryRun, + } + + p, matched, err := printFlags.ToPrinter(tc.outputFormat) + if tc.expectNoMatch { + if matched { + t.Fatalf("expected no printer matches for output format %q", tc.outputFormat) + } + return + } + if !matched { + t.Fatalf("expected to match template printer for output format %q", tc.outputFormat) + } + + if len(tc.expectedError) > 0 { + if err == nil || !strings.Contains(err.Error(), tc.expectedError) { + t.Errorf("expecting error %q, got %v", tc.expectedError, err) + } + return + } + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + out := bytes.NewBuffer([]byte{}) + err = p.PrintObj(testObject, out) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if !strings.Contains(out.String(), tc.expectedOutput) { + t.Errorf("unexpected output: expecting %q, got %q", tc.expectedOutput, out.String()) + } + }) + } +} diff --git a/pkg/printers/printers.go b/pkg/printers/printers.go index d0df738f9d8..95e6d577947 100644 --- a/pkg/printers/printers.go +++ b/pkg/printers/printers.go @@ -47,10 +47,16 @@ func GetStandardPrinter(typer runtime.ObjectTyper, encoder runtime.Encoder, deco printer = p case "name": - printer = &NamePrinter{ - Typer: typer, - Decoders: decoders, + nameFlags := NewNamePrintFlags("", false) + namePrinter, matched, err := nameFlags.ToPrinter(format) + if !matched { + return nil, fmt.Errorf("unable to match a name printer to handle current print options") } + if err != nil { + return nil, err + } + + printer = namePrinter case "template", "go-template": if len(formatArgument) == 0 {