Add '-o name' to mutations

This commit is contained in:
Janet Kuo 2015-07-01 15:47:43 -07:00
parent a171d2e4fb
commit 0e42d0699a
27 changed files with 241 additions and 56 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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