From d082d684454588d99e7d2cb24d8641074f037f8d Mon Sep 17 00:00:00 2001 From: garethgeorge Date: Tue, 14 Nov 2023 00:04:14 -0800 Subject: [PATCH] feat: initial oplog implementation --- cmd/resticui/resticui.go | 33 ++- gen/go/v1/operations.pb.go | 302 +++++++++++++++++++++------- gen/go/v1/service.pb.go | 110 +++++----- gen/go/v1/service.pb.gw.go | 50 +++++ gen/go/v1/service_grpc.pb.go | 77 ++++++- go.mod | 19 ++ go.sum | 147 ++++++++++++++ internal/api/api.go | 13 +- internal/api/server.go | 65 +++--- internal/oplog/oplog.go | 297 +++++++++++++++++++++++++++ internal/oplog/oplog_test.go | 205 +++++++++++++++++++ internal/oplog/serializationutil.go | 13 ++ proto/v1/operations.proto | 29 ++- proto/v1/service.proto | 8 +- webui/gen/ts/v1/operations.pb.ts | 18 +- webui/gen/ts/v1/service.pb.ts | 4 + 16 files changed, 1207 insertions(+), 183 deletions(-) create mode 100644 internal/oplog/oplog.go create mode 100644 internal/oplog/oplog_test.go create mode 100644 internal/oplog/serializationutil.go diff --git a/cmd/resticui/resticui.go b/cmd/resticui/resticui.go index 7b3b3928..3c03fc1b 100644 --- a/cmd/resticui/resticui.go +++ b/cmd/resticui/resticui.go @@ -13,6 +13,7 @@ import ( "github.com/garethgeorge/resticui/internal/api" "github.com/garethgeorge/resticui/internal/config" + "github.com/garethgeorge/resticui/internal/oplog" "github.com/garethgeorge/resticui/internal/orchestrator" static "github.com/garethgeorge/resticui/webui" "github.com/mattn/go-colorable" @@ -27,6 +28,7 @@ func main() { ctx, cancel := context.WithCancel(ctx) go onterm(cancel) + if _, err := config.Default.Get(); err != nil { zap.S().Fatalf("Error loading config: %v", err) } @@ -37,18 +39,24 @@ func main() { mux := http.NewServeMux() mux.Handle("/", http.FileServer(http.FS(&SubdirFilesystem{FS: static.FS, subdir: "dist"}))) - server := &http.Server{ - Addr: ":9090", - Handler: mux, - } - orchestrator := orchestrator.NewOrchestrator(config.Default) + // Create and serve API server + oplog, err := oplog.NewOpLog(path.Join(dataPath(), "oplog.boltdb")) + if err != nil { + zap.S().Fatalf("Error creating oplog: %v", err) + } + defer oplog.Close() + + apiServer := api.NewServer( + orchestrator.NewOrchestrator(config.Default), // TODO: eliminate default config + oplog, + ) // Serve the API wg.Add(1) go func() { defer wg.Done() - err := api.ServeAPI(ctx, orchestrator, mux) + err := api.ServeAPI(ctx, apiServer, mux) if err != nil { zap.S().Fatal("Error serving API", zap.Error(err)) } @@ -56,6 +64,11 @@ func main() { }() // Serve the HTTP gateway + server := &http.Server{ + Addr: ":9090", + Handler: mux, + } + wg.Add(1) go func() { defer wg.Done() @@ -113,4 +126,12 @@ func (s *SubdirFilesystem) ReadDir(name string) ([]fs.DirEntry, error) { return nil, &fs.PathError{Op: "readdir", Path: name, Err: errors.New("not implemented")} } return readDirFS.ReadDir(path.Join(s.subdir, name)) +} + +func dataPath() string { + datahome := os.Getenv("XDG_DATA_HOME") + if datahome == "" { + datahome = path.Join(os.Getenv("HOME") + "/.local/share") + } + return path.Join(datahome, "resticui") } \ No newline at end of file diff --git a/gen/go/v1/operations.pb.go b/gen/go/v1/operations.pb.go index a103aded..88bd1e55 100644 --- a/gen/go/v1/operations.pb.go +++ b/gen/go/v1/operations.pb.go @@ -20,6 +20,56 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// OperationEventType indicates whether the operation was created or updated +type OperationEventType int32 + +const ( + OperationEventType_EVENT_UNKNOWN OperationEventType = 0 + OperationEventType_EVENT_CREATED OperationEventType = 1 + OperationEventType_EVENT_UPDATED OperationEventType = 2 +) + +// Enum value maps for OperationEventType. +var ( + OperationEventType_name = map[int32]string{ + 0: "EVENT_UNKNOWN", + 1: "EVENT_CREATED", + 2: "EVENT_UPDATED", + } + OperationEventType_value = map[string]int32{ + "EVENT_UNKNOWN": 0, + "EVENT_CREATED": 1, + "EVENT_UPDATED": 2, + } +) + +func (x OperationEventType) Enum() *OperationEventType { + p := new(OperationEventType) + *p = x + return p +} + +func (x OperationEventType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (OperationEventType) Descriptor() protoreflect.EnumDescriptor { + return file_v1_operations_proto_enumTypes[0].Descriptor() +} + +func (OperationEventType) Type() protoreflect.EnumType { + return &file_v1_operations_proto_enumTypes[0] +} + +func (x OperationEventType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use OperationEventType.Descriptor instead. +func (OperationEventType) EnumDescriptor() ([]byte, []int) { + return file_v1_operations_proto_rawDescGZIP(), []int{0} +} + type OperationStatus int32 const ( @@ -27,6 +77,7 @@ const ( OperationStatus_STATUS_PENDING OperationStatus = 1 OperationStatus_STATUS_INPROGRESS OperationStatus = 2 OperationStatus_STATUS_SUCCESS OperationStatus = 3 + OperationStatus_STATUS_ERROR OperationStatus = 4 ) // Enum value maps for OperationStatus. @@ -36,12 +87,14 @@ var ( 1: "STATUS_PENDING", 2: "STATUS_INPROGRESS", 3: "STATUS_SUCCESS", + 4: "STATUS_ERROR", } OperationStatus_value = map[string]int32{ "STATUS_UNKNOWN": 0, "STATUS_PENDING": 1, "STATUS_INPROGRESS": 2, "STATUS_SUCCESS": 3, + "STATUS_ERROR": 4, } ) @@ -56,11 +109,11 @@ func (x OperationStatus) String() string { } func (OperationStatus) Descriptor() protoreflect.EnumDescriptor { - return file_v1_operations_proto_enumTypes[0].Descriptor() + return file_v1_operations_proto_enumTypes[1].Descriptor() } func (OperationStatus) Type() protoreflect.EnumType { - return &file_v1_operations_proto_enumTypes[0] + return &file_v1_operations_proto_enumTypes[1] } func (x OperationStatus) Number() protoreflect.EnumNumber { @@ -69,7 +122,7 @@ func (x OperationStatus) Number() protoreflect.EnumNumber { // Deprecated: Use OperationStatus.Descriptor instead. func (OperationStatus) EnumDescriptor() ([]byte, []int) { - return file_v1_operations_proto_rawDescGZIP(), []int{0} + return file_v1_operations_proto_rawDescGZIP(), []int{1} } type Operation struct { @@ -77,9 +130,13 @@ type Operation struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Status OperationStatus `protobuf:"varint,1,opt,name=status,proto3,enum=v1.OperationStatus" json:"status,omitempty"` - UnixTimeStartMs int64 `protobuf:"varint,2,opt,name=unix_time_start_ms,json=unixTimeStartMs,proto3" json:"unix_time_start_ms,omitempty"` - UnixTimeEndMs int64 `protobuf:"varint,3,opt,name=unix_time_end_ms,json=unixTimeEndMs,proto3" json:"unix_time_end_ms,omitempty"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + RepoId string `protobuf:"bytes,2,opt,name=repo_id,json=repoId,proto3" json:"repo_id,omitempty"` // repo id if associated with a repo (always true) + PlanId string `protobuf:"bytes,3,opt,name=plan_id,json=planId,proto3" json:"plan_id,omitempty"` // plan id if associated with a plan (always true) + Status OperationStatus `protobuf:"varint,4,opt,name=status,proto3,enum=v1.OperationStatus" json:"status,omitempty"` + UnixTimeStartMs int64 `protobuf:"varint,5,opt,name=unix_time_start_ms,json=unixTimeStartMs,proto3" json:"unix_time_start_ms,omitempty"` + UnixTimeEndMs int64 `protobuf:"varint,6,opt,name=unix_time_end_ms,json=unixTimeEndMs,proto3" json:"unix_time_end_ms,omitempty"` + DisplayMessage string `protobuf:"bytes,7,opt,name=display_message,json=displayMessage,proto3" json:"display_message,omitempty"` // human readable context message (if any) // Types that are assignable to Op: // // *Operation_Backup @@ -118,6 +175,27 @@ func (*Operation) Descriptor() ([]byte, []int) { return file_v1_operations_proto_rawDescGZIP(), []int{0} } +func (x *Operation) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *Operation) GetRepoId() string { + if x != nil { + return x.RepoId + } + return "" +} + +func (x *Operation) GetPlanId() string { + if x != nil { + return x.PlanId + } + return "" +} + func (x *Operation) GetStatus() OperationStatus { if x != nil { return x.Status @@ -139,6 +217,13 @@ func (x *Operation) GetUnixTimeEndMs() int64 { return 0 } +func (x *Operation) GetDisplayMessage() string { + if x != nil { + return x.DisplayMessage + } + return "" +} + func (m *Operation) GetOp() isOperation_Op { if m != nil { return m.Op @@ -163,20 +248,74 @@ type Operation_Backup struct { func (*Operation_Backup) isOperation_Op() {} +// OperationEvent is used in the wireformat to stream operation changes to clients +type OperationEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type OperationEventType `protobuf:"varint,1,opt,name=type,proto3,enum=v1.OperationEventType" json:"type,omitempty"` + Operation *Operation `protobuf:"bytes,2,opt,name=operation,proto3" json:"operation,omitempty"` +} + +func (x *OperationEvent) Reset() { + *x = OperationEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_operations_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OperationEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OperationEvent) ProtoMessage() {} + +func (x *OperationEvent) ProtoReflect() protoreflect.Message { + mi := &file_v1_operations_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OperationEvent.ProtoReflect.Descriptor instead. +func (*OperationEvent) Descriptor() ([]byte, []int) { + return file_v1_operations_proto_rawDescGZIP(), []int{1} +} + +func (x *OperationEvent) GetType() OperationEventType { + if x != nil { + return x.Type + } + return OperationEventType_EVENT_UNKNOWN +} + +func (x *OperationEvent) GetOperation() *Operation { + if x != nil { + return x.Operation + } + return nil +} + type OperationBackup struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - RepoId string `protobuf:"bytes,1,opt,name=repo_id,json=repoId,proto3" json:"repo_id,omitempty"` - PlanId string `protobuf:"bytes,2,opt,name=plan_id,json=planId,proto3" json:"plan_id,omitempty"` LastStatus *BackupProgressEntry `protobuf:"bytes,3,opt,name=last_status,json=lastStatus,proto3" json:"last_status,omitempty"` } func (x *OperationBackup) Reset() { *x = OperationBackup{} if protoimpl.UnsafeEnabled { - mi := &file_v1_operations_proto_msgTypes[1] + mi := &file_v1_operations_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -189,7 +328,7 @@ func (x *OperationBackup) String() string { func (*OperationBackup) ProtoMessage() {} func (x *OperationBackup) ProtoReflect() protoreflect.Message { - mi := &file_v1_operations_proto_msgTypes[1] + mi := &file_v1_operations_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -202,21 +341,7 @@ func (x *OperationBackup) ProtoReflect() protoreflect.Message { // Deprecated: Use OperationBackup.ProtoReflect.Descriptor instead. func (*OperationBackup) Descriptor() ([]byte, []int) { - return file_v1_operations_proto_rawDescGZIP(), []int{1} -} - -func (x *OperationBackup) GetRepoId() string { - if x != nil { - return x.RepoId - } - return "" -} - -func (x *OperationBackup) GetPlanId() string { - if x != nil { - return x.PlanId - } - return "" + return file_v1_operations_proto_rawDescGZIP(), []int{2} } func (x *OperationBackup) GetLastStatus() *BackupProgressEntry { @@ -231,37 +356,54 @@ var File_v1_operations_proto protoreflect.FileDescriptor var file_v1_operations_proto_rawDesc = []byte{ 0x0a, 0x13, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x76, 0x31, 0x1a, 0x0f, 0x76, 0x31, 0x2f, 0x72, 0x65, - 0x73, 0x74, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc3, 0x01, 0x0a, 0x09, 0x4f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x74, 0x69, - 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0f, 0x75, 0x6e, 0x69, 0x78, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, - 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x10, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, - 0x65, 0x6e, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x75, 0x6e, - 0x69, 0x78, 0x54, 0x69, 0x6d, 0x65, 0x45, 0x6e, 0x64, 0x4d, 0x73, 0x12, 0x2d, 0x0a, 0x06, 0x62, - 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x76, 0x31, - 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x48, 0x00, 0x52, 0x06, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x42, 0x04, 0x0a, 0x02, 0x6f, 0x70, - 0x22, 0x7d, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x63, - 0x6b, 0x75, 0x70, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, - 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, - 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 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, 0x2a, - 0x64, 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, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, - 0x2f, 0x72, 0x65, 0x73, 0x74, 0x69, 0x63, 0x75, 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x74, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xae, 0x02, 0x0a, 0x09, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, + 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x76, 0x31, 0x2e, + 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x75, 0x6e, 0x69, 0x78, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x0f, 0x75, 0x6e, 0x69, 0x78, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, + 0x72, 0x74, 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x10, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x5f, 0x65, 0x6e, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, + 0x75, 0x6e, 0x69, 0x78, 0x54, 0x69, 0x6d, 0x65, 0x45, 0x6e, 0x64, 0x4d, 0x73, 0x12, 0x27, 0x0a, + 0x0f, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, + 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x48, 0x00, 0x52, 0x06, 0x62, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x42, 0x04, 0x0a, 0x02, 0x6f, 0x70, 0x22, 0x69, 0x0a, 0x0e, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2a, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x76, 0x31, + 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2b, 0x0a, 0x09, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x76, + 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x4b, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x38, 0x0a, 0x0b, 0x6c, 0x61, 0x73, + 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, + 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 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, 0x2a, 0x4d, 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, 0x2a, 0x76, 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, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x41, 0x54, + 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, + 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x72, 0x65, 0x73, 0x74, 0x69, 0x63, 0x75, 0x69, 0x2f, 0x67, + 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -276,23 +418,27 @@ func file_v1_operations_proto_rawDescGZIP() []byte { return file_v1_operations_proto_rawDescData } -var file_v1_operations_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_v1_operations_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_v1_operations_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_v1_operations_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_v1_operations_proto_goTypes = []interface{}{ - (OperationStatus)(0), // 0: v1.OperationStatus - (*Operation)(nil), // 1: v1.Operation - (*OperationBackup)(nil), // 2: v1.OperationBackup - (*BackupProgressEntry)(nil), // 3: v1.BackupProgressEntry + (OperationEventType)(0), // 0: v1.OperationEventType + (OperationStatus)(0), // 1: v1.OperationStatus + (*Operation)(nil), // 2: v1.Operation + (*OperationEvent)(nil), // 3: v1.OperationEvent + (*OperationBackup)(nil), // 4: v1.OperationBackup + (*BackupProgressEntry)(nil), // 5: v1.BackupProgressEntry } var file_v1_operations_proto_depIdxs = []int32{ - 0, // 0: v1.Operation.status:type_name -> v1.OperationStatus - 2, // 1: v1.Operation.backup:type_name -> v1.OperationBackup - 3, // 2: v1.OperationBackup.last_status:type_name -> v1.BackupProgressEntry - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 1, // 0: v1.Operation.status:type_name -> v1.OperationStatus + 4, // 1: v1.Operation.backup:type_name -> v1.OperationBackup + 0, // 2: v1.OperationEvent.type:type_name -> v1.OperationEventType + 2, // 3: v1.OperationEvent.operation:type_name -> v1.Operation + 5, // 4: v1.OperationBackup.last_status:type_name -> v1.BackupProgressEntry + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name } func init() { file_v1_operations_proto_init() } @@ -315,6 +461,18 @@ func file_v1_operations_proto_init() { } } file_v1_operations_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OperationEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_operations_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*OperationBackup); i { case 0: return &v.state @@ -335,8 +493,8 @@ func file_v1_operations_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_operations_proto_rawDesc, - NumEnums: 1, - NumMessages: 2, + NumEnums: 2, + NumMessages: 3, NumExtensions: 0, NumServices: 0, }, diff --git a/gen/go/v1/service.pb.go b/gen/go/v1/service.pb.go index cc649b21..43a192da 100644 --- a/gen/go/v1/service.pb.go +++ b/gen/go/v1/service.pb.go @@ -84,44 +84,52 @@ var file_v1_service_proto_rawDesc = []byte{ 0x0a, 0x10, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x76, 0x31, 0x1a, 0x0f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x73, 0x74, - 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, - 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, - 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, - 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, - 0x32, 0x82, 0x03, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, 0x55, 0x49, 0x12, 0x43, 0x0a, - 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x12, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x12, 0x3a, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x0a, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x3a, - 0x01, 0x2a, 0x22, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3b, - 0x0a, 0x07, 0x41, 0x64, 0x64, 0x52, 0x65, 0x70, 0x6f, 0x12, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x70, 0x6f, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, - 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x12, 0x5b, 0x0a, 0x0d, 0x4c, - 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x12, 0x18, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, - 0x69, 0x63, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x18, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01, 0x2a, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x73, - 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x12, 0x5b, 0x0a, 0x10, 0x50, 0x61, 0x74, 0x68, - 0x41, 0x75, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x12, 0x2e, 0x74, - 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x1a, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, - 0x69, 0x73, 0x74, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, - 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, - 0x2f, 0x70, 0x61, 0x74, 0x68, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, - 0x2f, 0x72, 0x65, 0x73, 0x74, 0x69, 0x63, 0x75, 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x13, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x48, 0x0a, 0x14, 0x4c, + 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x32, 0xe5, 0x03, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, + 0x55, 0x49, 0x12, 0x43, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76, 0x31, + 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3a, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x15, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x0f, 0x3a, 0x01, 0x2a, 0x22, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x3b, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x52, 0x65, 0x70, 0x6f, 0x12, 0x08, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, 0x22, + 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x72, 0x65, 0x70, 0x6f, + 0x12, 0x61, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, + 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x22, 0x1d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, + 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x73, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, + 0x6f, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x18, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x3a, 0x01, + 0x2a, 0x22, 0x0d, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, + 0x12, 0x5b, 0x0a, 0x10, 0x50, 0x61, 0x74, 0x68, 0x41, 0x75, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x20, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x6f, + 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x42, 0x2e, 0x5a, + 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, + 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x72, 0x65, 0x73, 0x74, 0x69, 0x63, 0x75, + 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -143,22 +151,25 @@ var file_v1_service_proto_goTypes = []interface{}{ (*Config)(nil), // 2: v1.Config (*Repo)(nil), // 3: v1.Repo (*types.StringValue)(nil), // 4: types.StringValue - (*ResticSnapshotList)(nil), // 5: v1.ResticSnapshotList - (*types.StringList)(nil), // 6: types.StringList + (*OperationEvent)(nil), // 5: v1.OperationEvent + (*ResticSnapshotList)(nil), // 6: v1.ResticSnapshotList + (*types.StringList)(nil), // 7: types.StringList } var file_v1_service_proto_depIdxs = []int32{ 1, // 0: v1.ResticUI.GetConfig:input_type -> google.protobuf.Empty 2, // 1: v1.ResticUI.SetConfig:input_type -> v1.Config 3, // 2: v1.ResticUI.AddRepo:input_type -> v1.Repo - 0, // 3: v1.ResticUI.ListSnapshots:input_type -> v1.ListSnapshotsRequest - 4, // 4: v1.ResticUI.PathAutocomplete:input_type -> types.StringValue - 2, // 5: v1.ResticUI.GetConfig:output_type -> v1.Config - 2, // 6: v1.ResticUI.SetConfig:output_type -> v1.Config - 2, // 7: v1.ResticUI.AddRepo:output_type -> v1.Config - 5, // 8: v1.ResticUI.ListSnapshots:output_type -> v1.ResticSnapshotList - 6, // 9: v1.ResticUI.PathAutocomplete:output_type -> types.StringList - 5, // [5:10] is the sub-list for method output_type - 0, // [0:5] is the sub-list for method input_type + 1, // 3: v1.ResticUI.GetOperationEvents:input_type -> google.protobuf.Empty + 0, // 4: v1.ResticUI.ListSnapshots:input_type -> v1.ListSnapshotsRequest + 4, // 5: v1.ResticUI.PathAutocomplete:input_type -> types.StringValue + 2, // 6: v1.ResticUI.GetConfig:output_type -> v1.Config + 2, // 7: v1.ResticUI.SetConfig:output_type -> v1.Config + 2, // 8: v1.ResticUI.AddRepo:output_type -> v1.Config + 5, // 9: v1.ResticUI.GetOperationEvents:output_type -> v1.OperationEvent + 6, // 10: v1.ResticUI.ListSnapshots:output_type -> v1.ResticSnapshotList + 7, // 11: v1.ResticUI.PathAutocomplete:output_type -> types.StringList + 6, // [6:12] is the sub-list for method output_type + 0, // [0:6] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name @@ -171,6 +182,7 @@ func file_v1_service_proto_init() { } file_v1_config_proto_init() file_v1_restic_proto_init() + file_v1_operations_proto_init() if !protoimpl.UnsafeEnabled { file_v1_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListSnapshotsRequest); i { diff --git a/gen/go/v1/service.pb.gw.go b/gen/go/v1/service.pb.gw.go index e7faf81d..4f9f8b30 100644 --- a/gen/go/v1/service.pb.gw.go +++ b/gen/go/v1/service.pb.gw.go @@ -119,6 +119,23 @@ func local_request_ResticUI_AddRepo_0(ctx context.Context, marshaler runtime.Mar } +func request_ResticUI_GetOperationEvents_0(ctx context.Context, marshaler runtime.Marshaler, client ResticUIClient, req *http.Request, pathParams map[string]string) (ResticUI_GetOperationEventsClient, runtime.ServerMetadata, error) { + var protoReq emptypb.Empty + var metadata runtime.ServerMetadata + + stream, err := client.GetOperationEvents(ctx, &protoReq) + if err != nil { + return nil, metadata, err + } + header, err := stream.Header() + if err != nil { + return nil, metadata, err + } + metadata.HeaderMD = header + return stream, metadata, nil + +} + func request_ResticUI_ListSnapshots_0(ctx context.Context, marshaler runtime.Marshaler, client ResticUIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ListSnapshotsRequest var metadata runtime.ServerMetadata @@ -268,6 +285,13 @@ func RegisterResticUIHandlerServer(ctx context.Context, mux *runtime.ServeMux, s }) + mux.Handle("GET", pattern_ResticUI_GetOperationEvents_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") + _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + }) + mux.Handle("POST", pattern_ResticUI_ListSnapshots_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -425,6 +449,28 @@ func RegisterResticUIHandlerClient(ctx context.Context, mux *runtime.ServeMux, c }) + mux.Handle("GET", pattern_ResticUI_GetOperationEvents_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/v1.ResticUI/GetOperationEvents", runtime.WithHTTPPathPattern("/v1/events/operations")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_ResticUI_GetOperationEvents_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_ResticUI_GetOperationEvents_0(annotatedContext, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_ResticUI_ListSnapshots_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -479,6 +525,8 @@ var ( pattern_ResticUI_AddRepo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "config", "repo"}, "")) + pattern_ResticUI_GetOperationEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "events", "operations"}, "")) + pattern_ResticUI_ListSnapshots_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "snapshots"}, "")) pattern_ResticUI_PathAutocomplete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "autocomplete", "path"}, "")) @@ -491,6 +539,8 @@ var ( forward_ResticUI_AddRepo_0 = runtime.ForwardResponseMessage + forward_ResticUI_GetOperationEvents_0 = runtime.ForwardResponseStream + forward_ResticUI_ListSnapshots_0 = runtime.ForwardResponseMessage forward_ResticUI_PathAutocomplete_0 = runtime.ForwardResponseMessage diff --git a/gen/go/v1/service_grpc.pb.go b/gen/go/v1/service_grpc.pb.go index 7812cedc..7f423360 100644 --- a/gen/go/v1/service_grpc.pb.go +++ b/gen/go/v1/service_grpc.pb.go @@ -21,11 +21,12 @@ import ( const _ = grpc.SupportPackageIsVersion7 const ( - ResticUI_GetConfig_FullMethodName = "/v1.ResticUI/GetConfig" - ResticUI_SetConfig_FullMethodName = "/v1.ResticUI/SetConfig" - ResticUI_AddRepo_FullMethodName = "/v1.ResticUI/AddRepo" - ResticUI_ListSnapshots_FullMethodName = "/v1.ResticUI/ListSnapshots" - ResticUI_PathAutocomplete_FullMethodName = "/v1.ResticUI/PathAutocomplete" + ResticUI_GetConfig_FullMethodName = "/v1.ResticUI/GetConfig" + ResticUI_SetConfig_FullMethodName = "/v1.ResticUI/SetConfig" + ResticUI_AddRepo_FullMethodName = "/v1.ResticUI/AddRepo" + ResticUI_GetOperationEvents_FullMethodName = "/v1.ResticUI/GetOperationEvents" + ResticUI_ListSnapshots_FullMethodName = "/v1.ResticUI/ListSnapshots" + ResticUI_PathAutocomplete_FullMethodName = "/v1.ResticUI/PathAutocomplete" ) // ResticUIClient is the client API for ResticUI service. @@ -35,6 +36,7 @@ type ResticUIClient interface { GetConfig(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*Config, error) SetConfig(ctx context.Context, in *Config, opts ...grpc.CallOption) (*Config, error) AddRepo(ctx context.Context, in *Repo, opts ...grpc.CallOption) (*Config, error) + GetOperationEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (ResticUI_GetOperationEventsClient, error) ListSnapshots(ctx context.Context, in *ListSnapshotsRequest, opts ...grpc.CallOption) (*ResticSnapshotList, error) PathAutocomplete(ctx context.Context, in *types.StringValue, opts ...grpc.CallOption) (*types.StringList, error) } @@ -74,6 +76,38 @@ func (c *resticUIClient) AddRepo(ctx context.Context, in *Repo, opts ...grpc.Cal return out, nil } +func (c *resticUIClient) GetOperationEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (ResticUI_GetOperationEventsClient, error) { + stream, err := c.cc.NewStream(ctx, &ResticUI_ServiceDesc.Streams[0], ResticUI_GetOperationEvents_FullMethodName, opts...) + if err != nil { + return nil, err + } + x := &resticUIGetOperationEventsClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type ResticUI_GetOperationEventsClient interface { + Recv() (*OperationEvent, error) + grpc.ClientStream +} + +type resticUIGetOperationEventsClient struct { + grpc.ClientStream +} + +func (x *resticUIGetOperationEventsClient) Recv() (*OperationEvent, error) { + m := new(OperationEvent) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + func (c *resticUIClient) ListSnapshots(ctx context.Context, in *ListSnapshotsRequest, opts ...grpc.CallOption) (*ResticSnapshotList, error) { out := new(ResticSnapshotList) err := c.cc.Invoke(ctx, ResticUI_ListSnapshots_FullMethodName, in, out, opts...) @@ -99,6 +133,7 @@ type ResticUIServer interface { GetConfig(context.Context, *emptypb.Empty) (*Config, error) SetConfig(context.Context, *Config) (*Config, error) AddRepo(context.Context, *Repo) (*Config, error) + GetOperationEvents(*emptypb.Empty, ResticUI_GetOperationEventsServer) error ListSnapshots(context.Context, *ListSnapshotsRequest) (*ResticSnapshotList, error) PathAutocomplete(context.Context, *types.StringValue) (*types.StringList, error) mustEmbedUnimplementedResticUIServer() @@ -117,6 +152,9 @@ func (UnimplementedResticUIServer) SetConfig(context.Context, *Config) (*Config, func (UnimplementedResticUIServer) AddRepo(context.Context, *Repo) (*Config, error) { return nil, status.Errorf(codes.Unimplemented, "method AddRepo not implemented") } +func (UnimplementedResticUIServer) GetOperationEvents(*emptypb.Empty, ResticUI_GetOperationEventsServer) error { + return status.Errorf(codes.Unimplemented, "method GetOperationEvents not implemented") +} func (UnimplementedResticUIServer) ListSnapshots(context.Context, *ListSnapshotsRequest) (*ResticSnapshotList, error) { return nil, status.Errorf(codes.Unimplemented, "method ListSnapshots not implemented") } @@ -190,6 +228,27 @@ func _ResticUI_AddRepo_Handler(srv interface{}, ctx context.Context, dec func(in return interceptor(ctx, in, info, handler) } +func _ResticUI_GetOperationEvents_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(emptypb.Empty) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(ResticUIServer).GetOperationEvents(m, &resticUIGetOperationEventsServer{stream}) +} + +type ResticUI_GetOperationEventsServer interface { + Send(*OperationEvent) error + grpc.ServerStream +} + +type resticUIGetOperationEventsServer struct { + grpc.ServerStream +} + +func (x *resticUIGetOperationEventsServer) Send(m *OperationEvent) error { + return x.ServerStream.SendMsg(m) +} + func _ResticUI_ListSnapshots_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListSnapshotsRequest) if err := dec(in); err != nil { @@ -254,6 +313,12 @@ var ResticUI_ServiceDesc = grpc.ServiceDesc{ Handler: _ResticUI_PathAutocomplete_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "GetOperationEvents", + Handler: _ResticUI_GetOperationEvents_Handler, + ServerStreams: true, + }, + }, Metadata: "v1/service.proto", } diff --git a/go.mod b/go.mod index 61c2116b..b85e5c62 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 github.com/hashicorp/go-multierror v1.1.1 github.com/mattn/go-colorable v0.1.13 + go.etcd.io/bbolt v1.3.8 go.uber.org/zap v1.26.0 google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 google.golang.org/grpc v1.59.0 @@ -17,12 +18,30 @@ require ( ) require ( + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgraph-io/badger/v3 v3.2103.5 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/glog v1.1.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/flatbuffers v23.5.26+incompatible // indirect + github.com/google/orderedcode v0.0.1 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/klauspost/compress v1.17.2 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ostafen/clover v1.2.0 // indirect + github.com/ostafen/clover/v2 v2.0.0-alpha.3 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/satori/go.uuid v1.2.0 // indirect github.com/vine-io/vine v1.6.16 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + go.opencensus.io v0.24.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.18.0 // indirect golang.org/x/sync v0.5.0 // indirect diff --git a/go.sum b/go.sum index 58079eb8..263afcd1 100644 --- a/go.sum +++ b/go.sum @@ -1,30 +1,86 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/brianvoe/gofakeit/v6 v6.17.0/go.mod h1:Ow6qC71xtwm79anlwKRlWZW6zVq9D2XHE4QSSMP/rU8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger/v3 v3.2103.2/go.mod h1:RHo4/GmYcKKh5Lxu63wLEMHJ70Pac2JqZRYGhlyAo2M= +github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= +github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= +github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gitploy-io/cronexpr v0.2.2 h1:Au+wK6FqmOLAF7AkW6q4gnrNXTe3rEW97XFZ4chy0xs= github.com/gitploy-io/cronexpr v0.2.2/go.mod h1:Uep5sbzUSocMZvJ1s0lNI9zi37s5iUI1llkw3vRGK9M= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v2.0.6+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v23.5.26+incompatible h1:M9dgRyhJemaM4Sw8+66GHBu8ioaQmyPLg1b8VwK5WJg= +github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/orderedcode v0.0.1 h1:UzfcAexk9Vhv8+9pNOgRu41f16lHq725vPwnSeiG/Us= +github.com/google/orderedcode v0.0.1/go.mod h1:iVyU4/qPKHY5h/wSd6rZZCDcLJNxiWO6dvsYES2Sb20= github.com/google/renameio v1.0.1 h1:Lh/jXZmvZxb0BBeSY5VKEfidcbcbenKjZFzM/q0fSeU= github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -42,23 +98,79 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/ostafen/clover v1.2.0 h1:9y/Uy/T0C0rcPrVt9UlB+KtkVnLx8+/1g4TTUa+aJGc= +github.com/ostafen/clover v1.2.0/go.mod h1:FUueveVNOVH62aIk+54GcYFE8kFeYIMtTtJ5g2fllIU= +github.com/ostafen/clover/v2 v2.0.0-alpha.3 h1:fXC7tVHQkUPFlxlj/kD98h0ngrTpIeJymaxVIqDzw3Q= +github.com/ostafen/clover/v2 v2.0.0-alpha.3/go.mod h1:5YCDt+wJDUNN1uSXE5csxSQBuJrNjidkOkJTXWuNhDY= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/vine-io/vine v1.6.16 h1:KZ1sqxjdeCNJ+rOVKAH+hysQeXqzSJ7nrJPcMtfCKbs= github.com/vine-io/vine v1.6.16/go.mod h1:Ur2SyDUlnwvanr8uc1vx8fa+Eq06Zlpr6rNozKLYyVs= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= +go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -78,6 +190,9 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220728211354-c7608f3a8462/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= @@ -87,22 +202,37 @@ golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -120,8 +250,10 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405 h1:I6WNifs6pF9tNdSob2W24JtyxIYjzFB9qDlpUC76q+U= google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4= google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= @@ -133,18 +265,33 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/api/api.go b/internal/api/api.go index 4910b135..8f1f1cb8 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -9,7 +9,6 @@ import ( "path/filepath" v1 "github.com/garethgeorge/resticui/gen/go/v1" - "github.com/garethgeorge/resticui/internal/orchestrator" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "go.uber.org/zap" @@ -55,7 +54,7 @@ func loggingFunc(l *zap.Logger) logging.Logger { } -func serveGRPC(ctx context.Context, orchestrator *orchestrator.Orchestrator, socket string) error { +func serveGRPC(ctx context.Context, socket string, server *Server) error { lis, err := net.Listen("unix", socket) if err != nil { return fmt.Errorf("failed to listen: %w", err) @@ -70,7 +69,7 @@ func serveGRPC(ctx context.Context, orchestrator *orchestrator.Orchestrator, soc logging.StreamServerInterceptor(loggingFunc(logger)), ), ) - v1.RegisterResticUIServer(grpcServer, NewServer(orchestrator)) + v1.RegisterResticUIServer(grpcServer, server) go func() { <-ctx.Done() grpcServer.GracefulStop() @@ -82,7 +81,7 @@ func serveGRPC(ctx context.Context, orchestrator *orchestrator.Orchestrator, soc return nil } -func serveHTTPHandlers(ctx context.Context, orchestrator *orchestrator.Orchestrator, mux *runtime.ServeMux) error { +func serveHTTPHandlers(ctx context.Context, server *Server, mux *runtime.ServeMux) error { tmpDir, err := os.MkdirTemp("", "resticui") if err != nil { return fmt.Errorf("failed to create temp dir for unix domain socket: %w", err) @@ -99,7 +98,7 @@ func serveHTTPHandlers(ctx context.Context, orchestrator *orchestrator.Orchestra return fmt.Errorf("failed to register gateway: %w", err) } - if err := serveGRPC(ctx, orchestrator, socket); err != nil { + if err := serveGRPC(ctx, socket, server); err != nil { return err } @@ -107,8 +106,8 @@ func serveHTTPHandlers(ctx context.Context, orchestrator *orchestrator.Orchestra } // Handler returns an http.Handler serving the API, cancel the context to cleanly shut down the server. -func ServeAPI(ctx context.Context, orchestrator *orchestrator.Orchestrator, mux *http.ServeMux) error { +func ServeAPI(ctx context.Context, server *Server, mux *http.ServeMux) error { apiMux := runtime.NewServeMux() mux.Handle("/api/", http.StripPrefix("/api", apiMux)) - return serveHTTPHandlers(ctx, orchestrator, apiMux) + return serveHTTPHandlers(ctx, server, apiMux) } \ No newline at end of file diff --git a/internal/api/server.go b/internal/api/server.go index be5c726d..94a62626 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -11,6 +11,7 @@ import ( "github.com/garethgeorge/resticui/gen/go/types" v1 "github.com/garethgeorge/resticui/gen/go/v1" "github.com/garethgeorge/resticui/internal/config" + "github.com/garethgeorge/resticui/internal/oplog" "github.com/garethgeorge/resticui/internal/orchestrator" "github.com/garethgeorge/resticui/pkg/restic" "go.uber.org/zap" @@ -21,6 +22,7 @@ import ( type Server struct { *v1.UnimplementedResticUIServer orchestrator *orchestrator.Orchestrator + oplog *oplog.OpLog reqId atomic.Uint64 eventChannelsMu sync.Mutex @@ -29,26 +31,12 @@ type Server struct { var _ v1.ResticUIServer = &Server{} -func NewServer(orchestrator *orchestrator.Orchestrator) *Server { +func NewServer(orchestrator *orchestrator.Orchestrator, oplog *oplog.OpLog) *Server { s := &Server{ eventChannels: make(map[uint64]chan *v1.Event), orchestrator: orchestrator, } - // go func() { - // // TODO: disable this when proper event sources are implemented. - // for { - // time.Sleep(3 * time.Second) - // s.PublishEvent(&v1.Event{ - // Event: &v1.Event_Log{ - // Log: &v1.LogEvent{ - // Message: fmt.Sprintf("event push test, it is %v", time.Now().Format(time.RFC3339)), - // }, - // }, - // }) - // } - // }() - return s } @@ -140,23 +128,38 @@ func (s *Server) ListSnapshots(ctx context.Context, query *v1.ListSnapshotsReque } // GetEvents implements GET /v1/events -func (s *Server) GetEvents(_ *emptypb.Empty, stream v1.ResticUI_GetEventsServer) error { - reqId := s.reqId.Add(1) - - // Register a channel to receive events for this invocation - s.eventChannelsMu.Lock() - eventChan := make(chan *v1.Event, 3) - s.eventChannels[reqId] = eventChan - s.eventChannelsMu.Unlock() - defer delete(s.eventChannels, reqId) - - for { - select { - case <-stream.Context().Done(): - return nil - case event := <-eventChan: - stream.Send(event) +func (s *Server) GetOperationEvents(_ *emptypb.Empty, stream v1.ResticUI_GetOperationEventsServer) error { + errorChan := make(chan error) + defer close(errorChan) + callback := func(eventType oplog.EventType, op *v1.Operation) { + var eventTypeMapped v1.OperationEventType + switch eventType { + case oplog.EventTypeOpCreated: + eventTypeMapped = v1.OperationEventType_EVENT_CREATED + case oplog.EventTypeOpUpdated: + eventTypeMapped = v1.OperationEventType_EVENT_UPDATED + default: + zap.S().Error("Unknown event type", zap.Int("eventType", int(eventType))) + eventTypeMapped = v1.OperationEventType_EVENT_UNKNOWN } + + event := &v1.OperationEvent{ + Type: eventTypeMapped, + Operation: op, + } + + if err := stream.Send(event); err != nil { + errorChan <- fmt.Errorf("failed to send event: %w", err) + } + } + s.oplog.Subscribe(&callback) + defer s.oplog.Unsubscribe(&callback) + + select { + case <-stream.Context().Done(): + return nil + case err := <-errorChan: + return err } } diff --git a/internal/oplog/oplog.go b/internal/oplog/oplog.go new file mode 100644 index 00000000..1c465c0f --- /dev/null +++ b/internal/oplog/oplog.go @@ -0,0 +1,297 @@ +package oplog + +import ( + "bytes" + "errors" + "fmt" + "sync" + "time" + + v1 "github.com/garethgeorge/resticui/gen/go/v1" + bolt "go.etcd.io/bbolt" + "google.golang.org/protobuf/proto" +) + + +type EventType int + +const ( + EventTypeUnknown = EventType(iota) + EventTypeOpCreated = EventType(iota) + EventTypeOpUpdated = EventType(iota) +) + +var ( + SystemBucket = []byte("oplog.system") // system stores metadata + OpLogBucket = []byte("oplog.log") // oplog stores the operations themselves + RepoIndexBucket = []byte("oplog.repo_idx") // repo_index tracks IDs of operations affecting a given repo + PlanIndexBucket = []byte("oplog.plan_idx") // plan_index tracks IDs of operations affecting a given plan +) + + +// OpLog represents a log of operations performed. +// TODO: implement trim support for old operations. +type OpLog struct { + db *bolt.DB + + subscribersMu sync.RWMutex + subscribers []*func(EventType, *v1.Operation) +} + +func NewOpLog(databaseDir string) (*OpLog, error) { + db, err := bolt.Open(databaseDir, 0600, &bolt.Options{Timeout: 1 * time.Second}) + if err != nil { + return nil, fmt.Errorf("error opening database: %s", err) + } + + // Create the buckets if they don't exist + if err := db.Update(func(tx *bolt.Tx) error { + for _, bucket := range [][]byte{SystemBucket, OpLogBucket, RepoIndexBucket, PlanIndexBucket} { + if _, err := tx.CreateBucketIfNotExists(bucket); err != nil { + return fmt.Errorf("error creating bucket %s: %s", string(bucket), err) + } + } + return nil + }); err != nil { + return nil, err + } + + return &OpLog{db: db}, nil +} + +func (o *OpLog) Close() error { + return o.db.Close() +} + +func (o *OpLog) Add(op *v1.Operation) error { + if op.Id != 0 { + return errors.New("operation already has an ID, OpLog.Add is expected to set the ID") + } + + err := o.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(OpLogBucket) + + id, err := b.NextSequence() + if err != nil { + return fmt.Errorf("error getting next sequence: %w", err) + } + + op.Id = int64(id) + + bytes, err := proto.Marshal(op) + if err != nil { + return fmt.Errorf("error marshalling operation: %w", err) + } + + if err := b.Put(itob(op.Id), bytes); err != nil { + return fmt.Errorf("error putting operation into bucket: %w", err) + } + + if op.RepoId != "" { + if err := o.addOpToIndexBucket(tx, RepoIndexBucket, op.RepoId, op.Id); err != nil { + return fmt.Errorf("error adding operation to repo index: %w", err) + } + } + + if op.PlanId != "" { + if err := o.addOpToIndexBucket(tx, PlanIndexBucket, op.PlanId, op.Id); err != nil { + return fmt.Errorf("error adding operation to plan index: %w", err) + } + } + + return nil + }) + if err != nil { + o.subscribersMu.RLock() + defer o.subscribersMu.RUnlock() + for _, sub := range o.subscribers { + (*sub)(EventTypeOpCreated, op) + } + } + return err +} + +func (o *OpLog) Update(op *v1.Operation) error { + if op.Id == 0 { + return errors.New("operation does not have an ID, OpLog.Update expects operation with an ID") + } + + err := o.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(OpLogBucket) + + if b.Get(itob(op.Id)) == nil { + return fmt.Errorf("operation with ID %d does not exist", op.Id) + } + + bytes, err := proto.Marshal(op) + if err != nil { + return fmt.Errorf("error marshalling operation: %w", err) + } + + + if err := b.Put(itob(op.Id), bytes); err != nil { + return fmt.Errorf("error putting operation into bucket: %w", err) + } + + return nil + }) + if err != nil { + o.subscribersMu.RLock() + defer o.subscribersMu.RUnlock() + for _, sub := range o.subscribers { + (*sub)(EventTypeOpUpdated, op) + } + } + return err +} + +func (o *OpLog) getHelper(b *bolt.Bucket, id int64) (*v1.Operation, error) { + bytes := b.Get(itob(id)) + if bytes == nil { + return nil, fmt.Errorf("operation with ID %d does not exist", id) + } + + var op v1.Operation + if err := proto.Unmarshal(bytes, &op); err != nil { + return nil, fmt.Errorf("error unmarshalling operation: %w", err) + } + + return &op, nil +} + +func (o *OpLog) Get(id int64) (*v1.Operation, error) { + var op *v1.Operation + if err := o.db.View(func(tx *bolt.Tx) error { + var err error + op, err = o.getHelper(tx.Bucket(OpLogBucket), id) + return err + }); err != nil { + return nil, err + } + return op, nil +} + +func (o *OpLog) GetByRepo(repoId string, filter Filter) ([]*v1.Operation, error) { + var ops []*v1.Operation + if err := o.db.View(func(tx *bolt.Tx) error { + ids, err := o.readOpsFromIndexBucket(tx, RepoIndexBucket, repoId) + if err != nil { + return err + } + + b := tx.Bucket(OpLogBucket) + for _, id := range ids { + op, err := o.getHelper(b, id) + if err != nil { + return err + } + ops = append(ops, op) + } + + return nil + }); err != nil { + return nil, err + } + return ops, nil +} + +func (o *OpLog) GetByPlan(planId string, filter Filter) ([]*v1.Operation, error) { + var ops []*v1.Operation + if err := o.db.View(func(tx *bolt.Tx) error { + ids, err := o.readOpsFromIndexBucket(tx, PlanIndexBucket, planId) + if err != nil { + return err + } + + b := tx.Bucket(OpLogBucket) + for _, id := range ids { + op, err := o.getHelper(b, id) + if err != nil { + return err + } + ops = append(ops, op) + } + + return nil + }); err != nil { + return nil, err + } + return ops, nil +} + +func (o *OpLog) Subscribe(callback *func(EventType, *v1.Operation)) { + o.subscribersMu.Lock() + defer o.subscribersMu.Unlock() + o.subscribers = append(o.subscribers, callback) +} + +func (o *OpLog) Unsubscribe(callback *func(EventType, *v1.Operation)) { + o.subscribersMu.Lock() + defer o.subscribersMu.Unlock() + subs := o.subscribers + for i, c := range subs { + if c == callback { + subs[i] = subs[len(subs) - 1] + o.subscribers = subs[:len(o.subscribers) - 1] + } + } +} + +// addOpToIndexBucket adds the given operation ID to the given index bucket for the given key ID. +func (o *OpLog) addOpToIndexBucket(tx *bolt.Tx, bucket []byte, indexId string, opId int64) error { + b := tx.Bucket(bucket) + + var key []byte + key = append(key, itob(int64(len(indexId)))...) + key = append(key, []byte(indexId)...) + key = append(key, itob(opId)...) + if err := b.Put(key, []byte{}); err != nil { + return fmt.Errorf("error adding operation to repo index: %w", err) + } + return nil +} + +// readOpsFromIndexBucket reads all operations from the given index bucket for the given key ID. +func (o *OpLog) readOpsFromIndexBucket(tx *bolt.Tx, bucket []byte, indexId string) ([]int64, error) { + b := tx.Bucket(bucket) + + var ops []int64 + c := b.Cursor() + var prefix []byte + prefix = append(prefix, itob(int64(len(indexId)))...) + prefix = append(prefix, []byte(indexId)...) + for k, _ := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, _ = c.Next() { + ops = append(ops, btoi(k[len(prefix):])) + } + + return ops, nil +} + +type Filter func([]int64)[]int64 + +func FilterKeepAll() Filter { + return func(ids []int64) []int64 { + return ids + } +} + +func FilterLastN(n int64) Filter { + return func(ids []int64) []int64 { + if len(ids) > int(n) { + ids = ids[len(ids)-int(n):] + } + return ids + } +} + +func FilterLimitOffset(limit, offset int64) Filter { + return func(ids []int64) []int64 { + if len(ids) > int(offset) { + ids = ids[offset:] + } + if len(ids) > int(limit) { + ids = ids[:limit] + } + return ids + } +} diff --git a/internal/oplog/oplog_test.go b/internal/oplog/oplog_test.go new file mode 100644 index 00000000..8ef43969 --- /dev/null +++ b/internal/oplog/oplog_test.go @@ -0,0 +1,205 @@ +package oplog + +import ( + "slices" + "testing" + + v1 "github.com/garethgeorge/resticui/gen/go/v1" +) + +func TestCreate(t *testing.T) { + t.Parallel() + log, err := NewOpLog(t.TempDir() + "/test.boltdb") + if err != nil { + t.Fatalf("error creating oplog: %s", err) + } + if err := log.Close(); err != nil { + t.Fatalf("error closing oplog: %s", err) + } +} + +func TestAddOperation(t *testing.T) { + t.Parallel() + log, err := NewOpLog(t.TempDir() + "/test.boltdb") + if err != nil { + t.Fatalf("error creating oplog: %s", err) + } + defer log.Close() + + var tests = []struct { + name string + op *v1.Operation + wantErr bool + }{ + { + name: "basic operation", + op: &v1.Operation{ + Id: 0, + }, + wantErr: false, + }, + { + name: "operation with ID", + op: &v1.Operation{ + Id: 1, + }, + wantErr: true, + }, + { + name: "operation with repo", + op: &v1.Operation{ + Id: 0, + RepoId: "testrepo", + }, + }, + { + name: "operation with plan", + op: &v1.Operation{ + Id: 0, + PlanId: "testplan", + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if err := log.Add(tc.op); (err != nil) != tc.wantErr { + t.Errorf("Add() error = %v, wantErr %v", err, tc.wantErr) + } + if !tc.wantErr { + if tc.op.Id == 0 { + t.Errorf("Add() did not set op ID") + } + } + }) + } +} + +func TestListOperation(t *testing.T) { + t.Parallel() + log, err := NewOpLog(t.TempDir() + "/test.boltdb") + if err != nil { + t.Fatalf("error creating oplog: %s", err) + } + + // these should get assigned IDs 1-3 respectively by the oplog + ops := []*v1.Operation{ + { + PlanId: "plan1", + RepoId: "repo1", + DisplayMessage: "op1", + }, + { + PlanId: "plan1", + RepoId: "repo2", + DisplayMessage: "op2", + }, + { + PlanId: "plan2", + RepoId: "repo2", + DisplayMessage: "op3", + }, + } + + for _, op := range ops { + if err := log.Add(op); err != nil { + t.Fatalf("error adding operation: %s", err) + } + } + + tests := []struct { + name string + byPlan bool + byRepo bool + id string + expected []string + }{ + { + name: "list plan1", + byPlan: true, + id: "plan1", + expected: []string{"op1", "op2"}, + }, + { + name: "list plan2", + byPlan: true, + id: "plan2", + expected: []string{"op3"}, + }, + { + name: "list repo1", + byRepo: true, + id: "repo1", + expected: []string{"op1"}, + }, + { + name: "list repo2", + byRepo: true, + id: "repo2", + expected: []string{"op2", "op3"}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + var ops []*v1.Operation + var err error + if tc.byPlan { + ops, err = log.GetByPlan(tc.id, FilterKeepAll()) + } else if tc.byRepo { + ops, err = log.GetByRepo(tc.id, FilterKeepAll()) + } else { + t.Fatalf("must specify byPlan or byRepo") + } + if err != nil { + t.Fatalf("error listing operations: %s", err) + } + got := collectMessages(ops) + if slices.Compare(got, tc.expected) != 0 { + t.Errorf("want operations: %v, got unexpected operations: %v", tc.expected, got) + } + }) + } +} + +func TestBigIO(t *testing.T) { + t.Parallel() + log, err := NewOpLog(t.TempDir() + "/test.boltdb") + if err != nil { + t.Fatalf("error creating oplog: %s", err) + } + + for i := 0; i < 1000; i++ { + if err := log.Add(&v1.Operation{ + PlanId: "plan1", + RepoId: "repo1", + }); err != nil { + t.Fatalf("error adding operation: %s", err) + } + } + + ops, err := log.GetByPlan("plan1", FilterKeepAll()) + if err != nil { + t.Fatalf("error listing operations: %s", err) + } + if len(ops) != 1000 { + t.Errorf("want 1000 operations, got %d", len(ops)) + } + + ops, err = log.GetByRepo("repo1", FilterKeepAll()) + if err != nil { + t.Fatalf("error listing operations: %s", err) + } + if len(ops) != 1000 { + t.Errorf("want 1000 operations, got %d", len(ops)) + } +} + +func collectMessages(ops []*v1.Operation) []string { + var messages []string + for _, op := range ops { + messages = append(messages, op.DisplayMessage) + } + return messages +} \ No newline at end of file diff --git a/internal/oplog/serializationutil.go b/internal/oplog/serializationutil.go new file mode 100644 index 00000000..6267e14a --- /dev/null +++ b/internal/oplog/serializationutil.go @@ -0,0 +1,13 @@ +package oplog + +import "encoding/binary" + +func itob(v int64) []byte { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(v)) + return b +} + +func btoi(b []byte) int64 { + return int64(binary.BigEndian.Uint64(b)) +} \ No newline at end of file diff --git a/proto/v1/operations.proto b/proto/v1/operations.proto index 59dfd10f..61843a81 100644 --- a/proto/v1/operations.proto +++ b/proto/v1/operations.proto @@ -7,24 +7,41 @@ option go_package = "github.com/garethgeorge/resticui/go/proto/v1"; import "v1/restic.proto"; message Operation { - OperationStatus status = 1; - int64 unix_time_start_ms = 2; - int64 unix_time_end_ms = 3; + int64 id = 1; + string repo_id = 2; // repo id if associated with a repo (always true) + string plan_id = 3; // plan id if associated with a plan (always true) + OperationStatus status = 4; + int64 unix_time_start_ms = 5; + int64 unix_time_end_ms = 6; + string display_message = 7; // human readable context message (if any) oneof op { OperationBackup backup = 100; } } +// OperationEvent is used in the wireformat to stream operation changes to clients +message OperationEvent { + OperationEventType type = 1; + Operation operation = 2; +} + +// OperationEventType indicates whether the operation was created or updated +enum OperationEventType { + EVENT_UNKNOWN = 0; + EVENT_CREATED = 1; + EVENT_UPDATED = 2; +} + enum OperationStatus { STATUS_UNKNOWN = 0; STATUS_PENDING = 1; STATUS_INPROGRESS = 2; STATUS_SUCCESS = 3; + STATUS_ERROR = 4; } message OperationBackup { - string repo_id = 1; - string plan_id = 2; - BackupProgressEntry last_status = 1; + BackupProgressEntry last_status = 3; } + diff --git a/proto/v1/service.proto b/proto/v1/service.proto index 4991628c..6c1ced11 100644 --- a/proto/v1/service.proto +++ b/proto/v1/service.proto @@ -5,8 +5,8 @@ package v1; option go_package = "github.com/garethgeorge/resticui/go/proto/v1"; import "v1/config.proto"; -import "v1/events.proto"; import "v1/restic.proto"; +import "v1/operations.proto"; import "types/value.proto"; import "google/protobuf/empty.proto"; import "google/api/annotations.proto"; @@ -32,9 +32,9 @@ service ResticUI { }; } - rpc GetEvents (google.protobuf.Empty) returns (stream Event) { + rpc GetOperationEvents (google.protobuf.Empty) returns (stream OperationEvent) { option (google.api.http) = { - get: "/v1/events" + get: "/v1/events/operations" }; } @@ -56,4 +56,4 @@ service ResticUI { message ListSnapshotsRequest { string repo_id = 1; string plan_id = 2; -} +} \ No newline at end of file diff --git a/webui/gen/ts/v1/operations.pb.ts b/webui/gen/ts/v1/operations.pb.ts index 89c2cb59..ac072dfa 100644 --- a/webui/gen/ts/v1/operations.pb.ts +++ b/webui/gen/ts/v1/operations.pb.ts @@ -15,25 +15,39 @@ type OneOf = : never) : never); +export enum OperationEventType { + EVENT_UNKNOWN = "EVENT_UNKNOWN", + EVENT_CREATED = "EVENT_CREATED", + EVENT_UPDATED = "EVENT_UPDATED", +} + export enum OperationStatus { STATUS_UNKNOWN = "STATUS_UNKNOWN", STATUS_PENDING = "STATUS_PENDING", STATUS_INPROGRESS = "STATUS_INPROGRESS", STATUS_SUCCESS = "STATUS_SUCCESS", + STATUS_ERROR = "STATUS_ERROR", } type BaseOperation = { + id?: string + repoId?: string + planId?: string status?: OperationStatus unixTimeStartMs?: string unixTimeEndMs?: string + displayMessage?: string } export type Operation = BaseOperation & OneOf<{ backup: OperationBackup }> +export type OperationEvent = { + type?: OperationEventType + operation?: Operation +} + export type OperationBackup = { - repoId?: string - planId?: string lastStatus?: V1Restic.BackupProgressEntry } \ No newline at end of file diff --git a/webui/gen/ts/v1/service.pb.ts b/webui/gen/ts/v1/service.pb.ts index 191dfcc1..d6d3a173 100644 --- a/webui/gen/ts/v1/service.pb.ts +++ b/webui/gen/ts/v1/service.pb.ts @@ -8,6 +8,7 @@ import * as fm from "../fetch.pb" import * as GoogleProtobufEmpty from "../google/protobuf/empty.pb" import * as TypesValue from "../types/value.pb" import * as V1Config from "./config.pb" +import * as V1Operations from "./operations.pb" import * as V1Restic from "./restic.pb" export type ListSnapshotsRequest = { repoId?: string @@ -24,6 +25,9 @@ export class ResticUI { static AddRepo(req: V1Config.Repo, initReq?: fm.InitReq): Promise { return fm.fetchReq(`/v1/config/repo`, {...initReq, method: "POST", body: JSON.stringify(req, fm.replacer)}) } + static GetOperationEvents(req: GoogleProtobufEmpty.Empty, entityNotifier?: fm.NotifyStreamEntityArrival, initReq?: fm.InitReq): Promise { + return fm.fetchStreamingRequest(`/v1/events/operations?${fm.renderURLSearchParams(req, [])}`, entityNotifier, {...initReq, method: "GET"}) + } static ListSnapshots(req: ListSnapshotsRequest, initReq?: fm.InitReq): Promise { return fm.fetchReq(`/v1/snapshots`, {...initReq, method: "POST", body: JSON.stringify(req, fm.replacer)}) }