From 98837de446fa4fbf441b69eb04da1ca5b66c2095 Mon Sep 17 00:00:00 2001 From: Ross Peoples Date: Mon, 21 Mar 2022 11:27:10 -0500 Subject: [PATCH 1/4] TimeZone support for CronJobs --- .../controller-manager.go | 1 + pkg/apis/batch/types.go | 6 + pkg/apis/batch/validation/validation.go | 34 +++- pkg/apis/batch/validation/validation_test.go | 165 ++++++++++++++++++ .../cronjob/cronjob_controllerv2.go | 41 ++++- .../cronjob/cronjob_controllerv2_test.go | 90 ++++++++++ staging/src/k8s.io/api/batch/v1/types.go | 14 +- staging/src/k8s.io/api/batch/v1beta1/types.go | 6 + .../apiserver/pkg/features/kube_features.go | 8 + 9 files changed, 352 insertions(+), 13 deletions(-) diff --git a/cmd/kube-controller-manager/controller-manager.go b/cmd/kube-controller-manager/controller-manager.go index 2a34cfcdf3e..77bc10a3517 100644 --- a/cmd/kube-controller-manager/controller-manager.go +++ b/cmd/kube-controller-manager/controller-manager.go @@ -22,6 +22,7 @@ package main import ( "os" + _ "time/tzdata" // for CronJob Time Zone support "k8s.io/component-base/cli" _ "k8s.io/component-base/logs/json/register" // for JSON log format registration diff --git a/pkg/apis/batch/types.go b/pkg/apis/batch/types.go index e4593a6c1b8..f7e87a8ed20 100644 --- a/pkg/apis/batch/types.go +++ b/pkg/apis/batch/types.go @@ -376,6 +376,12 @@ type CronJobSpec struct { // The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. Schedule string + // The time zone for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. + // If not specified, this will rely on the time zone of the kube-controller-manager process. + // ALPHA: This field is in alpha and must be enabled via the `CronJobTimeZone` feature gate. + // +optional + TimeZone *string + // Optional deadline in seconds for starting the job if it misses scheduled // time for any reason. Missed jobs executions will be counted as failed ones. // +optional diff --git a/pkg/apis/batch/validation/validation.go b/pkg/apis/batch/validation/validation.go index 61b59523864..8f7b5288691 100644 --- a/pkg/apis/batch/validation/validation.go +++ b/pkg/apis/batch/validation/validation.go @@ -18,6 +18,8 @@ package validation import ( "fmt" + "strings" + "time" "github.com/robfig/cron/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -308,11 +310,14 @@ func ValidateCronJobSpec(spec *batch.CronJobSpec, fldPath *field.Path, opts apiv if len(spec.Schedule) == 0 { allErrs = append(allErrs, field.Required(fldPath.Child("schedule"), "")) } else { - allErrs = append(allErrs, validateScheduleFormat(spec.Schedule, fldPath.Child("schedule"))...) + allErrs = append(allErrs, validateScheduleFormat(spec.Schedule, spec.TimeZone, fldPath.Child("schedule"))...) } + if spec.StartingDeadlineSeconds != nil { allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.StartingDeadlineSeconds), fldPath.Child("startingDeadlineSeconds"))...) } + + allErrs = append(allErrs, validateTimeZone(spec.TimeZone, fldPath.Child("timeZone"))...) allErrs = append(allErrs, validateConcurrencyPolicy(&spec.ConcurrencyPolicy, fldPath.Child("concurrencyPolicy"))...) allErrs = append(allErrs, ValidateJobTemplateSpec(&spec.JobTemplate, fldPath.Child("jobTemplate"), opts)...) @@ -343,11 +348,36 @@ func validateConcurrencyPolicy(concurrencyPolicy *batch.ConcurrencyPolicy, fldPa return allErrs } -func validateScheduleFormat(schedule string, fldPath *field.Path) field.ErrorList { +func validateScheduleFormat(schedule string, timeZone *string, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} if _, err := cron.ParseStandard(schedule); err != nil { allErrs = append(allErrs, field.Invalid(fldPath, schedule, err.Error())) } + if strings.Contains(schedule, "TZ") && timeZone != nil { + allErrs = append(allErrs, field.Invalid(fldPath, schedule, "cannot use both timeZone field and TZ or CRON_TZ in schedule")) + } + + return allErrs +} + +func validateTimeZone(timeZone *string, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + if timeZone == nil { + return allErrs + } + + if len(*timeZone) == 0 { + allErrs = append(allErrs, field.Invalid(fldPath, timeZone, "timeZone must be nil or non-empty string")) + return allErrs + } + + if strings.EqualFold(*timeZone, "Local") { + allErrs = append(allErrs, field.Invalid(fldPath, timeZone, "timeZone must be an explicit time zone as defined in https://www.iana.org/time-zones")) + } + + if _, err := time.LoadLocation(*timeZone); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath, timeZone, err.Error())) + } return allErrs } diff --git a/pkg/apis/batch/validation/validation_test.go b/pkg/apis/batch/validation/validation_test.go index b33bdff409b..641419f7199 100644 --- a/pkg/apis/batch/validation/validation_test.go +++ b/pkg/apis/batch/validation/validation_test.go @@ -31,6 +31,18 @@ import ( "k8s.io/utils/pointer" ) +var ( + timeZoneEmpty = "" + timeZoneLocal = "LOCAL" + timeZoneUTC = "UTC" + timeZoneCorrectCasing = "America/New_York" + timeZoneBadCasing = "AMERICA/new_york" + timeZoneBadPrefix = " America/New_York" + timeZoneBadSuffix = "America/New_York " + timeZoneBadName = "America/New York" + timeZoneEmptySpace = " " +) + var ignoreErrValueDetail = cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail") func getValidManualSelector() *metav1.LabelSelector { @@ -902,6 +914,23 @@ func TestValidateCronJob(t *testing.T) { }, }, }, + "correct timeZone value casing": { + ObjectMeta: metav1.ObjectMeta{ + Name: "mycronjob", + Namespace: metav1.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: &timeZoneCorrectCasing, + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, } for k, v := range successCases { if errs := ValidateCronJob(&v, corevalidation.PodValidationOptions{}); len(errs) != 0 { @@ -953,6 +982,142 @@ func TestValidateCronJob(t *testing.T) { }, }, }, + "spec.schedule: cannot use both timeZone field and TZ or CRON_TZ in schedule": { + ObjectMeta: metav1.ObjectMeta{ + Name: "mycronjob", + Namespace: metav1.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.CronJobSpec{ + Schedule: "TZ=UTC 0 * * * *", + TimeZone: &timeZoneUTC, + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, + "spec.timeZone: timeZone must be nil or non-empty string": { + ObjectMeta: metav1.ObjectMeta{ + Name: "mycronjob", + Namespace: metav1.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: &timeZoneEmpty, + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, + "spec.timeZone: timeZone must be an explicit time zone as defined in https://www.iana.org/time-zones": { + ObjectMeta: metav1.ObjectMeta{ + Name: "mycronjob", + Namespace: metav1.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: &timeZoneLocal, + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, + "spec.timeZone: Invalid value: \"AMERICA/new_york\": unknown time zone AMERICA/new_york": { + ObjectMeta: metav1.ObjectMeta{ + Name: "mycronjob", + Namespace: metav1.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: &timeZoneBadCasing, + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, + "spec.timeZone: Invalid value: \" America/New_York\": unknown time zone America/New_York": { + ObjectMeta: metav1.ObjectMeta{ + Name: "mycronjob", + Namespace: metav1.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: &timeZoneBadPrefix, + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, + "spec.timeZone: Invalid value: \"America/New_York \": unknown time zone America/New_York ": { + ObjectMeta: metav1.ObjectMeta{ + Name: "mycronjob", + Namespace: metav1.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: &timeZoneBadSuffix, + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, + "spec.timeZone: Invalid value: \"America/New York\": unknown time zone America/New York": { + ObjectMeta: metav1.ObjectMeta{ + Name: "mycronjob", + Namespace: metav1.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: &timeZoneBadName, + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, + "spec.timeZone: Invalid value: \" \": unknown time zone ": { + ObjectMeta: metav1.ObjectMeta{ + Name: "mycronjob", + Namespace: metav1.NamespaceDefault, + UID: types.UID("1a2b3c"), + }, + Spec: batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: &timeZoneEmptySpace, + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, "spec.startingDeadlineSeconds:must be greater than or equal to 0": { ObjectMeta: metav1.ObjectMeta{ Name: "mycronjob", diff --git a/pkg/controller/cronjob/cronjob_controllerv2.go b/pkg/controller/cronjob/cronjob_controllerv2.go index fa5749f236f..9f607856a35 100644 --- a/pkg/controller/cronjob/cronjob_controllerv2.go +++ b/pkg/controller/cronjob/cronjob_controllerv2.go @@ -35,6 +35,8 @@ import ( "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apiserver/pkg/features" + utilfeature "k8s.io/apiserver/pkg/util/feature" batchv1informers "k8s.io/client-go/informers/batch/v1" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" @@ -48,6 +50,7 @@ import ( "k8s.io/klog/v2" "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller/cronjob/metrics" + "k8s.io/utils/pointer" ) var ( @@ -371,6 +374,7 @@ func (jm *ControllerV2) enqueueControllerAfter(obj interface{}, t time.Duration) // updateCronJob re-queues the CronJob for next scheduled time if there is a // change in spec.schedule otherwise it re-queues it now func (jm *ControllerV2) updateCronJob(old interface{}, curr interface{}) { + timeZoneEnabled := utilfeature.DefaultFeatureGate.Enabled(features.CronJobTimeZone) oldCJ, okOld := old.(*batchv1.CronJob) newCJ, okNew := curr.(*batchv1.CronJob) @@ -381,9 +385,9 @@ func (jm *ControllerV2) updateCronJob(old interface{}, curr interface{}) { // if the change in schedule results in next requeue having to be sooner than it already was, // it will be handled here by the queue. If the next requeue is further than previous schedule, // the sync loop will essentially be a no-op for the already queued key with old schedule. - if oldCJ.Spec.Schedule != newCJ.Spec.Schedule { + if oldCJ.Spec.Schedule != newCJ.Spec.Schedule || (timeZoneEnabled && !pointer.StringEqual(oldCJ.Spec.TimeZone, newCJ.Spec.TimeZone)) { // schedule changed, change the requeue time - sched, err := cron.ParseStandard(newCJ.Spec.Schedule) + sched, err := cron.ParseStandard(formatSchedule(timeZoneEnabled, newCJ, jm.recorder)) if err != nil { // this is likely a user error in defining the spec value // we should log the error and not reconcile this cronjob until an update to spec @@ -420,6 +424,7 @@ func (jm *ControllerV2) syncCronJob( cronJob = cronJob.DeepCopy() now := jm.now() updateStatus := false + timeZoneEnabled := utilfeature.DefaultFeatureGate.Enabled(features.CronJobTimeZone) childrenJobs := make(map[types.UID]bool) for _, j := range jobs { @@ -487,12 +492,21 @@ func (jm *ControllerV2) syncCronJob( return cronJob, nil, updateStatus, nil } + if timeZoneEnabled && cronJob.Spec.TimeZone != nil { + if _, err := time.LoadLocation(*cronJob.Spec.TimeZone); err != nil { + timeZone := pointer.StringDeref(cronJob.Spec.TimeZone, "") + klog.V(4).InfoS("Not starting job because timeZone is invalid", "cronjob", klog.KRef(cronJob.GetNamespace(), cronJob.GetName()), "timeZone", timeZone, "err", err) + jm.recorder.Eventf(cronJob, corev1.EventTypeWarning, "UnknownTimeZone", "invalid timeZone: %q: %s", timeZone, err) + return cronJob, nil, updateStatus, nil + } + } + if cronJob.Spec.Suspend != nil && *cronJob.Spec.Suspend { klog.V(4).InfoS("Not starting job because the cron is suspended", "cronjob", klog.KRef(cronJob.GetNamespace(), cronJob.GetName())) return cronJob, nil, updateStatus, nil } - sched, err := cron.ParseStandard(cronJob.Spec.Schedule) + sched, err := cron.ParseStandard(formatSchedule(timeZoneEnabled, cronJob, jm.recorder)) if err != nil { // this is likely a user error in defining the spec value // we should log the error and not reconcile this cronjob until an update to spec @@ -501,10 +515,6 @@ func (jm *ControllerV2) syncCronJob( return cronJob, nil, updateStatus, nil } - if strings.Contains(cronJob.Spec.Schedule, "TZ") { - jm.recorder.Eventf(cronJob, corev1.EventTypeWarning, "UnsupportedSchedule", "CRON_TZ or TZ used in schedule %q is not officially supported, see https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/ for more details", cronJob.Spec.Schedule) - } - scheduledTime, err := getNextScheduleTime(*cronJob, now, sched, jm.recorder) if err != nil { // this is likely a user error in defining the spec value @@ -739,3 +749,20 @@ func deleteJob(cj *batchv1.CronJob, job *batchv1.Job, jc jobControlInterface, re func getRef(object runtime.Object) (*corev1.ObjectReference, error) { return ref.GetReference(scheme.Scheme, object) } + +func formatSchedule(timeZoneEnabled bool, cj *batchv1.CronJob, recorder record.EventRecorder) string { + if strings.Contains(cj.Spec.Schedule, "TZ") { + recorder.Eventf(cj, corev1.EventTypeWarning, "UnsupportedSchedule", "CRON_TZ or TZ used in schedule %q is not officially supported, see https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/ for more details", cj.Spec.Schedule) + return cj.Spec.Schedule + } + + if timeZoneEnabled && cj.Spec.TimeZone != nil { + if _, err := time.LoadLocation(*cj.Spec.TimeZone); err != nil { + return cj.Spec.Schedule + } + + return fmt.Sprintf("TZ=%s %s", *cj.Spec.TimeZone, cj.Spec.Schedule) + } + + return cj.Spec.Schedule +} diff --git a/pkg/controller/cronjob/cronjob_controllerv2_test.go b/pkg/controller/cronjob/cronjob_controllerv2_test.go index 4f474469908..19578bd1f39 100644 --- a/pkg/controller/cronjob/cronjob_controllerv2_test.go +++ b/pkg/controller/cronjob/cronjob_controllerv2_test.go @@ -32,10 +32,13 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" + "k8s.io/apiserver/pkg/features" + "k8s.io/apiserver/pkg/util/feature" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" + featuregatetesting "k8s.io/component-base/featuregate/testing" _ "k8s.io/kubernetes/pkg/apis/batch/install" _ "k8s.io/kubernetes/pkg/apis/core/install" "k8s.io/kubernetes/pkg/controller" @@ -50,6 +53,9 @@ var ( errorSchedule = "obvious error schedule" // schedule is hourly on the hour onTheHour = "0 * * * ?" + + errorTimeZone = "bad timezone" + newYork = "America/New_York" ) // returns a cronJob with some fields filled in. @@ -127,6 +133,19 @@ func justAfterTheHour() *time.Time { return &T1 } +func justAfterTheHourInZone(tz string) time.Time { + location, err := time.LoadLocation(tz) + if err != nil { + panic("tz error: " + err.Error()) + } + + T1, err := time.ParseInLocation(time.RFC3339, "2016-05-19T10:01:00Z", location) + if err != nil { + panic("test setup error: " + err.Error()) + } + return T1 +} + func justBeforeTheHour() time.Time { T1, err := time.Parse(time.RFC3339, "2016-05-19T09:59:00Z") if err != nil { @@ -162,6 +181,7 @@ func TestControllerV2SyncCronJob(t *testing.T) { concurrencyPolicy batchv1.ConcurrencyPolicy suspend bool schedule string + timeZone *string deadline int64 // cj status @@ -173,6 +193,7 @@ func TestControllerV2SyncCronJob(t *testing.T) { now time.Time jobCreateError error jobGetErr error + enableTimeZone bool // expectations expectCreate bool @@ -212,6 +233,17 @@ func TestControllerV2SyncCronJob(t *testing.T) { expectedWarnings: 1, jobPresentInCJActiveStatus: true, }, + "never ran, not valid time zone": { + concurrencyPolicy: "Allow", + schedule: onTheHour, + timeZone: &errorTimeZone, + deadline: noDead, + jobCreationTime: justAfterThePriorHour(), + now: justBeforeTheHour(), + enableTimeZone: true, + expectedWarnings: 1, + jobPresentInCJActiveStatus: true, + }, "never ran, not time, A": { concurrencyPolicy: "Allow", schedule: onTheHour, @@ -238,6 +270,17 @@ func TestControllerV2SyncCronJob(t *testing.T) { expectRequeueAfter: true, jobPresentInCJActiveStatus: true, }, + "never ran, not time in zone": { + concurrencyPolicy: "Allow", + schedule: onTheHour, + timeZone: &newYork, + deadline: noDead, + jobCreationTime: justAfterThePriorHour(), + now: justBeforeTheHour(), + enableTimeZone: true, + expectRequeueAfter: true, + jobPresentInCJActiveStatus: true, + }, "never ran, is time, A": { concurrencyPolicy: "Allow", schedule: onTheHour, @@ -274,6 +317,48 @@ func TestControllerV2SyncCronJob(t *testing.T) { expectUpdateStatus: true, jobPresentInCJActiveStatus: true, }, + "never ran, is time in zone, but time zone disabled": { + concurrencyPolicy: "Allow", + schedule: onTheHour, + timeZone: &newYork, + deadline: noDead, + jobCreationTime: justAfterThePriorHour(), + now: justAfterTheHourInZone(newYork), + enableTimeZone: false, + expectCreate: true, + expectActive: 1, + expectRequeueAfter: true, + expectUpdateStatus: true, + jobPresentInCJActiveStatus: true, + }, + "never ran, is time in zone": { + concurrencyPolicy: "Allow", + schedule: onTheHour, + timeZone: &newYork, + deadline: noDead, + jobCreationTime: justAfterThePriorHour(), + now: justAfterTheHourInZone(newYork), + enableTimeZone: true, + expectCreate: true, + expectActive: 1, + expectRequeueAfter: true, + expectUpdateStatus: true, + jobPresentInCJActiveStatus: true, + }, + "never ran, is time in zone, but TZ is also set in schedule": { + concurrencyPolicy: "Allow", + schedule: "TZ=UTC " + onTheHour, + timeZone: &newYork, + deadline: noDead, + jobCreationTime: justAfterThePriorHour(), + now: justAfterTheHourInZone(newYork), + enableTimeZone: true, + expectCreate: true, + expectedWarnings: 1, + expectRequeueAfter: true, + expectUpdateStatus: true, + jobPresentInCJActiveStatus: true, + }, "never ran, is time, suspended": { concurrencyPolicy: "Allow", suspend: true, @@ -820,10 +905,15 @@ func TestControllerV2SyncCronJob(t *testing.T) { cj.Spec.ConcurrencyPolicy = tc.concurrencyPolicy cj.Spec.Suspend = &tc.suspend cj.Spec.Schedule = tc.schedule + cj.Spec.TimeZone = tc.timeZone if tc.deadline != noDead { cj.Spec.StartingDeadlineSeconds = &tc.deadline } + if tc.enableTimeZone { + defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.CronJobTimeZone, true) + } + var ( job *batchv1.Job err error diff --git a/staging/src/k8s.io/api/batch/v1/types.go b/staging/src/k8s.io/api/batch/v1/types.go index 66acde36974..e5a34e2c2cf 100644 --- a/staging/src/k8s.io/api/batch/v1/types.go +++ b/staging/src/k8s.io/api/batch/v1/types.go @@ -17,7 +17,7 @@ limitations under the License. package v1 import ( - "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ) @@ -146,7 +146,7 @@ type JobSpec struct { // Describes the pod that will be created when executing a job. // More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/ - Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,6,opt,name=template"` + Template corev1.PodTemplateSpec `json:"template" protobuf:"bytes,6,opt,name=template"` // ttlSecondsAfterFinished limits the lifetime of a Job that has finished // execution (either Complete or Failed). If this field is set, @@ -304,7 +304,7 @@ type JobCondition struct { // Type of job condition, Complete or Failed. Type JobConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=JobConditionType"` // Status of the condition, one of True, False, Unknown. - Status v1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"` + Status corev1.ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus"` // Last time the condition was checked. // +optional LastProbeTime metav1.Time `json:"lastProbeTime,omitempty" protobuf:"bytes,3,opt,name=lastProbeTime"` @@ -375,6 +375,12 @@ type CronJobSpec struct { // The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. Schedule string `json:"schedule" protobuf:"bytes,1,opt,name=schedule"` + // The time zone for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. + // If not specified, this will rely on the time zone of the kube-controller-manager process. + // ALPHA: This field is in alpha and must be enabled via the `CronJobTimeZone` feature gate. + // +optional + TimeZone *string `json:"timeZone,omitempty" protobuf:"bytes,8,opt,name=timeZone"` + // Optional deadline in seconds for starting the job if it misses scheduled // time for any reason. Missed jobs executions will be counted as failed ones. // +optional @@ -431,7 +437,7 @@ type CronJobStatus struct { // A list of pointers to currently running jobs. // +optional // +listType=atomic - Active []v1.ObjectReference `json:"active,omitempty" protobuf:"bytes,1,rep,name=active"` + Active []corev1.ObjectReference `json:"active,omitempty" protobuf:"bytes,1,rep,name=active"` // Information when was the last time the job was successfully scheduled. // +optional diff --git a/staging/src/k8s.io/api/batch/v1beta1/types.go b/staging/src/k8s.io/api/batch/v1beta1/types.go index cd9af7a9e7c..54a2d14318f 100644 --- a/staging/src/k8s.io/api/batch/v1beta1/types.go +++ b/staging/src/k8s.io/api/batch/v1beta1/types.go @@ -104,6 +104,12 @@ type CronJobSpec struct { // The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. Schedule string `json:"schedule" protobuf:"bytes,1,opt,name=schedule"` + // The time zone for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. + // If not specified, this will rely on the time zone of the kube-controller-manager process. + // ALPHA: This field is in alpha and must be enabled via the `CronJobTimeZone` feature gate. + // +optional + TimeZone *string `json:"timeZone,omitempty" protobuf:"bytes,8,opt,name=timeZone"` + // Optional deadline in seconds for starting the job if it misses scheduled // time for any reason. Missed jobs executions will be counted as failed ones. // +optional diff --git a/staging/src/k8s.io/apiserver/pkg/features/kube_features.go b/staging/src/k8s.io/apiserver/pkg/features/kube_features.go index c4f4d0b837c..760568e681d 100644 --- a/staging/src/k8s.io/apiserver/pkg/features/kube_features.go +++ b/staging/src/k8s.io/apiserver/pkg/features/kube_features.go @@ -178,6 +178,13 @@ const ( // // Enables server-side field validation. ServerSideFieldValidation featuregate.Feature = "ServerSideFieldValidation" + + // owner: @deejross + // kep: http://kep.k8s.io/3140 + // alpha: v1.24 + // + // Enables support for time zones in CronJobs. + CronJobTimeZone featuregate.Feature = "CronJobTimeZone" ) func init() { @@ -207,4 +214,5 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS CustomResourceValidationExpressions: {Default: false, PreRelease: featuregate.Alpha}, OpenAPIV3: {Default: false, PreRelease: featuregate.Alpha}, ServerSideFieldValidation: {Default: true, PreRelease: featuregate.Beta}, + CronJobTimeZone: {Default: false, PreRelease: featuregate.Alpha}, } From f3b928a23da592c6e7a3b9405549e8751a8a5dfb Mon Sep 17 00:00:00 2001 From: Ross Peoples Date: Thu, 24 Mar 2022 17:09:45 -0500 Subject: [PATCH 2/4] Import tzdata in apiserver, CronJob.timeZone fixes --- cmd/kube-apiserver/apiserver.go | 1 + pkg/controller/cronjob/cronjob_controllerv2.go | 9 ++++++--- pkg/controller/cronjob/cronjob_controllerv2_test.go | 7 +++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/cmd/kube-apiserver/apiserver.go b/cmd/kube-apiserver/apiserver.go index 42956a9bf6e..9767fa6ab35 100644 --- a/cmd/kube-apiserver/apiserver.go +++ b/cmd/kube-apiserver/apiserver.go @@ -20,6 +20,7 @@ package main import ( "os" + _ "time/tzdata" // for timeZone support in CronJob "k8s.io/component-base/cli" _ "k8s.io/component-base/logs/json/register" // for JSON log format registration diff --git a/pkg/controller/cronjob/cronjob_controllerv2.go b/pkg/controller/cronjob/cronjob_controllerv2.go index 9f607856a35..d31affdf719 100644 --- a/pkg/controller/cronjob/cronjob_controllerv2.go +++ b/pkg/controller/cronjob/cronjob_controllerv2.go @@ -386,8 +386,8 @@ func (jm *ControllerV2) updateCronJob(old interface{}, curr interface{}) { // it will be handled here by the queue. If the next requeue is further than previous schedule, // the sync loop will essentially be a no-op for the already queued key with old schedule. if oldCJ.Spec.Schedule != newCJ.Spec.Schedule || (timeZoneEnabled && !pointer.StringEqual(oldCJ.Spec.TimeZone, newCJ.Spec.TimeZone)) { - // schedule changed, change the requeue time - sched, err := cron.ParseStandard(formatSchedule(timeZoneEnabled, newCJ, jm.recorder)) + // schedule changed, change the requeue time, pass nil recorder so that syncCronJob will output any warnings + sched, err := cron.ParseStandard(formatSchedule(timeZoneEnabled, newCJ, nil)) if err != nil { // this is likely a user error in defining the spec value // we should log the error and not reconcile this cronjob until an update to spec @@ -752,7 +752,10 @@ func getRef(object runtime.Object) (*corev1.ObjectReference, error) { func formatSchedule(timeZoneEnabled bool, cj *batchv1.CronJob, recorder record.EventRecorder) string { if strings.Contains(cj.Spec.Schedule, "TZ") { - recorder.Eventf(cj, corev1.EventTypeWarning, "UnsupportedSchedule", "CRON_TZ or TZ used in schedule %q is not officially supported, see https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/ for more details", cj.Spec.Schedule) + if recorder != nil { + recorder.Eventf(cj, corev1.EventTypeWarning, "UnsupportedSchedule", "CRON_TZ or TZ used in schedule %q is not officially supported, see https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/ for more details", cj.Spec.Schedule) + } + return cj.Spec.Schedule } diff --git a/pkg/controller/cronjob/cronjob_controllerv2_test.go b/pkg/controller/cronjob/cronjob_controllerv2_test.go index 19578bd1f39..b782597b7c6 100644 --- a/pkg/controller/cronjob/cronjob_controllerv2_test.go +++ b/pkg/controller/cronjob/cronjob_controllerv2_test.go @@ -900,7 +900,10 @@ func TestControllerV2SyncCronJob(t *testing.T) { for name, tc := range testCases { name := name tc := tc + t.Run(name, func(t *testing.T) { + defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.CronJobTimeZone, tc.enableTimeZone) + cj := cronJob() cj.Spec.ConcurrencyPolicy = tc.concurrencyPolicy cj.Spec.Suspend = &tc.suspend @@ -910,10 +913,6 @@ func TestControllerV2SyncCronJob(t *testing.T) { cj.Spec.StartingDeadlineSeconds = &tc.deadline } - if tc.enableTimeZone { - defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.CronJobTimeZone, true) - } - var ( job *batchv1.Job err error From dbb3906a093d5808752c88510fe690d075eee1d9 Mon Sep 17 00:00:00 2001 From: Ross Peoples Date: Mon, 28 Mar 2022 14:08:12 -0500 Subject: [PATCH 3/4] Fix for timeZone validation and strategy --- pkg/apis/batch/validation/validation.go | 19 +- pkg/apis/batch/validation/validation_test.go | 217 +++++++++++++++++- .../cronjob/cronjob_controllerv2_test.go | 59 ++++- pkg/registry/batch/cronjob/strategy.go | 12 +- 4 files changed, 293 insertions(+), 14 deletions(-) diff --git a/pkg/apis/batch/validation/validation.go b/pkg/apis/batch/validation/validation.go index 8f7b5288691..129d14a5a30 100644 --- a/pkg/apis/batch/validation/validation.go +++ b/pkg/apis/batch/validation/validation.go @@ -31,6 +31,7 @@ import ( "k8s.io/kubernetes/pkg/apis/batch" api "k8s.io/kubernetes/pkg/apis/core" apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" + "k8s.io/utils/pointer" ) // maxParallelismForIndexJob is the maximum parallelism that an Indexed Job @@ -279,11 +280,11 @@ func ValidateJobStatusUpdate(status, oldStatus batch.JobStatus) field.ErrorList return allErrs } -// ValidateCronJob validates a CronJob and returns an ErrorList with any errors. -func ValidateCronJob(cronJob *batch.CronJob, opts apivalidation.PodValidationOptions) field.ErrorList { +// ValidateCronJobCreate validates a CronJob on creation and returns an ErrorList with any errors. +func ValidateCronJobCreate(cronJob *batch.CronJob, opts apivalidation.PodValidationOptions) field.ErrorList { // CronJobs and rcs have the same name validation allErrs := apivalidation.ValidateObjectMeta(&cronJob.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateCronJobSpec(&cronJob.Spec, field.NewPath("spec"), opts)...) + allErrs = append(allErrs, validateCronJobSpec(&cronJob.Spec, nil, field.NewPath("spec"), opts)...) if len(cronJob.ObjectMeta.Name) > apimachineryvalidation.DNS1035LabelMaxLength-11 { // The cronjob controller appends a 11-character suffix to the cronjob (`-$TIMESTAMP`) when // creating a job. The job name length limit is 63 characters. @@ -297,14 +298,15 @@ func ValidateCronJob(cronJob *batch.CronJob, opts apivalidation.PodValidationOpt // ValidateCronJobUpdate validates an update to a CronJob and returns an ErrorList with any errors. func ValidateCronJobUpdate(job, oldJob *batch.CronJob, opts apivalidation.PodValidationOptions) field.ErrorList { allErrs := apivalidation.ValidateObjectMetaUpdate(&job.ObjectMeta, &oldJob.ObjectMeta, field.NewPath("metadata")) - allErrs = append(allErrs, ValidateCronJobSpec(&job.Spec, field.NewPath("spec"), opts)...) + allErrs = append(allErrs, validateCronJobSpec(&job.Spec, &oldJob.Spec, field.NewPath("spec"), opts)...) + // skip the 52-character name validation limit on update validation // to allow old cronjobs with names > 52 chars to be updated/deleted return allErrs } -// ValidateCronJobSpec validates a CronJobSpec and returns an ErrorList with any errors. -func ValidateCronJobSpec(spec *batch.CronJobSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList { +// validateCronJobSpec validates a CronJobSpec and returns an ErrorList with any errors. +func validateCronJobSpec(spec, oldSpec *batch.CronJobSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList { allErrs := field.ErrorList{} if len(spec.Schedule) == 0 { @@ -317,7 +319,10 @@ func ValidateCronJobSpec(spec *batch.CronJobSpec, fldPath *field.Path, opts apiv allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.StartingDeadlineSeconds), fldPath.Child("startingDeadlineSeconds"))...) } - allErrs = append(allErrs, validateTimeZone(spec.TimeZone, fldPath.Child("timeZone"))...) + if oldSpec == nil || !pointer.StringEqual(oldSpec.TimeZone, spec.TimeZone) { + allErrs = append(allErrs, validateTimeZone(spec.TimeZone, fldPath.Child("timeZone"))...) + } + allErrs = append(allErrs, validateConcurrencyPolicy(&spec.ConcurrencyPolicy, fldPath.Child("concurrencyPolicy"))...) allErrs = append(allErrs, ValidateJobTemplateSpec(&spec.JobTemplate, fldPath.Child("jobTemplate"), opts)...) diff --git a/pkg/apis/batch/validation/validation_test.go b/pkg/apis/batch/validation/validation_test.go index 641419f7199..3e2b0b6e958 100644 --- a/pkg/apis/batch/validation/validation_test.go +++ b/pkg/apis/batch/validation/validation_test.go @@ -933,7 +933,7 @@ func TestValidateCronJob(t *testing.T) { }, } for k, v := range successCases { - if errs := ValidateCronJob(&v, corevalidation.PodValidationOptions{}); len(errs) != 0 { + if errs := ValidateCronJobCreate(&v, corevalidation.PodValidationOptions{}); len(errs) != 0 { t.Errorf("expected success for %s: %v", k, errs) } @@ -1350,7 +1350,7 @@ func TestValidateCronJob(t *testing.T) { } for k, v := range errorCases { - errs := ValidateCronJob(&v, corevalidation.PodValidationOptions{}) + errs := ValidateCronJobCreate(&v, corevalidation.PodValidationOptions{}) if len(errs) == 0 { t.Errorf("expected failure for %s", k) } else { @@ -1363,9 +1363,14 @@ func TestValidateCronJob(t *testing.T) { // Update validation should fail all failure cases other than the 52 character name limit // copy to avoid polluting the testcase object, set a resourceVersion to allow validating update, and test a no-op update - v = *v.DeepCopy() - v.ResourceVersion = "1" - errs = ValidateCronJobUpdate(&v, &v, corevalidation.PodValidationOptions{}) + oldSpec := *v.DeepCopy() + oldSpec.ResourceVersion = "1" + oldSpec.Spec.TimeZone = nil + + newSpec := *v.DeepCopy() + newSpec.ResourceVersion = "2" + + errs = ValidateCronJobUpdate(&newSpec, &oldSpec, corevalidation.PodValidationOptions{}) if len(errs) == 0 { if k == "metadata.name: must be no more than 52 characters" { continue @@ -1381,6 +1386,208 @@ func TestValidateCronJob(t *testing.T) { } } +func TestValidateCronJobSpec(t *testing.T) { + validPodTemplateSpec := getValidPodTemplateSpecForGenerated(getValidGeneratedSelector()) + validPodTemplateSpec.Labels = map[string]string{} + + type testCase struct { + old *batch.CronJobSpec + new *batch.CronJobSpec + expectErr bool + } + + cases := map[string]testCase{ + "no validation because timeZone is nil for old and new": { + old: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: nil, + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + new: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: nil, + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, + "check validation because timeZone is different for new": { + old: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: nil, + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + new: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: pointer.String("America/New_York"), + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, + "check validation because timeZone is different for new and invalid": { + old: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: nil, + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + new: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: pointer.String("broken"), + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + expectErr: true, + }, + "old timeZone and new timeZone are valid": { + old: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: pointer.String("America/New_York"), + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + new: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: pointer.String("America/Chicago"), + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, + "old timeZone is valid, but new timeZone is invalid": { + old: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: pointer.String("America/New_York"), + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + new: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: pointer.String("broken"), + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + expectErr: true, + }, + "old timeZone and new timeZone are invalid, but unchanged": { + old: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: pointer.String("broken"), + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + new: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: pointer.String("broken"), + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, + "old timeZone and new timeZone are invalid, but different": { + old: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: pointer.String("broken"), + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + new: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: pointer.String("still broken"), + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + expectErr: true, + }, + "old timeZone is invalid, but new timeZone is valid": { + old: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: pointer.String("broken"), + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + new: &batch.CronJobSpec{ + Schedule: "0 * * * *", + TimeZone: pointer.String("America/New_York"), + ConcurrencyPolicy: batch.AllowConcurrent, + JobTemplate: batch.JobTemplateSpec{ + Spec: batch.JobSpec{ + Template: validPodTemplateSpec, + }, + }, + }, + }, + } + + for k, v := range cases { + errs := validateCronJobSpec(v.new, v.old, field.NewPath("spec"), corevalidation.PodValidationOptions{}) + if len(errs) > 0 && !v.expectErr { + t.Errorf("unexpected error for %s: %v", k, errs) + } else if len(errs) == 0 && v.expectErr { + t.Errorf("expected error for %s but got nil", k) + } + } +} + func completionModePtr(m batch.CompletionMode) *batch.CompletionMode { return &m } diff --git a/pkg/controller/cronjob/cronjob_controllerv2_test.go b/pkg/controller/cronjob/cronjob_controllerv2_test.go index b782597b7c6..6c420ad056b 100644 --- a/pkg/controller/cronjob/cronjob_controllerv2_test.go +++ b/pkg/controller/cronjob/cronjob_controllerv2_test.go @@ -902,7 +902,7 @@ func TestControllerV2SyncCronJob(t *testing.T) { tc := tc t.Run(name, func(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.CronJobTimeZone, tc.enableTimeZone) + defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.CronJobTimeZone, tc.enableTimeZone)() cj := cronJob() cj.Spec.ConcurrencyPolicy = tc.concurrencyPolicy @@ -1147,6 +1147,63 @@ func TestControllerV2UpdateCronJob(t *testing.T) { }, expectedDelay: 1*time.Second + nextScheduleDelta, }, + { + name: "spec.timeZone not changed", + oldCronJob: &batchv1.CronJob{ + Spec: batchv1.CronJobSpec{ + TimeZone: &newYork, + JobTemplate: batchv1.JobTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"a": "b"}, + Annotations: map[string]string{"x": "y"}, + }, + Spec: jobSpec(), + }, + }, + }, + newCronJob: &batchv1.CronJob{ + Spec: batchv1.CronJobSpec{ + TimeZone: &newYork, + JobTemplate: batchv1.JobTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"a": "foo"}, + Annotations: map[string]string{"x": "y"}, + }, + Spec: jobSpec(), + }, + }, + }, + expectedDelay: 0 * time.Second, + }, + { + name: "spec.timeZone changed", + oldCronJob: &batchv1.CronJob{ + Spec: batchv1.CronJobSpec{ + TimeZone: &newYork, + JobTemplate: batchv1.JobTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"a": "b"}, + Annotations: map[string]string{"x": "y"}, + }, + Spec: jobSpec(), + }, + }, + }, + newCronJob: &batchv1.CronJob{ + Spec: batchv1.CronJobSpec{ + TimeZone: nil, + JobTemplate: batchv1.JobTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"a": "foo"}, + Annotations: map[string]string{"x": "y"}, + }, + Spec: jobSpec(), + }, + }, + }, + expectedDelay: 0 * time.Second, + }, + // TODO: Add more test cases for updating scheduling. } for _, tt := range tests { diff --git a/pkg/registry/batch/cronjob/strategy.go b/pkg/registry/batch/cronjob/strategy.go index c75a2144519..db44ff95dca 100644 --- a/pkg/registry/batch/cronjob/strategy.go +++ b/pkg/registry/batch/cronjob/strategy.go @@ -27,8 +27,10 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/features" "k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/storage/names" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/pod" "k8s.io/kubernetes/pkg/apis/batch" @@ -88,6 +90,10 @@ func (cronJobStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) cronJob.Generation = 1 + if !utilfeature.DefaultFeatureGate.Enabled(features.CronJobTimeZone) { + cronJob.Spec.TimeZone = nil + } + pod.DropDisabledTemplateFields(&cronJob.Spec.JobTemplate.Spec.Template, nil) } @@ -97,6 +103,10 @@ func (cronJobStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Ob oldCronJob := old.(*batch.CronJob) newCronJob.Status = oldCronJob.Status + if !utilfeature.DefaultFeatureGate.Enabled(features.CronJobTimeZone) && oldCronJob.Spec.TimeZone == nil { + newCronJob.Spec.TimeZone = nil + } + pod.DropDisabledTemplateFields(&newCronJob.Spec.JobTemplate.Spec.Template, &oldCronJob.Spec.JobTemplate.Spec.Template) // Any changes to the spec increment the generation number. @@ -110,7 +120,7 @@ func (cronJobStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Ob func (cronJobStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { cronJob := obj.(*batch.CronJob) opts := pod.GetValidationOptionsFromPodTemplate(&cronJob.Spec.JobTemplate.Spec.Template, nil) - return validation.ValidateCronJob(cronJob, opts) + return validation.ValidateCronJobCreate(cronJob, opts) } // WarningsOnCreate returns warnings for the creation of the given object. From d26e6cca7255c2c96ba28e8e5550bbc87a7577b9 Mon Sep 17 00:00:00 2001 From: Ross Peoples Date: Mon, 28 Mar 2022 14:59:03 -0500 Subject: [PATCH 4/4] make update after timeZone support for CronJob --- api/openapi-spec/swagger.json | 8 + .../v3/apis__batch__v1_openapi.json | 4 + .../v3/apis__batch__v1beta1_openapi.json | 4 + pkg/apis/batch/v1/zz_generated.conversion.go | 2 + .../batch/v1beta1/zz_generated.conversion.go | 2 + pkg/apis/batch/zz_generated.deepcopy.go | 5 + pkg/generated/openapi/zz_generated.openapi.go | 14 ++ .../src/k8s.io/api/batch/v1/generated.pb.go | 226 +++++++++++------- .../src/k8s.io/api/batch/v1/generated.proto | 6 + .../batch/v1/types_swagger_doc_generated.go | 1 + .../api/batch/v1/zz_generated.deepcopy.go | 5 + .../k8s.io/api/batch/v1beta1/generated.pb.go | 148 ++++++++---- .../k8s.io/api/batch/v1beta1/generated.proto | 6 + .../v1beta1/types_swagger_doc_generated.go | 1 + .../batch/v1beta1/zz_generated.deepcopy.go | 5 + .../api/testdata/HEAD/batch.v1.CronJob.json | 1 + .../api/testdata/HEAD/batch.v1.CronJob.pb | Bin 10314 -> 10329 bytes .../api/testdata/HEAD/batch.v1.CronJob.yaml | 1 + .../testdata/HEAD/batch.v1beta1.CronJob.json | 1 + .../testdata/HEAD/batch.v1beta1.CronJob.pb | Bin 10319 -> 10334 bytes .../testdata/HEAD/batch.v1beta1.CronJob.yaml | 1 + .../batch/v1/cronjobspec.go | 9 + .../batch/v1beta1/cronjobspec.go | 9 + .../applyconfigurations/internal/internal.go | 6 + 24 files changed, 324 insertions(+), 141 deletions(-) diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 3a10c295b12..87cfa4ea723 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -4075,6 +4075,10 @@ "suspend": { "description": "This flag tells the controller to suspend subsequent executions, it does not apply to already started executions. Defaults to false.", "type": "boolean" + }, + "timeZone": { + "description": "The time zone for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. If not specified, this will rely on the time zone of the kube-controller-manager process. ALPHA: This field is in alpha and must be enabled via the `CronJobTimeZone` feature gate.", + "type": "string" } }, "required": [ @@ -4450,6 +4454,10 @@ "suspend": { "description": "This flag tells the controller to suspend subsequent executions, it does not apply to already started executions. Defaults to false.", "type": "boolean" + }, + "timeZone": { + "description": "The time zone for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. If not specified, this will rely on the time zone of the kube-controller-manager process. ALPHA: This field is in alpha and must be enabled via the `CronJobTimeZone` feature gate.", + "type": "string" } }, "required": [ diff --git a/api/openapi-spec/v3/apis__batch__v1_openapi.json b/api/openapi-spec/v3/apis__batch__v1_openapi.json index d12028554f9..132ead6adea 100644 --- a/api/openapi-spec/v3/apis__batch__v1_openapi.json +++ b/api/openapi-spec/v3/apis__batch__v1_openapi.json @@ -114,6 +114,10 @@ "suspend": { "description": "This flag tells the controller to suspend subsequent executions, it does not apply to already started executions. Defaults to false.", "type": "boolean" + }, + "timeZone": { + "description": "The time zone for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. If not specified, this will rely on the time zone of the kube-controller-manager process. ALPHA: This field is in alpha and must be enabled via the `CronJobTimeZone` feature gate.", + "type": "string" } }, "required": [ diff --git a/api/openapi-spec/v3/apis__batch__v1beta1_openapi.json b/api/openapi-spec/v3/apis__batch__v1beta1_openapi.json index 90ce5f3b2d0..e5d7dd8b58f 100644 --- a/api/openapi-spec/v3/apis__batch__v1beta1_openapi.json +++ b/api/openapi-spec/v3/apis__batch__v1beta1_openapi.json @@ -164,6 +164,10 @@ "suspend": { "description": "This flag tells the controller to suspend subsequent executions, it does not apply to already started executions. Defaults to false.", "type": "boolean" + }, + "timeZone": { + "description": "The time zone for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. If not specified, this will rely on the time zone of the kube-controller-manager process. ALPHA: This field is in alpha and must be enabled via the `CronJobTimeZone` feature gate.", + "type": "string" } }, "required": [ diff --git a/pkg/apis/batch/v1/zz_generated.conversion.go b/pkg/apis/batch/v1/zz_generated.conversion.go index 78d8f364ee3..e01e9662822 100644 --- a/pkg/apis/batch/v1/zz_generated.conversion.go +++ b/pkg/apis/batch/v1/zz_generated.conversion.go @@ -231,6 +231,7 @@ func Convert_batch_CronJobList_To_v1_CronJobList(in *batch.CronJobList, out *v1. func autoConvert_v1_CronJobSpec_To_batch_CronJobSpec(in *v1.CronJobSpec, out *batch.CronJobSpec, s conversion.Scope) error { out.Schedule = in.Schedule + out.TimeZone = (*string)(unsafe.Pointer(in.TimeZone)) out.StartingDeadlineSeconds = (*int64)(unsafe.Pointer(in.StartingDeadlineSeconds)) out.ConcurrencyPolicy = batch.ConcurrencyPolicy(in.ConcurrencyPolicy) out.Suspend = (*bool)(unsafe.Pointer(in.Suspend)) @@ -249,6 +250,7 @@ func Convert_v1_CronJobSpec_To_batch_CronJobSpec(in *v1.CronJobSpec, out *batch. func autoConvert_batch_CronJobSpec_To_v1_CronJobSpec(in *batch.CronJobSpec, out *v1.CronJobSpec, s conversion.Scope) error { out.Schedule = in.Schedule + out.TimeZone = (*string)(unsafe.Pointer(in.TimeZone)) out.StartingDeadlineSeconds = (*int64)(unsafe.Pointer(in.StartingDeadlineSeconds)) out.ConcurrencyPolicy = v1.ConcurrencyPolicy(in.ConcurrencyPolicy) out.Suspend = (*bool)(unsafe.Pointer(in.Suspend)) diff --git a/pkg/apis/batch/v1beta1/zz_generated.conversion.go b/pkg/apis/batch/v1beta1/zz_generated.conversion.go index a6d7f5b17d4..944c06d0efe 100644 --- a/pkg/apis/batch/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/batch/v1beta1/zz_generated.conversion.go @@ -180,6 +180,7 @@ func Convert_batch_CronJobList_To_v1beta1_CronJobList(in *batch.CronJobList, out func autoConvert_v1beta1_CronJobSpec_To_batch_CronJobSpec(in *v1beta1.CronJobSpec, out *batch.CronJobSpec, s conversion.Scope) error { out.Schedule = in.Schedule + out.TimeZone = (*string)(unsafe.Pointer(in.TimeZone)) out.StartingDeadlineSeconds = (*int64)(unsafe.Pointer(in.StartingDeadlineSeconds)) out.ConcurrencyPolicy = batch.ConcurrencyPolicy(in.ConcurrencyPolicy) out.Suspend = (*bool)(unsafe.Pointer(in.Suspend)) @@ -198,6 +199,7 @@ func Convert_v1beta1_CronJobSpec_To_batch_CronJobSpec(in *v1beta1.CronJobSpec, o func autoConvert_batch_CronJobSpec_To_v1beta1_CronJobSpec(in *batch.CronJobSpec, out *v1beta1.CronJobSpec, s conversion.Scope) error { out.Schedule = in.Schedule + out.TimeZone = (*string)(unsafe.Pointer(in.TimeZone)) out.StartingDeadlineSeconds = (*int64)(unsafe.Pointer(in.StartingDeadlineSeconds)) out.ConcurrencyPolicy = v1beta1.ConcurrencyPolicy(in.ConcurrencyPolicy) out.Suspend = (*bool)(unsafe.Pointer(in.Suspend)) diff --git a/pkg/apis/batch/zz_generated.deepcopy.go b/pkg/apis/batch/zz_generated.deepcopy.go index ccc36fd51db..d5e5c02c338 100644 --- a/pkg/apis/batch/zz_generated.deepcopy.go +++ b/pkg/apis/batch/zz_generated.deepcopy.go @@ -92,6 +92,11 @@ func (in *CronJobList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CronJobSpec) DeepCopyInto(out *CronJobSpec) { *out = *in + if in.TimeZone != nil { + in, out := &in.TimeZone, &out.TimeZone + *out = new(string) + **out = **in + } if in.StartingDeadlineSeconds != nil { in, out := &in.StartingDeadlineSeconds, &out.StartingDeadlineSeconds *out = new(int64) diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index 2659d740e15..4e92d32ded2 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -12655,6 +12655,13 @@ func schema_k8sio_api_batch_v1_CronJobSpec(ref common.ReferenceCallback) common. Format: "", }, }, + "timeZone": { + SchemaProps: spec.SchemaProps{ + Description: "The time zone for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. If not specified, this will rely on the time zone of the kube-controller-manager process. ALPHA: This field is in alpha and must be enabled via the `CronJobTimeZone` feature gate.", + Type: []string{"string"}, + Format: "", + }, + }, "startingDeadlineSeconds": { SchemaProps: spec.SchemaProps{ Description: "Optional deadline in seconds for starting the job if it misses scheduled time for any reason. Missed jobs executions will be counted as failed ones.", @@ -13285,6 +13292,13 @@ func schema_k8sio_api_batch_v1beta1_CronJobSpec(ref common.ReferenceCallback) co Format: "", }, }, + "timeZone": { + SchemaProps: spec.SchemaProps{ + Description: "The time zone for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. If not specified, this will rely on the time zone of the kube-controller-manager process. ALPHA: This field is in alpha and must be enabled via the `CronJobTimeZone` feature gate.", + Type: []string{"string"}, + Format: "", + }, + }, "startingDeadlineSeconds": { SchemaProps: spec.SchemaProps{ Description: "Optional deadline in seconds for starting the job if it misses scheduled time for any reason. Missed jobs executions will be counted as failed ones.", diff --git a/staging/src/k8s.io/api/batch/v1/generated.pb.go b/staging/src/k8s.io/api/batch/v1/generated.pb.go index 618c59afe6b..483406d84c5 100644 --- a/staging/src/k8s.io/api/batch/v1/generated.pb.go +++ b/staging/src/k8s.io/api/batch/v1/generated.pb.go @@ -375,96 +375,97 @@ func init() { } var fileDescriptor_3b52da57c93de713 = []byte{ - // 1416 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0xc1, 0x6f, 0x1b, 0x45, - 0x17, 0xcf, 0x26, 0x71, 0x6c, 0x8f, 0x93, 0xd4, 0x9d, 0x7e, 0x6d, 0xfd, 0xf9, 0xab, 0xbc, 0xa9, - 0xbf, 0x82, 0x02, 0x2a, 0x6b, 0x12, 0x22, 0x04, 0x08, 0x90, 0xb2, 0xa9, 0x0a, 0x0d, 0x8e, 0x1a, - 0xc6, 0x8e, 0x90, 0xa0, 0x20, 0xd6, 0xbb, 0x63, 0x67, 0x9b, 0xdd, 0x1d, 0x6b, 0x67, 0x1c, 0xe1, - 0x1b, 0x12, 0xff, 0x00, 0xfc, 0x13, 0x88, 0x13, 0x42, 0x82, 0x33, 0x47, 0xd4, 0x63, 0x8f, 0x3d, - 0xad, 0xe8, 0xf2, 0x07, 0x70, 0x0f, 0x17, 0x34, 0xb3, 0xe3, 0xdd, 0xb5, 0xbd, 0x1b, 0xd2, 0x1e, - 0x2a, 0x6e, 0xdd, 0x37, 0xbf, 0xf7, 0x9b, 0xe7, 0xf7, 0x7e, 0xf3, 0xde, 0x4b, 0xc1, 0xbb, 0x27, - 0x6f, 0x51, 0xcd, 0x26, 0xad, 0x93, 0x51, 0x0f, 0xfb, 0x1e, 0x66, 0x98, 0xb6, 0x4e, 0xb1, 0x67, - 0x11, 0xbf, 0x25, 0x0f, 0x8c, 0xa1, 0xdd, 0xea, 0x19, 0xcc, 0x3c, 0x6e, 0x9d, 0x6e, 0xb5, 0x06, - 0xd8, 0xc3, 0xbe, 0xc1, 0xb0, 0xa5, 0x0d, 0x7d, 0xc2, 0x08, 0xbc, 0x12, 0x81, 0x34, 0x63, 0x68, - 0x6b, 0x02, 0xa4, 0x9d, 0x6e, 0xd5, 0x5f, 0x1b, 0xd8, 0xec, 0x78, 0xd4, 0xd3, 0x4c, 0xe2, 0xb6, - 0x06, 0x64, 0x40, 0x5a, 0x02, 0xdb, 0x1b, 0xf5, 0xc5, 0x97, 0xf8, 0x10, 0xff, 0x8a, 0x38, 0xea, - 0xcd, 0xd4, 0x45, 0x26, 0xf1, 0x71, 0xc6, 0x3d, 0xf5, 0x9d, 0x04, 0xe3, 0x1a, 0xe6, 0xb1, 0xed, - 0x61, 0x7f, 0xdc, 0x1a, 0x9e, 0x0c, 0xb8, 0x81, 0xb6, 0x5c, 0xcc, 0x8c, 0x2c, 0xaf, 0x56, 0x9e, - 0x97, 0x3f, 0xf2, 0x98, 0xed, 0xe2, 0x39, 0x87, 0x37, 0xff, 0xc9, 0x81, 0x9a, 0xc7, 0xd8, 0x35, - 0x66, 0xfd, 0x9a, 0x7f, 0x29, 0xa0, 0xb8, 0xe7, 0x13, 0x6f, 0x9f, 0xf4, 0xe0, 0x97, 0xa0, 0xc4, - 0xe3, 0xb1, 0x0c, 0x66, 0xd4, 0x94, 0x0d, 0x65, 0xb3, 0xb2, 0xfd, 0xba, 0x96, 0x64, 0x29, 0xa6, - 0xd5, 0x86, 0x27, 0x03, 0x6e, 0xa0, 0x1a, 0x47, 0x6b, 0xa7, 0x5b, 0xda, 0xfd, 0xde, 0x43, 0x6c, - 0xb2, 0x03, 0xcc, 0x0c, 0x1d, 0x3e, 0x0a, 0xd4, 0x85, 0x30, 0x50, 0x41, 0x62, 0x43, 0x31, 0x2b, - 0xd4, 0xc1, 0x32, 0x1d, 0x62, 0xb3, 0xb6, 0x28, 0xd8, 0x37, 0xb4, 0x8c, 0x1a, 0x68, 0x32, 0x9a, - 0xce, 0x10, 0x9b, 0xfa, 0xaa, 0x64, 0x5b, 0xe6, 0x5f, 0x48, 0xf8, 0xc2, 0x7d, 0xb0, 0x42, 0x99, - 0xc1, 0x46, 0xb4, 0xb6, 0x24, 0x58, 0x9a, 0xe7, 0xb2, 0x08, 0xa4, 0xbe, 0x2e, 0x79, 0x56, 0xa2, - 0x6f, 0x24, 0x19, 0x9a, 0x3f, 0x2a, 0xa0, 0x22, 0x91, 0x6d, 0x9b, 0x32, 0xf8, 0x60, 0x2e, 0x03, - 0xda, 0xc5, 0x32, 0xc0, 0xbd, 0xc5, 0xef, 0xaf, 0xca, 0x9b, 0x4a, 0x13, 0x4b, 0xea, 0xd7, 0xef, - 0x82, 0x82, 0xcd, 0xb0, 0x4b, 0x6b, 0x8b, 0x1b, 0x4b, 0x9b, 0x95, 0xed, 0x1b, 0xe7, 0x05, 0xae, - 0xaf, 0x49, 0xa2, 0xc2, 0x3d, 0xee, 0x82, 0x22, 0xcf, 0xe6, 0x0f, 0xcb, 0x71, 0xc0, 0x3c, 0x25, - 0xf0, 0x36, 0x28, 0xf1, 0xc2, 0x5a, 0x23, 0x07, 0x8b, 0x80, 0xcb, 0x49, 0x00, 0x1d, 0x69, 0x47, - 0x31, 0x02, 0x1e, 0x81, 0xeb, 0x94, 0x19, 0x3e, 0xb3, 0xbd, 0xc1, 0x1d, 0x6c, 0x58, 0x8e, 0xed, - 0xe1, 0x0e, 0x36, 0x89, 0x67, 0x51, 0x51, 0x91, 0x25, 0xfd, 0x7f, 0x61, 0xa0, 0x5e, 0xef, 0x64, - 0x43, 0x50, 0x9e, 0x2f, 0x7c, 0x00, 0x2e, 0x9b, 0xc4, 0x33, 0x47, 0xbe, 0x8f, 0x3d, 0x73, 0x7c, - 0x48, 0x1c, 0xdb, 0x1c, 0x8b, 0xe2, 0x94, 0x75, 0x4d, 0x46, 0x73, 0x79, 0x6f, 0x16, 0x70, 0x96, - 0x65, 0x44, 0xf3, 0x44, 0xf0, 0x25, 0x50, 0xa4, 0x23, 0x3a, 0xc4, 0x9e, 0x55, 0x5b, 0xde, 0x50, - 0x36, 0x4b, 0x7a, 0x25, 0x0c, 0xd4, 0x62, 0x27, 0x32, 0xa1, 0xc9, 0x19, 0xfc, 0x0c, 0x54, 0x1e, - 0x92, 0x5e, 0x17, 0xbb, 0x43, 0xc7, 0x60, 0xb8, 0x56, 0x10, 0xd5, 0xbb, 0x95, 0x99, 0xe2, 0xfd, - 0x04, 0x27, 0x54, 0x76, 0x45, 0x06, 0x59, 0x49, 0x1d, 0xa0, 0x34, 0x1b, 0xfc, 0x02, 0xd4, 0xe9, - 0xc8, 0x34, 0x31, 0xa5, 0xfd, 0x91, 0xb3, 0x4f, 0x7a, 0xf4, 0x43, 0x9b, 0x32, 0xe2, 0x8f, 0xdb, - 0xb6, 0x6b, 0xb3, 0xda, 0xca, 0x86, 0xb2, 0x59, 0xd0, 0x1b, 0x61, 0xa0, 0xd6, 0x3b, 0xb9, 0x28, - 0x74, 0x0e, 0x03, 0x44, 0xe0, 0x5a, 0xdf, 0xb0, 0x1d, 0x6c, 0xcd, 0x71, 0x17, 0x05, 0x77, 0x3d, - 0x0c, 0xd4, 0x6b, 0x77, 0x33, 0x11, 0x28, 0xc7, 0xb3, 0xf9, 0xeb, 0x22, 0x58, 0x9b, 0x7a, 0x05, - 0xf0, 0x23, 0xb0, 0x62, 0x98, 0xcc, 0x3e, 0xe5, 0x52, 0xe1, 0x02, 0xfc, 0x7f, 0x3a, 0x3b, 0xbc, - 0x7f, 0x25, 0x6f, 0x19, 0xe1, 0x3e, 0xe6, 0x45, 0xc0, 0xc9, 0xd3, 0xd9, 0x15, 0xae, 0x48, 0x52, - 0x40, 0x07, 0x54, 0x1d, 0x83, 0xb2, 0x89, 0xca, 0xba, 0xb6, 0x8b, 0x45, 0x7d, 0x2a, 0xdb, 0xaf, - 0x5e, 0xec, 0xc9, 0x70, 0x0f, 0xfd, 0x3f, 0x61, 0xa0, 0x56, 0xdb, 0x33, 0x3c, 0x68, 0x8e, 0x19, - 0xfa, 0x00, 0x0a, 0x5b, 0x9c, 0x42, 0x71, 0x5f, 0xe1, 0x99, 0xef, 0xbb, 0x16, 0x06, 0x2a, 0x6c, - 0xcf, 0x31, 0xa1, 0x0c, 0xf6, 0xe6, 0x9f, 0x0a, 0x58, 0x7a, 0x31, 0x6d, 0xf1, 0xfd, 0xa9, 0xb6, - 0x78, 0x23, 0x4f, 0xb4, 0xb9, 0x2d, 0xf1, 0xee, 0x4c, 0x4b, 0x6c, 0xe4, 0x32, 0x9c, 0xdf, 0x0e, - 0x7f, 0x5b, 0x02, 0xab, 0xfb, 0xa4, 0xb7, 0x47, 0x3c, 0xcb, 0x66, 0x36, 0xf1, 0xe0, 0x0e, 0x58, - 0x66, 0xe3, 0xe1, 0xa4, 0xb5, 0x6c, 0x4c, 0xae, 0xee, 0x8e, 0x87, 0xf8, 0x2c, 0x50, 0xab, 0x69, - 0x2c, 0xb7, 0x21, 0x81, 0x86, 0xed, 0x38, 0x9c, 0x45, 0xe1, 0xb7, 0x33, 0x7d, 0xdd, 0x59, 0xa0, - 0x66, 0x0c, 0x4e, 0x2d, 0x66, 0x9a, 0x0e, 0x0a, 0x0e, 0xc0, 0x1a, 0x2f, 0xce, 0xa1, 0x4f, 0x7a, - 0x91, 0xca, 0x96, 0x9e, 0xb9, 0xea, 0x57, 0x65, 0x00, 0x6b, 0xed, 0x34, 0x11, 0x9a, 0xe6, 0x85, - 0xa7, 0x91, 0xc6, 0xba, 0xbe, 0xe1, 0xd1, 0xe8, 0x27, 0x3d, 0x9f, 0xa6, 0xeb, 0xf2, 0x36, 0xa1, - 0xb3, 0x69, 0x36, 0x94, 0x71, 0x03, 0x7c, 0x19, 0xac, 0xf8, 0xd8, 0xa0, 0xc4, 0x13, 0x7a, 0x2e, - 0x27, 0xd5, 0x41, 0xc2, 0x8a, 0xe4, 0x29, 0x7c, 0x05, 0x14, 0x5d, 0x4c, 0xa9, 0x31, 0xc0, 0xa2, - 0xe3, 0x94, 0xf5, 0x4b, 0x12, 0x58, 0x3c, 0x88, 0xcc, 0x68, 0x72, 0xde, 0xfc, 0x5e, 0x01, 0xc5, - 0x17, 0x33, 0xd3, 0xde, 0x9b, 0x9e, 0x69, 0xb5, 0x3c, 0xe5, 0xe5, 0xcc, 0xb3, 0x9f, 0x0a, 0x22, - 0x50, 0x31, 0xcb, 0xb6, 0x40, 0x65, 0x68, 0xf8, 0x86, 0xe3, 0x60, 0xc7, 0xa6, 0xae, 0x88, 0xb5, - 0xa0, 0x5f, 0xe2, 0x7d, 0xf9, 0x30, 0x31, 0xa3, 0x34, 0x86, 0xbb, 0x98, 0xc4, 0x1d, 0x3a, 0x98, - 0x27, 0x33, 0x92, 0x9b, 0x74, 0xd9, 0x4b, 0xcc, 0x28, 0x8d, 0x81, 0xf7, 0xc1, 0xd5, 0xa8, 0x83, - 0xcd, 0x4e, 0xc0, 0x25, 0x31, 0x01, 0xff, 0x1b, 0x06, 0xea, 0xd5, 0xdd, 0x2c, 0x00, 0xca, 0xf6, - 0x83, 0x3b, 0x60, 0xb5, 0x67, 0x98, 0x27, 0xa4, 0xdf, 0x4f, 0x77, 0xec, 0x6a, 0x18, 0xa8, 0xab, - 0x7a, 0xca, 0x8e, 0xa6, 0x50, 0xf0, 0x73, 0x50, 0xa2, 0xd8, 0xc1, 0x26, 0x23, 0xbe, 0x94, 0xd8, - 0x1b, 0x17, 0xac, 0x8a, 0xd1, 0xc3, 0x4e, 0x47, 0xba, 0xea, 0xab, 0x62, 0xd2, 0xcb, 0x2f, 0x14, - 0x53, 0xc2, 0x77, 0xc0, 0xba, 0x6b, 0x78, 0x23, 0x23, 0x46, 0x0a, 0x6d, 0x95, 0x74, 0x18, 0x06, - 0xea, 0xfa, 0xc1, 0xd4, 0x09, 0x9a, 0x41, 0xc2, 0x8f, 0x41, 0x89, 0x4d, 0xc6, 0xe8, 0x8a, 0x08, - 0x2d, 0x73, 0x50, 0x1c, 0x12, 0x6b, 0x6a, 0x8a, 0xc6, 0x2a, 0x89, 0x47, 0x68, 0x4c, 0xc3, 0x17, - 0x0f, 0xc6, 0x1c, 0x99, 0xb1, 0xdd, 0x3e, 0xc3, 0xfe, 0x5d, 0xdb, 0xb3, 0xe9, 0x31, 0xb6, 0x6a, - 0x25, 0x91, 0x2e, 0xb1, 0x78, 0x74, 0xbb, 0xed, 0x2c, 0x08, 0xca, 0xf3, 0x85, 0x6d, 0xb0, 0x9e, - 0x94, 0xf6, 0x80, 0x58, 0xb8, 0x56, 0x16, 0x0f, 0xe3, 0x16, 0xff, 0x95, 0x7b, 0x53, 0x27, 0x67, - 0x73, 0x16, 0x34, 0xe3, 0x9b, 0x5e, 0x34, 0x40, 0xfe, 0xa2, 0xd1, 0xfc, 0xae, 0x00, 0xca, 0xc9, - 0x4c, 0x3d, 0x02, 0xc0, 0x9c, 0x34, 0x2e, 0x2a, 0xe7, 0xea, 0xcd, 0xbc, 0x47, 0x10, 0xb7, 0xb8, - 0x64, 0x1e, 0xc4, 0x26, 0x8a, 0x52, 0x44, 0xf0, 0x13, 0x50, 0x16, 0xdb, 0x96, 0x68, 0x41, 0x8b, - 0xcf, 0xdc, 0x82, 0xd6, 0xc2, 0x40, 0x2d, 0x77, 0x26, 0x04, 0x28, 0xe1, 0x82, 0xfd, 0x74, 0xca, - 0x9e, 0xb3, 0x9d, 0xc2, 0xe9, 0xf4, 0x8a, 0x2b, 0x66, 0x58, 0x79, 0x53, 0x93, 0xbb, 0xc6, 0xb2, - 0x28, 0x70, 0xde, 0x1a, 0xd1, 0x02, 0x65, 0xb1, 0x17, 0x61, 0x0b, 0x5b, 0x42, 0xa3, 0x05, 0xfd, - 0xb2, 0x84, 0x96, 0x3b, 0x93, 0x03, 0x94, 0x60, 0x38, 0x71, 0xb4, 0xf0, 0xc8, 0xb5, 0x2b, 0x26, - 0x8e, 0xd6, 0x23, 0x24, 0x4f, 0xe1, 0x1d, 0x50, 0x95, 0x21, 0x61, 0xeb, 0x9e, 0x67, 0xe1, 0xaf, - 0x30, 0x15, 0x4f, 0xb3, 0xac, 0xd7, 0xa4, 0x47, 0x75, 0x6f, 0xe6, 0x1c, 0xcd, 0x79, 0xc0, 0x6f, - 0x14, 0x70, 0x7d, 0xe4, 0x99, 0x64, 0xe4, 0x31, 0x6c, 0x75, 0xb1, 0xef, 0xda, 0x1e, 0xff, 0xe3, - 0xe9, 0x90, 0x58, 0x54, 0x28, 0xb7, 0xb2, 0x7d, 0x3b, 0xb3, 0xd8, 0x47, 0xd9, 0x3e, 0x91, 0xce, - 0x73, 0x0e, 0x51, 0xde, 0x4d, 0x50, 0x05, 0x05, 0x1f, 0x1b, 0xd6, 0x58, 0xc8, 0xbb, 0xa0, 0x97, - 0x79, 0x1b, 0x45, 0xdc, 0x80, 0x22, 0x7b, 0xf3, 0x67, 0x05, 0x5c, 0x9a, 0xd9, 0x6a, 0xff, 0xfd, - 0x6b, 0x4b, 0xf3, 0x17, 0x05, 0xe4, 0xe5, 0x02, 0x1e, 0xa6, 0x75, 0xc1, 0x9f, 0x55, 0x59, 0xdf, - 0x9e, 0xd2, 0xc4, 0x59, 0xa0, 0xde, 0xcc, 0xfb, 0x9b, 0x97, 0x6f, 0x21, 0x54, 0x3b, 0xba, 0x77, - 0x27, 0x2d, 0x9c, 0x0f, 0x62, 0xe1, 0x2c, 0x0a, 0xba, 0x56, 0x22, 0x9a, 0x8b, 0x71, 0x49, 0x77, - 0xfd, 0xed, 0x47, 0x4f, 0x1b, 0x0b, 0x8f, 0x9f, 0x36, 0x16, 0x9e, 0x3c, 0x6d, 0x2c, 0x7c, 0x1d, - 0x36, 0x94, 0x47, 0x61, 0x43, 0x79, 0x1c, 0x36, 0x94, 0x27, 0x61, 0x43, 0xf9, 0x3d, 0x6c, 0x28, - 0xdf, 0xfe, 0xd1, 0x58, 0xf8, 0xf4, 0x4a, 0xc6, 0x7f, 0x42, 0xfc, 0x1d, 0x00, 0x00, 0xff, 0xff, - 0x07, 0x84, 0xfd, 0x6a, 0xb3, 0x10, 0x00, 0x00, + // 1431 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x58, 0x41, 0x6f, 0x1b, 0xc5, + 0x17, 0xcf, 0xc6, 0x71, 0x6c, 0x8f, 0x93, 0xd4, 0x9d, 0xfe, 0xdb, 0xfa, 0x6f, 0x2a, 0x6f, 0x6a, + 0x0a, 0x0a, 0xa8, 0xac, 0x49, 0x88, 0x10, 0x20, 0x40, 0xca, 0xa6, 0x2a, 0x34, 0x38, 0x6a, 0x18, + 0x3b, 0x42, 0x2a, 0x05, 0xb1, 0xde, 0x1d, 0x3b, 0xdb, 0xac, 0x77, 0xac, 0x9d, 0x71, 0x84, 0x6f, + 0x48, 0x7c, 0x01, 0xf8, 0x12, 0x1c, 0x11, 0x12, 0x9c, 0x39, 0xa2, 0x1e, 0x2b, 0x4e, 0x3d, 0xad, + 0xe8, 0xf2, 0x01, 0xb8, 0x87, 0x0b, 0x9a, 0xd9, 0xf1, 0xee, 0xda, 0xde, 0x0d, 0x49, 0x0f, 0x15, + 0xb7, 0xcc, 0x9b, 0xdf, 0xfb, 0xcd, 0xdb, 0x79, 0xbf, 0x79, 0xef, 0x39, 0xe0, 0xfd, 0xe3, 0x77, + 0xa8, 0x66, 0x93, 0xe6, 0xf1, 0xa8, 0x8b, 0x3d, 0x17, 0x33, 0x4c, 0x9b, 0x27, 0xd8, 0xb5, 0x88, + 0xd7, 0x94, 0x1b, 0xc6, 0xd0, 0x6e, 0x76, 0x0d, 0x66, 0x1e, 0x35, 0x4f, 0x36, 0x9b, 0x7d, 0xec, + 0x62, 0xcf, 0x60, 0xd8, 0xd2, 0x86, 0x1e, 0x61, 0x04, 0x5e, 0x09, 0x41, 0x9a, 0x31, 0xb4, 0x35, + 0x01, 0xd2, 0x4e, 0x36, 0x6b, 0x6f, 0xf4, 0x6d, 0x76, 0x34, 0xea, 0x6a, 0x26, 0x19, 0x34, 0xfb, + 0xa4, 0x4f, 0x9a, 0x02, 0xdb, 0x1d, 0xf5, 0xc4, 0x4a, 0x2c, 0xc4, 0x5f, 0x21, 0x47, 0xad, 0x91, + 0x38, 0xc8, 0x24, 0x1e, 0x4e, 0x39, 0xa7, 0xb6, 0x1d, 0x63, 0x06, 0x86, 0x79, 0x64, 0xbb, 0xd8, + 0x1b, 0x37, 0x87, 0xc7, 0x7d, 0x6e, 0xa0, 0xcd, 0x01, 0x66, 0x46, 0x9a, 0x57, 0x33, 0xcb, 0xcb, + 0x1b, 0xb9, 0xcc, 0x1e, 0xe0, 0x39, 0x87, 0xb7, 0xff, 0xcd, 0x81, 0x9a, 0x47, 0x78, 0x60, 0xcc, + 0xfa, 0x35, 0xfe, 0x56, 0x40, 0x61, 0xd7, 0x23, 0xee, 0x1e, 0xe9, 0xc2, 0xaf, 0x40, 0x91, 0xc7, + 0x63, 0x19, 0xcc, 0xa8, 0x2a, 0xeb, 0xca, 0x46, 0x79, 0xeb, 0x4d, 0x2d, 0xbe, 0xa5, 0x88, 0x56, + 0x1b, 0x1e, 0xf7, 0xb9, 0x81, 0x6a, 0x1c, 0xad, 0x9d, 0x6c, 0x6a, 0xf7, 0xbb, 0x8f, 0xb0, 0xc9, + 0xf6, 0x31, 0x33, 0x74, 0xf8, 0xd8, 0x57, 0x17, 0x02, 0x5f, 0x05, 0xb1, 0x0d, 0x45, 0xac, 0x50, + 0x07, 0x4b, 0x74, 0x88, 0xcd, 0xea, 0xa2, 0x60, 0x5f, 0xd7, 0x52, 0x72, 0xa0, 0xc9, 0x68, 0xda, + 0x43, 0x6c, 0xea, 0x2b, 0x92, 0x6d, 0x89, 0xaf, 0x90, 0xf0, 0x85, 0x7b, 0x60, 0x99, 0x32, 0x83, + 0x8d, 0x68, 0x35, 0x27, 0x58, 0x1a, 0x67, 0xb2, 0x08, 0xa4, 0xbe, 0x26, 0x79, 0x96, 0xc3, 0x35, + 0x92, 0x0c, 0x8d, 0x1f, 0x15, 0x50, 0x96, 0xc8, 0x96, 0x4d, 0x19, 0x7c, 0x38, 0x77, 0x03, 0xda, + 0xf9, 0x6e, 0x80, 0x7b, 0x8b, 0xef, 0xaf, 0xc8, 0x93, 0x8a, 0x13, 0x4b, 0xe2, 0xeb, 0x77, 0x40, + 0xde, 0x66, 0x78, 0x40, 0xab, 0x8b, 0xeb, 0xb9, 0x8d, 0xf2, 0xd6, 0x8d, 0xb3, 0x02, 0xd7, 0x57, + 0x25, 0x51, 0xfe, 0x1e, 0x77, 0x41, 0xa1, 0x67, 0xe3, 0xf7, 0xa5, 0x28, 0x60, 0x7e, 0x25, 0xf0, + 0x36, 0x28, 0xf2, 0xc4, 0x5a, 0x23, 0x07, 0x8b, 0x80, 0x4b, 0x71, 0x00, 0x6d, 0x69, 0x47, 0x11, + 0x02, 0x6e, 0x80, 0x22, 0xd7, 0xc2, 0x03, 0xe2, 0xe2, 0x6a, 0x51, 0xa0, 0x57, 0x38, 0xb2, 0x23, + 0x6d, 0x28, 0xda, 0x85, 0x87, 0xe0, 0x3a, 0x65, 0x86, 0xc7, 0x6c, 0xb7, 0x7f, 0x07, 0x1b, 0x96, + 0x63, 0xbb, 0xb8, 0x8d, 0x4d, 0xe2, 0x5a, 0x54, 0xe4, 0x2e, 0xa7, 0xbf, 0x14, 0xf8, 0xea, 0xf5, + 0x76, 0x3a, 0x04, 0x65, 0xf9, 0xc2, 0x87, 0xe0, 0xb2, 0x49, 0x5c, 0x73, 0xe4, 0x79, 0xd8, 0x35, + 0xc7, 0x07, 0xc4, 0xb1, 0xcd, 0xb1, 0x48, 0x63, 0x49, 0xd7, 0x64, 0xdc, 0x97, 0x77, 0x67, 0x01, + 0xa7, 0x69, 0x46, 0x34, 0x4f, 0x04, 0x5f, 0x01, 0x05, 0x3a, 0xa2, 0x43, 0xec, 0x5a, 0xd5, 0xa5, + 0x75, 0x65, 0xa3, 0xa8, 0x97, 0x03, 0x5f, 0x2d, 0xb4, 0x43, 0x13, 0x9a, 0xec, 0xc1, 0xcf, 0x41, + 0xf9, 0x11, 0xe9, 0x76, 0xf0, 0x60, 0xe8, 0x18, 0x0c, 0x57, 0xf3, 0x22, 0xcf, 0xb7, 0x52, 0x93, + 0xb1, 0x17, 0xe3, 0x84, 0x1e, 0xaf, 0xc8, 0x20, 0xcb, 0x89, 0x0d, 0x94, 0x64, 0x83, 0x5f, 0x82, + 0x1a, 0x1d, 0x99, 0x26, 0xa6, 0xb4, 0x37, 0x72, 0xf6, 0x48, 0x97, 0x7e, 0x6c, 0x53, 0x46, 0xbc, + 0x71, 0xcb, 0x1e, 0xd8, 0xac, 0xba, 0xbc, 0xae, 0x6c, 0xe4, 0xf5, 0x7a, 0xe0, 0xab, 0xb5, 0x76, + 0x26, 0x0a, 0x9d, 0xc1, 0x00, 0x11, 0xb8, 0xd6, 0x33, 0x6c, 0x07, 0x5b, 0x73, 0xdc, 0x05, 0xc1, + 0x5d, 0x0b, 0x7c, 0xf5, 0xda, 0xdd, 0x54, 0x04, 0xca, 0xf0, 0x6c, 0xfc, 0xba, 0x08, 0x56, 0xa7, + 0xde, 0x0b, 0xfc, 0x04, 0x2c, 0x1b, 0x26, 0xb3, 0x4f, 0xb8, 0xa8, 0xb8, 0x54, 0x5f, 0x4e, 0xde, + 0x0e, 0xaf, 0x74, 0xf1, 0xab, 0x47, 0xb8, 0x87, 0x79, 0x12, 0x70, 0xfc, 0xc8, 0x76, 0x84, 0x2b, + 0x92, 0x14, 0xd0, 0x01, 0x15, 0xc7, 0xa0, 0x6c, 0xa2, 0x47, 0xae, 0x36, 0x91, 0x9f, 0xf2, 0xd6, + 0xeb, 0xe7, 0x7b, 0x5c, 0xdc, 0x43, 0xff, 0x5f, 0xe0, 0xab, 0x95, 0xd6, 0x0c, 0x0f, 0x9a, 0x63, + 0x86, 0x1e, 0x80, 0xc2, 0x16, 0x5d, 0xa1, 0x38, 0x2f, 0x7f, 0xe1, 0xf3, 0xae, 0x05, 0xbe, 0x0a, + 0x5b, 0x73, 0x4c, 0x28, 0x85, 0xbd, 0xf1, 0x97, 0x02, 0x72, 0x2f, 0xa6, 0x80, 0x7e, 0x38, 0x55, + 0x40, 0x6f, 0x64, 0x89, 0x36, 0xb3, 0x78, 0xde, 0x9d, 0x29, 0x9e, 0xf5, 0x4c, 0x86, 0xb3, 0x0b, + 0xe7, 0x6f, 0x39, 0xb0, 0xb2, 0x47, 0xba, 0xbb, 0xc4, 0xb5, 0x6c, 0x66, 0x13, 0x17, 0x6e, 0x83, + 0x25, 0x36, 0x1e, 0x4e, 0x8a, 0xd0, 0xfa, 0xe4, 0xe8, 0xce, 0x78, 0x88, 0x4f, 0x7d, 0xb5, 0x92, + 0xc4, 0x72, 0x1b, 0x12, 0x68, 0xd8, 0x8a, 0xc2, 0x59, 0x14, 0x7e, 0xdb, 0xd3, 0xc7, 0x9d, 0xfa, + 0x6a, 0x4a, 0x8b, 0xd5, 0x22, 0xa6, 0xe9, 0xa0, 0x60, 0x1f, 0xac, 0xf2, 0xe4, 0x1c, 0x78, 0xa4, + 0x1b, 0xaa, 0x2c, 0x77, 0xe1, 0xac, 0x5f, 0x95, 0x01, 0xac, 0xb6, 0x92, 0x44, 0x68, 0x9a, 0x17, + 0x9e, 0x84, 0x1a, 0xeb, 0x78, 0x86, 0x4b, 0xc3, 0x4f, 0x7a, 0x3e, 0x4d, 0xd7, 0xe4, 0x69, 0x42, + 0x67, 0xd3, 0x6c, 0x28, 0xe5, 0x04, 0xf8, 0x2a, 0x58, 0xf6, 0xb0, 0x41, 0x89, 0x2b, 0xf4, 0x5c, + 0x8a, 0xb3, 0x83, 0x84, 0x15, 0xc9, 0x5d, 0xf8, 0x1a, 0x28, 0x0c, 0x30, 0xa5, 0x46, 0x1f, 0x8b, + 0x8a, 0x53, 0xd2, 0x2f, 0x49, 0x60, 0x61, 0x3f, 0x34, 0xa3, 0xc9, 0x7e, 0xe3, 0x07, 0x05, 0x14, + 0x5e, 0x4c, 0xf7, 0xfb, 0x60, 0xba, 0xfb, 0x55, 0xb3, 0x94, 0x97, 0xd1, 0xf9, 0x7e, 0xca, 0x8b, + 0x40, 0x45, 0xd7, 0xdb, 0x04, 0xe5, 0xa1, 0xe1, 0x19, 0x8e, 0x83, 0x1d, 0x9b, 0x0e, 0x44, 0xac, + 0x79, 0xfd, 0x12, 0xaf, 0xcb, 0x07, 0xb1, 0x19, 0x25, 0x31, 0xdc, 0xc5, 0x24, 0x83, 0xa1, 0x83, + 0xf9, 0x65, 0x86, 0x72, 0x93, 0x2e, 0xbb, 0xb1, 0x19, 0x25, 0x31, 0xf0, 0x3e, 0xb8, 0x1a, 0x56, + 0xb0, 0xd9, 0x0e, 0x98, 0x13, 0x1d, 0xf0, 0xff, 0x81, 0xaf, 0x5e, 0xdd, 0x49, 0x03, 0xa0, 0x74, + 0x3f, 0xb8, 0x0d, 0x56, 0xba, 0x86, 0x79, 0x4c, 0x7a, 0xbd, 0x64, 0xc5, 0xae, 0x04, 0xbe, 0xba, + 0xa2, 0x27, 0xec, 0x68, 0x0a, 0x05, 0xbf, 0x00, 0x45, 0x8a, 0x1d, 0x6c, 0x32, 0xe2, 0x49, 0x89, + 0xbd, 0x75, 0xce, 0xac, 0x18, 0x5d, 0xec, 0xb4, 0xa5, 0x6b, 0xd8, 0xe9, 0x27, 0x2b, 0x14, 0x51, + 0xc2, 0xf7, 0xc0, 0xda, 0xc0, 0x70, 0x47, 0x46, 0x84, 0x14, 0xda, 0x2a, 0xea, 0x30, 0xf0, 0xd5, + 0xb5, 0xfd, 0xa9, 0x1d, 0x34, 0x83, 0x84, 0x9f, 0x82, 0x22, 0x9b, 0xb4, 0xd1, 0x65, 0x11, 0x5a, + 0x6a, 0xa3, 0x38, 0x20, 0xd6, 0x54, 0x17, 0x8d, 0x54, 0x12, 0xb5, 0xd0, 0x88, 0x86, 0x0f, 0x1e, + 0x8c, 0x39, 0xf2, 0xc6, 0x76, 0x7a, 0x0c, 0x7b, 0x77, 0x6d, 0xd7, 0xa6, 0x47, 0xd8, 0x12, 0x13, + 0x4b, 0x3e, 0x1c, 0x3c, 0x3a, 0x9d, 0x56, 0x1a, 0x04, 0x65, 0xf9, 0xc2, 0x16, 0x58, 0x8b, 0x53, + 0xbb, 0x4f, 0x2c, 0x5c, 0x2d, 0x89, 0x87, 0x71, 0x8b, 0x7f, 0xe5, 0xee, 0xd4, 0xce, 0xe9, 0x9c, + 0x05, 0xcd, 0xf8, 0x26, 0x07, 0x0d, 0x90, 0x3d, 0x68, 0x34, 0xbe, 0xcf, 0x83, 0x52, 0xdc, 0x53, + 0x0f, 0x01, 0x30, 0x27, 0x85, 0x8b, 0xca, 0xbe, 0x7a, 0x33, 0xeb, 0x11, 0x44, 0x25, 0x2e, 0xee, + 0x07, 0x91, 0x89, 0xa2, 0x04, 0x11, 0xfc, 0x0c, 0x94, 0xc4, 0xb4, 0x25, 0x4a, 0xd0, 0xe2, 0x85, + 0x4b, 0xd0, 0x6a, 0xe0, 0xab, 0xa5, 0xf6, 0x84, 0x00, 0xc5, 0x5c, 0xb0, 0x97, 0xbc, 0xb2, 0xe7, + 0x2c, 0xa7, 0x70, 0xfa, 0x7a, 0xc5, 0x11, 0x33, 0xac, 0xbc, 0xa8, 0xc9, 0x59, 0x63, 0x49, 0x24, + 0x38, 0x6b, 0x8c, 0x68, 0x82, 0x92, 0x98, 0x8b, 0xb0, 0x85, 0x2d, 0xa1, 0xd1, 0xbc, 0x7e, 0x59, + 0x42, 0x4b, 0xed, 0xc9, 0x06, 0x8a, 0x31, 0x9c, 0x38, 0x1c, 0x78, 0xe4, 0xd8, 0x15, 0x11, 0x87, + 0xe3, 0x11, 0x92, 0xbb, 0xf0, 0x0e, 0xa8, 0xc8, 0x90, 0xb0, 0x75, 0xcf, 0xb5, 0xf0, 0xd7, 0x98, + 0x8a, 0xa7, 0x59, 0xd2, 0xab, 0xd2, 0xa3, 0xb2, 0x3b, 0xb3, 0x8f, 0xe6, 0x3c, 0xe0, 0xb7, 0x0a, + 0xb8, 0x3e, 0x72, 0x4d, 0x32, 0x72, 0x19, 0xb6, 0x3a, 0xd8, 0x1b, 0xd8, 0x2e, 0xff, 0x99, 0x75, + 0x40, 0x2c, 0x2a, 0x94, 0x5b, 0xde, 0xba, 0x9d, 0x9a, 0xec, 0xc3, 0x74, 0x9f, 0x50, 0xe7, 0x19, + 0x9b, 0x28, 0xeb, 0x24, 0xa8, 0x82, 0xbc, 0x87, 0x0d, 0x6b, 0x2c, 0xe4, 0x9d, 0xd7, 0x4b, 0xbc, + 0x8c, 0x22, 0x6e, 0x40, 0xa1, 0xbd, 0xf1, 0xb3, 0x02, 0x2e, 0xcd, 0x4c, 0xb5, 0xff, 0xfd, 0xb1, + 0xa5, 0xf1, 0x8b, 0x02, 0xb2, 0xee, 0x02, 0x1e, 0x24, 0x75, 0xc1, 0x9f, 0x55, 0x49, 0xdf, 0x9a, + 0xd2, 0xc4, 0xa9, 0xaf, 0xde, 0xcc, 0xfa, 0x75, 0xcc, 0xa7, 0x10, 0xaa, 0x1d, 0xde, 0xbb, 0x93, + 0x14, 0xce, 0x47, 0x91, 0x70, 0x16, 0x05, 0x5d, 0x33, 0x16, 0xcd, 0xf9, 0xb8, 0xa4, 0xbb, 0xfe, + 0xee, 0xe3, 0x67, 0xf5, 0x85, 0x27, 0xcf, 0xea, 0x0b, 0x4f, 0x9f, 0xd5, 0x17, 0xbe, 0x09, 0xea, + 0xca, 0xe3, 0xa0, 0xae, 0x3c, 0x09, 0xea, 0xca, 0xd3, 0xa0, 0xae, 0xfc, 0x11, 0xd4, 0x95, 0xef, + 0xfe, 0xac, 0x2f, 0x3c, 0xb8, 0x92, 0xf2, 0xef, 0x8a, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xae, + 0xfd, 0x54, 0xb2, 0xdd, 0x10, 0x00, 0x00, } func (m *CronJob) Marshal() (dAtA []byte, err error) { @@ -587,6 +588,13 @@ func (m *CronJobSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.TimeZone != nil { + i -= len(*m.TimeZone) + copy(dAtA[i:], *m.TimeZone) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.TimeZone))) + i-- + dAtA[i] = 0x42 + } if m.FailedJobsHistoryLimit != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.FailedJobsHistoryLimit)) i-- @@ -1199,6 +1207,10 @@ func (m *CronJobSpec) Size() (n int) { if m.FailedJobsHistoryLimit != nil { n += 1 + sovGenerated(uint64(*m.FailedJobsHistoryLimit)) } + if m.TimeZone != nil { + l = len(*m.TimeZone) + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -1433,6 +1445,7 @@ func (this *CronJobSpec) String() string { `JobTemplate:` + strings.Replace(strings.Replace(this.JobTemplate.String(), "JobTemplateSpec", "JobTemplateSpec", 1), `&`, ``, 1) + `,`, `SuccessfulJobsHistoryLimit:` + valueToStringGenerated(this.SuccessfulJobsHistoryLimit) + `,`, `FailedJobsHistoryLimit:` + valueToStringGenerated(this.FailedJobsHistoryLimit) + `,`, + `TimeZone:` + valueToStringGenerated(this.TimeZone) + `,`, `}`, }, "") return s @@ -2042,6 +2055,39 @@ func (m *CronJobSpec) Unmarshal(dAtA []byte) error { } } m.FailedJobsHistoryLimit = &v + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeZone", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.TimeZone = &s + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/staging/src/k8s.io/api/batch/v1/generated.proto b/staging/src/k8s.io/api/batch/v1/generated.proto index b08cb3c5a47..4a7285969a8 100644 --- a/staging/src/k8s.io/api/batch/v1/generated.proto +++ b/staging/src/k8s.io/api/batch/v1/generated.proto @@ -63,6 +63,12 @@ message CronJobSpec { // The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. optional string schedule = 1; + // The time zone for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. + // If not specified, this will rely on the time zone of the kube-controller-manager process. + // ALPHA: This field is in alpha and must be enabled via the `CronJobTimeZone` feature gate. + // +optional + optional string timeZone = 8; + // Optional deadline in seconds for starting the job if it misses scheduled // time for any reason. Missed jobs executions will be counted as failed ones. // +optional diff --git a/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go index b9340de1e7a..c2899044f40 100644 --- a/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/batch/v1/types_swagger_doc_generated.go @@ -51,6 +51,7 @@ func (CronJobList) SwaggerDoc() map[string]string { var map_CronJobSpec = map[string]string{ "": "CronJobSpec describes how the job execution will look like and when it will actually run.", "schedule": "The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.", + "timeZone": "The time zone for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. If not specified, this will rely on the time zone of the kube-controller-manager process. ALPHA: This field is in alpha and must be enabled via the `CronJobTimeZone` feature gate.", "startingDeadlineSeconds": "Optional deadline in seconds for starting the job if it misses scheduled time for any reason. Missed jobs executions will be counted as failed ones.", "concurrencyPolicy": "Specifies how to treat concurrent executions of a Job. Valid values are: - \"Allow\" (default): allows CronJobs to run concurrently; - \"Forbid\": forbids concurrent runs, skipping next run if previous run hasn't finished yet; - \"Replace\": cancels currently running job and replaces it with a new one", "suspend": "This flag tells the controller to suspend subsequent executions, it does not apply to already started executions. Defaults to false.", diff --git a/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go index a9806a50246..4bc195a9f52 100644 --- a/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/batch/v1/zz_generated.deepcopy.go @@ -92,6 +92,11 @@ func (in *CronJobList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CronJobSpec) DeepCopyInto(out *CronJobSpec) { *out = *in + if in.TimeZone != nil { + in, out := &in.TimeZone, &out.TimeZone + *out = new(string) + **out = **in + } if in.StartingDeadlineSeconds != nil { in, out := &in.StartingDeadlineSeconds, &out.StartingDeadlineSeconds *out = new(int64) diff --git a/staging/src/k8s.io/api/batch/v1beta1/generated.pb.go b/staging/src/k8s.io/api/batch/v1beta1/generated.pb.go index 8e288d5ace0..d042fc69512 100644 --- a/staging/src/k8s.io/api/batch/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/api/batch/v1beta1/generated.pb.go @@ -227,57 +227,58 @@ func init() { } var fileDescriptor_e57b277b05179ae7 = []byte{ - // 796 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x96, 0x41, 0x6f, 0xdc, 0x44, - 0x14, 0xc7, 0xd7, 0x9b, 0x6c, 0xb2, 0x9d, 0xa5, 0x90, 0x0e, 0x28, 0xb5, 0x16, 0x64, 0x87, 0xad, - 0x2a, 0x02, 0x82, 0x31, 0x89, 0x10, 0xe2, 0x54, 0xa9, 0x2e, 0x2a, 0x10, 0x82, 0x8a, 0x66, 0x8b, - 0x90, 0x50, 0x85, 0x3a, 0x1e, 0xbf, 0x6c, 0xa6, 0xb1, 0x3d, 0x96, 0x67, 0x1c, 0x29, 0x37, 0x2e, - 0xdc, 0xf9, 0x22, 0x9c, 0xb8, 0x73, 0xce, 0xb1, 0xc7, 0x9e, 0x2c, 0x62, 0xbe, 0x05, 0x27, 0xe4, - 0x59, 0xc7, 0xbb, 0xdd, 0xf5, 0x36, 0xed, 0x25, 0xb7, 0xcc, 0x9b, 0xff, 0xff, 0x37, 0xcf, 0xef, - 0xbd, 0x99, 0x2c, 0xba, 0x7f, 0xf2, 0x95, 0x22, 0x42, 0x7a, 0x27, 0x79, 0x00, 0x59, 0x02, 0x1a, - 0x94, 0x77, 0x0a, 0x49, 0x28, 0x33, 0xaf, 0xde, 0x60, 0xa9, 0xf0, 0x02, 0xa6, 0xf9, 0xb1, 0x77, - 0xba, 0x17, 0x80, 0x66, 0x7b, 0xde, 0x04, 0x12, 0xc8, 0x98, 0x86, 0x90, 0xa4, 0x99, 0xd4, 0x12, - 0xdb, 0x53, 0x25, 0x61, 0xa9, 0x20, 0x46, 0x49, 0x6a, 0xe5, 0xf0, 0xb3, 0x89, 0xd0, 0xc7, 0x79, - 0x40, 0xb8, 0x8c, 0xbd, 0x89, 0x9c, 0x48, 0xcf, 0x18, 0x82, 0xfc, 0xc8, 0xac, 0xcc, 0xc2, 0xfc, - 0x35, 0x05, 0x0d, 0xef, 0xb4, 0x1c, 0xb9, 0x78, 0xda, 0x70, 0x34, 0x27, 0xe2, 0x32, 0x83, 0x36, - 0xcd, 0x17, 0x33, 0x4d, 0xcc, 0xf8, 0xb1, 0x48, 0x20, 0x3b, 0xf3, 0xd2, 0x93, 0x49, 0x15, 0x50, - 0x5e, 0x0c, 0x9a, 0xb5, 0xb9, 0xbc, 0x55, 0xae, 0x2c, 0x4f, 0xb4, 0x88, 0x61, 0xc9, 0xf0, 0xe5, - 0x55, 0x06, 0xc5, 0x8f, 0x21, 0x66, 0x8b, 0xbe, 0xd1, 0xef, 0x5d, 0xb4, 0xf9, 0x20, 0x93, 0xc9, - 0x81, 0x0c, 0xf0, 0x53, 0xd4, 0xaf, 0xf2, 0x09, 0x99, 0x66, 0xb6, 0xb5, 0x63, 0xed, 0x0e, 0xf6, - 0x3f, 0x27, 0xb3, 0x7a, 0x36, 0x58, 0x92, 0x9e, 0x4c, 0xaa, 0x80, 0x22, 0x95, 0x9a, 0x9c, 0xee, - 0x91, 0x47, 0xc1, 0x33, 0xe0, 0xfa, 0x07, 0xd0, 0xcc, 0xc7, 0xe7, 0x85, 0xdb, 0x29, 0x0b, 0x17, - 0xcd, 0x62, 0xb4, 0xa1, 0xe2, 0x6f, 0xd0, 0xba, 0x4a, 0x81, 0xdb, 0x5d, 0x43, 0xbf, 0x4b, 0x56, - 0x75, 0x8b, 0xd4, 0x29, 0x8d, 0x53, 0xe0, 0xfe, 0x5b, 0x35, 0x72, 0xbd, 0x5a, 0x51, 0x03, 0xc0, - 0x8f, 0xd0, 0x86, 0xd2, 0x4c, 0xe7, 0xca, 0x5e, 0x33, 0xa8, 0x8f, 0xae, 0x46, 0x19, 0xb9, 0xff, - 0x76, 0x0d, 0xdb, 0x98, 0xae, 0x69, 0x8d, 0x19, 0xfd, 0x65, 0xa1, 0x41, 0xad, 0x3c, 0x14, 0x4a, - 0xe3, 0x27, 0x4b, 0xb5, 0x20, 0xaf, 0x57, 0x8b, 0xca, 0x6d, 0x2a, 0xb1, 0x55, 0x9f, 0xd4, 0xbf, - 0x8c, 0xcc, 0xd5, 0xe1, 0x21, 0xea, 0x09, 0x0d, 0xb1, 0xb2, 0xbb, 0x3b, 0x6b, 0xbb, 0x83, 0xfd, - 0x0f, 0xaf, 0xcc, 0xde, 0xbf, 0x59, 0xd3, 0x7a, 0xdf, 0x55, 0x3e, 0x3a, 0xb5, 0x8f, 0xfe, 0x5c, - 0x6f, 0xb2, 0xae, 0x8a, 0x83, 0x3f, 0x45, 0xfd, 0xaa, 0xcf, 0x61, 0x1e, 0x81, 0xc9, 0xfa, 0xc6, - 0x2c, 0x8b, 0x71, 0x1d, 0xa7, 0x8d, 0x02, 0xff, 0x84, 0x6e, 0x2b, 0xcd, 0x32, 0x2d, 0x92, 0xc9, - 0xd7, 0xc0, 0xc2, 0x48, 0x24, 0x30, 0x06, 0x2e, 0x93, 0x50, 0x99, 0x06, 0xad, 0xf9, 0xef, 0x97, - 0x85, 0x7b, 0x7b, 0xdc, 0x2e, 0xa1, 0xab, 0xbc, 0xf8, 0x09, 0xba, 0xc5, 0x65, 0xc2, 0xf3, 0x2c, - 0x83, 0x84, 0x9f, 0xfd, 0x28, 0x23, 0xc1, 0xcf, 0x4c, 0x9b, 0x6e, 0xf8, 0xa4, 0xce, 0xe6, 0xd6, - 0x83, 0x45, 0xc1, 0x7f, 0x6d, 0x41, 0xba, 0x0c, 0xc2, 0x77, 0xd1, 0xa6, 0xca, 0x55, 0x0a, 0x49, - 0x68, 0xaf, 0xef, 0x58, 0xbb, 0x7d, 0x7f, 0x50, 0x16, 0xee, 0xe6, 0x78, 0x1a, 0xa2, 0x97, 0x7b, - 0xf8, 0x29, 0x1a, 0x3c, 0x93, 0xc1, 0x63, 0x88, 0xd3, 0x88, 0x69, 0xb0, 0x7b, 0xa6, 0x85, 0x1f, - 0xaf, 0xae, 0xf3, 0xc1, 0x4c, 0x6c, 0x86, 0xee, 0xdd, 0x3a, 0xd3, 0xc1, 0xdc, 0x06, 0x9d, 0x47, - 0xe2, 0x5f, 0xd1, 0x50, 0xe5, 0x9c, 0x83, 0x52, 0x47, 0x79, 0x74, 0x20, 0x03, 0xf5, 0xad, 0x50, - 0x5a, 0x66, 0x67, 0x87, 0x22, 0x16, 0xda, 0xde, 0xd8, 0xb1, 0x76, 0x7b, 0xbe, 0x53, 0x16, 0xee, - 0x70, 0xbc, 0x52, 0x45, 0x5f, 0x41, 0xc0, 0x14, 0x6d, 0x1f, 0x31, 0x11, 0x41, 0xb8, 0xc4, 0xde, - 0x34, 0xec, 0x61, 0x59, 0xb8, 0xdb, 0x0f, 0x5b, 0x15, 0x74, 0x85, 0x73, 0xf4, 0x77, 0x17, 0xdd, - 0x7c, 0xe9, 0x3e, 0xe0, 0xef, 0xd1, 0x06, 0xe3, 0x5a, 0x9c, 0x56, 0xf3, 0x52, 0x8d, 0xe2, 0x9d, - 0xf9, 0x12, 0x55, 0x6f, 0xda, 0xec, 0x7e, 0x53, 0x38, 0x82, 0xaa, 0x13, 0x30, 0xbb, 0x44, 0xf7, - 0x8d, 0x95, 0xd6, 0x08, 0x1c, 0xa1, 0xad, 0x88, 0x29, 0x7d, 0x39, 0x6a, 0x8f, 0x45, 0x0c, 0xa6, - 0x49, 0x83, 0xfd, 0x4f, 0x5e, 0xef, 0xf2, 0x54, 0x0e, 0xff, 0xbd, 0xb2, 0x70, 0xb7, 0x0e, 0x17, - 0x38, 0x74, 0x89, 0x8c, 0x33, 0x84, 0x4d, 0xac, 0x29, 0xa1, 0x39, 0xaf, 0xf7, 0xc6, 0xe7, 0x6d, - 0x97, 0x85, 0x8b, 0x0f, 0x97, 0x48, 0xb4, 0x85, 0x3e, 0x3a, 0xb7, 0xd0, 0xfc, 0x44, 0x5c, 0xc3, - 0x93, 0xf9, 0x33, 0xea, 0xeb, 0xcb, 0x29, 0xee, 0xbe, 0xe9, 0x14, 0x37, 0xb7, 0xbf, 0x19, 0xe1, - 0x06, 0x56, 0xbd, 0x78, 0xef, 0x2c, 0xe8, 0xaf, 0xe1, 0x73, 0xee, 0xbd, 0xf4, 0x1f, 0xe0, 0x83, - 0xb6, 0x4f, 0x21, 0xaf, 0x78, 0xf8, 0xfd, 0x7b, 0xe7, 0x17, 0x4e, 0xe7, 0xf9, 0x85, 0xd3, 0x79, - 0x71, 0xe1, 0x74, 0x7e, 0x2b, 0x1d, 0xeb, 0xbc, 0x74, 0xac, 0xe7, 0xa5, 0x63, 0xbd, 0x28, 0x1d, - 0xeb, 0x9f, 0xd2, 0xb1, 0xfe, 0xf8, 0xd7, 0xe9, 0xfc, 0x62, 0xaf, 0xfa, 0xc1, 0xf0, 0x7f, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x4f, 0x1b, 0x4a, 0x8e, 0x64, 0x08, 0x00, 0x00, + // 814 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x96, 0x41, 0x6f, 0x1b, 0x45, + 0x14, 0xc7, 0xbd, 0x4e, 0x9c, 0xb8, 0xe3, 0x16, 0xd2, 0x01, 0xa5, 0x2b, 0x83, 0xd6, 0xc1, 0x55, + 0x85, 0x41, 0x30, 0x4b, 0x22, 0x84, 0x38, 0x55, 0xea, 0x16, 0x15, 0x08, 0x41, 0x45, 0xe3, 0x22, + 0xa4, 0xaa, 0x42, 0x9d, 0x1d, 0xbf, 0x38, 0xd3, 0x78, 0x77, 0x56, 0x3b, 0xb3, 0x91, 0x72, 0xe3, + 0xc2, 0x9d, 0xef, 0xc2, 0x9d, 0x73, 0x8e, 0xbd, 0xd1, 0xd3, 0x8a, 0x2c, 0xdf, 0x82, 0x13, 0x9a, + 0xf1, 0x7a, 0xed, 0xda, 0xeb, 0xa6, 0xbd, 0xf4, 0xe6, 0x79, 0xf3, 0xff, 0xff, 0xe6, 0xed, 0x7b, + 0x6f, 0x67, 0x8d, 0xee, 0x9d, 0x7e, 0xad, 0x88, 0x90, 0xfe, 0x69, 0x16, 0x42, 0x1a, 0x83, 0x06, + 0xe5, 0x9f, 0x41, 0x3c, 0x92, 0xa9, 0x5f, 0x6e, 0xb0, 0x44, 0xf8, 0x21, 0xd3, 0xfc, 0xc4, 0x3f, + 0xdb, 0x0f, 0x41, 0xb3, 0x7d, 0x7f, 0x0c, 0x31, 0xa4, 0x4c, 0xc3, 0x88, 0x24, 0xa9, 0xd4, 0x12, + 0xbb, 0x53, 0x25, 0x61, 0x89, 0x20, 0x56, 0x49, 0x4a, 0x65, 0xf7, 0xf3, 0xb1, 0xd0, 0x27, 0x59, + 0x48, 0xb8, 0x8c, 0xfc, 0xb1, 0x1c, 0x4b, 0xdf, 0x1a, 0xc2, 0xec, 0xd8, 0xae, 0xec, 0xc2, 0xfe, + 0x9a, 0x82, 0xba, 0xb7, 0x6b, 0x8e, 0x5c, 0x3e, 0xad, 0xdb, 0x5f, 0x10, 0x71, 0x99, 0x42, 0x9d, + 0xe6, 0xcb, 0xb9, 0x26, 0x62, 0xfc, 0x44, 0xc4, 0x90, 0x9e, 0xfb, 0xc9, 0xe9, 0xd8, 0x04, 0x94, + 0x1f, 0x81, 0x66, 0x75, 0x2e, 0x7f, 0x9d, 0x2b, 0xcd, 0x62, 0x2d, 0x22, 0x58, 0x31, 0x7c, 0x75, + 0x95, 0x41, 0xf1, 0x13, 0x88, 0xd8, 0xb2, 0xaf, 0xff, 0x7b, 0x13, 0x6d, 0xdf, 0x4f, 0x65, 0x7c, + 0x28, 0x43, 0xfc, 0x14, 0xb5, 0x4d, 0x3e, 0x23, 0xa6, 0x99, 0xeb, 0xec, 0x39, 0x83, 0xce, 0xc1, + 0x17, 0x64, 0x5e, 0xcf, 0x0a, 0x4b, 0x92, 0xd3, 0xb1, 0x09, 0x28, 0x62, 0xd4, 0xe4, 0x6c, 0x9f, + 0x3c, 0x0c, 0x9f, 0x01, 0xd7, 0x3f, 0x82, 0x66, 0x01, 0xbe, 0xc8, 0x7b, 0x8d, 0x22, 0xef, 0xa1, + 0x79, 0x8c, 0x56, 0x54, 0xfc, 0x2d, 0xda, 0x54, 0x09, 0x70, 0xb7, 0x69, 0xe9, 0x77, 0xc8, 0xba, + 0x6e, 0x91, 0x32, 0xa5, 0x61, 0x02, 0x3c, 0xb8, 0x5e, 0x22, 0x37, 0xcd, 0x8a, 0x5a, 0x00, 0x7e, + 0x88, 0xb6, 0x94, 0x66, 0x3a, 0x53, 0xee, 0x86, 0x45, 0x7d, 0x7c, 0x35, 0xca, 0xca, 0x83, 0x77, + 0x4a, 0xd8, 0xd6, 0x74, 0x4d, 0x4b, 0x4c, 0xff, 0x4f, 0x07, 0x75, 0x4a, 0xe5, 0x91, 0x50, 0x1a, + 0x3f, 0x59, 0xa9, 0x05, 0x79, 0xbd, 0x5a, 0x18, 0xb7, 0xad, 0xc4, 0x4e, 0x79, 0x52, 0x7b, 0x16, + 0x59, 0xa8, 0xc3, 0x03, 0xd4, 0x12, 0x1a, 0x22, 0xe5, 0x36, 0xf7, 0x36, 0x06, 0x9d, 0x83, 0x8f, + 0xae, 0xcc, 0x3e, 0xb8, 0x51, 0xd2, 0x5a, 0xdf, 0x1b, 0x1f, 0x9d, 0xda, 0xfb, 0x7f, 0x6f, 0x56, + 0x59, 0x9b, 0xe2, 0xe0, 0xcf, 0x50, 0xdb, 0xf4, 0x79, 0x94, 0x4d, 0xc0, 0x66, 0x7d, 0x6d, 0x9e, + 0xc5, 0xb0, 0x8c, 0xd3, 0x4a, 0x81, 0x07, 0xa8, 0x6d, 0x46, 0xe3, 0xb1, 0x8c, 0xc1, 0x6d, 0x5b, + 0xf5, 0x75, 0xa3, 0x7c, 0x54, 0xc6, 0x68, 0xb5, 0x8b, 0x7f, 0x46, 0xb7, 0x94, 0x66, 0xa9, 0x16, + 0xf1, 0xf8, 0x1b, 0x60, 0xa3, 0x89, 0x88, 0x61, 0x08, 0x5c, 0xc6, 0x23, 0x65, 0x5b, 0xb9, 0x11, + 0x7c, 0x50, 0xe4, 0xbd, 0x5b, 0xc3, 0x7a, 0x09, 0x5d, 0xe7, 0xc5, 0x4f, 0xd0, 0x4d, 0x2e, 0x63, + 0x9e, 0xa5, 0x29, 0xc4, 0xfc, 0xfc, 0x27, 0x39, 0x11, 0xfc, 0xdc, 0x36, 0xf4, 0x5a, 0x40, 0xca, + 0xbc, 0x6f, 0xde, 0x5f, 0x16, 0xfc, 0x57, 0x17, 0xa4, 0xab, 0x20, 0x7c, 0x07, 0x6d, 0xab, 0x4c, + 0x25, 0x10, 0x8f, 0xdc, 0xcd, 0x3d, 0x67, 0xd0, 0x0e, 0x3a, 0x45, 0xde, 0xdb, 0x1e, 0x4e, 0x43, + 0x74, 0xb6, 0x87, 0x9f, 0xa2, 0xce, 0x33, 0x19, 0x3e, 0x82, 0x28, 0x99, 0x30, 0x0d, 0x6e, 0xcb, + 0x36, 0xfb, 0x93, 0xf5, 0x1d, 0x39, 0x9c, 0x8b, 0xed, 0x78, 0xbe, 0x57, 0x66, 0xda, 0x59, 0xd8, + 0xa0, 0x8b, 0x48, 0xfc, 0x2b, 0xea, 0xaa, 0x8c, 0x73, 0x50, 0xea, 0x38, 0x9b, 0x1c, 0xca, 0x50, + 0x7d, 0x27, 0x94, 0x96, 0xe9, 0xf9, 0x91, 0x88, 0x84, 0x76, 0xb7, 0xf6, 0x9c, 0x41, 0x2b, 0xf0, + 0x8a, 0xbc, 0xd7, 0x1d, 0xae, 0x55, 0xd1, 0x57, 0x10, 0x30, 0x45, 0xbb, 0xc7, 0x4c, 0x4c, 0x60, + 0xb4, 0xc2, 0xde, 0xb6, 0xec, 0x6e, 0x91, 0xf7, 0x76, 0x1f, 0xd4, 0x2a, 0xe8, 0x1a, 0x67, 0xff, + 0xaf, 0x26, 0xba, 0xf1, 0xd2, 0x9b, 0x83, 0x7f, 0x40, 0x5b, 0x8c, 0x6b, 0x71, 0x66, 0x26, 0xcb, + 0x0c, 0xed, 0xed, 0xc5, 0x12, 0x99, 0xdb, 0x6f, 0x7e, 0x13, 0x50, 0x38, 0x06, 0xd3, 0x09, 0x98, + 0xbf, 0x6e, 0xf7, 0xac, 0x95, 0x96, 0x08, 0x3c, 0x41, 0x3b, 0x13, 0xa6, 0xf4, 0x6c, 0x28, 0xcd, + 0xc8, 0xd9, 0x26, 0x75, 0x0e, 0x3e, 0x7d, 0xbd, 0xd7, 0xcc, 0x38, 0x82, 0xf7, 0x8b, 0xbc, 0xb7, + 0x73, 0xb4, 0xc4, 0xa1, 0x2b, 0x64, 0x9c, 0x22, 0x6c, 0x63, 0x55, 0x09, 0xed, 0x79, 0xad, 0x37, + 0x3e, 0x6f, 0xb7, 0xc8, 0x7b, 0xf8, 0x68, 0x85, 0x44, 0x6b, 0xe8, 0xfd, 0x0b, 0x07, 0x2d, 0x4e, + 0xc4, 0x5b, 0xb8, 0x5c, 0x7f, 0x41, 0x6d, 0x3d, 0x9b, 0xe2, 0xe6, 0x9b, 0x4e, 0x71, 0x75, 0x4f, + 0x54, 0x23, 0x5c, 0xc1, 0xcc, 0xdd, 0xf8, 0xee, 0x92, 0xfe, 0x2d, 0x3c, 0xce, 0xdd, 0x97, 0xbe, + 0x15, 0x1f, 0xd6, 0x3d, 0x0a, 0x79, 0xc5, 0x27, 0x22, 0xb8, 0x7b, 0x71, 0xe9, 0x35, 0x9e, 0x5f, + 0x7a, 0x8d, 0x17, 0x97, 0x5e, 0xe3, 0xb7, 0xc2, 0x73, 0x2e, 0x0a, 0xcf, 0x79, 0x5e, 0x78, 0xce, + 0x8b, 0xc2, 0x73, 0xfe, 0x29, 0x3c, 0xe7, 0x8f, 0x7f, 0xbd, 0xc6, 0x63, 0x77, 0xdd, 0x5f, 0x8b, + 0xff, 0x03, 0x00, 0x00, 0xff, 0xff, 0xd7, 0xf2, 0x8b, 0xe9, 0x8e, 0x08, 0x00, 0x00, } func (m *CronJob) Marshal() (dAtA []byte, err error) { @@ -400,6 +401,13 @@ func (m *CronJobSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.TimeZone != nil { + i -= len(*m.TimeZone) + copy(dAtA[i:], *m.TimeZone) + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.TimeZone))) + i-- + dAtA[i] = 0x42 + } if m.FailedJobsHistoryLimit != nil { i = encodeVarintGenerated(dAtA, i, uint64(*m.FailedJobsHistoryLimit)) i-- @@ -662,6 +670,10 @@ func (m *CronJobSpec) Size() (n int) { if m.FailedJobsHistoryLimit != nil { n += 1 + sovGenerated(uint64(*m.FailedJobsHistoryLimit)) } + if m.TimeZone != nil { + l = len(*m.TimeZone) + n += 1 + l + sovGenerated(uint64(l)) + } return n } @@ -760,6 +772,7 @@ func (this *CronJobSpec) String() string { `JobTemplate:` + strings.Replace(strings.Replace(this.JobTemplate.String(), "JobTemplateSpec", "JobTemplateSpec", 1), `&`, ``, 1) + `,`, `SuccessfulJobsHistoryLimit:` + valueToStringGenerated(this.SuccessfulJobsHistoryLimit) + `,`, `FailedJobsHistoryLimit:` + valueToStringGenerated(this.FailedJobsHistoryLimit) + `,`, + `TimeZone:` + valueToStringGenerated(this.TimeZone) + `,`, `}`, }, "") return s @@ -1284,6 +1297,39 @@ func (m *CronJobSpec) Unmarshal(dAtA []byte) error { } } m.FailedJobsHistoryLimit = &v + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeZone", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenerated + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + s := string(dAtA[iNdEx:postIndex]) + m.TimeZone = &s + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) diff --git a/staging/src/k8s.io/api/batch/v1beta1/generated.proto b/staging/src/k8s.io/api/batch/v1beta1/generated.proto index ce6cf099882..c73b7a65403 100644 --- a/staging/src/k8s.io/api/batch/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/batch/v1beta1/generated.proto @@ -64,6 +64,12 @@ message CronJobSpec { // The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron. optional string schedule = 1; + // The time zone for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. + // If not specified, this will rely on the time zone of the kube-controller-manager process. + // ALPHA: This field is in alpha and must be enabled via the `CronJobTimeZone` feature gate. + // +optional + optional string timeZone = 8; + // Optional deadline in seconds for starting the job if it misses scheduled // time for any reason. Missed jobs executions will be counted as failed ones. // +optional diff --git a/staging/src/k8s.io/api/batch/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/batch/v1beta1/types_swagger_doc_generated.go index 9973898122a..87194807631 100644 --- a/staging/src/k8s.io/api/batch/v1beta1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/batch/v1beta1/types_swagger_doc_generated.go @@ -51,6 +51,7 @@ func (CronJobList) SwaggerDoc() map[string]string { var map_CronJobSpec = map[string]string{ "": "CronJobSpec describes how the job execution will look like and when it will actually run.", "schedule": "The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.", + "timeZone": "The time zone for the given schedule, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. If not specified, this will rely on the time zone of the kube-controller-manager process. ALPHA: This field is in alpha and must be enabled via the `CronJobTimeZone` feature gate.", "startingDeadlineSeconds": "Optional deadline in seconds for starting the job if it misses scheduled time for any reason. Missed jobs executions will be counted as failed ones.", "concurrencyPolicy": "Specifies how to treat concurrent executions of a Job. Valid values are: - \"Allow\" (default): allows CronJobs to run concurrently; - \"Forbid\": forbids concurrent runs, skipping next run if previous run hasn't finished yet; - \"Replace\": cancels currently running job and replaces it with a new one", "suspend": "This flag tells the controller to suspend subsequent executions, it does not apply to already started executions. Defaults to false.", diff --git a/staging/src/k8s.io/api/batch/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/batch/v1beta1/zz_generated.deepcopy.go index 8c256848dc2..c3a3494c4a0 100644 --- a/staging/src/k8s.io/api/batch/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/batch/v1beta1/zz_generated.deepcopy.go @@ -90,6 +90,11 @@ func (in *CronJobList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CronJobSpec) DeepCopyInto(out *CronJobSpec) { *out = *in + if in.TimeZone != nil { + in, out := &in.TimeZone, &out.TimeZone + *out = new(string) + **out = **in + } if in.StartingDeadlineSeconds != nil { in, out := &in.StartingDeadlineSeconds, &out.StartingDeadlineSeconds *out = new(int64) diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.json b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.json index 22d420b04a0..2699c5ce01f 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.json +++ b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.json @@ -46,6 +46,7 @@ }, "spec": { "schedule": "scheduleValue", + "timeZone": "timeZoneValue", "startingDeadlineSeconds": 2, "concurrencyPolicy": "concurrencyPolicyValue", "suspend": true, diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.pb b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.pb index 6e66596e9932a2ad45ba84177a646d79b8067d92..e2416c93ef26c3d06ed661b163e399b85f42326f 100644 GIT binary patch delta 39 vcmX>Va5G?n6ywf~(&dbdhc-`UG*RPs;w{O{O^wRWOASlRDNUVxT)i0pE0YfT delta 23 fcmcZ^a4KMe6yvIm(&dbdn>SBpG*O#;O}!ZabM*;D diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.yaml b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.yaml index 40a0a0d8f01..9d7205ac8d6 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/batch.v1.CronJob.yaml @@ -1170,6 +1170,7 @@ spec: startingDeadlineSeconds: 2 successfulJobsHistoryLimit: 6 suspend: true + timeZone: timeZoneValue status: active: - apiVersion: apiVersionValue diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.json b/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.json index 636c0be56e0..f58c224036c 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.json +++ b/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.json @@ -46,6 +46,7 @@ }, "spec": { "schedule": "scheduleValue", + "timeZone": "timeZoneValue", "startingDeadlineSeconds": 2, "concurrencyPolicy": "concurrencyPolicyValue", "suspend": true, diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.pb b/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.pb index 75e60016dcbf47c44ea42d05e89420d627e160bf..084f9a8b18be9cac91756c95d16366a2ddcbb05c 100644 GIT binary patch delta 39 vcmX>fa4%qjJmb!d3gwK9hc?e(G*RPs;w{O{O^wRWOASlRDNUVxM!gvTF4hki delta 23 fcmcZ?a6VvyJmad33gwK9n>Wv3G*O#;TfG?ob*Kqr diff --git a/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.yaml b/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.yaml index 281a8f35b44..06231c31bbb 100644 --- a/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.yaml +++ b/staging/src/k8s.io/api/testdata/HEAD/batch.v1beta1.CronJob.yaml @@ -1170,6 +1170,7 @@ spec: startingDeadlineSeconds: 2 successfulJobsHistoryLimit: 6 suspend: true + timeZone: timeZoneValue status: active: - apiVersion: apiVersionValue diff --git a/staging/src/k8s.io/client-go/applyconfigurations/batch/v1/cronjobspec.go b/staging/src/k8s.io/client-go/applyconfigurations/batch/v1/cronjobspec.go index eaf3ba8e65e..22a34dcb618 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/batch/v1/cronjobspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/batch/v1/cronjobspec.go @@ -26,6 +26,7 @@ import ( // with apply. type CronJobSpecApplyConfiguration struct { Schedule *string `json:"schedule,omitempty"` + TimeZone *string `json:"timeZone,omitempty"` StartingDeadlineSeconds *int64 `json:"startingDeadlineSeconds,omitempty"` ConcurrencyPolicy *v1.ConcurrencyPolicy `json:"concurrencyPolicy,omitempty"` Suspend *bool `json:"suspend,omitempty"` @@ -48,6 +49,14 @@ func (b *CronJobSpecApplyConfiguration) WithSchedule(value string) *CronJobSpecA return b } +// WithTimeZone sets the TimeZone field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TimeZone field is set to the value of the last call. +func (b *CronJobSpecApplyConfiguration) WithTimeZone(value string) *CronJobSpecApplyConfiguration { + b.TimeZone = &value + return b +} + // WithStartingDeadlineSeconds sets the StartingDeadlineSeconds field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the StartingDeadlineSeconds field is set to the value of the last call. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/batch/v1beta1/cronjobspec.go b/staging/src/k8s.io/client-go/applyconfigurations/batch/v1beta1/cronjobspec.go index 7ca431b1e6e..68c0777de01 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/batch/v1beta1/cronjobspec.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/batch/v1beta1/cronjobspec.go @@ -26,6 +26,7 @@ import ( // with apply. type CronJobSpecApplyConfiguration struct { Schedule *string `json:"schedule,omitempty"` + TimeZone *string `json:"timeZone,omitempty"` StartingDeadlineSeconds *int64 `json:"startingDeadlineSeconds,omitempty"` ConcurrencyPolicy *v1beta1.ConcurrencyPolicy `json:"concurrencyPolicy,omitempty"` Suspend *bool `json:"suspend,omitempty"` @@ -48,6 +49,14 @@ func (b *CronJobSpecApplyConfiguration) WithSchedule(value string) *CronJobSpecA return b } +// WithTimeZone sets the TimeZone field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TimeZone field is set to the value of the last call. +func (b *CronJobSpecApplyConfiguration) WithTimeZone(value string) *CronJobSpecApplyConfiguration { + b.TimeZone = &value + return b +} + // WithStartingDeadlineSeconds sets the StartingDeadlineSeconds field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the StartingDeadlineSeconds field is set to the value of the last call. diff --git a/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go b/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go index bd7f58d02dc..12e9f6db4ce 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/internal/internal.go @@ -2947,6 +2947,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: suspend type: scalar: boolean + - name: timeZone + type: + scalar: string - name: io.k8s.api.batch.v1.CronJobStatus map: fields: @@ -3148,6 +3151,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: suspend type: scalar: boolean + - name: timeZone + type: + scalar: string - name: io.k8s.api.batch.v1beta1.CronJobStatus map: fields: