From 87690842097d1fe3bb16e59d4446b3d1220b4db0 Mon Sep 17 00:00:00 2001 From: Allen Ray Date: Fri, 8 May 2026 15:18:49 -0400 Subject: [PATCH 1/4] Remove unused backup code, refactor backup code, fix deprecations --- cmd/cluster-etcd-operator/main.go | 9 +- pkg/backuphelpers/backupvars.go | 122 ----------- pkg/backuphelpers/backupvars_test.go | 161 --------------- pkg/cmd/backuprestore/backupserver.go | 153 -------------- pkg/cmd/backuprestore/backupserver_test.go | 191 ------------------ pkg/cmd/prune-backups/prune.go | 68 +++---- pkg/cmd/prune-backups/prune_test.go | 20 +- pkg/etcdenvvar/envvarcontroller_test.go | 9 + .../backupcontroller/backupcontroller.go | 25 ++- .../periodicbackupcontroller.go | 7 +- .../targetconfigcontroller.go | 42 ---- 11 files changed, 66 insertions(+), 741 deletions(-) delete mode 100644 pkg/backuphelpers/backupvars.go delete mode 100644 pkg/backuphelpers/backupvars_test.go delete mode 100644 pkg/cmd/backuprestore/backupserver.go delete mode 100644 pkg/cmd/backuprestore/backupserver_test.go diff --git a/cmd/cluster-etcd-operator/main.go b/cmd/cluster-etcd-operator/main.go index 4cb8fc89aa..c9c0237d04 100644 --- a/cmd/cluster-etcd-operator/main.go +++ b/cmd/cluster-etcd-operator/main.go @@ -4,10 +4,8 @@ import ( "context" goflag "flag" "fmt" - "io/ioutil" - "math/rand" + "io" "os" - "time" prune_backups "github.com/openshift/cluster-etcd-operator/pkg/cmd/prune-backups" @@ -36,9 +34,7 @@ func main() { // overwrite gRPC logger, to discard all gRPC info-level logging // https://github.com/kubernetes/kubernetes/issues/80741 // https://github.com/kubernetes/kubernetes/pull/84061 - grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, os.Stderr, os.Stderr)) - - rand.Seed(time.Now().UTC().UnixNano()) + grpclog.SetLoggerV2(grpclog.NewLoggerV2(io.Discard, os.Stderr, os.Stderr)) pflag.CommandLine.SetNormalizeFunc(utilflag.WordSepNormalizeFunc) pflag.CommandLine.AddGoFlagSet(goflag.CommandLine) @@ -78,7 +74,6 @@ func NewSSCSCommand(ctx context.Context) *cobra.Command { cmd.AddCommand(prune_backups.NewPruneCommand()) cmd.AddCommand(requestbackup.NewRequestBackupCommand(ctx)) cmd.AddCommand(rev.NewRevCommand(ctx)) - cmd.AddCommand(backuprestore.NewBackupServer(ctx)) cmd.AddCommand(ensureenv.NewEnsureEnvCommand()) return cmd diff --git a/pkg/backuphelpers/backupvars.go b/pkg/backuphelpers/backupvars.go deleted file mode 100644 index 25205282ec..0000000000 --- a/pkg/backuphelpers/backupvars.go +++ /dev/null @@ -1,122 +0,0 @@ -package backuphelpers - -import ( - "fmt" - "reflect" - "strings" - "sync" - - backupv1alpha1 "github.com/openshift/api/config/v1alpha1" - prune "github.com/openshift/cluster-etcd-operator/pkg/cmd/prune-backups" -) - -type Enqueueable interface { - Enqueue() -} - -type BackupVar interface { - AddListener(listener Enqueueable) - SetBackupSpec(spec *backupv1alpha1.EtcdBackupSpec) - ArgString() string - ArgList() []string -} - -type BackupConfig struct { - enabled bool - spec *backupv1alpha1.EtcdBackupSpec - listeners []Enqueueable - mux sync.Mutex -} - -func NewDisabledBackupConfig() *BackupConfig { - return &BackupConfig{ - enabled: false, - mux: sync.Mutex{}, - } -} - -func (b *BackupConfig) SetBackupSpec(spec *backupv1alpha1.EtcdBackupSpec) { - b.mux.Lock() - defer b.mux.Unlock() - - if reflect.DeepEqual(b.spec, spec) { - return - } - - b.enabled = spec != nil - - if spec == nil { - b.spec = nil - } else { - b.spec = spec.DeepCopy() - } - - for _, l := range b.listeners { - l.Enqueue() - } -} - -func (b *BackupConfig) ArgList() []string { - b.mux.Lock() - defer b.mux.Unlock() - args := []string{fmt.Sprintf("--%s=%v", "enabled", b.enabled)} - - if !b.enabled || b.spec == nil { - return args - } - - if b.spec.TimeZone != "" { - args = append(args, fmt.Sprintf("--%s=%s", "timezone", b.spec.TimeZone)) - } - - if b.spec.Schedule != "" { - args = append(args, fmt.Sprintf("--%s=%s", "schedule", b.spec.Schedule)) - } - - if b.spec.RetentionPolicy.RetentionType == prune.RetentionTypeNumber { - args = append(args, fmt.Sprintf("--%s=%s", "type", b.spec.RetentionPolicy.RetentionType)) - args = append(args, fmt.Sprintf("--%s=%d", "maxNumberOfBackups", b.spec.RetentionPolicy.RetentionNumber.MaxNumberOfBackups)) - } else if b.spec.RetentionPolicy.RetentionType == prune.RetentionTypeSize { - args = append(args, fmt.Sprintf("--%s=%s", "type", b.spec.RetentionPolicy.RetentionType)) - args = append(args, fmt.Sprintf("--%s=%d", "maxSizeOfBackupsGb", b.spec.RetentionPolicy.RetentionSize.MaxSizeOfBackupsGb)) - } - - return args -} - -func (b *BackupConfig) ArgString() string { - b.mux.Lock() - defer b.mux.Unlock() - - args := []string{" args:"} - args = append(args, fmt.Sprintf("- --%s=%v", "enabled", b.enabled)) - - if !b.enabled || b.spec == nil { - return strings.Join(args, "\n ") - } - - if b.spec.TimeZone != "" { - args = append(args, fmt.Sprintf("- --%s=%s", "timezone", b.spec.TimeZone)) - } - - if b.spec.Schedule != "" { - args = append(args, fmt.Sprintf("- --%s=%s", "schedule", b.spec.Schedule)) - } - - if b.spec.RetentionPolicy.RetentionType == prune.RetentionTypeNumber { - args = append(args, fmt.Sprintf("- --%s=%s", "type", b.spec.RetentionPolicy.RetentionType)) - args = append(args, fmt.Sprintf("- --%s=%d", "maxNumberOfBackups", b.spec.RetentionPolicy.RetentionNumber.MaxNumberOfBackups)) - } else if b.spec.RetentionPolicy.RetentionType == prune.RetentionTypeSize { - args = append(args, fmt.Sprintf("- --%s=%s", "type", b.spec.RetentionPolicy.RetentionType)) - args = append(args, fmt.Sprintf("- --%s=%d", "maxSizeOfBackupsGb", b.spec.RetentionPolicy.RetentionSize.MaxSizeOfBackupsGb)) - } - - return strings.Join(args, "\n ") -} - -func (b *BackupConfig) AddListener(listener Enqueueable) { - b.mux.Lock() - defer b.mux.Unlock() - - b.listeners = append(b.listeners, listener) -} diff --git a/pkg/backuphelpers/backupvars_test.go b/pkg/backuphelpers/backupvars_test.go deleted file mode 100644 index 2acc104a30..0000000000 --- a/pkg/backuphelpers/backupvars_test.go +++ /dev/null @@ -1,161 +0,0 @@ -package backuphelpers - -import ( - "testing" - - backupv1alpha1 "github.com/openshift/api/config/v1alpha1" - prune "github.com/openshift/cluster-etcd-operator/pkg/cmd/prune-backups" - - "github.com/stretchr/testify/require" -) - -const ( - schedule = "0 */2 * * *" - timezone = "GMT" -) - -func TestBackupConfig_ToArgs(t *testing.T) { - testCases := []struct { - name string - cr *backupv1alpha1.EtcdBackupSpec - expected string - }{ - { - "backup spec with timezone and schedule", - createEtcdBackupSpec(timezone, schedule), - " args:\n - --enabled=true\n - --timezone=GMT\n - --schedule=0 */2 * * *", - }, - { - "backup spec with timezone and empty schedule", - createEtcdBackupSpec(timezone, ""), - " args:\n - --enabled=true\n - --timezone=GMT", - }, - { - "backup spec with empty timezone and schedule", - createEtcdBackupSpec("", schedule), - " args:\n - --enabled=true\n - --schedule=0 */2 * * *", - }, - { - "backup spec with timezone and schedule and retention number", - withRetentionNumberThreeBackups(createEtcdBackupSpec(timezone, schedule)), - " args:\n - --enabled=true\n - --timezone=GMT\n - --schedule=0 */2 * * *\n - --type=RetentionNumber\n - --maxNumberOfBackups=3", - }, - { - "backup spec with timezone and schedule and retention size", - withRetentionSizeOneGB(createEtcdBackupSpec(timezone, schedule)), - " args:\n - --enabled=true\n - --timezone=GMT\n - --schedule=0 */2 * * *\n - --type=RetentionSize\n - --maxSizeOfBackupsGb=1", - }, - { - "backup spec with empty timezone and empty schedule", - nil, - " args:\n - --enabled=false", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - - c := NewDisabledBackupConfig() - c.SetBackupSpec(tc.cr) - - act := c.ArgString() - - require.Equal(t, tc.expected, act) - }) - } -} - -func TestBackupConfig_ToArgList(t *testing.T) { - testCases := []struct { - name string - cr *backupv1alpha1.EtcdBackupSpec - expected []string - }{ - { - "backup spec with timezone and schedule", - createEtcdBackupSpec(timezone, schedule), - []string{ - "--enabled=true", - "--timezone=GMT", - "--schedule=0 */2 * * *", - }, - }, - { - "backup spec with timezone and empty schedule", - createEtcdBackupSpec(timezone, ""), - []string{ - "--enabled=true", - "--timezone=GMT", - }, - }, - { - "backup spec with empty timezone and schedule", - createEtcdBackupSpec("", schedule), - []string{ - "--enabled=true", - "--schedule=0 */2 * * *", - }, - }, - { - "backup spec with timezone and schedule and retention number", - withRetentionNumberThreeBackups(createEtcdBackupSpec(timezone, schedule)), - []string{ - "--enabled=true", - "--timezone=GMT", - "--schedule=0 */2 * * *", - "--type=RetentionNumber", - "--maxNumberOfBackups=3", - }, - }, - { - "backup spec with timezone and schedule and retention size", - withRetentionSizeOneGB(createEtcdBackupSpec(timezone, schedule)), - []string{ - "--enabled=true", - "--timezone=GMT", - "--schedule=0 */2 * * *", - "--type=RetentionSize", - "--maxSizeOfBackupsGb=1", - }, - }, - { - "backup spec with empty timezone and empty schedule", - nil, - []string{ - "--enabled=false", - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - - c := NewDisabledBackupConfig() - c.SetBackupSpec(tc.cr) - require.Equal(t, tc.expected, c.ArgList()) - }) - } -} - -func createEtcdBackupSpec(timezone, schedule string) *backupv1alpha1.EtcdBackupSpec { - return &backupv1alpha1.EtcdBackupSpec{ - Schedule: schedule, - TimeZone: timezone, - } -} - -func withRetentionNumberThreeBackups(b *backupv1alpha1.EtcdBackupSpec) *backupv1alpha1.EtcdBackupSpec { - b.RetentionPolicy.RetentionType = prune.RetentionTypeNumber - b.RetentionPolicy.RetentionNumber = &backupv1alpha1.RetentionNumberConfig{ - MaxNumberOfBackups: 3, - } - return b -} - -func withRetentionSizeOneGB(b *backupv1alpha1.EtcdBackupSpec) *backupv1alpha1.EtcdBackupSpec { - b.RetentionPolicy.RetentionType = prune.RetentionTypeSize - b.RetentionPolicy.RetentionSize = &backupv1alpha1.RetentionSizeConfig{ - MaxSizeOfBackupsGb: 1, - } - return b -} diff --git a/pkg/cmd/backuprestore/backupserver.go b/pkg/cmd/backuprestore/backupserver.go deleted file mode 100644 index 75acb9c78c..0000000000 --- a/pkg/cmd/backuprestore/backupserver.go +++ /dev/null @@ -1,153 +0,0 @@ -package backuprestore - -import ( - "context" - "fmt" - "os" - "os/signal" - "path" - "syscall" - "time" - - prune "github.com/openshift/cluster-etcd-operator/pkg/cmd/prune-backups" - - "k8s.io/klog/v2" - - "github.com/robfig/cron/v3" - "github.com/spf13/cobra" -) - -const backupVolume = "/var/lib/etcd-auto-backup" - -var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM} - -type backupRunner interface { - runBackup(*backupOptions, *prune.PruneOpts) error -} - -type backupRunnerImpl struct{} - -func (b backupRunnerImpl) runBackup(backupOpts *backupOptions, pruneOpts *prune.PruneOpts) error { - dateString := time.Now().Format("2006-01-02_150405") - backupOpts.backupDir = path.Join(backupVolume, dateString) - err := backup(backupOpts) - if err != nil { - return err - } - - err = pruneOpts.Run() - if err != nil { - return err - } - - return nil -} - -type backupServer struct { - schedule string - timeZone string - enabled bool - cronSchedule cron.Schedule - backupOptions - prune.PruneOpts -} - -func NewBackupServer(ctx context.Context) *cobra.Command { - backupSrv := &backupServer{ - backupOptions: backupOptions{errOut: os.Stderr}, - PruneOpts: prune.PruneOpts{ - RetentionType: "None", - BackupPath: backupVolume, - }, - } - - cmd := &cobra.Command{ - Use: "backup-server", - Short: "Backs up a snapshot of etcd database and static pod resources without config", - Run: func(cmd *cobra.Command, args []string) { - - if err := backupSrv.Validate(); err != nil { - klog.Fatal(err) - } - if err := backupSrv.Run(ctx); err != nil { - klog.Fatal(err) - } - }, - } - - backupSrv.AddFlags(cmd) - return cmd -} - -func (b *backupServer) AddFlags(cmd *cobra.Command) { - fs := cmd.Flags() - fs.BoolVar(&b.enabled, "enabled", false, "enable backup server") - fs.StringVar(&b.schedule, "schedule", "", "schedule specifies the cron schedule to run the backup") - fs.StringVar(&b.timeZone, "timezone", "", "timezone specifies the timezone of the cron schedule to run the backup") - - b.backupOptions.AddFlags(fs) - b.PruneOpts.AddFlags(cmd) -} - -func (b *backupServer) Validate() error { - if !b.enabled { - klog.Infof("backup-server is disabled") - return nil - } - - cronSchedule, err := cron.ParseStandard(b.schedule) - if err != nil { - return fmt.Errorf("error parsing backup schedule %v: %w", b.schedule, err) - } - b.cronSchedule = cronSchedule - - b.backupOptions.backupDir = backupVolume - err = b.backupOptions.Validate() - if err != nil { - return fmt.Errorf("error validating backup %v: %w", b.backupOptions, err) - } - - err = b.PruneOpts.Validate() - if err != nil { - return fmt.Errorf("error validating prune args %v: %w", b.PruneOpts, err) - } - - return nil -} - -func (b *backupServer) Run(ctx context.Context) error { - // handle teardown - cCtx, cancel := signal.NotifyContext(ctx, shutdownSignals...) - defer cancel() - - if b.enabled { - bck := backupRunnerImpl{} - err := b.scheduleBackup(cCtx, bck) - if err != nil { - klog.Infof("error running etcd backup: %v", err) - return err - } - } - - <-ctx.Done() - return nil -} - -func (b *backupServer) scheduleBackup(ctx context.Context, bck backupRunner) error { - ticker := time.NewTicker(time.Until(b.cronSchedule.Next(time.Now()))) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - err := bck.runBackup(&b.backupOptions, &b.PruneOpts) - if err != nil { - klog.Errorf("error running backup: %v", err) - return err - } - ticker.Reset(time.Until(b.cronSchedule.Next(time.Now()))) - case <-ctx.Done(): - return nil - } - } -} diff --git a/pkg/cmd/backuprestore/backupserver_test.go b/pkg/cmd/backuprestore/backupserver_test.go deleted file mode 100644 index 5fad878b30..0000000000 --- a/pkg/cmd/backuprestore/backupserver_test.go +++ /dev/null @@ -1,191 +0,0 @@ -package backuprestore - -import ( - "context" - "errors" - "testing" - "time" - - prune "github.com/openshift/cluster-etcd-operator/pkg/cmd/prune-backups" - - "github.com/robfig/cron" - "github.com/stretchr/testify/require" -) - -const validSchedule = "* * * * *" - -func TestBackupServer_Validate(t *testing.T) { - testCases := []struct { - name string - backupServer backupServer - expErr error - }{ - { - "BackupServer is disabled", - backupServer{enabled: false}, - nil, - }, - { - "BackupServer is disabled and invalid schedule", - backupServer{ - enabled: false, - schedule: "invalid schedule", - }, - nil, - }, - { - "BackupServer is disabled and invalid schedule and invalid backup directory", - backupServer{ - enabled: false, - schedule: "invalid schedule", - backupOptions: backupOptions{ - backupDir: "", - }, - }, - nil, - }, - { - "BackupServer is enabled", - backupServer{ - enabled: true, - }, - errors.New("error parsing backup schedule : empty spec string"), - }, - { - "BackupServer is enabled and invalid schedule", - backupServer{ - enabled: true, - schedule: "invalid schedule", - }, - errors.New("error parsing backup schedule invalid schedule"), - }, - { - "BackupServer is enabled and valid schedule", - backupServer{ - enabled: true, - schedule: validSchedule, - PruneOpts: prune.PruneOpts{ - RetentionType: prune.RetentionTypeNone, - }, - }, - nil, - }, - { - "BackupServer is enabled and invalid schedule and invalid backup directory", - backupServer{ - enabled: true, - schedule: "invalid schedule", - backupOptions: backupOptions{ - backupDir: "", - }, - }, - errors.New("error parsing backup schedule invalid schedule"), - }, - { - "BackupServer is enabled and valid schedule and invalid backup directory", - backupServer{ - schedule: validSchedule, - enabled: true, - backupOptions: backupOptions{ - backupDir: "", - }, - PruneOpts: prune.PruneOpts{ - RetentionType: prune.RetentionTypeNone, - }, - }, - nil, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - actErr := tc.backupServer.Validate() - if tc.expErr != nil { - require.Contains(t, actErr.Error(), tc.expErr.Error()) - } else { - require.Equal(t, tc.expErr, actErr) - } - }) - } -} - -func TestNewBackupServer_scheduleBackup(t *testing.T) { - srvr := &backupServer{ - timeZone: "", - enabled: true, - backupOptions: backupOptions{}, - } - - testCases := []struct { - name string - schedule string - timeout time.Duration - slow bool - expBackups int - expErr error - }{ - { - name: "valid schedule", - schedule: "*/1 * * * * *", - timeout: time.Second * 3, - slow: false, - expBackups: 3, - expErr: nil, - }, - { - name: "slow valid schedule", - schedule: "*/2 * * * * *", - timeout: time.Second * 8, - slow: true, - expBackups: 2, - expErr: nil, - }, - { - name: "invalid schedule", - schedule: "invalid schedule", - timeout: time.Minute * 3, - expBackups: 0, - expErr: errors.New("Expected 5 to 6 fields"), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - - cronSchedule, err := cron.Parse(tc.schedule) - if tc.expErr != nil { - require.Contains(t, err.Error(), tc.expErr.Error()) - return - } - require.NoError(t, err) - srvr.cronSchedule = cronSchedule - - mock := backupRunnerMock{counter: 0} - if tc.slow { - mock.slow = true - mock.delay = time.Second * 3 - } - ctxTimeout, cancel := context.WithTimeout(context.Background(), tc.timeout) - defer cancel() - - err = srvr.scheduleBackup(ctxTimeout, &mock) - require.Equal(t, tc.expErr, err) - require.GreaterOrEqual(t, mock.counter, tc.expBackups) - }) - } -} - -type backupRunnerMock struct { - counter int - slow bool - delay time.Duration -} - -func (b *backupRunnerMock) runBackup(backupOpts *backupOptions, pruneOpts *prune.PruneOpts) error { - if b.slow { - time.Sleep(b.delay) - } - - b.counter++ - return nil -} diff --git a/pkg/cmd/prune-backups/prune.go b/pkg/cmd/prune-backups/prune.go index b4c02920e7..3bfa2f6a16 100644 --- a/pkg/cmd/prune-backups/prune.go +++ b/pkg/cmd/prune-backups/prune.go @@ -6,10 +6,11 @@ import ( "io/fs" "os" "path" - "sort" + "slices" "strings" "time" + backupv1alpha1 "github.com/openshift/api/config/v1alpha1" "github.com/spf13/cobra" "k8s.io/klog/v2" ) @@ -19,10 +20,6 @@ const ( // for backup-server we use another path for storing backups (i.e. /var/lib/etcd-auto-backup) // for tests we will change this to a tmp directory BasePath = "/etc/kubernetes/cluster-backup/" - - RetentionTypeNone = "None" - RetentionTypeSize = "RetentionSize" - RetentionTypeNumber = "RetentionNumber" ) type backupDirStats []backupDirStat @@ -78,43 +75,43 @@ func (r *PruneOpts) AddFlags(cmd *cobra.Command) { } func (r *PruneOpts) Validate() error { - if r.RetentionType != RetentionTypeNone && r.RetentionType != RetentionTypeNumber && r.RetentionType != RetentionTypeSize { - return fmt.Errorf("unknown retention type: [%s]", r.RetentionType) - } - - if r.RetentionType == RetentionTypeNumber { + retType := backupv1alpha1.RetentionType(r.RetentionType) + switch retType { + case "": + return nil + case backupv1alpha1.RetentionTypeNumber: if r.MaxNumberOfBackups < 1 { return fmt.Errorf("unexpected amount of backups [%d] found, expected at least 1", r.MaxNumberOfBackups) } if r.MaxSizeOfBackupsGb != 0 { - return fmt.Errorf("unexpected argument [MaxSizeOfBackupsGb] found while using %s", RetentionTypeNumber) + return fmt.Errorf("unexpected argument [MaxSizeOfBackupsGb] found while using %s", retType) } - - } else if r.RetentionType == RetentionTypeSize { + case backupv1alpha1.RetentionTypeSize: if r.MaxSizeOfBackupsGb < 1 { return fmt.Errorf("unexpected size of backups [%d]gb found, expected at least 1", r.MaxSizeOfBackupsGb) } if r.MaxNumberOfBackups != 0 { - return fmt.Errorf("unexpected argument [MaxNumberOfBackups] found while using %s", RetentionTypeSize) + return fmt.Errorf("unexpected argument [MaxNumberOfBackups] found while using %s", retType) } + default: + return fmt.Errorf("unknown retention type: [%s]", r.RetentionType) } return nil } func (r *PruneOpts) Run() error { - if r.RetentionType == RetentionTypeNone { - klog.Infof("nothing to do, retention type is none") - return nil - } else if r.RetentionType == RetentionTypeSize { + switch backupv1alpha1.RetentionType(r.RetentionType) { + case backupv1alpha1.RetentionTypeSize: return retainBySizeGb(r.MaxSizeOfBackupsGb, r.BackupPath) - } else if r.RetentionType == RetentionTypeNumber { + case backupv1alpha1.RetentionTypeNumber: return retainByNumber(r.MaxNumberOfBackups, r.BackupPath) + default: + klog.Infof("nothing to do, retention type is none") + return nil } - - return nil } func retainBySizeGb(sizeInGb int, backupPath string) error { @@ -123,7 +120,7 @@ func retainBySizeGb(sizeInGb int, backupPath string) error { return err } - sort.Sort(folders) + folders.Sort() // we keep the latest - up to sizeInGb folders around, the remainder is deleted // the newest backups are always found at the beginning of the list @@ -163,7 +160,7 @@ func retainByNumber(maxNumBackups int, backupPath string) error { return nil } - sort.Sort(folders) + folders.Sort() // the newest backups are always found at the beginning of the list for _, f := range folders[maxNumBackups:] { bPath := path.Join(backupPath, f.name) @@ -226,21 +223,14 @@ func (s backupDirStat) String() string { return fmt.Sprintf("Name=[%s] SizeBytes=[%d] ModTime=[%s]", s.name, s.sizeBytes, s.modTime.String()) } -func (b backupDirStats) Len() int { - return len(b) -} - -// Less causes the slice to be sorted descending by modtime, the newest comes first +// Sorts descending by modtime, the newest comes first // in case the mod times are equal, we compare on the names in the same order. -func (b backupDirStats) Less(i, j int) bool { - if b[j].modTime == b[i].modTime { - return strings.Compare(b[j].name, b[i].name) < 0 - } - return b[j].modTime.Before(b[i].modTime) -} - -func (b backupDirStats) Swap(i, j int) { - tmp := b[i] - b[i] = b[j] - b[j] = tmp +func (s backupDirStats) Sort() { + slices.SortFunc(s, func(a, b backupDirStat) int { + modTimeComp := b.modTime.Compare(a.modTime) + if modTimeComp == 0 { + return strings.Compare(b.name, a.name) + } + return modTimeComp + }) } diff --git a/pkg/cmd/prune-backups/prune_test.go b/pkg/cmd/prune-backups/prune_test.go index dd5b39b994..30115043ce 100644 --- a/pkg/cmd/prune-backups/prune_test.go +++ b/pkg/cmd/prune-backups/prune_test.go @@ -5,10 +5,10 @@ import ( "math/rand" "os" "path" - "sort" "testing" "time" + "github.com/openshift/api/config/v1alpha1" "github.com/stretchr/testify/require" ) @@ -17,17 +17,17 @@ func TestCommandValidation(t *testing.T) { opts PruneOpts expectedErr error }{ - "none happy": {opts: PruneOpts{RetentionType: RetentionTypeNone}}, - "number happy": {opts: PruneOpts{RetentionType: RetentionTypeNumber, MaxNumberOfBackups: 1}}, - "number zero": {opts: PruneOpts{RetentionType: RetentionTypeNumber, MaxNumberOfBackups: 0}, + "none happy": {opts: PruneOpts{RetentionType: ""}}, + "number happy": {opts: PruneOpts{RetentionType: string(v1alpha1.RetentionTypeNumber), MaxNumberOfBackups: 1}}, + "number zero": {opts: PruneOpts{RetentionType: string(v1alpha1.RetentionTypeNumber), MaxNumberOfBackups: 0}, expectedErr: fmt.Errorf("unexpected amount of backups [0] found, expected at least 1")}, - "number flipped": {opts: PruneOpts{RetentionType: RetentionTypeNumber, MaxNumberOfBackups: 2, MaxSizeOfBackupsGb: 25}, + "number flipped": {opts: PruneOpts{RetentionType: string(v1alpha1.RetentionTypeNumber), MaxNumberOfBackups: 2, MaxSizeOfBackupsGb: 25}, expectedErr: fmt.Errorf("unexpected argument [MaxSizeOfBackupsGb] found while using RetentionNumber")}, - "size happy": {opts: PruneOpts{RetentionType: RetentionTypeSize, MaxSizeOfBackupsGb: 1}}, - "size zero": {opts: PruneOpts{RetentionType: RetentionTypeSize, MaxSizeOfBackupsGb: 0}, + "size happy": {opts: PruneOpts{RetentionType: string(v1alpha1.RetentionTypeSize), MaxSizeOfBackupsGb: 1}}, + "size zero": {opts: PruneOpts{RetentionType: string(v1alpha1.RetentionTypeSize), MaxSizeOfBackupsGb: 0}, expectedErr: fmt.Errorf("unexpected size of backups [0]gb found, expected at least 1")}, - "size flipped": {opts: PruneOpts{RetentionType: RetentionTypeSize, MaxSizeOfBackupsGb: 2, MaxNumberOfBackups: 25}, + "size flipped": {opts: PruneOpts{RetentionType: string(v1alpha1.RetentionTypeSize), MaxSizeOfBackupsGb: 2, MaxNumberOfBackups: 25}, expectedErr: fmt.Errorf("unexpected argument [MaxNumberOfBackups] found while using RetentionSize")}, } @@ -133,7 +133,7 @@ func TestDirectorySorting(t *testing.T) { stats = append(stats, backupDirStat{name: "a", modTime: time.Unix(2500, 0)}) stats = append(stats, backupDirStat{name: "b", modTime: time.Unix(3500, 0)}) - sort.Sort(stats) + stats.Sort() var names []string for _, a := range stats { names = append(names, a.name) @@ -147,7 +147,7 @@ func TestDirectorySortingByName(t *testing.T) { stats = append(stats, backupDirStat{name: "backup-name-2022-01-02_150405"}) stats = append(stats, backupDirStat{name: "backup-name-2022-01-02_50405"}) - sort.Sort(stats) + stats.Sort() var names []string for _, a := range stats { names = append(names, a.name) diff --git a/pkg/etcdenvvar/envvarcontroller_test.go b/pkg/etcdenvvar/envvarcontroller_test.go index f417d7e051..e96b8a50f0 100644 --- a/pkg/etcdenvvar/envvarcontroller_test.go +++ b/pkg/etcdenvvar/envvarcontroller_test.go @@ -3,6 +3,7 @@ package etcdenvvar import ( "context" "fmt" + goruntime "runtime" "testing" "github.com/openshift/cluster-etcd-operator/pkg/tlshelpers" @@ -68,6 +69,14 @@ var ( } ) +func init() { + // getUnsupportedArch sets ETCD_UNSUPPORTED_ARCH based on runtime.GOARCH, so unit tests on arm64 (e.g. Apple Silicon) or s390x need to include it in the expected output + switch goruntime.GOARCH { + case "arm64", "s390x": + defaultEnvResult["ETCD_UNSUPPORTED_ARCH"] = goruntime.GOARCH + } +} + func TestEnvVarController(t *testing.T) { scenarios := []struct { name string diff --git a/pkg/operator/backupcontroller/backupcontroller.go b/pkg/operator/backupcontroller/backupcontroller.go index eee94f2650..e517b4bbc9 100644 --- a/pkg/operator/backupcontroller/backupcontroller.go +++ b/pkg/operator/backupcontroller/backupcontroller.go @@ -3,7 +3,7 @@ package backupcontroller import ( "context" "fmt" - "sort" + "slices" "strings" "time" @@ -89,9 +89,8 @@ func (c *BackupController) sync(ctx context.Context, _ factory.SyncContext) erro } // we only allow to run one at a time, if there's currently a job running then we will skip it in this reconciliation step - runningJobs := findRunningJobs(currentJobs) - if len(runningJobs) > 0 { - klog.V(4).Infof("BackupController already found [%d] running jobs, skipping", len(runningJobs)) + if numRunningJobs := findNumRunningJobs(currentJobs); numRunningJobs > 0 { + klog.V(4).Infof("BackupController already found [%d] running jobs, skipping", numRunningJobs) return nil } @@ -138,8 +137,8 @@ func (c *BackupController) sync(ctx context.Context, _ factory.SyncContext) erro } // in case of multiple backups requested, we're trying to reconcile in order of their names (also to reduce flakiness in tests) - sort.Slice(backupsToRun, func(i, j int) bool { - return strings.Compare(backupsToRun[i].Name, backupsToRun[j].Name) < 0 + slices.SortFunc(backupsToRun, func(a, b operatorv1alpha1.EtcdBackup) int { + return strings.Compare(a.Name, b.Name) }) klog.V(4).Infof("BackupController backupsToRun: %v, chooses %v", backupsToRun, backupsToRun[0]) @@ -195,7 +194,7 @@ func validateBackup(ctx context.Context, func markBackupSkipped(ctx context.Context, client operatorv1alpha1client.EtcdBackupInterface, backup operatorv1alpha1.EtcdBackup) error { // mark all previous conditions as false, only BackupSkipped should be true - for i := 0; i < len(backup.Status.Conditions); i++ { + for i := range backup.Status.Conditions { backup.Status.Conditions[i].Status = v1.ConditionFalse } @@ -233,7 +232,7 @@ func markBackupFailed(ctx context.Context, failedMessage string) error { // mark all previous conditions as false, only BackupFailed should be true - for i := 0; i < len(backup.Status.Conditions); i++ { + for i := range backup.Status.Conditions { backup.Status.Conditions[i].Status = v1.ConditionFalse } @@ -281,15 +280,15 @@ func indexJobsByBackupLabelName(jobs *batchv1.JobList) map[string]batchv1.Job { return m } -func findRunningJobs(jobs *batchv1.JobList) []batchv1.Job { - var running []batchv1.Job +func findNumRunningJobs(jobs *batchv1.JobList) int { if jobs == nil { - return running + return 0 } + running := 0 for _, j := range jobs.Items { if !isJobFinished(&j) { - running = append(running, *j.DeepCopy()) + running++ } } @@ -351,7 +350,7 @@ func reconcileJobStatus(ctx context.Context, } // only the new completed condition should stay true, mark all others false - for i := 0; i < len(bp.Status.Conditions); i++ { + for i := range bp.Status.Conditions { bp.Status.Conditions[i].Status = v1.ConditionFalse } diff --git a/pkg/operator/periodicbackupcontroller/periodicbackupcontroller.go b/pkg/operator/periodicbackupcontroller/periodicbackupcontroller.go index 16be582c0f..50ec66be7b 100644 --- a/pkg/operator/periodicbackupcontroller/periodicbackupcontroller.go +++ b/pkg/operator/periodicbackupcontroller/periodicbackupcontroller.go @@ -212,14 +212,15 @@ func setRetentionPolicyInitContainer(retentionPolicy backupv1alpha1.RetentionPol fmt.Sprintf("--type=%s", retentionType), } - if retentionType == backupv1alpha1.RetentionTypeNumber { + switch retentionType { + case backupv1alpha1.RetentionTypeNumber: maxNum := 15 if retentionPolicy.RetentionNumber != nil { maxNum = retentionPolicy.RetentionNumber.MaxNumberOfBackups } retentionInitArgs = append(retentionInitArgs, fmt.Sprintf("--maxNumberOfBackups=%d", maxNum)) - } else if retentionType == backupv1alpha1.RetentionTypeSize { + case backupv1alpha1.RetentionTypeSize: // that's not defined in the API, but we assume that's about 15-20 median sized snapshots in line with MaxNumberOfBackups maxSizeGb := 10 if retentionPolicy.RetentionSize != nil { @@ -227,7 +228,7 @@ func setRetentionPolicyInitContainer(retentionPolicy backupv1alpha1.RetentionPol } retentionInitArgs = append(retentionInitArgs, fmt.Sprintf("--maxSizeOfBackupsGb=%d", maxSizeGb)) - } else { + default: return fmt.Errorf("unknown retention type: %s", retentionPolicy.RetentionType) } diff --git a/pkg/operator/targetconfigcontroller/targetconfigcontroller.go b/pkg/operator/targetconfigcontroller/targetconfigcontroller.go index fce8ee26c3..756e1124df 100644 --- a/pkg/operator/targetconfigcontroller/targetconfigcontroller.go +++ b/pkg/operator/targetconfigcontroller/targetconfigcontroller.go @@ -19,7 +19,6 @@ import ( "github.com/openshift/library-go/pkg/operator/resource/resourceread" "github.com/openshift/library-go/pkg/operator/v1helpers" corev1 "k8s.io/api/core/v1" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" @@ -243,44 +242,3 @@ func (c *TargetConfigController) manageStandardPod(ctx context.Context, subs *ce func (c *TargetConfigController) Enqueue() { c.enqueueFn() } - -func (c *TargetConfigController) namespaceEventHandler() cache.ResourceEventHandler { - return cache.ResourceEventHandlerFuncs{ - AddFunc: func(obj any) { - ns, ok := obj.(*corev1.Namespace) - if !ok { - c.Enqueue() - } - if ns.Name == ("openshift-etcd") { - c.Enqueue() - } - }, - UpdateFunc: func(old, new any) { - ns, ok := old.(*corev1.Namespace) - if !ok { - c.Enqueue() - } - if ns.Name == ("openshift-etcd") { - c.Enqueue() - } - }, - DeleteFunc: func(obj any) { - ns, ok := obj.(*corev1.Namespace) - if !ok { - tombstone, ok := obj.(cache.DeletedFinalStateUnknown) - if !ok { - utilruntime.HandleError(fmt.Errorf("couldn't get object from tombstone %#v", obj)) - return - } - ns, ok = tombstone.Obj.(*corev1.Namespace) - if !ok { - utilruntime.HandleError(fmt.Errorf("tombstone contained object that is not a Namespace %#v", obj)) - return - } - } - if ns.Name == ("openshift-etcd") { - c.Enqueue() - } - }, - } -} From ebf00ec0897416868db69d69f6596afd694cadae Mon Sep 17 00:00:00 2001 From: Allen Ray Date: Fri, 8 May 2026 15:58:43 -0400 Subject: [PATCH 2/4] Move etcd backup cr into bindata --- bindata/etcd/etcd-backup-cr.yaml | 12 +++++++ pkg/cmd/request-backup/requestbackup.go | 48 +++++++++++++------------ 2 files changed, 37 insertions(+), 23 deletions(-) create mode 100644 bindata/etcd/etcd-backup-cr.yaml diff --git a/bindata/etcd/etcd-backup-cr.yaml b/bindata/etcd/etcd-backup-cr.yaml new file mode 100644 index 0000000000..9f2bab309a --- /dev/null +++ b/bindata/etcd/etcd-backup-cr.yaml @@ -0,0 +1,12 @@ +apiVersion: operator.openshift.io/v1alpha1 +kind: EtcdBackup +metadata: + name: templated + namespace: openshift-etcd + ownerReferences: + - apiVersion: batch/v1 + kind: Job + name: templated + uid: templated +spec: + pvcName: templated diff --git a/pkg/cmd/request-backup/requestbackup.go b/pkg/cmd/request-backup/requestbackup.go index ddc32abc0a..0370688123 100644 --- a/pkg/cmd/request-backup/requestbackup.go +++ b/pkg/cmd/request-backup/requestbackup.go @@ -8,12 +8,14 @@ import ( "os/signal" "syscall" - "github.com/openshift/cluster-etcd-operator/pkg/operator/operatorclient" + "github.com/openshift/cluster-etcd-operator/bindata" operatorv1alpha1 "github.com/openshift/api/operator/v1alpha1" operatorversionedclientv1alpha1 "github.com/openshift/client-go/operator/clientset/versioned/typed/operator/v1alpha1" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/clientcmd" "k8s.io/klog/v2" @@ -138,28 +140,28 @@ func (r *requestBackupOpts) Run(ctx context.Context) error { } etcdBackupClient := operatorClient.EtcdBackups() - // Create the EtcdBackup CR - // TODO(haseeb): This EtcdBackup manifest is small enough but should we template this manifest from bindata/etcd - // like we usually do for other manifests? - etcdBackup := &operatorv1alpha1.EtcdBackup{ - ObjectMeta: metav1.ObjectMeta{ - Name: r.etcdBackupName, - Namespace: operatorclient.TargetNamespace, - // Due to a limitation of the kube-controller, we can't rely on the api to garbage collect non-namespaced - // etcdbackups from their corresponding namespaced jobs. - // We set this job information solely to prune those in the backup controller. - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: "batch/v1", - Kind: "Job", - Name: r.ownerJobName, - UID: types.UID(r.ownerJobUID), - }, - }, - }, - Spec: operatorv1alpha1.EtcdBackupSpec{ - PVCName: r.pvcName, - }, + // Create the EtcdBackup CR from the bindata template + scheme := runtime.NewScheme() + if err := operatorv1alpha1.Install(scheme); err != nil { + return fmt.Errorf("could not add operatorv1alpha1 scheme: %w", err) + } + codec := serializer.NewCodecFactory(scheme) + obj, err := runtime.Decode(codec.UniversalDecoder(operatorv1alpha1.GroupVersion), bindata.MustAsset("etcd/etcd-backup-cr.yaml")) + if err != nil { + return fmt.Errorf("could not decode EtcdBackup template: %w", err) + } + etcdBackup := obj.(*operatorv1alpha1.EtcdBackup) + etcdBackup.Name = r.etcdBackupName + etcdBackup.Spec.PVCName = r.pvcName + // Due to a limitation of the kube-controller, we can't rely on the api to garbage collect non-namespaced + // etcdbackups from their corresponding namespaced jobs. + // We set this job information solely to prune those in the backup controller. + for i := range etcdBackup.OwnerReferences { + if etcdBackup.OwnerReferences[i].Kind == "Job" { + etcdBackup.OwnerReferences[i].Name = r.ownerJobName + etcdBackup.OwnerReferences[i].UID = types.UID(r.ownerJobUID) + break + } } klog.Infof("creating CRD: %v", etcdBackup) From b164d1ae156a59556af7a78e64bcb2e78bd26591 Mon Sep 17 00:00:00 2001 From: Allen Ray Date: Fri, 8 May 2026 16:17:42 -0400 Subject: [PATCH 3/4] fix coderabbit --- pkg/cmd/prune-backups/prune.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/prune-backups/prune.go b/pkg/cmd/prune-backups/prune.go index 3bfa2f6a16..d4635948dc 100644 --- a/pkg/cmd/prune-backups/prune.go +++ b/pkg/cmd/prune-backups/prune.go @@ -77,7 +77,7 @@ func (r *PruneOpts) AddFlags(cmd *cobra.Command) { func (r *PruneOpts) Validate() error { retType := backupv1alpha1.RetentionType(r.RetentionType) switch retType { - case "": + case "", "None": return nil case backupv1alpha1.RetentionTypeNumber: if r.MaxNumberOfBackups < 1 { From d9e89279da583857015f37c6eb4a66c2b799f5ee Mon Sep 17 00:00:00 2001 From: Allen Ray Date: Thu, 14 May 2026 14:24:01 -0400 Subject: [PATCH 4/4] fix failures --- go.mod | 3 +- go.sum | 2 - pkg/cmd/request-backup/requestbackup.go | 2 + vendor/github.com/robfig/cron/v3/.gitignore | 22 - vendor/github.com/robfig/cron/v3/.travis.yml | 1 - vendor/github.com/robfig/cron/v3/LICENSE | 21 - vendor/github.com/robfig/cron/v3/README.md | 125 ----- vendor/github.com/robfig/cron/v3/chain.go | 92 ---- .../robfig/cron/v3/constantdelay.go | 27 -- vendor/github.com/robfig/cron/v3/cron.go | 355 -------------- vendor/github.com/robfig/cron/v3/doc.go | 231 ---------- vendor/github.com/robfig/cron/v3/logger.go | 86 ---- vendor/github.com/robfig/cron/v3/option.go | 45 -- vendor/github.com/robfig/cron/v3/parser.go | 434 ------------------ vendor/github.com/robfig/cron/v3/spec.go | 188 -------- vendor/modules.txt | 3 - 16 files changed, 3 insertions(+), 1634 deletions(-) delete mode 100644 vendor/github.com/robfig/cron/v3/.gitignore delete mode 100644 vendor/github.com/robfig/cron/v3/.travis.yml delete mode 100644 vendor/github.com/robfig/cron/v3/LICENSE delete mode 100644 vendor/github.com/robfig/cron/v3/README.md delete mode 100644 vendor/github.com/robfig/cron/v3/chain.go delete mode 100644 vendor/github.com/robfig/cron/v3/constantdelay.go delete mode 100644 vendor/github.com/robfig/cron/v3/cron.go delete mode 100644 vendor/github.com/robfig/cron/v3/doc.go delete mode 100644 vendor/github.com/robfig/cron/v3/logger.go delete mode 100644 vendor/github.com/robfig/cron/v3/option.go delete mode 100644 vendor/github.com/robfig/cron/v3/parser.go delete mode 100644 vendor/github.com/robfig/cron/v3/spec.go diff --git a/go.mod b/go.mod index 809b7dfb15..0fd111c161 100644 --- a/go.mod +++ b/go.mod @@ -17,8 +17,6 @@ require ( github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/common v0.66.1 - github.com/robfig/cron v1.2.0 - github.com/robfig/cron/v3 v3.0.1 github.com/spf13/cobra v1.10.0 github.com/spf13/pflag v1.0.9 github.com/stretchr/testify v1.11.1 @@ -103,6 +101,7 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/robfig/cron v1.2.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect diff --git a/go.sum b/go.sum index f3016af968..9f111b2b51 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,6 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= -github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= -github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/pkg/cmd/request-backup/requestbackup.go b/pkg/cmd/request-backup/requestbackup.go index 0370688123..0535f4fe85 100644 --- a/pkg/cmd/request-backup/requestbackup.go +++ b/pkg/cmd/request-backup/requestbackup.go @@ -9,6 +9,7 @@ import ( "syscall" "github.com/openshift/cluster-etcd-operator/bindata" + "github.com/openshift/cluster-etcd-operator/pkg/operator/operatorclient" operatorv1alpha1 "github.com/openshift/api/operator/v1alpha1" operatorversionedclientv1alpha1 "github.com/openshift/client-go/operator/clientset/versioned/typed/operator/v1alpha1" @@ -152,6 +153,7 @@ func (r *requestBackupOpts) Run(ctx context.Context) error { } etcdBackup := obj.(*operatorv1alpha1.EtcdBackup) etcdBackup.Name = r.etcdBackupName + etcdBackup.Namespace = operatorclient.TargetNamespace etcdBackup.Spec.PVCName = r.pvcName // Due to a limitation of the kube-controller, we can't rely on the api to garbage collect non-namespaced // etcdbackups from their corresponding namespaced jobs. diff --git a/vendor/github.com/robfig/cron/v3/.gitignore b/vendor/github.com/robfig/cron/v3/.gitignore deleted file mode 100644 index 00268614f0..0000000000 --- a/vendor/github.com/robfig/cron/v3/.gitignore +++ /dev/null @@ -1,22 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe diff --git a/vendor/github.com/robfig/cron/v3/.travis.yml b/vendor/github.com/robfig/cron/v3/.travis.yml deleted file mode 100644 index 4f2ee4d973..0000000000 --- a/vendor/github.com/robfig/cron/v3/.travis.yml +++ /dev/null @@ -1 +0,0 @@ -language: go diff --git a/vendor/github.com/robfig/cron/v3/LICENSE b/vendor/github.com/robfig/cron/v3/LICENSE deleted file mode 100644 index 3a0f627ffe..0000000000 --- a/vendor/github.com/robfig/cron/v3/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (C) 2012 Rob Figueiredo -All Rights Reserved. - -MIT LICENSE - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/robfig/cron/v3/README.md b/vendor/github.com/robfig/cron/v3/README.md deleted file mode 100644 index 984c537c01..0000000000 --- a/vendor/github.com/robfig/cron/v3/README.md +++ /dev/null @@ -1,125 +0,0 @@ -[![GoDoc](http://godoc.org/github.com/robfig/cron?status.png)](http://godoc.org/github.com/robfig/cron) -[![Build Status](https://travis-ci.org/robfig/cron.svg?branch=master)](https://travis-ci.org/robfig/cron) - -# cron - -Cron V3 has been released! - -To download the specific tagged release, run: - - go get github.com/robfig/cron/v3@v3.0.0 - -Import it in your program as: - - import "github.com/robfig/cron/v3" - -It requires Go 1.11 or later due to usage of Go Modules. - -Refer to the documentation here: -http://godoc.org/github.com/robfig/cron - -The rest of this document describes the the advances in v3 and a list of -breaking changes for users that wish to upgrade from an earlier version. - -## Upgrading to v3 (June 2019) - -cron v3 is a major upgrade to the library that addresses all outstanding bugs, -feature requests, and rough edges. It is based on a merge of master which -contains various fixes to issues found over the years and the v2 branch which -contains some backwards-incompatible features like the ability to remove cron -jobs. In addition, v3 adds support for Go Modules, cleans up rough edges like -the timezone support, and fixes a number of bugs. - -New features: - -- Support for Go modules. Callers must now import this library as - `github.com/robfig/cron/v3`, instead of `gopkg.in/...` - -- Fixed bugs: - - 0f01e6b parser: fix combining of Dow and Dom (#70) - - dbf3220 adjust times when rolling the clock forward to handle non-existent midnight (#157) - - eeecf15 spec_test.go: ensure an error is returned on 0 increment (#144) - - 70971dc cron.Entries(): update request for snapshot to include a reply channel (#97) - - 1cba5e6 cron: fix: removing a job causes the next scheduled job to run too late (#206) - -- Standard cron spec parsing by default (first field is "minute"), with an easy - way to opt into the seconds field (quartz-compatible). Although, note that the - year field (optional in Quartz) is not supported. - -- Extensible, key/value logging via an interface that complies with - the https://github.com/go-logr/logr project. - -- The new Chain & JobWrapper types allow you to install "interceptors" to add - cross-cutting behavior like the following: - - Recover any panics from jobs - - Delay a job's execution if the previous run hasn't completed yet - - Skip a job's execution if the previous run hasn't completed yet - - Log each job's invocations - - Notification when jobs are completed - -It is backwards incompatible with both v1 and v2. These updates are required: - -- The v1 branch accepted an optional seconds field at the beginning of the cron - spec. This is non-standard and has led to a lot of confusion. The new default - parser conforms to the standard as described by [the Cron wikipedia page]. - - UPDATING: To retain the old behavior, construct your Cron with a custom - parser: - - // Seconds field, required - cron.New(cron.WithSeconds()) - - // Seconds field, optional - cron.New( - cron.WithParser( - cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)) - -- The Cron type now accepts functional options on construction rather than the - previous ad-hoc behavior modification mechanisms (setting a field, calling a setter). - - UPDATING: Code that sets Cron.ErrorLogger or calls Cron.SetLocation must be - updated to provide those values on construction. - -- CRON_TZ is now the recommended way to specify the timezone of a single - schedule, which is sanctioned by the specification. The legacy "TZ=" prefix - will continue to be supported since it is unambiguous and easy to do so. - - UPDATING: No update is required. - -- By default, cron will no longer recover panics in jobs that it runs. - Recovering can be surprising (see issue #192) and seems to be at odds with - typical behavior of libraries. Relatedly, the `cron.WithPanicLogger` option - has been removed to accommodate the more general JobWrapper type. - - UPDATING: To opt into panic recovery and configure the panic logger: - - cron.New(cron.WithChain( - cron.Recover(logger), // or use cron.DefaultLogger - )) - -- In adding support for https://github.com/go-logr/logr, `cron.WithVerboseLogger` was - removed, since it is duplicative with the leveled logging. - - UPDATING: Callers should use `WithLogger` and specify a logger that does not - discard `Info` logs. For convenience, one is provided that wraps `*log.Logger`: - - cron.New( - cron.WithLogger(cron.VerbosePrintfLogger(logger))) - - -### Background - Cron spec format - -There are two cron spec formats in common usage: - -- The "standard" cron format, described on [the Cron wikipedia page] and used by - the cron Linux system utility. - -- The cron format used by [the Quartz Scheduler], commonly used for scheduled - jobs in Java software - -[the Cron wikipedia page]: https://en.wikipedia.org/wiki/Cron -[the Quartz Scheduler]: http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/tutorial-lesson-06.html - -The original version of this package included an optional "seconds" field, which -made it incompatible with both of these formats. Now, the "standard" format is -the default format accepted, and the Quartz format is opt-in. diff --git a/vendor/github.com/robfig/cron/v3/chain.go b/vendor/github.com/robfig/cron/v3/chain.go deleted file mode 100644 index 9565b418e0..0000000000 --- a/vendor/github.com/robfig/cron/v3/chain.go +++ /dev/null @@ -1,92 +0,0 @@ -package cron - -import ( - "fmt" - "runtime" - "sync" - "time" -) - -// JobWrapper decorates the given Job with some behavior. -type JobWrapper func(Job) Job - -// Chain is a sequence of JobWrappers that decorates submitted jobs with -// cross-cutting behaviors like logging or synchronization. -type Chain struct { - wrappers []JobWrapper -} - -// NewChain returns a Chain consisting of the given JobWrappers. -func NewChain(c ...JobWrapper) Chain { - return Chain{c} -} - -// Then decorates the given job with all JobWrappers in the chain. -// -// This: -// NewChain(m1, m2, m3).Then(job) -// is equivalent to: -// m1(m2(m3(job))) -func (c Chain) Then(j Job) Job { - for i := range c.wrappers { - j = c.wrappers[len(c.wrappers)-i-1](j) - } - return j -} - -// Recover panics in wrapped jobs and log them with the provided logger. -func Recover(logger Logger) JobWrapper { - return func(j Job) Job { - return FuncJob(func() { - defer func() { - if r := recover(); r != nil { - const size = 64 << 10 - buf := make([]byte, size) - buf = buf[:runtime.Stack(buf, false)] - err, ok := r.(error) - if !ok { - err = fmt.Errorf("%v", r) - } - logger.Error(err, "panic", "stack", "...\n"+string(buf)) - } - }() - j.Run() - }) - } -} - -// DelayIfStillRunning serializes jobs, delaying subsequent runs until the -// previous one is complete. Jobs running after a delay of more than a minute -// have the delay logged at Info. -func DelayIfStillRunning(logger Logger) JobWrapper { - return func(j Job) Job { - var mu sync.Mutex - return FuncJob(func() { - start := time.Now() - mu.Lock() - defer mu.Unlock() - if dur := time.Since(start); dur > time.Minute { - logger.Info("delay", "duration", dur) - } - j.Run() - }) - } -} - -// SkipIfStillRunning skips an invocation of the Job if a previous invocation is -// still running. It logs skips to the given logger at Info level. -func SkipIfStillRunning(logger Logger) JobWrapper { - return func(j Job) Job { - var ch = make(chan struct{}, 1) - ch <- struct{}{} - return FuncJob(func() { - select { - case v := <-ch: - j.Run() - ch <- v - default: - logger.Info("skip") - } - }) - } -} diff --git a/vendor/github.com/robfig/cron/v3/constantdelay.go b/vendor/github.com/robfig/cron/v3/constantdelay.go deleted file mode 100644 index cd6e7b1be9..0000000000 --- a/vendor/github.com/robfig/cron/v3/constantdelay.go +++ /dev/null @@ -1,27 +0,0 @@ -package cron - -import "time" - -// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes". -// It does not support jobs more frequent than once a second. -type ConstantDelaySchedule struct { - Delay time.Duration -} - -// Every returns a crontab Schedule that activates once every duration. -// Delays of less than a second are not supported (will round up to 1 second). -// Any fields less than a Second are truncated. -func Every(duration time.Duration) ConstantDelaySchedule { - if duration < time.Second { - duration = time.Second - } - return ConstantDelaySchedule{ - Delay: duration - time.Duration(duration.Nanoseconds())%time.Second, - } -} - -// Next returns the next time this should be run. -// This rounds so that the next activation time will be on the second. -func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time { - return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond) -} diff --git a/vendor/github.com/robfig/cron/v3/cron.go b/vendor/github.com/robfig/cron/v3/cron.go deleted file mode 100644 index c7e9176658..0000000000 --- a/vendor/github.com/robfig/cron/v3/cron.go +++ /dev/null @@ -1,355 +0,0 @@ -package cron - -import ( - "context" - "sort" - "sync" - "time" -) - -// Cron keeps track of any number of entries, invoking the associated func as -// specified by the schedule. It may be started, stopped, and the entries may -// be inspected while running. -type Cron struct { - entries []*Entry - chain Chain - stop chan struct{} - add chan *Entry - remove chan EntryID - snapshot chan chan []Entry - running bool - logger Logger - runningMu sync.Mutex - location *time.Location - parser ScheduleParser - nextID EntryID - jobWaiter sync.WaitGroup -} - -// ScheduleParser is an interface for schedule spec parsers that return a Schedule -type ScheduleParser interface { - Parse(spec string) (Schedule, error) -} - -// Job is an interface for submitted cron jobs. -type Job interface { - Run() -} - -// Schedule describes a job's duty cycle. -type Schedule interface { - // Next returns the next activation time, later than the given time. - // Next is invoked initially, and then each time the job is run. - Next(time.Time) time.Time -} - -// EntryID identifies an entry within a Cron instance -type EntryID int - -// Entry consists of a schedule and the func to execute on that schedule. -type Entry struct { - // ID is the cron-assigned ID of this entry, which may be used to look up a - // snapshot or remove it. - ID EntryID - - // Schedule on which this job should be run. - Schedule Schedule - - // Next time the job will run, or the zero time if Cron has not been - // started or this entry's schedule is unsatisfiable - Next time.Time - - // Prev is the last time this job was run, or the zero time if never. - Prev time.Time - - // WrappedJob is the thing to run when the Schedule is activated. - WrappedJob Job - - // Job is the thing that was submitted to cron. - // It is kept around so that user code that needs to get at the job later, - // e.g. via Entries() can do so. - Job Job -} - -// Valid returns true if this is not the zero entry. -func (e Entry) Valid() bool { return e.ID != 0 } - -// byTime is a wrapper for sorting the entry array by time -// (with zero time at the end). -type byTime []*Entry - -func (s byTime) Len() int { return len(s) } -func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s byTime) Less(i, j int) bool { - // Two zero times should return false. - // Otherwise, zero is "greater" than any other time. - // (To sort it at the end of the list.) - if s[i].Next.IsZero() { - return false - } - if s[j].Next.IsZero() { - return true - } - return s[i].Next.Before(s[j].Next) -} - -// New returns a new Cron job runner, modified by the given options. -// -// Available Settings -// -// Time Zone -// Description: The time zone in which schedules are interpreted -// Default: time.Local -// -// Parser -// Description: Parser converts cron spec strings into cron.Schedules. -// Default: Accepts this spec: https://en.wikipedia.org/wiki/Cron -// -// Chain -// Description: Wrap submitted jobs to customize behavior. -// Default: A chain that recovers panics and logs them to stderr. -// -// See "cron.With*" to modify the default behavior. -func New(opts ...Option) *Cron { - c := &Cron{ - entries: nil, - chain: NewChain(), - add: make(chan *Entry), - stop: make(chan struct{}), - snapshot: make(chan chan []Entry), - remove: make(chan EntryID), - running: false, - runningMu: sync.Mutex{}, - logger: DefaultLogger, - location: time.Local, - parser: standardParser, - } - for _, opt := range opts { - opt(c) - } - return c -} - -// FuncJob is a wrapper that turns a func() into a cron.Job -type FuncJob func() - -func (f FuncJob) Run() { f() } - -// AddFunc adds a func to the Cron to be run on the given schedule. -// The spec is parsed using the time zone of this Cron instance as the default. -// An opaque ID is returned that can be used to later remove it. -func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error) { - return c.AddJob(spec, FuncJob(cmd)) -} - -// AddJob adds a Job to the Cron to be run on the given schedule. -// The spec is parsed using the time zone of this Cron instance as the default. -// An opaque ID is returned that can be used to later remove it. -func (c *Cron) AddJob(spec string, cmd Job) (EntryID, error) { - schedule, err := c.parser.Parse(spec) - if err != nil { - return 0, err - } - return c.Schedule(schedule, cmd), nil -} - -// Schedule adds a Job to the Cron to be run on the given schedule. -// The job is wrapped with the configured Chain. -func (c *Cron) Schedule(schedule Schedule, cmd Job) EntryID { - c.runningMu.Lock() - defer c.runningMu.Unlock() - c.nextID++ - entry := &Entry{ - ID: c.nextID, - Schedule: schedule, - WrappedJob: c.chain.Then(cmd), - Job: cmd, - } - if !c.running { - c.entries = append(c.entries, entry) - } else { - c.add <- entry - } - return entry.ID -} - -// Entries returns a snapshot of the cron entries. -func (c *Cron) Entries() []Entry { - c.runningMu.Lock() - defer c.runningMu.Unlock() - if c.running { - replyChan := make(chan []Entry, 1) - c.snapshot <- replyChan - return <-replyChan - } - return c.entrySnapshot() -} - -// Location gets the time zone location -func (c *Cron) Location() *time.Location { - return c.location -} - -// Entry returns a snapshot of the given entry, or nil if it couldn't be found. -func (c *Cron) Entry(id EntryID) Entry { - for _, entry := range c.Entries() { - if id == entry.ID { - return entry - } - } - return Entry{} -} - -// Remove an entry from being run in the future. -func (c *Cron) Remove(id EntryID) { - c.runningMu.Lock() - defer c.runningMu.Unlock() - if c.running { - c.remove <- id - } else { - c.removeEntry(id) - } -} - -// Start the cron scheduler in its own goroutine, or no-op if already started. -func (c *Cron) Start() { - c.runningMu.Lock() - defer c.runningMu.Unlock() - if c.running { - return - } - c.running = true - go c.run() -} - -// Run the cron scheduler, or no-op if already running. -func (c *Cron) Run() { - c.runningMu.Lock() - if c.running { - c.runningMu.Unlock() - return - } - c.running = true - c.runningMu.Unlock() - c.run() -} - -// run the scheduler.. this is private just due to the need to synchronize -// access to the 'running' state variable. -func (c *Cron) run() { - c.logger.Info("start") - - // Figure out the next activation times for each entry. - now := c.now() - for _, entry := range c.entries { - entry.Next = entry.Schedule.Next(now) - c.logger.Info("schedule", "now", now, "entry", entry.ID, "next", entry.Next) - } - - for { - // Determine the next entry to run. - sort.Sort(byTime(c.entries)) - - var timer *time.Timer - if len(c.entries) == 0 || c.entries[0].Next.IsZero() { - // If there are no entries yet, just sleep - it still handles new entries - // and stop requests. - timer = time.NewTimer(100000 * time.Hour) - } else { - timer = time.NewTimer(c.entries[0].Next.Sub(now)) - } - - for { - select { - case now = <-timer.C: - now = now.In(c.location) - c.logger.Info("wake", "now", now) - - // Run every entry whose next time was less than now - for _, e := range c.entries { - if e.Next.After(now) || e.Next.IsZero() { - break - } - c.startJob(e.WrappedJob) - e.Prev = e.Next - e.Next = e.Schedule.Next(now) - c.logger.Info("run", "now", now, "entry", e.ID, "next", e.Next) - } - - case newEntry := <-c.add: - timer.Stop() - now = c.now() - newEntry.Next = newEntry.Schedule.Next(now) - c.entries = append(c.entries, newEntry) - c.logger.Info("added", "now", now, "entry", newEntry.ID, "next", newEntry.Next) - - case replyChan := <-c.snapshot: - replyChan <- c.entrySnapshot() - continue - - case <-c.stop: - timer.Stop() - c.logger.Info("stop") - return - - case id := <-c.remove: - timer.Stop() - now = c.now() - c.removeEntry(id) - c.logger.Info("removed", "entry", id) - } - - break - } - } -} - -// startJob runs the given job in a new goroutine. -func (c *Cron) startJob(j Job) { - c.jobWaiter.Add(1) - go func() { - defer c.jobWaiter.Done() - j.Run() - }() -} - -// now returns current time in c location -func (c *Cron) now() time.Time { - return time.Now().In(c.location) -} - -// Stop stops the cron scheduler if it is running; otherwise it does nothing. -// A context is returned so the caller can wait for running jobs to complete. -func (c *Cron) Stop() context.Context { - c.runningMu.Lock() - defer c.runningMu.Unlock() - if c.running { - c.stop <- struct{}{} - c.running = false - } - ctx, cancel := context.WithCancel(context.Background()) - go func() { - c.jobWaiter.Wait() - cancel() - }() - return ctx -} - -// entrySnapshot returns a copy of the current cron entry list. -func (c *Cron) entrySnapshot() []Entry { - var entries = make([]Entry, len(c.entries)) - for i, e := range c.entries { - entries[i] = *e - } - return entries -} - -func (c *Cron) removeEntry(id EntryID) { - var entries []*Entry - for _, e := range c.entries { - if e.ID != id { - entries = append(entries, e) - } - } - c.entries = entries -} diff --git a/vendor/github.com/robfig/cron/v3/doc.go b/vendor/github.com/robfig/cron/v3/doc.go deleted file mode 100644 index fa5d08b4db..0000000000 --- a/vendor/github.com/robfig/cron/v3/doc.go +++ /dev/null @@ -1,231 +0,0 @@ -/* -Package cron implements a cron spec parser and job runner. - -Installation - -To download the specific tagged release, run: - - go get github.com/robfig/cron/v3@v3.0.0 - -Import it in your program as: - - import "github.com/robfig/cron/v3" - -It requires Go 1.11 or later due to usage of Go Modules. - -Usage - -Callers may register Funcs to be invoked on a given schedule. Cron will run -them in their own goroutines. - - c := cron.New() - c.AddFunc("30 * * * *", func() { fmt.Println("Every hour on the half hour") }) - c.AddFunc("30 3-6,20-23 * * *", func() { fmt.Println(".. in the range 3-6am, 8-11pm") }) - c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * *", func() { fmt.Println("Runs at 04:30 Tokyo time every day") }) - c.AddFunc("@hourly", func() { fmt.Println("Every hour, starting an hour from now") }) - c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty, starting an hour thirty from now") }) - c.Start() - .. - // Funcs are invoked in their own goroutine, asynchronously. - ... - // Funcs may also be added to a running Cron - c.AddFunc("@daily", func() { fmt.Println("Every day") }) - .. - // Inspect the cron job entries' next and previous run times. - inspect(c.Entries()) - .. - c.Stop() // Stop the scheduler (does not stop any jobs already running). - -CRON Expression Format - -A cron expression represents a set of times, using 5 space-separated fields. - - Field name | Mandatory? | Allowed values | Allowed special characters - ---------- | ---------- | -------------- | -------------------------- - Minutes | Yes | 0-59 | * / , - - Hours | Yes | 0-23 | * / , - - Day of month | Yes | 1-31 | * / , - ? - Month | Yes | 1-12 or JAN-DEC | * / , - - Day of week | Yes | 0-6 or SUN-SAT | * / , - ? - -Month and Day-of-week field values are case insensitive. "SUN", "Sun", and -"sun" are equally accepted. - -The specific interpretation of the format is based on the Cron Wikipedia page: -https://en.wikipedia.org/wiki/Cron - -Alternative Formats - -Alternative Cron expression formats support other fields like seconds. You can -implement that by creating a custom Parser as follows. - - cron.New( - cron.WithParser( - cron.NewParser( - cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor))) - -Since adding Seconds is the most common modification to the standard cron spec, -cron provides a builtin function to do that, which is equivalent to the custom -parser you saw earlier, except that its seconds field is REQUIRED: - - cron.New(cron.WithSeconds()) - -That emulates Quartz, the most popular alternative Cron schedule format: -http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/crontrigger.html - -Special Characters - -Asterisk ( * ) - -The asterisk indicates that the cron expression will match for all values of the -field; e.g., using an asterisk in the 5th field (month) would indicate every -month. - -Slash ( / ) - -Slashes are used to describe increments of ranges. For example 3-59/15 in the -1st field (minutes) would indicate the 3rd minute of the hour and every 15 -minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...", -that is, an increment over the largest possible range of the field. The form -"N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the -increment until the end of that specific range. It does not wrap around. - -Comma ( , ) - -Commas are used to separate items of a list. For example, using "MON,WED,FRI" in -the 5th field (day of week) would mean Mondays, Wednesdays and Fridays. - -Hyphen ( - ) - -Hyphens are used to define ranges. For example, 9-17 would indicate every -hour between 9am and 5pm inclusive. - -Question mark ( ? ) - -Question mark may be used instead of '*' for leaving either day-of-month or -day-of-week blank. - -Predefined schedules - -You may use one of several pre-defined schedules in place of a cron expression. - - Entry | Description | Equivalent To - ----- | ----------- | ------------- - @yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 1 1 * - @monthly | Run once a month, midnight, first of month | 0 0 1 * * - @weekly | Run once a week, midnight between Sat/Sun | 0 0 * * 0 - @daily (or @midnight) | Run once a day, midnight | 0 0 * * * - @hourly | Run once an hour, beginning of hour | 0 * * * * - -Intervals - -You may also schedule a job to execute at fixed intervals, starting at the time it's added -or cron is run. This is supported by formatting the cron spec like this: - - @every - -where "duration" is a string accepted by time.ParseDuration -(http://golang.org/pkg/time/#ParseDuration). - -For example, "@every 1h30m10s" would indicate a schedule that activates after -1 hour, 30 minutes, 10 seconds, and then every interval after that. - -Note: The interval does not take the job runtime into account. For example, -if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes, -it will have only 2 minutes of idle time between each run. - -Time zones - -By default, all interpretation and scheduling is done in the machine's local -time zone (time.Local). You can specify a different time zone on construction: - - cron.New( - cron.WithLocation(time.UTC)) - -Individual cron schedules may also override the time zone they are to be -interpreted in by providing an additional space-separated field at the beginning -of the cron spec, of the form "CRON_TZ=Asia/Tokyo". - -For example: - - # Runs at 6am in time.Local - cron.New().AddFunc("0 6 * * ?", ...) - - # Runs at 6am in America/New_York - nyc, _ := time.LoadLocation("America/New_York") - c := cron.New(cron.WithLocation(nyc)) - c.AddFunc("0 6 * * ?", ...) - - # Runs at 6am in Asia/Tokyo - cron.New().AddFunc("CRON_TZ=Asia/Tokyo 0 6 * * ?", ...) - - # Runs at 6am in Asia/Tokyo - c := cron.New(cron.WithLocation(nyc)) - c.SetLocation("America/New_York") - c.AddFunc("CRON_TZ=Asia/Tokyo 0 6 * * ?", ...) - -The prefix "TZ=(TIME ZONE)" is also supported for legacy compatibility. - -Be aware that jobs scheduled during daylight-savings leap-ahead transitions will -not be run! - -Job Wrappers - -A Cron runner may be configured with a chain of job wrappers to add -cross-cutting functionality to all submitted jobs. For example, they may be used -to achieve the following effects: - - - Recover any panics from jobs (activated by default) - - Delay a job's execution if the previous run hasn't completed yet - - Skip a job's execution if the previous run hasn't completed yet - - Log each job's invocations - -Install wrappers for all jobs added to a cron using the `cron.WithChain` option: - - cron.New(cron.WithChain( - cron.SkipIfStillRunning(logger), - )) - -Install wrappers for individual jobs by explicitly wrapping them: - - job = cron.NewChain( - cron.SkipIfStillRunning(logger), - ).Then(job) - -Thread safety - -Since the Cron service runs concurrently with the calling code, some amount of -care must be taken to ensure proper synchronization. - -All cron methods are designed to be correctly synchronized as long as the caller -ensures that invocations have a clear happens-before ordering between them. - -Logging - -Cron defines a Logger interface that is a subset of the one defined in -github.com/go-logr/logr. It has two logging levels (Info and Error), and -parameters are key/value pairs. This makes it possible for cron logging to plug -into structured logging systems. An adapter, [Verbose]PrintfLogger, is provided -to wrap the standard library *log.Logger. - -For additional insight into Cron operations, verbose logging may be activated -which will record job runs, scheduling decisions, and added or removed jobs. -Activate it with a one-off logger as follows: - - cron.New( - cron.WithLogger( - cron.VerbosePrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags)))) - - -Implementation - -Cron entries are stored in an array, sorted by their next activation time. Cron -sleeps until the next job is due to be run. - -Upon waking: - - it runs each entry that is active on that second - - it calculates the next run times for the jobs that were run - - it re-sorts the array of entries by next activation time. - - it goes to sleep until the soonest job. -*/ -package cron diff --git a/vendor/github.com/robfig/cron/v3/logger.go b/vendor/github.com/robfig/cron/v3/logger.go deleted file mode 100644 index b4efcc0535..0000000000 --- a/vendor/github.com/robfig/cron/v3/logger.go +++ /dev/null @@ -1,86 +0,0 @@ -package cron - -import ( - "io/ioutil" - "log" - "os" - "strings" - "time" -) - -// DefaultLogger is used by Cron if none is specified. -var DefaultLogger Logger = PrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags)) - -// DiscardLogger can be used by callers to discard all log messages. -var DiscardLogger Logger = PrintfLogger(log.New(ioutil.Discard, "", 0)) - -// Logger is the interface used in this package for logging, so that any backend -// can be plugged in. It is a subset of the github.com/go-logr/logr interface. -type Logger interface { - // Info logs routine messages about cron's operation. - Info(msg string, keysAndValues ...interface{}) - // Error logs an error condition. - Error(err error, msg string, keysAndValues ...interface{}) -} - -// PrintfLogger wraps a Printf-based logger (such as the standard library "log") -// into an implementation of the Logger interface which logs errors only. -func PrintfLogger(l interface{ Printf(string, ...interface{}) }) Logger { - return printfLogger{l, false} -} - -// VerbosePrintfLogger wraps a Printf-based logger (such as the standard library -// "log") into an implementation of the Logger interface which logs everything. -func VerbosePrintfLogger(l interface{ Printf(string, ...interface{}) }) Logger { - return printfLogger{l, true} -} - -type printfLogger struct { - logger interface{ Printf(string, ...interface{}) } - logInfo bool -} - -func (pl printfLogger) Info(msg string, keysAndValues ...interface{}) { - if pl.logInfo { - keysAndValues = formatTimes(keysAndValues) - pl.logger.Printf( - formatString(len(keysAndValues)), - append([]interface{}{msg}, keysAndValues...)...) - } -} - -func (pl printfLogger) Error(err error, msg string, keysAndValues ...interface{}) { - keysAndValues = formatTimes(keysAndValues) - pl.logger.Printf( - formatString(len(keysAndValues)+2), - append([]interface{}{msg, "error", err}, keysAndValues...)...) -} - -// formatString returns a logfmt-like format string for the number of -// key/values. -func formatString(numKeysAndValues int) string { - var sb strings.Builder - sb.WriteString("%s") - if numKeysAndValues > 0 { - sb.WriteString(", ") - } - for i := 0; i < numKeysAndValues/2; i++ { - if i > 0 { - sb.WriteString(", ") - } - sb.WriteString("%v=%v") - } - return sb.String() -} - -// formatTimes formats any time.Time values as RFC3339. -func formatTimes(keysAndValues []interface{}) []interface{} { - var formattedArgs []interface{} - for _, arg := range keysAndValues { - if t, ok := arg.(time.Time); ok { - arg = t.Format(time.RFC3339) - } - formattedArgs = append(formattedArgs, arg) - } - return formattedArgs -} diff --git a/vendor/github.com/robfig/cron/v3/option.go b/vendor/github.com/robfig/cron/v3/option.go deleted file mode 100644 index 09e4278e77..0000000000 --- a/vendor/github.com/robfig/cron/v3/option.go +++ /dev/null @@ -1,45 +0,0 @@ -package cron - -import ( - "time" -) - -// Option represents a modification to the default behavior of a Cron. -type Option func(*Cron) - -// WithLocation overrides the timezone of the cron instance. -func WithLocation(loc *time.Location) Option { - return func(c *Cron) { - c.location = loc - } -} - -// WithSeconds overrides the parser used for interpreting job schedules to -// include a seconds field as the first one. -func WithSeconds() Option { - return WithParser(NewParser( - Second | Minute | Hour | Dom | Month | Dow | Descriptor, - )) -} - -// WithParser overrides the parser used for interpreting job schedules. -func WithParser(p ScheduleParser) Option { - return func(c *Cron) { - c.parser = p - } -} - -// WithChain specifies Job wrappers to apply to all jobs added to this cron. -// Refer to the Chain* functions in this package for provided wrappers. -func WithChain(wrappers ...JobWrapper) Option { - return func(c *Cron) { - c.chain = NewChain(wrappers...) - } -} - -// WithLogger uses the provided logger. -func WithLogger(logger Logger) Option { - return func(c *Cron) { - c.logger = logger - } -} diff --git a/vendor/github.com/robfig/cron/v3/parser.go b/vendor/github.com/robfig/cron/v3/parser.go deleted file mode 100644 index 3cf8879f7e..0000000000 --- a/vendor/github.com/robfig/cron/v3/parser.go +++ /dev/null @@ -1,434 +0,0 @@ -package cron - -import ( - "fmt" - "math" - "strconv" - "strings" - "time" -) - -// Configuration options for creating a parser. Most options specify which -// fields should be included, while others enable features. If a field is not -// included the parser will assume a default value. These options do not change -// the order fields are parse in. -type ParseOption int - -const ( - Second ParseOption = 1 << iota // Seconds field, default 0 - SecondOptional // Optional seconds field, default 0 - Minute // Minutes field, default 0 - Hour // Hours field, default 0 - Dom // Day of month field, default * - Month // Month field, default * - Dow // Day of week field, default * - DowOptional // Optional day of week field, default * - Descriptor // Allow descriptors such as @monthly, @weekly, etc. -) - -var places = []ParseOption{ - Second, - Minute, - Hour, - Dom, - Month, - Dow, -} - -var defaults = []string{ - "0", - "0", - "0", - "*", - "*", - "*", -} - -// A custom Parser that can be configured. -type Parser struct { - options ParseOption -} - -// NewParser creates a Parser with custom options. -// -// It panics if more than one Optional is given, since it would be impossible to -// correctly infer which optional is provided or missing in general. -// -// Examples -// -// // Standard parser without descriptors -// specParser := NewParser(Minute | Hour | Dom | Month | Dow) -// sched, err := specParser.Parse("0 0 15 */3 *") -// -// // Same as above, just excludes time fields -// subsParser := NewParser(Dom | Month | Dow) -// sched, err := specParser.Parse("15 */3 *") -// -// // Same as above, just makes Dow optional -// subsParser := NewParser(Dom | Month | DowOptional) -// sched, err := specParser.Parse("15 */3") -// -func NewParser(options ParseOption) Parser { - optionals := 0 - if options&DowOptional > 0 { - optionals++ - } - if options&SecondOptional > 0 { - optionals++ - } - if optionals > 1 { - panic("multiple optionals may not be configured") - } - return Parser{options} -} - -// Parse returns a new crontab schedule representing the given spec. -// It returns a descriptive error if the spec is not valid. -// It accepts crontab specs and features configured by NewParser. -func (p Parser) Parse(spec string) (Schedule, error) { - if len(spec) == 0 { - return nil, fmt.Errorf("empty spec string") - } - - // Extract timezone if present - var loc = time.Local - if strings.HasPrefix(spec, "TZ=") || strings.HasPrefix(spec, "CRON_TZ=") { - var err error - i := strings.Index(spec, " ") - eq := strings.Index(spec, "=") - if loc, err = time.LoadLocation(spec[eq+1 : i]); err != nil { - return nil, fmt.Errorf("provided bad location %s: %v", spec[eq+1:i], err) - } - spec = strings.TrimSpace(spec[i:]) - } - - // Handle named schedules (descriptors), if configured - if strings.HasPrefix(spec, "@") { - if p.options&Descriptor == 0 { - return nil, fmt.Errorf("parser does not accept descriptors: %v", spec) - } - return parseDescriptor(spec, loc) - } - - // Split on whitespace. - fields := strings.Fields(spec) - - // Validate & fill in any omitted or optional fields - var err error - fields, err = normalizeFields(fields, p.options) - if err != nil { - return nil, err - } - - field := func(field string, r bounds) uint64 { - if err != nil { - return 0 - } - var bits uint64 - bits, err = getField(field, r) - return bits - } - - var ( - second = field(fields[0], seconds) - minute = field(fields[1], minutes) - hour = field(fields[2], hours) - dayofmonth = field(fields[3], dom) - month = field(fields[4], months) - dayofweek = field(fields[5], dow) - ) - if err != nil { - return nil, err - } - - return &SpecSchedule{ - Second: second, - Minute: minute, - Hour: hour, - Dom: dayofmonth, - Month: month, - Dow: dayofweek, - Location: loc, - }, nil -} - -// normalizeFields takes a subset set of the time fields and returns the full set -// with defaults (zeroes) populated for unset fields. -// -// As part of performing this function, it also validates that the provided -// fields are compatible with the configured options. -func normalizeFields(fields []string, options ParseOption) ([]string, error) { - // Validate optionals & add their field to options - optionals := 0 - if options&SecondOptional > 0 { - options |= Second - optionals++ - } - if options&DowOptional > 0 { - options |= Dow - optionals++ - } - if optionals > 1 { - return nil, fmt.Errorf("multiple optionals may not be configured") - } - - // Figure out how many fields we need - max := 0 - for _, place := range places { - if options&place > 0 { - max++ - } - } - min := max - optionals - - // Validate number of fields - if count := len(fields); count < min || count > max { - if min == max { - return nil, fmt.Errorf("expected exactly %d fields, found %d: %s", min, count, fields) - } - return nil, fmt.Errorf("expected %d to %d fields, found %d: %s", min, max, count, fields) - } - - // Populate the optional field if not provided - if min < max && len(fields) == min { - switch { - case options&DowOptional > 0: - fields = append(fields, defaults[5]) // TODO: improve access to default - case options&SecondOptional > 0: - fields = append([]string{defaults[0]}, fields...) - default: - return nil, fmt.Errorf("unknown optional field") - } - } - - // Populate all fields not part of options with their defaults - n := 0 - expandedFields := make([]string, len(places)) - copy(expandedFields, defaults) - for i, place := range places { - if options&place > 0 { - expandedFields[i] = fields[n] - n++ - } - } - return expandedFields, nil -} - -var standardParser = NewParser( - Minute | Hour | Dom | Month | Dow | Descriptor, -) - -// ParseStandard returns a new crontab schedule representing the given -// standardSpec (https://en.wikipedia.org/wiki/Cron). It requires 5 entries -// representing: minute, hour, day of month, month and day of week, in that -// order. It returns a descriptive error if the spec is not valid. -// -// It accepts -// - Standard crontab specs, e.g. "* * * * ?" -// - Descriptors, e.g. "@midnight", "@every 1h30m" -func ParseStandard(standardSpec string) (Schedule, error) { - return standardParser.Parse(standardSpec) -} - -// getField returns an Int with the bits set representing all of the times that -// the field represents or error parsing field value. A "field" is a comma-separated -// list of "ranges". -func getField(field string, r bounds) (uint64, error) { - var bits uint64 - ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' }) - for _, expr := range ranges { - bit, err := getRange(expr, r) - if err != nil { - return bits, err - } - bits |= bit - } - return bits, nil -} - -// getRange returns the bits indicated by the given expression: -// number | number "-" number [ "/" number ] -// or error parsing range. -func getRange(expr string, r bounds) (uint64, error) { - var ( - start, end, step uint - rangeAndStep = strings.Split(expr, "/") - lowAndHigh = strings.Split(rangeAndStep[0], "-") - singleDigit = len(lowAndHigh) == 1 - err error - ) - - var extra uint64 - if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" { - start = r.min - end = r.max - extra = starBit - } else { - start, err = parseIntOrName(lowAndHigh[0], r.names) - if err != nil { - return 0, err - } - switch len(lowAndHigh) { - case 1: - end = start - case 2: - end, err = parseIntOrName(lowAndHigh[1], r.names) - if err != nil { - return 0, err - } - default: - return 0, fmt.Errorf("too many hyphens: %s", expr) - } - } - - switch len(rangeAndStep) { - case 1: - step = 1 - case 2: - step, err = mustParseInt(rangeAndStep[1]) - if err != nil { - return 0, err - } - - // Special handling: "N/step" means "N-max/step". - if singleDigit { - end = r.max - } - if step > 1 { - extra = 0 - } - default: - return 0, fmt.Errorf("too many slashes: %s", expr) - } - - if start < r.min { - return 0, fmt.Errorf("beginning of range (%d) below minimum (%d): %s", start, r.min, expr) - } - if end > r.max { - return 0, fmt.Errorf("end of range (%d) above maximum (%d): %s", end, r.max, expr) - } - if start > end { - return 0, fmt.Errorf("beginning of range (%d) beyond end of range (%d): %s", start, end, expr) - } - if step == 0 { - return 0, fmt.Errorf("step of range should be a positive number: %s", expr) - } - - return getBits(start, end, step) | extra, nil -} - -// parseIntOrName returns the (possibly-named) integer contained in expr. -func parseIntOrName(expr string, names map[string]uint) (uint, error) { - if names != nil { - if namedInt, ok := names[strings.ToLower(expr)]; ok { - return namedInt, nil - } - } - return mustParseInt(expr) -} - -// mustParseInt parses the given expression as an int or returns an error. -func mustParseInt(expr string) (uint, error) { - num, err := strconv.Atoi(expr) - if err != nil { - return 0, fmt.Errorf("failed to parse int from %s: %s", expr, err) - } - if num < 0 { - return 0, fmt.Errorf("negative number (%d) not allowed: %s", num, expr) - } - - return uint(num), nil -} - -// getBits sets all bits in the range [min, max], modulo the given step size. -func getBits(min, max, step uint) uint64 { - var bits uint64 - - // If step is 1, use shifts. - if step == 1 { - return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min) - } - - // Else, use a simple loop. - for i := min; i <= max; i += step { - bits |= 1 << i - } - return bits -} - -// all returns all bits within the given bounds. (plus the star bit) -func all(r bounds) uint64 { - return getBits(r.min, r.max, 1) | starBit -} - -// parseDescriptor returns a predefined schedule for the expression, or error if none matches. -func parseDescriptor(descriptor string, loc *time.Location) (Schedule, error) { - switch descriptor { - case "@yearly", "@annually": - return &SpecSchedule{ - Second: 1 << seconds.min, - Minute: 1 << minutes.min, - Hour: 1 << hours.min, - Dom: 1 << dom.min, - Month: 1 << months.min, - Dow: all(dow), - Location: loc, - }, nil - - case "@monthly": - return &SpecSchedule{ - Second: 1 << seconds.min, - Minute: 1 << minutes.min, - Hour: 1 << hours.min, - Dom: 1 << dom.min, - Month: all(months), - Dow: all(dow), - Location: loc, - }, nil - - case "@weekly": - return &SpecSchedule{ - Second: 1 << seconds.min, - Minute: 1 << minutes.min, - Hour: 1 << hours.min, - Dom: all(dom), - Month: all(months), - Dow: 1 << dow.min, - Location: loc, - }, nil - - case "@daily", "@midnight": - return &SpecSchedule{ - Second: 1 << seconds.min, - Minute: 1 << minutes.min, - Hour: 1 << hours.min, - Dom: all(dom), - Month: all(months), - Dow: all(dow), - Location: loc, - }, nil - - case "@hourly": - return &SpecSchedule{ - Second: 1 << seconds.min, - Minute: 1 << minutes.min, - Hour: all(hours), - Dom: all(dom), - Month: all(months), - Dow: all(dow), - Location: loc, - }, nil - - } - - const every = "@every " - if strings.HasPrefix(descriptor, every) { - duration, err := time.ParseDuration(descriptor[len(every):]) - if err != nil { - return nil, fmt.Errorf("failed to parse duration %s: %s", descriptor, err) - } - return Every(duration), nil - } - - return nil, fmt.Errorf("unrecognized descriptor: %s", descriptor) -} diff --git a/vendor/github.com/robfig/cron/v3/spec.go b/vendor/github.com/robfig/cron/v3/spec.go deleted file mode 100644 index fa1e241e5f..0000000000 --- a/vendor/github.com/robfig/cron/v3/spec.go +++ /dev/null @@ -1,188 +0,0 @@ -package cron - -import "time" - -// SpecSchedule specifies a duty cycle (to the second granularity), based on a -// traditional crontab specification. It is computed initially and stored as bit sets. -type SpecSchedule struct { - Second, Minute, Hour, Dom, Month, Dow uint64 - - // Override location for this schedule. - Location *time.Location -} - -// bounds provides a range of acceptable values (plus a map of name to value). -type bounds struct { - min, max uint - names map[string]uint -} - -// The bounds for each field. -var ( - seconds = bounds{0, 59, nil} - minutes = bounds{0, 59, nil} - hours = bounds{0, 23, nil} - dom = bounds{1, 31, nil} - months = bounds{1, 12, map[string]uint{ - "jan": 1, - "feb": 2, - "mar": 3, - "apr": 4, - "may": 5, - "jun": 6, - "jul": 7, - "aug": 8, - "sep": 9, - "oct": 10, - "nov": 11, - "dec": 12, - }} - dow = bounds{0, 6, map[string]uint{ - "sun": 0, - "mon": 1, - "tue": 2, - "wed": 3, - "thu": 4, - "fri": 5, - "sat": 6, - }} -) - -const ( - // Set the top bit if a star was included in the expression. - starBit = 1 << 63 -) - -// Next returns the next time this schedule is activated, greater than the given -// time. If no time can be found to satisfy the schedule, return the zero time. -func (s *SpecSchedule) Next(t time.Time) time.Time { - // General approach - // - // For Month, Day, Hour, Minute, Second: - // Check if the time value matches. If yes, continue to the next field. - // If the field doesn't match the schedule, then increment the field until it matches. - // While incrementing the field, a wrap-around brings it back to the beginning - // of the field list (since it is necessary to re-verify previous field - // values) - - // Convert the given time into the schedule's timezone, if one is specified. - // Save the original timezone so we can convert back after we find a time. - // Note that schedules without a time zone specified (time.Local) are treated - // as local to the time provided. - origLocation := t.Location() - loc := s.Location - if loc == time.Local { - loc = t.Location() - } - if s.Location != time.Local { - t = t.In(s.Location) - } - - // Start at the earliest possible time (the upcoming second). - t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond) - - // This flag indicates whether a field has been incremented. - added := false - - // If no time is found within five years, return zero. - yearLimit := t.Year() + 5 - -WRAP: - if t.Year() > yearLimit { - return time.Time{} - } - - // Find the first applicable month. - // If it's this month, then do nothing. - for 1< 12 { - t = t.Add(time.Duration(24-t.Hour()) * time.Hour) - } else { - t = t.Add(time.Duration(-t.Hour()) * time.Hour) - } - } - - if t.Day() == 1 { - goto WRAP - } - } - - for 1< 0 - dowMatch bool = 1< 0 - ) - if s.Dom&starBit > 0 || s.Dow&starBit > 0 { - return domMatch && dowMatch - } - return domMatch || dowMatch -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 623b074f6a..b7283f72fc 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -519,9 +519,6 @@ github.com/rivo/uniseg # github.com/robfig/cron v1.2.0 ## explicit github.com/robfig/cron -# github.com/robfig/cron/v3 v3.0.1 -## explicit; go 1.12 -github.com/robfig/cron/v3 # github.com/sirupsen/logrus v1.9.3 ## explicit; go 1.13 github.com/sirupsen/logrus