Merge pull request #108032 from deejross/kep3140-cronjob-timezone

KEP 3140: TimeZone support for CronJob
This commit is contained in:
Kubernetes Prow Robot 2022-03-29 17:34:20 -07:00 committed by GitHub
commit 0f2300575c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 971 additions and 167 deletions

View File

@ -4079,6 +4079,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": [
@ -4454,6 +4458,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": [

View File

@ -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": [

View File

@ -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": [

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,6 +18,8 @@ package validation
import (
"fmt"
"strings"
"time"
"github.com/robfig/cron/v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -29,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
@ -277,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.
@ -295,24 +298,31 @@ 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 {
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"))...)
}
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)...)
@ -343,11 +353,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
}

View File

@ -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,9 +914,26 @@ 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 {
if errs := ValidateCronJobCreate(&v, corevalidation.PodValidationOptions{}); len(errs) != 0 {
t.Errorf("expected success for %s: %v", k, errs)
}
@ -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",
@ -1185,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 {
@ -1198,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
@ -1216,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
}

View File

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

View File

@ -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 {
// schedule changed, change the requeue time
sched, err := cron.ParseStandard(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, 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
@ -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,23 @@ 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") {
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
}
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
}

View File

@ -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,
@ -815,11 +900,15 @@ 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
cj.Spec.Schedule = tc.schedule
cj.Spec.TimeZone = tc.timeZone
if tc.deadline != noDead {
cj.Spec.StartingDeadlineSeconds = &tc.deadline
}
@ -1058,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 {

View File

@ -12682,6 +12682,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.",
@ -13312,6 +13319,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.",

View File

@ -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.

View File

@ -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:])

View File

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

View File

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

View File

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

View File

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

View File

@ -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:])

View File

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

View File

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

View File

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

View File

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

View File

@ -46,6 +46,7 @@
},
"spec": {
"schedule": "scheduleValue",
"timeZone": "timeZoneValue",
"startingDeadlineSeconds": 2,
"concurrencyPolicy": "concurrencyPolicyValue",
"suspend": true,

View File

@ -1170,6 +1170,7 @@ spec:
startingDeadlineSeconds: 2
successfulJobsHistoryLimit: 6
suspend: true
timeZone: timeZoneValue
status:
active:
- apiVersion: apiVersionValue

View File

@ -46,6 +46,7 @@
},
"spec": {
"schedule": "scheduleValue",
"timeZone": "timeZoneValue",
"startingDeadlineSeconds": 2,
"concurrencyPolicy": "concurrencyPolicyValue",
"suspend": true,

View File

@ -1170,6 +1170,7 @@ spec:
startingDeadlineSeconds: 2
successfulJobsHistoryLimit: 6
suspend: true
timeZone: timeZoneValue
status:
active:
- apiVersion: apiVersionValue

View File

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

View File

@ -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.

View File

@ -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.

View File

@ -2956,6 +2956,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:
@ -3157,6 +3160,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: