diff --git a/gen/go/v1/config.pb.go b/gen/go/v1/config.pb.go index ddcd5b69..2b869880 100644 --- a/gen/go/v1/config.pb.go +++ b/gen/go/v1/config.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.32.0 -// protoc v5.27.2 +// protoc (unknown) // source: v1/config.proto package v1 diff --git a/gen/go/v1/operations.pb.go b/gen/go/v1/operations.pb.go index b3630281..9dda65cf 100644 --- a/gen/go/v1/operations.pb.go +++ b/gen/go/v1/operations.pb.go @@ -218,6 +218,7 @@ type Operation struct { // *Operation_OperationStats // *Operation_OperationRunHook // *Operation_OperationCheck + // *Operation_OperationRunCommand Op isOperation_Op `protobuf_oneof:"op"` } @@ -393,6 +394,13 @@ func (x *Operation) GetOperationCheck() *OperationCheck { return nil } +func (x *Operation) GetOperationRunCommand() *OperationRunCommand { + if x, ok := x.GetOp().(*Operation_OperationRunCommand); ok { + return x.OperationRunCommand + } + return nil +} + type isOperation_Op interface { isOperation_Op() } @@ -429,6 +437,10 @@ type Operation_OperationCheck struct { OperationCheck *OperationCheck `protobuf:"bytes,107,opt,name=operation_check,json=operationCheck,proto3,oneof"` } +type Operation_OperationRunCommand struct { + OperationRunCommand *OperationRunCommand `protobuf:"bytes,108,opt,name=operation_run_command,json=operationRunCommand,proto3,oneof"` +} + func (*Operation_OperationBackup) isOperation_Op() {} func (*Operation_OperationIndexSnapshot) isOperation_Op() {} @@ -445,6 +457,8 @@ func (*Operation_OperationRunHook) isOperation_Op() {} func (*Operation_OperationCheck) isOperation_Op() {} +func (*Operation_OperationRunCommand) isOperation_Op() {} + // OperationEvent is used in the wireformat to stream operation changes to clients type OperationEvent struct { state protoimpl.MessageState @@ -838,6 +852,62 @@ func (x *OperationCheck) GetOutputLogref() string { return "" } +// OperationRunCommand tracks a long running command. Commands are grouped into a flow ID for each session. +type OperationRunCommand struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Command string `protobuf:"bytes,1,opt,name=command,proto3" json:"command,omitempty"` + OutputLogref string `protobuf:"bytes,2,opt,name=output_logref,json=outputLogref,proto3" json:"output_logref,omitempty"` +} + +func (x *OperationRunCommand) Reset() { + *x = OperationRunCommand{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_operations_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OperationRunCommand) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OperationRunCommand) ProtoMessage() {} + +func (x *OperationRunCommand) ProtoReflect() protoreflect.Message { + mi := &file_v1_operations_proto_msgTypes[8] + 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 OperationRunCommand.ProtoReflect.Descriptor instead. +func (*OperationRunCommand) Descriptor() ([]byte, []int) { + return file_v1_operations_proto_rawDescGZIP(), []int{8} +} + +func (x *OperationRunCommand) GetCommand() string { + if x != nil { + return x.Command + } + return "" +} + +func (x *OperationRunCommand) GetOutputLogref() string { + if x != nil { + return x.OutputLogref + } + return "" +} + // OperationRestore tracks a restore operation. type OperationRestore struct { state protoimpl.MessageState @@ -852,7 +922,7 @@ type OperationRestore struct { func (x *OperationRestore) Reset() { *x = OperationRestore{} if protoimpl.UnsafeEnabled { - mi := &file_v1_operations_proto_msgTypes[8] + mi := &file_v1_operations_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -865,7 +935,7 @@ func (x *OperationRestore) String() string { func (*OperationRestore) ProtoMessage() {} func (x *OperationRestore) ProtoReflect() protoreflect.Message { - mi := &file_v1_operations_proto_msgTypes[8] + mi := &file_v1_operations_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -878,7 +948,7 @@ func (x *OperationRestore) ProtoReflect() protoreflect.Message { // Deprecated: Use OperationRestore.ProtoReflect.Descriptor instead. func (*OperationRestore) Descriptor() ([]byte, []int) { - return file_v1_operations_proto_rawDescGZIP(), []int{8} + return file_v1_operations_proto_rawDescGZIP(), []int{9} } func (x *OperationRestore) GetPath() string { @@ -914,7 +984,7 @@ type OperationStats struct { func (x *OperationStats) Reset() { *x = OperationStats{} if protoimpl.UnsafeEnabled { - mi := &file_v1_operations_proto_msgTypes[9] + mi := &file_v1_operations_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -927,7 +997,7 @@ func (x *OperationStats) String() string { func (*OperationStats) ProtoMessage() {} func (x *OperationStats) ProtoReflect() protoreflect.Message { - mi := &file_v1_operations_proto_msgTypes[9] + mi := &file_v1_operations_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -940,7 +1010,7 @@ func (x *OperationStats) ProtoReflect() protoreflect.Message { // Deprecated: Use OperationStats.ProtoReflect.Descriptor instead. func (*OperationStats) Descriptor() ([]byte, []int) { - return file_v1_operations_proto_rawDescGZIP(), []int{9} + return file_v1_operations_proto_rawDescGZIP(), []int{10} } func (x *OperationStats) GetStats() *RepoStats { @@ -965,7 +1035,7 @@ type OperationRunHook struct { func (x *OperationRunHook) Reset() { *x = OperationRunHook{} if protoimpl.UnsafeEnabled { - mi := &file_v1_operations_proto_msgTypes[10] + mi := &file_v1_operations_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -978,7 +1048,7 @@ func (x *OperationRunHook) String() string { func (*OperationRunHook) ProtoMessage() {} func (x *OperationRunHook) ProtoReflect() protoreflect.Message { - mi := &file_v1_operations_proto_msgTypes[10] + mi := &file_v1_operations_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -991,7 +1061,7 @@ func (x *OperationRunHook) ProtoReflect() protoreflect.Message { // Deprecated: Use OperationRunHook.ProtoReflect.Descriptor instead. func (*OperationRunHook) Descriptor() ([]byte, []int) { - return file_v1_operations_proto_rawDescGZIP(), []int{10} + return file_v1_operations_proto_rawDescGZIP(), []int{11} } func (x *OperationRunHook) GetParentOp() int64 { @@ -1033,7 +1103,7 @@ var file_v1_operations_proto_rawDesc = []byte{ 0x0a, 0x0d, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x96, + 0x6f, 0x6e, 0x52, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe5, 0x07, 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, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x66, @@ -1091,98 +1161,108 @@ var file_v1_operations_proto_rawDesc = []byte{ 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x6b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x48, 0x00, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x42, 0x04, 0x0a, 0x02, 0x6f, 0x70, 0x22, 0x93, 0x02, 0x0a, 0x0e, 0x4f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2d, 0x0a, 0x0a, 0x6b, 0x65, - 0x65, 0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, - 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x48, 0x00, 0x52, 0x09, - 0x6b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x42, 0x0a, 0x12, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x42, 0x0a, - 0x12, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x4f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x11, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x41, 0x0a, 0x12, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, - 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x4c, 0x69, 0x73, 0x74, 0x48, - 0x00, 0x52, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x7c, 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, 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, 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, 0x51, 0x0a, 0x0e, 0x4f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x12, 0x1a, 0x0a, 0x06, 0x6f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, - 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 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, 0x22, 0x51, 0x0a, 0x0e, - 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1a, - 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, - 0x18, 0x01, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 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, 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, + 0x6b, 0x12, 0x4d, 0x0a, 0x15, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, + 0x75, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x6c, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x13, 0x6f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x42, 0x04, 0x0a, 0x02, 0x6f, 0x70, 0x22, 0x93, 0x02, 0x0a, 0x0e, 0x4f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2d, 0x0a, 0x0a, 0x6b, 0x65, 0x65, + 0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x48, 0x00, 0x52, 0x09, 0x6b, + 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x42, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x42, 0x0a, 0x12, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x11, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x41, 0x0a, 0x12, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, + 0x52, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x7c, 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, 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, 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, 0x51, 0x0a, 0x0e, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x12, 0x1a, 0x0a, 0x06, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 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, 0x22, 0x51, 0x0a, 0x0e, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1a, 0x0a, + 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, + 0x01, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 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, 0x22, 0x54, + 0x0a, 0x13, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 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, 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, 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 ( @@ -1198,7 +1278,7 @@ func file_v1_operations_proto_rawDescGZIP() []byte { } var file_v1_operations_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_v1_operations_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_v1_operations_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_v1_operations_proto_goTypes = []interface{}{ (OperationEventType)(0), // 0: v1.OperationEventType (OperationStatus)(0), // 1: v1.OperationStatus @@ -1210,18 +1290,19 @@ var file_v1_operations_proto_goTypes = []interface{}{ (*OperationForget)(nil), // 7: v1.OperationForget (*OperationPrune)(nil), // 8: v1.OperationPrune (*OperationCheck)(nil), // 9: v1.OperationCheck - (*OperationRestore)(nil), // 10: v1.OperationRestore - (*OperationStats)(nil), // 11: v1.OperationStats - (*OperationRunHook)(nil), // 12: v1.OperationRunHook - (*types.Empty)(nil), // 13: types.Empty - (*types.Int64List)(nil), // 14: types.Int64List - (*BackupProgressEntry)(nil), // 15: v1.BackupProgressEntry - (*BackupProgressError)(nil), // 16: v1.BackupProgressError - (*ResticSnapshot)(nil), // 17: v1.ResticSnapshot - (*RetentionPolicy)(nil), // 18: v1.RetentionPolicy - (*RestoreProgressEntry)(nil), // 19: v1.RestoreProgressEntry - (*RepoStats)(nil), // 20: v1.RepoStats - (Hook_Condition)(0), // 21: v1.Hook.Condition + (*OperationRunCommand)(nil), // 10: v1.OperationRunCommand + (*OperationRestore)(nil), // 11: v1.OperationRestore + (*OperationStats)(nil), // 12: v1.OperationStats + (*OperationRunHook)(nil), // 13: v1.OperationRunHook + (*types.Empty)(nil), // 14: types.Empty + (*types.Int64List)(nil), // 15: types.Int64List + (*BackupProgressEntry)(nil), // 16: v1.BackupProgressEntry + (*BackupProgressError)(nil), // 17: v1.BackupProgressError + (*ResticSnapshot)(nil), // 18: v1.ResticSnapshot + (*RetentionPolicy)(nil), // 19: v1.RetentionPolicy + (*RestoreProgressEntry)(nil), // 20: v1.RestoreProgressEntry + (*RepoStats)(nil), // 21: v1.RepoStats + (Hook_Condition)(0), // 22: v1.Hook.Condition } var file_v1_operations_proto_depIdxs = []int32{ 3, // 0: v1.OperationList.operations:type_name -> v1.Operation @@ -1230,27 +1311,28 @@ var file_v1_operations_proto_depIdxs = []int32{ 6, // 3: v1.Operation.operation_index_snapshot:type_name -> v1.OperationIndexSnapshot 7, // 4: v1.Operation.operation_forget:type_name -> v1.OperationForget 8, // 5: v1.Operation.operation_prune:type_name -> v1.OperationPrune - 10, // 6: v1.Operation.operation_restore:type_name -> v1.OperationRestore - 11, // 7: v1.Operation.operation_stats:type_name -> v1.OperationStats - 12, // 8: v1.Operation.operation_run_hook:type_name -> v1.OperationRunHook + 11, // 6: v1.Operation.operation_restore:type_name -> v1.OperationRestore + 12, // 7: v1.Operation.operation_stats:type_name -> v1.OperationStats + 13, // 8: v1.Operation.operation_run_hook:type_name -> v1.OperationRunHook 9, // 9: v1.Operation.operation_check:type_name -> v1.OperationCheck - 13, // 10: v1.OperationEvent.keep_alive:type_name -> types.Empty - 2, // 11: v1.OperationEvent.created_operations:type_name -> v1.OperationList - 2, // 12: v1.OperationEvent.updated_operations:type_name -> v1.OperationList - 14, // 13: v1.OperationEvent.deleted_operations:type_name -> types.Int64List - 15, // 14: v1.OperationBackup.last_status:type_name -> v1.BackupProgressEntry - 16, // 15: v1.OperationBackup.errors:type_name -> v1.BackupProgressError - 17, // 16: v1.OperationIndexSnapshot.snapshot:type_name -> v1.ResticSnapshot - 17, // 17: v1.OperationForget.forget:type_name -> v1.ResticSnapshot - 18, // 18: v1.OperationForget.policy:type_name -> v1.RetentionPolicy - 19, // 19: v1.OperationRestore.last_status:type_name -> v1.RestoreProgressEntry - 20, // 20: v1.OperationStats.stats:type_name -> v1.RepoStats - 21, // 21: v1.OperationRunHook.condition:type_name -> v1.Hook.Condition - 22, // [22:22] is the sub-list for method output_type - 22, // [22:22] is the sub-list for method input_type - 22, // [22:22] is the sub-list for extension type_name - 22, // [22:22] is the sub-list for extension extendee - 0, // [0:22] is the sub-list for field type_name + 10, // 10: v1.Operation.operation_run_command:type_name -> v1.OperationRunCommand + 14, // 11: v1.OperationEvent.keep_alive:type_name -> types.Empty + 2, // 12: v1.OperationEvent.created_operations:type_name -> v1.OperationList + 2, // 13: v1.OperationEvent.updated_operations:type_name -> v1.OperationList + 15, // 14: v1.OperationEvent.deleted_operations:type_name -> types.Int64List + 16, // 15: v1.OperationBackup.last_status:type_name -> v1.BackupProgressEntry + 17, // 16: v1.OperationBackup.errors:type_name -> v1.BackupProgressError + 18, // 17: v1.OperationIndexSnapshot.snapshot:type_name -> v1.ResticSnapshot + 18, // 18: v1.OperationForget.forget:type_name -> v1.ResticSnapshot + 19, // 19: v1.OperationForget.policy:type_name -> v1.RetentionPolicy + 20, // 20: v1.OperationRestore.last_status:type_name -> v1.RestoreProgressEntry + 21, // 21: v1.OperationStats.stats:type_name -> v1.RepoStats + 22, // 22: v1.OperationRunHook.condition:type_name -> v1.Hook.Condition + 23, // [23:23] is the sub-list for method output_type + 23, // [23:23] is the sub-list for method input_type + 23, // [23:23] is the sub-list for extension type_name + 23, // [23:23] is the sub-list for extension extendee + 0, // [0:23] is the sub-list for field type_name } func init() { file_v1_operations_proto_init() } @@ -1358,7 +1440,7 @@ func file_v1_operations_proto_init() { } } file_v1_operations_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OperationRestore); i { + switch v := v.(*OperationRunCommand); i { case 0: return &v.state case 1: @@ -1370,7 +1452,7 @@ func file_v1_operations_proto_init() { } } file_v1_operations_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OperationStats); i { + switch v := v.(*OperationRestore); i { case 0: return &v.state case 1: @@ -1382,6 +1464,18 @@ func file_v1_operations_proto_init() { } } file_v1_operations_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OperationStats); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_operations_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*OperationRunHook); i { case 0: return &v.state @@ -1403,6 +1497,7 @@ func file_v1_operations_proto_init() { (*Operation_OperationStats)(nil), (*Operation_OperationRunHook)(nil), (*Operation_OperationCheck)(nil), + (*Operation_OperationRunCommand)(nil), } file_v1_operations_proto_msgTypes[2].OneofWrappers = []interface{}{ (*OperationEvent_KeepAlive)(nil), @@ -1416,7 +1511,7 @@ func file_v1_operations_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_operations_proto_rawDesc, NumEnums: 2, - NumMessages: 11, + NumMessages: 12, NumExtensions: 0, NumServices: 0, }, diff --git a/gen/go/v1/service.pb.go b/gen/go/v1/service.pb.go index f2cb2563..0dfe4324 100644 --- a/gen/go/v1/service.pb.go +++ b/gen/go/v1/service.pb.go @@ -961,7 +961,7 @@ var file_v1_service_proto_rawDesc = []byte{ 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, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x32, 0xf9, 0x07, 0x0a, 0x08, 0x42, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, + 0x6d, 0x61, 0x6e, 0x64, 0x32, 0xf7, 0x07, 0x0a, 0x08, 0x42, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x12, 0x31, 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, @@ -1009,26 +1009,26 @@ var file_v1_service_proto_rawDesc = []byte{ 0x79, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x73, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x3a, 0x0a, 0x0a, 0x52, 0x75, 0x6e, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x0a, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, - 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x39, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, - 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x12, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, - 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x12, 0x2e, 0x74, 0x79, 0x70, - 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, - 0x12, 0x41, 0x0a, 0x0c, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, - 0x12, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, - 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x00, 0x12, 0x3b, 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, 0x00, - 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, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, + 0x61, 0x64, 0x55, 0x52, 0x4c, 0x12, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, + 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x41, + 0x0a, 0x0c, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x17, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, + 0x00, 0x12, 0x3b, 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, 0x00, 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 ( @@ -1105,7 +1105,7 @@ var file_v1_service_proto_depIdxs = []int32{ 13, // 31: v1.Backrest.Restore:output_type -> google.protobuf.Empty 13, // 32: v1.Backrest.Cancel:output_type -> google.protobuf.Empty 21, // 33: v1.Backrest.GetLogs:output_type -> types.BytesValue - 21, // 34: v1.Backrest.RunCommand:output_type -> types.BytesValue + 17, // 34: v1.Backrest.RunCommand:output_type -> types.Int64Value 16, // 35: v1.Backrest.GetDownloadURL:output_type -> types.StringValue 13, // 36: v1.Backrest.ClearHistory:output_type -> google.protobuf.Empty 22, // 37: v1.Backrest.PathAutocomplete:output_type -> types.StringList diff --git a/gen/go/v1/service_grpc.pb.go b/gen/go/v1/service_grpc.pb.go index ee2ebfd4..617160ba 100644 --- a/gen/go/v1/service_grpc.pb.go +++ b/gen/go/v1/service_grpc.pb.go @@ -64,7 +64,7 @@ type BackrestClient interface { // GetLogs returns the keyed large data for the given operation. GetLogs(ctx context.Context, in *LogDataRequest, opts ...grpc.CallOption) (Backrest_GetLogsClient, error) // RunCommand executes a generic restic command on the repository. - RunCommand(ctx context.Context, in *RunCommandRequest, opts ...grpc.CallOption) (Backrest_RunCommandClient, error) + RunCommand(ctx context.Context, in *RunCommandRequest, opts ...grpc.CallOption) (*types.Int64Value, error) // GetDownloadURL returns a signed download URL given a forget operation ID. GetDownloadURL(ctx context.Context, in *types.Int64Value, opts ...grpc.CallOption) (*types.StringValue, error) // Clears the history of operations @@ -244,36 +244,13 @@ func (x *backrestGetLogsClient) Recv() (*types.BytesValue, error) { return m, nil } -func (c *backrestClient) RunCommand(ctx context.Context, in *RunCommandRequest, opts ...grpc.CallOption) (Backrest_RunCommandClient, error) { - stream, err := c.cc.NewStream(ctx, &Backrest_ServiceDesc.Streams[2], Backrest_RunCommand_FullMethodName, opts...) +func (c *backrestClient) RunCommand(ctx context.Context, in *RunCommandRequest, opts ...grpc.CallOption) (*types.Int64Value, error) { + out := new(types.Int64Value) + err := c.cc.Invoke(ctx, Backrest_RunCommand_FullMethodName, in, out, opts...) if err != nil { return nil, err } - x := &backrestRunCommandClient{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 Backrest_RunCommandClient interface { - Recv() (*types.BytesValue, error) - grpc.ClientStream -} - -type backrestRunCommandClient struct { - grpc.ClientStream -} - -func (x *backrestRunCommandClient) Recv() (*types.BytesValue, error) { - m := new(types.BytesValue) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil + return out, nil } func (c *backrestClient) GetDownloadURL(ctx context.Context, in *types.Int64Value, opts ...grpc.CallOption) (*types.StringValue, error) { @@ -327,7 +304,7 @@ type BackrestServer interface { // GetLogs returns the keyed large data for the given operation. GetLogs(*LogDataRequest, Backrest_GetLogsServer) error // RunCommand executes a generic restic command on the repository. - RunCommand(*RunCommandRequest, Backrest_RunCommandServer) error + RunCommand(context.Context, *RunCommandRequest) (*types.Int64Value, error) // GetDownloadURL returns a signed download URL given a forget operation ID. GetDownloadURL(context.Context, *types.Int64Value) (*types.StringValue, error) // Clears the history of operations @@ -380,8 +357,8 @@ func (UnimplementedBackrestServer) Cancel(context.Context, *types.Int64Value) (* func (UnimplementedBackrestServer) GetLogs(*LogDataRequest, Backrest_GetLogsServer) error { return status.Errorf(codes.Unimplemented, "method GetLogs not implemented") } -func (UnimplementedBackrestServer) RunCommand(*RunCommandRequest, Backrest_RunCommandServer) error { - return status.Errorf(codes.Unimplemented, "method RunCommand not implemented") +func (UnimplementedBackrestServer) RunCommand(context.Context, *RunCommandRequest) (*types.Int64Value, error) { + return nil, status.Errorf(codes.Unimplemented, "method RunCommand not implemented") } func (UnimplementedBackrestServer) GetDownloadURL(context.Context, *types.Int64Value) (*types.StringValue, error) { return nil, status.Errorf(codes.Unimplemented, "method GetDownloadURL not implemented") @@ -645,25 +622,22 @@ func (x *backrestGetLogsServer) Send(m *types.BytesValue) error { return x.ServerStream.SendMsg(m) } -func _Backrest_RunCommand_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(RunCommandRequest) - if err := stream.RecvMsg(m); err != nil { - return err +func _Backrest_RunCommand_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RunCommandRequest) + if err := dec(in); err != nil { + return nil, err } - return srv.(BackrestServer).RunCommand(m, &backrestRunCommandServer{stream}) -} - -type Backrest_RunCommandServer interface { - Send(*types.BytesValue) error - grpc.ServerStream -} - -type backrestRunCommandServer struct { - grpc.ServerStream -} - -func (x *backrestRunCommandServer) Send(m *types.BytesValue) error { - return x.ServerStream.SendMsg(m) + if interceptor == nil { + return srv.(BackrestServer).RunCommand(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Backrest_RunCommand_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackrestServer).RunCommand(ctx, req.(*RunCommandRequest)) + } + return interceptor(ctx, in, info, handler) } func _Backrest_GetDownloadURL_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { @@ -771,6 +745,10 @@ var Backrest_ServiceDesc = grpc.ServiceDesc{ MethodName: "Cancel", Handler: _Backrest_Cancel_Handler, }, + { + MethodName: "RunCommand", + Handler: _Backrest_RunCommand_Handler, + }, { MethodName: "GetDownloadURL", Handler: _Backrest_GetDownloadURL_Handler, @@ -795,11 +773,6 @@ var Backrest_ServiceDesc = grpc.ServiceDesc{ Handler: _Backrest_GetLogs_Handler, ServerStreams: true, }, - { - StreamName: "RunCommand", - Handler: _Backrest_RunCommand_Handler, - ServerStreams: true, - }, }, Metadata: "v1/service.proto", } diff --git a/gen/go/v1/v1connect/service.connect.go b/gen/go/v1/v1connect/service.connect.go index 2326a1e9..c5d26790 100644 --- a/gen/go/v1/v1connect/service.connect.go +++ b/gen/go/v1/v1connect/service.connect.go @@ -118,7 +118,7 @@ type BackrestClient interface { // GetLogs returns the keyed large data for the given operation. GetLogs(context.Context, *connect.Request[v1.LogDataRequest]) (*connect.ServerStreamForClient[types.BytesValue], error) // RunCommand executes a generic restic command on the repository. - RunCommand(context.Context, *connect.Request[v1.RunCommandRequest]) (*connect.ServerStreamForClient[types.BytesValue], error) + RunCommand(context.Context, *connect.Request[v1.RunCommandRequest]) (*connect.Response[types.Int64Value], error) // GetDownloadURL returns a signed download URL given a forget operation ID. GetDownloadURL(context.Context, *connect.Request[types.Int64Value]) (*connect.Response[types.StringValue], error) // Clears the history of operations @@ -215,7 +215,7 @@ func NewBackrestClient(httpClient connect.HTTPClient, baseURL string, opts ...co connect.WithSchema(backrestGetLogsMethodDescriptor), connect.WithClientOptions(opts...), ), - runCommand: connect.NewClient[v1.RunCommandRequest, types.BytesValue]( + runCommand: connect.NewClient[v1.RunCommandRequest, types.Int64Value]( httpClient, baseURL+BackrestRunCommandProcedure, connect.WithSchema(backrestRunCommandMethodDescriptor), @@ -257,7 +257,7 @@ type backrestClient struct { restore *connect.Client[v1.RestoreSnapshotRequest, emptypb.Empty] cancel *connect.Client[types.Int64Value, emptypb.Empty] getLogs *connect.Client[v1.LogDataRequest, types.BytesValue] - runCommand *connect.Client[v1.RunCommandRequest, types.BytesValue] + runCommand *connect.Client[v1.RunCommandRequest, types.Int64Value] getDownloadURL *connect.Client[types.Int64Value, types.StringValue] clearHistory *connect.Client[v1.ClearHistoryRequest, emptypb.Empty] pathAutocomplete *connect.Client[types.StringValue, types.StringList] @@ -329,8 +329,8 @@ func (c *backrestClient) GetLogs(ctx context.Context, req *connect.Request[v1.Lo } // RunCommand calls v1.Backrest.RunCommand. -func (c *backrestClient) RunCommand(ctx context.Context, req *connect.Request[v1.RunCommandRequest]) (*connect.ServerStreamForClient[types.BytesValue], error) { - return c.runCommand.CallServerStream(ctx, req) +func (c *backrestClient) RunCommand(ctx context.Context, req *connect.Request[v1.RunCommandRequest]) (*connect.Response[types.Int64Value], error) { + return c.runCommand.CallUnary(ctx, req) } // GetDownloadURL calls v1.Backrest.GetDownloadURL. @@ -370,7 +370,7 @@ type BackrestHandler interface { // GetLogs returns the keyed large data for the given operation. GetLogs(context.Context, *connect.Request[v1.LogDataRequest], *connect.ServerStream[types.BytesValue]) error // RunCommand executes a generic restic command on the repository. - RunCommand(context.Context, *connect.Request[v1.RunCommandRequest], *connect.ServerStream[types.BytesValue]) error + RunCommand(context.Context, *connect.Request[v1.RunCommandRequest]) (*connect.Response[types.Int64Value], error) // GetDownloadURL returns a signed download URL given a forget operation ID. GetDownloadURL(context.Context, *connect.Request[types.Int64Value]) (*connect.Response[types.StringValue], error) // Clears the history of operations @@ -463,7 +463,7 @@ func NewBackrestHandler(svc BackrestHandler, opts ...connect.HandlerOption) (str connect.WithSchema(backrestGetLogsMethodDescriptor), connect.WithHandlerOptions(opts...), ) - backrestRunCommandHandler := connect.NewServerStreamHandler( + backrestRunCommandHandler := connect.NewUnaryHandler( BackrestRunCommandProcedure, svc.RunCommand, connect.WithSchema(backrestRunCommandMethodDescriptor), @@ -584,8 +584,8 @@ func (UnimplementedBackrestHandler) GetLogs(context.Context, *connect.Request[v1 return connect.NewError(connect.CodeUnimplemented, errors.New("v1.Backrest.GetLogs is not implemented")) } -func (UnimplementedBackrestHandler) RunCommand(context.Context, *connect.Request[v1.RunCommandRequest], *connect.ServerStream[types.BytesValue]) error { - return connect.NewError(connect.CodeUnimplemented, errors.New("v1.Backrest.RunCommand is not implemented")) +func (UnimplementedBackrestHandler) RunCommand(context.Context, *connect.Request[v1.RunCommandRequest]) (*connect.Response[types.Int64Value], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1.Backrest.RunCommand is not implemented")) } func (UnimplementedBackrestHandler) GetDownloadURL(context.Context, *connect.Request[types.Int64Value]) (*connect.Response[types.StringValue], error) { diff --git a/internal/api/backresthandler.go b/internal/api/backresthandler.go index 690702bf..25c3d28c 100644 --- a/internal/api/backresthandler.go +++ b/internal/api/backresthandler.go @@ -429,73 +429,28 @@ func (s *BackrestHandler) Restore(ctx context.Context, req *connect.Request[v1.R return connect.NewResponse(&emptypb.Empty{}), nil } -func (s *BackrestHandler) RunCommand(ctx context.Context, req *connect.Request[v1.RunCommandRequest], resp *connect.ServerStream[types.BytesValue]) error { - repo, err := s.orchestrator.GetRepoOrchestrator(req.Msg.RepoId) - if err != nil { - return fmt.Errorf("failed to get repo %q: %w", req.Msg.RepoId, err) - } - - ctx, cancel := context.WithCancel(ctx) - - outputs := make(chan []byte, 100) - errChan := make(chan error, 1) - go func() { - start := time.Now() - zap.S().Infof("running command for webui: %v", req.Msg.Command) - if err := repo.RunCommand(ctx, req.Msg.Command, func(output []byte) { - outputs <- bytes.Clone(output) - }); err != nil && ctx.Err() == nil { - zap.S().Errorf("error running command for webui: %v", err) - errChan <- err - } else { - zap.S().Infof("command completed for webui: %v", time.Since(start)) - } - outputs <- []byte("took " + time.Since(start).String()) - cancel() - }() - - ticker := time.NewTicker(100 * time.Millisecond) - defer ticker.Stop() - - bufSize := 32 * 1024 - buf := make([]byte, 0, bufSize) - - flush := func() error { - if len(buf) > 0 { - if err := resp.Send(&types.BytesValue{Value: buf}); err != nil { - return fmt.Errorf("failed to write output: %w", err) - } - buf = buf[:0] +func (s *BackrestHandler) RunCommand(ctx context.Context, req *connect.Request[v1.RunCommandRequest]) (*connect.Response[types.Int64Value], error) { + // group commands within the last 24 hours (or 256 operations) into the same flow ID + var flowID int64 + if s.oplog.Query(oplog.Query{RepoID: req.Msg.RepoId, Limit: 256, Reversed: true}, func(op *v1.Operation) error { + if op.GetOperationRunCommand() != nil && time.Since(time.UnixMilli(op.UnixTimeStartMs)) < 30*time.Minute { + flowID = op.FlowId } return nil + }) != nil { + return nil, fmt.Errorf("failed to query operations") } - for { - select { - case err := <-errChan: - if err := flush(); err != nil { - return err - } - return err - case <-ctx.Done(): - return flush() - case output := <-outputs: - if len(output)+len(buf) > bufSize { - flush() - } - if len(output) > bufSize { - if err := resp.Send(&types.BytesValue{Value: output}); err != nil { - return fmt.Errorf("failed to write output: %w", err) - } - continue - } - buf = append(buf, output...) - case <-ticker.C: - if len(buf) > 0 { - flush() - } - } + task := tasks.NewOneoffRunCommandTask(req.Msg.RepoId, tasks.PlanForSystemTasks, flowID, time.Now(), req.Msg.Command) + st, err := s.orchestrator.CreateUnscheduledTask(task, tasks.TaskPriorityInteractive, time.Now()) + if err != nil { + return nil, fmt.Errorf("failed to create task: %w", err) } + if err := s.orchestrator.RunTask(context.Background(), st); err != nil { + return nil, fmt.Errorf("failed to run command: %w", err) + } + + return connect.NewResponse(&types.Int64Value{Value: st.Op.GetId()}), nil } func (s *BackrestHandler) Cancel(ctx context.Context, req *connect.Request[types.Int64Value]) (*connect.Response[emptypb.Empty], error) { diff --git a/internal/api/backresthandler_test.go b/internal/api/backresthandler_test.go index ce25d509..f53e06ee 100644 --- a/internal/api/backresthandler_test.go +++ b/internal/api/backresthandler_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "io/fs" "os" "path" @@ -764,6 +765,67 @@ func TestRestore(t *testing.T) { } } +func TestRunCommand(t *testing.T) { + sut := createSystemUnderTest(t, &config.MemoryStore{ + Config: &v1.Config{ + Modno: 1234, + Instance: "test", + Repos: []*v1.Repo{ + { + Id: "local", + Uri: t.TempDir(), + Password: "test", + Flags: []string{"--no-cache"}, + }, + }, + }, + }) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + res, err := sut.handler.RunCommand(ctx, connect.NewRequest(&v1.RunCommandRequest{ + RepoId: "local", + Command: "help", + })) + if err != nil { + t.Fatalf("RunCommand() error = %v", err) + } + op, err := sut.oplog.Get(res.Msg.Value) + if err != nil { + t.Fatalf("Failed to find runcommand operation: %v", err) + } + + if op.Status != v1.OperationStatus_STATUS_SUCCESS { + t.Fatalf("Expected runcommand operation to succeed") + } + + cmdOp := op.GetOperationRunCommand() + if cmdOp == nil { + t.Fatalf("Expected runcommand operation to be of type OperationRunCommand") + } + if cmdOp.Command != "help" { + t.Fatalf("Expected runcommand operation to have correct command") + } + if cmdOp.OutputLogref == "" { + t.Fatalf("Expected runcommand operation to have output logref") + } + + log, err := sut.logStore.Open(cmdOp.OutputLogref) + if err != nil { + t.Fatalf("Failed to open log: %v", err) + } + defer log.Close() + + data, err := io.ReadAll(log) + if err != nil { + t.Fatalf("Failed to read log: %v", err) + } + if !strings.Contains(string(data), "Usage") { + t.Fatalf("Expected log output to contain help text") + } +} + type systemUnderTest struct { handler *BackrestHandler oplog *oplog.OpLog diff --git a/internal/logstore/logstore.go b/internal/logstore/logstore.go index 8be8e39d..75a6808b 100644 --- a/internal/logstore/logstore.go +++ b/internal/logstore/logstore.go @@ -427,7 +427,6 @@ func (w *writer) Close() error { } } w.ls.trackingMu.Unlock() - w.ls.maybeReleaseTempFile(w.fname) }) diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index acf97460..b1b2433b 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -38,8 +38,8 @@ type Orchestrator struct { taskQueue *queue.TimePriorityQueue[stContainer] logStore *logstore.LogStore - // cancelNotify is a list of channels that are notified when a task should be cancelled. - cancelNotify []chan int64 + taskCancelMu sync.Mutex + taskCancel map[int64]context.CancelFunc // now for the purpose of testing; used by Run() to get the current time. now func() time.Time @@ -70,9 +70,10 @@ func NewOrchestrator(resticBin string, cfg *v1.Config, log *oplog.OpLog, logStor OpLog: log, config: cfg, // repoPool created with a memory store to ensure the config is updated in an atomic operation with the repo pool's config value. - repoPool: newResticRepoPool(resticBin, cfg), - taskQueue: queue.NewTimePriorityQueue[stContainer](), - logStore: logStore, + repoPool: newResticRepoPool(resticBin, cfg), + taskQueue: queue.NewTimePriorityQueue[stContainer](), + logStore: logStore, + taskCancel: make(map[int64]context.CancelFunc), } // verify the operation log and mark any incomplete operations as failed. @@ -166,10 +167,8 @@ func (o *Orchestrator) ScheduleDefaultTasks(config *v1.Config) error { continue } - if err := o.cancelHelper(t.Op, v1.OperationStatus_STATUS_SYSTEM_CANCELLED); err != nil { - zap.L().Error("failed to cancel queued task", zap.String("task", t.Task.Name()), zap.Error(err)) - } else { - zap.L().Debug("queued task cancelled due to config change", zap.String("task", t.Task.Name())) + if err := o.OpLog.Delete(t.Op.Id); err != nil { + zap.S().Warnf("failed to cancel pending task %d: %v", t.Op.Id, err) } } @@ -239,14 +238,11 @@ func (o *Orchestrator) GetPlan(planID string) (*v1.Plan, error) { } func (o *Orchestrator) CancelOperation(operationId int64, status v1.OperationStatus) error { - o.mu.Lock() - for _, c := range o.cancelNotify { - select { - case c <- operationId: - default: - } + o.taskCancelMu.Lock() + if cancel, ok := o.taskCancel[operationId]; ok { + cancel() } - o.mu.Unlock() + o.taskCancelMu.Unlock() allTasks := o.taskQueue.GetAll() idx := slices.IndexFunc(allTasks, func(t stContainer) bool { @@ -262,9 +258,15 @@ func (o *Orchestrator) CancelOperation(operationId int64, status v1.OperationSta } o.taskQueue.Remove(t) - if err := o.scheduleTaskHelper(t.Task, tasks.TaskPriorityDefault, t.RunAt); err != nil { + if st, err := o.CreateUnscheduledTask(t.Task, tasks.TaskPriorityDefault, t.RunAt); err != nil { return fmt.Errorf("reschedule cancelled task: %w", err) + } else if !st.Eq(tasks.NeverScheduledTask) { + o.taskQueue.Enqueue(st.RunAt, tasks.TaskPriorityDefault, stContainer{ + ScheduledTask: st, + configModno: o.config.Modno, + }) } + return nil } @@ -281,20 +283,6 @@ func (o *Orchestrator) cancelHelper(op *v1.Operation, status v1.OperationStatus) func (o *Orchestrator) Run(ctx context.Context) { zap.L().Info("starting orchestrator loop") - // subscribe to cancel notifications. - o.mu.Lock() - cancelNotifyChan := make(chan int64, 10) // buffered to queue up cancel notifications. - o.cancelNotify = append(o.cancelNotify, cancelNotifyChan) - o.mu.Unlock() - - defer func() { - o.mu.Lock() - if idx := slices.Index(o.cancelNotify, cancelNotifyChan); idx != -1 { - o.cancelNotify = slices.Delete(o.cancelNotify, idx, idx+1) - } - o.mu.Unlock() - }() - for { if ctx.Err() != nil { zap.L().Info("shutting down orchestrator loop, context cancelled.") @@ -306,20 +294,6 @@ func (o *Orchestrator) Run(ctx context.Context) { continue } - taskCtx, cancelTaskCtx := context.WithCancel(ctx) - go func() { - for { - select { - case <-taskCtx.Done(): - return - case opID := <-cancelNotifyChan: - if t.Op != nil && opID == t.Op.GetId() { - cancelTaskCtx() - } - } - } - }() - // Clone the operation incase we need to reset changes and reschedule the task for a retry originalOp := proto.Clone(t.Op).(*v1.Operation) if t.Op != nil && t.retryCount != 0 { @@ -340,7 +314,7 @@ func (o *Orchestrator) Run(ctx context.Context) { } } - err := o.RunTask(taskCtx, t.ScheduledTask) + err := o.RunTask(ctx, t.ScheduledTask) o.mu.Lock() curCfgModno := o.config.Modno @@ -372,8 +346,6 @@ func (o *Orchestrator) Run(ctx context.Context) { zap.L().Error("reschedule task", zap.String("task", t.Task.Name()), zap.Error(e)) } } - cancelTaskCtx() - for _, cb := range t.callbacks { go cb(err) } @@ -381,11 +353,22 @@ func (o *Orchestrator) Run(ctx context.Context) { } func (o *Orchestrator) RunTask(ctx context.Context, st tasks.ScheduledTask) error { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + zap.L().Info("running task", zap.String("task", st.Task.Name()), zap.String("runAt", st.RunAt.Format(time.RFC3339))) var logWriter io.WriteCloser op := st.Op if op != nil { var err error + o.taskCancelMu.Lock() + o.taskCancel[op.Id] = cancel + o.taskCancelMu.Unlock() + defer func() { + o.taskCancelMu.Lock() + delete(o.taskCancel, op.Id) + o.taskCancelMu.Unlock() + }() randBytes := make([]byte, 8) if _, err := rand.Read(randBytes); err != nil { @@ -465,39 +448,47 @@ func (o *Orchestrator) RunTask(ctx context.Context, st tasks.ScheduledTask) erro // ScheduleTask schedules a task to run at the next available time. // note that o.mu must not be held when calling this function. func (o *Orchestrator) ScheduleTask(t tasks.Task, priority int, callbacks ...func(error)) error { - return o.scheduleTaskHelper(t, priority, o.curTime(), callbacks...) -} - -func (o *Orchestrator) scheduleTaskHelper(t tasks.Task, priority int, curTime time.Time, callbacks ...func(error)) error { - nextRun, err := t.Next(curTime, newTaskRunnerImpl(o, t, nil)) + nextRun, err := o.CreateUnscheduledTask(t, priority, o.curTime()) if err != nil { - return fmt.Errorf("finding run time for task %q: %w", t.Name(), err) + return err } if nextRun.Eq(tasks.NeverScheduledTask) { return nil } - nextRun.Task = t + stc := stContainer{ ScheduledTask: nextRun, configModno: o.config.Modno, callbacks: callbacks, } - if stc.Op != nil { - stc.Op.InstanceId = o.config.Instance - stc.Op.PlanId = t.PlanID() - stc.Op.RepoId = t.RepoID() - stc.Op.Status = v1.OperationStatus_STATUS_PENDING - stc.Op.UnixTimeStartMs = nextRun.RunAt.UnixMilli() + o.taskQueue.Enqueue(nextRun.RunAt, priority, stc) + zap.L().Info("scheduled task", zap.String("task", t.Name()), zap.String("runAt", nextRun.RunAt.Format(time.RFC3339))) + return nil +} + +func (o *Orchestrator) CreateUnscheduledTask(t tasks.Task, priority int, curTime time.Time) (tasks.ScheduledTask, error) { + nextRun, err := t.Next(curTime, newTaskRunnerImpl(o, t, nil)) + if err != nil { + return tasks.NeverScheduledTask, fmt.Errorf("finding run time for task %q: %w", t.Name(), err) + } + if nextRun.Eq(tasks.NeverScheduledTask) { + return tasks.NeverScheduledTask, nil + } + nextRun.Task = t + + if nextRun.Op != nil { + nextRun.Op.InstanceId = o.config.Instance + nextRun.Op.PlanId = t.PlanID() + nextRun.Op.RepoId = t.RepoID() + nextRun.Op.Status = v1.OperationStatus_STATUS_PENDING + nextRun.Op.UnixTimeStartMs = nextRun.RunAt.UnixMilli() if err := o.OpLog.Add(nextRun.Op); err != nil { - return fmt.Errorf("add operation to oplog: %w", err) + return tasks.NeverScheduledTask, fmt.Errorf("add operation to oplog: %w", err) } } - - o.taskQueue.Enqueue(nextRun.RunAt, priority, stc) - zap.L().Info("scheduled task", zap.String("task", t.Name()), zap.String("runAt", nextRun.RunAt.Format(time.RFC3339))) - return nil + return nextRun, nil } func (o *Orchestrator) Config() *v1.Config { diff --git a/internal/orchestrator/repo/repo.go b/internal/orchestrator/repo/repo.go index e367cd52..53fce9b5 100644 --- a/internal/orchestrator/repo/repo.go +++ b/internal/orchestrator/repo/repo.go @@ -389,7 +389,7 @@ func (r *RepoOrchestrator) AddTags(ctx context.Context, snapshotIDs []string, ta } // RunCommand runs a command in the repo's environment. Output is buffered and sent to the onProgress callback in batches. -func (r *RepoOrchestrator) RunCommand(ctx context.Context, command string, onProgress func([]byte)) error { +func (r *RepoOrchestrator) RunCommand(ctx context.Context, command string, writer io.Writer) error { r.mu.Lock() defer r.mu.Unlock() ctx, flush := forwardResticLogs(ctx) @@ -401,8 +401,7 @@ func (r *RepoOrchestrator) RunCommand(ctx context.Context, command string, onPro return fmt.Errorf("parse command: %w", err) } - ctx = restic.ContextWithLogger(ctx, &callbackWriter{callback: onProgress}) - + ctx = restic.ContextWithLogger(ctx, writer) return r.repo.GenericCommand(ctx, args) } @@ -425,12 +424,3 @@ func chunkBy[T any](items []T, chunkSize int) (chunks [][]T) { } return append(chunks, items) } - -type callbackWriter struct { - callback func([]byte) // note: callback must not retain the byte slice -} - -func (w *callbackWriter) Write(p []byte) (n int, err error) { - w.callback(p) - return len(p), nil -} diff --git a/internal/orchestrator/taskrunnerimpl.go b/internal/orchestrator/taskrunnerimpl.go index 8788cc69..d8c061d9 100644 --- a/internal/orchestrator/taskrunnerimpl.go +++ b/internal/orchestrator/taskrunnerimpl.go @@ -104,10 +104,9 @@ func (t *taskRunnerImpl) ExecuteHooks(ctx context.Context, events []v1.Hook_Cond } for _, task := range hookTasks { - st, _ := task.Next(time.Now(), t) - st.Task = task - if err := t.OpLog().Add(st.Op); err != nil { - return fmt.Errorf("%v: %w", task.Name(), err) + st, err := t.orchestrator.CreateUnscheduledTask(task, tasks.TaskPriorityDefault, time.Now()) + if err != nil { + return fmt.Errorf("creating task for hook: %w", err) } if err := t.orchestrator.RunTask(ctx, st); hook.IsHaltingError(err) { var cancelErr *hook.HookErrorRequestCancel diff --git a/internal/orchestrator/tasks/taskruncommand.go b/internal/orchestrator/tasks/taskruncommand.go new file mode 100644 index 00000000..d7a37644 --- /dev/null +++ b/internal/orchestrator/tasks/taskruncommand.go @@ -0,0 +1,68 @@ +package tasks + +import ( + "context" + "fmt" + "time" + + v1 "github.com/garethgeorge/backrest/gen/go/v1" +) + +func NewOneoffRunCommandTask(repoID string, planID string, flowID int64, at time.Time, command string) Task { + return &GenericOneoffTask{ + OneoffTask: OneoffTask{ + BaseTask: BaseTask{ + TaskType: "forget_snapshot", + TaskName: fmt.Sprintf("run command in repo %q", repoID), + TaskRepoID: repoID, + TaskPlanID: planID, + }, + FlowID: flowID, + RunAt: at, + ProtoOp: &v1.Operation{ + Op: &v1.Operation_OperationRunCommand{ + OperationRunCommand: &v1.OperationRunCommand{ + Command: command, + }, + }, + }, + }, + Do: func(ctx context.Context, st ScheduledTask, taskRunner TaskRunner) error { + op := st.Op + rc := op.GetOperationRunCommand() + if rc == nil { + panic("forget task with non-forget operation") + } + + return runCommandHelper(ctx, st, taskRunner, command) + }, + } +} + +func runCommandHelper(ctx context.Context, st ScheduledTask, taskRunner TaskRunner, command string) error { + t := st.Task + + repo, err := taskRunner.GetRepoOrchestrator(t.RepoID()) + if err != nil { + return fmt.Errorf("get repo %q: %w", t.RepoID(), err) + } + + id, writer, err := taskRunner.LogrefWriter() + if err != nil { + return fmt.Errorf("get logref writer: %w", err) + } + st.Op.GetOperationRunCommand().OutputLogref = id + if err := taskRunner.UpdateOperation(st.Op); err != nil { + return fmt.Errorf("update operation: %w", err) + } + + if err := repo.RunCommand(ctx, command, writer); err != nil { + return fmt.Errorf("command %q: %w", command, err) + } + + if err := writer.Close(); err != nil { + return fmt.Errorf("close logref writer: %w", err) + } + + return err +} diff --git a/proto/v1/operations.proto b/proto/v1/operations.proto index 2b4277e6..77abaec0 100644 --- a/proto/v1/operations.proto +++ b/proto/v1/operations.proto @@ -41,6 +41,7 @@ message Operation { OperationStats operation_stats = 105; OperationRunHook operation_run_hook = 106; OperationCheck operation_check = 107; + OperationRunCommand operation_run_command = 108; } } @@ -102,6 +103,12 @@ message OperationCheck { string output_logref = 2; // logref of the check output. } +// OperationRunCommand tracks a long running command. Commands are grouped into a flow ID for each session. +message OperationRunCommand { + string command = 1; + string output_logref = 2; +} + // OperationRestore tracks a restore operation. message OperationRestore { string path = 1; // path in the snapshot to restore. diff --git a/proto/v1/service.proto b/proto/v1/service.proto index 629cc672..5ef79f4c 100644 --- a/proto/v1/service.proto +++ b/proto/v1/service.proto @@ -45,7 +45,7 @@ service Backrest { rpc GetLogs(LogDataRequest) returns (stream types.BytesValue) {} // RunCommand executes a generic restic command on the repository. - rpc RunCommand(RunCommandRequest) returns (stream types.BytesValue) {} + rpc RunCommand(RunCommandRequest) returns (types.Int64Value) {} // GetDownloadURL returns a signed download URL given a forget operation ID. rpc GetDownloadURL(types.Int64Value) returns (types.StringValue) {} diff --git a/webui/gen/ts/v1/operations_pb.ts b/webui/gen/ts/v1/operations_pb.ts index b335ce07..79e9a15c 100644 --- a/webui/gen/ts/v1/operations_pb.ts +++ b/webui/gen/ts/v1/operations_pb.ts @@ -278,6 +278,12 @@ export class Operation extends Message { */ value: OperationCheck; case: "operationCheck"; + } | { + /** + * @generated from field: v1.OperationRunCommand operation_run_command = 108; + */ + value: OperationRunCommand; + case: "operationRunCommand"; } | { case: undefined; value?: undefined } = { case: undefined }; constructor(data?: PartialMessage) { @@ -307,6 +313,7 @@ export class Operation extends Message { { no: 105, name: "operation_stats", kind: "message", T: OperationStats, oneof: "op" }, { no: 106, name: "operation_run_hook", kind: "message", T: OperationRunHook, oneof: "op" }, { no: 107, name: "operation_check", kind: "message", T: OperationCheck, oneof: "op" }, + { no: 108, name: "operation_run_command", kind: "message", T: OperationRunCommand, oneof: "op" }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): Operation { @@ -629,6 +636,51 @@ export class OperationCheck extends Message { } } +/** + * OperationRunCommand tracks a long running command. Commands are grouped into a flow ID for each session. + * + * @generated from message v1.OperationRunCommand + */ +export class OperationRunCommand extends Message { + /** + * @generated from field: string command = 1; + */ + command = ""; + + /** + * @generated from field: string output_logref = 2; + */ + outputLogref = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "v1.OperationRunCommand"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "command", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "output_logref", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): OperationRunCommand { + return new OperationRunCommand().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): OperationRunCommand { + return new OperationRunCommand().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): OperationRunCommand { + return new OperationRunCommand().fromJsonString(jsonString, options); + } + + static equals(a: OperationRunCommand | PlainMessage | undefined, b: OperationRunCommand | PlainMessage | undefined): boolean { + return proto3.util.equals(OperationRunCommand, a, b); + } +} + /** * OperationRestore tracks a restore operation. * diff --git a/webui/gen/ts/v1/service_connect.ts b/webui/gen/ts/v1/service_connect.ts index e22c8b36..2a65f649 100644 --- a/webui/gen/ts/v1/service_connect.ts +++ b/webui/gen/ts/v1/service_connect.ts @@ -153,8 +153,8 @@ export const Backrest = { runCommand: { name: "RunCommand", I: RunCommandRequest, - O: BytesValue, - kind: MethodKind.ServerStreaming, + O: Int64Value, + kind: MethodKind.Unary, }, /** * GetDownloadURL returns a signed download URL given a forget operation ID. diff --git a/webui/src/components/LogView.tsx b/webui/src/components/LogView.tsx index 6d0205aa..dfc8475f 100644 --- a/webui/src/components/LogView.tsx +++ b/webui/src/components/LogView.tsx @@ -1,16 +1,12 @@ import React, { useEffect, useState } from "react"; import { LogDataRequest } from "../../gen/ts/v1/service_pb"; import { backrestService } from "../api"; - -import AutoSizer from "react-virtualized/dist/commonjs/AutoSizer"; -import List from "react-virtualized/dist/commonjs/List"; -import { set } from "lodash"; +import { Button } from "antd"; // TODO: refactor this to use the provider pattern export const LogView = ({ logref }: { logref: string }) => { const [lines, setLines] = useState([""]); - - console.log("LogView", logref); + const [limit, setLimit] = useState(100); useEffect(() => { if (!logref) { @@ -47,7 +43,10 @@ export const LogView = ({ logref }: { logref: string }) => { }; }, [logref]); - console.log("LogView", lines); + let displayLines = lines; + if (lines.length > limit) { + displayLines = lines.slice(0, limit); + } return (
{ width: "100%", }} > - {lines.map((line, i) => ( + {displayLines.map((line, i) => (
 {
           {line}
         
))} + {lines.length > limit ? ( + <> + + + ) : null}
); }; diff --git a/webui/src/components/OperationIcon.tsx b/webui/src/components/OperationIcon.tsx index f3521864..1886ae65 100644 --- a/webui/src/components/OperationIcon.tsx +++ b/webui/src/components/OperationIcon.tsx @@ -1,6 +1,7 @@ import React from "react"; import { DisplayType, colorForStatus } from "../state/flowdisplayaggregator"; import { + CodeOutlined, DeleteOutlined, DownloadOutlined, FileSearchOutlined, @@ -23,20 +24,10 @@ export const OperationIcon = ({ let avatar: React.ReactNode; switch (type) { case DisplayType.BACKUP: - avatar = ( - - ); + avatar = ; break; case DisplayType.FORGET: - avatar = ( - - ); + avatar = ; break; case DisplayType.SNAPSHOT: avatar = ; @@ -55,6 +46,9 @@ export const OperationIcon = ({ case DisplayType.STATS: avatar = ; break; + case DisplayType.RUNCOMMAND: + avatar = ; + break; } return avatar; diff --git a/webui/src/components/OperationList.tsx b/webui/src/components/OperationList.tsx index c214f4f1..75897ccb 100644 --- a/webui/src/components/OperationList.tsx +++ b/webui/src/components/OperationList.tsx @@ -15,11 +15,13 @@ export const OperationList = ({ useOperations, showPlan, displayHooksInline, + filter, }: React.PropsWithoutRef<{ req?: GetOperationsRequest; useOperations?: Operation[]; // exact set of operations to display; no filtering will be applied. showPlan?: boolean; displayHooksInline?: boolean; + filter?: (op: Operation) => boolean; }>) => { const alertApi = useAlertApi(); @@ -27,7 +29,9 @@ export const OperationList = ({ if (req) { useEffect(() => { - const logState = new OplogState((op) => !shouldHideStatus(op.status)); + const logState = new OplogState( + (op) => !shouldHideStatus(op.status) && (!filter || filter(op)) + ); logState.subscribe((ids, flowIDs, event) => { setOperations(logState.getAll()); diff --git a/webui/src/components/OperationRow.tsx b/webui/src/components/OperationRow.tsx index 301e8c03..28bbdf0f 100644 --- a/webui/src/components/OperationRow.tsx +++ b/webui/src/components/OperationRow.tsx @@ -234,6 +234,18 @@ export const OperationRow = ({
{check.output}
), }); + } else if (operation.op.case === "operationRunCommand") { + const run = operation.op.value; + expandedBodyItems.push("run"); + bodyItems.push({ + key: "run", + label: "Command Output", + children: ( + <> + + + ), + }); } else if (operation.op.case === "operationRestore") { expandedBodyItems.push("restore"); bodyItems.push({ diff --git a/webui/src/components/OperationTree.tsx b/webui/src/components/OperationTree.tsx index 5abb2bee..5b2117af 100644 --- a/webui/src/components/OperationTree.tsx +++ b/webui/src/components/OperationTree.tsx @@ -487,7 +487,7 @@ const BackupView = ({ backup }: { backup?: FlowDisplayInfo }) => { backrestService.clearHistory( new ClearHistoryRequest({ selector: new OpSelector({ - ids: backup.operations.map((op) => op.id), + flowId: backup.flowID, }), }) ); diff --git a/webui/src/state/flowdisplayaggregator.ts b/webui/src/state/flowdisplayaggregator.ts index a3393743..0b02e323 100644 --- a/webui/src/state/flowdisplayaggregator.ts +++ b/webui/src/state/flowdisplayaggregator.ts @@ -11,6 +11,7 @@ export enum DisplayType { RESTORE, STATS, RUNHOOK, + RUNCOMMAND, } export interface FlowDisplayInfo { @@ -156,6 +157,8 @@ export const getTypeForDisplay = (op: Operation) => { return DisplayType.STATS; case "operationRunHook": return DisplayType.RUNHOOK; + case "operationRunCommand": + return DisplayType.RUNCOMMAND; default: return DisplayType.UNKNOWN; } @@ -179,6 +182,8 @@ export const displayTypeToString = (type: DisplayType) => { return "Stats"; case DisplayType.RUNHOOK: return "Run Hook"; + case DisplayType.RUNCOMMAND: + return "Run Command"; default: return "Unknown"; } diff --git a/webui/src/views/RunCommandModal.tsx b/webui/src/views/RunCommandModal.tsx index 91e6d43a..797688e0 100644 --- a/webui/src/views/RunCommandModal.tsx +++ b/webui/src/views/RunCommandModal.tsx @@ -5,6 +5,11 @@ import { backrestService } from "../api"; import { SpinButton } from "../components/SpinButton"; import { ConnectError } from "@connectrpc/connect"; import { useAlertApi } from "../components/Alerts"; +import { + GetOperationsRequest, + RunCommandRequest, +} from "../../gen/ts/v1/service_pb"; +import { OperationList } from "../components/OperationList"; interface Invocation { command: string; @@ -17,64 +22,29 @@ export const RunCommandModal = ({ repoId }: { repoId: string }) => { const alertApi = useAlertApi()!; const [command, setCommand] = React.useState(""); const [running, setRunning] = React.useState(false); - const [invocations, setInvocations] = React.useState([]); - const [abortController, setAbortController] = React.useState< - AbortController | undefined - >(); const handleCancel = () => { - if (abortController) { - alertApi.warning("In-progress restic command was aborted"); - abortController.abort(); - } showModal(null); }; const doExecute = async () => { - if (running) return; + if (!command) return; setRunning(true); - const newInvocation = { command, output: "", error: "" }; - setInvocations((invocations) => [newInvocation, ...invocations]); - - let segments: string[] = []; + const toRun = command.trim(); + setCommand(""); try { - const abortController = new AbortController(); - setAbortController(abortController); - - for await (const bytes of backrestService.runCommand( - { + const opID = await backrestService.runCommand( + new RunCommandRequest({ repoId, - command, - }, - { - signal: abortController.signal, - } - )) { - const output = new TextDecoder("utf-8").decode(bytes.value); - segments.push(output); - setInvocations((invocations) => { - const copy = [...invocations]; - copy[0] = { - ...copy[0], - output: segments.join(""), - }; - return copy; - }); - } + command: toRun, + }) + ); } catch (e: any) { - setInvocations((invocations) => { - const copy = [...invocations]; - copy[0] = { - ...copy[0], - error: (e as Error).message, - }; - return copy; - }); + alertApi.error("Command failed: " + e.message); } finally { setRunning(false); - setAbortController(undefined); } }; @@ -89,6 +59,7 @@ export const RunCommandModal = ({ repoId }: { repoId: string }) => { setCommand(e.target.value)} onKeyUp={(e) => { if (e.key === "Enter") { @@ -100,14 +71,23 @@ export const RunCommandModal = ({ repoId }: { repoId: string }) => { Execute - {invocations.map((invocation, i) => ( -
- {invocation.output ?
{invocation.output}
: null} - {invocation.error ? ( -
{invocation.error}
- ) : null} -
- ))} + {running && command ? ( + + Warning: another command is already running. Wait for it to finish + before running another operation that requires the repo lock. + + ) : null} + op.op.case === "operationRunCommand"} + /> ); };