Merge pull request #62300 from juanvallejo/jvallejo/wire-print-flags-delete-cmd

Automatic merge from submit-queue (batch tested with PRs 62748, 60536, 62300, 62661, 62731). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

Wire PrintFlags through delete, replace, run commands

**Release note**:
```release-note
NONE
```

Wires PrintFlags through the `delete`, `replace`, and `run` commands.
All three commands grouped in this patch as they depend on DeleteOptions.

~~Tagged as WIP for now, as I still need to update tests.~~

cc @soltysh @deads2k
This commit is contained in:
Kubernetes Submit Queue 2018-04-17 19:53:18 -07:00 committed by GitHub
commit a5958c4e39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 559 additions and 420 deletions

View File

@ -332,6 +332,7 @@ package_group(
name = "pkg_kubectl_validation_CONSUMERS", name = "pkg_kubectl_validation_CONSUMERS",
packages = [ packages = [
"//pkg/kubectl", "//pkg/kubectl",
"//pkg/kubectl/cmd",
"//pkg/kubectl/cmd/testing", "//pkg/kubectl/cmd/testing",
"//pkg/kubectl/cmd/util", "//pkg/kubectl/cmd/util",
"//pkg/kubectl/resource", "//pkg/kubectl/resource",

View File

@ -25,6 +25,7 @@ go_library(
"convert.go", "convert.go",
"cp.go", "cp.go",
"delete.go", "delete.go",
"delete_flags.go",
"describe.go", "describe.go",
"diff.go", "diff.go",
"drain.go", "drain.go",
@ -86,6 +87,7 @@ go_library(
"//pkg/kubectl/util:go_default_library", "//pkg/kubectl/util:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library", "//pkg/kubectl/util/i18n:go_default_library",
"//pkg/kubectl/util/term:go_default_library", "//pkg/kubectl/util/term:go_default_library",
"//pkg/kubectl/validation:go_default_library",
"//pkg/printers:go_default_library", "//pkg/printers:go_default_library",
"//pkg/printers/internalversion:go_default_library", "//pkg/printers/internalversion:go_default_library",
"//pkg/util/interrupt:go_default_library", "//pkg/util/interrupt:go_default_library",

View File

@ -305,7 +305,7 @@ func NewKubectlCommand(f cmdutil.Factory, in io.Reader, out, err io.Writer) *cob
Commands: []*cobra.Command{ Commands: []*cobra.Command{
NewCmdApply("kubectl", f, out, err), NewCmdApply("kubectl", f, out, err),
NewCmdPatch(f, out), NewCmdPatch(f, out),
NewCmdReplace(f, out), NewCmdReplace(f, out, err),
NewCmdConvert(f, out), NewCmdConvert(f, out),
}, },
}, },

View File

@ -151,192 +151,6 @@ func stringBody(body string) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader([]byte(body))) return ioutil.NopCloser(bytes.NewReader([]byte(body)))
} }
func Example_printMultiContainersReplicationControllerWithWide() {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(tf, os.Stdin, os.Stdout, os.Stderr)
ctrl := &api.ReplicationController{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{"foo": "bar"},
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.ReplicationControllerSpec{
Replicas: 1,
Selector: map[string]string{"foo": "bar"},
Template: &api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"foo": "bar"},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "foo",
Image: "someimage",
},
{
Name: "foo2",
Image: "someimage2",
},
},
},
},
},
Status: api.ReplicationControllerStatus{
Replicas: 1,
},
}
cmd.Flags().Set("output", "wide")
err := cmdutil.PrintObject(cmd, ctrl, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
// Output:
// NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
// foo 1 1 0 10y foo,foo2 someimage,someimage2 foo=bar
}
func Example_printReplicationController() {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(tf, os.Stdin, os.Stdout, os.Stderr)
ctrl := &api.ReplicationController{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Labels: map[string]string{"foo": "bar"},
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.ReplicationControllerSpec{
Replicas: 1,
Selector: map[string]string{"foo": "bar"},
Template: &api.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"foo": "bar"},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "foo",
Image: "someimage",
},
{
Name: "foo2",
Image: "someimage",
},
},
},
},
},
Status: api.ReplicationControllerStatus{
Replicas: 1,
},
}
err := cmdutil.PrintObject(cmd, ctrl, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
// Output:
// NAME DESIRED CURRENT READY AGE
// foo 1 1 0 10y
}
func Example_printPodWithWideFormat() {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
nodeName := "kubernetes-node-abcd"
cmd := NewCmdRun(tf, os.Stdin, os.Stdout, os.Stderr)
pod := &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test1",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: "podPhase",
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
PodIP: "10.1.1.3",
},
}
cmd.Flags().Set("output", "wide")
err := cmdutil.PrintObject(cmd, pod, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
// Output:
// NAME READY STATUS RESTARTS AGE IP NODE
// test1 1/2 podPhase 6 10y 10.1.1.3 kubernetes-node-abcd
}
func Example_printPodWithShowLabels() {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
nodeName := "kubernetes-node-abcd"
cmd := NewCmdRun(tf, os.Stdin, os.Stdout, os.Stderr)
pod := &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test1",
CreationTimestamp: metav1.Time{Time: time.Now().AddDate(-10, 0, 0)},
Labels: map[string]string{
"l1": "key",
"l2": "value",
},
},
Spec: api.PodSpec{
Containers: make([]api.Container, 2),
NodeName: nodeName,
},
Status: api.PodStatus{
Phase: "podPhase",
ContainerStatuses: []api.ContainerStatus{
{Ready: true, RestartCount: 3, State: api.ContainerState{Running: &api.ContainerStateRunning{}}},
{RestartCount: 3},
},
},
}
cmd.Flags().Set("show-labels", "true")
err := cmdutil.PrintObject(cmd, pod, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
// Output:
// NAME READY STATUS RESTARTS AGE LABELS
// test1 1/2 podPhase 6 10y l1=key,l2=value
}
func newAllPhasePodList() *api.PodList { func newAllPhasePodList() *api.PodList {
nodeName := "kubernetes-node-abcd" nodeName := "kubernetes-node-abcd"
return &api.PodList{ return &api.PodList{
@ -429,37 +243,6 @@ func newAllPhasePodList() *api.PodList {
} }
} }
func Example_printPodShowTerminated() {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: nil,
}
cmd := NewCmdRun(tf, os.Stdin, os.Stdout, os.Stderr)
podList := newAllPhasePodList()
printer, err := cmdutil.PrinterForOptions(cmdutil.ExtractCmdPrintOptions(cmd, false))
if err != nil {
fmt.Printf("Unexpected printer get error: %v\n", err)
}
for _, pod := range []runtime.Object{podList} {
err := printer.PrintObj(pod, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
}
// Output:
// NAME READY STATUS RESTARTS AGE
// test1 1/2 Pending 6 10y
// test2 1/2 Running 6 10y
// test3 1/2 Succeeded 6 10y
// test4 1/2 Failed 6 10y
// test5 1/2 Unknown 6 10y
}
func Example_printServiceWithLabels() { func Example_printServiceWithLabels() {
tf := cmdtesting.NewTestFactory() tf := cmdtesting.NewTestFactory()
defer tf.Cleanup() defer tf.Cleanup()

View File

@ -98,6 +98,8 @@ type DeleteOptions struct {
ForceDeletion bool ForceDeletion bool
WaitForDeletion bool WaitForDeletion bool
Reaper func(mapping *meta.RESTMapping) (kubectl.Reaper, error)
GracePeriod int GracePeriod int
Timeout time.Duration Timeout time.Duration
@ -107,21 +109,12 @@ type DeleteOptions struct {
Mapper meta.RESTMapper Mapper meta.RESTMapper
Result *resource.Result Result *resource.Result
f cmdutil.Factory
Out io.Writer Out io.Writer
ErrOut io.Writer ErrOut io.Writer
} }
func NewDeleteOptions() *DeleteOptions {
return &DeleteOptions{
Cascade: true,
GracePeriod: -1,
Include3rdParty: true,
}
}
func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
options := NewDeleteOptions() deleteFlags := NewDeleteCommandFlags("containing the resource to delete.")
validArgs := cmdutil.ValidArgList(f) validArgs := cmdutil.ValidArgList(f)
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -131,7 +124,9 @@ func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
Long: delete_long, Long: delete_long,
Example: delete_example, Example: delete_example,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
options := deleteFlags.ToOptions(out, errOut)
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
if err := options.Complete(f, out, errOut, args, cmd); err != nil { if err := options.Complete(f, out, errOut, args, cmd); err != nil {
cmdutil.CheckErr(err) cmdutil.CheckErr(err)
} }
@ -146,18 +141,12 @@ func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
ValidArgs: validArgs, ValidArgs: validArgs,
ArgAliases: kubectl.ResourceAliases(validArgs), ArgAliases: kubectl.ResourceAliases(validArgs),
} }
usage := "containing the resource to delete."
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage) deleteFlags.AddFlags(cmd)
cmd.Flags().StringVarP(&options.Selector, "selector", "l", options.Selector, "Selector (label query) to filter on, not including uninitialized ones.")
cmd.Flags().BoolVar(&options.DeleteAll, "all", options.DeleteAll, "Delete all resources, including uninitialized ones, in the namespace of the specified resource types.") // flag-specific output flag, as this command does not depend on PrintFlags
cmd.Flags().BoolVar(&options.IgnoreNotFound, "ignore-not-found", options.IgnoreNotFound, "Treat \"resource not found\" as a successful delete. Defaults to \"true\" when --all is specified.") cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on, not including uninitialized ones.")
cmd.Flags().BoolVar(&options.Cascade, "cascade", options.Cascade, "If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.")
cmd.Flags().IntVar(&options.GracePeriod, "grace-period", options.GracePeriod, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative. Set to 1 for immediate shutdown. Can only be set to 0 when --force is true (force deletion).")
cmd.Flags().BoolVar(&options.DeleteNow, "now", options.DeleteNow, "If true, resources are signaled for immediate shutdown (same as --grace-period=1).")
cmd.Flags().BoolVar(&options.ForceDeletion, "force", options.ForceDeletion, "Only used when grace-period=0. If true, immediately remove resources from API and bypass graceful deletion. Note that immediate deletion of some resources may result in inconsistency or data loss and requires confirmation.")
cmd.Flags().DurationVar(&options.Timeout, "timeout", options.Timeout, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object")
cmdutil.AddOutputVarFlagsForMutation(cmd, &options.Output)
cmdutil.AddInclude3rdPartyVarFlags(cmd, &options.Include3rdParty)
cmdutil.AddIncludeUninitializedFlag(cmd) cmdutil.AddIncludeUninitializedFlag(cmd)
return cmd return cmd
} }
@ -168,6 +157,9 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args
return err return err
} }
o.Selector = cmdutil.GetFlagString(cmd, "selector")
o.Reaper = f.Reaper
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false) includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
r := f.NewBuilder(). r := f.NewBuilder().
Unstructured(). Unstructured().
@ -187,7 +179,6 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args
o.Result = r o.Result = r
o.Mapper = r.Mapper().RESTMapper o.Mapper = r.Mapper().RESTMapper
o.f = f
// Set up writer // Set up writer
o.Out = out o.Out = out
o.ErrOut = errOut o.ErrOut = errOut
@ -233,17 +224,20 @@ func (o *DeleteOptions) Validate(cmd *cobra.Command) error {
} }
func (o *DeleteOptions) RunDelete() error { 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 o.Cascade { if o.Cascade {
return ReapResult(o.Result, o.f, o.Out, true, o.IgnoreNotFound, o.Timeout, o.GracePeriod, o.WaitForDeletion, shortOutput, false) // TODO(juanvallejo): although o.Result can be accessed from the options
// it is also passed here so that callers of this method outside of the "delete"
// command do not have to tack it to the "delete" options as well.
// Find a cleaner way to approach this.
return o.ReapResult(o.Result, true, false)
} }
return DeleteResult(o.Result, o.Out, o.IgnoreNotFound, o.GracePeriod, shortOutput) return o.DeleteResult(o.Result)
} }
func ReapResult(r *resource.Result, f cmdutil.Factory, out io.Writer, isDefaultDelete, ignoreNotFound bool, timeout time.Duration, gracePeriod int, waitForDeletion, shortOutput bool, quiet bool) error { func (o *DeleteOptions) ReapResult(r *resource.Result, isDefaultDelete, quiet bool) error {
found := 0 found := 0
if ignoreNotFound { if o.IgnoreNotFound {
r = r.IgnoreErrors(errors.IsNotFound) r = r.IgnoreErrors(errors.IsNotFound)
} }
err := r.Visit(func(info *resource.Info, err error) error { err := r.Visit(func(info *resource.Info, err error) error {
@ -251,29 +245,29 @@ func ReapResult(r *resource.Result, f cmdutil.Factory, out io.Writer, isDefaultD
return err return err
} }
found++ found++
reaper, err := f.Reaper(info.Mapping) reaper, err := o.Reaper(info.Mapping)
if err != nil { if err != nil {
// If there is no reaper for this resources and the user didn't explicitly ask for stop. // If there is no reaper for this resources and the user didn't explicitly ask for stop.
if kubectl.IsNoSuchReaperError(err) && isDefaultDelete { if kubectl.IsNoSuchReaperError(err) && isDefaultDelete {
// No client side reaper found. Let the server do cascading deletion. // No client side reaper found. Let the server do cascading deletion.
return cascadingDeleteResource(info, out, shortOutput, gracePeriod) return o.cascadingDeleteResource(info)
} }
return cmdutil.AddSourceToErr("reaping", info.Source, err) return cmdutil.AddSourceToErr("reaping", info.Source, err)
} }
var options *metav1.DeleteOptions var options *metav1.DeleteOptions
if gracePeriod >= 0 { if o.GracePeriod >= 0 {
options = metav1.NewDeleteOptions(int64(gracePeriod)) options = metav1.NewDeleteOptions(int64(o.GracePeriod))
} }
if err := reaper.Stop(info.Namespace, info.Name, timeout, options); err != nil { if err := reaper.Stop(info.Namespace, info.Name, o.Timeout, options); err != nil {
return cmdutil.AddSourceToErr("stopping", info.Source, err) return cmdutil.AddSourceToErr("stopping", info.Source, err)
} }
if waitForDeletion { if o.WaitForDeletion {
if err := waitForObjectDeletion(info, timeout); err != nil { if err := waitForObjectDeletion(info, o.Timeout); err != nil {
return cmdutil.AddSourceToErr("stopping", info.Source, err) return cmdutil.AddSourceToErr("stopping", info.Source, err)
} }
} }
if !quiet { if !quiet {
printDeletion(info, out, shortOutput, gracePeriod) o.PrintObj(info)
} }
return nil return nil
}) })
@ -281,14 +275,14 @@ func ReapResult(r *resource.Result, f cmdutil.Factory, out io.Writer, isDefaultD
return err return err
} }
if found == 0 { if found == 0 {
fmt.Fprintf(out, "No resources found\n") fmt.Fprintf(o.Out, "No resources found\n")
} }
return nil return nil
} }
func DeleteResult(r *resource.Result, out io.Writer, ignoreNotFound bool, gracePeriod int, shortOutput bool) error { func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
found := 0 found := 0
if ignoreNotFound { if o.IgnoreNotFound {
r = r.IgnoreErrors(errors.IsNotFound) r = r.IgnoreErrors(errors.IsNotFound)
} }
err := r.Visit(func(info *resource.Info, err error) error { err := r.Visit(func(info *resource.Info, err error) error {
@ -300,38 +294,38 @@ func DeleteResult(r *resource.Result, out io.Writer, ignoreNotFound bool, graceP
// if we're here, it means that cascade=false (not the default), so we should orphan as requested // if we're here, it means that cascade=false (not the default), so we should orphan as requested
orphan := true orphan := true
options := &metav1.DeleteOptions{} options := &metav1.DeleteOptions{}
if gracePeriod >= 0 { if o.GracePeriod >= 0 {
options = metav1.NewDeleteOptions(int64(gracePeriod)) options = metav1.NewDeleteOptions(int64(o.GracePeriod))
} }
options.OrphanDependents = &orphan options.OrphanDependents = &orphan
return deleteResource(info, out, shortOutput, options, gracePeriod) return o.deleteResource(info, options)
}) })
if err != nil { if err != nil {
return err return err
} }
if found == 0 { if found == 0 {
fmt.Fprintf(out, "No resources found\n") fmt.Fprintf(o.Out, "No resources found\n")
} }
return nil return nil
} }
func cascadingDeleteResource(info *resource.Info, out io.Writer, shortOutput bool, gracePeriod int) error { func (o *DeleteOptions) cascadingDeleteResource(info *resource.Info) error {
falseVar := false falseVar := false
deleteOptions := &metav1.DeleteOptions{OrphanDependents: &falseVar} return o.deleteResource(info, &metav1.DeleteOptions{OrphanDependents: &falseVar})
return deleteResource(info, out, shortOutput, deleteOptions, gracePeriod)
} }
func deleteResource(info *resource.Info, out io.Writer, shortOutput bool, deleteOptions *metav1.DeleteOptions, gracePeriod int) error { func (o *DeleteOptions) deleteResource(info *resource.Info, deleteOptions *metav1.DeleteOptions) error {
if err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, deleteOptions); err != nil { if err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, deleteOptions); err != nil {
return cmdutil.AddSourceToErr("deleting", info.Source, err) return cmdutil.AddSourceToErr("deleting", info.Source, err)
} }
printDeletion(info, out, shortOutput, gracePeriod) o.PrintObj(info)
return nil return nil
} }
// deletion printing is special because they don't have an object to print. This logic mirrors PrintSuccess // deletion printing is special because we do not have an object to print.
func printDeletion(info *resource.Info, out io.Writer, shortOutput bool, gracePeriod int) { // This mirrors name printer behavior
func (o *DeleteOptions) PrintObj(info *resource.Info) {
operation := "deleted" operation := "deleted"
groupKind := info.Mapping.GroupVersionKind groupKind := info.Mapping.GroupVersionKind
kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group) kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group)
@ -339,18 +333,18 @@ func printDeletion(info *resource.Info, out io.Writer, shortOutput bool, gracePe
kindString = strings.ToLower(groupKind.Kind) kindString = strings.ToLower(groupKind.Kind)
} }
if gracePeriod == 0 { if o.GracePeriod == 0 {
operation = "force deleted" operation = "force deleted"
} }
if shortOutput { if o.Output == "name" {
// -o name: prints resource/name // -o name: prints resource/name
fmt.Fprintf(out, "%s/%s\n", kindString, info.Name) fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
return return
} }
// understandable output by default // understandable output by default
fmt.Fprintf(out, "%s \"%s\" %s\n", kindString, info.Name, operation) fmt.Fprintf(o.Out, "%s \"%s\" %s\n", kindString, info.Name, operation)
} }
// objectDeletionWaitInterval is the interval to wait between checks for deletion. // objectDeletionWaitInterval is the interval to wait between checks for deletion.

View File

@ -0,0 +1,214 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"io"
"time"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/resource"
)
type FileNameFlags struct {
Usage string
Filenames *[]string
Recursive *bool
}
func (o *FileNameFlags) ToOptions() resource.FilenameOptions {
options := resource.FilenameOptions{}
if o.Recursive != nil {
options.Recursive = *o.Recursive
}
if o.Filenames != nil {
options.Filenames = *o.Filenames
}
return options
}
func (o *FileNameFlags) AddFlags(cmd *cobra.Command) {
if o.Recursive != nil {
cmd.Flags().BoolVarP(o.Recursive, "recursive", "R", *o.Recursive, "Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.")
}
if o.Filenames != nil {
kubectl.AddJsonFilenameFlag(cmd, o.Filenames, "Filename, directory, or URL to files "+o.Usage)
}
}
// PrintFlags composes common printer flag structs
// used for commands requiring deletion logic.
type DeleteFlags struct {
FileNameFlags *FileNameFlags
All *bool
Cascade *bool
Force *bool
GracePeriod *int
IgnoreNotFound *bool
Now *bool
Timeout *time.Duration
Output *string
IncludeThirdParty *bool
}
func (f *DeleteFlags) ToOptions(out, errOut io.Writer) *DeleteOptions {
options := &DeleteOptions{
Out: out,
ErrOut: errOut,
}
// add filename options
if f.FileNameFlags != nil {
options.FilenameOptions = f.FileNameFlags.ToOptions()
}
// add output format
if f.Output != nil {
options.Output = *f.Output
}
if f.All != nil {
options.DeleteAll = *f.All
}
if f.Cascade != nil {
options.Cascade = *f.Cascade
}
if f.Force != nil {
options.ForceDeletion = *f.Force
}
if f.GracePeriod != nil {
options.GracePeriod = *f.GracePeriod
}
if f.IgnoreNotFound != nil {
options.IgnoreNotFound = *f.IgnoreNotFound
}
if f.Now != nil {
options.DeleteNow = *f.Now
}
if f.Timeout != nil {
options.Timeout = *f.Timeout
}
if f.IncludeThirdParty != nil {
options.Include3rdParty = *f.IncludeThirdParty
}
return options
}
func (f *DeleteFlags) AddFlags(cmd *cobra.Command) {
f.FileNameFlags.AddFlags(cmd)
if f.All != nil {
cmd.Flags().BoolVar(f.All, "all", *f.All, "Delete all resources, including uninitialized ones, in the namespace of the specified resource types.")
}
if f.Force != nil {
cmd.Flags().BoolVar(f.Force, "force", *f.Force, "Only used when grace-period=0. If true, immediately remove resources from API and bypass graceful deletion. Note that immediate deletion of some resources may result in inconsistency or data loss and requires confirmation.")
}
if f.Cascade != nil {
cmd.Flags().BoolVar(f.Cascade, "cascade", *f.Cascade, "If true, cascade the deletion of the resources managed by this resource (e.g. Pods created by a ReplicationController). Default true.")
}
if f.Now != nil {
cmd.Flags().BoolVar(f.Now, "now", *f.Now, "If true, resources are signaled for immediate shutdown (same as --grace-period=1).")
}
if f.GracePeriod != nil {
cmd.Flags().IntVar(f.GracePeriod, "grace-period", *f.GracePeriod, "Period of time in seconds given to the resource to terminate gracefully. Ignored if negative. Set to 1 for immediate shutdown. Can only be set to 0 when --force is true (force deletion).")
}
if f.Timeout != nil {
cmd.Flags().DurationVar(f.Timeout, "timeout", *f.Timeout, "The length of time to wait before giving up on a delete, zero means determine a timeout from the size of the object")
}
if f.IgnoreNotFound != nil {
cmd.Flags().BoolVar(f.IgnoreNotFound, "ignore-not-found", *f.IgnoreNotFound, "Treat \"resource not found\" as a successful delete. Defaults to \"true\" when --all is specified.")
}
if f.Output != nil {
cmd.Flags().StringVarP(f.Output, "output", "o", *f.Output, "Output mode. Use \"-o name\" for shorter output (resource/name).")
}
// TODO: this is deprecated. Remove.
if f.IncludeThirdParty != nil {
cmd.Flags().BoolVar(f.IncludeThirdParty, "include-extended-apis", *f.IncludeThirdParty, "If true, include definitions of new APIs via calls to the API server. [default true]")
cmd.Flags().MarkDeprecated("include-extended-apis", "No longer required.")
}
}
// NewDeleteCommandFlags provides default flags and values for use with the "delete" command
func NewDeleteCommandFlags(usage string) *DeleteFlags {
includeThirdParty := true
cascade := true
gracePeriod := -1
// setup command defaults
all := false
force := false
ignoreNotFound := false
now := false
output := ""
timeout := time.Duration(0)
filenames := []string{}
recursive := false
return &DeleteFlags{
FileNameFlags: &FileNameFlags{Usage: usage, Filenames: &filenames, Recursive: &recursive},
Cascade: &cascade,
GracePeriod: &gracePeriod,
All: &all,
Force: &force,
IgnoreNotFound: &ignoreNotFound,
Now: &now,
Timeout: &timeout,
Output: &output,
IncludeThirdParty: &includeThirdParty,
}
}
// NewDeleteFlags provides default flags and values for use in commands outside of "delete"
func NewDeleteFlags(usage string) *DeleteFlags {
includeThirdParty := true
cascade := true
gracePeriod := -1
force := false
timeout := time.Duration(0)
filenames := []string{}
recursive := false
return &DeleteFlags{
FileNameFlags: &FileNameFlags{Usage: usage, Filenames: &filenames, Recursive: &recursive},
Cascade: &cascade,
GracePeriod: &gracePeriod,
IncludeThirdParty: &includeThirdParty,
// add non-defaults
Force: &force,
Timeout: &timeout,
}
}

View File

@ -44,12 +44,17 @@ import (
var unstructuredSerializer = dynamic.ContentConfig().NegotiatedSerializer var unstructuredSerializer = dynamic.ContentConfig().NegotiatedSerializer
var fakecmd = &cobra.Command{ func fakecmd() *cobra.Command {
Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])", cmd := &cobra.Command{
DisableFlagsInUseLine: true, Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])",
Run: func(cmd *cobra.Command, args []string) { DisableFlagsInUseLine: true,
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd)) Run: func(cmd *cobra.Command, args []string) {
}, cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
},
}
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on, not including uninitialized ones.")
return cmd
} }
func TestDeleteObjectByTuple(t *testing.T) { func TestDeleteObjectByTuple(t *testing.T) {
@ -366,7 +371,7 @@ func TestDeleteObjectNotFound(t *testing.T) {
Cascade: false, Cascade: false,
Output: "name", Output: "name",
} }
err := options.Complete(tf, buf, errBuf, []string{}, fakecmd) err := options.Complete(tf, buf, errBuf, []string{}, fakecmd())
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -448,7 +453,7 @@ func TestDeleteAllNotFound(t *testing.T) {
IgnoreNotFound: false, IgnoreNotFound: false,
Output: "name", Output: "name",
} }
err := options.Complete(tf, buf, errBuf, []string{"services"}, fakecmd) err := options.Complete(tf, buf, errBuf, []string{"services"}, fakecmd())
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -573,7 +578,7 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) {
Cascade: false, Cascade: false,
Output: "name", Output: "name",
} }
err := options.Complete(tf, buf, errBuf, []string{}, fakecmd) err := options.Complete(tf, buf, errBuf, []string{}, fakecmd())
if err != nil { if err != nil {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)
} }
@ -749,7 +754,7 @@ func TestResourceErrors(t *testing.T) {
Cascade: false, Cascade: false,
Output: "name", Output: "name",
} }
err := options.Complete(tf, buf, errBuf, testCase.args, fakecmd) err := options.Complete(tf, buf, errBuf, testCase.args, fakecmd())
if !testCase.errFn(err) { if !testCase.errFn(err) {
t.Errorf("%s: unexpected error: %v", k, err) t.Errorf("%s: unexpected error: %v", k, err)
return return

View File

@ -26,13 +26,17 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/kubectl/validation"
"k8s.io/kubernetes/pkg/printers"
) )
var ( var (
@ -60,8 +64,39 @@ var (
kubectl replace --force -f ./pod.json`)) kubectl replace --force -f ./pod.json`))
) )
func NewCmdReplace(f cmdutil.Factory, out io.Writer) *cobra.Command { type ReplaceOpts struct {
options := &resource.FilenameOptions{} PrintFlags *printers.PrintFlags
DeleteFlags *DeleteFlags
DeleteOptions *DeleteOptions
PrintObj func(obj runtime.Object) error
createAnnotation bool
changeCause string
validate bool
Schema validation.Schema
Builder func() *resource.Builder
BuilderArgs []string
ShouldRecord func(info *resource.Info) bool
Namespace string
EnforceNamespace bool
Out io.Writer
ErrOut io.Writer
}
func NewCmdReplace(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
options := &ReplaceOpts{
PrintFlags: printers.NewPrintFlags("replaced"),
DeleteFlags: NewDeleteFlags("to use to replace the resource."),
Out: out,
ErrOut: errOut,
}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "replace -f FILENAME", Use: "replace -f FILENAME",
@ -71,61 +106,100 @@ func NewCmdReplace(f cmdutil.Factory, out io.Writer) *cobra.Command {
Example: replaceExample, Example: replaceExample,
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 := RunReplace(f, out, cmd, args, options) cmdutil.CheckErr(options.Complete(f, cmd, args))
cmdutil.CheckErr(err) cmdutil.CheckErr(options.Validate(cmd))
cmdutil.CheckErr(options.Run())
}, },
} }
usage := "to use to replace the resource."
cmdutil.AddFilenameOptionFlags(cmd, options, usage) options.PrintFlags.AddFlags(cmd)
options.DeleteFlags.AddFlags(cmd)
cmd.MarkFlagRequired("filename") cmd.MarkFlagRequired("filename")
cmd.Flags().Bool("force", false, "Delete and re-create the specified resource")
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).")
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. Any other values should contain a corresponding time unit (e.g. 1s, 2m, 3h).")
cmdutil.AddValidateFlags(cmd) cmdutil.AddValidateFlags(cmd)
cmdutil.AddOutputFlagsForMutation(cmd)
cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd) cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd return cmd
} }
func RunReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions) error { func (o *ReplaceOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate")) o.validate = cmdutil.GetFlagBool(cmd, "validate")
o.changeCause = f.Command(cmd, false)
o.createAnnotation = cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag)
o.ShouldRecord = func(info *resource.Info) bool {
return cmdutil.ShouldRecord(cmd, info)
}
printer, err := o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = func(obj runtime.Object) error {
return printer.PrintObj(obj, o.Out)
}
deleteOpts := o.DeleteFlags.ToOptions(o.Out, o.ErrOut)
//Replace will create a resource if it doesn't exist already, so ignore not found error
deleteOpts.IgnoreNotFound = true
deleteOpts.Reaper = f.Reaper
if o.PrintFlags.OutputFormat != nil {
deleteOpts.Output = *o.PrintFlags.OutputFormat
}
if deleteOpts.GracePeriod == 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.
deleteOpts.GracePeriod = 1
deleteOpts.WaitForDeletion = true
}
o.DeleteOptions = deleteOpts
schema, err := f.Validator(o.validate)
if err != nil { if err != nil {
return err return err
} }
cmdNamespace, enforceNamespace, err := f.DefaultNamespace() o.Schema = schema
o.Builder = f.NewBuilder
o.BuilderArgs = args
o.Namespace, o.EnforceNamespace, err = f.DefaultNamespace()
if err != nil { if err != nil {
return err return err
} }
force := cmdutil.GetFlagBool(cmd, "force") return nil
if cmdutil.IsFilenameSliceEmpty(options.Filenames) { }
return cmdutil.UsageErrorf(cmd, "Must specify --filename to replace")
}
shortOutput := cmdutil.GetFlagString(cmd, "output") == "name" func (o *ReplaceOpts) Validate(cmd *cobra.Command) error {
if force { if o.DeleteOptions.GracePeriod >= 0 && !o.DeleteOptions.ForceDeletion {
return forceReplace(f, out, cmd, args, shortOutput, options)
}
if cmdutil.GetFlagInt(cmd, "grace-period") >= 0 {
return fmt.Errorf("--grace-period must have --force specified") return fmt.Errorf("--grace-period must have --force specified")
} }
if cmdutil.GetFlagDuration(cmd, "timeout") != 0 { if o.DeleteOptions.Timeout != 0 && !o.DeleteOptions.ForceDeletion {
return fmt.Errorf("--timeout must have --force specified") return fmt.Errorf("--timeout must have --force specified")
} }
r := f.NewBuilder(). if cmdutil.IsFilenameSliceEmpty(o.DeleteOptions.FilenameOptions.Filenames) {
return cmdutil.UsageErrorf(cmd, "Must specify --filename to replace")
}
return nil
}
func (o *ReplaceOpts) Run() error {
if o.DeleteOptions.ForceDeletion {
return o.forceReplace()
}
r := o.Builder().
Unstructured(). Unstructured().
Schema(schema). Schema(o.Schema).
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(o.Namespace).DefaultNamespace().
FilenameParam(enforceNamespace, options). FilenameParam(o.EnforceNamespace, &o.DeleteOptions.FilenameOptions).
Flatten(). Flatten().
Do() Do()
if err := r.Err(); err != nil { if err := r.Err(); err != nil {
@ -137,12 +211,12 @@ func RunReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
return err return err
} }
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, cmdutil.InternalVersionJSONEncoder()); err != nil { if err := kubectl.CreateOrUpdateAnnotation(o.createAnnotation, info, cmdutil.InternalVersionJSONEncoder()); err != nil {
return cmdutil.AddSourceToErr("replacing", info.Source, err) return cmdutil.AddSourceToErr("replacing", info.Source, err)
} }
if cmdutil.ShouldRecord(cmd, info) { if o.ShouldRecord(info) {
if err := cmdutil.RecordChangeCause(info.Object, f.Command(cmd, false)); err != nil { if err := cmdutil.RecordChangeCause(info.Object, o.changeCause); err != nil {
return cmdutil.AddSourceToErr("replacing", info.Source, err) return cmdutil.AddSourceToErr("replacing", info.Source, err)
} }
} }
@ -154,23 +228,12 @@ func RunReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str
} }
info.Refresh(obj, true) info.Refresh(obj, true)
cmdutil.PrintSuccess(shortOutput, out, info.Object, false, "replaced") return o.PrintObj(info.AsVersioned())
return nil
}) })
} }
func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, shortOutput bool, options *resource.FilenameOptions) error { func (o *ReplaceOpts) forceReplace() error {
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate")) for i, filename := range o.DeleteOptions.FilenameOptions.Filenames {
if err != nil {
return err
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
for i, filename := range options.Filenames {
if filename == "-" { if filename == "-" {
tempDir, err := ioutil.TempDir("", "kubectl_replace_") tempDir, err := ioutil.TempDir("", "kubectl_replace_")
if err != nil { if err != nil {
@ -182,42 +245,30 @@ func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s
if err != nil { if err != nil {
return err return err
} }
options.Filenames[i] = tempFilename o.DeleteOptions.FilenameOptions.Filenames[i] = tempFilename
} }
} }
r := f.NewBuilder(). r := o.Builder().
Unstructured(). Unstructured().
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(o.Namespace).DefaultNamespace().
FilenameParam(enforceNamespace, options). ResourceTypeOrNameArgs(false, o.BuilderArgs...).RequireObject(false).
ResourceTypeOrNameArgs(false, args...).RequireObject(false). FilenameParam(o.EnforceNamespace, &o.DeleteOptions.FilenameOptions).
Flatten(). Flatten().
Do() Do()
if err := r.Err(); err != nil { if err := r.Err(); err != nil {
return err return err
} }
//Replace will create a resource if it doesn't exist already, so ignore not found error var err error
ignoreNotFound := true
timeout := cmdutil.GetFlagDuration(cmd, "timeout")
gracePeriod := cmdutil.GetFlagInt(cmd, "grace-period")
waitForDeletion := false
if gracePeriod == 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.
gracePeriod = 1
waitForDeletion = true
}
// 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.DeleteOptions.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.") 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, timeout, gracePeriod, waitForDeletion, shortOutput, false) err = o.DeleteOptions.ReapResult(r, o.DeleteOptions.Cascade, false)
} else { } else {
err = DeleteResult(r, out, ignoreNotFound, gracePeriod, shortOutput) err = o.DeleteOptions.DeleteResult(r)
}
if err != nil {
return err
} }
if timeout == 0 { if timeout == 0 {
@ -239,12 +290,12 @@ func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s
return err return err
} }
r = f.NewBuilder(). r = o.Builder().
Unstructured(). Unstructured().
Schema(schema). Schema(o.Schema).
ContinueOnError(). ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace(). NamespaceParam(o.Namespace).DefaultNamespace().
FilenameParam(enforceNamespace, options). FilenameParam(o.EnforceNamespace, &o.DeleteOptions.FilenameOptions).
Flatten(). Flatten().
Do() Do()
err = r.Err() err = r.Err()
@ -258,12 +309,12 @@ func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s
return err return err
} }
if err := kubectl.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), info, cmdutil.InternalVersionJSONEncoder()); err != nil { if err := kubectl.CreateOrUpdateAnnotation(o.createAnnotation, info, cmdutil.InternalVersionJSONEncoder()); err != nil {
return err return err
} }
if cmdutil.ShouldRecord(cmd, info) { if o.ShouldRecord(info) {
if err := cmdutil.RecordChangeCause(info.Object, f.Command(cmd, false)); err != nil { if err := cmdutil.RecordChangeCause(info.Object, o.changeCause); err != nil {
return cmdutil.AddSourceToErr("replacing", info.Source, err) return cmdutil.AddSourceToErr("replacing", info.Source, err)
} }
} }
@ -275,8 +326,7 @@ func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s
count++ count++
info.Refresh(obj, true) info.Refresh(obj, true)
cmdutil.PrintSuccess(shortOutput, out, info.Object, false, "replaced") return o.PrintObj(info.AsVersioned())
return nil
}) })
if err != nil { if err != nil {
return err return err

View File

@ -65,7 +65,7 @@ func TestReplaceObject(t *testing.T) {
tf.Namespace = "test" tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdReplace(tf, buf) cmd := NewCmdReplace(tf, buf, buf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("output", "name") cmd.Flags().Set("output", "name")
cmd.Run(cmd, []string{}) cmd.Run(cmd, []string{})
@ -136,7 +136,7 @@ func TestReplaceMultipleObject(t *testing.T) {
tf.Namespace = "test" tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdReplace(tf, buf) cmd := NewCmdReplace(tf, buf, buf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml") cmd.Flags().Set("filename", "../../../examples/guestbook/frontend-service.yaml")
cmd.Flags().Set("output", "name") cmd.Flags().Set("output", "name")
@ -194,7 +194,7 @@ func TestReplaceDirectory(t *testing.T) {
tf.Namespace = "test" tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdReplace(tf, buf) cmd := NewCmdReplace(tf, buf, buf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy") cmd.Flags().Set("filename", "../../../examples/guestbook/legacy")
cmd.Flags().Set("namespace", "test") cmd.Flags().Set("namespace", "test")
cmd.Flags().Set("output", "name") cmd.Flags().Set("output", "name")
@ -241,7 +241,7 @@ func TestForceReplaceObjectNotFound(t *testing.T) {
tf.Namespace = "test" tf.Namespace = "test"
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
cmd := NewCmdReplace(tf, buf) cmd := NewCmdReplace(tf, buf, buf)
cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml") cmd.Flags().Set("filename", "../../../examples/guestbook/legacy/redis-master-controller.yaml")
cmd.Flags().Set("force", "true") cmd.Flags().Set("force", "true")
cmd.Flags().Set("cascade", "false") cmd.Flags().Set("cascade", "false")

View File

@ -90,7 +90,40 @@ type RunObject struct {
Mapping *meta.RESTMapping Mapping *meta.RESTMapping
} }
type RunOpts struct {
DeleteFlags *DeleteFlags
DeleteOptions *DeleteOptions
DryRun bool
ArgsLenAtDash int
Attach bool
Expose bool
Generator string
Image string
Interactive bool
LeaveStdinOpen bool
Port string
Quiet bool
Record bool
Schedule string
TTY bool
In io.Reader
Out io.Writer
ErrOut io.Writer
}
func NewCmdRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command { func NewCmdRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command {
options := &RunOpts{
DeleteFlags: NewDeleteFlags("to use to replace the resource."),
In: cmdIn,
Out: cmdOut,
ErrOut: cmdErr,
}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "run NAME --image=image [--env=\"key=value\"] [--port=port] [--replicas=replicas] [--dry-run=bool] [--overrides=inline-json] [--command] -- [COMMAND] [args...]", Use: "run NAME --image=image [--env=\"key=value\"] [--port=port] [--replicas=replicas] [--dry-run=bool] [--overrides=inline-json] [--command] -- [COMMAND] [args...]",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
@ -98,16 +131,17 @@ func NewCmdRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *co
Long: runLong, Long: runLong,
Example: runExample, Example: runExample,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
argsLenAtDash := cmd.ArgsLenAtDash() cmdutil.CheckErr(options.Complete(f, cmd))
err := RunRun(f, cmdIn, cmdOut, cmdErr, cmd, args, argsLenAtDash) cmdutil.CheckErr(options.Run(f, cmd, args))
cmdutil.CheckErr(err)
}, },
} }
options.DeleteFlags.AddFlags(cmd)
cmdutil.AddPrinterFlags(cmd) cmdutil.AddPrinterFlags(cmd)
addRunFlags(cmd) addRunFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd) cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodAttachTimeout) cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodAttachTimeout)
return cmd return cmd
} }
@ -141,9 +175,40 @@ func addRunFlags(cmd *cobra.Command) {
cmd.Flags().String("schedule", "", i18n.T("A schedule in the Cron format the job should be run with.")) cmd.Flags().String("schedule", "", i18n.T("A schedule in the Cron format the job should be run with."))
} }
func RunRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cobra.Command, args []string, argsLenAtDash int) error { func (o *RunOpts) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
o.ArgsLenAtDash = cmd.ArgsLenAtDash()
o.DryRun = cmdutil.GetFlagBool(cmd, "dry-run")
o.Expose = cmdutil.GetFlagBool(cmd, "expose")
o.Generator = cmdutil.GetFlagString(cmd, "generator")
o.Image = cmdutil.GetFlagString(cmd, "image")
o.Interactive = cmdutil.GetFlagBool(cmd, "stdin")
o.LeaveStdinOpen = cmdutil.GetFlagBool(cmd, "leave-stdin-open")
o.Port = cmdutil.GetFlagString(cmd, "port")
o.Quiet = cmdutil.GetFlagBool(cmd, "quiet")
o.Record = cmdutil.GetRecordFlag(cmd)
o.Schedule = cmdutil.GetFlagString(cmd, "schedule")
o.TTY = cmdutil.GetFlagBool(cmd, "tty")
attachFlag := cmd.Flags().Lookup("attach")
o.Attach = cmdutil.GetFlagBool(cmd, "attach")
if !attachFlag.Changed && o.Interactive {
o.Attach = true
}
deleteOpts := o.DeleteFlags.ToOptions(o.Out, o.ErrOut)
deleteOpts.IgnoreNotFound = true
deleteOpts.WaitForDeletion = false
deleteOpts.GracePeriod = -1
deleteOpts.Reaper = f.Reaper
o.DeleteOptions = deleteOpts
return nil
}
func (o *RunOpts) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
// Let kubectl run follow rules for `--`, see #13004 issue // Let kubectl run follow rules for `--`, see #13004 issue
if len(args) == 0 || argsLenAtDash == 0 { if len(args) == 0 || o.ArgsLenAtDash == 0 {
return cmdutil.UsageErrorf(cmd, "NAME is required for run") return cmdutil.UsageErrorf(cmd, "NAME is required for run")
} }
@ -153,7 +218,7 @@ func RunRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *c
} }
// validate image name // validate image name
imageName := cmdutil.GetFlagString(cmd, "image") imageName := o.Image
if imageName == "" { if imageName == "" {
return fmt.Errorf("--image is required") return fmt.Errorf("--image is required")
} }
@ -162,16 +227,14 @@ func RunRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *c
return fmt.Errorf("Invalid image name %q: %v", imageName, reference.ErrReferenceInvalidFormat) return fmt.Errorf("Invalid image name %q: %v", imageName, reference.ErrReferenceInvalidFormat)
} }
interactive := cmdutil.GetFlagBool(cmd, "stdin") if o.TTY && !o.Interactive {
tty := cmdutil.GetFlagBool(cmd, "tty")
if tty && !interactive {
return cmdutil.UsageErrorf(cmd, "-i/--stdin is required for containers with -t/--tty=true") return cmdutil.UsageErrorf(cmd, "-i/--stdin is required for containers with -t/--tty=true")
} }
replicas := cmdutil.GetFlagInt(cmd, "replicas") replicas := cmdutil.GetFlagInt(cmd, "replicas")
if interactive && replicas != 1 { if o.Interactive && replicas != 1 {
return cmdutil.UsageErrorf(cmd, "-i/--stdin requires that replicas is 1, found %d", replicas) return cmdutil.UsageErrorf(cmd, "-i/--stdin requires that replicas is 1, found %d", replicas)
} }
if cmdutil.GetFlagBool(cmd, "expose") && len(cmdutil.GetFlagString(cmd, "port")) == 0 { if o.Expose && len(o.Port) == 0 {
return cmdutil.UsageErrorf(cmd, "--port must be set when exposing a service") return cmdutil.UsageErrorf(cmd, "--port must be set when exposing a service")
} }
@ -179,7 +242,7 @@ func RunRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *c
if err != nil { if err != nil {
return err return err
} }
restartPolicy, err := getRestartPolicy(cmd, interactive) restartPolicy, err := getRestartPolicy(cmd, o.Interactive)
if err != nil { if err != nil {
return err return err
} }
@ -187,19 +250,12 @@ func RunRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *c
return cmdutil.UsageErrorf(cmd, "--restart=%s requires that --replicas=1, found %d", restartPolicy, replicas) return cmdutil.UsageErrorf(cmd, "--restart=%s requires that --replicas=1, found %d", restartPolicy, replicas)
} }
attachFlag := cmd.Flags().Lookup("attach")
attach := cmdutil.GetFlagBool(cmd, "attach")
if !attachFlag.Changed && interactive {
attach = true
}
remove := cmdutil.GetFlagBool(cmd, "rm") remove := cmdutil.GetFlagBool(cmd, "rm")
if !attach && remove { if !o.Attach && remove {
return cmdutil.UsageErrorf(cmd, "--rm should only be used for attached containers") return cmdutil.UsageErrorf(cmd, "--rm should only be used for attached containers")
} }
if attach && cmdutil.GetDryRunFlag(cmd) { if o.Attach && o.DryRun {
return cmdutil.UsageErrorf(cmd, "--dry-run can't be used with attached containers options (--attach, --stdin, or --tty)") return cmdutil.UsageErrorf(cmd, "--dry-run can't be used with attached containers options (--attach, --stdin, or --tty)")
} }
@ -212,8 +268,8 @@ func RunRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *c
return err return err
} }
generatorName := cmdutil.GetFlagString(cmd, "generator") generatorName := o.Generator
schedule := cmdutil.GetFlagString(cmd, "schedule") schedule := o.Schedule
if len(schedule) != 0 && len(generatorName) == 0 { if len(schedule) != 0 && len(generatorName) == 0 {
generatorName = cmdutil.CronJobV1Beta1GeneratorName generatorName = cmdutil.CronJobV1Beta1GeneratorName
} }
@ -228,12 +284,12 @@ func RunRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *c
} }
// Falling back because the generator was not provided and the default one could be unavailable. // Falling back because the generator was not provided and the default one could be unavailable.
generatorNameTemp, err := cmdutil.FallbackGeneratorNameIfNecessary(generatorName, clientset.Discovery(), cmdErr) generatorNameTemp, err := cmdutil.FallbackGeneratorNameIfNecessary(generatorName, clientset.Discovery(), o.ErrOut)
if err != nil { if err != nil {
return err return err
} }
if generatorNameTemp != generatorName { if generatorNameTemp != generatorName {
cmdutil.Warning(cmdErr, generatorName, generatorNameTemp) cmdutil.Warning(o.ErrOut, generatorName, generatorNameTemp)
} else { } else {
generatorName = generatorNameTemp generatorName = generatorNameTemp
} }
@ -254,19 +310,19 @@ func RunRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *c
params["env"] = cmdutil.GetFlagStringArray(cmd, "env") params["env"] = cmdutil.GetFlagStringArray(cmd, "env")
var createdObjects = []*RunObject{} var createdObjects = []*RunObject{}
runObject, err := createGeneratedObject(f, cmd, generator, names, params, cmdutil.GetFlagString(cmd, "overrides"), namespace) runObject, err := o.createGeneratedObject(f, cmd, generator, names, params, cmdutil.GetFlagString(cmd, "overrides"), namespace)
if err != nil { if err != nil {
return err return err
} else { } else {
createdObjects = append(createdObjects, runObject) createdObjects = append(createdObjects, runObject)
} }
allErrs := []error{} allErrs := []error{}
if cmdutil.GetFlagBool(cmd, "expose") { if o.Expose {
serviceGenerator := cmdutil.GetFlagString(cmd, "service-generator") serviceGenerator := cmdutil.GetFlagString(cmd, "service-generator")
if len(serviceGenerator) == 0 { if len(serviceGenerator) == 0 {
return cmdutil.UsageErrorf(cmd, "No service generator specified") return cmdutil.UsageErrorf(cmd, "No service generator specified")
} }
serviceRunObject, err := generateService(f, cmd, args, serviceGenerator, params, namespace, cmdOut) serviceRunObject, err := o.generateService(f, cmd, serviceGenerator, params, namespace)
if err != nil { if err != nil {
allErrs = append(allErrs, err) allErrs = append(allErrs, err)
} else { } else {
@ -274,20 +330,19 @@ func RunRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *c
} }
} }
if attach { if o.Attach {
if remove { if remove {
defer removeCreatedObjects(f, createdObjects, cmdOut) defer o.removeCreatedObjects(f, createdObjects)
} }
quiet := cmdutil.GetFlagBool(cmd, "quiet")
opts := &AttachOptions{ opts := &AttachOptions{
StreamOptions: StreamOptions{ StreamOptions: StreamOptions{
In: cmdIn, In: o.In,
Out: cmdOut, Out: o.Out,
Err: cmdErr, Err: o.ErrOut,
Stdin: interactive, Stdin: o.Interactive,
TTY: tty, TTY: o.TTY,
Quiet: quiet, Quiet: o.Quiet,
}, },
GetPodTimeout: timeout, GetPodTimeout: timeout,
CommandName: cmd.Parent().CommandPath() + " attach", CommandName: cmd.Parent().CommandPath() + " attach",
@ -316,7 +371,7 @@ func RunRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *c
} }
var pod *api.Pod var pod *api.Pod
leaveStdinOpen := cmdutil.GetFlagBool(cmd, "leave-stdin-open") leaveStdinOpen := o.LeaveStdinOpen
waitForExitCode := !leaveStdinOpen && restartPolicy == api.RestartPolicyNever waitForExitCode := !leaveStdinOpen && restartPolicy == api.RestartPolicyNever
if waitForExitCode { if waitForExitCode {
pod, err = waitForPod(clientset.Core(), attachablePod.Namespace, attachablePod.Name, kubectl.PodCompleted) pod, err = waitForPod(clientset.Core(), attachablePod.Namespace, attachablePod.Name, kubectl.PodCompleted)
@ -355,15 +410,15 @@ func RunRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *c
if runObject != nil { if runObject != nil {
outputFormat := cmdutil.GetFlagString(cmd, "output") outputFormat := cmdutil.GetFlagString(cmd, "output")
if outputFormat != "" || cmdutil.GetDryRunFlag(cmd) { if outputFormat != "" || cmdutil.GetDryRunFlag(cmd) {
return cmdutil.PrintObject(cmd, runObject.Object, cmdOut) return cmdutil.PrintObject(cmd, runObject.Object, o.Out)
} }
cmdutil.PrintSuccess(false, cmdOut, runObject.Object, cmdutil.GetDryRunFlag(cmd), "created") cmdutil.PrintSuccess(false, o.Out, runObject.Object, cmdutil.GetDryRunFlag(cmd), "created")
} }
return utilerrors.NewAggregate(allErrs) return utilerrors.NewAggregate(allErrs)
} }
func removeCreatedObjects(f cmdutil.Factory, createdObjects []*RunObject, cmdOut io.Writer) error { func (o *RunOpts) removeCreatedObjects(f cmdutil.Factory, createdObjects []*RunObject) error {
for _, obj := range createdObjects { for _, obj := range createdObjects {
namespace, err := obj.Mapping.MetadataAccessor.Namespace(obj.Object) namespace, err := obj.Mapping.MetadataAccessor.Namespace(obj.Object)
if err != nil { if err != nil {
@ -387,7 +442,7 @@ func removeCreatedObjects(f cmdutil.Factory, createdObjects []*RunObject, cmdOut
// asked for us to remove the pod (via --rm) then telling them // asked for us to remove the pod (via --rm) then telling them
// its been deleted is unnecessary since that's what they asked // its been deleted is unnecessary since that's what they asked
// for. We should only print something if the "rm" fails. // for. We should only print something if the "rm" fails.
err = ReapResult(r, f, cmdOut, true, true, 0, -1, false, false, true) err = o.DeleteOptions.ReapResult(r, true, true)
if err != nil { if err != nil {
return err return err
} }
@ -502,7 +557,7 @@ func verifyImagePullPolicy(cmd *cobra.Command) error {
return cmdutil.UsageErrorf(cmd, "invalid image pull policy: %s", pullPolicy) return cmdutil.UsageErrorf(cmd, "invalid image pull policy: %s", pullPolicy)
} }
func generateService(f cmdutil.Factory, cmd *cobra.Command, args []string, serviceGenerator string, paramsIn map[string]interface{}, namespace string, out io.Writer) (*RunObject, error) { func (o *RunOpts) generateService(f cmdutil.Factory, cmd *cobra.Command, serviceGenerator string, paramsIn map[string]interface{}, namespace string) (*RunObject, error) {
generators := f.Generators("expose") generators := f.Generators("expose")
generator, found := generators[serviceGenerator] generator, found := generators[serviceGenerator]
if !found { if !found {
@ -532,27 +587,27 @@ func generateService(f cmdutil.Factory, cmd *cobra.Command, args []string, servi
params["default-name"] = name params["default-name"] = name
} }
runObject, err := createGeneratedObject(f, cmd, generator, names, params, cmdutil.GetFlagString(cmd, "service-overrides"), namespace) runObject, err := o.createGeneratedObject(f, cmd, generator, names, params, cmdutil.GetFlagString(cmd, "service-overrides"), namespace)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if cmdutil.GetFlagString(cmd, "output") != "" || cmdutil.GetDryRunFlag(cmd) { if cmdutil.GetFlagString(cmd, "output") != "" || cmdutil.GetDryRunFlag(cmd) {
err := cmdutil.PrintObject(cmd, runObject.Object, out) err := cmdutil.PrintObject(cmd, runObject.Object, o.Out)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if cmdutil.GetFlagString(cmd, "output") == "yaml" { if cmdutil.GetFlagString(cmd, "output") == "yaml" {
fmt.Fprintln(out, "---") fmt.Fprintln(o.Out, "---")
} }
return runObject, nil return runObject, nil
} }
cmdutil.PrintSuccess(false, out, runObject.Object, cmdutil.GetDryRunFlag(cmd), "created") cmdutil.PrintSuccess(false, o.Out, runObject.Object, cmdutil.GetDryRunFlag(cmd), "created")
return runObject, nil return runObject, nil
} }
func createGeneratedObject(f cmdutil.Factory, cmd *cobra.Command, generator kubectl.Generator, names []kubectl.GeneratorParam, params map[string]interface{}, overrides, namespace string) (*RunObject, error) { func (o *RunOpts) createGeneratedObject(f cmdutil.Factory, cmd *cobra.Command, generator kubectl.Generator, names []kubectl.GeneratorParam, params map[string]interface{}, overrides, namespace string) (*RunObject, error) {
err := kubectl.ValidateParams(names, params) err := kubectl.ValidateParams(names, params)
if err != nil { if err != nil {
return nil, err return nil, err
@ -592,12 +647,13 @@ func createGeneratedObject(f cmdutil.Factory, cmd *cobra.Command, generator kube
if err != nil { if err != nil {
return nil, err return nil, err
} }
if cmdutil.GetRecordFlag(cmd) || len(annotations[kubectl.ChangeCauseAnnotation]) > 0 { if o.Record || len(annotations[kubectl.ChangeCauseAnnotation]) > 0 {
if err := cmdutil.RecordChangeCause(obj, f.Command(cmd, false)); err != nil { if err := cmdutil.RecordChangeCause(obj, f.Command(cmd, false)); err != nil {
return nil, err return nil, err
} }
} }
if !cmdutil.GetDryRunFlag(cmd) {
if !o.DryRun {
resourceMapper := &resource.Mapper{ resourceMapper := &resource.Mapper{
ObjectTyper: typer, ObjectTyper: typer,
RESTMapper: mapper, RESTMapper: mapper,

View File

@ -188,12 +188,29 @@ func TestRunArgsFollowDashRules(t *testing.T) {
}, nil }, nil
}), }),
} }
tf.Namespace = "test" tf.Namespace = "test"
tf.ClientConfigVal = &restclient.Config{} tf.ClientConfigVal = &restclient.Config{}
cmd := NewCmdRun(tf, os.Stdin, os.Stdout, os.Stderr) cmd := NewCmdRun(tf, os.Stdin, os.Stdout, os.Stderr)
cmd.Flags().Set("image", "nginx") cmd.Flags().Set("image", "nginx")
cmd.Flags().Set("generator", "run/v1") cmd.Flags().Set("generator", "run/v1")
err := RunRun(tf, os.Stdin, os.Stdout, os.Stderr, cmd, test.args, test.argsLenAtDash)
deleteFlags := NewDeleteFlags("to use to replace the resource.")
opts := &RunOpts{
DeleteOptions: deleteFlags.ToOptions(os.Stdout, os.Stderr),
In: os.Stdin,
Out: os.Stdout,
ErrOut: os.Stderr,
Image: "nginx",
Generator: "run/v1",
ArgsLenAtDash: test.argsLenAtDash,
}
err := opts.Run(tf, cmd, test.args)
if test.expectError && err == nil { if test.expectError && err == nil {
t.Errorf("unexpected non-error (%s)", test.name) t.Errorf("unexpected non-error (%s)", test.name)
} }
@ -335,6 +352,19 @@ func TestGenerateService(t *testing.T) {
} }
}), }),
} }
buff := &bytes.Buffer{}
deleteFlags := NewDeleteFlags("to use to replace the resource.")
opts := &RunOpts{
DeleteOptions: deleteFlags.ToOptions(os.Stdout, os.Stderr),
Out: buff,
ErrOut: buff,
Port: test.port,
Record: false,
}
cmd := &cobra.Command{} cmd := &cobra.Command{}
cmd.Flags().Bool(cmdutil.ApplyAnnotationsFlag, false, "") cmd.Flags().Bool(cmdutil.ApplyAnnotationsFlag, false, "")
cmd.Flags().Bool("record", false, "Record current kubectl command in the resource annotation. If set to false, do not record the command. If set to true, record the command. If not set, default to updating the existing annotation value only if one already exists.") cmd.Flags().Bool("record", false, "Record current kubectl command in the resource annotation. If set to false, do not record the command. If set to true, record the command. If not set, default to updating the existing annotation value only if one already exists.")
@ -343,7 +373,7 @@ func TestGenerateService(t *testing.T) {
addRunFlags(cmd) addRunFlags(cmd)
if !test.expectPOST { if !test.expectPOST {
cmd.Flags().Set("dry-run", "true") opts.DryRun = true
} }
if len(test.port) > 0 { if len(test.port) > 0 {
@ -351,8 +381,7 @@ func TestGenerateService(t *testing.T) {
test.params["port"] = test.port test.params["port"] = test.port
} }
buff := &bytes.Buffer{} _, err := opts.generateService(tf, cmd, test.serviceGenerator, test.params, "namespace")
_, err := generateService(tf, cmd, test.args, test.serviceGenerator, test.params, "namespace", buff)
if test.expectErr { if test.expectErr {
if err == nil { if err == nil {
t.Error("unexpected non-error") t.Error("unexpected non-error")
@ -473,7 +502,12 @@ func TestRunValidations(t *testing.T) {
for flagName, flagValue := range test.flags { for flagName, flagValue := range test.flags {
cmd.Flags().Set(flagName, flagValue) cmd.Flags().Set(flagName, flagValue)
} }
err := RunRun(tf, inBuf, outBuf, errBuf, cmd, test.args, cmd.ArgsLenAtDash()) cmd.Run(cmd, test.args)
var err error
if errBuf.Len() > 0 {
err = fmt.Errorf("%v", errBuf.String())
}
if err != nil && len(test.expectedErr) > 0 { if err != nil && len(test.expectedErr) > 0 {
if !strings.Contains(err.Error(), test.expectedErr) { if !strings.Contains(err.Error(), test.expectedErr) {
t.Errorf("unexpected error: %v", err) t.Errorf("unexpected error: %v", err)