Add kubectl create job --from=cronjob/<name>

parent 5b57c2db
......@@ -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",
......
......@@ -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
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)
Name string
From 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)
}
......@@ -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
},
Name: testJobName,
Namespace: testNamespaceName,
Client: clientset.BatchV1(),
Out: buf,
Cmd: NewCmdCreateJob(f, buf),
}
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)
err := cmdOptions.createJob(cronJob)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
annotationsArrayLength := len(submittedJob.Annotations)
if annotationsArrayLength != 1 {
t.Errorf("expected length of annotations array to be 1, got %d", annotationsArrayLength)
if submittedJob.ObjectMeta.Name != testJobName {
t.Errorf("expected '%s', got '%s'", testJobName, submittedJob.ObjectMeta.Name)
}
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 l := len(submittedJob.Annotations); l != 1 {
t.Errorf("expected length of annotations array to be 1, got %d", l)
}
labelsArrayLength := len(submittedJob.Labels)
if labelsArrayLength != 1 {
t.Errorf("expected length of labels array to be 1, got %d", labelsArrayLength)
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)
}
containerArrayLength := len(submittedJob.Spec.Template.Spec.Containers)
if containerArrayLength != 1 {
t.Errorf("expected length of container array to be 1, got %d", containerArrayLength)
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)
}
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)
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment