diff --git a/contrib/completions/bash/kubectl b/contrib/completions/bash/kubectl index 6cc57a27fbde1..0100d09c7a477 100644 --- a/contrib/completions/bash/kubectl +++ b/contrib/completions/bash/kubectl @@ -314,6 +314,8 @@ _kubectl_create() flags_completion+=("__handle_filename_extension_flag json|yaml|yml") flags+=("--help") flags+=("-h") + flags+=("--output=") + two_word_flags+=("-o") must_have_one_flag=() must_have_one_flag+=("--filename=") @@ -342,6 +344,8 @@ _kubectl_replace() flags+=("--grace-period=") flags+=("--help") flags+=("-h") + flags+=("--output=") + two_word_flags+=("-o") flags+=("--timeout=") must_have_one_flag=() @@ -362,6 +366,8 @@ _kubectl_patch() flags+=("--help") flags+=("-h") + flags+=("--output=") + two_word_flags+=("-o") flags+=("--patch=") two_word_flags+=("-p") @@ -393,6 +399,8 @@ _kubectl_delete() flags+=("--help") flags+=("-h") flags+=("--ignore-not-found") + flags+=("--output=") + two_word_flags+=("-o") flags+=("--selector=") two_word_flags+=("-l") flags+=("--timeout=") @@ -487,6 +495,8 @@ _kubectl_scale() flags+=("--current-replicas=") flags+=("--help") flags+=("-h") + flags+=("--output=") + two_word_flags+=("-o") flags+=("--replicas=") flags+=("--resource-version=") @@ -625,6 +635,8 @@ _kubectl_stop() flags+=("--help") flags+=("-h") flags+=("--ignore-not-found") + flags+=("--output=") + two_word_flags+=("-o") flags+=("--selector=") two_word_flags+=("-l") flags+=("--timeout=") diff --git a/docs/man/man1/kubectl-create.1 b/docs/man/man1/kubectl-create.1 index 1229c0ca87623..f1eeae6551b3b 100644 --- a/docs/man/man1/kubectl-create.1 +++ b/docs/man/man1/kubectl-create.1 @@ -28,6 +28,10 @@ JSON and YAML formats are accepted. \fB\-h\fP, \fB\-\-help\fP=false help for create +.PP +\fB\-o\fP, \fB\-\-output\fP="" + Output mode. Use "\-o name" for shorter output (resource/name). + .SH OPTIONS INHERITED FROM PARENT COMMANDS .PP diff --git a/docs/man/man1/kubectl-delete.1 b/docs/man/man1/kubectl-delete.1 index b44526ddc4415..83ef548e68b47 100644 --- a/docs/man/man1/kubectl-delete.1 +++ b/docs/man/man1/kubectl-delete.1 @@ -53,6 +53,10 @@ will be lost along with the rest of the resource. \fB\-\-ignore\-not\-found\fP=false Treat "resource not found" as a successful delete. Defaults to "true" when \-\-all is specified. +.PP +\fB\-o\fP, \fB\-\-output\fP="" + Output mode. Use "\-o name" for shorter output (resource/name). + .PP \fB\-l\fP, \fB\-\-selector\fP="" Selector (label query) to filter on. diff --git a/docs/man/man1/kubectl-patch.1 b/docs/man/man1/kubectl-patch.1 index 5966b340dc371..7d79280e97ec5 100644 --- a/docs/man/man1/kubectl-patch.1 +++ b/docs/man/man1/kubectl-patch.1 @@ -24,6 +24,10 @@ JSON and YAML formats are accepted. \fB\-h\fP, \fB\-\-help\fP=false help for patch +.PP +\fB\-o\fP, \fB\-\-output\fP="" + Output mode. Use "\-o name" for shorter output (resource/name). + .PP \fB\-p\fP, \fB\-\-patch\fP="" The patch to be applied to the resource JSON file. diff --git a/docs/man/man1/kubectl-replace.1 b/docs/man/man1/kubectl-replace.1 index 0526ab833f34e..9825fd6efd6f7 100644 --- a/docs/man/man1/kubectl-replace.1 +++ b/docs/man/man1/kubectl-replace.1 @@ -40,6 +40,10 @@ JSON and YAML formats are accepted. \fB\-h\fP, \fB\-\-help\fP=false help for replace +.PP +\fB\-o\fP, \fB\-\-output\fP="" + Output mode. Use "\-o name" for shorter output (resource/name). + .PP \fB\-\-timeout\fP=0 Only relevant during a force replace. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object diff --git a/docs/man/man1/kubectl-scale.1 b/docs/man/man1/kubectl-scale.1 index aa3cef1b1a058..03f3d12be89b2 100644 --- a/docs/man/man1/kubectl-scale.1 +++ b/docs/man/man1/kubectl-scale.1 @@ -31,6 +31,10 @@ scale is sent to the server. \fB\-h\fP, \fB\-\-help\fP=false help for scale +.PP +\fB\-o\fP, \fB\-\-output\fP="" + Output mode. Use "\-o name" for shorter output (resource/name). + .PP \fB\-\-replicas\fP=\-1 The new desired number of replicas. Required. diff --git a/docs/man/man1/kubectl-stop.1 b/docs/man/man1/kubectl-stop.1 index dfdf732b3344d..5f2a731708126 100644 --- a/docs/man/man1/kubectl-stop.1 +++ b/docs/man/man1/kubectl-stop.1 @@ -41,6 +41,10 @@ If the resource is scalable it will be scaled to 0 before deletion. \fB\-\-ignore\-not\-found\fP=false Treat "resource not found" as a successful stop. +.PP +\fB\-o\fP, \fB\-\-output\fP="" + Output mode. Use "\-o name" for shorter output (resource/name). + .PP \fB\-l\fP, \fB\-\-selector\fP="" Selector (label query) to filter on. diff --git a/docs/user-guide/kubectl/kubectl_create.md b/docs/user-guide/kubectl/kubectl_create.md index d62973c3fdc1d..ecff815d9bc61 100644 --- a/docs/user-guide/kubectl/kubectl_create.md +++ b/docs/user-guide/kubectl/kubectl_create.md @@ -61,6 +61,7 @@ $ cat pod.json | kubectl create -f - ``` -f, --filename=[]: Filename, directory, or URL to file to use to create the resource -h, --help=false: help for create + -o, --output="": Output mode. Use "-o name" for shorter output (resource/name). ``` ### Options inherited from parent commands diff --git a/docs/user-guide/kubectl/kubectl_delete.md b/docs/user-guide/kubectl/kubectl_delete.md index 5f245718f645e..36ad93c781161 100644 --- a/docs/user-guide/kubectl/kubectl_delete.md +++ b/docs/user-guide/kubectl/kubectl_delete.md @@ -81,6 +81,7 @@ $ kubectl delete pods --all --grace-period=-1: Period of time in seconds given to the resource to terminate gracefully. Ignored if negative. -h, --help=false: help for delete --ignore-not-found=false: Treat "resource not found" as a successful delete. Defaults to "true" when --all is specified. + -o, --output="": Output mode. Use "-o name" for shorter output (resource/name). -l, --selector="": Selector (label query) to filter on. --timeout=0: The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object ``` diff --git a/docs/user-guide/kubectl/kubectl_patch.md b/docs/user-guide/kubectl/kubectl_patch.md index 41e09c8eabb0e..36f6bd48cb250 100644 --- a/docs/user-guide/kubectl/kubectl_patch.md +++ b/docs/user-guide/kubectl/kubectl_patch.md @@ -58,6 +58,7 @@ kubectl patch node k8s-node-1 -p '{"spec":{"unschedulable":true}}' ``` -h, --help=false: help for patch + -o, --output="": Output mode. Use "-o name" for shorter output (resource/name). -p, --patch="": The patch to be applied to the resource JSON file. ``` diff --git a/docs/user-guide/kubectl/kubectl_replace.md b/docs/user-guide/kubectl/kubectl_replace.md index d6d1b90874469..296aff64cc509 100644 --- a/docs/user-guide/kubectl/kubectl_replace.md +++ b/docs/user-guide/kubectl/kubectl_replace.md @@ -67,6 +67,7 @@ kubectl replace --force -f ./pod.json --force=false: Delete and re-create the specified resource --grace-period=-1: Only relevant during a force replace. Period of time in seconds given to the old resource to terminate gracefully. Ignored if negative. -h, --help=false: help for replace + -o, --output="": Output mode. Use "-o name" for shorter output (resource/name). --timeout=0: Only relevant during a force replace. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object ``` diff --git a/docs/user-guide/kubectl/kubectl_scale.md b/docs/user-guide/kubectl/kubectl_scale.md index 863e444ef3279..3e4cc3443447f 100644 --- a/docs/user-guide/kubectl/kubectl_scale.md +++ b/docs/user-guide/kubectl/kubectl_scale.md @@ -64,6 +64,7 @@ $ kubectl scale --current-replicas=2 --replicas=3 replicationcontrollers foo ``` --current-replicas=-1: Precondition for current size. Requires that the current size of the replication controller match this value in order to scale. -h, --help=false: help for scale + -o, --output="": Output mode. Use "-o name" for shorter output (resource/name). --replicas=-1: The new desired number of replicas. Required. --resource-version="": Precondition for resource version. Requires that the current resource version match this value in order to scale. ``` diff --git a/docs/user-guide/kubectl/kubectl_stop.md b/docs/user-guide/kubectl/kubectl_stop.md index ed65ad3fe4dd4..c009fac58fa6f 100644 --- a/docs/user-guide/kubectl/kubectl_stop.md +++ b/docs/user-guide/kubectl/kubectl_stop.md @@ -71,6 +71,7 @@ $ kubectl stop -f path/to/resources --grace-period=-1: Period of time in seconds given to the resource to terminate gracefully. Ignored if negative. -h, --help=false: help for stop --ignore-not-found=false: Treat "resource not found" as a successful stop. + -o, --output="": Output mode. Use "-o name" for shorter output (resource/name). -l, --selector="": Selector (label query) to filter on. --timeout=0: The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object ``` diff --git a/pkg/api/meta/interfaces.go b/pkg/api/meta/interfaces.go index b5728bd7856b2..d64fdaccb9821 100644 --- a/pkg/api/meta/interfaces.go +++ b/pkg/api/meta/interfaces.go @@ -146,4 +146,5 @@ type RESTMapper interface { VersionAndKindForResource(resource string) (defaultVersion, kind string, err error) RESTMapping(kind string, versions ...string) (*RESTMapping, error) AliasesForResource(resource string) ([]string, bool) + ResourceSingularizer(resource string) (singular string, err error) } diff --git a/pkg/api/meta/restmapper.go b/pkg/api/meta/restmapper.go index 6f41bb016b8c4..3970c365b6716 100644 --- a/pkg/api/meta/restmapper.go +++ b/pkg/api/meta/restmapper.go @@ -77,6 +77,8 @@ type DefaultRESTMapper struct { reverse map[typeMeta]string scopes map[typeMeta]RESTScope versions []string + plurals map[string]string + singulars map[string]string interfacesFunc VersionInterfacesFunc } @@ -93,6 +95,8 @@ func NewDefaultRESTMapper(versions []string, f VersionInterfacesFunc) *DefaultRE mapping := make(map[string]typeMeta) reverse := make(map[typeMeta]string) scopes := make(map[typeMeta]RESTScope) + plurals := make(map[string]string) + singulars := make(map[string]string) // TODO: verify name mappings work correctly when versions differ return &DefaultRESTMapper{ @@ -100,12 +104,16 @@ func NewDefaultRESTMapper(versions []string, f VersionInterfacesFunc) *DefaultRE reverse: reverse, scopes: scopes, versions: versions, + plurals: plurals, + singulars: singulars, interfacesFunc: f, } } func (m *DefaultRESTMapper) Add(scope RESTScope, kind string, version string, mixedCase bool) { plural, singular := kindToResource(kind, mixedCase) + m.plurals[singular] = plural + m.singulars[plural] = singular meta := typeMeta{APIVersion: version, Kind: kind} _, ok1 := m.mapping[plural] _, ok2 := m.mapping[strings.ToLower(plural)] @@ -133,7 +141,7 @@ func kindToResource(kind string, mixedCase bool) (plural, singular string) { singular = strings.ToLower(kind) } if strings.HasSuffix(singular, "status") { - plural = strings.TrimSuffix(singular, "status") + "statuses" + plural = singular + "es" } else { switch string(singular[len(singular)-1]) { case "s": @@ -147,6 +155,16 @@ func kindToResource(kind string, mixedCase bool) (plural, singular string) { return } +// ResourceSingularizer implements RESTMapper +// It converts a resource name from plural to singular (e.g., from pods to pod) +func (m *DefaultRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { + singular, ok := m.singulars[resource] + if !ok { + return resource, fmt.Errorf("no singular of resource %q has been defined", resource) + } + return singular, nil +} + // VersionAndKindForResource implements RESTMapper func (m *DefaultRESTMapper) VersionAndKindForResource(resource string) (defaultVersion, kind string, err error) { meta, ok := m.mapping[strings.ToLower(resource)] @@ -249,6 +267,18 @@ func (m *DefaultRESTMapper) AliasesForResource(alias string) ([]string, bool) { // MultiRESTMapper is a wrapper for multiple RESTMappers. type MultiRESTMapper []RESTMapper +// ResourceSingularizer converts a REST resource name from plural to singular (e.g., from pods to pod) +// This implementation supports multiple REST schemas and return the first match. +func (m MultiRESTMapper) ResourceSingularizer(resource string) (singular string, err error) { + for _, t := range m { + singular, err = t.ResourceSingularizer(resource) + if err == nil { + return + } + } + return +} + // VersionAndKindForResource provides the Version and Kind mappings for the // REST resources. This implementation supports multiple REST schemas and return // the first match. diff --git a/pkg/api/meta/restmapper_test.go b/pkg/api/meta/restmapper_test.go index 3b4611179be6c..954f69c75917c 100644 --- a/pkg/api/meta/restmapper_test.go +++ b/pkg/api/meta/restmapper_test.go @@ -124,6 +124,40 @@ func TestKindToResource(t *testing.T) { } } +func TestRESTMapperResourceSingularizer(t *testing.T) { + testCases := []struct { + Kind, APIVersion string + MixedCase bool + Plural string + Singular string + }{ + {Kind: "Pod", APIVersion: "test", MixedCase: true, Plural: "pods", Singular: "pod"}, + {Kind: "Pod", APIVersion: "test", MixedCase: false, Plural: "pods", Singular: "pod"}, + + {Kind: "ReplicationController", APIVersion: "test", MixedCase: true, Plural: "replicationControllers", Singular: "replicationController"}, + {Kind: "ReplicationController", APIVersion: "test", MixedCase: false, Plural: "replicationcontrollers", Singular: "replicationcontroller"}, + + {Kind: "ImageRepository", APIVersion: "test", MixedCase: true, Plural: "imageRepositories", Singular: "imageRepository"}, + {Kind: "ImageRepository", APIVersion: "test", MixedCase: false, Plural: "imagerepositories", Singular: "imagerepository"}, + + {Kind: "Status", APIVersion: "test", MixedCase: true, Plural: "statuses", Singular: "status"}, + {Kind: "Status", APIVersion: "test", MixedCase: false, Plural: "statuses", Singular: "status"}, + + {Kind: "lowercase", APIVersion: "test", MixedCase: false, Plural: "lowercases", Singular: "lowercase"}, + // Don't add extra s if the original object is already plural + {Kind: "lowercases", APIVersion: "test", MixedCase: false, Plural: "lowercases", Singular: "lowercases"}, + } + for i, testCase := range testCases { + mapper := NewDefaultRESTMapper([]string{"test"}, fakeInterfaces) + // create singular/plural mapping + mapper.Add(RESTScopeNamespace, testCase.Kind, testCase.APIVersion, testCase.MixedCase) + singular, _ := mapper.ResourceSingularizer(testCase.Plural) + if singular != testCase.Singular { + t.Errorf("%d: mismatched singular: %s, should be %s", i, singular, testCase.Singular) + } + } +} + func TestRESTMapperRESTMapping(t *testing.T) { testCases := []struct { Kind string diff --git a/pkg/kubectl/cmd/create.go b/pkg/kubectl/cmd/create.go index e26d6a84f7d52..ab19e70012bea 100644 --- a/pkg/kubectl/cmd/create.go +++ b/pkg/kubectl/cmd/create.go @@ -51,7 +51,9 @@ func NewCmdCreate(f *cmdutil.Factory, out io.Writer) *cobra.Command { Example: create_example, Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(ValidateArgs(cmd, args)) - cmdutil.CheckErr(RunCreate(f, out, filenames)) + cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) + shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" + cmdutil.CheckErr(RunCreate(f, out, filenames, shortOutput)) }, } @@ -59,6 +61,7 @@ func NewCmdCreate(f *cmdutil.Factory, out io.Writer) *cobra.Command { kubectl.AddJsonFilenameFlag(cmd, &filenames, usage) cmd.MarkFlagRequired("filename") + cmdutil.AddOutputFlagsForMutation(cmd) return cmd } @@ -69,7 +72,7 @@ func ValidateArgs(cmd *cobra.Command, args []string) error { return nil } -func RunCreate(f *cmdutil.Factory, out io.Writer, filenames util.StringList) error { +func RunCreate(f *cmdutil.Factory, out io.Writer, filenames util.StringList, shortOutput bool) error { schema, err := f.Validator() if err != nil { return err @@ -105,8 +108,10 @@ func RunCreate(f *cmdutil.Factory, out io.Writer, filenames util.StringList) err } count++ info.Refresh(obj, true) - printObjectSpecificMessage(info.Object, out) - fmt.Fprintf(out, "%s/%s\n", info.Mapping.Resource, info.Name) + if !shortOutput { + printObjectSpecificMessage(info.Object, out) + } + cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "created") return nil }) if err != nil { diff --git a/pkg/kubectl/cmd/create_test.go b/pkg/kubectl/cmd/create_test.go index 29cfc416a2d4e..4532835275084 100644 --- a/pkg/kubectl/cmd/create_test.go +++ b/pkg/kubectl/cmd/create_test.go @@ -59,10 +59,11 @@ func TestCreateObject(t *testing.T) { cmd := NewCmdCreate(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) // uses the name from the file, not the response - if buf.String() != "replicationcontrollers/redis-master-controller\n" { + if buf.String() != "replicationcontroller/redis-master-controller\n" { t.Errorf("unexpected output: %s", buf.String()) } } @@ -92,10 +93,11 @@ func TestCreateMultipleObject(t *testing.T) { cmd := NewCmdCreate(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) // Names should come from the REST response, NOT the files - if buf.String() != "replicationcontrollers/rc1\nservices/baz\n" { + if buf.String() != "replicationcontroller/rc1\nservice/baz\n" { t.Errorf("unexpected output: %s", buf.String()) } } @@ -125,9 +127,10 @@ func TestCreateDirectory(t *testing.T) { cmd := NewCmdCreate(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) - if buf.String() != "replicationcontrollers/name\nservices/baz\nreplicationcontrollers/name\nservices/baz\nreplicationcontrollers/name\nservices/baz\n" { + if buf.String() != "replicationcontroller/name\nservice/baz\nreplicationcontroller/name\nservice/baz\nreplicationcontroller/name\nservice/baz\n" { t.Errorf("unexpected output: %s", buf.String()) } } diff --git a/pkg/kubectl/cmd/delete.go b/pkg/kubectl/cmd/delete.go index dd3d7db432580..57949c7a0caf8 100644 --- a/pkg/kubectl/cmd/delete.go +++ b/pkg/kubectl/cmd/delete.go @@ -25,6 +25,7 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" @@ -66,7 +67,9 @@ func NewCmdDelete(f *cmdutil.Factory, out io.Writer) *cobra.Command { Long: delete_long, Example: delete_example, Run: func(cmd *cobra.Command, args []string) { - err := RunDelete(f, out, cmd, args, filenames) + cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) + shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" + err := RunDelete(f, out, cmd, args, filenames, shortOutput) cmdutil.CheckErr(err) }, } @@ -78,10 +81,11 @@ func NewCmdDelete(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Bool("cascade", true, "If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.") cmd.Flags().Int("grace-period", -1, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative.") cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object") + cmdutil.AddOutputFlagsForMutation(cmd) return cmd } -func RunDelete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList) error { +func RunDelete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList, shortOutput bool) error { cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err @@ -117,12 +121,12 @@ func RunDelete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str // By default use a reaper to delete all related resources. if cmdutil.GetFlagBool(cmd, "cascade") { - return ReapResult(r, f, out, cmdutil.GetFlagBool(cmd, "cascade"), ignoreNotFound, cmdutil.GetFlagDuration(cmd, "timeout"), cmdutil.GetFlagInt(cmd, "grace-period")) + return ReapResult(r, f, out, cmdutil.GetFlagBool(cmd, "cascade"), ignoreNotFound, cmdutil.GetFlagDuration(cmd, "timeout"), cmdutil.GetFlagInt(cmd, "grace-period"), shortOutput, mapper) } - return DeleteResult(r, out, ignoreNotFound) + return DeleteResult(r, out, ignoreNotFound, shortOutput, mapper) } -func ReapResult(r *resource.Result, f *cmdutil.Factory, out io.Writer, isDefaultDelete, ignoreNotFound bool, timeout time.Duration, gracePeriod int) error { +func ReapResult(r *resource.Result, f *cmdutil.Factory, out io.Writer, isDefaultDelete, ignoreNotFound bool, timeout time.Duration, gracePeriod int, shortOutput bool, mapper meta.RESTMapper) error { found := 0 if ignoreNotFound { r = r.IgnoreErrors(errors.IsNotFound) @@ -133,7 +137,7 @@ func ReapResult(r *resource.Result, f *cmdutil.Factory, out io.Writer, isDefault if err != nil { // If there is no reaper for this resources and the user didn't explicitly ask for stop. if kubectl.IsNoSuchReaperError(err) && isDefaultDelete { - return deleteResource(info, out) + return deleteResource(info, out, shortOutput, mapper) } return cmdutil.AddSourceToErr("reaping", info.Source, err) } @@ -144,7 +148,7 @@ func ReapResult(r *resource.Result, f *cmdutil.Factory, out io.Writer, isDefault if _, err := reaper.Stop(info.Namespace, info.Name, timeout, options); err != nil { return cmdutil.AddSourceToErr("stopping", info.Source, err) } - fmt.Fprintf(out, "%s/%s\n", info.Mapping.Resource, info.Name) + cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "deleted") return nil }) if err != nil { @@ -156,14 +160,14 @@ func ReapResult(r *resource.Result, f *cmdutil.Factory, out io.Writer, isDefault return nil } -func DeleteResult(r *resource.Result, out io.Writer, ignoreNotFound bool) error { +func DeleteResult(r *resource.Result, out io.Writer, ignoreNotFound bool, shortOutput bool, mapper meta.RESTMapper) error { found := 0 if ignoreNotFound { r = r.IgnoreErrors(errors.IsNotFound) } err := r.Visit(func(info *resource.Info) error { found++ - return deleteResource(info, out) + return deleteResource(info, out, shortOutput, mapper) }) if err != nil { return err @@ -174,10 +178,10 @@ func DeleteResult(r *resource.Result, out io.Writer, ignoreNotFound bool) error return nil } -func deleteResource(info *resource.Info, out io.Writer) error { +func deleteResource(info *resource.Info, out io.Writer, shortOutput bool, mapper meta.RESTMapper) error { if err := resource.NewHelper(info.Client, info.Mapping).Delete(info.Namespace, info.Name); err != nil { return cmdutil.AddSourceToErr("deleting", info.Source, err) } - fmt.Fprintf(out, "%s/%s\n", info.Mapping.Resource, info.Name) + cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "deleted") return nil } diff --git a/pkg/kubectl/cmd/delete_test.go b/pkg/kubectl/cmd/delete_test.go index 742554b5c1932..d4467c713bf37 100644 --- a/pkg/kubectl/cmd/delete_test.go +++ b/pkg/kubectl/cmd/delete_test.go @@ -53,9 +53,10 @@ func TestDeleteObjectByTuple(t *testing.T) { cmd := NewCmdDelete(f, buf) cmd.Flags().Set("namespace", "test") cmd.Flags().Set("cascade", "false") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{"replicationcontrollers/redis-master-controller"}) - if buf.String() != "replicationcontrollers/redis-master-controller\n" { + if buf.String() != "replicationcontroller/redis-master-controller\n" { t.Errorf("unexpected output: %s", buf.String()) } } @@ -84,9 +85,10 @@ func TestDeleteNamedObject(t *testing.T) { cmd := NewCmdDelete(f, buf) cmd.Flags().Set("namespace", "test") cmd.Flags().Set("cascade", "false") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{"replicationcontrollers", "redis-master-controller"}) - if buf.String() != "replicationcontrollers/redis-master-controller\n" { + if buf.String() != "replicationcontroller/redis-master-controller\n" { t.Errorf("unexpected output: %s", buf.String()) } } @@ -114,10 +116,11 @@ func TestDeleteObject(t *testing.T) { cmd := NewCmdDelete(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") cmd.Flags().Set("cascade", "false") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) // uses the name from the file, not the response - if buf.String() != "replicationcontrollers/redis-master\n" { + if buf.String() != "replicationcontroller/redis-master\n" { t.Errorf("unexpected output: %s", buf.String()) } } @@ -143,8 +146,9 @@ func TestDeleteObjectNotFound(t *testing.T) { cmd := NewCmdDelete(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") cmd.Flags().Set("cascade", "false") + cmd.Flags().Set("output", "name") filenames := cmd.Flags().Lookup("filename").Value.(*util.StringList) - err := RunDelete(f, buf, cmd, []string{}, *filenames) + err := RunDelete(f, buf, cmd, []string{}, *filenames, true) if err == nil || !errors.IsNotFound(err) { t.Errorf("unexpected error: expected NotFound, got %v", err) } @@ -172,6 +176,7 @@ func TestDeleteObjectIgnoreNotFound(t *testing.T) { cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") cmd.Flags().Set("cascade", "false") cmd.Flags().Set("ignore-not-found", "true") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) if buf.String() != "" { @@ -214,7 +219,7 @@ func TestDeleteAllNotFound(t *testing.T) { // Make sure we can explicitly choose to fail on NotFound errors, even with --all cmd.Flags().Set("ignore-not-found", "false") - err := RunDelete(f, buf, cmd, []string{"services"}, nil) + err := RunDelete(f, buf, cmd, []string{"services"}, nil, true) if err == nil || !errors.IsNotFound(err) { t.Errorf("unexpected error: expected NotFound, got %v", err) } @@ -252,9 +257,10 @@ func TestDeleteAllIgnoreNotFound(t *testing.T) { cmd := NewCmdDelete(f, buf) cmd.Flags().Set("all", "true") cmd.Flags().Set("cascade", "false") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{"services"}) - if buf.String() != "services/baz\n" { + if buf.String() != "service/baz\n" { t.Errorf("unexpected output: %s", buf.String()) } } @@ -285,9 +291,10 @@ func TestDeleteMultipleObject(t *testing.T) { cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml") cmd.Flags().Set("cascade", "false") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) - if buf.String() != "replicationcontrollers/redis-master\nservices/frontend\n" { + if buf.String() != "replicationcontroller/redis-master\nservice/frontend\n" { t.Errorf("unexpected output: %s", buf.String()) } } @@ -318,14 +325,15 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) { cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml") cmd.Flags().Set("cascade", "false") + cmd.Flags().Set("output", "name") filenames := cmd.Flags().Lookup("filename").Value.(*util.StringList) t.Logf("filenames: %v\n", filenames) - err := RunDelete(f, buf, cmd, []string{}, *filenames) + err := RunDelete(f, buf, cmd, []string{}, *filenames, true) if err == nil || !errors.IsNotFound(err) { t.Errorf("unexpected error: expected NotFound, got %v", err) } - if buf.String() != "services/frontend\n" { + if buf.String() != "service/frontend\n" { t.Errorf("unexpected output: %s", buf.String()) } } @@ -355,9 +363,10 @@ func TestDeleteDirectory(t *testing.T) { cmd := NewCmdDelete(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook") cmd.Flags().Set("cascade", "false") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) - if buf.String() != "replicationcontrollers/frontend\nservices/frontend\nreplicationcontrollers/redis-master\nservices/redis-master\nreplicationcontrollers/redis-slave\nservices/redis-slave\n" { + if buf.String() != "replicationcontroller/frontend\nservice/frontend\nreplicationcontroller/redis-master\nservice/redis-master\nreplicationcontroller/redis-slave\nservice/redis-slave\n" { t.Errorf("unexpected output: %s", buf.String()) } } @@ -397,9 +406,10 @@ func TestDeleteMultipleSelector(t *testing.T) { cmd := NewCmdDelete(f, buf) cmd.Flags().Set("selector", "a=b") cmd.Flags().Set("cascade", "false") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{"pods,services"}) - if buf.String() != "pods/foo\npods/bar\nservices/baz\n" { + if buf.String() != "pod/foo\npod/bar\nservice/baz\n" { t.Errorf("unexpected output: %s", buf.String()) } } diff --git a/pkg/kubectl/cmd/patch.go b/pkg/kubectl/cmd/patch.go index 1dd34a2d51c75..dbc38781dff8c 100644 --- a/pkg/kubectl/cmd/patch.go +++ b/pkg/kubectl/cmd/patch.go @@ -17,7 +17,6 @@ limitations under the License. package cmd import ( - "fmt" "io" "github.com/spf13/cobra" @@ -43,16 +42,19 @@ func NewCmdPatch(f *cmdutil.Factory, out io.Writer) *cobra.Command { Long: patch_long, Example: patch_example, Run: func(cmd *cobra.Command, args []string) { - err := RunPatch(f, out, cmd, args) + cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) + shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" + err := RunPatch(f, out, cmd, args, shortOutput) cmdutil.CheckCustomErr("Patch failed", err) }, } cmd.Flags().StringP("patch", "p", "", "The patch to be applied to the resource JSON file.") cmd.MarkFlagRequired("patch") + cmdutil.AddOutputFlagsForMutation(cmd) return cmd } -func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { +func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, shortOutput bool) error { cmdNamespace, _, err := f.DefaultNamespace() if err != nil { return err @@ -94,6 +96,6 @@ func RunPatch(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri if err != nil { return err } - fmt.Fprintf(out, "%s\n", name) + cmdutil.PrintSuccess(mapper, shortOutput, out, "", name, "patched") return nil } diff --git a/pkg/kubectl/cmd/patch_test.go b/pkg/kubectl/cmd/patch_test.go index 3206caef669f3..15cbc154a84c0 100644 --- a/pkg/kubectl/cmd/patch_test.go +++ b/pkg/kubectl/cmd/patch_test.go @@ -47,6 +47,7 @@ func TestPatchObject(t *testing.T) { cmd := NewCmdPatch(f, buf) cmd.Flags().Set("namespace", "test") cmd.Flags().Set("patch", `{"spec":{"type":"NodePort"}}`) + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{"services/frontend"}) // uses the name from the file, not the response diff --git a/pkg/kubectl/cmd/replace.go b/pkg/kubectl/cmd/replace.go index def02c249c440..72fcb23486f3b 100644 --- a/pkg/kubectl/cmd/replace.go +++ b/pkg/kubectl/cmd/replace.go @@ -56,7 +56,9 @@ func NewCmdReplace(f *cmdutil.Factory, out io.Writer) *cobra.Command { Long: replace_long, Example: replace_example, Run: func(cmd *cobra.Command, args []string) { - err := RunReplace(f, out, cmd, args, filenames) + cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) + shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" + err := RunReplace(f, out, cmd, args, filenames, shortOutput) cmdutil.CheckCustomErr("Replace failed", err) }, } @@ -67,10 +69,11 @@ func NewCmdReplace(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Bool("cascade", false, "Only relevant during a force replace. If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.") cmd.Flags().Int("grace-period", -1, "Only relevant during a force replace. Period of time in seconds given to the old resource to terminate gracefully. Ignored if negative.") cmd.Flags().Duration("timeout", 0, "Only relevant during a force replace. The length of time to wait before giving up on a delete of the old resource, zero means determine a timeout from the size of the object") + cmdutil.AddOutputFlagsForMutation(cmd) return cmd } -func RunReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList) error { +func RunReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList, shortOutput bool) error { if len(os.Args) > 1 && os.Args[1] == "update" { printDeprecationWarning("replace", "update") } @@ -90,7 +93,7 @@ func RunReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st } if force { - return forceReplace(f, out, cmd, args, filenames) + return forceReplace(f, out, cmd, args, filenames, shortOutput) } mapper, typer := f.Object() @@ -117,12 +120,12 @@ func RunReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []st } info.Refresh(obj, true) printObjectSpecificMessage(obj, out) - fmt.Fprintf(out, "%s/%s\n", info.Mapping.Resource, info.Name) + cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "replaced") return nil }) } -func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList) error { +func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, filenames util.StringList, shortOutput bool) error { schema, err := f.Validator() if err != nil { return err @@ -166,9 +169,9 @@ func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] // By default use a reaper to delete all related resources. if cmdutil.GetFlagBool(cmd, "cascade") { glog.Warningf("\"cascade\" is set, kubectl will delete and re-create all resources managed by this resource (e.g. Pods created by a ReplicationController). Consider using \"kubectl rolling-update\" if you want to update a ReplicationController together with its Pods.") - err = ReapResult(r, f, out, cmdutil.GetFlagBool(cmd, "cascade"), ignoreNotFound, cmdutil.GetFlagDuration(cmd, "timeout"), cmdutil.GetFlagInt(cmd, "grace-period")) + err = ReapResult(r, f, out, cmdutil.GetFlagBool(cmd, "cascade"), ignoreNotFound, cmdutil.GetFlagDuration(cmd, "timeout"), cmdutil.GetFlagInt(cmd, "grace-period"), shortOutput, mapper) } else { - err = DeleteResult(r, out, ignoreNotFound) + err = DeleteResult(r, out, ignoreNotFound, shortOutput, mapper) } if err != nil { return err @@ -199,7 +202,7 @@ func forceReplace(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args [] count++ info.Refresh(obj, true) printObjectSpecificMessage(obj, out) - fmt.Fprintf(out, "%s/%s\n", info.Mapping.Resource, info.Name) + cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "replaced") return nil }) if err != nil { diff --git a/pkg/kubectl/cmd/replace_test.go b/pkg/kubectl/cmd/replace_test.go index f8d9c87aa637f..b2117fc551cfb 100644 --- a/pkg/kubectl/cmd/replace_test.go +++ b/pkg/kubectl/cmd/replace_test.go @@ -49,19 +49,21 @@ func TestReplaceObject(t *testing.T) { cmd := NewCmdReplace(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) // uses the name from the file, not the response - if buf.String() != "replicationcontrollers/rc1\n" { + if buf.String() != "replicationcontroller/rc1\n" { t.Errorf("unexpected output: %s", buf.String()) } buf.Reset() cmd.Flags().Set("force", "true") cmd.Flags().Set("cascade", "false") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) - if buf.String() != "replicationcontrollers/redis-master\nreplicationcontrollers/rc1\n" { + if buf.String() != "replicationcontroller/redis-master\nreplicationcontroller/rc1\n" { t.Errorf("unexpected output: %s", buf.String()) } } @@ -95,18 +97,20 @@ func TestReplaceMultipleObject(t *testing.T) { cmd := NewCmdReplace(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) - if buf.String() != "replicationcontrollers/rc1\nservices/baz\n" { + if buf.String() != "replicationcontroller/rc1\nservice/baz\n" { t.Errorf("unexpected output: %s", buf.String()) } buf.Reset() cmd.Flags().Set("force", "true") cmd.Flags().Set("cascade", "false") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) - if buf.String() != "replicationcontrollers/redis-master\nservices/frontend\nreplicationcontrollers/rc1\nservices/baz\n" { + if buf.String() != "replicationcontroller/redis-master\nservice/frontend\nreplicationcontroller/rc1\nservice/baz\n" { t.Errorf("unexpected output: %s", buf.String()) } } @@ -140,9 +144,10 @@ func TestReplaceDirectory(t *testing.T) { cmd := NewCmdReplace(f, buf) cmd.Flags().Set("filename", "../../../examples/guestbook") cmd.Flags().Set("namespace", "test") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) - if buf.String() != "replicationcontrollers/rc1\nservices/baz\nreplicationcontrollers/rc1\nservices/baz\nreplicationcontrollers/rc1\nservices/baz\n" { + if buf.String() != "replicationcontroller/rc1\nservice/baz\nreplicationcontroller/rc1\nservice/baz\nreplicationcontroller/rc1\nservice/baz\n" { t.Errorf("unexpected output: %s", buf.String()) } @@ -151,8 +156,8 @@ func TestReplaceDirectory(t *testing.T) { cmd.Flags().Set("cascade", "false") cmd.Run(cmd, []string{}) - if buf.String() != "replicationcontrollers/frontend\nservices/frontend\nreplicationcontrollers/redis-master\nservices/redis-master\nreplicationcontrollers/redis-slave\nservices/redis-slave\n"+ - "replicationcontrollers/rc1\nservices/baz\nreplicationcontrollers/rc1\nservices/baz\nreplicationcontrollers/rc1\nservices/baz\n" { + if buf.String() != "replicationcontroller/frontend\nservice/frontend\nreplicationcontroller/redis-master\nservice/redis-master\nreplicationcontroller/redis-slave\nservice/redis-slave\n"+ + "replicationcontroller/rc1\nservice/baz\nreplicationcontroller/rc1\nservice/baz\nreplicationcontroller/rc1\nservice/baz\n" { t.Errorf("unexpected output: %s", buf.String()) } } @@ -183,9 +188,10 @@ func TestForceReplaceObjectNotFound(t *testing.T) { cmd.Flags().Set("filename", "../../../examples/guestbook/redis-master-controller.yaml") cmd.Flags().Set("force", "true") cmd.Flags().Set("cascade", "false") + cmd.Flags().Set("output", "name") cmd.Run(cmd, []string{}) - if buf.String() != "replicationcontrollers/rc1\n" { + if buf.String() != "replicationcontroller/rc1\n" { t.Errorf("unexpected output: %s", buf.String()) } } diff --git a/pkg/kubectl/cmd/scale.go b/pkg/kubectl/cmd/scale.go index ec93c8b2a3d2f..0628dee0862dd 100644 --- a/pkg/kubectl/cmd/scale.go +++ b/pkg/kubectl/cmd/scale.go @@ -52,7 +52,9 @@ func NewCmdScale(f *cmdutil.Factory, out io.Writer) *cobra.Command { Long: scale_long, Example: scale_example, Run: func(cmd *cobra.Command, args []string) { - err := RunScale(f, out, cmd, args) + cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) + shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" + err := RunScale(f, out, cmd, args, shortOutput) cmdutil.CheckErr(err) }, } @@ -60,11 +62,12 @@ func NewCmdScale(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Int("current-replicas", -1, "Precondition for current size. Requires that the current size of the replication controller match this value in order to scale.") cmd.Flags().Int("replicas", -1, "The new desired number of replicas. Required.") cmd.MarkFlagRequired("replicas") + cmdutil.AddOutputFlagsForMutation(cmd) return cmd } // RunScale executes the scaling -func RunScale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { +func RunScale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, shortOutput bool) error { if len(os.Args) > 1 && os.Args[1] == "resize" { printDeprecationWarning("scale", "resize") } @@ -117,6 +120,6 @@ func RunScale(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []stri if err := scaler.Scale(info.Namespace, info.Name, uint(count), precondition, retry, waitForReplicas); err != nil { return err } - fmt.Fprint(out, "scaled\n") + cmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "scaled") return nil } diff --git a/pkg/kubectl/cmd/stop.go b/pkg/kubectl/cmd/stop.go index b8b214419bbe2..17168688781eb 100644 --- a/pkg/kubectl/cmd/stop.go +++ b/pkg/kubectl/cmd/stop.go @@ -54,7 +54,9 @@ func NewCmdStop(f *cmdutil.Factory, out io.Writer) *cobra.Command { Long: stop_long, Example: stop_example, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(RunStop(f, cmd, args, flags.Filenames, out)) + cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) + shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" + cmdutil.CheckErr(RunStop(f, cmd, args, flags.Filenames, out, shortOutput)) }, } usage := "Filename, directory, or URL to file of resource(s) to be stopped." @@ -64,10 +66,11 @@ func NewCmdStop(f *cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Bool("ignore-not-found", false, "Treat \"resource not found\" as a successful stop.") cmd.Flags().Int("grace-period", -1, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative.") cmd.Flags().Duration("timeout", 0, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object") + cmdutil.AddOutputFlagsForMutation(cmd) return cmd } -func RunStop(f *cmdutil.Factory, cmd *cobra.Command, args []string, filenames util.StringList, out io.Writer) error { +func RunStop(f *cmdutil.Factory, cmd *cobra.Command, args []string, filenames util.StringList, out io.Writer, shortOutput bool) error { cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { return err @@ -85,5 +88,5 @@ func RunStop(f *cmdutil.Factory, cmd *cobra.Command, args []string, filenames ut if r.Err() != nil { return r.Err() } - return ReapResult(r, f, out, false, cmdutil.GetFlagBool(cmd, "ignore-not-found"), cmdutil.GetFlagDuration(cmd, "timeout"), cmdutil.GetFlagInt(cmd, "grace-period")) + return ReapResult(r, f, out, false, cmdutil.GetFlagBool(cmd, "ignore-not-found"), cmdutil.GetFlagDuration(cmd, "timeout"), cmdutil.GetFlagInt(cmd, "grace-period"), shortOutput, mapper) } diff --git a/pkg/kubectl/cmd/util/printing.go b/pkg/kubectl/cmd/util/printing.go index 966cbb3baa270..41390ae708330 100644 --- a/pkg/kubectl/cmd/util/printing.go +++ b/pkg/kubectl/cmd/util/printing.go @@ -17,6 +17,10 @@ limitations under the License. package util import ( + "fmt" + "io" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" "github.com/spf13/cobra" @@ -30,6 +34,40 @@ func AddPrinterFlags(cmd *cobra.Command) { cmd.Flags().StringP("template", "t", "", "Template string or path to template file to use when -o=template or -o=templatefile. The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]") } +// AddOutputFlagsForMutation adds output related flags to a command. Used by mutations only. +func AddOutputFlagsForMutation(cmd *cobra.Command) { + cmd.Flags().StringP("output", "o", "", "Output mode. Use \"-o name\" for shorter output (resource/name).") +} + +// PrintSuccess prints message after finishing mutating operations +func PrintSuccess(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource string, name string, operation string) { + resource, _ = mapper.ResourceSingularizer(resource) + if shortOutput { + // -o name: prints resource/name + if len(resource) > 0 { + fmt.Fprintf(out, "%s/%s\n", resource, name) + } else { + fmt.Fprintf(out, "%s\n", name) + } + } else { + // understandable output by default + if len(resource) > 0 { + fmt.Fprintf(out, "%s \"%s\" %s\n", resource, name, operation) + } else { + fmt.Fprintf(out, "\"%s\" %s\n", name, operation) + } + } +} + +// ValidateOutputArgs validates -o flag args for mutations +func ValidateOutputArgs(cmd *cobra.Command) error { + outputMode := GetFlagString(cmd, "output") + if outputMode != "" && outputMode != "name" { + return UsageError(cmd, "Unexpected -o output mode: %v. We only support '-o name'.", outputMode) + } + return nil +} + // OutputVersion returns the preferred output version for generic content (JSON, YAML, or templates) func OutputVersion(cmd *cobra.Command, defaultVersion string) string { outputVersion := GetFlagString(cmd, "output-version")