package cmd import ( "fmt" "io" "github.com/spf13/pflag" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" ) type Runner interface { Run(list *kapi.List, namespace string) []error } // AfterFunc takes an info and an error, and returns true if processing should stop. type AfterFunc func(*resource.Info, error) bool // OpFunc takes the provided info and attempts to perform the operation type OpFunc func(info *resource.Info, namespace string, obj runtime.Object) (runtime.Object, error) // RetryFunc can retry the operation a single time by returning a non-nil object. // TODO: make this a more general retry "loop" function rather than one time. type RetryFunc func(info *resource.Info, err error) runtime.Object // Mapper is an interface testability that is equivalent to resource.Mapper type Mapper interface { meta.RESTMapper InfoForObject(obj runtime.Object, preferredGVKs []unversioned.GroupVersionKind) (*resource.Info, error) } // Bulk provides helpers for iterating over a list of items type Bulk struct { Mapper Mapper Op OpFunc After AfterFunc Retry RetryFunc } // Create attempts to create each item generically, gathering all errors in the // event a failure occurs. The contents of list will be updated to include the // version from the server. func (b *Bulk) Run(list *kapi.List, namespace string) []error { after := b.After if after == nil { after = func(*resource.Info, error) bool { return false } } errs := []error{} for i, item := range list.Items { info, err := b.Mapper.InfoForObject(item, nil) if err != nil { errs = append(errs, err) if after(info, err) { break } continue } obj, err := b.Op(info, namespace, item) if err != nil && b.Retry != nil { if obj = b.Retry(info, err); obj != nil { obj, err = b.Op(info, namespace, obj) } } if err != nil { errs = append(errs, err) if after(info, err) { break } continue } info.Refresh(obj, true) list.Items[i] = obj if after(info, nil) { break } } return errs } func NewPrintNameOrErrorAfterIndent(mapper meta.RESTMapper, short bool, operation string, out, errs io.Writer, dryRun bool, indent string) AfterFunc { return func(info *resource.Info, err error) bool { if err == nil { fmt.Fprintf(out, indent) cmdutil.PrintSuccess(mapper, short, out, info.Mapping.Resource, info.Name, dryRun, operation) } else { fmt.Fprintf(errs, "%serror: %v\n", indent, err) } return false } } func NewPrintErrorAfter(mapper meta.RESTMapper, errs io.Writer) func(*resource.Info, error) bool { return func(info *resource.Info, err error) bool { if err != nil { fmt.Fprintf(errs, "error: %v\n", err) } return false } } func HaltOnError(fn AfterFunc) AfterFunc { return func(info *resource.Info, err error) bool { if fn(info, err) || err != nil { return true } return false } } // Create is the default create operation for a generic resource. func Create(info *resource.Info, namespace string, obj runtime.Object) (runtime.Object, error) { return resource.NewHelper(info.Client, info.Mapping).Create(namespace, false, obj) } func NoOp(info *resource.Info, namespace string, obj runtime.Object) (runtime.Object, error) { return info.Object, nil } func labelSuffix(set map[string]string) string { if len(set) == 0 { return "" } return fmt.Sprintf(" with label %s", labels.SelectorFromSet(set).String()) } func CreateMessage(labels map[string]string) string { return fmt.Sprintf("Creating resources%s", labelSuffix(labels)) } type BulkAction struct { // required setup Bulk Bulk Out, ErrOut io.Writer // flags Output string DryRun bool StopOnError bool // output modifiers Action string } // BindForAction sets flags on this action for when setting -o should only change how the operation results are displayed. // Passing -o is changing the default output format. func (b *BulkAction) BindForAction(flags *pflag.FlagSet) { flags.StringVarP(&b.Output, "output", "o", "", "Output mode. Use \"-o name\" for shorter output (resource/name).") flags.BoolVar(&b.DryRun, "dry-run", false, "If true, show the result of the operation without performing it.") } // BindForOutput sets flags on this action for when setting -o will not execute the action (the point of the action is // primarily to generate the output). Passing -o is asking for output, not execution. func (b *BulkAction) BindForOutput(flags *pflag.FlagSet) { flags.StringVarP(&b.Output, "output", "o", "", "Output results as yaml or json instead of executing, or use name for succint output (resource/name).") flags.BoolVar(&b.DryRun, "dry-run", false, "If true, show the result of the operation without performing it.") flags.Bool("no-headers", false, "Omit table headers for default output.") flags.MarkHidden("no-headers") } // Compact sets the output to a minimal set func (b *BulkAction) Compact() { b.Output = "compact" } // ShouldPrint returns true if an external printer should handle this action instead of execution. func (b *BulkAction) ShouldPrint() bool { return b.Output != "" && b.Output != "name" && b.Output != "compact" } func (b *BulkAction) Verbose() bool { return b.Output == "" } func (b *BulkAction) DefaultIndent() string { if b.Verbose() { return " " } return "" } func (b BulkAction) WithMessage(action, individual string) Runner { b.Action = action switch { // TODO: this should be b printer case b.Output == "": b.Bulk.After = NewPrintNameOrErrorAfterIndent(b.Bulk.Mapper, false, individual, b.Out, b.ErrOut, b.DryRun, b.DefaultIndent()) // TODO: needs to be unified with the name printer (incremental vs exact execution), possibly by creating b synthetic printer? case b.Output == "name": b.Bulk.After = NewPrintNameOrErrorAfterIndent(b.Bulk.Mapper, true, individual, b.Out, b.ErrOut, b.DryRun, b.DefaultIndent()) default: b.Bulk.After = NewPrintErrorAfter(b.Bulk.Mapper, b.ErrOut) if b.StopOnError { b.Bulk.After = HaltOnError(b.Bulk.After) } } return &b } func (b *BulkAction) Run(list *kapi.List, namespace string) []error { run := b.Bulk if b.Verbose() { fmt.Fprintf(b.Out, "--> %s ...\n", b.Action) } var modifier string if b.DryRun { run.Op = NoOp modifier = " (dry run)" } errs := run.Run(list, namespace) if b.Verbose() { if len(errs) == 0 { fmt.Fprintf(b.Out, "--> Success%s\n", modifier) } else { fmt.Fprintf(b.Out, "--> Failed%s\n", modifier) } } return errs }