From 5b57c2db0067c8a16681fe9ec57ace3ad0342c8f Mon Sep 17 00:00:00 2001 From: Edmund Rhudy Date: Thu, 15 Feb 2018 16:36:25 -0500 Subject: [PATCH 1/2] Fixes #47538: Add functionality for manually creating a Job instance from a CronJob This changeset adds the command `kubectl create job` with the flag `--from-cronjob`, which allows a user to create a Job from a CronJob via the CLI. --- docs/.generated_docs | 2 + docs/man/man1/kubectl-create-job.1 | 3 + docs/user-guide/kubectl/kubectl_create_job.md | 3 + pkg/kubectl/cmd/BUILD | 8 + pkg/kubectl/cmd/create.go | 1 + pkg/kubectl/cmd/create_job.go | 147 +++++++++++++++++ pkg/kubectl/cmd/create_job_test.go | 149 ++++++++++++++++++ 7 files changed, 313 insertions(+) create mode 100644 docs/man/man1/kubectl-create-job.1 create mode 100644 docs/user-guide/kubectl/kubectl_create_job.md create mode 100644 pkg/kubectl/cmd/create_job.go create mode 100644 pkg/kubectl/cmd/create_job_test.go diff --git a/docs/.generated_docs b/docs/.generated_docs index 4886eea60ba..18e655dfcfc 100644 --- a/docs/.generated_docs +++ b/docs/.generated_docs @@ -172,6 +172,7 @@ docs/man/man1/kubectl-create-clusterrole.1 docs/man/man1/kubectl-create-clusterrolebinding.1 docs/man/man1/kubectl-create-configmap.1 docs/man/man1/kubectl-create-deployment.1 +docs/man/man1/kubectl-create-job.1 docs/man/man1/kubectl-create-namespace.1 docs/man/man1/kubectl-create-poddisruptionbudget.1 docs/man/man1/kubectl-create-priorityclass.1 @@ -272,6 +273,7 @@ docs/user-guide/kubectl/kubectl_create_clusterrole.md docs/user-guide/kubectl/kubectl_create_clusterrolebinding.md docs/user-guide/kubectl/kubectl_create_configmap.md docs/user-guide/kubectl/kubectl_create_deployment.md +docs/user-guide/kubectl/kubectl_create_job.md docs/user-guide/kubectl/kubectl_create_namespace.md docs/user-guide/kubectl/kubectl_create_poddisruptionbudget.md docs/user-guide/kubectl/kubectl_create_priorityclass.md diff --git a/docs/man/man1/kubectl-create-job.1 b/docs/man/man1/kubectl-create-job.1 new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/man/man1/kubectl-create-job.1 @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/docs/user-guide/kubectl/kubectl_create_job.md b/docs/user-guide/kubectl/kubectl_create_job.md new file mode 100644 index 00000000000..b6fd7a0f989 --- /dev/null +++ b/docs/user-guide/kubectl/kubectl_create_job.md @@ -0,0 +1,3 @@ +This file is autogenerated, but we've stopped checking such files into the +repository to reduce the need for rebases. Please run hack/generate-docs.sh to +populate this file. diff --git a/pkg/kubectl/cmd/BUILD b/pkg/kubectl/cmd/BUILD index 0f57c1247dd..7fe7a871687 100644 --- a/pkg/kubectl/cmd/BUILD +++ b/pkg/kubectl/cmd/BUILD @@ -28,6 +28,7 @@ go_library( "create_clusterrolebinding.go", "create_configmap.go", "create_deployment.go", + "create_job.go", "create_namespace.go", "create_pdb.go", "create_priorityclass.go", @@ -132,6 +133,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/jsonmergepatch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", @@ -142,6 +144,8 @@ go_library( "//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library", "//vendor/k8s.io/client-go/discovery:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/batch/v1:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/typed/batch/v1beta1:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", @@ -171,6 +175,7 @@ go_test( "create_clusterrolebinding_test.go", "create_configmap_test.go", "create_deployment_test.go", + "create_job_test.go", "create_namespace_test.go", "create_pdb_test.go", "create_priorityclass_test.go", @@ -231,6 +236,8 @@ go_test( "//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/gopkg.in/yaml.v2:go_default_library", + "//vendor/k8s.io/api/batch/v1:go_default_library", + "//vendor/k8s.io/api/batch/v1beta1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/policy/v1beta1:go_default_library", "//vendor/k8s.io/api/rbac/v1:go_default_library", @@ -252,6 +259,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apimachinery/pkg/version:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library", + "//vendor/k8s.io/client-go/kubernetes/fake:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest/fake:go_default_library", "//vendor/k8s.io/client-go/testing:go_default_library", diff --git a/pkg/kubectl/cmd/create.go b/pkg/kubectl/cmd/create.go index 8c02c098e3a..6784fef85e0 100644 --- a/pkg/kubectl/cmd/create.go +++ b/pkg/kubectl/cmd/create.go @@ -115,6 +115,7 @@ func NewCmdCreate(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { cmd.AddCommand(NewCmdCreateRoleBinding(f, out)) cmd.AddCommand(NewCmdCreatePodDisruptionBudget(f, out)) cmd.AddCommand(NewCmdCreatePriorityClass(f, out)) + cmd.AddCommand(NewCmdCreateJob(f, out)) return cmd } diff --git a/pkg/kubectl/cmd/create_job.go b/pkg/kubectl/cmd/create_job.go new file mode 100644 index 00000000000..ea3709db04c --- /dev/null +++ b/pkg/kubectl/cmd/create_job.go @@ -0,0 +1,147 @@ +/* +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 cmd + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + + batchv1 "k8s.io/api/batch/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/rand" + clientbatchv1 "k8s.io/client-go/kubernetes/typed/batch/v1" + clientbatchv1beta1 "k8s.io/client-go/kubernetes/typed/batch/v1beta1" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/util/i18n" +) + +var ( + jobLong = templates.LongDesc(i18n.T(` + Create a Job to execute immediately from a specified CronJob.`)) + + jobExample = templates.Examples(i18n.T(` + # Create a Job from a CronJob named "a-cronjob" + kubectl create job --from-cronjob=a-cronjob`)) +) + +type CreateJobOptions struct { + FromCronJob string + + OutputFormat string + Namespace string + V1Beta1Client clientbatchv1beta1.BatchV1beta1Interface + V1Client clientbatchv1.BatchV1Interface + Mapper meta.RESTMapper + Out io.Writer + PrintObject func(obj runtime.Object) error + PrintSuccess func(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource, name string, dryRun bool, operation string) +} + +// NewCmdCreateJob is a command to ease creating Jobs from CronJobs. +func NewCmdCreateJob(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command { + c := &CreateJobOptions{ + Out: cmdOut, + } + cmd := &cobra.Command{ + Use: "job --from-cronjob=CRONJOB", + Short: jobLong, + Long: jobLong, + Example: jobExample, + Run: func(cmd *cobra.Command, args []string) { + cmdutil.CheckErr(c.Complete(f, cmd, args)) + cmdutil.CheckErr(c.RunCreateJob()) + }, + } + cmdutil.AddApplyAnnotationFlags(cmd) + cmdutil.AddValidateFlags(cmd) + cmdutil.AddPrinterFlags(cmd) + cmd.Flags().String("from-cronjob", "", "Specify the name of the CronJob to create a Job from.") + + return cmd +} + +func (c *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) (err error) { + c.FromCronJob = cmdutil.GetFlagString(cmd, "from-cronjob") + + // Complete other options for Run. + c.Mapper, _ = f.Object() + + c.OutputFormat = cmdutil.GetFlagString(cmd, "output") + + c.Namespace, _, err = f.DefaultNamespace() + if err != nil { + return err + } + + c.PrintObject = func(obj runtime.Object) error { + return f.PrintObject(cmd, obj, c.Out) + } + + // need two client sets to deal with the differing versions of CronJobs and Jobs + clientset, err := f.KubernetesClientSet() + if err != nil { + return err + } + if c.V1Client == nil { + c.V1Client = clientset.BatchV1() + } + if c.V1Beta1Client == nil { + c.V1Beta1Client = clientset.BatchV1beta1() + } + + return nil +} + +func (c *CreateJobOptions) RunCreateJob() (err error) { + cronjob, err := c.V1Beta1Client.CronJobs(c.Namespace).Get(c.FromCronJob, metav1.GetOptions{}) + + if err != nil { + return fmt.Errorf("failed to fetch job: %v", err) + } + + annotations := make(map[string]string) + annotations["cronjob.kubernetes.io/instantiate"] = "manual" + + labels := make(map[string]string) + for k, v := range cronjob.Spec.JobTemplate.Labels { + labels[k] = v + } + + jobToCreate := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + // job name cannot exceed DNS1053LabelMaxLength (52 characters) + Name: cronjob.Name + "-manual-" + rand.String(3), + Namespace: c.Namespace, + Annotations: annotations, + Labels: labels, + }, + Spec: cronjob.Spec.JobTemplate.Spec, + } + + result, err := c.V1Client.Jobs(c.Namespace).Create(jobToCreate) + + if err != nil { + return fmt.Errorf("failed to create job: %v", err) + } + + return c.PrintObject(result) +} diff --git a/pkg/kubectl/cmd/create_job_test.go b/pkg/kubectl/cmd/create_job_test.go new file mode 100644 index 00000000000..08c73ce3d05 --- /dev/null +++ b/pkg/kubectl/cmd/create_job_test.go @@ -0,0 +1,149 @@ +/* +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 cmd + +import ( + "bytes" + "fmt" + "strings" + "testing" + + "k8s.io/apimachinery/pkg/runtime" + + 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" + fake "k8s.io/client-go/kubernetes/fake" + clienttesting "k8s.io/client-go/testing" + cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" +) + +var submittedJob *batchv1.Job + +func TestCreateJobFromCronJob(t *testing.T) { + testNamespaceName := "test" + testCronJobName := "test-cronjob" + testImageName := "fake" + + expectedLabels := make(map[string]string) + expectedAnnotations := make(map[string]string) + expectedLabels["test-label"] = "test-value" + expectedAnnotations["cronjob.kubernetes.io/instantiate"] = "manual" + + expectJob := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespaceName, + Labels: expectedLabels, + Annotations: expectedAnnotations, + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Image: testImageName}, + }, + }, + }, + }, + } + + cronJobToCreate := &batchv1beta1.CronJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: testCronJobName, + Namespace: testNamespaceName, + }, + Spec: batchv1beta1.CronJobSpec{ + Schedule: "* * * * *", + JobTemplate: batchv1beta1.JobTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespaceName, + Labels: expectedLabels, + }, + Spec: expectJob.Spec, + }, + }, + } + + clientset := fake.Clientset{} + clientset.PrependReactor("get", "cronjobs", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { + return true, cronJobToCreate, nil + }) + clientset.PrependReactor("create", "jobs", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { + ca := action.(clienttesting.CreateAction) + submittedJob = ca.GetObject().(*batchv1.Job) + return true, expectJob, nil + }) + + f, _, _, _ := cmdtesting.NewAPIFactory() + buf := bytes.NewBuffer([]byte{}) + + mapper, _ := f.Object() + cmdOptions := &CreateJobOptions{ + Out: buf, + OutputFormat: "na", + Namespace: testNamespaceName, + FromCronJob: testCronJobName, + V1Client: clientset.BatchV1(), + V1Beta1Client: clientset.BatchV1beta1(), + Mapper: mapper, + PrintObject: func(obj runtime.Object) error { + return nil + }, + } + cmdOptions.RunCreateJob() + + composedJobName := fmt.Sprintf("%s-manual-", testCronJobName) + if !strings.HasPrefix(submittedJob.ObjectMeta.Name, composedJobName) { + t.Errorf("expected '%s', got '%s'", composedJobName, submittedJob.ObjectMeta.Name) + } + + annotationsArrayLength := len(submittedJob.Annotations) + if annotationsArrayLength != 1 { + t.Errorf("expected length of annotations array to be 1, got %d", annotationsArrayLength) + } + + if v, ok := submittedJob.Annotations["cronjob.kubernetes.io/instantiate"]; ok { + if v != "manual" { + t.Errorf("expected annotation cronjob.kubernetes.io/instantiate to be 'manual', got '%s'", v) + } + } else { + t.Errorf("expected annotation cronjob.kubernetes.io/instantiate to exist") + } + + labelsArrayLength := len(submittedJob.Labels) + if labelsArrayLength != 1 { + t.Errorf("expected length of labels array to be 1, got %d", labelsArrayLength) + } + + if v, ok := submittedJob.Labels["test-label"]; ok { + if v != "test-value" { + t.Errorf("expected label test-label to be 'test-value', got '%s'", v) + } + } else { + t.Errorf("expected label test-label to exist") + } + + containerArrayLength := len(submittedJob.Spec.Template.Spec.Containers) + if containerArrayLength != 1 { + t.Errorf("expected length of container array to be 1, got %d", containerArrayLength) + } + + if submittedJob.Spec.Template.Spec.Containers[0].Image != testImageName { + t.Errorf("expected '%s', got '%s'", testImageName, submittedJob.Spec.Template.Spec.Containers[0].Image) + } +} From 8bf4cfcf604edff311d01e5c36a7e684ed255072 Mon Sep 17 00:00:00 2001 From: Maciej Szulik Date: Wed, 21 Feb 2018 22:18:59 +0100 Subject: [PATCH 2/2] Add kubectl create job --from=cronjob/ --- pkg/kubectl/cmd/BUILD | 2 - pkg/kubectl/cmd/create_job.go | 103 ++++++++++++++--------------- pkg/kubectl/cmd/create_job_test.go | 80 ++++++++-------------- 3 files changed, 78 insertions(+), 107 deletions(-) diff --git a/pkg/kubectl/cmd/BUILD b/pkg/kubectl/cmd/BUILD index 7fe7a871687..4f76d722425 100644 --- a/pkg/kubectl/cmd/BUILD +++ b/pkg/kubectl/cmd/BUILD @@ -133,7 +133,6 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/jsonmergepatch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", @@ -145,7 +144,6 @@ go_library( "//vendor/k8s.io/client-go/discovery:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/batch/v1:go_default_library", - "//vendor/k8s.io/client-go/kubernetes/typed/batch/v1beta1:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library", "//vendor/k8s.io/client-go/kubernetes/typed/rbac/v1:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library", diff --git a/pkg/kubectl/cmd/create_job.go b/pkg/kubectl/cmd/create_job.go index ea3709db04c..382ca5c346e 100644 --- a/pkg/kubectl/cmd/create_job.go +++ b/pkg/kubectl/cmd/create_job.go @@ -23,37 +23,34 @@ import ( "github.com/spf13/cobra" batchv1 "k8s.io/api/batch/v1" - "k8s.io/apimachinery/pkg/api/meta" + batchv1beta1 "k8s.io/api/batch/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/rand" clientbatchv1 "k8s.io/client-go/kubernetes/typed/batch/v1" - clientbatchv1beta1 "k8s.io/client-go/kubernetes/typed/batch/v1beta1" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/resource" "k8s.io/kubernetes/pkg/kubectl/util/i18n" ) var ( jobLong = templates.LongDesc(i18n.T(` - Create a Job to execute immediately from a specified CronJob.`)) + Create a job with the specified name.`)) jobExample = templates.Examples(i18n.T(` - # Create a Job from a CronJob named "a-cronjob" - kubectl create job --from-cronjob=a-cronjob`)) + # Create a job from a CronJob named "a-cronjob" + kubectl create job --from=cronjob/a-cronjob`)) ) type CreateJobOptions struct { - FromCronJob string + Name string + From string - OutputFormat string - Namespace string - V1Beta1Client clientbatchv1beta1.BatchV1beta1Interface - V1Client clientbatchv1.BatchV1Interface - Mapper meta.RESTMapper - Out io.Writer - PrintObject func(obj runtime.Object) error - PrintSuccess func(mapper meta.RESTMapper, shortOutput bool, out io.Writer, resource, name string, dryRun bool, operation string) + Namespace string + Client clientbatchv1.BatchV1Interface + Out io.Writer + DryRun bool + Builder *resource.Builder + Cmd *cobra.Command } // NewCmdCreateJob is a command to ease creating Jobs from CronJobs. @@ -62,7 +59,7 @@ func NewCmdCreateJob(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command { Out: cmdOut, } cmd := &cobra.Command{ - Use: "job --from-cronjob=CRONJOB", + Use: "job NAME [--from-cronjob=CRONJOB]", Short: jobLong, Long: jobLong, Example: jobExample, @@ -74,74 +71,76 @@ func NewCmdCreateJob(f cmdutil.Factory, cmdOut io.Writer) *cobra.Command { cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddValidateFlags(cmd) cmdutil.AddPrinterFlags(cmd) - cmd.Flags().String("from-cronjob", "", "Specify the name of the CronJob to create a Job from.") + cmd.Flags().String("from", "", "The name of the resource to create a Job from (only cronjob is supported).") return cmd } func (c *CreateJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) (err error) { - c.FromCronJob = cmdutil.GetFlagString(cmd, "from-cronjob") - - // Complete other options for Run. - c.Mapper, _ = f.Object() - - c.OutputFormat = cmdutil.GetFlagString(cmd, "output") + if len(args) == 0 { + return cmdutil.UsageErrorf(cmd, "NAME is required") + } + c.Name = args[0] + c.From = cmdutil.GetFlagString(cmd, "from") c.Namespace, _, err = f.DefaultNamespace() if err != nil { return err } - c.PrintObject = func(obj runtime.Object) error { - return f.PrintObject(cmd, obj, c.Out) - } - - // need two client sets to deal with the differing versions of CronJobs and Jobs clientset, err := f.KubernetesClientSet() if err != nil { return err } - if c.V1Client == nil { - c.V1Client = clientset.BatchV1() - } - if c.V1Beta1Client == nil { - c.V1Beta1Client = clientset.BatchV1beta1() - } + c.Client = clientset.BatchV1() + c.Builder = f.NewBuilder() + c.Cmd = cmd return nil } -func (c *CreateJobOptions) RunCreateJob() (err error) { - cronjob, err := c.V1Beta1Client.CronJobs(c.Namespace).Get(c.FromCronJob, metav1.GetOptions{}) - +func (c *CreateJobOptions) RunCreateJob() error { + infos, err := c.Builder. + Unstructured(). + NamespaceParam(c.Namespace).DefaultNamespace(). + ResourceTypeOrNameArgs(false, c.From). + Flatten(). + Latest(). + Do(). + Infos() if err != nil { - return fmt.Errorf("failed to fetch job: %v", err) + return err + } + if len(infos) != 1 { + return fmt.Errorf("from must be an existing cronjob") + } + cronJob, ok := infos[0].AsVersioned().(*batchv1beta1.CronJob) + if !ok { + return fmt.Errorf("from must be an existing cronjob") } + return c.createJob(cronJob) +} + +func (c *CreateJobOptions) createJob(cronJob *batchv1beta1.CronJob) error { annotations := make(map[string]string) annotations["cronjob.kubernetes.io/instantiate"] = "manual" - - labels := make(map[string]string) - for k, v := range cronjob.Spec.JobTemplate.Labels { - labels[k] = v + for k, v := range cronJob.Spec.JobTemplate.Annotations { + annotations[k] = v } - jobToCreate := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ - // job name cannot exceed DNS1053LabelMaxLength (52 characters) - Name: cronjob.Name + "-manual-" + rand.String(3), + Name: c.Name, Namespace: c.Namespace, Annotations: annotations, - Labels: labels, + Labels: cronJob.Spec.JobTemplate.Labels, }, - Spec: cronjob.Spec.JobTemplate.Spec, + Spec: cronJob.Spec.JobTemplate.Spec, } - result, err := c.V1Client.Jobs(c.Namespace).Create(jobToCreate) - + job, err := c.Client.Jobs(c.Namespace).Create(jobToCreate) if err != nil { return fmt.Errorf("failed to create job: %v", err) } - - return c.PrintObject(result) + return cmdutil.PrintObject(c.Cmd, job, c.Out) } diff --git a/pkg/kubectl/cmd/create_job_test.go b/pkg/kubectl/cmd/create_job_test.go index 08c73ce3d05..4fb54df19c2 100644 --- a/pkg/kubectl/cmd/create_job_test.go +++ b/pkg/kubectl/cmd/create_job_test.go @@ -18,32 +18,28 @@ package cmd import ( "bytes" - "fmt" - "strings" "testing" - "k8s.io/apimachinery/pkg/runtime" - 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" fake "k8s.io/client-go/kubernetes/fake" clienttesting "k8s.io/client-go/testing" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" ) -var submittedJob *batchv1.Job - func TestCreateJobFromCronJob(t *testing.T) { + var submittedJob *batchv1.Job testNamespaceName := "test" testCronJobName := "test-cronjob" + testJobName := "test-job" testImageName := "fake" expectedLabels := make(map[string]string) expectedAnnotations := make(map[string]string) expectedLabels["test-label"] = "test-value" - expectedAnnotations["cronjob.kubernetes.io/instantiate"] = "manual" expectJob := &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ @@ -62,7 +58,7 @@ func TestCreateJobFromCronJob(t *testing.T) { }, } - cronJobToCreate := &batchv1beta1.CronJob{ + cronJob := &batchv1beta1.CronJob{ ObjectMeta: metav1.ObjectMeta{ Name: testCronJobName, Namespace: testNamespaceName, @@ -80,69 +76,47 @@ func TestCreateJobFromCronJob(t *testing.T) { } clientset := fake.Clientset{} - clientset.PrependReactor("get", "cronjobs", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { - return true, cronJobToCreate, nil - }) clientset.PrependReactor("create", "jobs", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { ca := action.(clienttesting.CreateAction) submittedJob = ca.GetObject().(*batchv1.Job) return true, expectJob, nil }) - - f, _, _, _ := cmdtesting.NewAPIFactory() + f, _ := cmdtesting.NewAPIFactory() buf := bytes.NewBuffer([]byte{}) - - mapper, _ := f.Object() cmdOptions := &CreateJobOptions{ - Out: buf, - OutputFormat: "na", - Namespace: testNamespaceName, - FromCronJob: testCronJobName, - V1Client: clientset.BatchV1(), - V1Beta1Client: clientset.BatchV1beta1(), - Mapper: mapper, - PrintObject: func(obj runtime.Object) error { - return nil - }, - } - cmdOptions.RunCreateJob() - - composedJobName := fmt.Sprintf("%s-manual-", testCronJobName) - if !strings.HasPrefix(submittedJob.ObjectMeta.Name, composedJobName) { - t.Errorf("expected '%s', got '%s'", composedJobName, submittedJob.ObjectMeta.Name) + Name: testJobName, + Namespace: testNamespaceName, + Client: clientset.BatchV1(), + Out: buf, + Cmd: NewCmdCreateJob(f, buf), } - annotationsArrayLength := len(submittedJob.Annotations) - if annotationsArrayLength != 1 { - t.Errorf("expected length of annotations array to be 1, got %d", annotationsArrayLength) + err := cmdOptions.createJob(cronJob) + if err != nil { + t.Errorf("unexpected error: %v", err) } - if v, ok := submittedJob.Annotations["cronjob.kubernetes.io/instantiate"]; ok { - if v != "manual" { - t.Errorf("expected annotation cronjob.kubernetes.io/instantiate to be 'manual', got '%s'", v) - } - } else { - t.Errorf("expected annotation cronjob.kubernetes.io/instantiate to exist") + if submittedJob.ObjectMeta.Name != testJobName { + t.Errorf("expected '%s', got '%s'", testJobName, submittedJob.ObjectMeta.Name) } - labelsArrayLength := len(submittedJob.Labels) - if labelsArrayLength != 1 { - t.Errorf("expected length of labels array to be 1, got %d", labelsArrayLength) + if l := len(submittedJob.Annotations); l != 1 { + t.Errorf("expected length of annotations array to be 1, got %d", l) + } + if v, ok := submittedJob.Annotations["cronjob.kubernetes.io/instantiate"]; !ok || v != "manual" { + t.Errorf("expected annotation cronjob.kubernetes.io/instantiate=manual to exist, got '%s'", v) } - if v, ok := submittedJob.Labels["test-label"]; ok { - if v != "test-value" { - t.Errorf("expected label test-label to be 'test-value', got '%s'", v) - } - } else { - t.Errorf("expected label test-label to exist") + if l := len(submittedJob.Labels); l != 1 { + t.Errorf("expected length of labels array to be 1, got %d", l) + } + if v, ok := submittedJob.Labels["test-label"]; !ok || v != "test-value" { + t.Errorf("expected label test-label=test-value to to exist, got '%s'", v) } - containerArrayLength := len(submittedJob.Spec.Template.Spec.Containers) - if containerArrayLength != 1 { - t.Errorf("expected length of container array to be 1, got %d", containerArrayLength) + if l := len(submittedJob.Spec.Template.Spec.Containers); l != 1 { + t.Errorf("expected length of container array to be 1, got %d", l) } - if submittedJob.Spec.Template.Spec.Containers[0].Image != testImageName { t.Errorf("expected '%s', got '%s'", testImageName, submittedJob.Spec.Template.Spec.Containers[0].Image) }