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
pull/8/head
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",
packages = [
"//pkg/kubectl",
"//pkg/kubectl/cmd",
"//pkg/kubectl/cmd/testing",
"//pkg/kubectl/cmd/util",
"//pkg/kubectl/resource",

View File

@ -25,6 +25,7 @@ go_library(
"convert.go",
"cp.go",
"delete.go",
"delete_flags.go",
"describe.go",
"diff.go",
"drain.go",
@ -86,6 +87,7 @@ go_library(
"//pkg/kubectl/util:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library",
"//pkg/kubectl/util/term:go_default_library",
"//pkg/kubectl/validation:go_default_library",
"//pkg/printers:go_default_library",
"//pkg/printers/internalversion: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{
NewCmdApply("kubectl", f, out, err),
NewCmdPatch(f, out),
NewCmdReplace(f, out),
NewCmdReplace(f, out, err),
NewCmdConvert(f, out),
},
},

View File

@ -151,192 +151,6 @@ func stringBody(body string) io.ReadCloser {
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 {
nodeName := "kubernetes-node-abcd"
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() {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()

View File

@ -98,6 +98,8 @@ type DeleteOptions struct {
ForceDeletion bool
WaitForDeletion bool
Reaper func(mapping *meta.RESTMapping) (kubectl.Reaper, error)
GracePeriod int
Timeout time.Duration
@ -107,21 +109,12 @@ type DeleteOptions struct {
Mapper meta.RESTMapper
Result *resource.Result
f cmdutil.Factory
Out 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 {
options := NewDeleteOptions()
deleteFlags := NewDeleteCommandFlags("containing the resource to delete.")
validArgs := cmdutil.ValidArgList(f)
cmd := &cobra.Command{
@ -131,7 +124,9 @@ func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
Long: delete_long,
Example: delete_example,
Run: func(cmd *cobra.Command, args []string) {
options := deleteFlags.ToOptions(out, errOut)
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
if err := options.Complete(f, out, errOut, args, cmd); err != nil {
cmdutil.CheckErr(err)
}
@ -146,18 +141,12 @@ func NewCmdDelete(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
ValidArgs: validArgs,
ArgAliases: kubectl.ResourceAliases(validArgs),
}
usage := "containing the resource to delete."
cmdutil.AddFilenameOptionFlags(cmd, &options.FilenameOptions, usage)
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.")
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().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)
deleteFlags.AddFlags(cmd)
// flag-specific output flag, as this command does not depend on PrintFlags
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on, not including uninitialized ones.")
cmdutil.AddIncludeUninitializedFlag(cmd)
return cmd
}
@ -168,6 +157,9 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args
return err
}
o.Selector = cmdutil.GetFlagString(cmd, "selector")
o.Reaper = f.Reaper
includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
r := f.NewBuilder().
Unstructured().
@ -187,7 +179,6 @@ func (o *DeleteOptions) Complete(f cmdutil.Factory, out, errOut io.Writer, args
o.Result = r
o.Mapper = r.Mapper().RESTMapper
o.f = f
// Set up writer
o.Out = out
o.ErrOut = errOut
@ -233,17 +224,20 @@ func (o *DeleteOptions) Validate(cmd *cobra.Command) error {
}
func (o *DeleteOptions) RunDelete() error {
shortOutput := o.Output == "name"
// By default use a reaper to delete all related resources.
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
if ignoreNotFound {
if o.IgnoreNotFound {
r = r.IgnoreErrors(errors.IsNotFound)
}
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
}
found++
reaper, err := f.Reaper(info.Mapping)
reaper, err := o.Reaper(info.Mapping)
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 {
// 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)
}
var options *metav1.DeleteOptions
if gracePeriod >= 0 {
options = metav1.NewDeleteOptions(int64(gracePeriod))
if o.GracePeriod >= 0 {
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)
}
if waitForDeletion {
if err := waitForObjectDeletion(info, timeout); err != nil {
if o.WaitForDeletion {
if err := waitForObjectDeletion(info, o.Timeout); err != nil {
return cmdutil.AddSourceToErr("stopping", info.Source, err)
}
}
if !quiet {
printDeletion(info, out, shortOutput, gracePeriod)
o.PrintObj(info)
}
return nil
})
@ -281,14 +275,14 @@ func ReapResult(r *resource.Result, f cmdutil.Factory, out io.Writer, isDefaultD
return err
}
if found == 0 {
fmt.Fprintf(out, "No resources found\n")
fmt.Fprintf(o.Out, "No resources found\n")
}
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
if ignoreNotFound {
if o.IgnoreNotFound {
r = r.IgnoreErrors(errors.IsNotFound)
}
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
orphan := true
options := &metav1.DeleteOptions{}
if gracePeriod >= 0 {
options = metav1.NewDeleteOptions(int64(gracePeriod))
if o.GracePeriod >= 0 {
options = metav1.NewDeleteOptions(int64(o.GracePeriod))
}
options.OrphanDependents = &orphan
return deleteResource(info, out, shortOutput, options, gracePeriod)
return o.deleteResource(info, options)
})
if err != nil {
return err
}
if found == 0 {
fmt.Fprintf(out, "No resources found\n")
fmt.Fprintf(o.Out, "No resources found\n")
}
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
deleteOptions := &metav1.DeleteOptions{OrphanDependents: &falseVar}
return deleteResource(info, out, shortOutput, deleteOptions, gracePeriod)
return o.deleteResource(info, &metav1.DeleteOptions{OrphanDependents: &falseVar})
}
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 {
return cmdutil.AddSourceToErr("deleting", info.Source, err)
}
printDeletion(info, out, shortOutput, gracePeriod)
o.PrintObj(info)
return nil
}
// deletion printing is special because they don't have an object to print. This logic mirrors PrintSuccess
func printDeletion(info *resource.Info, out io.Writer, shortOutput bool, gracePeriod int) {
// deletion printing is special because we do not have an object to print.
// This mirrors name printer behavior
func (o *DeleteOptions) PrintObj(info *resource.Info) {
operation := "deleted"
groupKind := info.Mapping.GroupVersionKind
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)
}
if gracePeriod == 0 {
if o.GracePeriod == 0 {
operation = "force deleted"
}
if shortOutput {
if o.Output == "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
}
// 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.

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 fakecmd = &cobra.Command{
Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])",
DisableFlagsInUseLine: true,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
},
func fakecmd() *cobra.Command {
cmd := &cobra.Command{
Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])",
DisableFlagsInUseLine: true,
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) {
@ -366,7 +371,7 @@ func TestDeleteObjectNotFound(t *testing.T) {
Cascade: false,
Output: "name",
}
err := options.Complete(tf, buf, errBuf, []string{}, fakecmd)
err := options.Complete(tf, buf, errBuf, []string{}, fakecmd())
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -448,7 +453,7 @@ func TestDeleteAllNotFound(t *testing.T) {
IgnoreNotFound: false,
Output: "name",
}
err := options.Complete(tf, buf, errBuf, []string{"services"}, fakecmd)
err := options.Complete(tf, buf, errBuf, []string{"services"}, fakecmd())
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -573,7 +578,7 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) {
Cascade: false,
Output: "name",
}
err := options.Complete(tf, buf, errBuf, []string{}, fakecmd)
err := options.Complete(tf, buf, errBuf, []string{}, fakecmd())
if err != nil {
t.Errorf("unexpected error: %v", err)
}
@ -749,7 +754,7 @@ func TestResourceErrors(t *testing.T) {
Cascade: false,
Output: "name",
}
err := options.Complete(tf, buf, errBuf, testCase.args, fakecmd)
err := options.Complete(tf, buf, errBuf, testCase.args, fakecmd())
if !testCase.errFn(err) {
t.Errorf("%s: unexpected error: %v", k, err)
return

View File

@ -26,13 +26,17 @@ import (
"github.com/spf13/cobra"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/kubectl/validation"
"k8s.io/kubernetes/pkg/printers"
)
var (
@ -60,8 +64,39 @@ var (
kubectl replace --force -f ./pod.json`))
)
func NewCmdReplace(f cmdutil.Factory, out io.Writer) *cobra.Command {
options := &resource.FilenameOptions{}
type ReplaceOpts struct {
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{
Use: "replace -f FILENAME",
@ -71,61 +106,100 @@ func NewCmdReplace(f cmdutil.Factory, out io.Writer) *cobra.Command {
Example: replaceExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(cmdutil.ValidateOutputArgs(cmd))
err := RunReplace(f, out, cmd, args, options)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd, args))
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.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.AddOutputFlagsForMutation(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
return cmd
}
func RunReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, options *resource.FilenameOptions) error {
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
func (o *ReplaceOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
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 {
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 {
return err
}
force := cmdutil.GetFlagBool(cmd, "force")
if cmdutil.IsFilenameSliceEmpty(options.Filenames) {
return cmdutil.UsageErrorf(cmd, "Must specify --filename to replace")
}
return nil
}
shortOutput := cmdutil.GetFlagString(cmd, "output") == "name"
if force {
return forceReplace(f, out, cmd, args, shortOutput, options)
}
if cmdutil.GetFlagInt(cmd, "grace-period") >= 0 {
func (o *ReplaceOpts) Validate(cmd *cobra.Command) error {
if o.DeleteOptions.GracePeriod >= 0 && !o.DeleteOptions.ForceDeletion {
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")
}
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().
Schema(schema).
Schema(o.Schema).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, options).
NamespaceParam(o.Namespace).DefaultNamespace().
FilenameParam(o.EnforceNamespace, &o.DeleteOptions.FilenameOptions).
Flatten().
Do()
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
}
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)
}
if cmdutil.ShouldRecord(cmd, info) {
if err := cmdutil.RecordChangeCause(info.Object, f.Command(cmd, false)); err != nil {
if o.ShouldRecord(info) {
if err := cmdutil.RecordChangeCause(info.Object, o.changeCause); err != nil {
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)
cmdutil.PrintSuccess(shortOutput, out, info.Object, false, "replaced")
return nil
return o.PrintObj(info.AsVersioned())
})
}
func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string, shortOutput bool, options *resource.FilenameOptions) error {
schema, err := f.Validator(cmdutil.GetFlagBool(cmd, "validate"))
if err != nil {
return err
}
cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
return err
}
for i, filename := range options.Filenames {
func (o *ReplaceOpts) forceReplace() error {
for i, filename := range o.DeleteOptions.FilenameOptions.Filenames {
if filename == "-" {
tempDir, err := ioutil.TempDir("", "kubectl_replace_")
if err != nil {
@ -182,42 +245,30 @@ func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s
if err != nil {
return err
}
options.Filenames[i] = tempFilename
o.DeleteOptions.FilenameOptions.Filenames[i] = tempFilename
}
}
r := f.NewBuilder().
r := o.Builder().
Unstructured().
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, options).
ResourceTypeOrNameArgs(false, args...).RequireObject(false).
NamespaceParam(o.Namespace).DefaultNamespace().
ResourceTypeOrNameArgs(false, o.BuilderArgs...).RequireObject(false).
FilenameParam(o.EnforceNamespace, &o.DeleteOptions.FilenameOptions).
Flatten().
Do()
if err := r.Err(); err != nil {
return err
}
//Replace will create a resource if it doesn't exist already, so ignore not found 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
}
var err error
// 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.")
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 {
err = DeleteResult(r, out, ignoreNotFound, gracePeriod, shortOutput)
}
if err != nil {
return err
err = o.DeleteOptions.DeleteResult(r)
}
if timeout == 0 {
@ -239,12 +290,12 @@ func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s
return err
}
r = f.NewBuilder().
r = o.Builder().
Unstructured().
Schema(schema).
Schema(o.Schema).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(enforceNamespace, options).
NamespaceParam(o.Namespace).DefaultNamespace().
FilenameParam(o.EnforceNamespace, &o.DeleteOptions.FilenameOptions).
Flatten().
Do()
err = r.Err()
@ -258,12 +309,12 @@ func forceReplace(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []s
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
}
if cmdutil.ShouldRecord(cmd, info) {
if err := cmdutil.RecordChangeCause(info.Object, f.Command(cmd, false)); err != nil {
if o.ShouldRecord(info) {
if err := cmdutil.RecordChangeCause(info.Object, o.changeCause); err != nil {
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++
info.Refresh(obj, true)
cmdutil.PrintSuccess(shortOutput, out, info.Object, false, "replaced")
return nil
return o.PrintObj(info.AsVersioned())
})
if err != nil {
return err

View File

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

View File

@ -90,7 +90,40 @@ type RunObject struct {
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 {
options := &RunOpts{
DeleteFlags: NewDeleteFlags("to use to replace the resource."),
In: cmdIn,
Out: cmdOut,
ErrOut: cmdErr,
}
cmd := &cobra.Command{
Use: "run NAME --image=image [--env=\"key=value\"] [--port=port] [--replicas=replicas] [--dry-run=bool] [--overrides=inline-json] [--command] -- [COMMAND] [args...]",
DisableFlagsInUseLine: true,
@ -98,16 +131,17 @@ func NewCmdRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *co
Long: runLong,
Example: runExample,
Run: func(cmd *cobra.Command, args []string) {
argsLenAtDash := cmd.ArgsLenAtDash()
err := RunRun(f, cmdIn, cmdOut, cmdErr, cmd, args, argsLenAtDash)
cmdutil.CheckErr(err)
cmdutil.CheckErr(options.Complete(f, cmd))
cmdutil.CheckErr(options.Run(f, cmd, args))
},
}
options.DeleteFlags.AddFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
addRunFlags(cmd)
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddRecordFlag(cmd)
cmdutil.AddInclude3rdPartyFlags(cmd)
cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodAttachTimeout)
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."))
}
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
if len(args) == 0 || argsLenAtDash == 0 {
if len(args) == 0 || o.ArgsLenAtDash == 0 {
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
imageName := cmdutil.GetFlagString(cmd, "image")
imageName := o.Image
if imageName == "" {
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)
}
interactive := cmdutil.GetFlagBool(cmd, "stdin")
tty := cmdutil.GetFlagBool(cmd, "tty")
if tty && !interactive {
if o.TTY && !o.Interactive {
return cmdutil.UsageErrorf(cmd, "-i/--stdin is required for containers with -t/--tty=true")
}
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)
}
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")
}
@ -179,7 +242,7 @@ func RunRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *c
if err != nil {
return err
}
restartPolicy, err := getRestartPolicy(cmd, interactive)
restartPolicy, err := getRestartPolicy(cmd, o.Interactive)
if err != nil {
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)
}
attachFlag := cmd.Flags().Lookup("attach")
attach := cmdutil.GetFlagBool(cmd, "attach")
if !attachFlag.Changed && interactive {
attach = true
}
remove := cmdutil.GetFlagBool(cmd, "rm")
if !attach && remove {
if !o.Attach && remove {
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)")
}
@ -212,8 +268,8 @@ func RunRun(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *c
return err
}
generatorName := cmdutil.GetFlagString(cmd, "generator")
schedule := cmdutil.GetFlagString(cmd, "schedule")
generatorName := o.Generator
schedule := o.Schedule
if len(schedule) != 0 && len(generatorName) == 0 {
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.
generatorNameTemp, err := cmdutil.FallbackGeneratorNameIfNecessary(generatorName, clientset.Discovery(), cmdErr)
generatorNameTemp, err := cmdutil.FallbackGeneratorNameIfNecessary(generatorName, clientset.Discovery(), o.ErrOut)
if err != nil {
return err
}
if generatorNameTemp != generatorName {
cmdutil.Warning(cmdErr, generatorName, generatorNameTemp)
cmdutil.Warning(o.ErrOut, generatorName, generatorNameTemp)
} else {
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")
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 {
return err
} else {
createdObjects = append(createdObjects, runObject)
}
allErrs := []error{}
if cmdutil.GetFlagBool(cmd, "expose") {
if o.Expose {
serviceGenerator := cmdutil.GetFlagString(cmd, "service-generator")
if len(serviceGenerator) == 0 {
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 {
allErrs = append(allErrs, err)
} 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 {
defer removeCreatedObjects(f, createdObjects, cmdOut)
defer o.removeCreatedObjects(f, createdObjects)
}
quiet := cmdutil.GetFlagBool(cmd, "quiet")
opts := &AttachOptions{
StreamOptions: StreamOptions{
In: cmdIn,
Out: cmdOut,
Err: cmdErr,
Stdin: interactive,
TTY: tty,
Quiet: quiet,
In: o.In,
Out: o.Out,
Err: o.ErrOut,
Stdin: o.Interactive,
TTY: o.TTY,
Quiet: o.Quiet,
},
GetPodTimeout: timeout,
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
leaveStdinOpen := cmdutil.GetFlagBool(cmd, "leave-stdin-open")
leaveStdinOpen := o.LeaveStdinOpen
waitForExitCode := !leaveStdinOpen && restartPolicy == api.RestartPolicyNever
if waitForExitCode {
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 {
outputFormat := cmdutil.GetFlagString(cmd, "output")
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)
}
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 {
namespace, err := obj.Mapping.MetadataAccessor.Namespace(obj.Object)
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
// its been deleted is unnecessary since that's what they asked
// 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 {
return err
}
@ -502,7 +557,7 @@ func verifyImagePullPolicy(cmd *cobra.Command) error {
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")
generator, found := generators[serviceGenerator]
if !found {
@ -532,27 +587,27 @@ func generateService(f cmdutil.Factory, cmd *cobra.Command, args []string, servi
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 {
return nil, err
}
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 {
return nil, err
}
if cmdutil.GetFlagString(cmd, "output") == "yaml" {
fmt.Fprintln(out, "---")
fmt.Fprintln(o.Out, "---")
}
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
}
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)
if err != nil {
return nil, err
@ -592,12 +647,13 @@ func createGeneratedObject(f cmdutil.Factory, cmd *cobra.Command, generator kube
if err != nil {
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 {
return nil, err
}
}
if !cmdutil.GetDryRunFlag(cmd) {
if !o.DryRun {
resourceMapper := &resource.Mapper{
ObjectTyper: typer,
RESTMapper: mapper,

View File

@ -188,12 +188,29 @@ func TestRunArgsFollowDashRules(t *testing.T) {
}, nil
}),
}
tf.Namespace = "test"
tf.ClientConfigVal = &restclient.Config{}
cmd := NewCmdRun(tf, os.Stdin, os.Stdout, os.Stderr)
cmd.Flags().Set("image", "nginx")
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 {
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.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.")
@ -343,7 +373,7 @@ func TestGenerateService(t *testing.T) {
addRunFlags(cmd)
if !test.expectPOST {
cmd.Flags().Set("dry-run", "true")
opts.DryRun = true
}
if len(test.port) > 0 {
@ -351,8 +381,7 @@ func TestGenerateService(t *testing.T) {
test.params["port"] = test.port
}
buff := &bytes.Buffer{}
_, err := generateService(tf, cmd, test.args, test.serviceGenerator, test.params, "namespace", buff)
_, err := opts.generateService(tf, cmd, test.serviceGenerator, test.params, "namespace")
if test.expectErr {
if err == nil {
t.Error("unexpected non-error")
@ -473,7 +502,12 @@ func TestRunValidations(t *testing.T) {
for flagName, flagValue := range test.flags {
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 !strings.Contains(err.Error(), test.expectedErr) {
t.Errorf("unexpected error: %v", err)