From a99cc8545298ffb899249f62afc9213fb300e231 Mon Sep 17 00:00:00 2001 From: garethgeorge Date: Sat, 11 Nov 2023 23:38:50 -0800 Subject: [PATCH] feat: implement repo, edit, and supporting RPCs --- cmd/resticui/resticui.go | 12 +- gen/go/v1/config.pb.go | 226 +++++++++------------- gen/go/v1/restic.pb.go | 227 +++++++++++++++-------- gen/go/v1/service.pb.go | 204 +++++++++++++++----- gen/go/v1/service.pb.gw.go | 123 ++++++++++++ gen/go/v1/service_grpc.pb.go | 37 ++++ internal/api/api.go | 13 +- internal/api/server.go | 59 +++++- internal/config/config.go | 55 +++++- internal/config/config_test.go | 114 ++++++++++++ internal/config/validate.go | 12 +- internal/config/yamlstore.go | 19 +- internal/orchestrator/orchestrator.go | 117 ++++++++++++ internal/orchestrator/repo.go | 108 +++++++++++ pkg/restic/outputs.go | 20 ++ pkg/restic/outputs_test.go | 38 ++++ pkg/restic/restic.go | 12 +- pkg/restic/restic_test.go | 7 + proto/v1/config.proto | 13 +- proto/v1/restic.proto | 4 + proto/v1/service.proto | 12 ++ webui/gen/ts/v1/config.pb.ts | 8 +- webui/gen/ts/v1/restic.pb.ts | 4 + webui/gen/ts/v1/service.pb.ts | 9 + webui/src/components/URIAutocomplete.tsx | 4 +- webui/src/state/config.ts | 2 +- webui/src/views/AddRepoModel.tsx | 83 ++++++--- webui/src/views/App.tsx | 2 +- {static => webui}/static.go | 3 +- 29 files changed, 1194 insertions(+), 353 deletions(-) create mode 100644 internal/config/config_test.go create mode 100644 internal/orchestrator/orchestrator.go create mode 100644 internal/orchestrator/repo.go rename {static => webui}/static.go (54%) diff --git a/cmd/resticui/resticui.go b/cmd/resticui/resticui.go index 3f83f2b6..7b6fd4fe 100644 --- a/cmd/resticui/resticui.go +++ b/cmd/resticui/resticui.go @@ -10,7 +10,9 @@ import ( "syscall" "github.com/garethgeorge/resticui/internal/api" - "github.com/garethgeorge/resticui/static" + "github.com/garethgeorge/resticui/internal/config" + "github.com/garethgeorge/resticui/internal/orchestrator" + static "github.com/garethgeorge/resticui/webui" "go.uber.org/zap" _ "embed" @@ -21,6 +23,10 @@ func main() { ctx, cancel := context.WithCancel(ctx) go onterm(cancel) + if _, err := config.Default.Get(); err != nil { + zap.S().Fatalf("Error loading config: %v", err) + } + var wg sync.WaitGroup // Configure the HTTP mux @@ -32,11 +38,13 @@ func main() { Handler: mux, } + orchestrator := orchestrator.NewOrchestrator(config.Default) + // Serve the API wg.Add(1) go func() { defer wg.Done() - err := api.ServeAPI(ctx, mux) + err := api.ServeAPI(ctx, orchestrator, mux) if err != nil { zap.S().Fatal("Error serving API", zap.Error(err)) } diff --git a/gen/go/v1/config.pb.go b/gen/go/v1/config.pb.go index e937f02e..605ac79f 100644 --- a/gen/go/v1/config.pb.go +++ b/gen/go/v1/config.pb.go @@ -25,9 +25,12 @@ type Config struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Version int32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` - Repos []*Repo `protobuf:"bytes,3,rep,name=repos,proto3" json:"repos,omitempty"` - Plans []*Plan `protobuf:"bytes,4,rep,name=plans,proto3" json:"plans,omitempty"` + // modification number, used for read-modify-write consistency in the UI. Incremented on every write. + Modno int32 `protobuf:"varint,1,opt,name=modno,proto3" json:"modno,omitempty"` + // override the hostname tagged on backups. If provided it will be used in addition to tags to group backups. + HostOverride string `protobuf:"bytes,2,opt,name=host_override,proto3" json:"host_override,omitempty"` + Repos []*Repo `protobuf:"bytes,3,rep,name=repos,proto3" json:"repos,omitempty"` + Plans []*Plan `protobuf:"bytes,4,rep,name=plans,proto3" json:"plans,omitempty"` } func (x *Config) Reset() { @@ -62,13 +65,20 @@ func (*Config) Descriptor() ([]byte, []int) { return file_v1_config_proto_rawDescGZIP(), []int{0} } -func (x *Config) GetVersion() int32 { +func (x *Config) GetModno() int32 { if x != nil { - return x.Version + return x.Modno } return 0 } +func (x *Config) GetHostOverride() string { + if x != nil { + return x.HostOverride + } + return "" +} + func (x *Config) GetRepos() []*Repo { if x != nil { return x.Repos @@ -83,61 +93,6 @@ func (x *Config) GetPlans() []*Plan { return nil } -type User struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` // plaintext password -} - -func (x *User) Reset() { - *x = User{} - if protoimpl.UnsafeEnabled { - mi := &file_v1_config_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *User) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*User) ProtoMessage() {} - -func (x *User) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use User.ProtoReflect.Descriptor instead. -func (*User) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{1} -} - -func (x *User) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *User) GetPassword() string { - if x != nil { - return x.Password - } - return "" -} - type Repo struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -153,7 +108,7 @@ type Repo struct { func (x *Repo) Reset() { *x = Repo{} if protoimpl.UnsafeEnabled { - mi := &file_v1_config_proto_msgTypes[2] + mi := &file_v1_config_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -166,7 +121,7 @@ func (x *Repo) String() string { func (*Repo) ProtoMessage() {} func (x *Repo) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[2] + mi := &file_v1_config_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -179,7 +134,7 @@ func (x *Repo) ProtoReflect() protoreflect.Message { // Deprecated: Use Repo.ProtoReflect.Descriptor instead. func (*Repo) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{2} + return file_v1_config_proto_rawDescGZIP(), []int{1} } func (x *Repo) GetId() string { @@ -233,7 +188,7 @@ type Plan struct { func (x *Plan) Reset() { *x = Plan{} if protoimpl.UnsafeEnabled { - mi := &file_v1_config_proto_msgTypes[3] + mi := &file_v1_config_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -246,7 +201,7 @@ func (x *Plan) String() string { func (*Plan) ProtoMessage() {} func (x *Plan) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[3] + mi := &file_v1_config_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -259,7 +214,7 @@ func (x *Plan) ProtoReflect() protoreflect.Message { // Deprecated: Use Plan.ProtoReflect.Descriptor instead. func (*Plan) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{3} + return file_v1_config_proto_rawDescGZIP(), []int{2} } func (x *Plan) GetId() string { @@ -323,7 +278,7 @@ type RetentionPolicy struct { func (x *RetentionPolicy) Reset() { *x = RetentionPolicy{} if protoimpl.UnsafeEnabled { - mi := &file_v1_config_proto_msgTypes[4] + mi := &file_v1_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -336,7 +291,7 @@ func (x *RetentionPolicy) String() string { func (*RetentionPolicy) ProtoMessage() {} func (x *RetentionPolicy) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[4] + mi := &file_v1_config_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -349,7 +304,7 @@ func (x *RetentionPolicy) ProtoReflect() protoreflect.Message { // Deprecated: Use RetentionPolicy.ProtoReflect.Descriptor instead. func (*RetentionPolicy) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{4} + return file_v1_config_proto_rawDescGZIP(), []int{3} } func (x *RetentionPolicy) GetMaxUnusedLimit() string { @@ -412,57 +367,55 @@ var File_v1_config_proto protoreflect.FileDescriptor var file_v1_config_proto_rawDesc = []byte{ 0x0a, 0x0f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x12, 0x02, 0x76, 0x31, 0x22, 0x62, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x05, 0x72, 0x65, 0x70, - 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x70, 0x6f, 0x52, 0x05, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x1e, 0x0a, 0x05, 0x70, 0x6c, 0x61, - 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, - 0x61, 0x6e, 0x52, 0x05, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x22, 0x36, 0x0a, 0x04, 0x55, 0x73, 0x65, - 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, - 0x64, 0x22, 0x6c, 0x0a, 0x04, 0x52, 0x65, 0x70, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x1a, 0x0a, 0x08, 0x70, - 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, - 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x65, 0x6e, 0x76, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, - 0x67, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x22, - 0xa3, 0x01, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x65, 0x70, 0x6f, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x65, 0x70, 0x6f, 0x12, 0x14, 0x0a, 0x05, - 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x74, - 0x68, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x73, 0x18, 0x05, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x73, 0x12, 0x12, - 0x0a, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x72, - 0x6f, 0x6e, 0x12, 0x31, 0x0a, 0x09, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x07, 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, 0x09, 0x72, 0x65, 0x74, 0x65, - 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb2, 0x02, 0x0a, 0x0f, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, - 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x78, - 0x5f, 0x75, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x4c, 0x69, - 0x6d, 0x69, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x6c, 0x61, 0x73, 0x74, - 0x5f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6b, 0x65, 0x65, 0x70, 0x4c, 0x61, - 0x73, 0x74, 0x4e, 0x12, 0x1f, 0x0a, 0x0b, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x68, 0x6f, 0x75, 0x72, - 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6b, 0x65, 0x65, 0x70, 0x48, 0x6f, - 0x75, 0x72, 0x6c, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x64, 0x61, 0x69, - 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6b, 0x65, 0x65, 0x70, 0x44, 0x61, - 0x69, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x77, 0x65, 0x65, 0x6b, - 0x6c, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6b, 0x65, 0x65, 0x70, 0x57, 0x65, - 0x65, 0x6b, 0x6c, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x6d, 0x6f, 0x6e, - 0x74, 0x68, 0x6c, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6b, 0x65, 0x65, 0x70, - 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x6b, 0x65, 0x65, 0x70, 0x5f, - 0x79, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6b, 0x65, - 0x65, 0x70, 0x59, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x6b, 0x65, 0x65, 0x70, - 0x5f, 0x77, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6b, 0x65, 0x65, 0x70, 0x57, 0x69, 0x74, 0x68, - 0x69, 0x6e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, - 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x72, 0x65, 0x73, 0x74, 0x69, 0x63, 0x75, 0x69, 0x2f, 0x67, - 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x6f, 0x12, 0x02, 0x76, 0x31, 0x22, 0x84, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x6e, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x05, 0x6d, 0x6f, 0x64, 0x6e, 0x6f, 0x12, 0x24, 0x0a, 0x0d, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x6f, + 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x68, + 0x6f, 0x73, 0x74, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x1e, 0x0a, 0x05, + 0x72, 0x65, 0x70, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x52, 0x05, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x12, 0x1e, 0x0a, 0x05, + 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, + 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x05, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x22, 0x6c, 0x0a, 0x04, + 0x52, 0x65, 0x70, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x75, 0x72, 0x69, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x03, 0x65, 0x6e, 0x76, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x05, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x22, 0xa3, 0x01, 0x0a, 0x04, 0x50, + 0x6c, 0x61, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x65, 0x70, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x72, 0x65, 0x70, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x12, 0x1a, 0x0a, + 0x08, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x08, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x72, 0x6f, + 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x12, 0x31, 0x0a, + 0x09, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 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, 0x09, 0x72, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0xb2, 0x02, 0x0a, 0x0f, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x6e, 0x75, 0x73, + 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, + 0x6d, 0x61, 0x78, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1e, + 0x0a, 0x0b, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x09, 0x6b, 0x65, 0x65, 0x70, 0x4c, 0x61, 0x73, 0x74, 0x4e, 0x12, 0x1f, + 0x0a, 0x0b, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x68, 0x6f, 0x75, 0x72, 0x6c, 0x79, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6b, 0x65, 0x65, 0x70, 0x48, 0x6f, 0x75, 0x72, 0x6c, 0x79, 0x12, + 0x1d, 0x0a, 0x0a, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x09, 0x6b, 0x65, 0x65, 0x70, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x12, 0x1f, + 0x0a, 0x0b, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x77, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6b, 0x65, 0x65, 0x70, 0x57, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x12, + 0x21, 0x0a, 0x0c, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x6b, 0x65, 0x65, 0x70, 0x4d, 0x6f, 0x6e, 0x74, 0x68, + 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x79, 0x65, 0x61, 0x72, 0x6c, + 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6b, 0x65, 0x65, 0x70, 0x59, 0x65, 0x61, + 0x72, 0x6c, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x77, 0x69, 0x74, 0x68, + 0x69, 0x6e, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x12, 0x6b, 0x65, 0x65, 0x70, 0x57, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x44, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, + 0x2f, 0x72, 0x65, 0x73, 0x74, 0x69, 0x63, 0x75, 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -477,18 +430,17 @@ func file_v1_config_proto_rawDescGZIP() []byte { return file_v1_config_proto_rawDescData } -var file_v1_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_v1_config_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_v1_config_proto_goTypes = []interface{}{ (*Config)(nil), // 0: v1.Config - (*User)(nil), // 1: v1.User - (*Repo)(nil), // 2: v1.Repo - (*Plan)(nil), // 3: v1.Plan - (*RetentionPolicy)(nil), // 4: v1.RetentionPolicy + (*Repo)(nil), // 1: v1.Repo + (*Plan)(nil), // 2: v1.Plan + (*RetentionPolicy)(nil), // 3: v1.RetentionPolicy } var file_v1_config_proto_depIdxs = []int32{ - 2, // 0: v1.Config.repos:type_name -> v1.Repo - 3, // 1: v1.Config.plans:type_name -> v1.Plan - 4, // 2: v1.Plan.retention:type_name -> v1.RetentionPolicy + 1, // 0: v1.Config.repos:type_name -> v1.Repo + 2, // 1: v1.Config.plans:type_name -> v1.Plan + 3, // 2: v1.Plan.retention:type_name -> v1.RetentionPolicy 3, // [3:3] is the sub-list for method output_type 3, // [3:3] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name @@ -515,18 +467,6 @@ func file_v1_config_proto_init() { } } file_v1_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*User); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_v1_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Repo); i { case 0: return &v.state @@ -538,7 +478,7 @@ func file_v1_config_proto_init() { return nil } } - file_v1_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + file_v1_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Plan); i { case 0: return &v.state @@ -550,7 +490,7 @@ func file_v1_config_proto_init() { return nil } } - file_v1_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + file_v1_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RetentionPolicy); i { case 0: return &v.state @@ -569,7 +509,7 @@ func file_v1_config_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_config_proto_rawDesc, NumEnums: 0, - NumMessages: 5, + NumMessages: 4, NumExtensions: 0, NumServices: 0, }, diff --git a/gen/go/v1/restic.pb.go b/gen/go/v1/restic.pb.go index 87e1bb95..2391355d 100644 --- a/gen/go/v1/restic.pb.go +++ b/gen/go/v1/restic.pb.go @@ -123,6 +123,53 @@ func (x *ResticSnapshot) GetTags() []string { return nil } +type ResticSnapshotList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Snapshots []*ResticSnapshot `protobuf:"bytes,1,rep,name=snapshots,proto3" json:"snapshots,omitempty"` +} + +func (x *ResticSnapshotList) Reset() { + *x = ResticSnapshotList{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_restic_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResticSnapshotList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResticSnapshotList) ProtoMessage() {} + +func (x *ResticSnapshotList) ProtoReflect() protoreflect.Message { + mi := &file_v1_restic_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResticSnapshotList.ProtoReflect.Descriptor instead. +func (*ResticSnapshotList) Descriptor() ([]byte, []int) { + return file_v1_restic_proto_rawDescGZIP(), []int{1} +} + +func (x *ResticSnapshotList) GetSnapshots() []*ResticSnapshot { + if x != nil { + return x.Snapshots + } + return nil +} + type BackupProgressEntry struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -138,7 +185,7 @@ type BackupProgressEntry struct { func (x *BackupProgressEntry) Reset() { *x = BackupProgressEntry{} if protoimpl.UnsafeEnabled { - mi := &file_v1_restic_proto_msgTypes[1] + mi := &file_v1_restic_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -151,7 +198,7 @@ func (x *BackupProgressEntry) String() string { func (*BackupProgressEntry) ProtoMessage() {} func (x *BackupProgressEntry) ProtoReflect() protoreflect.Message { - mi := &file_v1_restic_proto_msgTypes[1] + mi := &file_v1_restic_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -164,7 +211,7 @@ func (x *BackupProgressEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use BackupProgressEntry.ProtoReflect.Descriptor instead. func (*BackupProgressEntry) Descriptor() ([]byte, []int) { - return file_v1_restic_proto_rawDescGZIP(), []int{1} + return file_v1_restic_proto_rawDescGZIP(), []int{2} } func (m *BackupProgressEntry) GetEntry() isBackupProgressEntry_Entry { @@ -219,7 +266,7 @@ type BackupProgressStatusEntry struct { func (x *BackupProgressStatusEntry) Reset() { *x = BackupProgressStatusEntry{} if protoimpl.UnsafeEnabled { - mi := &file_v1_restic_proto_msgTypes[2] + mi := &file_v1_restic_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -232,7 +279,7 @@ func (x *BackupProgressStatusEntry) String() string { func (*BackupProgressStatusEntry) ProtoMessage() {} func (x *BackupProgressStatusEntry) ProtoReflect() protoreflect.Message { - mi := &file_v1_restic_proto_msgTypes[2] + mi := &file_v1_restic_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -245,7 +292,7 @@ func (x *BackupProgressStatusEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use BackupProgressStatusEntry.ProtoReflect.Descriptor instead. func (*BackupProgressStatusEntry) Descriptor() ([]byte, []int) { - return file_v1_restic_proto_rawDescGZIP(), []int{2} + return file_v1_restic_proto_rawDescGZIP(), []int{3} } func (x *BackupProgressStatusEntry) GetPercentDone() float64 { @@ -306,7 +353,7 @@ type BackupProgressSummary struct { func (x *BackupProgressSummary) Reset() { *x = BackupProgressSummary{} if protoimpl.UnsafeEnabled { - mi := &file_v1_restic_proto_msgTypes[3] + mi := &file_v1_restic_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -319,7 +366,7 @@ func (x *BackupProgressSummary) String() string { func (*BackupProgressSummary) ProtoMessage() {} func (x *BackupProgressSummary) ProtoReflect() protoreflect.Message { - mi := &file_v1_restic_proto_msgTypes[3] + mi := &file_v1_restic_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -332,7 +379,7 @@ func (x *BackupProgressSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use BackupProgressSummary.ProtoReflect.Descriptor instead. func (*BackupProgressSummary) Descriptor() ([]byte, []int) { - return file_v1_restic_proto_rawDescGZIP(), []int{3} + return file_v1_restic_proto_rawDescGZIP(), []int{4} } func (x *BackupProgressSummary) GetFilesNew() int64 { @@ -443,64 +490,68 @@ var file_v1_restic_proto_rawDesc = []byte{ 0x52, 0x06, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x74, 0x68, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, - 0x67, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x13, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x72, 0x6f, - 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x37, 0x0a, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x76, 0x31, 0x2e, - 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x35, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x48, - 0x00, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x6e, - 0x74, 0x72, 0x79, 0x22, 0xbe, 0x01, 0x0a, 0x19, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x72, + 0x67, 0x73, 0x22, 0x46, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, 0x53, 0x6e, 0x61, 0x70, + 0x73, 0x68, 0x6f, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x30, 0x0a, 0x09, 0x73, 0x6e, 0x61, 0x70, + 0x73, 0x68, 0x6f, 0x74, 0x73, 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, + 0x09, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x22, 0x8e, 0x01, 0x0a, 0x13, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x37, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x1d, 0x0a, 0x0c, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x6f, 0x6e, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x07, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, - 0x12, 0x20, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, - 0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, - 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, - 0x79, 0x74, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x64, 0x6f, - 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, - 0x64, 0x6f, 0x6e, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x64, 0x6f, - 0x6e, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, - 0x64, 0x6f, 0x6e, 0x65, 0x22, 0x87, 0x04, 0x0a, 0x15, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x50, - 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x1c, - 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x6e, 0x65, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x6e, 0x65, 0x77, 0x12, 0x24, 0x0a, 0x0d, - 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x10, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x75, 0x6e, 0x6d, 0x6f, - 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x66, 0x69, - 0x6c, 0x65, 0x73, 0x5f, 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x1a, - 0x0a, 0x08, 0x64, 0x69, 0x72, 0x73, 0x5f, 0x6e, 0x65, 0x77, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x08, 0x64, 0x69, 0x72, 0x73, 0x5f, 0x6e, 0x65, 0x77, 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x69, - 0x72, 0x73, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0c, 0x64, 0x69, 0x72, 0x73, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x28, - 0x0a, 0x0f, 0x64, 0x69, 0x72, 0x73, 0x5f, 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, - 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x64, 0x69, 0x72, 0x73, 0x5f, 0x75, 0x6e, - 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, - 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x61, - 0x74, 0x61, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x72, 0x65, 0x65, - 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x72, - 0x65, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, - 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x61, - 0x74, 0x61, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x15, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, - 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, - 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x12, 0x34, - 0x0a, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x70, 0x72, - 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, - 0x73, 0x73, 0x65, 0x64, 0x12, 0x26, 0x0a, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, - 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x42, 0x2e, - 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, - 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x72, 0x65, 0x73, 0x74, 0x69, 0x63, - 0x75, 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x79, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x35, 0x0a, 0x07, 0x73, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x76, + 0x31, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, + 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x48, 0x00, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x79, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x22, 0xbe, 0x01, 0x0a, 0x19, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x1d, 0x0a, 0x0c, 0x70, 0x65, 0x72, + 0x63, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x6f, 0x6e, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, + 0x07, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x12, 0x1e, 0x0a, 0x0a, + 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x64, 0x6f, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0a, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x64, 0x6f, 0x6e, 0x65, 0x12, 0x1e, 0x0a, 0x0a, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x64, 0x6f, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x64, 0x6f, 0x6e, 0x65, 0x22, 0x87, 0x04, 0x0a, + 0x15, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, + 0x6e, 0x65, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x73, + 0x5f, 0x6e, 0x65, 0x77, 0x12, 0x24, 0x0a, 0x0d, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x66, 0x69, 0x6c, + 0x65, 0x73, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x10, 0x66, 0x69, + 0x6c, 0x65, 0x73, 0x5f, 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x75, 0x6e, 0x6d, 0x6f, + 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x72, 0x73, 0x5f, 0x6e, + 0x65, 0x77, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x64, 0x69, 0x72, 0x73, 0x5f, 0x6e, + 0x65, 0x77, 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x69, 0x72, 0x73, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x64, 0x69, 0x72, 0x73, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x64, 0x69, 0x72, 0x73, 0x5f, 0x75, + 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0f, 0x64, 0x69, 0x72, 0x73, 0x5f, 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, + 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x73, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x73, + 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x73, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x72, 0x65, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x73, + 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, + 0x12, 0x34, 0x0a, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, + 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x5f, 0x70, 0x72, 0x6f, + 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, + 0x65, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x12, 0x26, 0x0a, 0x0e, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, + 0x65, 0x2f, 0x72, 0x65, 0x73, 0x74, 0x69, 0x63, 0x75, 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -515,21 +566,23 @@ func file_v1_restic_proto_rawDescGZIP() []byte { return file_v1_restic_proto_rawDescData } -var file_v1_restic_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_v1_restic_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_v1_restic_proto_goTypes = []interface{}{ (*ResticSnapshot)(nil), // 0: v1.ResticSnapshot - (*BackupProgressEntry)(nil), // 1: v1.BackupProgressEntry - (*BackupProgressStatusEntry)(nil), // 2: v1.BackupProgressStatusEntry - (*BackupProgressSummary)(nil), // 3: v1.BackupProgressSummary + (*ResticSnapshotList)(nil), // 1: v1.ResticSnapshotList + (*BackupProgressEntry)(nil), // 2: v1.BackupProgressEntry + (*BackupProgressStatusEntry)(nil), // 3: v1.BackupProgressStatusEntry + (*BackupProgressSummary)(nil), // 4: v1.BackupProgressSummary } var file_v1_restic_proto_depIdxs = []int32{ - 2, // 0: v1.BackupProgressEntry.status:type_name -> v1.BackupProgressStatusEntry - 3, // 1: v1.BackupProgressEntry.summary:type_name -> v1.BackupProgressSummary - 2, // [2:2] is the sub-list for method output_type - 2, // [2:2] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 0, // 0: v1.ResticSnapshotList.snapshots:type_name -> v1.ResticSnapshot + 3, // 1: v1.BackupProgressEntry.status:type_name -> v1.BackupProgressStatusEntry + 4, // 2: v1.BackupProgressEntry.summary:type_name -> v1.BackupProgressSummary + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name } func init() { file_v1_restic_proto_init() } @@ -551,7 +604,7 @@ func file_v1_restic_proto_init() { } } file_v1_restic_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BackupProgressEntry); i { + switch v := v.(*ResticSnapshotList); i { case 0: return &v.state case 1: @@ -563,7 +616,7 @@ func file_v1_restic_proto_init() { } } file_v1_restic_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*BackupProgressStatusEntry); i { + switch v := v.(*BackupProgressEntry); i { case 0: return &v.state case 1: @@ -575,6 +628,18 @@ func file_v1_restic_proto_init() { } } file_v1_restic_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BackupProgressStatusEntry); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_v1_restic_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*BackupProgressSummary); i { case 0: return &v.state @@ -587,7 +652,7 @@ func file_v1_restic_proto_init() { } } } - file_v1_restic_proto_msgTypes[1].OneofWrappers = []interface{}{ + file_v1_restic_proto_msgTypes[2].OneofWrappers = []interface{}{ (*BackupProgressEntry_Status)(nil), (*BackupProgressEntry_Summary)(nil), } @@ -597,7 +662,7 @@ func file_v1_restic_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_restic_proto_rawDesc, NumEnums: 0, - NumMessages: 4, + NumMessages: 5, NumExtensions: 0, NumServices: 0, }, diff --git a/gen/go/v1/service.pb.go b/gen/go/v1/service.pb.go index 74c1a6fa..4c8bd017 100644 --- a/gen/go/v1/service.pb.go +++ b/gen/go/v1/service.pb.go @@ -13,6 +13,7 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" + sync "sync" ) const ( @@ -22,67 +23,152 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type ListSnapshotsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RepoId string `protobuf:"bytes,1,opt,name=repo_id,proto3" json:"repo_id,omitempty"` + PlanId string `protobuf:"bytes,2,opt,name=plan_id,proto3" json:"plan_id,omitempty"` +} + +func (x *ListSnapshotsRequest) Reset() { + *x = ListSnapshotsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_v1_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListSnapshotsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSnapshotsRequest) ProtoMessage() {} + +func (x *ListSnapshotsRequest) ProtoReflect() protoreflect.Message { + mi := &file_v1_service_proto_msgTypes[0] + 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 ListSnapshotsRequest.ProtoReflect.Descriptor instead. +func (*ListSnapshotsRequest) Descriptor() ([]byte, []int) { + return file_v1_service_proto_rawDescGZIP(), []int{0} +} + +func (x *ListSnapshotsRequest) GetRepoId() string { + if x != nil { + return x.RepoId + } + return "" +} + +func (x *ListSnapshotsRequest) GetPlanId() string { + if x != nil { + return x.PlanId + } + return "" +} + var File_v1_service_proto protoreflect.FileDescriptor var file_v1_service_proto_rawDesc = []byte{ 0x0a, 0x10, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x76, 0x31, 0x1a, 0x0f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, - 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, - 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xeb, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x74, 0x69, - 0x63, 0x55, 0x49, 0x12, 0x43, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76, - 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3a, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x15, 0x82, - 0xd3, 0xe4, 0x93, 0x02, 0x0f, 0x3a, 0x01, 0x2a, 0x22, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3b, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x52, 0x65, 0x70, 0x6f, 0x12, - 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, - 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x72, 0x65, 0x70, - 0x6f, 0x12, 0x44, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x09, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x65, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x30, 0x01, 0x12, 0x5b, 0x0a, 0x10, 0x50, 0x61, 0x74, 0x68, 0x41, - 0x75, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x12, 0x2e, 0x74, 0x79, - 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, - 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, - 0x73, 0x74, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, - 0x76, 0x31, 0x2f, 0x61, 0x75, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x2f, - 0x70, 0x61, 0x74, 0x68, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, - 0x72, 0x65, 0x73, 0x74, 0x69, 0x63, 0x75, 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x73, + 0x74, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, + 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x4a, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x53, + 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x18, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x6c, 0x61, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x6c, 0x61, 0x6e, + 0x5f, 0x69, 0x64, 0x32, 0xd9, 0x03, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, 0x55, 0x49, + 0x12, 0x43, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x22, 0x12, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3a, 0x0a, 0x09, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x12, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x0a, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x15, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x0f, 0x3a, 0x01, 0x2a, 0x22, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x12, 0x3b, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x52, 0x65, 0x70, 0x6f, 0x12, 0x08, 0x2e, 0x76, + 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x22, 0x1a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x14, 0x3a, 0x01, 0x2a, 0x22, 0x0f, 0x2f, + 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x12, 0x44, + 0x0a, 0x09, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x1a, 0x09, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x12, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0c, 0x12, 0x0a, 0x2f, 0x76, 0x31, 0x2f, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x73, 0x30, 0x01, 0x12, 0x6c, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, + 0x73, 0x68, 0x6f, 0x74, 0x73, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, + 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x16, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, 0x53, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x29, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x23, 0x12, + 0x21, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x2f, 0x7b, + 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x7d, 0x2f, 0x7b, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, + 0x64, 0x7d, 0x12, 0x5b, 0x0a, 0x10, 0x50, 0x61, 0x74, 0x68, 0x41, 0x75, 0x74, 0x6f, 0x63, 0x6f, + 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x11, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x20, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x3a, 0x01, 0x2a, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x61, 0x75, + 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x2f, 0x70, 0x61, 0x74, 0x68, 0x42, + 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, + 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x72, 0x65, 0x73, 0x74, 0x69, + 0x63, 0x75, 0x69, 0x2f, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_v1_service_proto_rawDescOnce sync.Once + file_v1_service_proto_rawDescData = file_v1_service_proto_rawDesc +) + +func file_v1_service_proto_rawDescGZIP() []byte { + file_v1_service_proto_rawDescOnce.Do(func() { + file_v1_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_v1_service_proto_rawDescData) + }) + return file_v1_service_proto_rawDescData } +var file_v1_service_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_v1_service_proto_goTypes = []interface{}{ - (*emptypb.Empty)(nil), // 0: google.protobuf.Empty - (*Config)(nil), // 1: v1.Config - (*Repo)(nil), // 2: v1.Repo - (*types.StringValue)(nil), // 3: types.StringValue - (*Event)(nil), // 4: v1.Event - (*types.StringList)(nil), // 5: types.StringList + (*ListSnapshotsRequest)(nil), // 0: v1.ListSnapshotsRequest + (*emptypb.Empty)(nil), // 1: google.protobuf.Empty + (*Config)(nil), // 2: v1.Config + (*Repo)(nil), // 3: v1.Repo + (*types.StringValue)(nil), // 4: types.StringValue + (*Event)(nil), // 5: v1.Event + (*ResticSnapshotList)(nil), // 6: v1.ResticSnapshotList + (*types.StringList)(nil), // 7: types.StringList } var file_v1_service_proto_depIdxs = []int32{ - 0, // 0: v1.ResticUI.GetConfig:input_type -> google.protobuf.Empty - 1, // 1: v1.ResticUI.SetConfig:input_type -> v1.Config - 2, // 2: v1.ResticUI.AddRepo:input_type -> v1.Repo - 0, // 3: v1.ResticUI.GetEvents:input_type -> google.protobuf.Empty - 3, // 4: v1.ResticUI.PathAutocomplete:input_type -> types.StringValue - 1, // 5: v1.ResticUI.GetConfig:output_type -> v1.Config - 1, // 6: v1.ResticUI.SetConfig:output_type -> v1.Config - 1, // 7: v1.ResticUI.AddRepo:output_type -> v1.Config - 4, // 8: v1.ResticUI.GetEvents:output_type -> v1.Event - 5, // 9: v1.ResticUI.PathAutocomplete:output_type -> types.StringList - 5, // [5:10] is the sub-list for method output_type - 0, // [0:5] is the sub-list for method input_type + 1, // 0: v1.ResticUI.GetConfig:input_type -> google.protobuf.Empty + 2, // 1: v1.ResticUI.SetConfig:input_type -> v1.Config + 3, // 2: v1.ResticUI.AddRepo:input_type -> v1.Repo + 1, // 3: v1.ResticUI.GetEvents:input_type -> google.protobuf.Empty + 0, // 4: v1.ResticUI.ListSnapshots:input_type -> v1.ListSnapshotsRequest + 4, // 5: v1.ResticUI.PathAutocomplete:input_type -> types.StringValue + 2, // 6: v1.ResticUI.GetConfig:output_type -> v1.Config + 2, // 7: v1.ResticUI.SetConfig:output_type -> v1.Config + 2, // 8: v1.ResticUI.AddRepo:output_type -> v1.Config + 5, // 9: v1.ResticUI.GetEvents:output_type -> v1.Event + 6, // 10: v1.ResticUI.ListSnapshots:output_type -> v1.ResticSnapshotList + 7, // 11: v1.ResticUI.PathAutocomplete:output_type -> types.StringList + 6, // [6:12] is the sub-list for method output_type + 0, // [0:6] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name @@ -95,18 +181,34 @@ func file_v1_service_proto_init() { } file_v1_config_proto_init() file_v1_events_proto_init() + file_v1_restic_proto_init() + if !protoimpl.UnsafeEnabled { + file_v1_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListSnapshotsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_service_proto_rawDesc, NumEnums: 0, - NumMessages: 0, + NumMessages: 1, NumExtensions: 0, NumServices: 1, }, GoTypes: file_v1_service_proto_goTypes, DependencyIndexes: file_v1_service_proto_depIdxs, + MessageInfos: file_v1_service_proto_msgTypes, }.Build() File_v1_service_proto = out.File file_v1_service_proto_rawDesc = nil diff --git a/gen/go/v1/service.pb.gw.go b/gen/go/v1/service.pb.gw.go index 8835f202..f96e01ae 100644 --- a/gen/go/v1/service.pb.gw.go +++ b/gen/go/v1/service.pb.gw.go @@ -136,6 +136,78 @@ func request_ResticUI_GetEvents_0(ctx context.Context, marshaler runtime.Marshal } +func request_ResticUI_ListSnapshots_0(ctx context.Context, marshaler runtime.Marshaler, client ResticUIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListSnapshotsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["repo_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "repo_id") + } + + protoReq.RepoId, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "repo_id", err) + } + + val, ok = pathParams["plan_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "plan_id") + } + + protoReq.PlanId, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "plan_id", err) + } + + msg, err := client.ListSnapshots(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_ResticUI_ListSnapshots_0(ctx context.Context, marshaler runtime.Marshaler, server ResticUIServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListSnapshotsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["repo_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "repo_id") + } + + protoReq.RepoId, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "repo_id", err) + } + + val, ok = pathParams["plan_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "plan_id") + } + + protoReq.PlanId, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "plan_id", err) + } + + msg, err := server.ListSnapshots(ctx, &protoReq) + return msg, metadata, err + +} + func request_ResticUI_PathAutocomplete_0(ctx context.Context, marshaler runtime.Marshaler, client ResticUIClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq types.StringValue var metadata runtime.ServerMetadata @@ -258,6 +330,31 @@ func RegisterResticUIHandlerServer(ctx context.Context, mux *runtime.ServeMux, s return }) + mux.Handle("GET", pattern_ResticUI_ListSnapshots_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/v1.ResticUI/ListSnapshots", runtime.WithHTTPPathPattern("/v1/snapshots/{repo_id}/{plan_id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_ResticUI_ListSnapshots_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_ResticUI_ListSnapshots_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_ResticUI_PathAutocomplete_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -412,6 +509,28 @@ func RegisterResticUIHandlerClient(ctx context.Context, mux *runtime.ServeMux, c }) + mux.Handle("GET", pattern_ResticUI_ListSnapshots_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/v1.ResticUI/ListSnapshots", runtime.WithHTTPPathPattern("/v1/snapshots/{repo_id}/{plan_id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_ResticUI_ListSnapshots_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_ResticUI_ListSnapshots_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("POST", pattern_ResticUI_PathAutocomplete_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -446,6 +565,8 @@ var ( pattern_ResticUI_GetEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "events"}, "")) + pattern_ResticUI_ListSnapshots_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "snapshots", "repo_id", "plan_id"}, "")) + pattern_ResticUI_PathAutocomplete_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "autocomplete", "path"}, "")) ) @@ -458,5 +579,7 @@ var ( forward_ResticUI_GetEvents_0 = runtime.ForwardResponseStream + forward_ResticUI_ListSnapshots_0 = runtime.ForwardResponseMessage + forward_ResticUI_PathAutocomplete_0 = runtime.ForwardResponseMessage ) diff --git a/gen/go/v1/service_grpc.pb.go b/gen/go/v1/service_grpc.pb.go index cc46df01..c2bbe64b 100644 --- a/gen/go/v1/service_grpc.pb.go +++ b/gen/go/v1/service_grpc.pb.go @@ -25,6 +25,7 @@ const ( ResticUI_SetConfig_FullMethodName = "/v1.ResticUI/SetConfig" ResticUI_AddRepo_FullMethodName = "/v1.ResticUI/AddRepo" ResticUI_GetEvents_FullMethodName = "/v1.ResticUI/GetEvents" + ResticUI_ListSnapshots_FullMethodName = "/v1.ResticUI/ListSnapshots" ResticUI_PathAutocomplete_FullMethodName = "/v1.ResticUI/PathAutocomplete" ) @@ -36,6 +37,7 @@ type ResticUIClient interface { SetConfig(ctx context.Context, in *Config, opts ...grpc.CallOption) (*Config, error) AddRepo(ctx context.Context, in *Repo, opts ...grpc.CallOption) (*Config, error) GetEvents(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (ResticUI_GetEventsClient, error) + ListSnapshots(ctx context.Context, in *ListSnapshotsRequest, opts ...grpc.CallOption) (*ResticSnapshotList, error) PathAutocomplete(ctx context.Context, in *types.StringValue, opts ...grpc.CallOption) (*types.StringList, error) } @@ -106,6 +108,15 @@ func (x *resticUIGetEventsClient) Recv() (*Event, error) { return m, nil } +func (c *resticUIClient) ListSnapshots(ctx context.Context, in *ListSnapshotsRequest, opts ...grpc.CallOption) (*ResticSnapshotList, error) { + out := new(ResticSnapshotList) + err := c.cc.Invoke(ctx, ResticUI_ListSnapshots_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *resticUIClient) PathAutocomplete(ctx context.Context, in *types.StringValue, opts ...grpc.CallOption) (*types.StringList, error) { out := new(types.StringList) err := c.cc.Invoke(ctx, ResticUI_PathAutocomplete_FullMethodName, in, out, opts...) @@ -123,6 +134,7 @@ type ResticUIServer interface { SetConfig(context.Context, *Config) (*Config, error) AddRepo(context.Context, *Repo) (*Config, error) GetEvents(*emptypb.Empty, ResticUI_GetEventsServer) error + ListSnapshots(context.Context, *ListSnapshotsRequest) (*ResticSnapshotList, error) PathAutocomplete(context.Context, *types.StringValue) (*types.StringList, error) mustEmbedUnimplementedResticUIServer() } @@ -143,6 +155,9 @@ func (UnimplementedResticUIServer) AddRepo(context.Context, *Repo) (*Config, err func (UnimplementedResticUIServer) GetEvents(*emptypb.Empty, ResticUI_GetEventsServer) error { return status.Errorf(codes.Unimplemented, "method GetEvents not implemented") } +func (UnimplementedResticUIServer) ListSnapshots(context.Context, *ListSnapshotsRequest) (*ResticSnapshotList, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListSnapshots not implemented") +} func (UnimplementedResticUIServer) PathAutocomplete(context.Context, *types.StringValue) (*types.StringList, error) { return nil, status.Errorf(codes.Unimplemented, "method PathAutocomplete not implemented") } @@ -234,6 +249,24 @@ func (x *resticUIGetEventsServer) Send(m *Event) error { return x.ServerStream.SendMsg(m) } +func _ResticUI_ListSnapshots_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListSnapshotsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResticUIServer).ListSnapshots(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResticUI_ListSnapshots_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResticUIServer).ListSnapshots(ctx, req.(*ListSnapshotsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _ResticUI_PathAutocomplete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(types.StringValue) if err := dec(in); err != nil { @@ -271,6 +304,10 @@ var ResticUI_ServiceDesc = grpc.ServiceDesc{ MethodName: "AddRepo", Handler: _ResticUI_AddRepo_Handler, }, + { + MethodName: "ListSnapshots", + Handler: _ResticUI_ListSnapshots_Handler, + }, { MethodName: "PathAutocomplete", Handler: _ResticUI_PathAutocomplete_Handler, diff --git a/internal/api/api.go b/internal/api/api.go index dca7f543..6e264015 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -9,18 +9,19 @@ import ( "path/filepath" v1 "github.com/garethgeorge/resticui/gen/go/v1" + "github.com/garethgeorge/resticui/internal/orchestrator" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) -func serveGRPC(ctx context.Context, socket string) error { +func serveGRPC(ctx context.Context, orchestrator *orchestrator.Orchestrator, socket string) error { lis, err := net.Listen("unix", socket) if err != nil { return fmt.Errorf("failed to listen: %w", err) } grpcServer := grpc.NewServer() - v1.RegisterResticUIServer(grpcServer, NewServer()) + v1.RegisterResticUIServer(grpcServer, NewServer(orchestrator)) go func() { <-ctx.Done() grpcServer.GracefulStop() @@ -32,7 +33,7 @@ func serveGRPC(ctx context.Context, socket string) error { return nil } -func serveHTTPHandlers(ctx context.Context, mux *runtime.ServeMux) error { +func serveHTTPHandlers(ctx context.Context, orchestrator *orchestrator.Orchestrator, mux *runtime.ServeMux) error { tmpDir, err := os.MkdirTemp("", "resticui") if err != nil { return fmt.Errorf("failed to create temp dir for unix domain socket: %w", err) @@ -49,7 +50,7 @@ func serveHTTPHandlers(ctx context.Context, mux *runtime.ServeMux) error { return fmt.Errorf("failed to register gateway: %w", err) } - if err := serveGRPC(ctx, socket); err != nil { + if err := serveGRPC(ctx, orchestrator, socket); err != nil { return err } @@ -57,8 +58,8 @@ func serveHTTPHandlers(ctx context.Context, mux *runtime.ServeMux) error { } // Handler returns an http.Handler serving the API, cancel the context to cleanly shut down the server. -func ServeAPI(ctx context.Context, mux *http.ServeMux) error { +func ServeAPI(ctx context.Context, orchestrator *orchestrator.Orchestrator, mux *http.ServeMux) error { apiMux := runtime.NewServeMux() mux.Handle("/api/", http.StripPrefix("/api", apiMux)) - return serveHTTPHandlers(ctx, apiMux) + return serveHTTPHandlers(ctx, orchestrator, apiMux) } \ No newline at end of file diff --git a/internal/api/server.go b/internal/api/server.go index 012bc6f9..f3876803 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -12,6 +12,7 @@ import ( "github.com/garethgeorge/resticui/gen/go/types" v1 "github.com/garethgeorge/resticui/gen/go/v1" "github.com/garethgeorge/resticui/internal/config" + "github.com/garethgeorge/resticui/internal/orchestrator" "github.com/garethgeorge/resticui/pkg/restic" "go.uber.org/zap" "google.golang.org/protobuf/types/known/emptypb" @@ -19,6 +20,7 @@ import ( type Server struct { *v1.UnimplementedResticUIServer + orchestrator *orchestrator.Orchestrator reqId atomic.Uint64 eventChannelsMu sync.Mutex @@ -27,12 +29,14 @@ type Server struct { var _ v1.ResticUIServer = &Server{} -func NewServer() *Server { +func NewServer(orchestrator *orchestrator.Orchestrator) *Server { s := &Server{ eventChannels: make(map[uint64]chan *v1.Event), + orchestrator: orchestrator, } go func() { + // TODO: disable this when proper event sources are implemented. for { time.Sleep(3 * time.Second) s.PublishEvent(&v1.Event{ @@ -55,8 +59,18 @@ func (s *Server) GetConfig(ctx context.Context, empty *emptypb.Empty) (*v1.Confi // SetConfig implements POST /v1/config func (s *Server) SetConfig(ctx context.Context, c *v1.Config) (*v1.Config, error) { - err := config.Default.Update(c) + existing, err := config.Default.Get() if err != nil { + return nil, fmt.Errorf("failed to check current config: %w", err) + } + + // Compare and increment modno + if existing.Modno != c.Modno { + return nil, errors.New("config modno mismatch, reload and try again") + } + c.Modno += 1 + + if err := config.Default.Update(c); err != nil { return nil, fmt.Errorf("failed to update config: %w", err) } return config.Default.Get() @@ -69,13 +83,18 @@ func (s *Server) AddRepo(ctx context.Context, repo *v1.Repo) (*v1.Config, error) return nil, fmt.Errorf("failed to get config: %w", err) } + c.Repos = append(c.Repos, repo) + + if err := config.ValidateConfig(c); err != nil { + return nil, fmt.Errorf("validation error: %w", err) + } + r := restic.NewRepo(repo) // use background context such that the init op can try to complete even if the connection is closed. if err := r.Init(context.Background()); err != nil { return nil, fmt.Errorf("failed to init repo: %w", err) } - c.Repos = append(c.Repos, repo) if err := config.Default.Update(c); err != nil { return nil, fmt.Errorf("failed to update config: %w", err) @@ -84,6 +103,40 @@ func (s *Server) AddRepo(ctx context.Context, repo *v1.Repo) (*v1.Config, error) return c, nil } +// ListSnapshots implements GET /v1/snapshots/{repo.id}/{plan.id?} +func (s *Server) ListSnapshots(ctx context.Context, query *v1.ListSnapshotsRequest) (*v1.ResticSnapshotList, error) { + repo, err := s.orchestrator.GetRepo(query.RepoId) + if err != nil { + return nil, fmt.Errorf("failed to get repo: %w", err) + } + + var snapshots []*restic.Snapshot + if query.PlanId != "" { + var plan *v1.Plan + plan, err = s.orchestrator.GetPlan(query.PlanId) + if err != nil { + return nil, fmt.Errorf("failed to get plan %q: %w", query.PlanId, err) + } + + snapshots, err = repo.SnapshotsForPlan(ctx, plan) + } else { + snapshots, err = repo.Snapshots(ctx) + } + + if err != nil { + return nil, fmt.Errorf("failed to list snapshots: %w", err) + } + + // Transform the snapshots and return them. + var rs []*v1.ResticSnapshot + for _, snapshot := range snapshots { + rs = append(rs, snapshot.ToProto()) + } + + return &v1.ResticSnapshotList{ + Snapshots: rs, + }, nil +} // GetEvents implements GET /v1/events func (s *Server) GetEvents(_ *emptypb.Empty, stream v1.ResticUI_GetEventsServer) error { diff --git a/internal/config/config.go b/internal/config/config.go index f987bbc6..0369aed7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,18 +1,23 @@ package config import ( + "errors" "flag" "fmt" "os" "path" + "sync" v1 "github.com/garethgeorge/resticui/gen/go/v1" ) +var ErrConfigNotFound = fmt.Errorf("config not found") var configDirFlag = flag.String("config_dir", "", "The directory to store the config file") -var Default ConfigStore = &YamlFileStore{ - Path: path.Join(configDir(*configDirFlag), "config.yaml"), +var Default ConfigStore = &CachingValidatingStore{ + ConfigStore: &YamlFileStore{ + Path: path.Join(configDir(*configDirFlag), "config.yaml"), + }, } type ConfigStore interface { @@ -42,4 +47,50 @@ func configDir(override string) string { } return fmt.Sprintf("%v/.config/resticui", home) +} + +type CachingValidatingStore struct { + ConfigStore + mu sync.Mutex + config *v1.Config +} + +func (c *CachingValidatingStore) Get() (*v1.Config, error) { + c.mu.Lock() + defer c.mu.Unlock() + + if c.config != nil { + return c.config, nil + } + + config, err := c.ConfigStore.Get() + if err != nil { + if errors.Is(err, ErrConfigNotFound) { + c.config = NewDefaultConfig() + } + return c.config, err + } + + if err := ValidateConfig(config); err != nil { + return nil, err + } + + c.config = config + return config, nil +} + +func (c *CachingValidatingStore) Update(config *v1.Config) error { + c.mu.Lock() + defer c.mu.Unlock() + + if err := ValidateConfig(config); err != nil { + return err + } + + if err := c.ConfigStore.Update(config); err != nil { + return err + } + + c.config = config + return nil } \ No newline at end of file diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 00000000..601aa5a0 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,114 @@ +package config + +import ( + "strings" + "testing" + + v1 "github.com/garethgeorge/resticui/gen/go/v1" + "google.golang.org/protobuf/proto" +) + + +func TestConfig(t *testing.T) { + dir := t.TempDir() + + testRepo := &v1.Repo{ + Id: "test-repo", + Uri: "/tmp/test", + Password: "test", + } + + testPlan := &v1.Plan{ + Id: "test-plan", + Repo: "test-repo", + Paths: []string{"/tmp/foo"}, + Cron: "* * * * *", + } + + tests := []struct { + name string + config *v1.Config + wantErr bool + wantErrContains string + store ConfigStore + }{ + { + name: "default config", + config: NewDefaultConfig(), + store: &CachingValidatingStore{ConfigStore: &YamlFileStore{Path: dir + "/default-config.yaml"}}, + }, + { + name: "simple valid config", + config: &v1.Config{ + Repos: []*v1.Repo{testRepo}, + Plans: []*v1.Plan{testPlan}, + }, + store: &CachingValidatingStore{ConfigStore: &YamlFileStore{Path: dir + "/valid-config.yaml"}}, + }, + { + name: "plan references non-existent repo", + config: &v1.Config{ + Plans: []*v1.Plan{testPlan}, + }, + store: &CachingValidatingStore{ConfigStore: &YamlFileStore{Path: dir + "/invalid-config.yaml"}}, + wantErr: true, + wantErrContains: "repo \"test-repo\" not found", + }, + { + name: "repo with duplicate id", + config: &v1.Config{ + Repos: []*v1.Repo{ + testRepo, + testRepo, + }, + }, + store: &CachingValidatingStore{ConfigStore: &YamlFileStore{Path: dir + "/invalid-config2.yaml"}}, + wantErr: true, + wantErrContains: "repo test-repo: duplicate id", + }, + { + name: "plan with bad cron", + config: &v1.Config{ + Repos: []*v1.Repo{ + testRepo, + }, + Plans: []*v1.Plan{ + { + Id: "test-plan", + Repo: "test-repo", + Paths: []string{"/tmp/foo"}, + Cron: "bad cron", + }, + }, + }, + store: &CachingValidatingStore{ConfigStore: &YamlFileStore{Path: dir + "/invalid-config3.yaml"}}, + wantErr: true, + wantErrContains: "invalid cron \"bad cron\"", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + err := tc.store.Update(tc.config) + if (err != nil) != tc.wantErr { + t.Errorf("Config.Update() error = %v, wantErr %v", err, tc.wantErr) + } + + if tc.wantErrContains != "" && (err == nil || !strings.Contains(err.Error(), tc.wantErrContains)) { + t.Errorf("Config.Update() error = %v, wantErrContains %v", err, tc.wantErrContains) + } + + if err == nil { + config, err := tc.store.Get() + if err != nil { + t.Errorf("Config.Get() error = %v, wantErr nil", err) + } + + if !proto.Equal(config, tc.config) { + t.Errorf("Config.Get() = %v, want %v", config, tc.config) + } + } + }) + } +} \ No newline at end of file diff --git a/internal/config/validate.go b/internal/config/validate.go index da8aab00..7253a20a 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/go-multierror" ) -func validateConfig(c *v1.Config) error { +func ValidateConfig(c *v1.Config) error { var err error repos := make(map[string]*v1.Repo) if c.Repos != nil { @@ -19,15 +19,17 @@ func validateConfig(c *v1.Config) error { if e := validateRepo(repo); e != nil { err = multierror.Append(e, fmt.Errorf("repo %s: %w", repo.GetId(), err)) } + if _, ok := repos[repo.GetId()]; ok { + err = multierror.Append(err, fmt.Errorf("repo %s: duplicate id", repo.GetId())) + } repos[repo.GetId()] = repo } } if c.Plans != nil { for _, plan := range c.Plans { - err := validatePlan(plan, repos); - if err != nil { - err = multierror.Append(err, fmt.Errorf("plan %s: %w", plan.GetId(), err)) + if e := validatePlan(plan, repos); e != nil { + err = multierror.Append(err, fmt.Errorf("plan %s: %w", plan.GetId(), e)) } } } @@ -73,7 +75,7 @@ func validatePlan(plan *v1.Plan, repos map[string]*v1.Repo) error { } - if _, e := cronexpr.Parse(plan.GetCron()); err != nil { + if _, e := cronexpr.Parse(plan.GetCron()); e != nil { err = multierror.Append(err, fmt.Errorf("invalid cron %q: %w", plan.GetCron(), e)) } diff --git a/internal/config/yamlstore.go b/internal/config/yamlstore.go index 830f36c9..df6f722f 100644 --- a/internal/config/yamlstore.go +++ b/internal/config/yamlstore.go @@ -17,7 +17,6 @@ import ( type YamlFileStore struct { Path string mu sync.Mutex - config *v1.Config } var _ ConfigStore = &YamlFileStore{} @@ -25,18 +24,10 @@ var _ ConfigStore = &YamlFileStore{} func (f *YamlFileStore) Get() (*v1.Config, error) { f.mu.Lock() - if f.config != nil { - f.mu.Unlock() - return f.config, nil - } - data, err := os.ReadFile(f.Path) if err != nil { if errors.Is(err, os.ErrNotExist) { - f.config = NewDefaultConfig() - f.mu.Unlock() - f.Update(f.config) - return f.config, nil + return nil, ErrConfigNotFound } return nil, fmt.Errorf("failed to read config file: %w", err) } @@ -54,19 +45,18 @@ func (f *YamlFileStore) Get() (*v1.Config, error) { return nil, fmt.Errorf("failed to unmarshal config: %w", err) } - if err := validateConfig(&config); err != nil { + if err := ValidateConfig(&config); err != nil { return nil, fmt.Errorf("invalid config: %w", err) } - f.config = &config - return f.config, nil + return &config, nil } func (f *YamlFileStore) Update(config *v1.Config) error { f.mu.Lock() defer f.mu.Unlock() - if err := validateConfig(config); err != nil { + if err := ValidateConfig(config); err != nil { return fmt.Errorf("invalid config: %w", err) } @@ -90,7 +80,6 @@ func (f *YamlFileStore) Update(config *v1.Config) error { return fmt.Errorf("failed to write config file: %w", err) } - f.config = config return nil } diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go new file mode 100644 index 00000000..8cc25fb4 --- /dev/null +++ b/internal/orchestrator/orchestrator.go @@ -0,0 +1,117 @@ +package orchestrator + +import ( + "errors" + "fmt" + "sync" + + v1 "github.com/garethgeorge/resticui/gen/go/v1" + "github.com/garethgeorge/resticui/internal/config" + "github.com/garethgeorge/resticui/pkg/restic" + "google.golang.org/protobuf/proto" +) + +var ErrRepoNotFound = errors.New("repo not found") +var ErrRepoInitializationFailed = errors.New("repo initialization failed") +var ErrPlanNotFound = errors.New("plan not found") + + + +// Orchestrator is responsible for managing repos and backups. +type Orchestrator struct { + configProvider config.ConfigStore + repoPool *resticRepoPool +} + +func NewOrchestrator(configProvider config.ConfigStore) *Orchestrator { + return &Orchestrator{ + configProvider: configProvider, + repoPool: newResticRepoPool(configProvider), + } +} + +func (o *Orchestrator) GetRepo(repoId string) (repo *RepoOrchestrator, err error) { + return o.repoPool.GetRepo(repoId) +} + +func (o *Orchestrator) GetPlan(planId string) (*v1.Plan, error) { + cfg, err := o.configProvider.Get() + if err != nil { + return nil, fmt.Errorf("failed to get config: %w", err) + } + + if cfg.Plans == nil { + return nil, ErrPlanNotFound + } + + for _, p := range cfg.Plans { + if p.Id == planId { + return p, nil + } + } + + return nil, ErrPlanNotFound +} + +// resticRepoPool caches restic repos. +type resticRepoPool struct { + mu sync.Mutex + repos map[string]*RepoOrchestrator + configProvider config.ConfigStore +} + + +func newResticRepoPool(configProvider config.ConfigStore) *resticRepoPool { + return &resticRepoPool{ + repos: make(map[string]*RepoOrchestrator), + configProvider: configProvider, + } +} + +func (rp *resticRepoPool) GetRepo(repoId string) (repo *RepoOrchestrator, err error) { + cfg, err := rp.configProvider.Get() + if err != nil { + return nil, fmt.Errorf("failed to get config: %w", err) + } + + rp.mu.Lock() + defer rp.mu.Unlock() + + if cfg.Repos == nil { + return nil, ErrRepoNotFound + } + + var repoProto *v1.Repo + for _, r := range cfg.Repos { + if r.GetId() == repoId { + repoProto = r + } + } + + if repoProto == nil { + return nil, ErrRepoNotFound + } + + // Check if we already have a repo for this id, if we do return it. + repo, ok := rp.repos[repoId] + if ok && proto.Equal(repo.repoConfig, repoProto) { + return repo, nil + } + delete(rp.repos, repoId); + + var opts []restic.GenericOption + if len(repoProto.GetEnv()) > 0 { + opts = append(opts, restic.WithEnv(repoProto.GetEnv()...)) + } + if len(repoProto.GetFlags()) > 0 { + opts = append(opts, restic.WithFlags(repoProto.GetFlags()...)) + } + + // Otherwise create a new repo. + repo = &RepoOrchestrator{ + repoConfig: repoProto, + repo: restic.NewRepo(repoProto, opts...), + } + rp.repos[repoId] = repo + return repo, nil +} diff --git a/internal/orchestrator/repo.go b/internal/orchestrator/repo.go new file mode 100644 index 00000000..595d33cf --- /dev/null +++ b/internal/orchestrator/repo.go @@ -0,0 +1,108 @@ +package orchestrator + +import ( + "context" + "fmt" + "slices" + "sort" + "sync" + "time" + + v1 "github.com/garethgeorge/resticui/gen/go/v1" + "github.com/garethgeorge/resticui/pkg/restic" +) + +// RepoOrchestrator is responsible for managing a single repo. +type RepoOrchestrator struct { + mu sync.Mutex + + repoConfig *v1.Repo + repo *restic.Repo + + snapshotsAge time.Time + snapshots []*restic.Snapshot +} + +func (r *RepoOrchestrator) updateSnapshotsIfNeeded(ctx context.Context) error { + if time.Since(r.snapshotsAge) > 10 * time.Minute { + r.snapshots = nil + } + + if r.snapshots != nil { + return nil + } + + snapshots, err := r.repo.Snapshots(ctx, restic.WithPropagatedEnvVars(restic.EnvToPropagate...)) + if err != nil { + return fmt.Errorf("failed to update snapshots: %w", err) + } + + sort.SliceStable(snapshots, func(i, j int) bool { + return snapshots[i].Time > snapshots[j].Time + }) + + r.snapshots = snapshots + + return nil +} + +func (r *RepoOrchestrator) Snapshots(ctx context.Context) ([]*restic.Snapshot, error) { + r.mu.Lock() + defer r.mu.Unlock() + + if err := r.updateSnapshotsIfNeeded(ctx); err != nil { + return nil, err + } + + return r.snapshots, nil +} + +func (r *RepoOrchestrator) SnapshotsForPlan(ctx context.Context, plan *v1.Plan) ([]*restic.Snapshot, error) { + r.mu.Lock() + defer r.mu.Unlock() + + if err := r.updateSnapshotsIfNeeded(ctx); err != nil { + return nil, err + } + + return filterSnapshotsForPlan(r.snapshots, plan), nil +} + +func (r *RepoOrchestrator) Backup(ctx context.Context, plan *v1.Plan, progressCallback func(event *restic.BackupProgressEntry)) error { + snapshots, err := r.SnapshotsForPlan(ctx, plan) + if err != nil { + return err + } + + var opts []restic.BackupOption + opts = append(opts, restic.WithBackupPaths(plan.Paths...)) + opts = append(opts, restic.WithBackupExcludes(plan.Excludes...)) + opts = append(opts, restic.WithBackupTags(tagForPlan(plan))) + + if len(snapshots) > 0 { + // TODO: design a test strategy to verify that the backup parent is used correctly. + opts = append(opts, restic.WithBackupParent(snapshots[len(snapshots) - 1].Id)) + } + + panic("not yet implemented") +} + +func filterSnapshotsForPlan(snapshots []*restic.Snapshot, plan *v1.Plan) []*restic.Snapshot { + wantTag := tagForPlan(plan) + var filtered []*restic.Snapshot + for _, snapshot := range snapshots { + if snapshot.Tags == nil { + continue + } + + if slices.Contains(snapshot.Tags, wantTag) { + filtered = append(filtered, snapshot) + } + } + + return filtered +} + +func tagForPlan(plan *v1.Plan) string { + return fmt.Sprintf("plan:%s", plan.Id) +} diff --git a/pkg/restic/outputs.go b/pkg/restic/outputs.go index 044a6bc9..efe4ee5b 100644 --- a/pkg/restic/outputs.go +++ b/pkg/restic/outputs.go @@ -7,6 +7,9 @@ import ( "io" "os/exec" "slices" + "time" + + v1 "github.com/garethgeorge/resticui/gen/go/v1" ) type LsEntry struct { @@ -33,6 +36,23 @@ type Snapshot struct { Parent string `json:"parent"` } +func (s *Snapshot) ToProto() *v1.ResticSnapshot { + t, err := time.Parse(time.RFC3339Nano, s.Time) + if err != nil { + t = time.Unix(0, 0) + } + return &v1.ResticSnapshot{ + Id: s.Id, + UnixTimeMs: t.UnixMilli(), + Tree: s.Tree, + Paths: s.Paths, + Hostname: s.Hostname, + Username: s.Username, + Tags: s.Tags, + Parent: s.Parent, + } +} + type BackupProgressEntry struct { // Common fields MessageType string `json:"message_type"` // "summary" or "status" diff --git a/pkg/restic/outputs_test.go b/pkg/restic/outputs_test.go index da27506f..63aafb4a 100644 --- a/pkg/restic/outputs_test.go +++ b/pkg/restic/outputs_test.go @@ -27,6 +27,7 @@ func TestReadBackupProgressEntries(t *testing.T) { } } + func TestReadLs(t *testing.T) { testInput := `{"time":"2023-11-10T19:14:17.053824063-08:00","tree":"3e2918b261948e69602ee9504b8f475bcc7cdc4dcec0b3f34ecdb014287d07b2","paths":["/resticui"],"hostname":"pop-os","username":"dontpanic","uid":1000,"gid":1000,"id":"db155169d788e6e432e320aedbdff5a54cc439653093bb56944a67682528aa52","short_id":"db155169","struct_type":"snapshot"} {"name":".git","type":"dir","path":"/.git","uid":1000,"gid":1000,"mode":2147484157,"mtime":"2023-11-10T18:32:38.156599473-08:00","atime":"2023-11-10T18:32:38.156599473-08:00","ctime":"2023-11-10T18:32:38.156599473-08:00","struct_type":"node"} @@ -45,4 +46,41 @@ func TestReadLs(t *testing.T) { if len(entries) != 3 { t.Errorf("wanted 3 entries, got: %d", len(entries)) } +} + +func TestSnapshotToProto(t *testing.T) { + snapshot := &Snapshot{ + Id: "db155169d788e6e432e320aedbdff5a54cc439653093bb56944a67682528aa52", + Time: "2023-11-10T19:14:17.053824063-08:00", + Tree: "3e2918b261948e69602ee9504b8f475bcc7cdc4dcec0b3f34ecdb014287d07b2", + Paths: []string{"/resticui"}, + Hostname: "pop-os", + Username: "dontpanic", + Tags: []string{}, + Parent: "", + } + + proto := snapshot.ToProto() + + if proto.Id != snapshot.Id { + t.Errorf("wanted id %q, got: %q", snapshot.Id, proto.Id) + } + if proto.Tree != snapshot.Tree { + t.Errorf("wanted tree %q, got: %q", snapshot.Tree, proto.Tree) + } + if proto.Hostname != snapshot.Hostname { + t.Errorf("wanted hostname %q, got: %q", snapshot.Hostname, proto.Hostname) + } + if proto.Username != snapshot.Username { + t.Errorf("wanted username %q, got: %q", snapshot.Username, proto.Username) + } + if len(proto.Tags) != len(snapshot.Tags) { + t.Errorf("wanted %d tags, got: %d", len(snapshot.Tags), len(proto.Tags)) + } + if proto.Parent != snapshot.Parent { + t.Errorf("wanted parent %q, got: %q", snapshot.Parent, proto.Parent) + } + if proto.UnixTimeMs != 1699672457053 { + t.Errorf("wanted unix time %d, got: %d", 1699672457053, proto.UnixTimeMs) + } } \ No newline at end of file diff --git a/pkg/restic/restic.go b/pkg/restic/restic.go index 64722617..efb3bf00 100644 --- a/pkg/restic/restic.go +++ b/pkg/restic/restic.go @@ -9,6 +9,7 @@ import ( "io" "os" "os/exec" + "strings" "sync" v1 "github.com/garethgeorge/resticui/gen/go/v1" @@ -25,6 +26,7 @@ type Repo struct { extraEnv []string } +// NewRepo instantiates a new repository. TODO: should not accept a v1.Repo func NewRepo(repo *v1.Repo, opts ...GenericOption) *Repo { opt := &GenericOpts{} for _, o := range opts { @@ -63,7 +65,9 @@ func (r *Repo) init(ctx context.Context) error { cmd.Env = append(cmd.Env, r.buildEnv()...) if output, err := cmd.CombinedOutput(); err != nil { - return NewCmdError(cmd, output, err) + if !strings.Contains(string(output), "config file already exists") { + return NewCmdError(cmd, output, err) + } } r.initialized = true @@ -242,6 +246,12 @@ func WithBackupTags(tags ...string) BackupOption { } } +func WithBackupParent(parent string) BackupOption { + return func(opts *BackupOpts) { + opts.extraArgs = append(opts.extraArgs, "--parent", parent) + } +} + type GenericOpts struct { extraArgs []string extraEnv []string diff --git a/pkg/restic/restic_test.go b/pkg/restic/restic_test.go index f8541727..3a9df435 100644 --- a/pkg/restic/restic_test.go +++ b/pkg/restic/restic_test.go @@ -140,6 +140,13 @@ func TestSnapshot(t *testing.T) { if len(snapshots) != tc.count { t.Errorf("wanted %d snapshots, got: %d", tc.count, len(snapshots)) } + + // Ensure that snapshot timestamps are set, this is critical for correct ordering in the orchestrator. + for _, snapshot := range snapshots { + if p := snapshot.ToProto(); p.UnixTimeMs == 0 { + t.Errorf("wanted snapshot time to be non-zero, got: %v", p.UnixTimeMs) + } + } }) } } diff --git a/proto/v1/config.proto b/proto/v1/config.proto index 6ae089ee..398e3462 100644 --- a/proto/v1/config.proto +++ b/proto/v1/config.proto @@ -5,14 +5,15 @@ package v1; option go_package = "github.com/garethgeorge/resticui/go/proto/v1"; message Config { - int32 version = 1; + // modification number, used for read-modify-write consistency in the UI. Incremented on every write. + int32 modno = 1 [json_name="modno"]; + + // override the hostname tagged on backups. If provided it will be used in addition to tags to group backups. + string host_override = 2 [json_name="host_override"]; + repeated Repo repos = 3 [json_name="repos"]; - repeated Plan plans = 4 [json_name="plans"]; -} -message User { - string name = 1; - string password = 2; // plaintext password + repeated Plan plans = 4 [json_name="plans"]; } message Repo { diff --git a/proto/v1/restic.proto b/proto/v1/restic.proto index b06f252b..c3aa83e6 100644 --- a/proto/v1/restic.proto +++ b/proto/v1/restic.proto @@ -15,6 +15,10 @@ message ResticSnapshot { repeated string tags = 8 [json_name = "tags"]; } +message ResticSnapshotList { + repeated ResticSnapshot snapshots = 1 [json_name = "snapshots"]; +} + message BackupProgressEntry { oneof entry { BackupProgressStatusEntry status = 1 [json_name = "status"]; diff --git a/proto/v1/service.proto b/proto/v1/service.proto index baff26a3..d29c0732 100644 --- a/proto/v1/service.proto +++ b/proto/v1/service.proto @@ -6,6 +6,7 @@ option go_package = "github.com/garethgeorge/resticui/go/proto/v1"; import "v1/config.proto"; import "v1/events.proto"; +import "v1/restic.proto"; import "types/value.proto"; import "google/protobuf/empty.proto"; import "google/api/annotations.proto"; @@ -37,6 +38,12 @@ service ResticUI { }; } + rpc ListSnapshots(ListSnapshotsRequest) returns (ResticSnapshotList) { + option (google.api.http) = { + get: "/v1/snapshots/{repo_id}/{plan_id}" + }; + } + rpc PathAutocomplete (types.StringValue) returns (types.StringList) { option (google.api.http) = { post: "/v1/autocomplete/path" @@ -44,3 +51,8 @@ service ResticUI { }; } } + +message ListSnapshotsRequest { + string repo_id = 1 [json_name = "repo_id"]; + string plan_id = 2 [json_name = "plan_id"]; +} diff --git a/webui/gen/ts/v1/config.pb.ts b/webui/gen/ts/v1/config.pb.ts index 3144820b..0a032111 100644 --- a/webui/gen/ts/v1/config.pb.ts +++ b/webui/gen/ts/v1/config.pb.ts @@ -4,16 +4,12 @@ * This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY */ export type Config = { - version?: number + modno?: number + hostOverride?: string repos?: Repo[] plans?: Plan[] } -export type User = { - name?: string - password?: string -} - export type Repo = { id?: string uri?: string diff --git a/webui/gen/ts/v1/restic.pb.ts b/webui/gen/ts/v1/restic.pb.ts index 2368423e..f0c5966a 100644 --- a/webui/gen/ts/v1/restic.pb.ts +++ b/webui/gen/ts/v1/restic.pb.ts @@ -23,6 +23,10 @@ export type ResticSnapshot = { tags?: string[] } +export type ResticSnapshotList = { + snapshots?: ResticSnapshot[] +} + type BaseBackupProgressEntry = { } diff --git a/webui/gen/ts/v1/service.pb.ts b/webui/gen/ts/v1/service.pb.ts index 8cd208b6..36010f5e 100644 --- a/webui/gen/ts/v1/service.pb.ts +++ b/webui/gen/ts/v1/service.pb.ts @@ -9,6 +9,12 @@ import * as GoogleProtobufEmpty from "../google/protobuf/empty.pb" import * as TypesValue from "../types/value.pb" import * as V1Config from "./config.pb" import * as V1Events from "./events.pb" +import * as V1Restic from "./restic.pb" +export type ListSnapshotsRequest = { + repoId?: string + planId?: string +} + export class ResticUI { static GetConfig(req: GoogleProtobufEmpty.Empty, initReq?: fm.InitReq): Promise { return fm.fetchReq(`/v1/config?${fm.renderURLSearchParams(req, [])}`, {...initReq, method: "GET"}) @@ -22,6 +28,9 @@ export class ResticUI { static GetEvents(req: GoogleProtobufEmpty.Empty, entityNotifier?: fm.NotifyStreamEntityArrival, initReq?: fm.InitReq): Promise { return fm.fetchStreamingRequest(`/v1/events?${fm.renderURLSearchParams(req, [])}`, entityNotifier, {...initReq, method: "GET"}) } + static ListSnapshots(req: ListSnapshotsRequest, initReq?: fm.InitReq): Promise { + return fm.fetchReq(`/v1/snapshots/${req["repoId"]}/${req["planId"]}?${fm.renderURLSearchParams(req, ["repoId", "planId"])}`, {...initReq, method: "GET"}) + } static PathAutocomplete(req: TypesValue.StringValue, initReq?: fm.InitReq): Promise { return fm.fetchReq(`/v1/autocomplete/path`, {...initReq, method: "POST", body: JSON.stringify(req, fm.replacer)}) } diff --git a/webui/src/components/URIAutocomplete.tsx b/webui/src/components/URIAutocomplete.tsx index c2d0cbad..92783eb5 100644 --- a/webui/src/components/URIAutocomplete.tsx +++ b/webui/src/components/URIAutocomplete.tsx @@ -5,7 +5,9 @@ import { StringList } from "../../gen/ts/types/value.pb"; let timeout: NodeJS.Timeout | undefined = undefined; -export const URIAutocomplete = (props: React.PropsWithChildren) => { +export const URIAutocomplete = ( + props: React.PropsWithChildren<{ disabled: boolean }> +) => { const [value, setValue] = useState(""); const [options, setOptions] = useState<{ value: string }[]>([]); const [showOptions, setShowOptions] = useState<{ value: string }[]>([]); diff --git a/webui/src/state/config.ts b/webui/src/state/config.ts index a2baee46..c9519398 100644 --- a/webui/src/state/config.ts +++ b/webui/src/state/config.ts @@ -17,7 +17,7 @@ export const addRepo = async (repo: Repo): Promise => { }); }; -export const setConfig = async (config: Config): Promise => { +export const updateConfig = async (config: Config): Promise => { return await ResticUI.SetConfig(config, { pathPrefix: "/api/", }); diff --git a/webui/src/views/AddRepoModel.tsx b/webui/src/views/AddRepoModel.tsx index 553a0177..d93f7dc7 100644 --- a/webui/src/views/AddRepoModel.tsx +++ b/webui/src/views/AddRepoModel.tsx @@ -14,20 +14,26 @@ import { URIAutocomplete } from "../components/URIAutocomplete"; import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"; import { useAlertApi } from "../components/Alerts"; import { ResticUI } from "../../gen/ts/v1/service.pb"; +import { + addRepo, + configState, + fetchConfig, + updateConfig, +} from "../state/config"; +import { useRecoilState } from "recoil"; export const AddRepoModel = ({ template, }: { template: Partial | null; }) => { + const [config, setConfig] = useRecoilState(configState); const [confirmLoading, setConfirmLoading] = useState(false); const showModal = useShowModal(); const alertsApi = useAlertApi()!; const [form] = Form.useForm(); - template = template || {}; - - const handleOk = () => { + const handleOk = async () => { const errors = form .getFieldsError() .map((e) => e.errors) @@ -40,22 +46,45 @@ export const AddRepoModel = ({ const repo = form.getFieldsValue() as Repo; - if (template === null) { - // We are in the create repo flow, create the new repo via the service - ResticUI.AddRepo(repo, { - pathPrefix: "/api", - }) - .then((res) => { - showModal(null); - alertsApi.success("Added repo " + repo.uri); - }) - .catch((e) => { - alertsApi.error("Error adding repo: " + e.message, 15); - }) - .finally(() => { - setConfirmLoading(false); - }); + if (template !== null) { + // We are in the edit repo flow, update the repo in the config + try { + let config = await fetchConfig(); + const idx = config.repos!.findIndex((r) => r.id === template!.id); + if (idx === -1) { + alertsApi.error("Can't update repo, not found"); + return; + } + config.repos![idx] = { ...repo }; + await updateConfig(config); + showModal(null); + setConfig(config); + alertsApi.success("Updated repo " + repo.uri); + + // Update the snapshots for the repo + await ResticUI.ListSnapshots( + { + repoId: repo.id, + planId: "", + }, + { pathPrefix: "/api" } + ); + } catch (e: any) { + alertsApi.error("Error updating repo: " + e.message, 15); + } finally { + setConfirmLoading(false); + } } else { + // We are in the create repo flow, create the new repo via the service + try { + addRepo(repo); + showModal(null); + alertsApi.success("Added repo " + repo.uri); + } catch (e: any) { + alertsApi.error("Error adding repo: " + e.message, 15); + } finally { + setConfirmLoading(false); + } } }; @@ -67,7 +96,7 @@ export const AddRepoModel = ({ <> - + {/* Repo.uri */} @@ -118,8 +147,8 @@ export const AddRepoModel = ({ hasFeedback name="uri" - label="Repo URI" - initialValue={template.id} + label="Repository URI (e.g. ./local-path or s3://bucket-name/path)" + initialValue={template && template.uri} validateTrigger={["onChange", "onBlur"]} rules={[ { @@ -128,7 +157,7 @@ export const AddRepoModel = ({ }, ]} > - + @@ -137,7 +166,7 @@ export const AddRepoModel = ({ hasFeedback name="password" label="Password" - initialValue={template.password} + initialValue={template && template.password} validateTrigger={["onChange", "onBlur"]} rules={[ { @@ -150,7 +179,7 @@ export const AddRepoModel = ({ }, ]} > - + {/* Repo.env */} @@ -164,7 +193,7 @@ export const AddRepoModel = ({ }, }, ]} - initialValue={[]} + initialValue={(template && template.env) || undefined} > {(fields, { add, remove }, { errors }) => ( <> diff --git a/webui/src/views/App.tsx b/webui/src/views/App.tsx index 377e8254..123235b3 100644 --- a/webui/src/views/App.tsx +++ b/webui/src/views/App.tsx @@ -11,7 +11,7 @@ import { configState, fetchConfig } from "../state/config"; import { useRecoilState } from "recoil"; import { Config } from "../../gen/ts/v1/config.pb"; import { AlertContextProvider, useAlertApi } from "../components/Alerts"; -import { useShowModal, useShowSpinner } from "../components/ModalManager"; +import { useShowModal } from "../components/ModalManager"; import { AddPlanModal } from "./AddPlanModel"; import { AddRepoModel } from "./AddRepoModel"; diff --git a/static/static.go b/webui/static.go similarity index 54% rename from static/static.go rename to webui/static.go index 9845722e..3ddcab10 100644 --- a/static/static.go +++ b/webui/static.go @@ -2,8 +2,7 @@ package static import ( "embed" - _ "embed" ) -//go:embed * +//go:embed dist/*.js dist/*.css dist/*.html var FS embed.FS \ No newline at end of file