Fix batch/v1 CronJob support in create, describe and polymorphichelpers

Kubernetes-commit: a68582d6086ffc8608779b0459de8d387042eed1
This commit is contained in:
Maciej Szulik 2021-03-09 09:44:02 +01:00 committed by Kubernetes Publisher
parent 00e8c91bc4
commit 69bc307556
5 changed files with 193 additions and 24 deletions

View File

@ -29,6 +29,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
batchv1client "k8s.io/client-go/kubernetes/typed/batch/v1"
batchv1beta1client "k8s.io/client-go/kubernetes/typed/batch/v1beta1"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/scheme"
@ -63,7 +64,8 @@ type CreateCronJobOptions struct {
Namespace string
EnforceNamespace bool
Client batchv1beta1client.BatchV1beta1Interface
ClientBeta batchv1beta1client.BatchV1beta1Interface
Client batchv1client.BatchV1Interface
DryRunStrategy cmdutil.DryRunStrategy
DryRunVerifier *resource.DryRunVerifier
Builder *resource.Builder
@ -130,10 +132,34 @@ func (o *CreateCronJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, a
if err != nil {
return err
}
o.Client, err = batchv1beta1client.NewForConfig(clientConfig)
// TODO: drop this condition when beta disappears in 1.25
clientset, err := f.KubernetesClientSet()
if err != nil {
return err
}
resources, err := clientset.Discovery().ServerResourcesForGroupVersion(batchv1.SchemeGroupVersion.String())
if err != nil {
return fmt.Errorf("failed to discover supported resources: %v", err)
}
found := false
for _, serverResource := range resources.APIResources {
if serverResource.Name == "cronjobs" {
found = true
break
}
}
if found {
o.Client, err = batchv1client.NewForConfig(clientConfig)
if err != nil {
return err
}
} else {
o.ClientBeta, err = batchv1beta1client.NewForConfig(clientConfig)
if err != nil {
return err
}
}
o.Namespace, o.EnforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
@ -166,9 +192,36 @@ func (o *CreateCronJobOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, a
// Run performs the execution of 'create cronjob' sub command
func (o *CreateCronJobOptions) Run() error {
cronjob := o.createCronJob()
if o.Client != nil {
cronJob := o.createCronJob()
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, cronJob, scheme.DefaultJSONEncoder()); err != nil {
return err
}
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, cronjob, scheme.DefaultJSONEncoder()); err != nil {
if o.DryRunStrategy != cmdutil.DryRunClient {
createOptions := metav1.CreateOptions{}
if o.FieldManager != "" {
createOptions.FieldManager = o.FieldManager
}
if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(cronJob.GroupVersionKind()); err != nil {
return err
}
createOptions.DryRun = []string{metav1.DryRunAll}
}
var err error
cronJob, err = o.Client.CronJobs(o.Namespace).Create(context.TODO(), cronJob, createOptions)
if err != nil {
return fmt.Errorf("failed to create cronjob: %v", err)
}
}
return o.PrintObj(cronJob)
}
// TODO: drop this condition when beta disappears in 1.25
cronJobBeta := o.createCronJobBeta()
if err := util.CreateOrUpdateAnnotation(o.CreateAnnotation, cronJobBeta, scheme.DefaultJSONEncoder()); err != nil {
return err
}
@ -178,22 +231,23 @@ func (o *CreateCronJobOptions) Run() error {
createOptions.FieldManager = o.FieldManager
}
if o.DryRunStrategy == cmdutil.DryRunServer {
if err := o.DryRunVerifier.HasSupport(cronjob.GroupVersionKind()); err != nil {
if err := o.DryRunVerifier.HasSupport(cronJobBeta.GroupVersionKind()); err != nil {
return err
}
createOptions.DryRun = []string{metav1.DryRunAll}
}
var err error
cronjob, err = o.Client.CronJobs(o.Namespace).Create(context.TODO(), cronjob, createOptions)
cronJobBeta, err = o.ClientBeta.CronJobs(o.Namespace).Create(context.TODO(), cronJobBeta, createOptions)
if err != nil {
return fmt.Errorf("failed to create cronjob: %v", err)
}
}
return o.PrintObj(cronjob)
return o.PrintObj(cronJobBeta)
}
func (o *CreateCronJobOptions) createCronJob() *batchv1beta1.CronJob {
func (o *CreateCronJobOptions) createCronJobBeta() *batchv1beta1.CronJob {
cronjob := &batchv1beta1.CronJob{
TypeMeta: metav1.TypeMeta{APIVersion: batchv1beta1.SchemeGroupVersion.String(), Kind: "CronJob"},
ObjectMeta: metav1.ObjectMeta{
@ -227,3 +281,38 @@ func (o *CreateCronJobOptions) createCronJob() *batchv1beta1.CronJob {
}
return cronjob
}
func (o *CreateCronJobOptions) createCronJob() *batchv1.CronJob {
cronjob := &batchv1.CronJob{
TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "CronJob"},
ObjectMeta: metav1.ObjectMeta{
Name: o.Name,
},
Spec: batchv1.CronJobSpec{
Schedule: o.Schedule,
JobTemplate: batchv1.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),
},
},
},
},
},
}
if o.EnforceNamespace {
cronjob.Namespace = o.Namespace
}
return cronjob
}

View File

@ -20,7 +20,6 @@ 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"
@ -33,20 +32,20 @@ func TestCreateCronJob(t *testing.T) {
command []string
schedule string
restart string
expected *batchv1beta1.CronJob
expected *batchv1.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"},
expected: &batchv1.CronJob{
TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "CronJob"},
ObjectMeta: metav1.ObjectMeta{
Name: cronjobName,
},
Spec: batchv1beta1.CronJobSpec{
Spec: batchv1.CronJobSpec{
Schedule: "0/5 * * * ?",
JobTemplate: batchv1beta1.JobTemplateSpec{
JobTemplate: batchv1.JobTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Name: cronjobName,
},
@ -72,14 +71,14 @@ func TestCreateCronJob(t *testing.T) {
command: []string{"date"},
schedule: "0/5 * * * ?",
restart: "Never",
expected: &batchv1beta1.CronJob{
TypeMeta: metav1.TypeMeta{APIVersion: batchv1beta1.SchemeGroupVersion.String(), Kind: "CronJob"},
expected: &batchv1.CronJob{
TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "CronJob"},
ObjectMeta: metav1.ObjectMeta{
Name: cronjobName,
},
Spec: batchv1beta1.CronJobSpec{
Spec: batchv1.CronJobSpec{
Schedule: "0/5 * * * ?",
JobTemplate: batchv1beta1.JobTemplateSpec{
JobTemplate: batchv1.JobTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Name: cronjobName,
},

View File

@ -215,6 +215,7 @@ func describerMap(clientConfig *rest.Config) (map[schema.GroupKind]ResourceDescr
{Group: networkingv1.GroupName, Kind: "IngressClass"}: &IngressClassDescriber{c},
{Group: batchv1.GroupName, Kind: "Job"}: &JobDescriber{c},
{Group: batchv1.GroupName, Kind: "CronJob"}: &CronJobDescriber{c},
{Group: batchv1beta1.GroupName, Kind: "CronJob"}: &CronJobDescriber{c},
{Group: appsv1.GroupName, Kind: "StatefulSet"}: &StatefulSetDescriber{c},
{Group: appsv1.GroupName, Kind: "Deployment"}: &DeploymentDescriber{c},
{Group: appsv1.GroupName, Kind: "DaemonSet"}: &DaemonSetDescriber{c},
@ -2211,19 +2212,28 @@ type CronJobDescriber struct {
}
func (d *CronJobDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (string, error) {
cronJob, err := d.client.BatchV1beta1().CronJobs(namespace).Get(context.TODO(), name, metav1.GetOptions{})
var events *corev1.EventList
cronJob, err := d.client.BatchV1().CronJobs(namespace).Get(context.TODO(), name, metav1.GetOptions{})
if err == nil {
if describerSettings.ShowEvents {
events, _ = d.client.CoreV1().Events(namespace).Search(scheme.Scheme, cronJob)
}
return describeCronJob(cronJob, events)
}
// TODO: drop this condition when beta disappears in 1.25
cronJobBeta, err := d.client.BatchV1beta1().CronJobs(namespace).Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
return "", err
}
var events *corev1.EventList
if describerSettings.ShowEvents {
events, _ = d.client.CoreV1().Events(namespace).Search(scheme.Scheme, cronJob)
}
return describeCronJob(cronJob, events)
return describeCronJobBeta(cronJobBeta, events)
}
func describeCronJob(cronJob *batchv1beta1.CronJob, events *corev1.EventList) (string, error) {
func describeCronJob(cronJob *batchv1.CronJob, events *corev1.EventList) (string, error) {
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)
w.Write(LEVEL_0, "Name:\t%s\n", cronJob.Name)
@ -2262,7 +2272,72 @@ func describeCronJob(cronJob *batchv1beta1.CronJob, events *corev1.EventList) (s
})
}
func describeJobTemplate(jobTemplate batchv1beta1.JobTemplateSpec, w PrefixWriter) {
func describeJobTemplate(jobTemplate batchv1.JobTemplateSpec, w PrefixWriter) {
if jobTemplate.Spec.Selector != nil {
if selector, err := metav1.LabelSelectorAsSelector(jobTemplate.Spec.Selector); err == nil {
w.Write(LEVEL_0, "Selector:\t%s\n", selector)
} else {
w.Write(LEVEL_0, "Selector:\tFailed to get selector: %s\n", err)
}
} else {
w.Write(LEVEL_0, "Selector:\t<unset>\n")
}
if jobTemplate.Spec.Parallelism != nil {
w.Write(LEVEL_0, "Parallelism:\t%d\n", *jobTemplate.Spec.Parallelism)
} else {
w.Write(LEVEL_0, "Parallelism:\t<unset>\n")
}
if jobTemplate.Spec.Completions != nil {
w.Write(LEVEL_0, "Completions:\t%d\n", *jobTemplate.Spec.Completions)
} else {
w.Write(LEVEL_0, "Completions:\t<unset>\n")
}
if jobTemplate.Spec.ActiveDeadlineSeconds != nil {
w.Write(LEVEL_0, "Active Deadline Seconds:\t%ds\n", *jobTemplate.Spec.ActiveDeadlineSeconds)
}
DescribePodTemplate(&jobTemplate.Spec.Template, w)
}
func describeCronJobBeta(cronJob *batchv1beta1.CronJob, events *corev1.EventList) (string, error) {
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)
w.Write(LEVEL_0, "Name:\t%s\n", cronJob.Name)
w.Write(LEVEL_0, "Namespace:\t%s\n", cronJob.Namespace)
printLabelsMultiline(w, "Labels", cronJob.Labels)
printAnnotationsMultiline(w, "Annotations", cronJob.Annotations)
w.Write(LEVEL_0, "Schedule:\t%s\n", cronJob.Spec.Schedule)
w.Write(LEVEL_0, "Concurrency Policy:\t%s\n", cronJob.Spec.ConcurrencyPolicy)
w.Write(LEVEL_0, "Suspend:\t%s\n", printBoolPtr(cronJob.Spec.Suspend))
if cronJob.Spec.SuccessfulJobsHistoryLimit != nil {
w.Write(LEVEL_0, "Successful Job History Limit:\t%d\n", *cronJob.Spec.SuccessfulJobsHistoryLimit)
} else {
w.Write(LEVEL_0, "Successful Job History Limit:\t<unset>\n")
}
if cronJob.Spec.FailedJobsHistoryLimit != nil {
w.Write(LEVEL_0, "Failed Job History Limit:\t%d\n", *cronJob.Spec.FailedJobsHistoryLimit)
} else {
w.Write(LEVEL_0, "Failed Job History Limit:\t<unset>\n")
}
if cronJob.Spec.StartingDeadlineSeconds != nil {
w.Write(LEVEL_0, "Starting Deadline Seconds:\t%ds\n", *cronJob.Spec.StartingDeadlineSeconds)
} else {
w.Write(LEVEL_0, "Starting Deadline Seconds:\t<unset>\n")
}
describeJobTemplateBeta(cronJob.Spec.JobTemplate, w)
if cronJob.Status.LastScheduleTime != nil {
w.Write(LEVEL_0, "Last Schedule Time:\t%s\n", cronJob.Status.LastScheduleTime.Time.Format(time.RFC1123Z))
} else {
w.Write(LEVEL_0, "Last Schedule Time:\t<unset>\n")
}
printActiveJobs(w, "Active Jobs", cronJob.Status.Active)
if events != nil {
DescribeEvents(events, w)
}
return nil
})
}
func describeJobTemplateBeta(jobTemplate batchv1beta1.JobTemplateSpec, w PrefixWriter) {
if jobTemplate.Spec.Selector != nil {
if selector, err := metav1.LabelSelectorAsSelector(jobTemplate.Spec.Selector); err == nil {
w.Write(LEVEL_0, "Selector:\t%s\n", selector)

View File

@ -81,6 +81,8 @@ func updatePodSpecForObject(obj runtime.Object, fn func(*v1.PodSpec) error) (boo
// CronJob
case *batchv1beta1.CronJob:
return true, fn(&t.Spec.JobTemplate.Spec.Template.Spec)
case *batchv1.CronJob:
return true, fn(&t.Spec.JobTemplate.Spec.Template.Spec)
default:
return false, fmt.Errorf("the object is not a pod or does not have a pod template: %T", t)

View File

@ -102,6 +102,10 @@ func TestUpdatePodSpecForObject(t *testing.T) {
object: &batchv1beta1.CronJob{},
expect: true,
},
{
object: &batchv1.CronJob{},
expect: true,
},
{
object: &v1.Node{},
expect: false,