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