From caac35a5402d056b626b59d19084d6a699d4346d Mon Sep 17 00:00:00 2001 From: Gareth Date: Sat, 17 Feb 2024 01:22:48 -0800 Subject: [PATCH] feat: display non-fatal errors in backup operations (e.g. unreadable files) in UI (#100) --- gen/go/v1/operations.pb.go | 157 ++++++++++++----------- gen/go/v1/restic.pb.go | 169 ++++++++++++++++++------- internal/orchestrator/taskbackup.go | 36 ++++-- internal/protoutil/conversion.go | 14 ++ pkg/restic/outputs.go | 7 +- pkg/restic/restic_test.go | 48 +++++++ proto/v1/operations.proto | 1 + proto/v1/restic.proto | 9 ++ test/helpers/testdata.go | 14 +- webui/gen/ts/v1/operations_pb.ts | 8 +- webui/gen/ts/v1/restic_pb.ts | 49 +++++++ webui/src/components/OperationList.tsx | 24 +++- 12 files changed, 397 insertions(+), 139 deletions(-) diff --git a/gen/go/v1/operations.pb.go b/gen/go/v1/operations.pb.go index 4e449241..5ae453e4 100644 --- a/gen/go/v1/operations.pb.go +++ b/gen/go/v1/operations.pb.go @@ -467,7 +467,8 @@ type OperationBackup struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - LastStatus *BackupProgressEntry `protobuf:"bytes,3,opt,name=last_status,json=lastStatus,proto3" json:"last_status,omitempty"` + LastStatus *BackupProgressEntry `protobuf:"bytes,3,opt,name=last_status,json=lastStatus,proto3" json:"last_status,omitempty"` + Errors []*BackupProgressError `protobuf:"bytes,4,rep,name=errors,proto3" json:"errors,omitempty"` } func (x *OperationBackup) Reset() { @@ -509,6 +510,13 @@ func (x *OperationBackup) GetLastStatus() *BackupProgressEntry { return nil } +func (x *OperationBackup) GetErrors() []*BackupProgressError { + if x != nil { + return x.Errors + } + return nil +} + // OperationIndexSnapshot tracks that a snapshot was detected by backrest. type OperationIndexSnapshot struct { state protoimpl.MessageState @@ -907,67 +915,70 @@ var file_v1_operations_proto_rawDesc = []byte{ 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, + 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 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, 0x22, 0x82, 0x01, 0x0a, 0x16, - 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x6e, - 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, - 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x73, 0x74, 0x69, 0x63, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x08, 0x73, 0x6e, - 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x12, 0x20, - 0x0a, 0x0c, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x5f, 0x62, 0x79, 0x5f, 0x6f, 0x70, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x42, 0x79, 0x4f, 0x70, - 0x22, 0x6a, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6f, 0x72, - 0x67, 0x65, 0x74, 0x12, 0x2a, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, 0x53, - 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x12, - 0x2b, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x28, 0x0a, 0x0e, - 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x70, 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, 0x30, 0x0a, 0x06, 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, 0x06, 0x73, 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, - 0x4b, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x48, - 0x6f, 0x6f, 0x6b, 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, 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, + 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, 0x82, 0x01, 0x0a, + 0x16, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, + 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x73, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x73, 0x74, 0x69, 0x63, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x08, 0x73, + 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x6f, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x12, + 0x20, 0x0a, 0x0c, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x5f, 0x62, 0x79, 0x5f, 0x6f, 0x70, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x42, 0x79, 0x4f, + 0x70, 0x22, 0x6a, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6f, + 0x72, 0x67, 0x65, 0x74, 0x12, 0x2a, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, + 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x65, 0x74, + 0x12, 0x2b, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x28, 0x0a, + 0x0e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x12, + 0x16, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x22, 0x70, 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, 0x30, 0x0a, 0x06, 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, 0x06, 0x73, 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, 0x4b, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, + 0x48, 0x6f, 0x6f, 0x6b, 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, 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 ( @@ -998,10 +1009,11 @@ var file_v1_operations_proto_goTypes = []interface{}{ (*OperationStats)(nil), // 10: v1.OperationStats (*OperationRunHook)(nil), // 11: v1.OperationRunHook (*BackupProgressEntry)(nil), // 12: v1.BackupProgressEntry - (*ResticSnapshot)(nil), // 13: v1.ResticSnapshot - (*RetentionPolicy)(nil), // 14: v1.RetentionPolicy - (*RestoreProgressEntry)(nil), // 15: v1.RestoreProgressEntry - (*RepoStats)(nil), // 16: v1.RepoStats + (*BackupProgressError)(nil), // 13: v1.BackupProgressError + (*ResticSnapshot)(nil), // 14: v1.ResticSnapshot + (*RetentionPolicy)(nil), // 15: v1.RetentionPolicy + (*RestoreProgressEntry)(nil), // 16: v1.RestoreProgressEntry + (*RepoStats)(nil), // 17: v1.RepoStats } var file_v1_operations_proto_depIdxs = []int32{ 3, // 0: v1.OperationList.operations:type_name -> v1.Operation @@ -1016,16 +1028,17 @@ var file_v1_operations_proto_depIdxs = []int32{ 0, // 9: v1.OperationEvent.type:type_name -> v1.OperationEventType 3, // 10: v1.OperationEvent.operation:type_name -> v1.Operation 12, // 11: v1.OperationBackup.last_status:type_name -> v1.BackupProgressEntry - 13, // 12: v1.OperationIndexSnapshot.snapshot:type_name -> v1.ResticSnapshot - 13, // 13: v1.OperationForget.forget:type_name -> v1.ResticSnapshot - 14, // 14: v1.OperationForget.policy:type_name -> v1.RetentionPolicy - 15, // 15: v1.OperationRestore.status:type_name -> v1.RestoreProgressEntry - 16, // 16: v1.OperationStats.stats:type_name -> v1.RepoStats - 17, // [17:17] is the sub-list for method output_type - 17, // [17:17] is the sub-list for method input_type - 17, // [17:17] is the sub-list for extension type_name - 17, // [17:17] is the sub-list for extension extendee - 0, // [0:17] is the sub-list for field type_name + 13, // 12: v1.OperationBackup.errors:type_name -> v1.BackupProgressError + 14, // 13: v1.OperationIndexSnapshot.snapshot:type_name -> v1.ResticSnapshot + 14, // 14: v1.OperationForget.forget:type_name -> v1.ResticSnapshot + 15, // 15: v1.OperationForget.policy:type_name -> v1.RetentionPolicy + 16, // 16: v1.OperationRestore.status:type_name -> v1.RestoreProgressEntry + 17, // 17: v1.OperationStats.stats:type_name -> v1.RepoStats + 18, // [18:18] is the sub-list for method output_type + 18, // [18:18] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name } func init() { file_v1_operations_proto_init() } diff --git a/gen/go/v1/restic.pb.go b/gen/go/v1/restic.pb.go index bab2171a..886f7ff2 100644 --- a/gen/go/v1/restic.pb.go +++ b/gen/go/v1/restic.pb.go @@ -260,6 +260,7 @@ type BackupProgressStatusEntry struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + // See https://restic.readthedocs.io/en/stable/075_scripting.html#id1 PercentDone float64 `protobuf:"fixed64,1,opt,name=percent_done,json=percentDone,proto3" json:"percent_done,omitempty"` // 0.0 - 1.0 TotalFiles int64 `protobuf:"varint,2,opt,name=total_files,json=totalFiles,proto3" json:"total_files,omitempty"` TotalBytes int64 `protobuf:"varint,3,opt,name=total_bytes,json=totalBytes,proto3" json:"total_bytes,omitempty"` @@ -348,6 +349,7 @@ type BackupProgressSummary struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + // See https://restic.readthedocs.io/en/stable/075_scripting.html#summary FilesNew int64 `protobuf:"varint,1,opt,name=files_new,json=filesNew,proto3" json:"files_new,omitempty"` FilesChanged int64 `protobuf:"varint,2,opt,name=files_changed,json=filesChanged,proto3" json:"files_changed,omitempty"` FilesUnmodified int64 `protobuf:"varint,3,opt,name=files_unmodified,json=filesUnmodified,proto3" json:"files_unmodified,omitempty"` @@ -486,6 +488,62 @@ func (x *BackupProgressSummary) GetSnapshotId() string { return "" } +type BackupProgressError struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // See https://restic.readthedocs.io/en/stable/075_scripting.html#error + Item string `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` + During string `protobuf:"bytes,2,opt,name=during,proto3" json:"during,omitempty"` +} + +func (x *BackupProgressError) Reset() { + *x = BackupProgressError{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_restic_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BackupProgressError) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BackupProgressError) ProtoMessage() {} + +func (x *BackupProgressError) ProtoReflect() protoreflect.Message { + mi := &file_v1_restic_proto_msgTypes[5] + 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 BackupProgressError.ProtoReflect.Descriptor instead. +func (*BackupProgressError) Descriptor() ([]byte, []int) { + return file_v1_restic_proto_rawDescGZIP(), []int{5} +} + +func (x *BackupProgressError) GetItem() string { + if x != nil { + return x.Item + } + return "" +} + +func (x *BackupProgressError) GetDuring() string { + if x != nil { + return x.During + } + return "" +} + // RestoreProgressEvent represents a single entry in the restore progress stream. type RestoreProgressEntry struct { state protoimpl.MessageState @@ -504,7 +562,7 @@ type RestoreProgressEntry struct { func (x *RestoreProgressEntry) Reset() { *x = RestoreProgressEntry{} if protoimpl.UnsafeEnabled { - mi := &file_v1_restic_proto_msgTypes[5] + mi := &file_v1_restic_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -517,7 +575,7 @@ func (x *RestoreProgressEntry) String() string { func (*RestoreProgressEntry) ProtoMessage() {} func (x *RestoreProgressEntry) ProtoReflect() protoreflect.Message { - mi := &file_v1_restic_proto_msgTypes[5] + mi := &file_v1_restic_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -530,7 +588,7 @@ func (x *RestoreProgressEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use RestoreProgressEntry.ProtoReflect.Descriptor instead. func (*RestoreProgressEntry) Descriptor() ([]byte, []int) { - return file_v1_restic_proto_rawDescGZIP(), []int{5} + return file_v1_restic_proto_rawDescGZIP(), []int{6} } func (x *RestoreProgressEntry) GetMessageType() string { @@ -597,7 +655,7 @@ type RepoStats struct { func (x *RepoStats) Reset() { *x = RepoStats{} if protoimpl.UnsafeEnabled { - mi := &file_v1_restic_proto_msgTypes[6] + mi := &file_v1_restic_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -610,7 +668,7 @@ func (x *RepoStats) String() string { func (*RepoStats) ProtoMessage() {} func (x *RepoStats) ProtoReflect() protoreflect.Message { - mi := &file_v1_restic_proto_msgTypes[6] + mi := &file_v1_restic_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -623,7 +681,7 @@ func (x *RepoStats) ProtoReflect() protoreflect.Message { // Deprecated: Use RepoStats.ProtoReflect.Descriptor instead. func (*RepoStats) Descriptor() ([]byte, []int) { - return file_v1_restic_proto_rawDescGZIP(), []int{6} + return file_v1_restic_proto_rawDescGZIP(), []int{7} } func (x *RepoStats) GetTotalSize() int64 { @@ -738,42 +796,46 @@ var file_v1_restic_proto_rawDesc = []byte{ 0x52, 0x0d, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x49, 0x64, - 0x22, 0x95, 0x02, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x50, 0x72, 0x6f, 0x67, - 0x72, 0x65, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x27, 0x0a, 0x0f, - 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x5f, 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x45, 0x6c, - 0x61, 0x70, 0x73, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, - 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, - 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, - 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x12, 0x1f, 0x0a, - 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x25, - 0x0a, 0x0e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, - 0x5f, 0x64, 0x6f, 0x6e, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, 0x70, 0x65, 0x72, - 0x63, 0x65, 0x6e, 0x74, 0x44, 0x6f, 0x6e, 0x65, 0x22, 0xe0, 0x01, 0x0a, 0x09, 0x52, 0x65, 0x70, - 0x6f, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, - 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x75, - 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x73, 0x69, 0x7a, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x55, 0x6e, 0x63, - 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2b, 0x0a, - 0x11, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x6c, 0x6f, 0x62, 0x43, - 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, - 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x73, 0x6e, - 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 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, + 0x22, 0x41, 0x0a, 0x13, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, + 0x73, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x16, 0x0a, 0x06, 0x64, + 0x75, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x75, 0x72, + 0x69, 0x6e, 0x67, 0x22, 0x95, 0x02, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x50, + 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x5f, 0x65, 0x6c, 0x61, 0x70, 0x73, + 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0e, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x73, 0x45, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x62, 0x79, 0x74, + 0x65, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0d, 0x62, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, + 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x46, 0x69, 0x6c, 0x65, + 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x69, 0x6c, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x65, 0x72, 0x63, + 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x6f, 0x6e, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, + 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x44, 0x6f, 0x6e, 0x65, 0x22, 0xe0, 0x01, 0x0a, 0x09, + 0x52, 0x65, 0x70, 0x6f, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, + 0x61, 0x6c, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x36, 0x0a, 0x17, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x73, + 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x55, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x53, 0x69, 0x7a, 0x65, + 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x63, 0x6f, 0x6d, + 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x28, 0x0a, + 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x6c, + 0x6f, 0x62, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0d, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 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 ( @@ -788,15 +850,16 @@ func file_v1_restic_proto_rawDescGZIP() []byte { return file_v1_restic_proto_rawDescData } -var file_v1_restic_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_v1_restic_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_v1_restic_proto_goTypes = []interface{}{ (*ResticSnapshot)(nil), // 0: v1.ResticSnapshot (*ResticSnapshotList)(nil), // 1: v1.ResticSnapshotList (*BackupProgressEntry)(nil), // 2: v1.BackupProgressEntry (*BackupProgressStatusEntry)(nil), // 3: v1.BackupProgressStatusEntry (*BackupProgressSummary)(nil), // 4: v1.BackupProgressSummary - (*RestoreProgressEntry)(nil), // 5: v1.RestoreProgressEntry - (*RepoStats)(nil), // 6: v1.RepoStats + (*BackupProgressError)(nil), // 5: v1.BackupProgressError + (*RestoreProgressEntry)(nil), // 6: v1.RestoreProgressEntry + (*RepoStats)(nil), // 7: v1.RepoStats } var file_v1_restic_proto_depIdxs = []int32{ 0, // 0: v1.ResticSnapshotList.snapshots:type_name -> v1.ResticSnapshot @@ -876,7 +939,7 @@ func file_v1_restic_proto_init() { } } file_v1_restic_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RestoreProgressEntry); i { + switch v := v.(*BackupProgressError); i { case 0: return &v.state case 1: @@ -888,6 +951,18 @@ func file_v1_restic_proto_init() { } } file_v1_restic_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RestoreProgressEntry); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_restic_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RepoStats); i { case 0: return &v.state @@ -910,7 +985,7 @@ func file_v1_restic_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_restic_proto_rawDesc, NumEnums: 0, - NumMessages: 7, + NumMessages: 8, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/orchestrator/taskbackup.go b/internal/orchestrator/taskbackup.go index ace9857d..d4752554 100644 --- a/internal/orchestrator/taskbackup.go +++ b/internal/orchestrator/taskbackup.go @@ -15,6 +15,8 @@ import ( "google.golang.org/protobuf/proto" ) +var maxBackupErrorHistoryLength = 20 // arbitrary limit on the number of file read errors recorded in a backup operation to prevent it from growing too large. + // BackupTask is a scheduled backup operation. type BackupTask struct { name string @@ -119,16 +121,32 @@ func backupHelper(ctx context.Context, t Task, orchestrator *Orchestrator, plan } lastSent = time.Now() - // prevents flickering output when a status entry omits the CurrentFiles property. Largely cosmetic. - if len(entry.CurrentFiles) == 0 { - entry.CurrentFiles = lastFiles - } else { - lastFiles = entry.CurrentFiles - } + if entry.MessageType == "status" { + // prevents flickering output when a status entry omits the CurrentFiles property. Largely cosmetic. + if len(entry.CurrentFiles) == 0 { + entry.CurrentFiles = lastFiles + } else { + lastFiles = entry.CurrentFiles + } - backupOp.OperationBackup.LastStatus = protoutil.BackupProgressEntryToProto(entry) - if err := orchestrator.OpLog.Update(op); err != nil { - zap.S().Errorf("failed to update oplog with progress for backup: %v", err) + backupOp.OperationBackup.LastStatus = protoutil.BackupProgressEntryToProto(entry) + if err := orchestrator.OpLog.Update(op); err != nil { + zap.S().Errorf("failed to update oplog with progress for backup: %v", err) + } + } else if entry.MessageType == "error" { + zap.S().Warnf("backup error: %v", entry.Error) + backupError, err := protoutil.BackupProgressEntryToBackupError(entry) + if err != nil { + zap.S().Errorf("failed to convert backup progress entry to backup error: %v", err) + return + } + if len(backupOp.OperationBackup.Errors) > maxBackupErrorHistoryLength { + zap.S().Warnf("too many errors, not adding more to backup entry.") + return + } + backupOp.OperationBackup.Errors = append(backupOp.OperationBackup.Errors, backupError) + } else if entry.MessageType != "summary" { + zap.S().Warnf("unexpected message type %q in backup progress entry", entry.MessageType) } }) diff --git a/internal/protoutil/conversion.go b/internal/protoutil/conversion.go index 7f1c7e24..d7b7b471 100644 --- a/internal/protoutil/conversion.go +++ b/internal/protoutil/conversion.go @@ -1,6 +1,8 @@ package protoutil import ( + "errors" + v1 "github.com/garethgeorge/backrest/gen/go/v1" "github.com/garethgeorge/backrest/pkg/restic" ) @@ -73,6 +75,18 @@ func BackupProgressEntryToProto(b *restic.BackupProgressEntry) *v1.BackupProgres } } +// BackupProgressEntryToBackupError converts a BackupProgressEntry to a BackupError if it's type is "error" +func BackupProgressEntryToBackupError(b *restic.BackupProgressEntry) (*v1.BackupProgressError, error) { + if b.MessageType != "error" { + return nil, errors.New("BackupProgressEntry is not of type error") + } + + return &v1.BackupProgressError{ + Item: b.Item, + During: b.During, + }, nil +} + func RetentionPolicyFromProto(p *v1.RetentionPolicy) *restic.RetentionPolicy { return &restic.RetentionPolicy{ KeepLastN: int(p.KeepLastN), diff --git a/pkg/restic/outputs.go b/pkg/restic/outputs.go index dade304d..c8b6857f 100644 --- a/pkg/restic/outputs.go +++ b/pkg/restic/outputs.go @@ -49,7 +49,12 @@ func (s *Snapshot) Validate() error { type BackupProgressEntry struct { // Common fields - MessageType string `json:"message_type"` // "summary" or "status" + MessageType string `json:"message_type"` // "summary" or "status" or "error" + + // Error fields + Error any `json:"error"` + During string `json:"during"` + Item string `json:"item"` // Summary fields FilesNew int `json:"files_new"` diff --git a/pkg/restic/restic_test.go b/pkg/restic/restic_test.go index 7001d2b7..ae86844a 100644 --- a/pkg/restic/restic_test.go +++ b/pkg/restic/restic_test.go @@ -3,6 +3,7 @@ package restic import ( "bytes" "context" + "errors" "fmt" "reflect" "slices" @@ -43,6 +44,8 @@ func TestResticBackup(t *testing.T) { testData := helpers.CreateTestData(t) testData2 := helpers.CreateTestData(t) + testDataUnreadable := t.TempDir() + helpers.CreateUnreadable(t, testDataUnreadable+"/unreadable") var tests = []struct { name string @@ -75,6 +78,11 @@ func TestResticBackup(t *testing.T) { opts: []BackupOption{}, wantErr: true, }, + { + name: "with unreadable file", + opts: []BackupOption{WithBackupPaths(testData), WithBackupPaths(testDataUnreadable)}, + wantErr: true, + }, } for _, tc := range tests { @@ -107,6 +115,46 @@ func TestResticBackup(t *testing.T) { } } +func TestResticPartialBackup(t *testing.T) { + t.Parallel() + repo := t.TempDir() + + // create a new repo with cache disabled for testing + r := NewRepo(helpers.ResticBinary(t), &v1.Repo{ + Id: "test", + Uri: repo, + Password: "test", + }, WithFlags("--no-cache")) + if err := r.Init(context.Background()); err != nil { + t.Fatalf("failed to init repo: %v", err) + } + + testDataUnreadable := t.TempDir() + helpers.CreateUnreadable(t, testDataUnreadable+"/unreadable") + + var entries []*BackupProgressEntry + + summary, err := r.Backup(context.Background(), func(entry *BackupProgressEntry) { + entries = append(entries, entry) + }, WithBackupPaths(testDataUnreadable)) + if !errors.Is(err, ErrPartialBackup) { + t.Fatalf("wanted error to be partial backup, got: %v", err) + } + if summary == nil { + t.Fatalf("wanted summary, got: nil") + } + + if summary.TotalFilesProcessed != 0 { + t.Errorf("wanted 0 files, got: %d", summary.TotalFilesProcessed) + } + + if !slices.ContainsFunc(entries, func(e *BackupProgressEntry) bool { + return e.MessageType == "error" && e.Item == testDataUnreadable+"/unreadable" + }) { + t.Errorf("wanted entries to contain an error event for the unreadable file, got: %v", entries) + } +} + func TestResticBackupLots(t *testing.T) { t.Parallel() t.Skip("this test takes a long time to run") diff --git a/proto/v1/operations.proto b/proto/v1/operations.proto index 7d4143b3..b9406f36 100644 --- a/proto/v1/operations.proto +++ b/proto/v1/operations.proto @@ -66,6 +66,7 @@ enum OperationStatus { message OperationBackup { BackupProgressEntry last_status = 3; + repeated BackupProgressError errors = 4; } // OperationIndexSnapshot tracks that a snapshot was detected by backrest. diff --git a/proto/v1/restic.proto b/proto/v1/restic.proto index 182e8db4..411a9483 100644 --- a/proto/v1/restic.proto +++ b/proto/v1/restic.proto @@ -31,6 +31,7 @@ message BackupProgressEntry { // BackupProgressStatusEntry represents a single status entry in the backup progress stream. message BackupProgressStatusEntry { + // See https://restic.readthedocs.io/en/stable/075_scripting.html#id1 double percent_done = 1; // 0.0 - 1.0 int64 total_files = 2; int64 total_bytes = 3; @@ -41,6 +42,7 @@ message BackupProgressStatusEntry { // BackupProgressSummary represents a the summary event emitted at the end of a backup stream. message BackupProgressSummary { + // See https://restic.readthedocs.io/en/stable/075_scripting.html#summary int64 files_new = 1; int64 files_changed = 2; int64 files_unmodified = 3; @@ -56,6 +58,13 @@ message BackupProgressSummary { string snapshot_id = 13; } +message BackupProgressError { + // See https://restic.readthedocs.io/en/stable/075_scripting.html#error + string item = 1; + string during = 2; + string message = 3; +} + // RestoreProgressEvent represents a single entry in the restore progress stream. message RestoreProgressEntry { string message_type = 1; // "summary" or "status" diff --git a/test/helpers/testdata.go b/test/helpers/testdata.go index ab912a90..235d23cb 100644 --- a/test/helpers/testdata.go +++ b/test/helpers/testdata.go @@ -10,7 +10,7 @@ import ( func CreateTestData(t *testing.T) string { t.Helper() dir := t.TempDir() - + for i := 0; i < 100; i++ { err := os.WriteFile(path.Join(dir, fmt.Sprintf("file%2d", i)), []byte(fmt.Sprintf("test data %d", i)), 0644) if err != nil { @@ -18,4 +18,14 @@ func CreateTestData(t *testing.T) string { } } return dir -} \ No newline at end of file +} + +func CreateUnreadable(t *testing.T, path string) { + t.Helper() + + // Create a file that can be written but can't be read by the current user + err := os.WriteFile(path, []byte("test data"), 0200) + if err != nil { + t.Fatalf("failed to create unreadable file: %v", err) + } +} diff --git a/webui/gen/ts/v1/operations_pb.ts b/webui/gen/ts/v1/operations_pb.ts index 0c3a0e3c..229c6461 100644 --- a/webui/gen/ts/v1/operations_pb.ts +++ b/webui/gen/ts/v1/operations_pb.ts @@ -5,7 +5,7 @@ import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; import { Message, proto3, protoInt64 } from "@bufbuild/protobuf"; -import { BackupProgressEntry, RepoStats, ResticSnapshot, RestoreProgressEntry } from "./restic_pb.js"; +import { BackupProgressEntry, BackupProgressError, RepoStats, ResticSnapshot, RestoreProgressEntry } from "./restic_pb.js"; import { RetentionPolicy } from "./config_pb.js"; /** @@ -352,6 +352,11 @@ export class OperationBackup extends Message { */ lastStatus?: BackupProgressEntry; + /** + * @generated from field: repeated v1.BackupProgressError errors = 4; + */ + errors: BackupProgressError[] = []; + constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); @@ -361,6 +366,7 @@ export class OperationBackup extends Message { static readonly typeName = "v1.OperationBackup"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ { no: 3, name: "last_status", kind: "message", T: BackupProgressEntry }, + { no: 4, name: "errors", kind: "message", T: BackupProgressError, repeated: true }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): OperationBackup { diff --git a/webui/gen/ts/v1/restic_pb.ts b/webui/gen/ts/v1/restic_pb.ts index bbdd09fd..1b605d3b 100644 --- a/webui/gen/ts/v1/restic_pb.ts +++ b/webui/gen/ts/v1/restic_pb.ts @@ -189,6 +189,8 @@ export class BackupProgressEntry extends Message { */ export class BackupProgressStatusEntry extends Message { /** + * See https://restic.readthedocs.io/en/stable/075_scripting.html#id1 + * * 0.0 - 1.0 * * @generated from field: double percent_done = 1; @@ -260,6 +262,8 @@ export class BackupProgressStatusEntry extends Message { /** + * See https://restic.readthedocs.io/en/stable/075_scripting.html#summary + * * @generated from field: int64 files_new = 1; */ filesNew = protoInt64.zero; @@ -364,6 +368,51 @@ export class BackupProgressSummary extends Message { } } +/** + * @generated from message v1.BackupProgressError + */ +export class BackupProgressError extends Message { + /** + * See https://restic.readthedocs.io/en/stable/075_scripting.html#error + * + * @generated from field: string item = 1; + */ + item = ""; + + /** + * @generated from field: string during = 2; + */ + during = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "v1.BackupProgressError"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "item", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "during", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): BackupProgressError { + return new BackupProgressError().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): BackupProgressError { + return new BackupProgressError().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): BackupProgressError { + return new BackupProgressError().fromJsonString(jsonString, options); + } + + static equals(a: BackupProgressError | PlainMessage | undefined, b: BackupProgressError | PlainMessage | undefined): boolean { + return proto3.util.equals(BackupProgressError, a, b); + } +} + /** * RestoreProgressEvent represents a single entry in the restore progress stream. * diff --git a/webui/src/components/OperationList.tsx b/webui/src/components/OperationList.tsx index 320c109e..8c30b1e6 100644 --- a/webui/src/components/OperationList.tsx +++ b/webui/src/components/OperationList.tsx @@ -210,19 +210,29 @@ export const OperationRow = ({ if (operation.op.case === "operationBackup") { const backupOp = operation.op.value; + const items: { key: number, label: string, children: React.ReactNode }[] = [ + { + key: 1, + label: "Backup Details", + children: , + }, + ]; + + if (backupOp.errors.length > 0) { + items.splice(0, 0, { + key: 2, + label: "Item Errors", + children:
{backupOp.errors.map(e => "Error on item: " + e.item).join("\n")}
, + }); + } + body = ( <> , - }, - ]} + items={items} /> );