From 9e7ca872b4e2c9c95f7e7c224bc79c1b698789b9 Mon Sep 17 00:00:00 2001 From: Maciej Szulik Date: Thu, 14 May 2020 18:34:27 +0200 Subject: [PATCH 1/2] Set schedule and image flags required for create cronjob --- .../kubectl/pkg/cmd/create/create_cronjob.go | 28 ++++++------------- .../kubectl/pkg/cmd/create/create_job.go | 9 +++--- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_cronjob.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_cronjob.go index 77fc62a5265..94fcc8ceb68 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_cronjob.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_cronjob.go @@ -80,14 +80,14 @@ func NewCreateCronJobOptions(ioStreams genericclioptions.IOStreams) *CreateCronJ func NewCmdCreateCronJob(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { o := NewCreateCronJobOptions(ioStreams) cmd := &cobra.Command{ - Use: "cronjob NAME --image=image --schedule='0/5 * * * ?' -- [COMMAND] [args...]", - Aliases: []string{"cj"}, - Short: cronjobLong, - Long: cronjobLong, - Example: cronjobExample, + Use: "cronjob NAME --image=image --schedule='0/5 * * * ?' -- [COMMAND] [args...]", + DisableFlagsInUseLine: false, + Aliases: []string{"cj"}, + Short: cronjobLong, + Long: cronjobLong, + Example: cronjobExample, Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) - cmdutil.CheckErr(o.Validate()) cmdutil.CheckErr(o.Run()) }, } @@ -98,7 +98,9 @@ func NewCmdCreateCronJob(f cmdutil.Factory, ioStreams genericclioptions.IOStream cmdutil.AddValidateFlags(cmd) cmdutil.AddDryRunFlag(cmd) cmd.Flags().StringVar(&o.Image, "image", o.Image, "Image name to run.") + cmd.MarkFlagRequired("image") cmd.Flags().StringVar(&o.Schedule, "schedule", o.Schedule, "A schedule in the Cron format the job should be run with.") + cmd.MarkFlagRequired("schedule") cmd.Flags().StringVar(&o.Restart, "restart", o.Restart, "job's restart policy. supported values: OnFailure, Never") cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") @@ -158,20 +160,8 @@ func (o *CreateCronJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, a return nil } -func (o *CreateCronJobOptions) Validate() error { - if len(o.Image) == 0 { - return fmt.Errorf("--image must be specified") - } - if len(o.Schedule) == 0 { - return fmt.Errorf("--schedule must be specified") - } - return nil -} - func (o *CreateCronJobOptions) Run() error { - var cronjob *batchv1beta1.CronJob - cronjob = o.createCronJob() - + cronjob := o.createCronJob() if o.DryRunStrategy != cmdutil.DryRunClient { createOptions := metav1.CreateOptions{} if o.FieldManager != "" { diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_job.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_job.go index a1604c9435c..96aa28dabbd 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_job.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_job.go @@ -85,10 +85,11 @@ func NewCreateJobOptions(ioStreams genericclioptions.IOStreams) *CreateJobOption func NewCmdCreateJob(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { o := NewCreateJobOptions(ioStreams) cmd := &cobra.Command{ - Use: "job NAME --image=image [--from=cronjob/name] -- [COMMAND] [args...]", - Short: jobLong, - Long: jobLong, - Example: jobExample, + Use: "job NAME --image=image [--from=cronjob/name] -- [COMMAND] [args...]", + DisableFlagsInUseLine: true, + Short: jobLong, + Long: jobLong, + Example: jobExample, Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, cmd, args)) cmdutil.CheckErr(o.Validate()) From ed1a0e945649095618bd3484c82b58046bcbd60c Mon Sep 17 00:00:00 2001 From: Maciej Szulik Date: Thu, 14 May 2020 18:34:40 +0200 Subject: [PATCH 2/2] Refactor create deployment and add --port flag --- .../pkg/cmd/create/create_deployment.go | 78 +++++++++++++------ .../pkg/cmd/create/create_deployment_test.go | 2 +- test/cmd/apps.sh | 2 +- 3 files changed, 56 insertions(+), 26 deletions(-) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_deployment.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_deployment.go index 18fcc6ea278..7da3a6f9ec0 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_deployment.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_deployment.go @@ -22,8 +22,9 @@ import ( "strings" "github.com/spf13/cobra" + appsv1 "k8s.io/api/apps/v1" - v1 "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" utilrand "k8s.io/apimachinery/pkg/util/rand" @@ -41,17 +42,26 @@ var ( Create a deployment with the specified name.`)) deploymentExample = templates.Examples(i18n.T(` - # Create a new deployment named my-dep that runs the busybox image. - kubectl create deployment my-dep --image=busybox`)) + # Create a deployment named my-dep that runs the busybox image. + kubectl create deployment my-dep --image=busybox + + # Create a deployment with command + kubectl create deployment my-dep --image=busybox -- date + + # Create a deployment named my-dep that runs the busybox image and expose port 5701. + kubectl create deployment my-dep --image=busybox --port=5701`)) ) -// DeploymentOpts is returned by NewCmdCreateDeployment -type DeploymentOpts struct { +// CreateDeploymentOptions is returned by NewCmdCreateDeployment +type CreateDeploymentOptions struct { PrintFlags *genericclioptions.PrintFlags - PrintObj func(obj runtime.Object) error + + PrintObj func(obj runtime.Object) error Name string Images []string + Port int32 + Command []string Namespace string EnforceNamespace bool FieldManager string @@ -63,46 +73,55 @@ type DeploymentOpts struct { genericclioptions.IOStreams } -// NewCmdCreateDeployment is a macro command to create a new deployment. -// This command is better known to users as `kubectl create deployment`. -func NewCmdCreateDeployment(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { - options := &DeploymentOpts{ +func NewCreateCreateDeploymentOptions(ioStreams genericclioptions.IOStreams) *CreateDeploymentOptions { + return &CreateDeploymentOptions{ + Port: -1, PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), IOStreams: ioStreams, } +} +// NewCmdCreateDeployment is a macro command to create a new deployment. +// This command is better known to users as `kubectl create deployment`. +func NewCmdCreateDeployment(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command { + o := NewCreateCreateDeploymentOptions(ioStreams) cmd := &cobra.Command{ - Use: "deployment NAME --image=image [--dry-run=server|client|none]", + Use: "deployment NAME --image=image -- [COMMAND] [args...]", DisableFlagsInUseLine: true, Aliases: []string{"deploy"}, Short: deploymentLong, Long: deploymentLong, Example: deploymentExample, Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(options.Complete(f, cmd, args)) - cmdutil.CheckErr(options.Run()) + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.Run()) }, } - options.PrintFlags.AddFlags(cmd) + o.PrintFlags.AddFlags(cmd) cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddValidateFlags(cmd) cmdutil.AddGeneratorFlags(cmd, "") - cmd.Flags().StringSliceVar(&options.Images, "image", []string{}, "Image name to run.") - _ = cmd.MarkFlagRequired("image") - cmdutil.AddFieldManagerFlagVar(cmd, &options.FieldManager, "kubectl-create") + cmd.Flags().StringSliceVar(&o.Images, "image", o.Images, "Image names to run.") + cmd.MarkFlagRequired("image") + cmd.Flags().Int32Var(&o.Port, "port", o.Port, "The port that this container exposes.") + cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") return cmd } // Complete completes all the options -func (o *DeploymentOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { +func (o *CreateDeploymentOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { name, err := NameFromCommandArgs(cmd, args) if err != nil { return err } o.Name = name + if len(args) > 1 { + o.Command = args[1:] + } clientConfig, err := f.ToRESTConfig() if err != nil { @@ -144,8 +163,15 @@ func (o *DeploymentOpts) Complete(f cmdutil.Factory, cmd *cobra.Command, args [] return nil } +func (o *CreateDeploymentOptions) Validate() error { + if len(o.Images) > 1 && len(o.Command) > 0 { + return fmt.Errorf("cannot specify multiple --image options and command") + } + return nil +} + // Run performs the execution of 'create deployment' sub command -func (o *DeploymentOpts) Run() error { +func (o *CreateDeploymentOptions) Run() error { deploy := o.createDeployment() if o.DryRunStrategy != cmdutil.DryRunClient { @@ -169,7 +195,7 @@ func (o *DeploymentOpts) Run() error { return o.PrintObj(deploy) } -func (o *DeploymentOpts) createDeployment() *appsv1.Deployment { +func (o *CreateDeploymentOptions) createDeployment() *appsv1.Deployment { one := int32(1) labels := map[string]string{"app": o.Name} selector := metav1.LabelSelector{MatchLabels: labels} @@ -188,7 +214,7 @@ func (o *DeploymentOpts) createDeployment() *appsv1.Deployment { Spec: appsv1.DeploymentSpec{ Replicas: &one, Selector: &selector, - Template: v1.PodTemplateSpec{ + Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ Labels: labels, }, @@ -200,8 +226,8 @@ func (o *DeploymentOpts) createDeployment() *appsv1.Deployment { // buildPodSpec parses the image strings and assemble them into the Containers // of a PodSpec. This is all you need to create the PodSpec for a deployment. -func (o *DeploymentOpts) buildPodSpec() v1.PodSpec { - podSpec := v1.PodSpec{Containers: []v1.Container{}} +func (o *CreateDeploymentOptions) buildPodSpec() corev1.PodSpec { + podSpec := corev1.PodSpec{Containers: []corev1.Container{}} for _, imageString := range o.Images { // Retain just the image name imageSplit := strings.Split(imageString, "/") @@ -214,7 +240,11 @@ func (o *DeploymentOpts) buildPodSpec() v1.PodSpec { name = strings.Split(name, "@")[0] } name = sanitizeAndUniquify(name) - podSpec.Containers = append(podSpec.Containers, v1.Container{Name: name, Image: imageString}) + podSpec.Containers = append(podSpec.Containers, corev1.Container{ + Name: name, + Image: imageString, + Command: o.Command, + }) } return podSpec } diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_deployment_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_deployment_test.go index ba7a8d32303..878f2a3c42f 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/create/create_deployment_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/create/create_deployment_test.go @@ -83,7 +83,7 @@ func TestCreateDeploymentNoImage(t *testing.T) { ioStreams := genericclioptions.NewTestIOStreamsDiscard() cmd := NewCmdCreateDeployment(tf, ioStreams) cmd.Flags().Set("output", "name") - options := &DeploymentOpts{ + options := &CreateDeploymentOptions{ PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), DryRunStrategy: cmdutil.DryRunClient, IOStreams: ioStreams, diff --git a/test/cmd/apps.sh b/test/cmd/apps.sh index d7056eb4885..c119a9b8b35 100755 --- a/test/cmd/apps.sh +++ b/test/cmd/apps.sh @@ -197,7 +197,7 @@ run_deployment_tests() { kubectl delete deployment test-nginx-extensions "${kube_flags[@]:?}" # Test kubectl create deployment - kubectl create deployment test-nginx-apps --image=k8s.gcr.io/nginx:test-cmd --generator=deployment-basic/apps.v1 + kubectl create deployment test-nginx-apps --image=k8s.gcr.io/nginx:test-cmd # Post-Condition: Deployment "nginx" is created. kube::test::get_object_assert 'deploy test-nginx-apps' "{{${container_name_field:?}}}" 'nginx' # and new generator was used, iow. new defaults are applied