diff --git a/pkg/kubectl/cmd/create/BUILD b/pkg/kubectl/cmd/create/BUILD index faa5e38e2e..1ee4e8a37a 100644 --- a/pkg/kubectl/cmd/create/BUILD +++ b/pkg/kubectl/cmd/create/BUILD @@ -7,6 +7,7 @@ go_library( "create_clusterrole.go", "create_clusterrolebinding.go", "create_configmap.go", + "create_cronjob.go", "create_deployment.go", "create_job.go", "create_namespace.go", @@ -45,6 +46,7 @@ go_library( "//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource:go_default_library", "//staging/src/k8s.io/client-go/dynamic:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/typed/batch/v1:go_default_library", + "//staging/src/k8s.io/client-go/kubernetes/typed/batch/v1beta1:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library", "//staging/src/k8s.io/component-base/cli/flag:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library", @@ -58,6 +60,7 @@ go_test( "create_clusterrole_test.go", "create_clusterrolebinding_test.go", "create_configmap_test.go", + "create_cronjob_test.go", "create_deployment_test.go", "create_job_test.go", "create_namespace_test.go", diff --git a/pkg/kubectl/cmd/create/create.go b/pkg/kubectl/cmd/create/create.go index f005ee37e2..a4920af4f0 100644 --- a/pkg/kubectl/cmd/create/create.go +++ b/pkg/kubectl/cmd/create/create.go @@ -146,6 +146,7 @@ func NewCmdCreate(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cob cmd.AddCommand(NewCmdCreatePodDisruptionBudget(f, ioStreams)) cmd.AddCommand(NewCmdCreatePriorityClass(f, ioStreams)) cmd.AddCommand(NewCmdCreateJob(f, ioStreams)) + cmd.AddCommand(NewCmdCreateCronJob(f, ioStreams)) return cmd } diff --git a/pkg/kubectl/cmd/create/create_cronjob.go b/pkg/kubectl/cmd/create/create_cronjob.go new file mode 100644 index 0000000000..22a5430d51 --- /dev/null +++ b/pkg/kubectl/cmd/create/create_cronjob.go @@ -0,0 +1,205 @@ +/* +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 create + +import ( + "fmt" + + "github.com/spf13/cobra" + + batchv1 "k8s.io/api/batch/v1" + batchv1beta1 "k8s.io/api/batch/v1beta1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/cli-runtime/pkg/genericclioptions/resource" + batchv1beta1client "k8s.io/client-go/kubernetes/typed/batch/v1beta1" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/scheme" + "k8s.io/kubernetes/pkg/kubectl/util/templates" +) + +var ( + cronjobLong = templates.LongDesc(` + Create a cronjob with the specified name.`) + + cronjobExample = templates.Examples(` + # Create a cronjob + kubectl create cronjob my-job --image=busybox + + # Create a cronjob with command + kubectl create cronjob my-job --image=busybox -- date + + # Create a cronjob with schedule + kubectl create cronjob test-job --image=busybox --schedule="*/1 * * * *"`) +) + +type CreateCronJobOptions struct { + PrintFlags *genericclioptions.PrintFlags + + PrintObj func(obj runtime.Object) error + + Name string + Image string + Schedule string + Command []string + Restart string + + Namespace string + Client batchv1beta1client.BatchV1beta1Interface + DryRun bool + Builder *resource.Builder + Cmd *cobra.Command + + genericclioptions.IOStreams +} + +func NewCreateCronJobOptions(ioStreams genericclioptions.IOStreams) *CreateCronJobOptions { + return &CreateCronJobOptions{ + PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), + IOStreams: ioStreams, + } +} + +// NewCmdCreateCronJob is a command to to create CronJobs. +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, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(o.Complete(f, cmd, args)) + cmdutil.CheckErr(o.Validate()) + cmdutil.CheckErr(o.Run()) + }, + } + + o.PrintFlags.AddFlags(cmd) + + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddValidateFlags(cmd) + cmdutil.AddDryRunFlag(cmd) + cmd.Flags().StringVar(&o.Image, "image", o.Image, "Image name to run.") + cmd.Flags().StringVar(&o.Schedule, "schedule", o.Schedule, "A schedule in the Cron format the job should be run with.") + cmd.Flags().StringVar(&o.Restart, "restart", o.Restart, "job's restart policy. supported values: OnFailure, Never") + + return cmd +} + +func (o *CreateCronJobOptions) 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:] + } + if len(o.Restart) == 0 { + o.Restart = "OnFailure" + } + + clientConfig, err := f.ToRESTConfig() + if err != nil { + return err + } + o.Client, err = batchv1beta1client.NewForConfig(clientConfig) + if err != nil { + return err + } + + o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + o.Builder = f.NewBuilder() + o.Cmd = cmd + + o.DryRun = cmdutil.GetDryRunFlag(cmd) + if o.DryRun { + o.PrintFlags.Complete("%s (dry run)") + } + printer, err := o.PrintFlags.ToPrinter() + if err != nil { + return err + } + o.PrintObj = func(obj runtime.Object) error { + return printer.PrintObj(obj, o.Out) + } + + 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() + + if !o.DryRun { + var err error + cronjob, err = o.Client.CronJobs(o.Namespace).Create(cronjob) + if err != nil { + return fmt.Errorf("failed to create cronjob: %v", err) + } + } + + return o.PrintObj(cronjob) +} + +func (o *CreateCronJobOptions) createCronJob() *batchv1beta1.CronJob { + return &batchv1beta1.CronJob{ + TypeMeta: metav1.TypeMeta{APIVersion: batchv1beta1.SchemeGroupVersion.String(), Kind: "CronJob"}, + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + }, + Spec: batchv1beta1.CronJobSpec{ + Schedule: o.Schedule, + JobTemplate: batchv1beta1.JobTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: o.Name, + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: o.Name, + Image: o.Image, + Command: o.Command, + }, + }, + RestartPolicy: corev1.RestartPolicy(o.Restart), + }, + }, + }, + }, + }, + } +} diff --git a/pkg/kubectl/cmd/create/create_cronjob_test.go b/pkg/kubectl/cmd/create/create_cronjob_test.go new file mode 100644 index 0000000000..cf0f911dae --- /dev/null +++ b/pkg/kubectl/cmd/create/create_cronjob_test.go @@ -0,0 +1,121 @@ +/* +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 create + +import ( + "testing" + + batchv1 "k8s.io/api/batch/v1" + batchv1beta1 "k8s.io/api/batch/v1beta1" + corev1 "k8s.io/api/core/v1" + apiequality "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestCreateCronJob(t *testing.T) { + cronjobName := "test-job" + tests := map[string]struct { + image string + command []string + schedule string + restart string + expected *batchv1beta1.CronJob + }{ + "just image and OnFailure restart policy": { + image: "busybox", + schedule: "0/5 * * * ?", + restart: "OnFailure", + expected: &batchv1beta1.CronJob{ + TypeMeta: metav1.TypeMeta{APIVersion: batchv1beta1.SchemeGroupVersion.String(), Kind: "CronJob"}, + ObjectMeta: metav1.ObjectMeta{ + Name: cronjobName, + }, + Spec: batchv1beta1.CronJobSpec{ + Schedule: "0/5 * * * ?", + JobTemplate: batchv1beta1.JobTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: cronjobName, + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: cronjobName, + Image: "busybox", + }, + }, + RestartPolicy: corev1.RestartPolicyOnFailure, + }, + }, + }, + }, + }, + }, + }, + "image, command , schedule and Never restart policy": { + image: "busybox", + command: []string{"date"}, + schedule: "0/5 * * * ?", + restart: "Never", + expected: &batchv1beta1.CronJob{ + TypeMeta: metav1.TypeMeta{APIVersion: batchv1beta1.SchemeGroupVersion.String(), Kind: "CronJob"}, + ObjectMeta: metav1.ObjectMeta{ + Name: cronjobName, + }, + Spec: batchv1beta1.CronJobSpec{ + Schedule: "0/5 * * * ?", + JobTemplate: batchv1beta1.JobTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: cronjobName, + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: cronjobName, + Image: "busybox", + Command: []string{"date"}, + }, + }, + RestartPolicy: corev1.RestartPolicyNever, + }, + }, + }, + }, + }, + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + o := &CreateCronJobOptions{ + Name: cronjobName, + Image: tc.image, + Command: tc.command, + Schedule: tc.schedule, + Restart: tc.restart, + } + cronjob := o.createCronJob() + if !apiequality.Semantic.DeepEqual(cronjob, tc.expected) { + t.Errorf("expected:\n%#v\ngot:\n%#v", tc.expected, cronjob) + } + }) + } +}