1/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package run
18
19import (
20	"context"
21	"fmt"
22	"time"
23
24	"github.com/docker/distribution/reference"
25	"github.com/spf13/cobra"
26	"k8s.io/klog/v2"
27
28	corev1 "k8s.io/api/core/v1"
29	"k8s.io/apimachinery/pkg/api/errors"
30	"k8s.io/apimachinery/pkg/api/meta"
31	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32	"k8s.io/apimachinery/pkg/fields"
33	"k8s.io/apimachinery/pkg/runtime"
34	"k8s.io/apimachinery/pkg/runtime/schema"
35	utilerrors "k8s.io/apimachinery/pkg/util/errors"
36	"k8s.io/apimachinery/pkg/watch"
37	"k8s.io/cli-runtime/pkg/genericclioptions"
38	"k8s.io/cli-runtime/pkg/resource"
39	"k8s.io/client-go/kubernetes"
40	corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
41	"k8s.io/client-go/tools/cache"
42	watchtools "k8s.io/client-go/tools/watch"
43	"k8s.io/kubectl/pkg/cmd/attach"
44	"k8s.io/kubectl/pkg/cmd/delete"
45	"k8s.io/kubectl/pkg/cmd/exec"
46	"k8s.io/kubectl/pkg/cmd/logs"
47	cmdutil "k8s.io/kubectl/pkg/cmd/util"
48	"k8s.io/kubectl/pkg/generate"
49	generateversioned "k8s.io/kubectl/pkg/generate/versioned"
50	"k8s.io/kubectl/pkg/polymorphichelpers"
51	"k8s.io/kubectl/pkg/scheme"
52	"k8s.io/kubectl/pkg/util"
53	"k8s.io/kubectl/pkg/util/i18n"
54	"k8s.io/kubectl/pkg/util/interrupt"
55	"k8s.io/kubectl/pkg/util/templates"
56	uexec "k8s.io/utils/exec"
57)
58
59var (
60	runLong = templates.LongDesc(i18n.T(`Create and run a particular image in a pod.`))
61
62	runExample = templates.Examples(i18n.T(`
63		# Start a nginx pod.
64		kubectl run nginx --image=nginx
65
66		# Start a hazelcast pod and let the container expose port 5701.
67		kubectl run hazelcast --image=hazelcast/hazelcast --port=5701
68
69		# Start a hazelcast pod and set environment variables "DNS_DOMAIN=cluster" and "POD_NAMESPACE=default" in the container.
70		kubectl run hazelcast --image=hazelcast/hazelcast --env="DNS_DOMAIN=cluster" --env="POD_NAMESPACE=default"
71
72		# Start a hazelcast pod and set labels "app=hazelcast" and "env=prod" in the container.
73		kubectl run hazelcast --image=hazelcast/hazelcast --labels="app=hazelcast,env=prod"
74
75		# Dry run. Print the corresponding API objects without creating them.
76		kubectl run nginx --image=nginx --dry-run=client
77
78		# Start a nginx pod, but overload the spec with a partial set of values parsed from JSON.
79		kubectl run nginx --image=nginx --overrides='{ "apiVersion": "v1", "spec": { ... } }'
80
81		# Start a busybox pod and keep it in the foreground, don't restart it if it exits.
82		kubectl run -i -t busybox --image=busybox --restart=Never
83
84		# Start the nginx pod using the default command, but use custom arguments (arg1 .. argN) for that command.
85		kubectl run nginx --image=nginx -- <arg1> <arg2> ... <argN>
86
87		# Start the nginx pod using a different command and custom arguments.
88		kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN>`))
89)
90
91const (
92	defaultPodAttachTimeout = 60 * time.Second
93)
94
95var metadataAccessor = meta.NewAccessor()
96
97type RunObject struct {
98	Object  runtime.Object
99	Mapping *meta.RESTMapping
100}
101
102type RunOptions struct {
103	PrintFlags  *genericclioptions.PrintFlags
104	RecordFlags *genericclioptions.RecordFlags
105
106	DeleteFlags   *delete.DeleteFlags
107	DeleteOptions *delete.DeleteOptions
108
109	DryRunStrategy cmdutil.DryRunStrategy
110	DryRunVerifier *resource.DryRunVerifier
111
112	PrintObj func(runtime.Object) error
113	Recorder genericclioptions.Recorder
114
115	ArgsLenAtDash  int
116	Attach         bool
117	Expose         bool
118	Generator      string
119	Image          string
120	Interactive    bool
121	LeaveStdinOpen bool
122	Port           string
123	Privileged     bool
124	Quiet          bool
125	Schedule       string
126	TTY            bool
127	fieldManager   string
128
129	Namespace        string
130	EnforceNamespace bool
131
132	genericclioptions.IOStreams
133}
134
135func NewRunOptions(streams genericclioptions.IOStreams) *RunOptions {
136	return &RunOptions{
137		PrintFlags:  genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
138		DeleteFlags: delete.NewDeleteFlags("to use to replace the resource."),
139		RecordFlags: genericclioptions.NewRecordFlags(),
140
141		Generator: generateversioned.RunPodV1GeneratorName,
142
143		Recorder: genericclioptions.NoopRecorder{},
144
145		IOStreams: streams,
146	}
147}
148
149func NewCmdRun(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
150	o := NewRunOptions(streams)
151
152	cmd := &cobra.Command{
153		Use:                   "run NAME --image=image [--env=\"key=value\"] [--port=port] [--dry-run=server|client] [--overrides=inline-json] [--command] -- [COMMAND] [args...]",
154		DisableFlagsInUseLine: true,
155		Short:                 i18n.T("Run a particular image on the cluster"),
156		Long:                  runLong,
157		Example:               runExample,
158		Run: func(cmd *cobra.Command, args []string) {
159			cmdutil.CheckErr(o.Complete(f, cmd))
160			cmdutil.CheckErr(o.Run(f, cmd, args))
161		},
162	}
163
164	o.DeleteFlags.AddFlags(cmd)
165	o.PrintFlags.AddFlags(cmd)
166	o.RecordFlags.AddFlags(cmd)
167
168	addRunFlags(cmd, o)
169	cmdutil.AddApplyAnnotationFlags(cmd)
170	cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodAttachTimeout)
171	return cmd
172}
173
174func addRunFlags(cmd *cobra.Command, opt *RunOptions) {
175	cmdutil.AddDryRunFlag(cmd)
176	cmd.Flags().StringArray("annotations", []string{}, i18n.T("Annotations to apply to the pod."))
177	cmd.Flags().StringVar(&opt.Generator, "generator", opt.Generator, i18n.T("The name of the API generator to use, see http://kubernetes.io/docs/user-guide/kubectl-conventions/#generators for a list."))
178	cmd.Flags().MarkDeprecated("generator", "has no effect and will be removed in the future.")
179	cmd.Flags().StringVar(&opt.Image, "image", opt.Image, i18n.T("The image for the container to run."))
180	cmd.MarkFlagRequired("image")
181	cmd.Flags().String("image-pull-policy", "", i18n.T("The image pull policy for the container. If left empty, this value will not be specified by the client and defaulted by the server"))
182	cmd.Flags().IntP("replicas", "r", 1, "Number of replicas to create for this container. Default is 1.")
183	cmd.Flags().MarkDeprecated("replicas", "has no effect and will be removed in the future.")
184	cmd.Flags().Bool("rm", false, "If true, delete resources created in this command for attached containers.")
185	cmd.Flags().String("overrides", "", i18n.T("An inline JSON override for the generated object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field."))
186	cmd.Flags().StringArray("env", []string{}, "Environment variables to set in the container.")
187	cmd.Flags().String("serviceaccount", "", "Service account to set in the pod spec.")
188	cmd.Flags().StringVar(&opt.Port, "port", opt.Port, i18n.T("The port that this container exposes."))
189	cmd.Flags().Int("hostport", -1, "The host port mapping for the container port. To demonstrate a single-machine container.")
190	cmd.Flags().StringP("labels", "l", "", "Comma separated labels to apply to the pod(s). Will override previous values.")
191	cmd.Flags().BoolVarP(&opt.Interactive, "stdin", "i", opt.Interactive, "Keep stdin open on the container(s) in the pod, even if nothing is attached.")
192	cmd.Flags().BoolVarP(&opt.TTY, "tty", "t", opt.TTY, "Allocated a TTY for each container in the pod.")
193	cmd.Flags().BoolVar(&opt.Attach, "attach", opt.Attach, "If true, wait for the Pod to start running, and then attach to the Pod as if 'kubectl attach ...' were called.  Default false, unless '-i/--stdin' is set, in which case the default is true. With '--restart=Never' the exit code of the container process is returned.")
194	cmd.Flags().BoolVar(&opt.LeaveStdinOpen, "leave-stdin-open", opt.LeaveStdinOpen, "If the pod is started in interactive mode or with stdin, leave stdin open after the first attach completes. By default, stdin will be closed after the first attach completes.")
195	cmd.Flags().String("restart", "Always", i18n.T("The restart policy for this Pod.  Legal values [Always, OnFailure, Never]."))
196	cmd.Flags().Bool("command", false, "If true and extra arguments are present, use them as the 'command' field in the container, rather than the 'args' field which is the default.")
197	cmd.Flags().String("requests", "", i18n.T("The resource requirement requests for this container.  For example, 'cpu=100m,memory=256Mi'.  Note that server side components may assign requests depending on the server configuration, such as limit ranges."))
198	cmd.Flags().String("limits", "", i18n.T("The resource requirement limits for this container.  For example, 'cpu=200m,memory=512Mi'.  Note that server side components may assign limits depending on the server configuration, such as limit ranges."))
199	cmd.Flags().BoolVar(&opt.Expose, "expose", opt.Expose, "If true, service is created for the container(s) which are run")
200	cmd.Flags().String("service-generator", "service/v2", i18n.T("The name of the generator to use for creating a service.  Only used if --expose is true"))
201	cmd.Flags().MarkDeprecated("service-generator", "and will be removed in the future.")
202	cmd.Flags().String("service-overrides", "", i18n.T("An inline JSON override for the generated service object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field.  Only used if --expose is true."))
203	cmd.Flags().MarkDeprecated("service-overrides", "and will be removed in the future.")
204	cmd.Flags().BoolVar(&opt.Quiet, "quiet", opt.Quiet, "If true, suppress prompt messages.")
205	cmd.Flags().StringVar(&opt.Schedule, "schedule", opt.Schedule, i18n.T("A schedule in the Cron format the job should be run with."))
206	cmd.Flags().MarkDeprecated("schedule", "has no effect and will be removed in the future.")
207	cmd.Flags().BoolVar(&opt.Privileged, "privileged", opt.Privileged, i18n.T("If true, run the container in privileged mode."))
208	cmdutil.AddFieldManagerFlagVar(cmd, &opt.fieldManager, "kubectl-run")
209}
210
211func (o *RunOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) error {
212	var err error
213
214	o.RecordFlags.Complete(cmd)
215	o.Recorder, err = o.RecordFlags.ToRecorder()
216	if err != nil {
217		return err
218	}
219
220	o.ArgsLenAtDash = cmd.ArgsLenAtDash()
221	o.DryRunStrategy, err = cmdutil.GetDryRunStrategy(cmd)
222	if err != nil {
223		return err
224	}
225	dynamicClient, err := f.DynamicClient()
226	if err != nil {
227		return err
228	}
229	discoveryClient, err := f.ToDiscoveryClient()
230	if err != nil {
231		return err
232	}
233	o.DryRunVerifier = resource.NewDryRunVerifier(dynamicClient, discoveryClient)
234
235	attachFlag := cmd.Flags().Lookup("attach")
236	if !attachFlag.Changed && o.Interactive {
237		o.Attach = true
238	}
239
240	cmdutil.PrintFlagsWithDryRunStrategy(o.PrintFlags, o.DryRunStrategy)
241	printer, err := o.PrintFlags.ToPrinter()
242	if err != nil {
243		return err
244	}
245	o.PrintObj = func(obj runtime.Object) error {
246		return printer.PrintObj(obj, o.Out)
247	}
248
249	deleteOpts, err := o.DeleteFlags.ToOptions(dynamicClient, o.IOStreams)
250	if err != nil {
251		return err
252	}
253
254	deleteOpts.IgnoreNotFound = true
255	deleteOpts.WaitForDeletion = false
256	deleteOpts.GracePeriod = -1
257	deleteOpts.Quiet = o.Quiet
258
259	o.DeleteOptions = deleteOpts
260
261	return nil
262}
263
264func (o *RunOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
265	// Let kubectl run follow rules for `--`, see #13004 issue
266	if len(args) == 0 || o.ArgsLenAtDash == 0 {
267		return cmdutil.UsageErrorf(cmd, "NAME is required for run")
268	}
269
270	timeout, err := cmdutil.GetPodRunningTimeoutFlag(cmd)
271	if err != nil {
272		return cmdutil.UsageErrorf(cmd, "%v", err)
273	}
274
275	// validate image name
276	imageName := o.Image
277	if imageName == "" {
278		return fmt.Errorf("--image is required")
279	}
280	validImageRef := reference.ReferenceRegexp.MatchString(imageName)
281	if !validImageRef {
282		return fmt.Errorf("Invalid image name %q: %v", imageName, reference.ErrReferenceInvalidFormat)
283	}
284
285	if o.TTY && !o.Interactive {
286		return cmdutil.UsageErrorf(cmd, "-i/--stdin is required for containers with -t/--tty=true")
287	}
288	if o.Expose && len(o.Port) == 0 {
289		return cmdutil.UsageErrorf(cmd, "--port must be set when exposing a service")
290	}
291
292	o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
293	if err != nil {
294		return err
295	}
296	restartPolicy, err := getRestartPolicy(cmd, o.Interactive)
297	if err != nil {
298		return err
299	}
300
301	remove := cmdutil.GetFlagBool(cmd, "rm")
302	if !o.Attach && remove {
303		return cmdutil.UsageErrorf(cmd, "--rm should only be used for attached containers")
304	}
305
306	if o.Attach && o.DryRunStrategy != cmdutil.DryRunNone {
307		return cmdutil.UsageErrorf(cmd, "--dry-run=[server|client] can't be used with attached containers options (--attach, --stdin, or --tty)")
308	}
309
310	if err := verifyImagePullPolicy(cmd); err != nil {
311		return err
312	}
313
314	generators := generateversioned.GeneratorFn("run")
315	generator, found := generators[generateversioned.RunPodV1GeneratorName]
316	if !found {
317		return cmdutil.UsageErrorf(cmd, "generator %q not found", o.Generator)
318	}
319
320	names := generator.ParamNames()
321	params := generate.MakeParams(cmd, names)
322	params["name"] = args[0]
323	if len(args) > 1 {
324		params["args"] = args[1:]
325	}
326
327	params["annotations"] = cmdutil.GetFlagStringArray(cmd, "annotations")
328	params["env"] = cmdutil.GetFlagStringArray(cmd, "env")
329
330	var createdObjects = []*RunObject{}
331	runObject, err := o.createGeneratedObject(f, cmd, generator, names, params, cmdutil.GetFlagString(cmd, "overrides"))
332	if err != nil {
333		return err
334	}
335	createdObjects = append(createdObjects, runObject)
336
337	allErrs := []error{}
338	if o.Expose {
339		serviceGenerator := cmdutil.GetFlagString(cmd, "service-generator")
340		if len(serviceGenerator) == 0 {
341			return cmdutil.UsageErrorf(cmd, "No service generator specified")
342		}
343		serviceRunObject, err := o.generateService(f, cmd, serviceGenerator, params)
344		if err != nil {
345			allErrs = append(allErrs, err)
346		} else {
347			createdObjects = append(createdObjects, serviceRunObject)
348		}
349	}
350
351	if o.Attach {
352		if remove {
353			defer o.removeCreatedObjects(f, createdObjects)
354		}
355
356		opts := &attach.AttachOptions{
357			StreamOptions: exec.StreamOptions{
358				IOStreams: o.IOStreams,
359				Stdin:     o.Interactive,
360				TTY:       o.TTY,
361				Quiet:     o.Quiet,
362			},
363			GetPodTimeout: timeout,
364			CommandName:   cmd.Parent().CommandPath() + " attach",
365
366			Attach: &attach.DefaultRemoteAttach{},
367		}
368		config, err := f.ToRESTConfig()
369		if err != nil {
370			return err
371		}
372		opts.Config = config
373		opts.AttachFunc = attach.DefaultAttachFunc
374
375		clientset, err := kubernetes.NewForConfig(config)
376		if err != nil {
377			return err
378		}
379
380		attachablePod, err := polymorphichelpers.AttachablePodForObjectFn(f, runObject.Object, opts.GetPodTimeout)
381		if err != nil {
382			return err
383		}
384		err = handleAttachPod(f, clientset.CoreV1(), attachablePod.Namespace, attachablePod.Name, opts)
385		if err != nil {
386			return err
387		}
388
389		var pod *corev1.Pod
390		waitForExitCode := !o.LeaveStdinOpen && (restartPolicy == corev1.RestartPolicyNever || restartPolicy == corev1.RestartPolicyOnFailure)
391		if waitForExitCode {
392			// we need different exit condition depending on restart policy
393			// for Never it can either fail or succeed, for OnFailure only
394			// success matters
395			exitCondition := podCompleted
396			if restartPolicy == corev1.RestartPolicyOnFailure {
397				exitCondition = podSucceeded
398			}
399			pod, err = waitForPod(clientset.CoreV1(), attachablePod.Namespace, attachablePod.Name, opts.GetPodTimeout, exitCondition)
400			if err != nil {
401				return err
402			}
403		} else {
404			// after removal is done, return successfully if we are not interested in the exit code
405			return nil
406		}
407
408		switch pod.Status.Phase {
409		case corev1.PodSucceeded:
410			return nil
411		case corev1.PodFailed:
412			unknownRcErr := fmt.Errorf("pod %s/%s failed with unknown exit code", pod.Namespace, pod.Name)
413			if len(pod.Status.ContainerStatuses) == 0 || pod.Status.ContainerStatuses[0].State.Terminated == nil {
414				return unknownRcErr
415			}
416			// assume here that we have at most one status because kubectl-run only creates one container per pod
417			rc := pod.Status.ContainerStatuses[0].State.Terminated.ExitCode
418			if rc == 0 {
419				return unknownRcErr
420			}
421			return uexec.CodeExitError{
422				Err:  fmt.Errorf("pod %s/%s terminated (%s)\n%s", pod.Namespace, pod.Name, pod.Status.ContainerStatuses[0].State.Terminated.Reason, pod.Status.ContainerStatuses[0].State.Terminated.Message),
423				Code: int(rc),
424			}
425		default:
426			return fmt.Errorf("pod %s/%s left in phase %s", pod.Namespace, pod.Name, pod.Status.Phase)
427		}
428
429	}
430	if runObject != nil {
431		if err := o.PrintObj(runObject.Object); err != nil {
432			return err
433		}
434	}
435
436	return utilerrors.NewAggregate(allErrs)
437}
438
439func (o *RunOptions) removeCreatedObjects(f cmdutil.Factory, createdObjects []*RunObject) error {
440	for _, obj := range createdObjects {
441		namespace, err := metadataAccessor.Namespace(obj.Object)
442		if err != nil {
443			return err
444		}
445		var name string
446		name, err = metadataAccessor.Name(obj.Object)
447		if err != nil {
448			return err
449		}
450		r := f.NewBuilder().
451			WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
452			ContinueOnError().
453			NamespaceParam(namespace).DefaultNamespace().
454			ResourceNames(obj.Mapping.Resource.Resource+"."+obj.Mapping.Resource.Group, name).
455			Flatten().
456			Do()
457		if err := o.DeleteOptions.DeleteResult(r); err != nil {
458			return err
459		}
460	}
461
462	return nil
463}
464
465// waitForPod watches the given pod until the exitCondition is true
466func waitForPod(podClient corev1client.PodsGetter, ns, name string, timeout time.Duration, exitCondition watchtools.ConditionFunc) (*corev1.Pod, error) {
467	ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout)
468	defer cancel()
469
470	fieldSelector := fields.OneTermEqualSelector("metadata.name", name).String()
471	lw := &cache.ListWatch{
472		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
473			options.FieldSelector = fieldSelector
474			return podClient.Pods(ns).List(context.TODO(), options)
475		},
476		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
477			options.FieldSelector = fieldSelector
478			return podClient.Pods(ns).Watch(context.TODO(), options)
479		},
480	}
481
482	intr := interrupt.New(nil, cancel)
483	var result *corev1.Pod
484	err := intr.Run(func() error {
485		ev, err := watchtools.UntilWithSync(ctx, lw, &corev1.Pod{}, nil, exitCondition)
486		if ev != nil {
487			result = ev.Object.(*corev1.Pod)
488		}
489		return err
490	})
491
492	return result, err
493}
494
495func handleAttachPod(f cmdutil.Factory, podClient corev1client.PodsGetter, ns, name string, opts *attach.AttachOptions) error {
496	pod, err := waitForPod(podClient, ns, name, opts.GetPodTimeout, podRunningAndReady)
497	if err != nil && err != ErrPodCompleted {
498		return err
499	}
500
501	if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
502		return logOpts(f, pod, opts)
503	}
504
505	opts.Pod = pod
506	opts.PodName = name
507	opts.Namespace = ns
508
509	if opts.AttachFunc == nil {
510		opts.AttachFunc = attach.DefaultAttachFunc
511	}
512
513	if err := opts.Run(); err != nil {
514		fmt.Fprintf(opts.ErrOut, "Error attaching, falling back to logs: %v\n", err)
515		return logOpts(f, pod, opts)
516	}
517	return nil
518}
519
520// logOpts logs output from opts to the pods log.
521func logOpts(restClientGetter genericclioptions.RESTClientGetter, pod *corev1.Pod, opts *attach.AttachOptions) error {
522	ctrName, err := opts.GetContainerName(pod)
523	if err != nil {
524		return err
525	}
526
527	requests, err := polymorphichelpers.LogsForObjectFn(restClientGetter, pod, &corev1.PodLogOptions{Container: ctrName}, opts.GetPodTimeout, false)
528	if err != nil {
529		return err
530	}
531	for _, request := range requests {
532		if err := logs.DefaultConsumeRequest(request, opts.Out); err != nil {
533			return err
534		}
535	}
536
537	return nil
538}
539
540func getRestartPolicy(cmd *cobra.Command, interactive bool) (corev1.RestartPolicy, error) {
541	restart := cmdutil.GetFlagString(cmd, "restart")
542	if len(restart) == 0 {
543		if interactive {
544			return corev1.RestartPolicyOnFailure, nil
545		}
546		return corev1.RestartPolicyAlways, nil
547	}
548	switch corev1.RestartPolicy(restart) {
549	case corev1.RestartPolicyAlways:
550		return corev1.RestartPolicyAlways, nil
551	case corev1.RestartPolicyOnFailure:
552		return corev1.RestartPolicyOnFailure, nil
553	case corev1.RestartPolicyNever:
554		return corev1.RestartPolicyNever, nil
555	}
556	return "", cmdutil.UsageErrorf(cmd, "invalid restart policy: %s", restart)
557}
558
559func verifyImagePullPolicy(cmd *cobra.Command) error {
560	pullPolicy := cmdutil.GetFlagString(cmd, "image-pull-policy")
561	switch corev1.PullPolicy(pullPolicy) {
562	case corev1.PullAlways, corev1.PullIfNotPresent, corev1.PullNever:
563		return nil
564	case "":
565		return nil
566	}
567	return cmdutil.UsageErrorf(cmd, "invalid image pull policy: %s", pullPolicy)
568}
569
570func (o *RunOptions) generateService(f cmdutil.Factory, cmd *cobra.Command, serviceGenerator string, paramsIn map[string]interface{}) (*RunObject, error) {
571	generators := generateversioned.GeneratorFn("expose")
572	generator, found := generators[serviceGenerator]
573	if !found {
574		return nil, fmt.Errorf("missing service generator: %s", serviceGenerator)
575	}
576	names := generator.ParamNames()
577
578	params := map[string]interface{}{}
579	for key, value := range paramsIn {
580		_, isString := value.(string)
581		if isString {
582			params[key] = value
583		}
584	}
585
586	name, found := params["name"]
587	if !found || len(name.(string)) == 0 {
588		return nil, fmt.Errorf("name is a required parameter")
589	}
590	selector, found := params["labels"]
591	if !found || len(selector.(string)) == 0 {
592		selector = fmt.Sprintf("run=%s", name.(string))
593	}
594	params["selector"] = selector
595
596	if defaultName, found := params["default-name"]; !found || len(defaultName.(string)) == 0 {
597		params["default-name"] = name
598	}
599
600	runObject, err := o.createGeneratedObject(f, cmd, generator, names, params, cmdutil.GetFlagString(cmd, "service-overrides"))
601	if err != nil {
602		return nil, err
603	}
604
605	if err := o.PrintObj(runObject.Object); err != nil {
606		return nil, err
607	}
608	// separate yaml objects
609	if o.PrintFlags.OutputFormat != nil && *o.PrintFlags.OutputFormat == "yaml" {
610		fmt.Fprintln(o.Out, "---")
611	}
612
613	return runObject, nil
614}
615
616func (o *RunOptions) createGeneratedObject(f cmdutil.Factory, cmd *cobra.Command, generator generate.Generator, names []generate.GeneratorParam, params map[string]interface{}, overrides string) (*RunObject, error) {
617	err := generate.ValidateParams(names, params)
618	if err != nil {
619		return nil, err
620	}
621
622	// TODO: Validate flag usage against selected generator. More tricky since --expose was added.
623	obj, err := generator.Generate(params)
624	if err != nil {
625		return nil, err
626	}
627
628	mapper, err := f.ToRESTMapper()
629	if err != nil {
630		return nil, err
631	}
632	// run has compiled knowledge of the thing is creating
633	gvks, _, err := scheme.Scheme.ObjectKinds(obj)
634	if err != nil {
635		return nil, err
636	}
637	mapping, err := mapper.RESTMapping(gvks[0].GroupKind(), gvks[0].Version)
638	if err != nil {
639		return nil, err
640	}
641
642	if len(overrides) > 0 {
643		codec := runtime.NewCodec(scheme.DefaultJSONEncoder(), scheme.Codecs.UniversalDecoder(scheme.Scheme.PrioritizedVersionsAllGroups()...))
644		obj, err = cmdutil.Merge(codec, obj, overrides)
645		if err != nil {
646			return nil, err
647		}
648	}
649
650	if err := o.Recorder.Record(obj); err != nil {
651		klog.V(4).Infof("error recording current command: %v", err)
652	}
653
654	actualObj := obj
655	if o.DryRunStrategy != cmdutil.DryRunClient {
656		if err := util.CreateOrUpdateAnnotation(cmdutil.GetFlagBool(cmd, cmdutil.ApplyAnnotationsFlag), obj, scheme.DefaultJSONEncoder()); err != nil {
657			return nil, err
658		}
659		client, err := f.ClientForMapping(mapping)
660		if err != nil {
661			return nil, err
662		}
663		if o.DryRunStrategy == cmdutil.DryRunServer {
664			if err := o.DryRunVerifier.HasSupport(mapping.GroupVersionKind); err != nil {
665				return nil, err
666			}
667		}
668		actualObj, err = resource.
669			NewHelper(client, mapping).
670			DryRun(o.DryRunStrategy == cmdutil.DryRunServer).
671			WithFieldManager(o.fieldManager).
672			Create(o.Namespace, false, obj)
673		if err != nil {
674			return nil, err
675		}
676	} else {
677		if meta, err := meta.Accessor(actualObj); err == nil && o.EnforceNamespace {
678			meta.SetNamespace(o.Namespace)
679		}
680	}
681
682	return &RunObject{
683		Object:  actualObj,
684		Mapping: mapping,
685	}, nil
686}
687
688// ErrPodCompleted is returned by PodRunning or PodContainerRunning to indicate that
689// the pod has already reached completed state.
690var ErrPodCompleted = fmt.Errorf("pod ran to completion")
691
692// podCompleted returns true if the pod has run to completion, false if the pod has not yet
693// reached running state, or an error in any other case.
694func podCompleted(event watch.Event) (bool, error) {
695	switch event.Type {
696	case watch.Deleted:
697		return false, errors.NewNotFound(schema.GroupResource{Resource: "pods"}, "")
698	}
699	switch t := event.Object.(type) {
700	case *corev1.Pod:
701		switch t.Status.Phase {
702		case corev1.PodFailed, corev1.PodSucceeded:
703			return true, nil
704		}
705	}
706	return false, nil
707}
708
709// podSucceeded returns true if the pod has run to completion, false if the pod has not yet
710// reached running state, or an error in any other case.
711func podSucceeded(event watch.Event) (bool, error) {
712	switch event.Type {
713	case watch.Deleted:
714		return false, errors.NewNotFound(schema.GroupResource{Resource: "pods"}, "")
715	}
716	switch t := event.Object.(type) {
717	case *corev1.Pod:
718		return t.Status.Phase == corev1.PodSucceeded, nil
719	}
720	return false, nil
721}
722
723// podRunningAndReady returns true if the pod is running and ready, false if the pod has not
724// yet reached those states, returns ErrPodCompleted if the pod has run to completion, or
725// an error in any other case.
726func podRunningAndReady(event watch.Event) (bool, error) {
727	switch event.Type {
728	case watch.Deleted:
729		return false, errors.NewNotFound(schema.GroupResource{Resource: "pods"}, "")
730	}
731	switch t := event.Object.(type) {
732	case *corev1.Pod:
733		switch t.Status.Phase {
734		case corev1.PodFailed, corev1.PodSucceeded:
735			return false, ErrPodCompleted
736		case corev1.PodRunning:
737			conditions := t.Status.Conditions
738			if conditions == nil {
739				return false, nil
740			}
741			for i := range conditions {
742				if conditions[i].Type == corev1.PodReady &&
743					conditions[i].Status == corev1.ConditionTrue {
744					return true, nil
745				}
746			}
747		}
748	}
749	return false, nil
750}
751