Merge pull request #39401 from AdoHe/delete_refactor

Automatic merge from submit-queue (batch tested with PRs 39806, 39887, 39401)

refactor delete to remove cobra dependency

FYI. As part of CLI Q1 roadmap, we would like to reduce the dependency of Cobra from actual commands implementations. In this PR, I tried to refactor delete command to achieve this. @kubernetes/sig-cli-misc a quick review is quite welcome, and I am just working on more PRs.
This commit is contained in:
Kubernetes Submit Queue 2017-01-16 13:41:36 -08:00 committed by GitHub
commit 6fe8ed738d
4 changed files with 136 additions and 64 deletions

View File

@ -89,8 +89,33 @@ var (
kubectl delete pods --all`) kubectl delete pods --all`)
) )
type DeleteOptions struct {
resource.FilenameOptions
Selector string
DeleteAll bool
IgnoreNotFound bool
Cascade bool
DeleteNow bool
ForceDeletion bool
WaitForDeletion bool
GracePeriod int
Timeout time.Duration
Include3rdParty bool
Output string
Mapper meta.RESTMapper
Result *resource.Result
f cmdutil.Factory
Out io.Writer
ErrOut io.Writer
}
func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
options := &resource.FilenameOptions{} options := &DeleteOptions{}
// retrieve a list of handled resources from printer as valid args // retrieve a list of handled resources from printer as valid args
validArgs, argAliases := []string{}, []string{} validArgs, argAliases := []string{}, []string{}
@ -110,44 +135,53 @@ func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
Example: delete_example, Example: delete_example,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
err := RunDelete(f, out, errOut, cmd, args, options) if err := options.Complete(f, out, errOut, args); err != nil {
cmdutil.CheckErr(err) cmdutil.CheckErr(err)
}
if err := options.Validate(f, cmd); err != nil {
cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error()))
}
if err := options.RunDelete(); err != nil {
cmdutil.CheckErr(err)
}
}, },
SuggestFor: []string{"rm"}, SuggestFor: []string{"rm"},
ValidArgs: validArgs, ValidArgs: validArgs,
ArgAliases: argAliases, ArgAliases: argAliases,
} }
usage := "containing the resource to delete." usage := "containing the resource to delete."
cmdutil.AddFilenameOptionFlags(cmd, options, usage) cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on.") cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on.")
cmd.Flags().Bool("all", false, "[-all] to select all the specified resources.") cmd.Flags().BoolVar(&options.DeleteAll, "all", false, "[-all] to select all the specified resources.")
cmd.Flags().Bool("ignore-not-found", false, "Treat \"resource not found\" as a successful delete. Defaults to \"true\" when --all is specified.") cmd.Flags().BoolVar(&options.IgnoreNotFound, "ignore-not-found", false, "Treat \"resource not found\" as a successful delete. Defaults to \"true\" when --all is specified.")
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().BoolVar(&options.Cascade, "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().IntVar(&options.GracePeriod, "grace-period", -1, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative.")
cmd.Flags().Bool("now", false, "If true, resources are signaled for immediate shutdown (same as --grace-period=1).") cmd.Flags().BoolVar(&options.DeleteNow, "now", false, "If true, resources are signaled for immediate shutdown (same as --grace-period=1).")
cmd.Flags().Bool("force", false, "Immediate deletion of some resources may result in inconsistency or data loss and requires confirmation.") cmd.Flags().BoolVar(&options.ForceDeletion, "force", false, "Immediate deletion of some resources may result in inconsistency or data loss and requires confirmation.")
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") cmd.Flags().DurationVar(&options.Timeout, "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) cmdutil.AddOutputVarFlagsForMutation(cmd, &options.Output)
cmdutil.AddInclude3rdPartyFlags(cmd) cmdutil.AddInclude3rdPartyVarFlags(cmd, &options.Include3rdParty)
return cmd return cmd
} }
func RunDelete(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions) error { func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args []string) error {
cmdNamespace, enforceNamespace, err := f.DefaultNamespace() cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil { if err != nil {
return err return err
} }
deleteAll := cmdutil.GetFlagBool(cmd, "all")
// Set up client based support.
mapper, typer, err := f.UnstructuredObject() mapper, typer, err := f.UnstructuredObject()
if err != nil { if err != nil {
return err return err
} }
o.Mapper = mapper
r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme). r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme).
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, options). FilenameParam(enforceNamespace, &o.FilenameOptions).
SelectorParam(cmdutil.GetFlagString(cmd, "selector")). SelectorParam(o.Selector).
SelectAllParam(deleteAll). SelectAllParam(o.DeleteAll).
ResourceTypeOrNameArgs(false, args...).RequireObject(false). ResourceTypeOrNameArgs(false, args...).RequireObject(false).
Flatten(). Flatten().
Do() Do()
@ -155,9 +189,18 @@ func RunDelete(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, arg
if err != nil { if err != nil {
return err return err
} }
o.Result = r
ignoreNotFound := cmdutil.GetFlagBool(cmd, "ignore-not-found") o.f = f
if deleteAll { // Set up writer
o.Out = out
o.ErrOut = errOut
return nil
}
func (o *DeleteOptions) Validate(f cmdutil.Factory, cmd *cobra.Command) error {
if o.DeleteAll {
f := cmd.Flags().Lookup("ignore-not-found") f := cmd.Flags().Lookup("ignore-not-found")
// The flag should never be missing // The flag should never be missing
if f == nil { if f == nil {
@ -165,37 +208,36 @@ func RunDelete(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, arg
} }
// If the user didn't explicitly set the option, default to ignoring NotFound errors when used with --all // If the user didn't explicitly set the option, default to ignoring NotFound errors when used with --all
if !f.Changed { if !f.Changed {
ignoreNotFound = true o.IgnoreNotFound = true
} }
} }
if o.DeleteNow {
gracePeriod := cmdutil.GetFlagInt(cmd, "grace-period") if o.GracePeriod != -1 {
force := cmdutil.GetFlagBool(cmd, "force")
if cmdutil.GetFlagBool(cmd, "now") {
if gracePeriod != -1 {
return fmt.Errorf("--now and --grace-period cannot be specified together") return fmt.Errorf("--now and --grace-period cannot be specified together")
} }
gracePeriod = 1 o.GracePeriod = 1
} }
wait := false if o.GracePeriod == 0 {
if gracePeriod == 0 { if o.ForceDeletion {
if force { fmt.Fprintf(o.ErrOut, "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n")
fmt.Fprintf(errOut, "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n")
} else { } else {
// To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0 // To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0
// into --grace-period=1 and wait until the object is successfully deleted. Users may provide --force // into --grace-period=1 and wait until the object is successfully deleted. Users may provide --force
// to bypass this wait. // to bypass this wait.
wait = true o.WaitForDeletion = true
gracePeriod = 1 o.GracePeriod = 1
} }
} }
return nil
}
shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" func (o *DeleteOptions) RunDelete() error {
shortOutput := o.Output == "name"
// By default use a reaper to delete all related resources. // By default use a reaper to delete all related resources.
if cmdutil.GetFlagBool(cmd, "cascade") { if o.Cascade {
return ReapResult(r, f, out, cmdutil.GetFlagBool(cmd, "cascade"), ignoreNotFound, cmdutil.GetFlagDuration(cmd, "timeout"), gracePeriod, wait, shortOutput, mapper, false) return ReapResult(o.Result, o.f, o.Out, true, o.IgnoreNotFound, o.Timeout, o.GracePeriod, o.WaitForDeletion, shortOutput, o.Mapper, false)
} }
return DeleteResult(r, out, ignoreNotFound, shortOutput, mapper) return DeleteResult(o.Result, o.Out, o.IgnoreNotFound, shortOutput, o.Mapper)
} }
func ReapResult(r *resource.Result, f cmdutil.Factory, out io.Writer, isDefaultDelete, ignoreNotFound bool, timeout time.Duration, gracePeriod int, waitForDeletion, shortOutput bool, mapper meta.RESTMapper, quiet bool) error { func ReapResult(r *resource.Result, f cmdutil.Factory, out io.Writer, isDefaultDelete, ignoreNotFound bool, timeout time.Duration, gracePeriod int, waitForDeletion, shortOutput bool, mapper meta.RESTMapper, quiet bool) error {

View File

@ -227,12 +227,19 @@ func TestDeleteObjectNotFound(t *testing.T) {
tf.Namespace = "test" tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(f, buf, errBuf) options := &DeleteOptions{
options := &resource.FilenameOptions{} FilenameOptions: resource.FilenameOptions{
options.Filenames = []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml"} Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml"},
cmd.Flags().Set("cascade", "false") },
cmd.Flags().Set("output", "name") GracePeriod: -1,
err := RunDelete(f, buf, errBuf, cmd, []string{}, options) Cascade: false,
Output: "name",
}
err := options.Complete(f, buf, errBuf, []string{})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = options.RunDelete()
if err == nil || !errors.IsNotFound(err) { if err == nil || !errors.IsNotFound(err) {
t.Errorf("unexpected error: expected NotFound, got %v", err) t.Errorf("unexpected error: expected NotFound, got %v", err)
} }
@ -296,14 +303,20 @@ func TestDeleteAllNotFound(t *testing.T) {
tf.Namespace = "test" tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(f, buf, errBuf)
cmd.Flags().Set("all", "true")
cmd.Flags().Set("cascade", "false")
// Make sure we can explicitly choose to fail on NotFound errors, even with --all // Make sure we can explicitly choose to fail on NotFound errors, even with --all
cmd.Flags().Set("ignore-not-found", "false") options := &DeleteOptions{
cmd.Flags().Set("output", "name") FilenameOptions: resource.FilenameOptions{},
GracePeriod: -1,
err := RunDelete(f, buf, errBuf, cmd, []string{"services"}, &resource.FilenameOptions{}) Cascade: false,
DeleteAll: true,
IgnoreNotFound: false,
Output: "name",
}
err := options.Complete(f, buf, errBuf, []string{"services"})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = options.RunDelete()
if err == nil || !errors.IsNotFound(err) { if err == nil || !errors.IsNotFound(err) {
t.Errorf("unexpected error: expected NotFound, got %v", err) t.Errorf("unexpected error: expected NotFound, got %v", err)
} }
@ -405,12 +418,19 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) {
tf.Namespace = "test" tf.Namespace = "test"
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(f, buf, errBuf) options := &DeleteOptions{
options := &resource.FilenameOptions{} FilenameOptions: resource.FilenameOptions{
options.Filenames = []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml", "../../../examples/guestbook/frontend-service.yaml"} Filenames: []string{"../../../examples/guestbook/legacy/redis-master-controller.yaml", "../../../examples/guestbook/frontend-service.yaml"},
cmd.Flags().Set("cascade", "false") },
cmd.Flags().Set("output", "name") GracePeriod: -1,
err := RunDelete(f, buf, errBuf, cmd, []string{}, options) Cascade: false,
Output: "name",
}
err := options.Complete(f, buf, errBuf, []string{})
if err != nil {
t.Errorf("unexpected error: %v", err)
}
err = options.RunDelete()
if err == nil || !errors.IsNotFound(err) { if err == nil || !errors.IsNotFound(err) {
t.Errorf("unexpected error: expected NotFound, got %v", err) t.Errorf("unexpected error: expected NotFound, got %v", err)
} }
@ -533,7 +553,6 @@ func TestDeleteMultipleSelector(t *testing.T) {
func TestResourceErrors(t *testing.T) { func TestResourceErrors(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
args []string args []string
flags map[string]string
errFn func(error) bool errFn func(error) bool
}{ }{
"no args": { "no args": {
@ -562,17 +581,18 @@ func TestResourceErrors(t *testing.T) {
buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) buf, errBuf := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd := NewCmdDelete(f, buf, errBuf) options := &DeleteOptions{
cmd.SetOutput(buf) FilenameOptions: resource.FilenameOptions{},
GracePeriod: -1,
for k, v := range testCase.flags { Cascade: false,
cmd.Flags().Set(k, v) Output: "name",
} }
err := RunDelete(f, buf, errBuf, cmd, testCase.args, &resource.FilenameOptions{}) err := options.Complete(f, buf, errBuf, testCase.args)
if !testCase.errFn(err) { if !testCase.errFn(err) {
t.Errorf("%s: unexpected error: %v", k, err) t.Errorf("%s: unexpected error: %v", k, err)
continue continue
} }
if tf.Printer.(*testPrinter).Objects != nil { if tf.Printer.(*testPrinter).Objects != nil {
t.Errorf("unexpected print to default printer") t.Errorf("unexpected print to default printer")
} }

View File

@ -557,6 +557,11 @@ func ShouldRecord(cmd *cobra.Command, info *resource.Info) bool {
return GetRecordFlag(cmd) || (ContainsChangeCause(info) && !cmd.Flags().Changed("record")) return GetRecordFlag(cmd) || (ContainsChangeCause(info) && !cmd.Flags().Changed("record"))
} }
func AddInclude3rdPartyVarFlags(cmd *cobra.Command, include3rdParty *bool) {
cmd.Flags().BoolVar(include3rdParty, "include-extended-apis", true, "If true, include definitions of new APIs via calls to the API server. [default true]")
cmd.Flags().MarkDeprecated("include-extended-apis", "No longer required.")
}
func AddInclude3rdPartyFlags(cmd *cobra.Command) { func AddInclude3rdPartyFlags(cmd *cobra.Command) {
cmd.Flags().Bool("include-extended-apis", true, "If true, include definitions of new APIs via calls to the API server. [default true]") cmd.Flags().Bool("include-extended-apis", true, "If true, include definitions of new APIs via calls to the API server. [default true]")
cmd.Flags().MarkDeprecated("include-extended-apis", "No longer required.") cmd.Flags().MarkDeprecated("include-extended-apis", "No longer required.")

View File

@ -46,6 +46,11 @@ func AddOutputFlagsForMutation(cmd *cobra.Command) {
cmd.Flags().StringP("output", "o", "", "Output mode. Use \"-o name\" for shorter output (resource/name).") cmd.Flags().StringP("output", "o", "", "Output mode. Use \"-o name\" for shorter output (resource/name).")
} }
// AddOutputVarFlagsForMutation adds output related flags to a command. Used by mutations only.
func AddOutputVarFlagsForMutation(cmd *cobra.Command, output *string) {
cmd.Flags().StringVarP(output, "output", "o", "", "Output mode. Use \"-o name\" for shorter output (resource/name).")
}
// AddOutputFlags adds output related flags to a command. // AddOutputFlags adds output related flags to a command.
func AddOutputFlags(cmd *cobra.Command) { func AddOutputFlags(cmd *cobra.Command) {
cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml|wide|name|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See custom columns [http://kubernetes.io/docs/user-guide/kubectl-overview/#custom-columns], golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://kubernetes.io/docs/user-guide/jsonpath].") cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml|wide|name|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See custom columns [http://kubernetes.io/docs/user-guide/kubectl-overview/#custom-columns], golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://kubernetes.io/docs/user-guide/jsonpath].")