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

This commit is contained in:
Maciej Szulik 2018-02-21 22:18:59 +01:00
parent 5b57c2db00
commit 8bf4cfcf60
No known key found for this signature in database
GPG Key ID: F15E55D276FA84C4
3 changed files with 78 additions and 107 deletions

View File

@ -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",

View File

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

View File

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