From 6ed1280869bf42d1901ca09a5cc6b316a1cd8394 Mon Sep 17 00:00:00 2001 From: Gareth Date: Mon, 26 Aug 2024 22:35:06 -0700 Subject: [PATCH] feat: implement scheduling relative to last task execution (#439) --- gen/go/v1/config.pb.go | 277 +++++++------ gen/go/v1/operations.pb.go | 138 +++---- internal/api/backresthandler_test.go | 1 + internal/config/config_test.go | 1 + .../config/migrations/001prunepolicy_test.go | 1 + .../migrations/003relativescheduling.go | 42 ++ .../migrations/003relativescheduling_test.go | 128 ++++++ internal/config/migrations/migrations.go | 1 + .../validationutil/validationutil_test.go | 1 + .../oplog/storetests/storecontract_test.go | 10 + internal/orchestrator/orchestrator.go | 5 +- internal/orchestrator/repo/repo_test.go | 4 + internal/orchestrator/taskrunnerimpl.go | 5 - .../orchestrator/tasks/scheduling_test.go | 372 ++++++++++++++++++ internal/orchestrator/tasks/task.go | 68 +++- internal/orchestrator/tasks/taskbackup.go | 24 +- internal/orchestrator/tasks/taskcheck.go | 9 +- internal/orchestrator/tasks/taskprune.go | 9 +- internal/protoutil/schedule.go | 32 +- pkg/restic/restic_test.go | 3 + proto/v1/config.proto | 3 + webui/gen/ts/v1/config_pb.ts | 27 ++ webui/gen/ts/v1/operations_pb.ts | 8 - webui/src/components/ScheduleFormItem.tsx | 173 ++++++-- webui/src/state/flowdisplayaggregator.ts | 7 +- webui/src/views/AddRepoModal.tsx | 20 +- 26 files changed, 1120 insertions(+), 249 deletions(-) create mode 100644 internal/config/migrations/003relativescheduling.go create mode 100644 internal/config/migrations/003relativescheduling_test.go create mode 100644 internal/orchestrator/tasks/scheduling_test.go diff --git a/gen/go/v1/config.pb.go b/gen/go/v1/config.pb.go index 38235a42..41bda45e 100644 --- a/gen/go/v1/config.pb.go +++ b/gen/go/v1/config.pb.go @@ -1101,6 +1101,9 @@ type Schedule struct { // *Schedule_Cron // *Schedule_MaxFrequencyDays // *Schedule_MaxFrequencyHours + // *Schedule_CronSinceLastRun + // *Schedule_MinHoursSinceLastRun + // *Schedule_MinDaysSinceLastRun Schedule isSchedule_Schedule `protobuf_oneof:"schedule"` } @@ -1171,6 +1174,27 @@ func (x *Schedule) GetMaxFrequencyHours() int32 { return 0 } +func (x *Schedule) GetCronSinceLastRun() string { + if x, ok := x.GetSchedule().(*Schedule_CronSinceLastRun); ok { + return x.CronSinceLastRun + } + return "" +} + +func (x *Schedule) GetMinHoursSinceLastRun() int32 { + if x, ok := x.GetSchedule().(*Schedule_MinHoursSinceLastRun); ok { + return x.MinHoursSinceLastRun + } + return 0 +} + +func (x *Schedule) GetMinDaysSinceLastRun() int32 { + if x, ok := x.GetSchedule().(*Schedule_MinDaysSinceLastRun); ok { + return x.MinDaysSinceLastRun + } + return 0 +} + type isSchedule_Schedule interface { isSchedule_Schedule() } @@ -1191,6 +1215,18 @@ type Schedule_MaxFrequencyHours struct { MaxFrequencyHours int32 `protobuf:"varint,4,opt,name=maxFrequencyHours,proto3,oneof"` // max frequency of runs in hours. } +type Schedule_CronSinceLastRun struct { + CronSinceLastRun string `protobuf:"bytes,100,opt,name=cronSinceLastRun,proto3,oneof"` // cron expression to run since the last run. +} + +type Schedule_MinHoursSinceLastRun struct { + MinHoursSinceLastRun int32 `protobuf:"varint,101,opt,name=minHoursSinceLastRun,proto3,oneof"` // max hours since the last run. +} + +type Schedule_MinDaysSinceLastRun struct { + MinDaysSinceLastRun int32 `protobuf:"varint,102,opt,name=minDaysSinceLastRun,proto3,oneof"` // max days since the last run. +} + func (*Schedule_Disabled) isSchedule_Schedule() {} func (*Schedule_Cron) isSchedule_Schedule() {} @@ -1199,6 +1235,12 @@ func (*Schedule_MaxFrequencyDays) isSchedule_Schedule() {} func (*Schedule_MaxFrequencyHours) isSchedule_Schedule() {} +func (*Schedule_CronSinceLastRun) isSchedule_Schedule() {} + +func (*Schedule_MinHoursSinceLastRun) isSchedule_Schedule() {} + +func (*Schedule_MinDaysSinceLastRun) isSchedule_Schedule() {} + type Hook struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2112,7 +2154,7 @@ var file_v1_config_proto_rawDesc = []byte{ 0x61, 0x74, 0x61, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x65, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x65, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x15, 0x72, 0x65, 0x61, 0x64, 0x44, 0x61, 0x74, 0x61, 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, 0x50, 0x65, 0x72, 0x63, 0x65, 0x6e, - 0x74, 0x42, 0x06, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0xa8, 0x01, 0x0a, 0x08, 0x53, 0x63, + 0x74, 0x42, 0x06, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0xc0, 0x02, 0x0a, 0x08, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x1c, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, @@ -2122,118 +2164,128 @@ var file_v1_config_proto_rawDesc = []byte{ 0x65, 0x6e, 0x63, 0x79, 0x44, 0x61, 0x79, 0x73, 0x12, 0x2e, 0x0a, 0x11, 0x6d, 0x61, 0x78, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x75, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x11, 0x6d, 0x61, 0x78, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x75, 0x72, 0x73, 0x42, 0x0a, 0x0a, 0x08, 0x73, 0x63, 0x68, 0x65, - 0x64, 0x75, 0x6c, 0x65, 0x22, 0x98, 0x0c, 0x0a, 0x04, 0x48, 0x6f, 0x6f, 0x6b, 0x12, 0x32, 0x0a, - 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0e, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x43, 0x6f, 0x6e, 0x64, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x4f, 0x6e, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x07, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x39, - 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, - 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x39, 0x0a, 0x0e, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x18, 0x65, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x57, 0x65, 0x62, 0x68, - 0x6f, 0x6f, 0x6b, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x65, 0x62, - 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x39, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, - 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x66, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, - 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x48, 0x00, - 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x12, - 0x36, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x67, 0x6f, 0x74, 0x69, 0x66, 0x79, - 0x18, 0x67, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, - 0x2e, 0x47, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x48, 0x00, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x47, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x33, 0x0a, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x73, 0x6c, 0x61, 0x63, 0x6b, 0x18, 0x68, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, - 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x48, 0x00, 0x52, - 0x0b, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x12, 0x3c, 0x0a, 0x0f, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x18, - 0x69, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, - 0x53, 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x48, 0x00, 0x52, 0x0e, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x1a, 0x23, 0x0a, 0x07, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x1a, - 0xa1, 0x01, 0x0a, 0x07, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x77, - 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x2f, 0x0a, 0x06, - 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x76, - 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x2e, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1a, 0x0a, - 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x22, 0x28, 0x0a, 0x06, 0x4d, 0x65, 0x74, - 0x68, 0x6f, 0x64, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, - 0x12, 0x07, 0x0a, 0x03, 0x47, 0x45, 0x54, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f, 0x53, - 0x54, 0x10, 0x02, 0x1a, 0x46, 0x0a, 0x07, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x12, - 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x1a, 0x7c, 0x0a, 0x06, 0x47, - 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x75, 0x72, - 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x55, 0x72, 0x6c, - 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, - 0x74, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x5f, 0x74, 0x65, 0x6d, 0x70, - 0x6c, 0x61, 0x74, 0x65, 0x18, 0x65, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x69, 0x74, 0x6c, - 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x1a, 0x44, 0x0a, 0x05, 0x53, 0x6c, 0x61, - 0x63, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, 0x72, - 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, - 0x55, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x1a, - 0x49, 0x0a, 0x08, 0x53, 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x73, - 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x73, 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x1a, - 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x22, 0xfc, 0x02, 0x0a, 0x09, 0x43, - 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4e, 0x44, - 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, - 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x4e, 0x59, - 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, 0x4e, 0x44, + 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x75, 0x72, 0x73, 0x12, 0x2c, 0x0a, 0x10, 0x63, 0x72, 0x6f, 0x6e, + 0x53, 0x69, 0x6e, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x52, 0x75, 0x6e, 0x18, 0x64, 0x20, 0x01, + 0x28, 0x09, 0x48, 0x00, 0x52, 0x10, 0x63, 0x72, 0x6f, 0x6e, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x4c, + 0x61, 0x73, 0x74, 0x52, 0x75, 0x6e, 0x12, 0x34, 0x0a, 0x14, 0x6d, 0x69, 0x6e, 0x48, 0x6f, 0x75, + 0x72, 0x73, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x52, 0x75, 0x6e, 0x18, 0x65, + 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x14, 0x6d, 0x69, 0x6e, 0x48, 0x6f, 0x75, 0x72, 0x73, + 0x53, 0x69, 0x6e, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x52, 0x75, 0x6e, 0x12, 0x32, 0x0a, 0x13, + 0x6d, 0x69, 0x6e, 0x44, 0x61, 0x79, 0x73, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, + 0x52, 0x75, 0x6e, 0x18, 0x66, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x13, 0x6d, 0x69, 0x6e, + 0x44, 0x61, 0x79, 0x73, 0x53, 0x69, 0x6e, 0x63, 0x65, 0x4c, 0x61, 0x73, 0x74, 0x52, 0x75, 0x6e, + 0x42, 0x0a, 0x0a, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x22, 0x98, 0x0c, 0x0a, + 0x04, 0x48, 0x6f, 0x6f, 0x6b, 0x12, 0x32, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x48, + 0x6f, 0x6f, 0x6b, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x63, + 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x6e, 0x5f, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x76, 0x31, + 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x4f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x07, 0x6f, + 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x39, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x12, 0x39, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x77, 0x65, 0x62, 0x68, + 0x6f, 0x6f, 0x6b, 0x18, 0x65, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x48, + 0x6f, 0x6f, 0x6b, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x48, 0x00, 0x52, 0x0d, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x39, 0x0a, 0x0e, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x66, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x36, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x67, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x18, 0x67, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, + 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x47, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x48, + 0x00, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x47, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, + 0x33, 0x0a, 0x0c, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x6c, 0x61, 0x63, 0x6b, 0x18, + 0x68, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, + 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x48, 0x00, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, + 0x6c, 0x61, 0x63, 0x6b, 0x12, 0x3c, 0x0a, 0x0f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, + 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x18, 0x69, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, + 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x53, 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, + 0x48, 0x00, 0x52, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x68, 0x6f, 0x75, 0x74, 0x72, + 0x72, 0x72, 0x1a, 0x23, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x18, 0x0a, + 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x1a, 0xa1, 0x01, 0x0a, 0x07, 0x57, 0x65, 0x62, 0x68, + 0x6f, 0x6f, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, + 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, + 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x2f, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x57, + 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x06, 0x6d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x22, 0x28, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x0b, 0x0a, 0x07, 0x55, + 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x47, 0x45, 0x54, 0x10, + 0x01, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f, 0x53, 0x54, 0x10, 0x02, 0x1a, 0x46, 0x0a, 0x07, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, + 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x65, 0x62, + 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x1a, 0x7c, 0x0a, 0x06, 0x47, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x19, 0x0a, + 0x08, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x62, 0x61, 0x73, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1a, + 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x65, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x1a, 0x44, 0x0a, 0x05, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x65, + 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x74, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x1a, 0x49, 0x0a, 0x08, 0x53, 0x68, 0x6f, 0x75, 0x74, + 0x72, 0x72, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x5f, + 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x68, 0x6f, 0x75, 0x74, + 0x72, 0x72, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x22, 0xfc, 0x02, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, + 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4e, 0x44, 0x49, + 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x4e, 0x59, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, + 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, + 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x02, 0x12, 0x1a, + 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, + 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x45, 0x4e, 0x44, 0x10, 0x03, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, + 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, + 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, 0x44, + 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x57, + 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x53, - 0x54, 0x41, 0x52, 0x54, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, - 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x45, 0x4e, 0x44, - 0x10, 0x03, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, - 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, - 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, - 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x05, - 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, - 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x06, - 0x12, 0x19, 0x0a, 0x15, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x52, - 0x55, 0x4e, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x64, 0x12, 0x19, 0x0a, 0x15, 0x43, - 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x55, 0x4e, 0x45, 0x5f, 0x45, - 0x52, 0x52, 0x4f, 0x52, 0x10, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, - 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x55, 0x4e, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, - 0x53, 0x10, 0x66, 0x12, 0x1a, 0x0a, 0x15, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, - 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0xc8, 0x01, 0x12, - 0x1a, 0x0a, 0x15, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x43, 0x48, 0x45, - 0x43, 0x4b, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0xc9, 0x01, 0x12, 0x1c, 0x0a, 0x17, 0x43, + 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x06, 0x12, 0x19, 0x0a, 0x15, 0x43, 0x4f, 0x4e, 0x44, + 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x55, 0x4e, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, + 0x54, 0x10, 0x64, 0x12, 0x19, 0x0a, 0x15, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, + 0x5f, 0x50, 0x52, 0x55, 0x4e, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x65, 0x12, 0x1b, + 0x0a, 0x17, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x55, 0x4e, + 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x66, 0x12, 0x1a, 0x0a, 0x15, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x53, - 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0xca, 0x01, 0x22, 0xa9, 0x01, 0x0a, 0x07, 0x4f, 0x6e, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, - 0x52, 0x5f, 0x49, 0x47, 0x4e, 0x4f, 0x52, 0x45, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x4e, - 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x10, 0x01, 0x12, - 0x12, 0x0a, 0x0e, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x46, 0x41, 0x54, 0x41, - 0x4c, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, - 0x52, 0x45, 0x54, 0x52, 0x59, 0x5f, 0x31, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x10, 0x64, 0x12, - 0x1c, 0x0a, 0x18, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x54, 0x52, - 0x59, 0x5f, 0x31, 0x30, 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x53, 0x10, 0x65, 0x12, 0x26, 0x0a, - 0x22, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x54, 0x52, 0x59, 0x5f, - 0x45, 0x58, 0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x42, 0x41, 0x43, 0x4b, - 0x4f, 0x46, 0x46, 0x10, 0x67, 0x42, 0x08, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, - 0x42, 0x0a, 0x04, 0x41, 0x75, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x12, 0x1e, 0x0a, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, - 0x65, 0x72, 0x73, 0x22, 0x51, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x29, 0x0a, 0x0f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x62, 0x63, 0x72, 0x79, - 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0e, 0x70, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, 0x42, 0x63, 0x72, 0x79, 0x70, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x70, 0x61, - 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, - 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, - 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x54, 0x41, 0x52, 0x54, 0x10, 0xc8, 0x01, 0x12, 0x1a, 0x0a, 0x15, 0x43, 0x4f, 0x4e, 0x44, 0x49, + 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x10, 0xc9, 0x01, 0x12, 0x1c, 0x0a, 0x17, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, + 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0xca, + 0x01, 0x22, 0xa9, 0x01, 0x0a, 0x07, 0x4f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x13, 0x0a, + 0x0f, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x47, 0x4e, 0x4f, 0x52, 0x45, + 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, + 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x4f, 0x4e, 0x5f, 0x45, 0x52, + 0x52, 0x4f, 0x52, 0x5f, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x4f, + 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x54, 0x52, 0x59, 0x5f, 0x31, 0x4d, + 0x49, 0x4e, 0x55, 0x54, 0x45, 0x10, 0x64, 0x12, 0x1c, 0x0a, 0x18, 0x4f, 0x4e, 0x5f, 0x45, 0x52, + 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x54, 0x52, 0x59, 0x5f, 0x31, 0x30, 0x4d, 0x49, 0x4e, 0x55, + 0x54, 0x45, 0x53, 0x10, 0x65, 0x12, 0x26, 0x0a, 0x22, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x5f, 0x52, 0x45, 0x54, 0x52, 0x59, 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54, + 0x49, 0x41, 0x4c, 0x5f, 0x42, 0x41, 0x43, 0x4b, 0x4f, 0x46, 0x46, 0x10, 0x67, 0x42, 0x08, 0x0a, + 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x42, 0x0a, 0x04, 0x41, 0x75, 0x74, 0x68, 0x12, + 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1e, 0x0a, 0x05, 0x75, + 0x73, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x22, 0x51, 0x0a, 0x04, 0x55, + 0x73, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x0f, 0x70, 0x61, 0x73, 0x73, 0x77, + 0x6f, 0x72, 0x64, 0x5f, 0x62, 0x63, 0x72, 0x79, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x00, 0x52, 0x0e, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x42, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x42, 0x2c, + 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, + 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, + 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2572,6 +2624,9 @@ func file_v1_config_proto_init() { (*Schedule_Cron)(nil), (*Schedule_MaxFrequencyDays)(nil), (*Schedule_MaxFrequencyHours)(nil), + (*Schedule_CronSinceLastRun)(nil), + (*Schedule_MinHoursSinceLastRun)(nil), + (*Schedule_MinDaysSinceLastRun)(nil), } file_v1_config_proto_msgTypes[9].OneofWrappers = []interface{}{ (*Hook_ActionCommand)(nil), diff --git a/gen/go/v1/operations.pb.go b/gen/go/v1/operations.pb.go index 2c80d54a..1941e2f7 100644 --- a/gen/go/v1/operations.pb.go +++ b/gen/go/v1/operations.pb.go @@ -616,9 +616,8 @@ type OperationIndexSnapshot struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Snapshot *ResticSnapshot `protobuf:"bytes,2,opt,name=snapshot,proto3" json:"snapshot,omitempty"` // the snapshot that was indexed. - Forgot bool `protobuf:"varint,3,opt,name=forgot,proto3" json:"forgot,omitempty"` // tracks whether this snapshot is forgotten yet. - ForgotByOp int64 `protobuf:"varint,4,opt,name=forgot_by_op,json=forgotByOp,proto3" json:"forgot_by_op,omitempty"` // ID of a forget operation that removed this snapshot. + Snapshot *ResticSnapshot `protobuf:"bytes,2,opt,name=snapshot,proto3" json:"snapshot,omitempty"` // the snapshot that was indexed. + Forgot bool `protobuf:"varint,3,opt,name=forgot,proto3" json:"forgot,omitempty"` // tracks whether this snapshot is forgotten yet. } func (x *OperationIndexSnapshot) Reset() { @@ -667,13 +666,6 @@ func (x *OperationIndexSnapshot) GetForgot() bool { return false } -func (x *OperationIndexSnapshot) GetForgotByOp() int64 { - if x != nil { - return x.ForgotByOp - } - return 0 -} - // OperationForget tracks a forget operation. type OperationForget struct { state protoimpl.MessageState @@ -1104,70 +1096,68 @@ var file_v1_operations_proto_rawDesc = []byte{ 0x6c, 0x61, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2f, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x72, - 0x72, 0x6f, 0x72, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x16, - 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x6e, - 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, - 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x73, 0x74, 0x69, 0x63, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x08, 0x73, 0x6e, - 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x12, 0x20, - 0x0a, 0x0c, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x5f, 0x62, 0x79, 0x5f, 0x6f, 0x70, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x42, 0x79, 0x4f, 0x70, - 0x22, 0x6a, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6f, 0x72, - 0x67, 0x65, 0x74, 0x12, 0x2a, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, 0x53, - 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x12, - 0x2b, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x28, 0x0a, 0x0e, - 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x28, 0x0a, 0x0e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x22, 0x79, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x12, 0x39, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, - 0x72, 0x65, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x35, 0x0a, 0x0e, 0x4f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x23, 0x0a, - 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, - 0x74, 0x73, 0x22, 0x9a, 0x01, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x75, 0x6e, 0x48, 0x6f, 0x6f, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, 0x6e, - 0x74, 0x5f, 0x6f, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, - 0x6e, 0x74, 0x4f, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x70, - 0x75, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x12, 0x30, 0x0a, - 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2a, - 0x60, 0x0a, 0x12, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x55, - 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x56, 0x45, 0x4e, - 0x54, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x45, - 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x11, - 0x0a, 0x0d, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, - 0x03, 0x2a, 0xc2, 0x01, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, - 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x15, 0x0a, - 0x11, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x49, 0x4e, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, - 0x53, 0x53, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, - 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, - 0x55, 0x53, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x07, 0x12, 0x10, 0x0a, 0x0c, - 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x1b, - 0x0a, 0x17, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x5f, - 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x05, 0x12, 0x19, 0x0a, 0x15, 0x53, - 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, - 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x06, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, - 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, - 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x6f, 0x72, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0x60, 0x0a, 0x16, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, + 0x74, 0x69, 0x63, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x08, 0x73, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x22, 0x6a, 0x0a, + 0x0f, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, + 0x12, 0x2a, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, 0x53, 0x6e, 0x61, 0x70, + 0x73, 0x68, 0x6f, 0x74, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x12, 0x2b, 0x0a, 0x06, + 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x28, 0x0a, 0x0e, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x22, 0x28, 0x0a, 0x0e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x79, 0x0a, + 0x10, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x39, 0x0a, + 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x50, + 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x6c, 0x61, + 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x35, 0x0a, 0x0e, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x05, 0x73, 0x74, + 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x70, 0x6f, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x22, + 0x9a, 0x01, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, + 0x48, 0x6f, 0x6f, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6f, + 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x4f, + 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, + 0x6c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x12, 0x30, 0x0a, 0x09, 0x63, 0x6f, + 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, + 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x60, 0x0a, 0x12, + 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, + 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x43, + 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x56, 0x45, 0x4e, + 0x54, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x45, + 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x03, 0x2a, 0xc2, + 0x01, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x4e, 0x4b, + 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, + 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, + 0x41, 0x54, 0x55, 0x53, 0x5f, 0x49, 0x4e, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, + 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x55, 0x43, 0x43, + 0x45, 0x53, 0x53, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, + 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x07, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x41, + 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x1b, 0x0a, 0x17, 0x53, + 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x5f, 0x43, 0x41, 0x4e, + 0x43, 0x45, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x05, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, 0x41, 0x54, + 0x55, 0x53, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x4c, 0x45, + 0x44, 0x10, 0x06, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x62, + 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/api/backresthandler_test.go b/internal/api/backresthandler_test.go index 96c9080d..91d52fbf 100644 --- a/internal/api/backresthandler_test.go +++ b/internal/api/backresthandler_test.go @@ -502,6 +502,7 @@ func TestHookOnErrorHandling(t *testing.T) { } for _, tc := range tests { + tc := tc t.Run(tc.name, func(t *testing.T) { sut.opstore.ResetForTest(t) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 88df2612..c7573728 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -125,6 +125,7 @@ func TestConfig(t *testing.T) { } for _, tc := range tests { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() err := tc.store.Update(tc.config) diff --git a/internal/config/migrations/001prunepolicy_test.go b/internal/config/migrations/001prunepolicy_test.go index 1b3b97fc..3084f8bd 100644 --- a/internal/config/migrations/001prunepolicy_test.go +++ b/internal/config/migrations/001prunepolicy_test.go @@ -94,6 +94,7 @@ func Test001Migration(t *testing.T) { } for _, tc := range cases { + tc := tc t.Run(tc.name, func(t *testing.T) { config := v1.Config{} err := protojson.Unmarshal([]byte(tc.config), &config) diff --git a/internal/config/migrations/003relativescheduling.go b/internal/config/migrations/003relativescheduling.go new file mode 100644 index 00000000..6377ceab --- /dev/null +++ b/internal/config/migrations/003relativescheduling.go @@ -0,0 +1,42 @@ +package migrations + +import ( + v1 "github.com/garethgeorge/backrest/gen/go/v1" +) + +func convertToRelativeSchedule(sched *v1.Schedule) { + switch s := sched.GetSchedule().(type) { + case *v1.Schedule_MaxFrequencyDays: + sched.Schedule = &v1.Schedule_MinDaysSinceLastRun{ + MinDaysSinceLastRun: s.MaxFrequencyDays, + } + case *v1.Schedule_MaxFrequencyHours: + sched.Schedule = &v1.Schedule_MinHoursSinceLastRun{ + MinHoursSinceLastRun: s.MaxFrequencyHours, + } + case *v1.Schedule_Cron: + sched.Schedule = &v1.Schedule_CronSinceLastRun{ + CronSinceLastRun: s.Cron, + } + default: + // do nothing + } +} + +func migration003RelativeScheduling(config *v1.Config) { + // loop over plans and examine prune policy's + for _, repo := range config.Repos { + prunePolicy := repo.GetPrunePolicy() + if prunePolicy == nil { + continue + } + + if schedule := repo.GetPrunePolicy().GetSchedule(); schedule != nil { + convertToRelativeSchedule(schedule) + } + + if schedule := repo.GetCheckPolicy().GetSchedule(); schedule != nil { + convertToRelativeSchedule(schedule) + } + } +} diff --git a/internal/config/migrations/003relativescheduling_test.go b/internal/config/migrations/003relativescheduling_test.go new file mode 100644 index 00000000..52385503 --- /dev/null +++ b/internal/config/migrations/003relativescheduling_test.go @@ -0,0 +1,128 @@ +package migrations + +import ( + "testing" + + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "google.golang.org/protobuf/proto" +) + +func Test003Migration(t *testing.T) { + config := &v1.Config{ + Repos: []*v1.Repo{ + { + Id: "prune daily", + PrunePolicy: &v1.PrunePolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MaxFrequencyDays{ + MaxFrequencyDays: 1, + }, + }, + }, + CheckPolicy: &v1.CheckPolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MaxFrequencyDays{ + MaxFrequencyDays: 1, + }, + }, + }, + }, + { + Id: "prune hourly", + PrunePolicy: &v1.PrunePolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MaxFrequencyHours{ + MaxFrequencyHours: 1, + }, + }, + }, + CheckPolicy: &v1.CheckPolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MaxFrequencyHours{ + MaxFrequencyHours: 1, + }, + }, + }, + }, + { + Id: "prune cron", + PrunePolicy: &v1.PrunePolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_Cron{ + Cron: "0 0 * * *", + }, + }, + }, + CheckPolicy: &v1.CheckPolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_Cron{ + Cron: "0 0 * * *", + }, + }, + }, + }, + }, + } + + want := &v1.Config{ + Repos: []*v1.Repo{ + { + Id: "prune daily", + PrunePolicy: &v1.PrunePolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MinDaysSinceLastRun{ + MinDaysSinceLastRun: 1, + }, + }, + }, + CheckPolicy: &v1.CheckPolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MinDaysSinceLastRun{ + MinDaysSinceLastRun: 1, + }, + }, + }, + }, + { + Id: "prune hourly", + PrunePolicy: &v1.PrunePolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MinHoursSinceLastRun{ + MinHoursSinceLastRun: 1, + }, + }, + }, + CheckPolicy: &v1.CheckPolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MinHoursSinceLastRun{ + MinHoursSinceLastRun: 1, + }, + }, + }, + }, + { + Id: "prune cron", + PrunePolicy: &v1.PrunePolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_CronSinceLastRun{ + CronSinceLastRun: "0 0 * * *", + }, + }, + }, + CheckPolicy: &v1.CheckPolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_CronSinceLastRun{ + CronSinceLastRun: "0 0 * * *", + }, + }, + }, + }, + }, + } + + migration003RelativeScheduling(config) + + if !proto.Equal(config, want) { + t.Errorf("got %v, want %v", config, want) + } +} diff --git a/internal/config/migrations/migrations.go b/internal/config/migrations/migrations.go index b11645b6..4f25809a 100644 --- a/internal/config/migrations/migrations.go +++ b/internal/config/migrations/migrations.go @@ -8,6 +8,7 @@ import ( var migrations = []func(*v1.Config){ migration001PrunePolicy, migration002Schedules, + migration003RelativeScheduling, } var CurrentVersion = int32(len(migrations)) diff --git a/internal/config/validationutil/validationutil_test.go b/internal/config/validationutil/validationutil_test.go index bc1df737..d505d2c4 100644 --- a/internal/config/validationutil/validationutil_test.go +++ b/internal/config/validationutil/validationutil_test.go @@ -38,6 +38,7 @@ func TestSanitizeID(t *testing.T) { } for _, tc := range tcs { + tc := tc t.Run(tc.name, func(t *testing.T) { got := SanitizeID(tc.id) if got != tc.want { diff --git a/internal/oplog/storetests/storecontract_test.go b/internal/oplog/storetests/storecontract_test.go index 032664ef..4aec2a91 100644 --- a/internal/oplog/storetests/storecontract_test.go +++ b/internal/oplog/storetests/storecontract_test.go @@ -124,7 +124,9 @@ func TestAddOperation(t *testing.T) { for name, store := range StoresForTest(t) { t.Run(name, func(t *testing.T) { for _, tc := range tests { + tc := tc t.Run(tc.name, func(t *testing.T) { + t.Parallel() log := oplog.NewOpLog(store) op := proto.Clone(tc.op).(*v1.Operation) if err := log.Add(op); (err != nil) != tc.wantErr { @@ -232,7 +234,9 @@ func TestListOperation(t *testing.T) { } for _, tc := range tests { + tc := tc t.Run(tc.name, func(t *testing.T) { + t.Parallel() var ops []*v1.Operation var err error collect := func(op *v1.Operation) error { @@ -259,7 +263,9 @@ func TestBigIO(t *testing.T) { count := 10 for name, store := range StoresForTest(t) { + store := store t.Run(name, func(t *testing.T) { + t.Parallel() log := oplog.NewOpLog(store) for i := 0; i < count; i++ { if err := log.Add(&v1.Operation{ @@ -292,7 +298,9 @@ func TestIndexSnapshot(t *testing.T) { } for name, store := range StoresForTest(t) { + store := store t.Run(name, func(t *testing.T) { + t.Parallel() log := oplog.NewOpLog(store) op := proto.Clone(op).(*v1.Operation) @@ -330,7 +338,9 @@ func TestUpdateOperation(t *testing.T) { } for name, store := range StoresForTest(t) { + store := store t.Run(name, func(t *testing.T) { + t.Parallel() log := oplog.NewOpLog(store) op := proto.Clone(op).(*v1.Operation) diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index 93f76396..f07f2743 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -184,10 +184,7 @@ func (o *Orchestrator) ScheduleDefaultTasks(config *v1.Config) error { } // Schedule a backup task for the plan - t, err := tasks.NewScheduledBackupTask(plan) - if err != nil { - return fmt.Errorf("schedule backup task for plan %q: %w", plan.Id, err) - } + t := tasks.NewScheduledBackupTask(plan) if err := o.ScheduleTask(t, tasks.TaskPriorityDefault); err != nil { return fmt.Errorf("schedule backup task for plan %q: %w", plan.Id, err) } diff --git a/internal/orchestrator/repo/repo_test.go b/internal/orchestrator/repo/repo_test.go index 65cd519b..3f8d9f6e 100644 --- a/internal/orchestrator/repo/repo_test.go +++ b/internal/orchestrator/repo/repo_test.go @@ -65,7 +65,9 @@ func TestBackup(t *testing.T) { } for _, tc := range tcs { + tc := tc t.Run(tc.name, func(t *testing.T) { + t.Parallel() if tc.unixOnly && runtime.GOOS == "windows" { t.Skip("skipping on windows") } @@ -312,7 +314,9 @@ func TestCheck(t *testing.T) { } for _, tc := range tcs { + tc := tc t.Run(tc.name, func(t *testing.T) { + t.Parallel() orchestrator := initRepoHelper(t, configForTest, tc.repo) buf := bytes.NewBuffer(nil) diff --git a/internal/orchestrator/taskrunnerimpl.go b/internal/orchestrator/taskrunnerimpl.go index 171e6175..895be7f6 100644 --- a/internal/orchestrator/taskrunnerimpl.go +++ b/internal/orchestrator/taskrunnerimpl.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "io" "time" v1 "github.com/garethgeorge/backrest/gen/go/v1" @@ -158,7 +157,3 @@ func (t *taskRunnerImpl) Config() *v1.Config { func (t *taskRunnerImpl) Logger(ctx context.Context) *zap.Logger { return logging.Logger(ctx).Named(t.t.Name()) } - -func (t *taskRunnerImpl) RawLogWriter(ctx context.Context) io.Writer { - return logging.WriterFromContext(ctx) -} diff --git a/internal/orchestrator/tasks/scheduling_test.go b/internal/orchestrator/tasks/scheduling_test.go new file mode 100644 index 00000000..f588f36e --- /dev/null +++ b/internal/orchestrator/tasks/scheduling_test.go @@ -0,0 +1,372 @@ +package tasks + +import ( + "os" + "runtime" + "testing" + "time" + + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/internal/config" + "github.com/garethgeorge/backrest/internal/oplog" + "github.com/garethgeorge/backrest/internal/oplog/memstore" +) + +func TestScheduling(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on windows") + } + + os.Setenv("TZ", "America/Los_Angeles") + defer os.Unsetenv("TZ") + + cfg := &v1.Config{ + Repos: []*v1.Repo{ + { + Id: "repo-absolute", + CheckPolicy: &v1.CheckPolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MaxFrequencyHours{ + MaxFrequencyHours: 1, + }, + }, + }, + PrunePolicy: &v1.PrunePolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MaxFrequencyHours{ + MaxFrequencyHours: 1, + }, + }, + }, + }, + { + Id: "repo-relative", + CheckPolicy: &v1.CheckPolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MinHoursSinceLastRun{ + MinHoursSinceLastRun: 1, + }, + }, + }, + PrunePolicy: &v1.PrunePolicy{ + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MinHoursSinceLastRun{ + MinHoursSinceLastRun: 1, + }, + }, + }, + }, + }, + Plans: []*v1.Plan{ + { + Id: "plan-cron", + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_Cron{ + Cron: "0 0 * * *", // every day at midnight + }, + }, + }, + { + Id: "plan-cron-since-last-run", + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_CronSinceLastRun{ + CronSinceLastRun: "0 0 * * *", // every day at midnight + }, + }, + }, + { + Id: "plan-max-frequency-days", + Repo: "repo1", + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MaxFrequencyDays{ + MaxFrequencyDays: 1, + }, + }, + }, + { + Id: "plan-min-days-since-last-run", + Repo: "repo1", + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MinDaysSinceLastRun{ + MinDaysSinceLastRun: 1, + }, + }, + }, + { + Id: "plan-max-frequency-hours", + Repo: "repo1", + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MaxFrequencyHours{ + MaxFrequencyHours: 1, + }, + }, + }, + { + Id: "plan-min-hours-since-last-run", + Repo: "repo1", + Schedule: &v1.Schedule{ + Schedule: &v1.Schedule_MinHoursSinceLastRun{ + MinHoursSinceLastRun: 1, + }, + }, + }, + }, + } + + now := time.Unix(100000, 0) // 1000 seconds after the epoch as an arbitrary time for the test + farFuture := time.Unix(999999, 0) + + tests := []struct { + name string + task Task + ops []*v1.Operation // operations in the log + wantTime time.Time // time to run the next task + }{ + { + name: "backup schedule max frequency days", + task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-max-frequency-days")), + ops: []*v1.Operation{ + { + InstanceId: "instance1", + RepoId: "repo1", + PlanId: "plan-max-frequency-days", + Op: &v1.Operation_OperationBackup{ + OperationBackup: &v1.OperationBackup{}, + }, + UnixTimeEndMs: farFuture.UnixMilli(), + }, + }, + wantTime: now.Add(time.Hour * 24), + }, + { + name: "backup schedule min days since last run", + task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-min-days-since-last-run")), + ops: []*v1.Operation{ + { + InstanceId: "instance1", + RepoId: "repo1", + PlanId: "plan-min-days-since-last-run", + Op: &v1.Operation_OperationBackup{ + OperationBackup: &v1.OperationBackup{}, + }, + UnixTimeEndMs: farFuture.UnixMilli(), + }, + }, + wantTime: farFuture.Add(time.Hour * 24), + }, + { + name: "backup schedule max frequency hours", + task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-max-frequency-hours")), + ops: []*v1.Operation{ + { + InstanceId: "instance1", + RepoId: "repo1", + PlanId: "plan-max-frequency-hours", + Op: &v1.Operation_OperationBackup{ + OperationBackup: &v1.OperationBackup{}, + }, + UnixTimeEndMs: farFuture.UnixMilli(), + }, + }, + wantTime: now.Add(time.Hour), + }, + { + name: "backup schedule min hours since last run", + task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-min-hours-since-last-run")), + ops: []*v1.Operation{ + { + InstanceId: "instance1", + RepoId: "repo1", + PlanId: "plan-min-hours-since-last-run", + Op: &v1.Operation_OperationBackup{ + OperationBackup: &v1.OperationBackup{}, + }, + UnixTimeEndMs: farFuture.UnixMilli(), + }, + }, + wantTime: farFuture.Add(time.Hour), + }, + { + name: "backup schedule cron", + task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-cron")), + ops: []*v1.Operation{ + { + InstanceId: "instance1", + RepoId: "repo1", + PlanId: "plan-cron", + Op: &v1.Operation_OperationBackup{ + OperationBackup: &v1.OperationBackup{}, + }, + UnixTimeEndMs: farFuture.UnixMilli(), + }, + }, + wantTime: mustParseTime(t, "1970-01-02T00:00:00-08:00"), + }, + { + name: "backup schedule cron since last run", + task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-cron-since-last-run")), + ops: []*v1.Operation{ + { + InstanceId: "instance1", + RepoId: "repo1", + PlanId: "plan-cron-since-last-run", + Op: &v1.Operation_OperationBackup{ + OperationBackup: &v1.OperationBackup{}, + }, + UnixTimeEndMs: farFuture.UnixMilli(), + }, + }, + wantTime: mustParseTime(t, "1970-01-13T00:00:00-08:00"), + }, + { + name: "check schedule absolute", + task: NewCheckTask("repo-absolute", "_system_", false), + ops: []*v1.Operation{ + { + InstanceId: "instance1", + RepoId: "repo-absolute", + PlanId: "_system_", + Op: &v1.Operation_OperationCheck{ + OperationCheck: &v1.OperationCheck{}, + }, + UnixTimeEndMs: farFuture.UnixMilli(), + }, + }, + wantTime: now.Add(time.Hour), + }, + { + name: "check schedule relative no backup yet", + task: NewCheckTask("repo-relative", "_system_", false), + ops: []*v1.Operation{ + { + InstanceId: "instance1", + RepoId: "repo-relative", + PlanId: "_system_", + Op: &v1.Operation_OperationCheck{ + OperationCheck: &v1.OperationCheck{}, + }, + UnixTimeEndMs: farFuture.UnixMilli(), + }, + }, + wantTime: now.Add(time.Hour), + }, + { + name: "check schedule relative", + task: NewCheckTask("repo-relative", "_system_", false), + ops: []*v1.Operation{ + { + InstanceId: "instance1", + RepoId: "repo-relative", + PlanId: "_system_", + Op: &v1.Operation_OperationCheck{ + OperationCheck: &v1.OperationCheck{}, + }, + UnixTimeEndMs: farFuture.UnixMilli(), + }, + { + InstanceId: "instance1", + RepoId: "repo-relative", + PlanId: "_system_", + Op: &v1.Operation_OperationBackup{ + OperationBackup: &v1.OperationBackup{}, + }, + UnixTimeEndMs: farFuture.UnixMilli(), + }, + }, + wantTime: farFuture.Add(time.Hour), + }, + { + name: "prune schedule absolute", + task: NewPruneTask("repo-absolute", "_system_", false), + ops: []*v1.Operation{ + { + InstanceId: "instance1", + RepoId: "repo-absolute", + PlanId: "_system_", + Op: &v1.Operation_OperationPrune{ + OperationPrune: &v1.OperationPrune{}, + }, + UnixTimeEndMs: farFuture.UnixMilli(), + }, + }, + wantTime: now.Add(time.Hour), + }, + { + name: "prune schedule relative no backup yet", + task: NewPruneTask("repo-relative", "_system_", false), + ops: []*v1.Operation{ + { + InstanceId: "instance1", + RepoId: "repo-relative", + PlanId: "_system_", + Op: &v1.Operation_OperationPrune{ + OperationPrune: &v1.OperationPrune{}, + }, + UnixTimeEndMs: farFuture.UnixMilli(), + }, + }, + wantTime: now.Add(time.Hour), + }, + { + name: "prune schedule relative", + task: NewPruneTask("repo-relative", "_system_", false), + ops: []*v1.Operation{ + { + InstanceId: "instance1", + RepoId: "repo-relative", + PlanId: "_system_", + Op: &v1.Operation_OperationPrune{ + OperationPrune: &v1.OperationPrune{}, + }, + UnixTimeEndMs: farFuture.UnixMilli(), + }, + { + InstanceId: "instance1", + RepoId: "repo-relative", + PlanId: "_system_", + Op: &v1.Operation_OperationBackup{ + OperationBackup: &v1.OperationBackup{}, + }, + UnixTimeEndMs: farFuture.UnixMilli(), + }, + }, + wantTime: farFuture.Add(time.Hour), + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + opstore := memstore.NewMemStore() + for _, op := range tc.ops { + if err := opstore.Add(op); err != nil { + t.Fatalf("failed to add operation to opstore: %v", err) + } + } + + log := oplog.NewOpLog(opstore) + + runner := newTestTaskRunner(t, cfg, log) + + st, err := tc.task.Next(now, runner) + if err != nil { + t.Fatalf("failed to get next task: %v", err) + } + + if !st.RunAt.Equal(tc.wantTime) { + t.Errorf("got run at %v, want %v", st.RunAt.Format(time.RFC3339), tc.wantTime.Format(time.RFC3339)) + } + }) + } +} + +func mustParseTime(t *testing.T, s string) time.Time { + t.Helper() + tm, err := time.Parse(time.RFC3339, s) + if err != nil { + t.Fatalf("failed to parse time: %v", err) + } + return tm +} diff --git a/internal/orchestrator/tasks/task.go b/internal/orchestrator/tasks/task.go index c7fe9c01..ffb200f5 100644 --- a/internal/orchestrator/tasks/task.go +++ b/internal/orchestrator/tasks/task.go @@ -2,10 +2,12 @@ package tasks import ( "context" - "io" + "errors" + "testing" "time" v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/internal/config" "github.com/garethgeorge/backrest/internal/oplog" "github.com/garethgeorge/backrest/internal/orchestrator/repo" "go.uber.org/zap" @@ -50,8 +52,6 @@ type TaskRunner interface { Config() *v1.Config // Logger returns the logger. Logger(ctx context.Context) *zap.Logger - // RawLogWriter returns a writer for raw logs. - RawLogWriter(ctx context.Context) io.Writer } type TaskExecutor interface { @@ -149,3 +149,65 @@ func timeToUnixMillis(t time.Time) int64 { func curTimeMillis() int64 { return timeToUnixMillis(time.Now()) } + +type testTaskRunner struct { + config *v1.Config // the config to use for the task runner. + oplog *oplog.OpLog +} + +var _ TaskRunner = &testTaskRunner{} + +func newTestTaskRunner(t testing.TB, config *v1.Config, oplog *oplog.OpLog) *testTaskRunner { + return &testTaskRunner{ + config: config, + oplog: oplog, + } +} + +func (t *testTaskRunner) CreateOperation(op *v1.Operation) error { + panic("not implemented") +} + +func (t *testTaskRunner) UpdateOperation(op *v1.Operation) error { + panic("not implemented") +} + +func (t *testTaskRunner) ExecuteHooks(ctx context.Context, events []v1.Hook_Condition, vars HookVars) error { + panic("not implemented") +} + +func (t *testTaskRunner) OpLog() *oplog.OpLog { + return t.oplog +} + +func (t *testTaskRunner) GetRepo(repoID string) (*v1.Repo, error) { + cfg := config.FindRepo(t.config, repoID) + if cfg == nil { + return nil, errors.New("repo not found") + } + return cfg, nil +} + +func (t *testTaskRunner) GetPlan(planID string) (*v1.Plan, error) { + cfg := config.FindPlan(t.config, planID) + if cfg == nil { + return nil, errors.New("plan not found") + } + return cfg, nil +} + +func (t *testTaskRunner) GetRepoOrchestrator(repoID string) (*repo.RepoOrchestrator, error) { + panic("not implemented") +} + +func (t *testTaskRunner) ScheduleTask(task Task, priority int) error { + panic("not implemented") +} + +func (t *testTaskRunner) Config() *v1.Config { + return t.config +} + +func (t *testTaskRunner) Logger(ctx context.Context) *zap.Logger { + return zap.L() +} diff --git a/internal/orchestrator/tasks/taskbackup.go b/internal/orchestrator/tasks/taskbackup.go index 5201de0a..9ba4971d 100644 --- a/internal/orchestrator/tasks/taskbackup.go +++ b/internal/orchestrator/tasks/taskbackup.go @@ -9,6 +9,7 @@ import ( "time" v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/internal/oplog" "github.com/garethgeorge/backrest/internal/protoutil" "github.com/garethgeorge/backrest/pkg/restic" "go.uber.org/zap" @@ -25,14 +26,14 @@ type BackupTask struct { var _ Task = &BackupTask{} -func NewScheduledBackupTask(plan *v1.Plan) (*BackupTask, error) { +func NewScheduledBackupTask(plan *v1.Plan) *BackupTask { return &BackupTask{ BaseTask: BaseTask{ TaskName: fmt.Sprintf("backup for plan %q", plan.Id), TaskRepoID: plan.Repo, TaskPlanID: plan.Id, }, - }, nil + } } func NewOneoffBackupTask(plan *v1.Plan, at time.Time) *BackupTask { @@ -69,7 +70,24 @@ func (t *BackupTask) Next(now time.Time, runner TaskRunner) (ScheduledTask, erro if plan.Schedule == nil { return NeverScheduledTask, nil } - nextRun, err := protoutil.ResolveSchedule(plan.Schedule, now) + + var lastRan time.Time + if err := runner.OpLog().Query(oplog.Query{RepoID: t.RepoID(), PlanID: t.PlanID(), Reversed: true}, func(op *v1.Operation) error { + if op.Status == v1.OperationStatus_STATUS_PENDING || op.Status == v1.OperationStatus_STATUS_SYSTEM_CANCELLED { + return nil + } + if _, ok := op.Op.(*v1.Operation_OperationBackup); ok && op.UnixTimeEndMs != 0 { + lastRan = time.Unix(0, op.UnixTimeEndMs*int64(time.Millisecond)) + return oplog.ErrStopIteration + } + return nil + }); err != nil { + return NeverScheduledTask, fmt.Errorf("finding last backup run time: %w", err) + } else if lastRan.IsZero() { + lastRan = time.Now() + } + + nextRun, err := protoutil.ResolveSchedule(plan.Schedule, lastRan, now) if errors.Is(err, protoutil.ErrScheduleDisabled) { return NeverScheduledTask, nil } else if err != nil { diff --git a/internal/orchestrator/tasks/taskcheck.go b/internal/orchestrator/tasks/taskcheck.go index 2d4bef0c..06bddcc5 100644 --- a/internal/orchestrator/tasks/taskcheck.go +++ b/internal/orchestrator/tasks/taskcheck.go @@ -60,7 +60,10 @@ func (t *CheckTask) Next(now time.Time, runner TaskRunner) (ScheduledTask, error var foundBackup bool if err := runner.OpLog().Query(oplog.Query{RepoID: t.RepoID(), Reversed: true}, func(op *v1.Operation) error { - if _, ok := op.Op.(*v1.Operation_OperationCheck); ok { + if op.Status == v1.OperationStatus_STATUS_PENDING || op.Status == v1.OperationStatus_STATUS_SYSTEM_CANCELLED { + return nil + } + if _, ok := op.Op.(*v1.Operation_OperationCheck); ok && op.UnixTimeEndMs != 0 { lastRan = time.Unix(0, op.UnixTimeEndMs*int64(time.Millisecond)) return oplog.ErrStopIteration } @@ -71,10 +74,10 @@ func (t *CheckTask) Next(now time.Time, runner TaskRunner) (ScheduledTask, error }); err != nil { return NeverScheduledTask, fmt.Errorf("finding last check run time: %w", err) } else if !foundBackup { - lastRan = time.Now() + lastRan = now } - runAt, err := protoutil.ResolveSchedule(repo.CheckPolicy.GetSchedule(), lastRan) + runAt, err := protoutil.ResolveSchedule(repo.CheckPolicy.GetSchedule(), lastRan, now) if errors.Is(err, protoutil.ErrScheduleDisabled) { return NeverScheduledTask, nil } else if err != nil { diff --git a/internal/orchestrator/tasks/taskprune.go b/internal/orchestrator/tasks/taskprune.go index 3579eddc..5d07604f 100644 --- a/internal/orchestrator/tasks/taskprune.go +++ b/internal/orchestrator/tasks/taskprune.go @@ -59,7 +59,10 @@ func (t *PruneTask) Next(now time.Time, runner TaskRunner) (ScheduledTask, error var lastRan time.Time var foundBackup bool if err := runner.OpLog().Query(oplog.Query{RepoID: t.RepoID(), Reversed: true}, func(op *v1.Operation) error { - if _, ok := op.Op.(*v1.Operation_OperationPrune); ok { + if op.Status == v1.OperationStatus_STATUS_PENDING || op.Status == v1.OperationStatus_STATUS_SYSTEM_CANCELLED { + return nil + } + if _, ok := op.Op.(*v1.Operation_OperationPrune); ok && op.UnixTimeEndMs != 0 { lastRan = time.Unix(0, op.UnixTimeEndMs*int64(time.Millisecond)) return oplog.ErrStopIteration } @@ -70,10 +73,10 @@ func (t *PruneTask) Next(now time.Time, runner TaskRunner) (ScheduledTask, error }); err != nil { return NeverScheduledTask, fmt.Errorf("finding last prune run time: %w", err) } else if !foundBackup { - lastRan = time.Now() + lastRan = now } - runAt, err := protoutil.ResolveSchedule(repo.PrunePolicy.GetSchedule(), lastRan) + runAt, err := protoutil.ResolveSchedule(repo.PrunePolicy.GetSchedule(), lastRan, now) if errors.Is(err, protoutil.ErrScheduleDisabled) { return NeverScheduledTask, nil } else if err != nil { diff --git a/internal/protoutil/schedule.go b/internal/protoutil/schedule.go index ea275091..cf09cbd9 100644 --- a/internal/protoutil/schedule.go +++ b/internal/protoutil/schedule.go @@ -13,19 +13,29 @@ var ErrScheduleDisabled = errors.New("never") // ResolveSchedule resolves a schedule to the next time it should run based on last execution. // note that this is different from backup behavior which is always relative to the current time. -func ResolveSchedule(sched *v1.Schedule, lastRan time.Time) (time.Time, error) { +func ResolveSchedule(sched *v1.Schedule, lastRan time.Time, curTime time.Time) (time.Time, error) { switch s := sched.GetSchedule().(type) { case *v1.Schedule_Disabled: return time.Time{}, ErrScheduleDisabled case *v1.Schedule_MaxFrequencyDays: - return lastRan.Add(time.Duration(s.MaxFrequencyDays) * 24 * time.Hour), nil + return curTime.Add(time.Duration(s.MaxFrequencyDays) * 24 * time.Hour), nil case *v1.Schedule_MaxFrequencyHours: - return lastRan.Add(time.Duration(s.MaxFrequencyHours) * time.Hour), nil + return curTime.Add(time.Duration(s.MaxFrequencyHours) * time.Hour), nil case *v1.Schedule_Cron: cron, err := cronexpr.ParseInLocation(s.Cron, time.Now().Location().String()) if err != nil { return time.Time{}, fmt.Errorf("parse cron %q: %w", s.Cron, err) } + return cron.Next(curTime), nil + case *v1.Schedule_MinHoursSinceLastRun: + return lastRan.Add(time.Duration(s.MinHoursSinceLastRun) * time.Hour), nil + case *v1.Schedule_MinDaysSinceLastRun: + return lastRan.Add(time.Duration(s.MinDaysSinceLastRun) * 24 * time.Hour), nil + case *v1.Schedule_CronSinceLastRun: + cron, err := cronexpr.ParseInLocation(s.CronSinceLastRun, time.Now().Location().String()) + if err != nil { + return time.Time{}, fmt.Errorf("parse cron %q: %w", s.CronSinceLastRun, err) + } return cron.Next(lastRan), nil default: return time.Time{}, fmt.Errorf("unknown schedule type: %T", s) @@ -50,6 +60,22 @@ func ValidateSchedule(sched *v1.Schedule) error { if err != nil { return fmt.Errorf("invalid cron %q: %w", s.Cron, err) } + case *v1.Schedule_MinHoursSinceLastRun: + if s.MinHoursSinceLastRun < 1 { + return errors.New("invalid min hours since last run") + } + case *v1.Schedule_MinDaysSinceLastRun: + if s.MinDaysSinceLastRun < 1 { + return errors.New("invalid min days since last run") + } + case *v1.Schedule_CronSinceLastRun: + if s.CronSinceLastRun == "" { + return errors.New("empty cron expression") + } + _, err := cronexpr.ParseInLocation(s.CronSinceLastRun, time.Now().Location().String()) + if err != nil { + return fmt.Errorf("invalid cron %q: %w", s.CronSinceLastRun, err) + } case *v1.Schedule_Disabled: if !s.Disabled { return errors.New("disabled boolean must be set to true") diff --git a/pkg/restic/restic_test.go b/pkg/restic/restic_test.go index ffcee2f3..014b1eff 100644 --- a/pkg/restic/restic_test.go +++ b/pkg/restic/restic_test.go @@ -107,6 +107,7 @@ func TestResticBackup(t *testing.T) { } for _, tc := range tests { + tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() if runtime.GOOS == "windows" && tc.unixOnly { @@ -246,7 +247,9 @@ func TestSnapshot(t *testing.T) { } for _, tc := range tests { + tc := tc t.Run(tc.name, func(t *testing.T) { + t.Parallel() snapshots, err := r.Snapshots(context.Background(), tc.opts...) if err != nil { t.Fatalf("failed to list snapshots: %v", err) diff --git a/proto/v1/config.proto b/proto/v1/config.proto index 42bb5fe9..54ed0344 100644 --- a/proto/v1/config.proto +++ b/proto/v1/config.proto @@ -124,6 +124,9 @@ message Schedule { string cron = 2 [json_name="cron"]; // cron expression describing the schedule. int32 maxFrequencyDays = 3 [json_name="maxFrequencyDays"]; // max frequency of runs in days. int32 maxFrequencyHours = 4 [json_name="maxFrequencyHours"]; // max frequency of runs in hours. + string cronSinceLastRun = 100 [json_name="cronSinceLastRun"]; // cron expression to run since the last run. + int32 minHoursSinceLastRun = 101 [json_name="minHoursSinceLastRun"]; // max hours since the last run. + int32 minDaysSinceLastRun = 102 [json_name="minDaysSinceLastRun"]; // max days since the last run. } } diff --git a/webui/gen/ts/v1/config_pb.ts b/webui/gen/ts/v1/config_pb.ts index 290295d1..6de152a3 100644 --- a/webui/gen/ts/v1/config_pb.ts +++ b/webui/gen/ts/v1/config_pb.ts @@ -848,6 +848,30 @@ export class Schedule extends Message { */ value: number; case: "maxFrequencyHours"; + } | { + /** + * cron expression to run since the last run. + * + * @generated from field: string cronSinceLastRun = 100; + */ + value: string; + case: "cronSinceLastRun"; + } | { + /** + * max hours since the last run. + * + * @generated from field: int32 minHoursSinceLastRun = 101; + */ + value: number; + case: "minHoursSinceLastRun"; + } | { + /** + * max days since the last run. + * + * @generated from field: int32 minDaysSinceLastRun = 102; + */ + value: number; + case: "minDaysSinceLastRun"; } | { case: undefined; value?: undefined } = { case: undefined }; constructor(data?: PartialMessage) { @@ -862,6 +886,9 @@ export class Schedule extends Message { { no: 2, name: "cron", kind: "scalar", T: 9 /* ScalarType.STRING */, oneof: "schedule" }, { no: 3, name: "maxFrequencyDays", kind: "scalar", T: 5 /* ScalarType.INT32 */, oneof: "schedule" }, { no: 4, name: "maxFrequencyHours", kind: "scalar", T: 5 /* ScalarType.INT32 */, oneof: "schedule" }, + { no: 100, name: "cronSinceLastRun", kind: "scalar", T: 9 /* ScalarType.STRING */, oneof: "schedule" }, + { no: 101, name: "minHoursSinceLastRun", kind: "scalar", T: 5 /* ScalarType.INT32 */, oneof: "schedule" }, + { no: 102, name: "minDaysSinceLastRun", kind: "scalar", T: 5 /* ScalarType.INT32 */, oneof: "schedule" }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): Schedule { diff --git a/webui/gen/ts/v1/operations_pb.ts b/webui/gen/ts/v1/operations_pb.ts index 4f7796ef..461a6d2d 100644 --- a/webui/gen/ts/v1/operations_pb.ts +++ b/webui/gen/ts/v1/operations_pb.ts @@ -455,13 +455,6 @@ export class OperationIndexSnapshot extends Message { */ forgot = false; - /** - * ID of a forget operation that removed this snapshot. - * - * @generated from field: int64 forgot_by_op = 4; - */ - forgotByOp = protoInt64.zero; - constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); @@ -472,7 +465,6 @@ export class OperationIndexSnapshot extends Message { static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 2, name: "snapshot", kind: "message", T: ResticSnapshot }, { no: 3, name: "forgot", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, - { no: 4, name: "forgot_by_op", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): OperationIndexSnapshot { diff --git a/webui/src/components/ScheduleFormItem.tsx b/webui/src/components/ScheduleFormItem.tsx index 1b7951a3..bf402132 100644 --- a/webui/src/components/ScheduleFormItem.tsx +++ b/webui/src/components/ScheduleFormItem.tsx @@ -35,30 +35,48 @@ export const ScheduleDefaultsDaily: ScheduleDefaults = { cronPeriods: ["day", "hour", "month", "week"], }; +type SchedulingMode = + | "" + | "disabled" + | "maxFrequencyDays" + | "maxFrequencyHours" + | "cron" + | "cronSinceLastRun" + | "minHoursSinceLastRun" + | "minDaysSinceLastRun"; export const ScheduleFormItem = ({ name, defaults, + allowedModes, }: { name: string[]; defaults?: ScheduleDefaults; + allowedModes?: SchedulingMode[]; }) => { const form = Form.useFormInstance(); - const retention = Form.useWatch(name, { form, preserve: true }) as any; + const schedule = Form.useWatch(name, { form, preserve: true }) as any; defaults = defaults || ScheduleDefaultsInfrequent; - const determineMode = () => { - if (!retention) { + const determineMode = (): SchedulingMode => { + if (!schedule) { return ""; - } else if (retention.disabled) { + } else if (schedule.disabled) { return "disabled"; - } else if (retention.maxFrequencyDays) { + } else if (schedule.maxFrequencyDays) { return "maxFrequencyDays"; - } else if (retention.maxFrequencyHours) { + } else if (schedule.maxFrequencyHours) { return "maxFrequencyHours"; - } else if (retention.cron) { + } else if (schedule.cron) { return "cron"; + } else if (schedule.cronSinceLastRun) { + return "cronSinceLastRun"; + } else if (schedule.minHoursSinceLastRun) { + return "minHoursSinceLastRun"; + } else if (schedule.minDaysSinceLastRun) { + return "minDaysSinceLastRun"; } + return ""; }; const mode = determineMode(); @@ -104,6 +122,7 @@ export const ScheduleFormItem = ({ Interval in Days} type="number" + min={1} /> ); @@ -123,6 +142,71 @@ export const ScheduleFormItem = ({ Interval in Hours} type="number" + min={1} + /> + + ); + } else if (mode === "cronSinceLastRun") { + elem = ( + + { + form.setFieldValue(name.concat(["cronSinceLastRun"]), val); + }} + allowedDropdowns={defaults.cronDropdowns} + allowedPeriods={defaults.cronPeriods} + clearButton={false} + /> + + ); + } else if (mode === "minHoursSinceLastRun") { + elem = ( + + Interval in Hours} + type="number" + min={1} + /> + + ); + } else if (mode === "minDaysSinceLastRun") { + elem = ( + + Interval in Days} + type="number" + min={1} /> ); @@ -156,31 +240,66 @@ export const ScheduleFormItem = ({ }); } else if (selected === "cron") { form.setFieldValue(name, { cron: defaults!.cron }); + } else if (selected === "minHoursSinceLastRun") { + form.setFieldValue(name, { minHoursSinceLastRun: 1 }); + } else if (selected === "minDaysSinceLastRun") { + form.setFieldValue(name, { minDaysSinceLastRun: 1 }); + } else if (selected === "cronSinceLastRun") { + form.setFieldValue(name, { cronSinceLastRun: defaults!.cron }); } else { form.setFieldValue(name, { disabled: true }); } }} > - - - Disabled - - - - - Max Frequency Hours - - - - - Max Frequency Days - - - - - Cron - - + {(!allowedModes || allowedModes.includes("disabled")) && ( + + + Disabled + + + )} + {(!allowedModes || allowedModes.includes("maxFrequencyHours")) && ( + + + Interval Hours + + + )} + {(!allowedModes || allowedModes.includes("maxFrequencyDays")) && ( + + + Interval Days + + + )} + {(!allowedModes || allowedModes.includes("cron")) && ( + + + Startup Relative Cron + + + )} + {(!allowedModes || allowedModes.includes("minHoursSinceLastRun")) && ( + + + Hours After Last Run + + + )} + {(!allowedModes || allowedModes.includes("minDaysSinceLastRun")) && ( + + + Days After Last Run + + + )} + {(!allowedModes || allowedModes.includes("cronSinceLastRun")) && ( + + + Last Run Relative Cron + + + )}
diff --git a/webui/src/state/flowdisplayaggregator.ts b/webui/src/state/flowdisplayaggregator.ts index 58df265a..a3393743 100644 --- a/webui/src/state/flowdisplayaggregator.ts +++ b/webui/src/state/flowdisplayaggregator.ts @@ -47,6 +47,10 @@ export const displayInfoForFlow = (ops: Operation[]): FlowDisplayInfo => { const duration = Number(firstOp.unixTimeEndMs - firstOp.unixTimeStartMs); + if (firstOp.status === OperationStatus.STATUS_PENDING) { + info.subtitleComponents.push("scheduled, waiting"); + } + switch (firstOp.op.case) { case "operationBackup": { @@ -93,9 +97,6 @@ export const displayInfoForFlow = (ops: Operation[]): FlowDisplayInfo => { info.subtitleComponents.push(`ID: ${normalizeSnapshotId(snapshot.id)}`); default: switch (firstOp.status) { - case OperationStatus.STATUS_PENDING: - info.subtitleComponents.push("scheduled, waiting"); - break; case OperationStatus.STATUS_INPROGRESS: info.subtitleComponents.push("running"); break; diff --git a/webui/src/views/AddRepoModal.tsx b/webui/src/views/AddRepoModal.tsx index 66da781d..99bbb70b 100644 --- a/webui/src/views/AddRepoModal.tsx +++ b/webui/src/views/AddRepoModal.tsx @@ -476,7 +476,15 @@ export const AddRepoModal = ({ template }: { template: Repo | null }) => { } /> - + {/* Repo.checkPolicy */} @@ -512,7 +520,15 @@ export const AddRepoModal = ({ template }: { template: Repo | null }) => { } /> - + {/* Repo.commandPrefix */}