mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-11-03 23:40:03 +00:00 
			
		
		
		
	TimeZone support for CronJobs
This commit is contained in:
		@@ -22,6 +22,7 @@ package main
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
						_ "time/tzdata" // for CronJob Time Zone support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"k8s.io/component-base/cli"
 | 
						"k8s.io/component-base/cli"
 | 
				
			||||||
	_ "k8s.io/component-base/logs/json/register"          // for JSON log format registration
 | 
						_ "k8s.io/component-base/logs/json/register"          // for JSON log format registration
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -376,6 +376,12 @@ type CronJobSpec struct {
 | 
				
			|||||||
	// The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.
 | 
						// The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.
 | 
				
			||||||
	Schedule string
 | 
						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
 | 
						// 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.
 | 
						// time for any reason.  Missed jobs executions will be counted as failed ones.
 | 
				
			||||||
	// +optional
 | 
						// +optional
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,8 @@ package validation
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/robfig/cron/v3"
 | 
						"github.com/robfig/cron/v3"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						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 {
 | 
						if len(spec.Schedule) == 0 {
 | 
				
			||||||
		allErrs = append(allErrs, field.Required(fldPath.Child("schedule"), ""))
 | 
							allErrs = append(allErrs, field.Required(fldPath.Child("schedule"), ""))
 | 
				
			||||||
	} else {
 | 
						} 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 {
 | 
						if spec.StartingDeadlineSeconds != nil {
 | 
				
			||||||
		allErrs = append(allErrs, apivalidation.ValidateNonnegativeField(int64(*spec.StartingDeadlineSeconds), fldPath.Child("startingDeadlineSeconds"))...)
 | 
							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, validateConcurrencyPolicy(&spec.ConcurrencyPolicy, fldPath.Child("concurrencyPolicy"))...)
 | 
				
			||||||
	allErrs = append(allErrs, ValidateJobTemplateSpec(&spec.JobTemplate, fldPath.Child("jobTemplate"), opts)...)
 | 
						allErrs = append(allErrs, ValidateJobTemplateSpec(&spec.JobTemplate, fldPath.Child("jobTemplate"), opts)...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -343,11 +348,36 @@ func validateConcurrencyPolicy(concurrencyPolicy *batch.ConcurrencyPolicy, fldPa
 | 
				
			|||||||
	return allErrs
 | 
						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{}
 | 
						allErrs := field.ErrorList{}
 | 
				
			||||||
	if _, err := cron.ParseStandard(schedule); err != nil {
 | 
						if _, err := cron.ParseStandard(schedule); err != nil {
 | 
				
			||||||
		allErrs = append(allErrs, field.Invalid(fldPath, schedule, err.Error()))
 | 
							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
 | 
						return allErrs
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,6 +31,18 @@ import (
 | 
				
			|||||||
	"k8s.io/utils/pointer"
 | 
						"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")
 | 
					var ignoreErrValueDetail = cmpopts.IgnoreFields(field.Error{}, "BadValue", "Detail")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getValidManualSelector() *metav1.LabelSelector {
 | 
					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 {
 | 
						for k, v := range successCases {
 | 
				
			||||||
		if errs := ValidateCronJob(&v, corevalidation.PodValidationOptions{}); len(errs) != 0 {
 | 
							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": {
 | 
							"spec.startingDeadlineSeconds:must be greater than or equal to 0": {
 | 
				
			||||||
			ObjectMeta: metav1.ObjectMeta{
 | 
								ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
				Name:      "mycronjob",
 | 
									Name:      "mycronjob",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,6 +35,8 @@ import (
 | 
				
			|||||||
	"k8s.io/apimachinery/pkg/types"
 | 
						"k8s.io/apimachinery/pkg/types"
 | 
				
			||||||
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
						utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/util/wait"
 | 
						"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"
 | 
						batchv1informers "k8s.io/client-go/informers/batch/v1"
 | 
				
			||||||
	clientset "k8s.io/client-go/kubernetes"
 | 
						clientset "k8s.io/client-go/kubernetes"
 | 
				
			||||||
	"k8s.io/client-go/kubernetes/scheme"
 | 
						"k8s.io/client-go/kubernetes/scheme"
 | 
				
			||||||
@@ -48,6 +50,7 @@ import (
 | 
				
			|||||||
	"k8s.io/klog/v2"
 | 
						"k8s.io/klog/v2"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/controller"
 | 
						"k8s.io/kubernetes/pkg/controller"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/controller/cronjob/metrics"
 | 
						"k8s.io/kubernetes/pkg/controller/cronjob/metrics"
 | 
				
			||||||
 | 
						"k8s.io/utils/pointer"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					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
 | 
					// updateCronJob re-queues the CronJob for next scheduled time if there is a
 | 
				
			||||||
// change in spec.schedule otherwise it re-queues it now
 | 
					// change in spec.schedule otherwise it re-queues it now
 | 
				
			||||||
func (jm *ControllerV2) updateCronJob(old interface{}, curr interface{}) {
 | 
					func (jm *ControllerV2) updateCronJob(old interface{}, curr interface{}) {
 | 
				
			||||||
 | 
						timeZoneEnabled := utilfeature.DefaultFeatureGate.Enabled(features.CronJobTimeZone)
 | 
				
			||||||
	oldCJ, okOld := old.(*batchv1.CronJob)
 | 
						oldCJ, okOld := old.(*batchv1.CronJob)
 | 
				
			||||||
	newCJ, okNew := curr.(*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,
 | 
						// 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,
 | 
						// 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.
 | 
						// 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
 | 
							// 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 {
 | 
							if err != nil {
 | 
				
			||||||
			// this is likely a user error in defining the spec value
 | 
								// 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
 | 
								// 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()
 | 
						cronJob = cronJob.DeepCopy()
 | 
				
			||||||
	now := jm.now()
 | 
						now := jm.now()
 | 
				
			||||||
	updateStatus := false
 | 
						updateStatus := false
 | 
				
			||||||
 | 
						timeZoneEnabled := utilfeature.DefaultFeatureGate.Enabled(features.CronJobTimeZone)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	childrenJobs := make(map[types.UID]bool)
 | 
						childrenJobs := make(map[types.UID]bool)
 | 
				
			||||||
	for _, j := range jobs {
 | 
						for _, j := range jobs {
 | 
				
			||||||
@@ -487,12 +492,21 @@ func (jm *ControllerV2) syncCronJob(
 | 
				
			|||||||
		return cronJob, nil, updateStatus, nil
 | 
							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 {
 | 
						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()))
 | 
							klog.V(4).InfoS("Not starting job because the cron is suspended", "cronjob", klog.KRef(cronJob.GetNamespace(), cronJob.GetName()))
 | 
				
			||||||
		return cronJob, nil, updateStatus, nil
 | 
							return cronJob, nil, updateStatus, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sched, err := cron.ParseStandard(cronJob.Spec.Schedule)
 | 
						sched, err := cron.ParseStandard(formatSchedule(timeZoneEnabled, cronJob, jm.recorder))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		// this is likely a user error in defining the spec value
 | 
							// 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
 | 
							// 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
 | 
							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)
 | 
						scheduledTime, err := getNextScheduleTime(*cronJob, now, sched, jm.recorder)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		// this is likely a user error in defining the spec value
 | 
							// 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) {
 | 
					func getRef(object runtime.Object) (*corev1.ObjectReference, error) {
 | 
				
			||||||
	return ref.GetReference(scheme.Scheme, object)
 | 
						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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,10 +32,13 @@ import (
 | 
				
			|||||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
						"k8s.io/apimachinery/pkg/runtime"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/runtime/schema"
 | 
						"k8s.io/apimachinery/pkg/runtime/schema"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/types"
 | 
						"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/informers"
 | 
				
			||||||
	"k8s.io/client-go/kubernetes/fake"
 | 
						"k8s.io/client-go/kubernetes/fake"
 | 
				
			||||||
	"k8s.io/client-go/tools/record"
 | 
						"k8s.io/client-go/tools/record"
 | 
				
			||||||
	"k8s.io/client-go/util/workqueue"
 | 
						"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/batch/install"
 | 
				
			||||||
	_ "k8s.io/kubernetes/pkg/apis/core/install"
 | 
						_ "k8s.io/kubernetes/pkg/apis/core/install"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/controller"
 | 
						"k8s.io/kubernetes/pkg/controller"
 | 
				
			||||||
@@ -50,6 +53,9 @@ var (
 | 
				
			|||||||
	errorSchedule = "obvious error schedule"
 | 
						errorSchedule = "obvious error schedule"
 | 
				
			||||||
	// schedule is hourly on the hour
 | 
						// schedule is hourly on the hour
 | 
				
			||||||
	onTheHour = "0 * * * ?"
 | 
						onTheHour = "0 * * * ?"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errorTimeZone = "bad timezone"
 | 
				
			||||||
 | 
						newYork       = "America/New_York"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// returns a cronJob with some fields filled in.
 | 
					// returns a cronJob with some fields filled in.
 | 
				
			||||||
@@ -127,6 +133,19 @@ func justAfterTheHour() *time.Time {
 | 
				
			|||||||
	return &T1
 | 
						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 {
 | 
					func justBeforeTheHour() time.Time {
 | 
				
			||||||
	T1, err := time.Parse(time.RFC3339, "2016-05-19T09:59:00Z")
 | 
						T1, err := time.Parse(time.RFC3339, "2016-05-19T09:59:00Z")
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -162,6 +181,7 @@ func TestControllerV2SyncCronJob(t *testing.T) {
 | 
				
			|||||||
		concurrencyPolicy batchv1.ConcurrencyPolicy
 | 
							concurrencyPolicy batchv1.ConcurrencyPolicy
 | 
				
			||||||
		suspend           bool
 | 
							suspend           bool
 | 
				
			||||||
		schedule          string
 | 
							schedule          string
 | 
				
			||||||
 | 
							timeZone          *string
 | 
				
			||||||
		deadline          int64
 | 
							deadline          int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// cj status
 | 
							// cj status
 | 
				
			||||||
@@ -173,6 +193,7 @@ func TestControllerV2SyncCronJob(t *testing.T) {
 | 
				
			|||||||
		now             time.Time
 | 
							now             time.Time
 | 
				
			||||||
		jobCreateError  error
 | 
							jobCreateError  error
 | 
				
			||||||
		jobGetErr       error
 | 
							jobGetErr       error
 | 
				
			||||||
 | 
							enableTimeZone  bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// expectations
 | 
							// expectations
 | 
				
			||||||
		expectCreate               bool
 | 
							expectCreate               bool
 | 
				
			||||||
@@ -212,6 +233,17 @@ func TestControllerV2SyncCronJob(t *testing.T) {
 | 
				
			|||||||
			expectedWarnings:           1,
 | 
								expectedWarnings:           1,
 | 
				
			||||||
			jobPresentInCJActiveStatus: true,
 | 
								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": {
 | 
							"never ran, not time, A": {
 | 
				
			||||||
			concurrencyPolicy:          "Allow",
 | 
								concurrencyPolicy:          "Allow",
 | 
				
			||||||
			schedule:                   onTheHour,
 | 
								schedule:                   onTheHour,
 | 
				
			||||||
@@ -238,6 +270,17 @@ func TestControllerV2SyncCronJob(t *testing.T) {
 | 
				
			|||||||
			expectRequeueAfter:         true,
 | 
								expectRequeueAfter:         true,
 | 
				
			||||||
			jobPresentInCJActiveStatus: 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": {
 | 
							"never ran, is time, A": {
 | 
				
			||||||
			concurrencyPolicy:          "Allow",
 | 
								concurrencyPolicy:          "Allow",
 | 
				
			||||||
			schedule:                   onTheHour,
 | 
								schedule:                   onTheHour,
 | 
				
			||||||
@@ -274,6 +317,48 @@ func TestControllerV2SyncCronJob(t *testing.T) {
 | 
				
			|||||||
			expectUpdateStatus:         true,
 | 
								expectUpdateStatus:         true,
 | 
				
			||||||
			jobPresentInCJActiveStatus: 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": {
 | 
							"never ran, is time, suspended": {
 | 
				
			||||||
			concurrencyPolicy:          "Allow",
 | 
								concurrencyPolicy:          "Allow",
 | 
				
			||||||
			suspend:                    true,
 | 
								suspend:                    true,
 | 
				
			||||||
@@ -820,10 +905,15 @@ func TestControllerV2SyncCronJob(t *testing.T) {
 | 
				
			|||||||
			cj.Spec.ConcurrencyPolicy = tc.concurrencyPolicy
 | 
								cj.Spec.ConcurrencyPolicy = tc.concurrencyPolicy
 | 
				
			||||||
			cj.Spec.Suspend = &tc.suspend
 | 
								cj.Spec.Suspend = &tc.suspend
 | 
				
			||||||
			cj.Spec.Schedule = tc.schedule
 | 
								cj.Spec.Schedule = tc.schedule
 | 
				
			||||||
 | 
								cj.Spec.TimeZone = tc.timeZone
 | 
				
			||||||
			if tc.deadline != noDead {
 | 
								if tc.deadline != noDead {
 | 
				
			||||||
				cj.Spec.StartingDeadlineSeconds = &tc.deadline
 | 
									cj.Spec.StartingDeadlineSeconds = &tc.deadline
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if tc.enableTimeZone {
 | 
				
			||||||
 | 
									defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.CronJobTimeZone, true)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			var (
 | 
								var (
 | 
				
			||||||
				job *batchv1.Job
 | 
									job *batchv1.Job
 | 
				
			||||||
				err error
 | 
									err error
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,7 @@ limitations under the License.
 | 
				
			|||||||
package v1
 | 
					package v1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"k8s.io/api/core/v1"
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
	"k8s.io/apimachinery/pkg/types"
 | 
						"k8s.io/apimachinery/pkg/types"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -146,7 +146,7 @@ type JobSpec struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Describes the pod that will be created when executing a job.
 | 
						// Describes the pod that will be created when executing a job.
 | 
				
			||||||
	// More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/
 | 
						// 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
 | 
						// ttlSecondsAfterFinished limits the lifetime of a Job that has finished
 | 
				
			||||||
	// execution (either Complete or Failed). If this field is set,
 | 
						// 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 of job condition, Complete or Failed.
 | 
				
			||||||
	Type JobConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=JobConditionType"`
 | 
						Type JobConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=JobConditionType"`
 | 
				
			||||||
	// Status of the condition, one of True, False, Unknown.
 | 
						// 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.
 | 
						// Last time the condition was checked.
 | 
				
			||||||
	// +optional
 | 
						// +optional
 | 
				
			||||||
	LastProbeTime metav1.Time `json:"lastProbeTime,omitempty" protobuf:"bytes,3,opt,name=lastProbeTime"`
 | 
						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.
 | 
						// The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.
 | 
				
			||||||
	Schedule string `json:"schedule" protobuf:"bytes,1,opt,name=schedule"`
 | 
						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
 | 
						// 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.
 | 
						// time for any reason.  Missed jobs executions will be counted as failed ones.
 | 
				
			||||||
	// +optional
 | 
						// +optional
 | 
				
			||||||
@@ -431,7 +437,7 @@ type CronJobStatus struct {
 | 
				
			|||||||
	// A list of pointers to currently running jobs.
 | 
						// A list of pointers to currently running jobs.
 | 
				
			||||||
	// +optional
 | 
						// +optional
 | 
				
			||||||
	// +listType=atomic
 | 
						// +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.
 | 
						// Information when was the last time the job was successfully scheduled.
 | 
				
			||||||
	// +optional
 | 
						// +optional
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -104,6 +104,12 @@ type CronJobSpec struct {
 | 
				
			|||||||
	// The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.
 | 
						// The schedule in Cron format, see https://en.wikipedia.org/wiki/Cron.
 | 
				
			||||||
	Schedule string `json:"schedule" protobuf:"bytes,1,opt,name=schedule"`
 | 
						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
 | 
						// 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.
 | 
						// time for any reason.  Missed jobs executions will be counted as failed ones.
 | 
				
			||||||
	// +optional
 | 
						// +optional
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -178,6 +178,13 @@ const (
 | 
				
			|||||||
	//
 | 
						//
 | 
				
			||||||
	// Enables server-side field validation.
 | 
						// Enables server-side field validation.
 | 
				
			||||||
	ServerSideFieldValidation featuregate.Feature = "ServerSideFieldValidation"
 | 
						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() {
 | 
					func init() {
 | 
				
			||||||
@@ -207,4 +214,5 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
 | 
				
			|||||||
	CustomResourceValidationExpressions: {Default: false, PreRelease: featuregate.Alpha},
 | 
						CustomResourceValidationExpressions: {Default: false, PreRelease: featuregate.Alpha},
 | 
				
			||||||
	OpenAPIV3:                           {Default: false, PreRelease: featuregate.Alpha},
 | 
						OpenAPIV3:                           {Default: false, PreRelease: featuregate.Alpha},
 | 
				
			||||||
	ServerSideFieldValidation:           {Default: true, PreRelease: featuregate.Beta},
 | 
						ServerSideFieldValidation:           {Default: true, PreRelease: featuregate.Beta},
 | 
				
			||||||
 | 
						CronJobTimeZone:                     {Default: false, PreRelease: featuregate.Alpha},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user