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) }