diff --git a/.gitignore b/.gitignore index a8443a8..74a12b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +test* backrest-* dist __debug_bin diff --git a/cmd/backrest/backrest.go b/cmd/backrest/backrest.go index 2610cea..c7f83dd 100644 --- a/cmd/backrest/backrest.go +++ b/cmd/backrest/backrest.go @@ -18,6 +18,7 @@ import ( v1 "github.com/garethgeorge/backrest/gen/go/v1" "github.com/garethgeorge/backrest/gen/go/v1/v1connect" "github.com/garethgeorge/backrest/internal/api" + syncapi "github.com/garethgeorge/backrest/internal/api/syncapi" "github.com/garethgeorge/backrest/internal/auth" "github.com/garethgeorge/backrest/internal/config" "github.com/garethgeorge/backrest/internal/env" @@ -67,6 +68,7 @@ func main() { if err != nil { zap.S().Fatalf("error loading config: %v", err) } + configMgr := &config.ConfigManager{Store: configStore} var wg sync.WaitGroup @@ -86,6 +88,7 @@ func main() { zap.S().Fatalf("error creating oplog: %v", err) } migrateBboltOplog(opstore) + migratePopulateGuids(opstore, cfg) // Create rotating log storage logStore, err := logstore.NewLogStore(filepath.Join(env.DataDir(), "tasklogs")) @@ -112,6 +115,7 @@ func main() { }() // Create orchestrator and start task loop. + // TODO: update the orchestrator to accept a configMgr and auto-refresh the config w/o explicit ApplyConfig call. orchestrator, err := orchestrator.NewOrchestrator(resticPath, cfg, log, logStore) if err != nil { zap.S().Fatalf("error creating orchestrator: %v", err) @@ -124,18 +128,29 @@ func main() { }() // Create and serve the HTTP gateway + remoteConfigStore := syncapi.NewJSONDirRemoteConfigStore(filepath.Join(env.DataDir(), "sync", "remote_configs")) + syncMgr := syncapi.NewSyncManager(configMgr, remoteConfigStore, log, orchestrator) + wg.Add(1) + go func() { + syncMgr.RunSync(ctx) + wg.Done() + }() + + syncHandler := syncapi.NewBackrestSyncHandler(syncMgr) + apiBackrestHandler := api.NewBackrestHandler( - configStore, + configMgr, + remoteConfigStore, orchestrator, log, logStore, ) - - authenticator := auth.NewAuthenticator(getSecret(), configStore) + authenticator := auth.NewAuthenticator(getSecret(), configMgr) apiAuthenticationHandler := api.NewAuthenticationHandler(authenticator) mux := http.NewServeMux() mux.Handle(v1connect.NewAuthenticationHandler(apiAuthenticationHandler)) + mux.Handle(v1connect.NewBackrestSyncServiceHandler(syncHandler)) backrestHandlerPath, backrestHandler := v1connect.NewBackrestHandler(apiBackrestHandler) mux.Handle(backrestHandlerPath, auth.RequireAuthentication(backrestHandler, authenticator)) mux.Handle("/", webui.Handler()) @@ -217,10 +232,15 @@ func installLoggers() { c := zap.NewDevelopmentEncoderConfig() c.EncodeLevel = zapcore.CapitalColorLevelEncoder c.EncodeTime = zapcore.ISO8601TimeEncoder + + debugLevel := zapcore.InfoLevel + if version == "unknown" { // dev build + debugLevel = zapcore.DebugLevel + } pretty := zapcore.NewCore( zapcore.NewConsoleEncoder(c), zapcore.AddSync(colorable.NewColorableStdout()), - zapcore.InfoLevel, + debugLevel, ) // JSON logging to log directory @@ -294,3 +314,31 @@ func migrateBboltOplog(logstore oplog.OpStore) { } zap.S().Infof("migrated %d operations from old bbolt oplog to sqlite", count) } + +func migratePopulateGuids(logstore oplog.OpStore, cfg *v1.Config) { + zap.S().Info("migrating oplog to populate GUIDs") + + repoToGUID := make(map[string]string) + for _, repo := range cfg.Repos { + if repo.Guid != "" { + repoToGUID[repo.Id] = repo.Guid + } + } + + migratedOpCount := 0 + if err := logstore.Transform(oplog.Query{}.SetRepoGUID(""), func(op *v1.Operation) (*v1.Operation, error) { + if op.RepoGuid != "" { + return nil, nil + } + if guid, ok := repoToGUID[op.RepoId]; ok { + op.RepoGuid = guid + migratedOpCount++ + return op, nil + } + return nil, nil + }); err != nil { + zap.S().Fatalf("error populating repo GUIDs for existing operations: %v", err) + } else if migratedOpCount > 0 { + zap.S().Infof("populated repo GUIDs for %d existing operations", migratedOpCount) + } +} diff --git a/cmd/devtools/oplogexport/main.go b/cmd/devtools/oplogexport/main.go deleted file mode 100644 index e17de76..0000000 --- a/cmd/devtools/oplogexport/main.go +++ /dev/null @@ -1,77 +0,0 @@ -package main - -import ( - "bytes" - "compress/gzip" - "flag" - "log" - "os" - "path" - - v1 "github.com/garethgeorge/backrest/gen/go/v1" - "github.com/garethgeorge/backrest/internal/env" - "github.com/garethgeorge/backrest/internal/oplog" - "github.com/garethgeorge/backrest/internal/oplog/bboltstore" - "google.golang.org/protobuf/encoding/prototext" -) - -var ( - outpath = flag.String("export-oplog-path", "", "path to export the oplog as a compressed textproto e.g. .textproto.gz") -) - -func main() { - flag.Parse() - - if *outpath == "" { - flag.Usage() - return - } - - oplogFile := path.Join(env.DataDir(), "oplog.boltdb") - opstore, err := bboltstore.NewBboltStore(oplogFile) - if err != nil { - log.Fatalf("error creating oplog : %v", err) - } - defer opstore.Close() - - output := &v1.OperationList{} - - l, err := oplog.NewOpLog(opstore) - if err != nil { - log.Fatalf("error creating oplog: %v", err) - } - l.Query(oplog.Query{}, func(op *v1.Operation) error { - output.Operations = append(output.Operations, op) - return nil - }) - log.Printf("exporting %d operations", len(output.Operations)) - - bytes, err := prototext.MarshalOptions{Multiline: true}.Marshal(output) - if err != nil { - log.Fatalf("error marshalling operations: %v", err) - } - - bytes, err = compress(bytes) - if err != nil { - log.Fatalf("error compressing operations: %v", err) - } - - if err := os.WriteFile(*outpath, bytes, 0644); err != nil { - log.Fatalf("error writing to file: %v", err) - } -} - -func compress(data []byte) ([]byte, error) { - var buf bytes.Buffer - zw := gzip.NewWriter(&buf) - - if _, err := zw.Write(data); err != nil { - return nil, err - } - - if err := zw.Close(); err != nil { - return nil, err - } - - return buf.Bytes(), nil -} diff --git a/cmd/devtools/oplogimport/main.go b/cmd/devtools/oplogimport/main.go deleted file mode 100644 index 0c7b67f..0000000 --- a/cmd/devtools/oplogimport/main.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "bytes" - "compress/gzip" - "flag" - "log" - "os" - "path" - - v1 "github.com/garethgeorge/backrest/gen/go/v1" - "github.com/garethgeorge/backrest/internal/env" - "github.com/garethgeorge/backrest/internal/oplog/bboltstore" - "go.uber.org/zap" - "google.golang.org/protobuf/encoding/prototext" -) - -var ( - outpath = flag.String("import-oplog-path", "", "path to import the oplog from compressed textproto e.g. .textproto.gz") -) - -func main() { - flag.Parse() - - if *outpath == "" { - flag.Usage() - return - } - - // create a reader from the file - f, err := os.Open(*outpath) - if err != nil { - log.Fatalf("error opening file: %v", err) - } - defer f.Close() - - cr, err := gzip.NewReader(f) - if err != nil { - log.Fatalf("error creating gzip reader: %v", err) - } - defer cr.Close() - - // read into a buffer - var buf bytes.Buffer - if _, err := buf.ReadFrom(cr); err != nil { - log.Printf("error reading from gzip reader: %v", err) - } - - log.Printf("importing operations from %q", *outpath) - - output := &v1.OperationList{} - if err := prototext.Unmarshal(buf.Bytes(), output); err != nil { - log.Fatalf("error unmarshalling operations: %v", err) - } - - zap.S().Infof("importing %d operations", len(output.Operations)) - - oplogFile := path.Join(env.DataDir(), "oplog.boltdb") - opstore, err := bboltstore.NewBboltStore(oplogFile) - if err != nil { - log.Fatalf("error creating oplog : %v", err) - } - defer opstore.Close() - - for _, op := range output.Operations { - if err := opstore.Add(op); err != nil { - log.Printf("error adding operation to oplog: %v", err) - } - } -} diff --git a/cmd/devtools/oplogimport/testdata/v1.4.0-config.json b/cmd/devtools/oplogimport/testdata/v1.4.0-config.json deleted file mode 100644 index e69de29..0000000 diff --git a/cmd/devtools/oplogimport/testdata/v1.4.0-ops.v04log.textproto.gz b/cmd/devtools/oplogimport/testdata/v1.4.0-ops.v04log.textproto.gz deleted file mode 100644 index ec77d31..0000000 Binary files a/cmd/devtools/oplogimport/testdata/v1.4.0-ops.v04log.textproto.gz and /dev/null differ diff --git a/gen/go/v1/config.pb.go b/gen/go/v1/config.pb.go index 4cbe3ab..f1c9cac 100644 --- a/gen/go/v1/config.pb.go +++ b/gen/go/v1/config.pb.go @@ -70,7 +70,7 @@ func (x CommandPrefix_IONiceLevel) Number() protoreflect.EnumNumber { // Deprecated: Use CommandPrefix_IONiceLevel.Descriptor instead. func (CommandPrefix_IONiceLevel) EnumDescriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{4, 0} + return file_v1_config_proto_rawDescGZIP(), []int{5, 0} } type CommandPrefix_CPUNiceLevel int32 @@ -119,7 +119,7 @@ func (x CommandPrefix_CPUNiceLevel) Number() protoreflect.EnumNumber { // Deprecated: Use CommandPrefix_CPUNiceLevel.Descriptor instead. func (CommandPrefix_CPUNiceLevel) EnumDescriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{4, 1} + return file_v1_config_proto_rawDescGZIP(), []int{5, 1} } type Schedule_Clock int32 @@ -171,7 +171,7 @@ func (x Schedule_Clock) Number() protoreflect.EnumNumber { // Deprecated: Use Schedule_Clock.Descriptor instead. func (Schedule_Clock) EnumDescriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{8, 0} + return file_v1_config_proto_rawDescGZIP(), []int{9, 0} } type Hook_Condition int32 @@ -255,7 +255,7 @@ func (x Hook_Condition) Number() protoreflect.EnumNumber { // Deprecated: Use Hook_Condition.Descriptor instead. func (Hook_Condition) EnumDescriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{9, 0} + return file_v1_config_proto_rawDescGZIP(), []int{10, 0} } type Hook_OnError int32 @@ -313,7 +313,7 @@ func (x Hook_OnError) Number() protoreflect.EnumNumber { // Deprecated: Use Hook_OnError.Descriptor instead. func (Hook_OnError) EnumDescriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{9, 1} + return file_v1_config_proto_rawDescGZIP(), []int{10, 1} } type Hook_Webhook_Method int32 @@ -362,7 +362,7 @@ func (x Hook_Webhook_Method) Number() protoreflect.EnumNumber { // Deprecated: Use Hook_Webhook_Method.Descriptor instead. func (Hook_Webhook_Method) EnumDescriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{9, 1, 0} + return file_v1_config_proto_rawDescGZIP(), []int{10, 1, 0} } type HubConfig struct { @@ -421,10 +421,11 @@ type Config struct { Version int32 `protobuf:"varint,6,opt,name=version,proto3" json:"version,omitempty"` // version of the config file format. Used to determine when to run migrations. // The instance name for the Backrest installation. // This identifies backups created by this instance and is displayed in the UI. - Instance string `protobuf:"bytes,2,opt,name=instance,proto3" json:"instance,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"` - Auth *Auth `protobuf:"bytes,5,opt,name=auth,proto3" json:"auth,omitempty"` + Instance string `protobuf:"bytes,2,opt,name=instance,proto3" json:"instance,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"` + Auth *Auth `protobuf:"bytes,5,opt,name=auth,proto3" json:"auth,omitempty"` + Multihost *Multihost `protobuf:"bytes,7,opt,name=multihost,json=sync,proto3" json:"multihost,omitempty"` } func (x *Config) Reset() { @@ -499,26 +500,88 @@ func (x *Config) GetAuth() *Auth { return nil } +func (x *Config) GetMultihost() *Multihost { + if x != nil { + return x.Multihost + } + return nil +} + +type Multihost struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + KnownHosts []*Multihost_Peer `protobuf:"bytes,1,rep,name=known_hosts,json=knownHosts,proto3" json:"known_hosts,omitempty"` + AuthorizedClients []*Multihost_Peer `protobuf:"bytes,2,rep,name=authorized_clients,json=authorizedClients,proto3" json:"authorized_clients,omitempty"` +} + +func (x *Multihost) Reset() { + *x = Multihost{} + mi := &file_v1_config_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Multihost) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Multihost) ProtoMessage() {} + +func (x *Multihost) ProtoReflect() protoreflect.Message { + mi := &file_v1_config_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Multihost.ProtoReflect.Descriptor instead. +func (*Multihost) Descriptor() ([]byte, []int) { + return file_v1_config_proto_rawDescGZIP(), []int{2} +} + +func (x *Multihost) GetKnownHosts() []*Multihost_Peer { + if x != nil { + return x.KnownHosts + } + return nil +} + +func (x *Multihost) GetAuthorizedClients() []*Multihost_Peer { + if x != nil { + return x.AuthorizedClients + } + return nil +} + type Repo struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // unique but human readable ID for this repo. - Uri string `protobuf:"bytes,2,opt,name=uri,proto3" json:"uri,omitempty"` // restic repo URI - Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` // plaintext password - Env []string `protobuf:"bytes,4,rep,name=env,proto3" json:"env,omitempty"` // extra environment variables to set for restic. - Flags []string `protobuf:"bytes,5,rep,name=flags,proto3" json:"flags,omitempty"` // extra flags set on the restic command. - PrunePolicy *PrunePolicy `protobuf:"bytes,6,opt,name=prune_policy,json=prunePolicy,proto3" json:"prune_policy,omitempty"` // policy for when to run prune. - CheckPolicy *CheckPolicy `protobuf:"bytes,9,opt,name=check_policy,json=checkPolicy,proto3" json:"check_policy,omitempty"` // policy for when to run check. - Hooks []*Hook `protobuf:"bytes,7,rep,name=hooks,proto3" json:"hooks,omitempty"` // hooks to run on events for this repo. - AutoUnlock bool `protobuf:"varint,8,opt,name=auto_unlock,json=autoUnlock,proto3" json:"auto_unlock,omitempty"` // automatically unlock the repo when needed. - CommandPrefix *CommandPrefix `protobuf:"bytes,10,opt,name=command_prefix,json=commandPrefix,proto3" json:"command_prefix,omitempty"` // modifiers for the restic commands + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // unique but human readable ID for this repo. + Uri string `protobuf:"bytes,2,opt,name=uri,proto3" json:"uri,omitempty"` // URI of the repo. + Guid string `protobuf:"bytes,11,opt,name=guid,proto3" json:"guid,omitempty"` // a globally unique ID for this repo. Should be derived as the 'id' field in `restic cat config --json`. + Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` // plaintext password + Env []string `protobuf:"bytes,4,rep,name=env,proto3" json:"env,omitempty"` // extra environment variables to set for restic. + Flags []string `protobuf:"bytes,5,rep,name=flags,proto3" json:"flags,omitempty"` // extra flags set on the restic command. + PrunePolicy *PrunePolicy `protobuf:"bytes,6,opt,name=prune_policy,json=prunePolicy,proto3" json:"prune_policy,omitempty"` // policy for when to run prune. + CheckPolicy *CheckPolicy `protobuf:"bytes,9,opt,name=check_policy,json=checkPolicy,proto3" json:"check_policy,omitempty"` // policy for when to run check. + Hooks []*Hook `protobuf:"bytes,7,rep,name=hooks,proto3" json:"hooks,omitempty"` // hooks to run on events for this repo. + AutoUnlock bool `protobuf:"varint,8,opt,name=auto_unlock,json=autoUnlock,proto3" json:"auto_unlock,omitempty"` // automatically unlock the repo when needed. + CommandPrefix *CommandPrefix `protobuf:"bytes,10,opt,name=command_prefix,json=commandPrefix,proto3" json:"command_prefix,omitempty"` // modifiers for the restic commands + AllowedPeerInstanceIds []string `protobuf:"bytes,100,rep,name=allowed_peer_instance_ids,json=allowedPeers,proto3" json:"allowed_peer_instance_ids,omitempty"` // list of peer instance IDs allowed to access this repo. } func (x *Repo) Reset() { *x = Repo{} - mi := &file_v1_config_proto_msgTypes[2] + mi := &file_v1_config_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -530,7 +593,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[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -543,7 +606,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{3} } func (x *Repo) GetId() string { @@ -560,6 +623,13 @@ func (x *Repo) GetUri() string { return "" } +func (x *Repo) GetGuid() string { + if x != nil { + return x.Guid + } + return "" +} + func (x *Repo) GetPassword() string { if x != nil { return x.Password @@ -616,6 +686,13 @@ func (x *Repo) GetCommandPrefix() *CommandPrefix { return nil } +func (x *Repo) GetAllowedPeerInstanceIds() []string { + if x != nil { + return x.AllowedPeerInstanceIds + } + return nil +} + type Plan struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -635,7 +712,7 @@ type Plan struct { func (x *Plan) Reset() { *x = Plan{} - mi := &file_v1_config_proto_msgTypes[3] + mi := &file_v1_config_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -647,7 +724,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[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -660,7 +737,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{4} } func (x *Plan) GetId() string { @@ -744,7 +821,7 @@ type CommandPrefix struct { func (x *CommandPrefix) Reset() { *x = CommandPrefix{} - mi := &file_v1_config_proto_msgTypes[4] + mi := &file_v1_config_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -756,7 +833,7 @@ func (x *CommandPrefix) String() string { func (*CommandPrefix) ProtoMessage() {} func (x *CommandPrefix) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[4] + mi := &file_v1_config_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -769,7 +846,7 @@ func (x *CommandPrefix) ProtoReflect() protoreflect.Message { // Deprecated: Use CommandPrefix.ProtoReflect.Descriptor instead. func (*CommandPrefix) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{4} + return file_v1_config_proto_rawDescGZIP(), []int{5} } func (x *CommandPrefix) GetIoNice() CommandPrefix_IONiceLevel { @@ -801,7 +878,7 @@ type RetentionPolicy struct { func (x *RetentionPolicy) Reset() { *x = RetentionPolicy{} - mi := &file_v1_config_proto_msgTypes[5] + mi := &file_v1_config_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -813,7 +890,7 @@ func (x *RetentionPolicy) String() string { func (*RetentionPolicy) ProtoMessage() {} func (x *RetentionPolicy) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[5] + mi := &file_v1_config_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -826,7 +903,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{5} + return file_v1_config_proto_rawDescGZIP(), []int{6} } func (m *RetentionPolicy) GetPolicy() isRetentionPolicy_Policy { @@ -891,7 +968,7 @@ type PrunePolicy struct { func (x *PrunePolicy) Reset() { *x = PrunePolicy{} - mi := &file_v1_config_proto_msgTypes[6] + mi := &file_v1_config_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -903,7 +980,7 @@ func (x *PrunePolicy) String() string { func (*PrunePolicy) ProtoMessage() {} func (x *PrunePolicy) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[6] + mi := &file_v1_config_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -916,7 +993,7 @@ func (x *PrunePolicy) ProtoReflect() protoreflect.Message { // Deprecated: Use PrunePolicy.ProtoReflect.Descriptor instead. func (*PrunePolicy) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{6} + return file_v1_config_proto_rawDescGZIP(), []int{7} } func (x *PrunePolicy) GetSchedule() *Schedule { @@ -955,7 +1032,7 @@ type CheckPolicy struct { func (x *CheckPolicy) Reset() { *x = CheckPolicy{} - mi := &file_v1_config_proto_msgTypes[7] + mi := &file_v1_config_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -967,7 +1044,7 @@ func (x *CheckPolicy) String() string { func (*CheckPolicy) ProtoMessage() {} func (x *CheckPolicy) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[7] + mi := &file_v1_config_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -980,7 +1057,7 @@ func (x *CheckPolicy) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckPolicy.ProtoReflect.Descriptor instead. func (*CheckPolicy) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{7} + return file_v1_config_proto_rawDescGZIP(), []int{8} } func (x *CheckPolicy) GetSchedule() *Schedule { @@ -1044,7 +1121,7 @@ type Schedule struct { func (x *Schedule) Reset() { *x = Schedule{} - mi := &file_v1_config_proto_msgTypes[8] + mi := &file_v1_config_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1056,7 +1133,7 @@ func (x *Schedule) String() string { func (*Schedule) ProtoMessage() {} func (x *Schedule) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[8] + mi := &file_v1_config_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1069,7 +1146,7 @@ func (x *Schedule) ProtoReflect() protoreflect.Message { // Deprecated: Use Schedule.ProtoReflect.Descriptor instead. func (*Schedule) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{8} + return file_v1_config_proto_rawDescGZIP(), []int{9} } func (m *Schedule) GetSchedule() isSchedule_Schedule { @@ -1163,7 +1240,7 @@ type Hook struct { func (x *Hook) Reset() { *x = Hook{} - mi := &file_v1_config_proto_msgTypes[9] + mi := &file_v1_config_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1175,7 +1252,7 @@ func (x *Hook) String() string { func (*Hook) ProtoMessage() {} func (x *Hook) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[9] + mi := &file_v1_config_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1188,7 +1265,7 @@ func (x *Hook) ProtoReflect() protoreflect.Message { // Deprecated: Use Hook.ProtoReflect.Descriptor instead. func (*Hook) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{9} + return file_v1_config_proto_rawDescGZIP(), []int{10} } func (x *Hook) GetConditions() []Hook_Condition { @@ -1318,7 +1395,7 @@ type Auth struct { func (x *Auth) Reset() { *x = Auth{} - mi := &file_v1_config_proto_msgTypes[10] + mi := &file_v1_config_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1330,7 +1407,7 @@ func (x *Auth) String() string { func (*Auth) ProtoMessage() {} func (x *Auth) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[10] + mi := &file_v1_config_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1343,7 +1420,7 @@ func (x *Auth) ProtoReflect() protoreflect.Message { // Deprecated: Use Auth.ProtoReflect.Descriptor instead. func (*Auth) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{10} + return file_v1_config_proto_rawDescGZIP(), []int{11} } func (x *Auth) GetDisabled() bool { @@ -1374,7 +1451,7 @@ type User struct { func (x *User) Reset() { *x = User{} - mi := &file_v1_config_proto_msgTypes[11] + mi := &file_v1_config_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1386,7 +1463,7 @@ func (x *User) String() string { func (*User) ProtoMessage() {} func (x *User) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[11] + mi := &file_v1_config_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1399,7 +1476,7 @@ func (x *User) ProtoReflect() protoreflect.Message { // Deprecated: Use User.ProtoReflect.Descriptor instead. func (*User) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{11} + return file_v1_config_proto_rawDescGZIP(), []int{12} } func (x *User) GetName() string { @@ -1444,7 +1521,7 @@ type HubConfig_InstanceInfo struct { func (x *HubConfig_InstanceInfo) Reset() { *x = HubConfig_InstanceInfo{} - mi := &file_v1_config_proto_msgTypes[12] + mi := &file_v1_config_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1456,7 +1533,7 @@ func (x *HubConfig_InstanceInfo) String() string { func (*HubConfig_InstanceInfo) ProtoMessage() {} func (x *HubConfig_InstanceInfo) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[12] + mi := &file_v1_config_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1486,6 +1563,76 @@ func (x *HubConfig_InstanceInfo) GetSecret() string { return "" } +type Multihost_Peer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + InstanceId string `protobuf:"bytes,1,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` // instance ID of the peer. + PublicKey *PublicKey `protobuf:"bytes,3,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` // public key of the peer. If changed, the peer must re-verify the public key. + PublicKeyVerified bool `protobuf:"varint,4,opt,name=public_key_verified,json=publicKeyVerified,proto3" json:"public_key_verified,omitempty"` // whether the public key is verified. This must be set for a host to authenticate a client. Clients implicitly validate the first key they see on initial connection. + // Known host only fields + InstanceUrl string `protobuf:"bytes,2,opt,name=instance_url,json=instanceUrl,proto3" json:"instance_url,omitempty"` // instance URL, required for a known host. Otherwise meaningless. +} + +func (x *Multihost_Peer) Reset() { + *x = Multihost_Peer{} + mi := &file_v1_config_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Multihost_Peer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Multihost_Peer) ProtoMessage() {} + +func (x *Multihost_Peer) ProtoReflect() protoreflect.Message { + mi := &file_v1_config_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Multihost_Peer.ProtoReflect.Descriptor instead. +func (*Multihost_Peer) Descriptor() ([]byte, []int) { + return file_v1_config_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *Multihost_Peer) GetInstanceId() string { + if x != nil { + return x.InstanceId + } + return "" +} + +func (x *Multihost_Peer) GetPublicKey() *PublicKey { + if x != nil { + return x.PublicKey + } + return nil +} + +func (x *Multihost_Peer) GetPublicKeyVerified() bool { + if x != nil { + return x.PublicKeyVerified + } + return false +} + +func (x *Multihost_Peer) GetInstanceUrl() string { + if x != nil { + return x.InstanceUrl + } + return "" +} + type RetentionPolicy_TimeBucketedCounts struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1500,7 +1647,7 @@ type RetentionPolicy_TimeBucketedCounts struct { func (x *RetentionPolicy_TimeBucketedCounts) Reset() { *x = RetentionPolicy_TimeBucketedCounts{} - mi := &file_v1_config_proto_msgTypes[13] + mi := &file_v1_config_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1512,7 +1659,7 @@ func (x *RetentionPolicy_TimeBucketedCounts) String() string { func (*RetentionPolicy_TimeBucketedCounts) ProtoMessage() {} func (x *RetentionPolicy_TimeBucketedCounts) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[13] + mi := &file_v1_config_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1525,7 +1672,7 @@ func (x *RetentionPolicy_TimeBucketedCounts) ProtoReflect() protoreflect.Message // Deprecated: Use RetentionPolicy_TimeBucketedCounts.ProtoReflect.Descriptor instead. func (*RetentionPolicy_TimeBucketedCounts) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{5, 0} + return file_v1_config_proto_rawDescGZIP(), []int{6, 0} } func (x *RetentionPolicy_TimeBucketedCounts) GetHourly() int32 { @@ -1573,7 +1720,7 @@ type Hook_Command struct { func (x *Hook_Command) Reset() { *x = Hook_Command{} - mi := &file_v1_config_proto_msgTypes[14] + mi := &file_v1_config_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1585,7 +1732,7 @@ func (x *Hook_Command) String() string { func (*Hook_Command) ProtoMessage() {} func (x *Hook_Command) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[14] + mi := &file_v1_config_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1598,7 +1745,7 @@ func (x *Hook_Command) ProtoReflect() protoreflect.Message { // Deprecated: Use Hook_Command.ProtoReflect.Descriptor instead. func (*Hook_Command) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{9, 0} + return file_v1_config_proto_rawDescGZIP(), []int{10, 0} } func (x *Hook_Command) GetCommand() string { @@ -1620,7 +1767,7 @@ type Hook_Webhook struct { func (x *Hook_Webhook) Reset() { *x = Hook_Webhook{} - mi := &file_v1_config_proto_msgTypes[15] + mi := &file_v1_config_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1632,7 +1779,7 @@ func (x *Hook_Webhook) String() string { func (*Hook_Webhook) ProtoMessage() {} func (x *Hook_Webhook) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[15] + mi := &file_v1_config_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1645,7 +1792,7 @@ func (x *Hook_Webhook) ProtoReflect() protoreflect.Message { // Deprecated: Use Hook_Webhook.ProtoReflect.Descriptor instead. func (*Hook_Webhook) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{9, 1} + return file_v1_config_proto_rawDescGZIP(), []int{10, 1} } func (x *Hook_Webhook) GetWebhookUrl() string { @@ -1680,7 +1827,7 @@ type Hook_Discord struct { func (x *Hook_Discord) Reset() { *x = Hook_Discord{} - mi := &file_v1_config_proto_msgTypes[16] + mi := &file_v1_config_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1692,7 +1839,7 @@ func (x *Hook_Discord) String() string { func (*Hook_Discord) ProtoMessage() {} func (x *Hook_Discord) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[16] + mi := &file_v1_config_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1705,7 +1852,7 @@ func (x *Hook_Discord) ProtoReflect() protoreflect.Message { // Deprecated: Use Hook_Discord.ProtoReflect.Descriptor instead. func (*Hook_Discord) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{9, 2} + return file_v1_config_proto_rawDescGZIP(), []int{10, 2} } func (x *Hook_Discord) GetWebhookUrl() string { @@ -1735,7 +1882,7 @@ type Hook_Gotify struct { func (x *Hook_Gotify) Reset() { *x = Hook_Gotify{} - mi := &file_v1_config_proto_msgTypes[17] + mi := &file_v1_config_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1747,7 +1894,7 @@ func (x *Hook_Gotify) String() string { func (*Hook_Gotify) ProtoMessage() {} func (x *Hook_Gotify) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[17] + mi := &file_v1_config_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1760,7 +1907,7 @@ func (x *Hook_Gotify) ProtoReflect() protoreflect.Message { // Deprecated: Use Hook_Gotify.ProtoReflect.Descriptor instead. func (*Hook_Gotify) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{9, 3} + return file_v1_config_proto_rawDescGZIP(), []int{10, 3} } func (x *Hook_Gotify) GetBaseUrl() string { @@ -1802,7 +1949,7 @@ type Hook_Slack struct { func (x *Hook_Slack) Reset() { *x = Hook_Slack{} - mi := &file_v1_config_proto_msgTypes[18] + mi := &file_v1_config_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1814,7 +1961,7 @@ func (x *Hook_Slack) String() string { func (*Hook_Slack) ProtoMessage() {} func (x *Hook_Slack) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[18] + mi := &file_v1_config_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1827,7 +1974,7 @@ func (x *Hook_Slack) ProtoReflect() protoreflect.Message { // Deprecated: Use Hook_Slack.ProtoReflect.Descriptor instead. func (*Hook_Slack) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{9, 4} + return file_v1_config_proto_rawDescGZIP(), []int{10, 4} } func (x *Hook_Slack) GetWebhookUrl() string { @@ -1855,7 +2002,7 @@ type Hook_Shoutrrr struct { func (x *Hook_Shoutrrr) Reset() { *x = Hook_Shoutrrr{} - mi := &file_v1_config_proto_msgTypes[19] + mi := &file_v1_config_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1867,7 +2014,7 @@ func (x *Hook_Shoutrrr) String() string { func (*Hook_Shoutrrr) ProtoMessage() {} func (x *Hook_Shoutrrr) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[19] + mi := &file_v1_config_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1880,7 +2027,7 @@ func (x *Hook_Shoutrrr) ProtoReflect() protoreflect.Message { // Deprecated: Use Hook_Shoutrrr.ProtoReflect.Descriptor instead. func (*Hook_Shoutrrr) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{9, 5} + return file_v1_config_proto_rawDescGZIP(), []int{10, 5} } func (x *Hook_Shoutrrr) GetShoutrrrUrl() string { @@ -1908,7 +2055,7 @@ type Hook_Healthchecks struct { func (x *Hook_Healthchecks) Reset() { *x = Hook_Healthchecks{} - mi := &file_v1_config_proto_msgTypes[20] + mi := &file_v1_config_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1920,7 +2067,7 @@ func (x *Hook_Healthchecks) String() string { func (*Hook_Healthchecks) ProtoMessage() {} func (x *Hook_Healthchecks) ProtoReflect() protoreflect.Message { - mi := &file_v1_config_proto_msgTypes[20] + mi := &file_v1_config_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1933,7 +2080,7 @@ func (x *Hook_Healthchecks) ProtoReflect() protoreflect.Message { // Deprecated: Use Hook_Healthchecks.ProtoReflect.Descriptor instead. func (*Hook_Healthchecks) Descriptor() ([]byte, []int) { - return file_v1_config_proto_rawDescGZIP(), []int{9, 6} + return file_v1_config_proto_rawDescGZIP(), []int{10, 6} } func (x *Hook_Healthchecks) GetWebhookUrl() string { @@ -1956,269 +2103,296 @@ 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, 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, 0x22, 0x7d, 0x0a, 0x09, 0x48, 0x75, 0x62, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x38, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x75, 0x62, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x09, - 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x1a, 0x36, 0x0a, 0x0c, 0x49, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, - 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x22, 0xb2, 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, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, - 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 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, 0x12, 0x1c, 0x0a, 0x04, 0x61, 0x75, 0x74, 0x68, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, 0x68, - 0x52, 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0xcf, 0x02, 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, 0x12, 0x32, 0x0a, 0x0c, 0x70, 0x72, 0x75, 0x6e, 0x65, 0x5f, 0x70, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x31, - 0x2e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0b, 0x70, 0x72, - 0x75, 0x6e, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x32, 0x0a, 0x0c, 0x63, 0x68, 0x65, - 0x63, 0x6b, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x52, 0x0b, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1e, 0x0a, - 0x05, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, - 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x05, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x12, 0x1f, 0x0a, - 0x0b, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x0a, 0x61, 0x75, 0x74, 0x6f, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x38, - 0x0a, 0x0e, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, - 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, - 0x61, 0x6e, 0x64, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0xd9, 0x02, 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, 0x1c, 0x0a, 0x09, 0x69, 0x65, 0x78, 0x63, 0x6c, - 0x75, 0x64, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x69, 0x65, 0x78, 0x63, - 0x6c, 0x75, 0x64, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, - 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, + 0x74, 0x6f, 0x1a, 0x0f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x7d, 0x0a, 0x09, 0x48, 0x75, 0x62, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x38, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x75, 0x62, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x1a, 0x36, 0x0a, 0x0c, 0x49, 0x6e, + 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x22, 0xda, 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, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, + 0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 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, 0x12, 0x1c, 0x0a, 0x04, 0x61, 0x75, 0x74, + 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x74, + 0x68, 0x52, 0x04, 0x61, 0x75, 0x74, 0x68, 0x12, 0x26, 0x0a, 0x09, 0x6d, 0x75, 0x6c, 0x74, 0x69, + 0x68, 0x6f, 0x73, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x76, 0x31, 0x2e, + 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x68, 0x6f, 0x73, 0x74, 0x52, 0x04, 0x73, 0x79, 0x6e, 0x63, 0x22, + 0xae, 0x02, 0x0a, 0x09, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x33, 0x0a, + 0x0b, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x68, 0x6f, 0x73, + 0x74, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x52, 0x0a, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x48, 0x6f, 0x73, + 0x74, 0x73, 0x12, 0x41, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, + 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x68, 0x6f, 0x73, 0x74, 0x2e, 0x50, 0x65, + 0x65, 0x72, 0x52, 0x11, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0xa8, 0x01, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x12, 0x1f, + 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, + 0x2c, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, + 0x65, 0x79, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x2e, 0x0a, + 0x13, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x76, 0x65, 0x72, 0x69, + 0x66, 0x69, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x4b, 0x65, 0x79, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x21, 0x0a, + 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x55, 0x72, 0x6c, + 0x22, 0x94, 0x03, 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, 0x12, 0x0a, 0x04, 0x67, + 0x75, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 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, 0x12, 0x32, 0x0a, 0x0c, 0x70, 0x72, 0x75, 0x6e, 0x65, 0x5f, 0x70, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x50, + 0x72, 0x75, 0x6e, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0b, 0x70, 0x72, 0x75, 0x6e, + 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x32, 0x0a, 0x0c, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0b, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1e, 0x0a, 0x05, 0x68, + 0x6f, 0x6f, 0x6b, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, + 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x05, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x61, + 0x75, 0x74, 0x6f, 0x5f, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0a, 0x61, 0x75, 0x74, 0x6f, 0x55, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x38, 0x0a, 0x0e, + 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x2f, 0x0a, 0x19, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, + 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, + 0x69, 0x64, 0x73, 0x18, 0x64, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x6c, 0x6c, 0x6f, 0x77, + 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x73, 0x22, 0xd9, 0x02, 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, 0x1c, 0x0a, 0x09, 0x69, 0x65, 0x78, 0x63, 0x6c, 0x75, + 0x64, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x69, 0x65, 0x78, 0x63, 0x6c, + 0x75, 0x64, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, 0x65, + 0x64, 0x75, 0x6c, 0x65, 0x52, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 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, 0x12, 0x1e, 0x0a, 0x05, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x05, 0x68, 0x6f, 0x6f, 0x6b, + 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x5f, 0x66, 0x6c, 0x61, 0x67, + 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x5f, + 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x69, 0x66, + 0x5f, 0x75, 0x6e, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0f, 0x73, 0x6b, 0x69, 0x70, 0x49, 0x66, 0x55, 0x6e, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x64, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x4a, 0x04, 0x08, + 0x0b, 0x10, 0x0c, 0x22, 0x9b, 0x02, 0x0a, 0x0d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, + 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x36, 0x0a, 0x07, 0x69, 0x6f, 0x5f, 0x6e, 0x69, 0x63, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x2e, 0x49, 0x4f, 0x4e, 0x69, 0x63, 0x65, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x06, 0x69, 0x6f, 0x4e, 0x69, 0x63, 0x65, 0x12, 0x39, 0x0a, + 0x08, 0x63, 0x70, 0x75, 0x5f, 0x6e, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x1e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x72, 0x65, 0x66, + 0x69, 0x78, 0x2e, 0x43, 0x50, 0x55, 0x4e, 0x69, 0x63, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, + 0x07, 0x63, 0x70, 0x75, 0x4e, 0x69, 0x63, 0x65, 0x22, 0x5b, 0x0a, 0x0b, 0x49, 0x4f, 0x4e, 0x69, + 0x63, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x4f, 0x5f, 0x44, 0x45, + 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4f, 0x5f, 0x42, 0x45, + 0x53, 0x54, 0x5f, 0x45, 0x46, 0x46, 0x4f, 0x52, 0x54, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, + 0x17, 0x0a, 0x13, 0x49, 0x4f, 0x5f, 0x42, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x46, 0x46, 0x4f, 0x52, + 0x54, 0x5f, 0x48, 0x49, 0x47, 0x48, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4f, 0x5f, 0x49, + 0x44, 0x4c, 0x45, 0x10, 0x03, 0x22, 0x3a, 0x0a, 0x0c, 0x43, 0x50, 0x55, 0x4e, 0x69, 0x63, 0x65, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x50, 0x55, 0x5f, 0x44, 0x45, 0x46, + 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x50, 0x55, 0x5f, 0x48, 0x49, + 0x47, 0x48, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x50, 0x55, 0x5f, 0x4c, 0x4f, 0x57, 0x10, + 0x02, 0x22, 0xdf, 0x02, 0x0a, 0x0f, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x2d, 0x0a, 0x12, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, + 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x05, 0x48, 0x00, 0x52, 0x0f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4b, 0x65, 0x65, 0x70, 0x4c, + 0x61, 0x73, 0x74, 0x4e, 0x12, 0x5a, 0x0a, 0x14, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x5f, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, + 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x75, 0x63, 0x6b, + 0x65, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x48, 0x00, 0x52, 0x12, 0x70, 0x6f, + 0x6c, 0x69, 0x63, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x65, 0x64, + 0x12, 0x28, 0x0a, 0x0f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x6b, 0x65, 0x65, 0x70, 0x5f, + 0x61, 0x6c, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0d, 0x70, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x6c, 0x1a, 0x8c, 0x01, 0x0a, 0x12, 0x54, + 0x69, 0x6d, 0x65, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, + 0x73, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x6f, 0x75, 0x72, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x06, 0x68, 0x6f, 0x75, 0x72, 0x6c, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x61, 0x69, + 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x12, + 0x16, 0x0a, 0x06, 0x77, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x06, 0x77, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x6f, 0x6e, 0x74, 0x68, + 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, + 0x79, 0x12, 0x16, 0x0a, 0x06, 0x79, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x06, 0x79, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x42, 0x08, 0x0a, 0x06, 0x70, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x22, 0x8f, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x50, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, + 0x75, 0x6c, 0x65, 0x52, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x28, 0x0a, + 0x10, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, + 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x55, 0x6e, 0x75, 0x73, + 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, 0x75, + 0x6e, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x01, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x50, 0x65, + 0x72, 0x63, 0x65, 0x6e, 0x74, 0x22, 0xa3, 0x01, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x50, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 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, 0x12, 0x1e, 0x0a, 0x05, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x52, 0x05, 0x68, 0x6f, 0x6f, - 0x6b, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x5f, 0x66, 0x6c, 0x61, - 0x67, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x69, - 0x66, 0x5f, 0x75, 0x6e, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0f, 0x73, 0x6b, 0x69, 0x70, 0x49, 0x66, 0x55, 0x6e, 0x63, 0x68, 0x61, 0x6e, 0x67, - 0x65, 0x64, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x4a, 0x04, - 0x08, 0x0b, 0x10, 0x0c, 0x22, 0x9b, 0x02, 0x0a, 0x0d, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x36, 0x0a, 0x07, 0x69, 0x6f, 0x5f, 0x6e, 0x69, 0x63, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x2e, 0x49, 0x4f, 0x4e, 0x69, 0x63, - 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x06, 0x69, 0x6f, 0x4e, 0x69, 0x63, 0x65, 0x12, 0x39, - 0x0a, 0x08, 0x63, 0x70, 0x75, 0x5f, 0x6e, 0x69, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x1e, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x50, 0x72, 0x65, - 0x66, 0x69, 0x78, 0x2e, 0x43, 0x50, 0x55, 0x4e, 0x69, 0x63, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, - 0x52, 0x07, 0x63, 0x70, 0x75, 0x4e, 0x69, 0x63, 0x65, 0x22, 0x5b, 0x0a, 0x0b, 0x49, 0x4f, 0x4e, - 0x69, 0x63, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x4f, 0x5f, 0x44, - 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x49, 0x4f, 0x5f, 0x42, - 0x45, 0x53, 0x54, 0x5f, 0x45, 0x46, 0x46, 0x4f, 0x52, 0x54, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x01, - 0x12, 0x17, 0x0a, 0x13, 0x49, 0x4f, 0x5f, 0x42, 0x45, 0x53, 0x54, 0x5f, 0x45, 0x46, 0x46, 0x4f, - 0x52, 0x54, 0x5f, 0x48, 0x49, 0x47, 0x48, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4f, 0x5f, - 0x49, 0x44, 0x4c, 0x45, 0x10, 0x03, 0x22, 0x3a, 0x0a, 0x0c, 0x43, 0x50, 0x55, 0x4e, 0x69, 0x63, - 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x50, 0x55, 0x5f, 0x44, 0x45, - 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x50, 0x55, 0x5f, 0x48, - 0x49, 0x47, 0x48, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x50, 0x55, 0x5f, 0x4c, 0x4f, 0x57, - 0x10, 0x02, 0x22, 0xdf, 0x02, 0x0a, 0x0f, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x2d, 0x0a, 0x12, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x5f, 0x6b, 0x65, 0x65, 0x70, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6e, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x05, 0x48, 0x00, 0x52, 0x0f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4b, 0x65, 0x65, 0x70, - 0x4c, 0x61, 0x73, 0x74, 0x4e, 0x12, 0x5a, 0x0a, 0x14, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, - 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, - 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x75, 0x63, - 0x6b, 0x65, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x48, 0x00, 0x52, 0x12, 0x70, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x65, - 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x6b, 0x65, 0x65, 0x70, - 0x5f, 0x61, 0x6c, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0d, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x4b, 0x65, 0x65, 0x70, 0x41, 0x6c, 0x6c, 0x1a, 0x8c, 0x01, 0x0a, 0x12, - 0x54, 0x69, 0x6d, 0x65, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x6f, 0x75, 0x72, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x06, 0x68, 0x6f, 0x75, 0x72, 0x6c, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x61, - 0x69, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x64, 0x61, 0x69, 0x6c, 0x79, - 0x12, 0x16, 0x0a, 0x06, 0x77, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x06, 0x77, 0x65, 0x65, 0x6b, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x6f, 0x6e, 0x74, - 0x68, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x6d, 0x6f, 0x6e, 0x74, 0x68, - 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x79, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x06, 0x79, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x42, 0x08, 0x0a, 0x06, 0x70, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x22, 0x8f, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, 0x65, - 0x64, 0x75, 0x6c, 0x65, 0x52, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x28, - 0x0a, 0x10, 0x6d, 0x61, 0x78, 0x5f, 0x75, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x74, - 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, 0x61, 0x78, 0x55, 0x6e, 0x75, - 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x6d, 0x61, 0x78, 0x5f, - 0x75, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x01, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x55, 0x6e, 0x75, 0x73, 0x65, 0x64, 0x50, - 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x22, 0xa3, 0x01, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x28, 0x0a, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, - 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, - 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x08, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, - 0x12, 0x27, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6f, 0x6e, - 0x6c, 0x79, 0x18, 0x64, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0d, 0x73, 0x74, 0x72, 0x75, - 0x63, 0x74, 0x75, 0x72, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x39, 0x0a, 0x18, 0x72, 0x65, 0x61, - 0x64, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x65, 0x74, 0x5f, 0x70, 0x65, - 0x72, 0x63, 0x65, 0x6e, 0x74, 0x18, 0x65, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x15, 0x72, - 0x65, 0x61, 0x64, 0x44, 0x61, 0x74, 0x61, 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, 0x50, 0x65, 0x72, - 0x63, 0x65, 0x6e, 0x74, 0x42, 0x06, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0xa7, 0x02, 0x0a, - 0x08, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x1c, 0x0a, 0x08, 0x64, 0x69, 0x73, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x08, 0x64, - 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, - 0x10, 0x6d, 0x61, 0x78, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x61, 0x79, - 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x46, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x61, 0x79, 0x73, 0x12, 0x2e, 0x0a, 0x11, 0x6d, - 0x61, 0x78, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x75, 0x72, 0x73, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x11, 0x6d, 0x61, 0x78, 0x46, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x75, 0x72, 0x73, 0x12, 0x28, 0x0a, 0x05, 0x63, - 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, - 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x05, - 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x53, 0x0a, 0x05, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x11, - 0x0a, 0x0d, 0x43, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, - 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, - 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x55, 0x54, 0x43, 0x10, - 0x02, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x4c, 0x41, 0x53, 0x54, 0x5f, - 0x52, 0x55, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x10, 0x03, 0x42, 0x0a, 0x0a, 0x08, 0x73, 0x63, - 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x22, 0xcf, 0x0d, 0x0a, 0x04, 0x48, 0x6f, 0x6f, 0x6b, 0x12, - 0x32, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x43, 0x6f, - 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, - 0x4f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x07, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x12, 0x39, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, - 0x6f, 0x6b, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x39, 0x0a, 0x0e, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x18, 0x65, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x57, 0x65, - 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x57, - 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x39, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x66, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, - 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, - 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, - 0x64, 0x12, 0x36, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x67, 0x6f, 0x74, 0x69, - 0x66, 0x79, 0x18, 0x67, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, - 0x6f, 0x6b, 0x2e, 0x47, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x48, 0x00, 0x52, 0x0c, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x47, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x33, 0x0a, 0x0c, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x6c, 0x61, 0x63, 0x6b, 0x18, 0x68, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0e, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x48, - 0x00, 0x52, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x12, 0x3c, - 0x0a, 0x0f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, - 0x72, 0x18, 0x69, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, - 0x6b, 0x2e, 0x53, 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x48, 0x00, 0x52, 0x0e, 0x61, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x12, 0x48, 0x0a, 0x13, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, - 0x63, 0x6b, 0x73, 0x18, 0x6a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x48, - 0x6f, 0x6f, 0x6b, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, - 0x48, 0x00, 0x52, 0x12, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x1a, 0x23, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x1a, 0xa1, 0x01, 0x0a, 0x07, - 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, - 0x6f, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x65, - 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x2f, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, - 0x6f, 0x6b, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x22, 0x28, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, - 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, - 0x47, 0x45, 0x54, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f, 0x53, 0x54, 0x10, 0x02, 0x1a, - 0x46, 0x0a, 0x07, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x65, - 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x74, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, - 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x1a, 0x7c, 0x0a, 0x06, 0x47, 0x6f, 0x74, 0x69, 0x66, - 0x79, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x64, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x25, - 0x0a, 0x0e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x18, 0x65, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x54, 0x65, 0x6d, - 0x70, 0x6c, 0x61, 0x74, 0x65, 0x1a, 0x44, 0x0a, 0x05, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x12, 0x1f, - 0x0a, 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x12, - 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x1a, 0x49, 0x0a, 0x08, 0x53, - 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x68, 0x6f, 0x75, 0x74, - 0x72, 0x72, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, - 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x1a, 0x4b, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, + 0x27, 0x0a, 0x0e, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6f, 0x6e, 0x6c, + 0x79, 0x18, 0x64, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x0d, 0x73, 0x74, 0x72, 0x75, 0x63, + 0x74, 0x75, 0x72, 0x65, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x39, 0x0a, 0x18, 0x72, 0x65, 0x61, 0x64, + 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x65, 0x74, 0x5f, 0x70, 0x65, 0x72, + 0x63, 0x65, 0x6e, 0x74, 0x18, 0x65, 0x20, 0x01, 0x28, 0x01, 0x48, 0x00, 0x52, 0x15, 0x72, 0x65, + 0x61, 0x64, 0x44, 0x61, 0x74, 0x61, 0x53, 0x75, 0x62, 0x73, 0x65, 0x74, 0x50, 0x65, 0x72, 0x63, + 0x65, 0x6e, 0x74, 0x42, 0x06, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0xa7, 0x02, 0x0a, 0x08, + 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x1c, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, + 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x08, 0x64, 0x69, + 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x14, 0x0a, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x10, + 0x6d, 0x61, 0x78, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x61, 0x79, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x10, 0x6d, 0x61, 0x78, 0x46, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x44, 0x61, 0x79, 0x73, 0x12, 0x2e, 0x0a, 0x11, 0x6d, 0x61, + 0x78, 0x46, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x75, 0x72, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x11, 0x6d, 0x61, 0x78, 0x46, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x6e, 0x63, 0x79, 0x48, 0x6f, 0x75, 0x72, 0x73, 0x12, 0x28, 0x0a, 0x05, 0x63, 0x6c, + 0x6f, 0x63, 0x6b, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x05, 0x63, + 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x53, 0x0a, 0x05, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x11, 0x0a, + 0x0d, 0x43, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x44, 0x45, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x10, 0x00, + 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x10, + 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x55, 0x54, 0x43, 0x10, 0x02, + 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x4c, 0x41, 0x53, 0x54, 0x5f, 0x52, + 0x55, 0x4e, 0x5f, 0x54, 0x49, 0x4d, 0x45, 0x10, 0x03, 0x42, 0x0a, 0x0a, 0x08, 0x73, 0x63, 0x68, + 0x65, 0x64, 0x75, 0x6c, 0x65, 0x22, 0xcf, 0x0d, 0x0a, 0x04, 0x48, 0x6f, 0x6f, 0x6b, 0x12, 0x32, + 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x43, 0x6f, 0x6e, + 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x2b, 0x0a, 0x08, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x4f, + 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x07, 0x6f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, + 0x39, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, + 0x6b, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x39, 0x0a, 0x0e, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x18, 0x65, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x57, 0x65, 0x62, + 0x68, 0x6f, 0x6f, 0x6b, 0x48, 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x57, 0x65, + 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x39, 0x0a, 0x0e, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x64, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x18, 0x66, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, + 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x48, + 0x00, 0x52, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, + 0x12, 0x36, 0x0a, 0x0d, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x67, 0x6f, 0x74, 0x69, 0x66, + 0x79, 0x18, 0x67, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, + 0x6b, 0x2e, 0x47, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x48, 0x00, 0x52, 0x0c, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x47, 0x6f, 0x74, 0x69, 0x66, 0x79, 0x12, 0x33, 0x0a, 0x0c, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x73, 0x6c, 0x61, 0x63, 0x6b, 0x18, 0x68, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, + 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x48, 0x00, + 0x52, 0x0b, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x12, 0x3c, 0x0a, + 0x0f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, + 0x18, 0x69, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, + 0x2e, 0x53, 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x48, 0x00, 0x52, 0x0e, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x68, 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x12, 0x48, 0x0a, 0x13, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, + 0x6b, 0x73, 0x18, 0x6a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, + 0x6f, 0x6b, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x48, + 0x00, 0x52, 0x12, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x73, 0x1a, 0x23, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x1a, 0xa1, 0x01, 0x0a, 0x07, 0x57, + 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x65, 0x62, - 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x22, 0x9c, 0x03, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, - 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4e, 0x44, - 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x4e, 0x59, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, - 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x02, 0x12, - 0x1a, 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, - 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x45, 0x4e, 0x44, 0x10, 0x03, 0x12, 0x1c, 0x0a, 0x18, 0x43, - 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, - 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, - 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, - 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, - 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, - 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x06, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, - 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, - 0x53, 0x4b, 0x49, 0x50, 0x50, 0x45, 0x44, 0x10, 0x07, 0x12, 0x19, 0x0a, 0x15, 0x43, 0x4f, 0x4e, - 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x55, 0x4e, 0x45, 0x5f, 0x53, 0x54, 0x41, - 0x52, 0x54, 0x10, 0x64, 0x12, 0x19, 0x0a, 0x15, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, - 0x4e, 0x5f, 0x50, 0x52, 0x55, 0x4e, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x65, 0x12, - 0x1b, 0x0a, 0x17, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x55, - 0x4e, 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x66, 0x12, 0x1a, 0x0a, 0x15, - 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, - 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0xc8, 0x01, 0x12, 0x1a, 0x0a, 0x15, 0x43, 0x4f, 0x4e, 0x44, - 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x45, 0x52, 0x52, 0x4f, - 0x52, 0x10, 0xc9, 0x01, 0x12, 0x1c, 0x0a, 0x17, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, - 0x4e, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, - 0xca, 0x01, 0x22, 0xa9, 0x01, 0x0a, 0x07, 0x4f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x13, - 0x0a, 0x0f, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x47, 0x4e, 0x4f, 0x52, - 0x45, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, - 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x4f, 0x4e, 0x5f, 0x45, - 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, - 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x54, 0x52, 0x59, 0x5f, 0x31, - 0x4d, 0x49, 0x4e, 0x55, 0x54, 0x45, 0x10, 0x64, 0x12, 0x1c, 0x0a, 0x18, 0x4f, 0x4e, 0x5f, 0x45, - 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x54, 0x52, 0x59, 0x5f, 0x31, 0x30, 0x4d, 0x49, 0x4e, - 0x55, 0x54, 0x45, 0x53, 0x10, 0x65, 0x12, 0x26, 0x0a, 0x22, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x54, 0x52, 0x59, 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x4e, 0x45, 0x4e, - 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x42, 0x41, 0x43, 0x4b, 0x4f, 0x46, 0x46, 0x10, 0x67, 0x42, 0x08, - 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x42, 0x0a, 0x04, 0x41, 0x75, 0x74, 0x68, - 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1e, 0x0a, 0x05, - 0x75, 0x73, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, - 0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x22, 0x51, 0x0a, 0x04, - 0x55, 0x73, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x0f, 0x70, 0x61, 0x73, 0x73, - 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x62, 0x63, 0x72, 0x79, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x48, 0x00, 0x52, 0x0e, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x42, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x42, - 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, - 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, - 0x65, 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x2f, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, + 0x6b, 0x2e, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x22, 0x28, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x0b, + 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x47, + 0x45, 0x54, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x50, 0x4f, 0x53, 0x54, 0x10, 0x02, 0x1a, 0x46, + 0x0a, 0x07, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x65, 0x62, + 0x68, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x1a, 0x7c, 0x0a, 0x06, 0x47, 0x6f, 0x74, 0x69, 0x66, 0x79, + 0x12, 0x19, 0x0a, 0x08, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x73, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x64, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x25, 0x0a, + 0x0e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x5f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, + 0x65, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x54, 0x65, 0x6d, 0x70, + 0x6c, 0x61, 0x74, 0x65, 0x1a, 0x44, 0x0a, 0x05, 0x53, 0x6c, 0x61, 0x63, 0x6b, 0x12, 0x1f, 0x0a, + 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x1a, + 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x1a, 0x49, 0x0a, 0x08, 0x53, 0x68, + 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x68, 0x6f, 0x75, 0x74, 0x72, + 0x72, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x68, + 0x6f, 0x75, 0x74, 0x72, 0x72, 0x72, 0x55, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x1a, 0x4b, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, + 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x65, 0x62, 0x68, + 0x6f, 0x6f, 0x6b, 0x55, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x22, 0x9c, 0x03, 0x0a, 0x09, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x15, 0x0a, 0x11, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, + 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x4f, 0x4e, 0x44, 0x49, + 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x41, 0x4e, 0x59, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, + 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, + 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x02, 0x12, 0x1a, + 0x0a, 0x16, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, + 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x45, 0x4e, 0x44, 0x10, 0x03, 0x12, 0x1c, 0x0a, 0x18, 0x43, 0x4f, + 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, + 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, 0x44, + 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x57, + 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, 0x44, + 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x53, + 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x06, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, 0x44, + 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x5f, 0x53, + 0x4b, 0x49, 0x50, 0x50, 0x45, 0x44, 0x10, 0x07, 0x12, 0x19, 0x0a, 0x15, 0x43, 0x4f, 0x4e, 0x44, + 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x55, 0x4e, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, + 0x54, 0x10, 0x64, 0x12, 0x19, 0x0a, 0x15, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, + 0x5f, 0x50, 0x52, 0x55, 0x4e, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x65, 0x12, 0x1b, + 0x0a, 0x17, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x55, 0x4e, + 0x45, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x66, 0x12, 0x1a, 0x0a, 0x15, 0x43, + 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x53, + 0x54, 0x41, 0x52, 0x54, 0x10, 0xc8, 0x01, 0x12, 0x1a, 0x0a, 0x15, 0x43, 0x4f, 0x4e, 0x44, 0x49, + 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x10, 0xc9, 0x01, 0x12, 0x1c, 0x0a, 0x17, 0x43, 0x4f, 0x4e, 0x44, 0x49, 0x54, 0x49, 0x4f, 0x4e, + 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x5f, 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0xca, + 0x01, 0x22, 0xa9, 0x01, 0x0a, 0x07, 0x4f, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x13, 0x0a, + 0x0f, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x49, 0x47, 0x4e, 0x4f, 0x52, 0x45, + 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, + 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x4f, 0x4e, 0x5f, 0x45, 0x52, + 0x52, 0x4f, 0x52, 0x5f, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x4f, + 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x54, 0x52, 0x59, 0x5f, 0x31, 0x4d, + 0x49, 0x4e, 0x55, 0x54, 0x45, 0x10, 0x64, 0x12, 0x1c, 0x0a, 0x18, 0x4f, 0x4e, 0x5f, 0x45, 0x52, + 0x52, 0x4f, 0x52, 0x5f, 0x52, 0x45, 0x54, 0x52, 0x59, 0x5f, 0x31, 0x30, 0x4d, 0x49, 0x4e, 0x55, + 0x54, 0x45, 0x53, 0x10, 0x65, 0x12, 0x26, 0x0a, 0x22, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x5f, 0x52, 0x45, 0x54, 0x52, 0x59, 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x4e, 0x45, 0x4e, 0x54, + 0x49, 0x41, 0x4c, 0x5f, 0x42, 0x41, 0x43, 0x4b, 0x4f, 0x46, 0x46, 0x10, 0x67, 0x42, 0x08, 0x0a, + 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x42, 0x0a, 0x04, 0x41, 0x75, 0x74, 0x68, 0x12, + 0x1a, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x08, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1e, 0x0a, 0x05, 0x75, + 0x73, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x76, 0x31, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x52, 0x05, 0x75, 0x73, 0x65, 0x72, 0x73, 0x22, 0x51, 0x0a, 0x04, 0x55, + 0x73, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x0f, 0x70, 0x61, 0x73, 0x73, 0x77, + 0x6f, 0x72, 0x64, 0x5f, 0x62, 0x63, 0x72, 0x79, 0x70, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x48, 0x00, 0x52, 0x0e, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x42, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x42, 0x2c, + 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, + 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, + 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2234,7 +2408,7 @@ func file_v1_config_proto_rawDescGZIP() []byte { } var file_v1_config_proto_enumTypes = make([]protoimpl.EnumInfo, 6) -var file_v1_config_proto_msgTypes = make([]protoimpl.MessageInfo, 21) +var file_v1_config_proto_msgTypes = make([]protoimpl.MessageInfo, 23) var file_v1_config_proto_goTypes = []any{ (CommandPrefix_IONiceLevel)(0), // 0: v1.CommandPrefix.IONiceLevel (CommandPrefix_CPUNiceLevel)(0), // 1: v1.CommandPrefix.CPUNiceLevel @@ -2244,60 +2418,67 @@ var file_v1_config_proto_goTypes = []any{ (Hook_Webhook_Method)(0), // 5: v1.Hook.Webhook.Method (*HubConfig)(nil), // 6: v1.HubConfig (*Config)(nil), // 7: v1.Config - (*Repo)(nil), // 8: v1.Repo - (*Plan)(nil), // 9: v1.Plan - (*CommandPrefix)(nil), // 10: v1.CommandPrefix - (*RetentionPolicy)(nil), // 11: v1.RetentionPolicy - (*PrunePolicy)(nil), // 12: v1.PrunePolicy - (*CheckPolicy)(nil), // 13: v1.CheckPolicy - (*Schedule)(nil), // 14: v1.Schedule - (*Hook)(nil), // 15: v1.Hook - (*Auth)(nil), // 16: v1.Auth - (*User)(nil), // 17: v1.User - (*HubConfig_InstanceInfo)(nil), // 18: v1.HubConfig.InstanceInfo - (*RetentionPolicy_TimeBucketedCounts)(nil), // 19: v1.RetentionPolicy.TimeBucketedCounts - (*Hook_Command)(nil), // 20: v1.Hook.Command - (*Hook_Webhook)(nil), // 21: v1.Hook.Webhook - (*Hook_Discord)(nil), // 22: v1.Hook.Discord - (*Hook_Gotify)(nil), // 23: v1.Hook.Gotify - (*Hook_Slack)(nil), // 24: v1.Hook.Slack - (*Hook_Shoutrrr)(nil), // 25: v1.Hook.Shoutrrr - (*Hook_Healthchecks)(nil), // 26: v1.Hook.Healthchecks + (*Multihost)(nil), // 8: v1.Multihost + (*Repo)(nil), // 9: v1.Repo + (*Plan)(nil), // 10: v1.Plan + (*CommandPrefix)(nil), // 11: v1.CommandPrefix + (*RetentionPolicy)(nil), // 12: v1.RetentionPolicy + (*PrunePolicy)(nil), // 13: v1.PrunePolicy + (*CheckPolicy)(nil), // 14: v1.CheckPolicy + (*Schedule)(nil), // 15: v1.Schedule + (*Hook)(nil), // 16: v1.Hook + (*Auth)(nil), // 17: v1.Auth + (*User)(nil), // 18: v1.User + (*HubConfig_InstanceInfo)(nil), // 19: v1.HubConfig.InstanceInfo + (*Multihost_Peer)(nil), // 20: v1.Multihost.Peer + (*RetentionPolicy_TimeBucketedCounts)(nil), // 21: v1.RetentionPolicy.TimeBucketedCounts + (*Hook_Command)(nil), // 22: v1.Hook.Command + (*Hook_Webhook)(nil), // 23: v1.Hook.Webhook + (*Hook_Discord)(nil), // 24: v1.Hook.Discord + (*Hook_Gotify)(nil), // 25: v1.Hook.Gotify + (*Hook_Slack)(nil), // 26: v1.Hook.Slack + (*Hook_Shoutrrr)(nil), // 27: v1.Hook.Shoutrrr + (*Hook_Healthchecks)(nil), // 28: v1.Hook.Healthchecks + (*PublicKey)(nil), // 29: v1.PublicKey } var file_v1_config_proto_depIdxs = []int32{ - 18, // 0: v1.HubConfig.instances:type_name -> v1.HubConfig.InstanceInfo - 8, // 1: v1.Config.repos:type_name -> v1.Repo - 9, // 2: v1.Config.plans:type_name -> v1.Plan - 16, // 3: v1.Config.auth:type_name -> v1.Auth - 12, // 4: v1.Repo.prune_policy:type_name -> v1.PrunePolicy - 13, // 5: v1.Repo.check_policy:type_name -> v1.CheckPolicy - 15, // 6: v1.Repo.hooks:type_name -> v1.Hook - 10, // 7: v1.Repo.command_prefix:type_name -> v1.CommandPrefix - 14, // 8: v1.Plan.schedule:type_name -> v1.Schedule - 11, // 9: v1.Plan.retention:type_name -> v1.RetentionPolicy - 15, // 10: v1.Plan.hooks:type_name -> v1.Hook - 0, // 11: v1.CommandPrefix.io_nice:type_name -> v1.CommandPrefix.IONiceLevel - 1, // 12: v1.CommandPrefix.cpu_nice:type_name -> v1.CommandPrefix.CPUNiceLevel - 19, // 13: v1.RetentionPolicy.policy_time_bucketed:type_name -> v1.RetentionPolicy.TimeBucketedCounts - 14, // 14: v1.PrunePolicy.schedule:type_name -> v1.Schedule - 14, // 15: v1.CheckPolicy.schedule:type_name -> v1.Schedule - 2, // 16: v1.Schedule.clock:type_name -> v1.Schedule.Clock - 3, // 17: v1.Hook.conditions:type_name -> v1.Hook.Condition - 4, // 18: v1.Hook.on_error:type_name -> v1.Hook.OnError - 20, // 19: v1.Hook.action_command:type_name -> v1.Hook.Command - 21, // 20: v1.Hook.action_webhook:type_name -> v1.Hook.Webhook - 22, // 21: v1.Hook.action_discord:type_name -> v1.Hook.Discord - 23, // 22: v1.Hook.action_gotify:type_name -> v1.Hook.Gotify - 24, // 23: v1.Hook.action_slack:type_name -> v1.Hook.Slack - 25, // 24: v1.Hook.action_shoutrrr:type_name -> v1.Hook.Shoutrrr - 26, // 25: v1.Hook.action_healthchecks:type_name -> v1.Hook.Healthchecks - 17, // 26: v1.Auth.users:type_name -> v1.User - 5, // 27: v1.Hook.Webhook.method:type_name -> v1.Hook.Webhook.Method - 28, // [28:28] is the sub-list for method output_type - 28, // [28:28] is the sub-list for method input_type - 28, // [28:28] is the sub-list for extension type_name - 28, // [28:28] is the sub-list for extension extendee - 0, // [0:28] is the sub-list for field type_name + 19, // 0: v1.HubConfig.instances:type_name -> v1.HubConfig.InstanceInfo + 9, // 1: v1.Config.repos:type_name -> v1.Repo + 10, // 2: v1.Config.plans:type_name -> v1.Plan + 17, // 3: v1.Config.auth:type_name -> v1.Auth + 8, // 4: v1.Config.multihost:type_name -> v1.Multihost + 20, // 5: v1.Multihost.known_hosts:type_name -> v1.Multihost.Peer + 20, // 6: v1.Multihost.authorized_clients:type_name -> v1.Multihost.Peer + 13, // 7: v1.Repo.prune_policy:type_name -> v1.PrunePolicy + 14, // 8: v1.Repo.check_policy:type_name -> v1.CheckPolicy + 16, // 9: v1.Repo.hooks:type_name -> v1.Hook + 11, // 10: v1.Repo.command_prefix:type_name -> v1.CommandPrefix + 15, // 11: v1.Plan.schedule:type_name -> v1.Schedule + 12, // 12: v1.Plan.retention:type_name -> v1.RetentionPolicy + 16, // 13: v1.Plan.hooks:type_name -> v1.Hook + 0, // 14: v1.CommandPrefix.io_nice:type_name -> v1.CommandPrefix.IONiceLevel + 1, // 15: v1.CommandPrefix.cpu_nice:type_name -> v1.CommandPrefix.CPUNiceLevel + 21, // 16: v1.RetentionPolicy.policy_time_bucketed:type_name -> v1.RetentionPolicy.TimeBucketedCounts + 15, // 17: v1.PrunePolicy.schedule:type_name -> v1.Schedule + 15, // 18: v1.CheckPolicy.schedule:type_name -> v1.Schedule + 2, // 19: v1.Schedule.clock:type_name -> v1.Schedule.Clock + 3, // 20: v1.Hook.conditions:type_name -> v1.Hook.Condition + 4, // 21: v1.Hook.on_error:type_name -> v1.Hook.OnError + 22, // 22: v1.Hook.action_command:type_name -> v1.Hook.Command + 23, // 23: v1.Hook.action_webhook:type_name -> v1.Hook.Webhook + 24, // 24: v1.Hook.action_discord:type_name -> v1.Hook.Discord + 25, // 25: v1.Hook.action_gotify:type_name -> v1.Hook.Gotify + 26, // 26: v1.Hook.action_slack:type_name -> v1.Hook.Slack + 27, // 27: v1.Hook.action_shoutrrr:type_name -> v1.Hook.Shoutrrr + 28, // 28: v1.Hook.action_healthchecks:type_name -> v1.Hook.Healthchecks + 18, // 29: v1.Auth.users:type_name -> v1.User + 29, // 30: v1.Multihost.Peer.public_key:type_name -> v1.PublicKey + 5, // 31: v1.Hook.Webhook.method:type_name -> v1.Hook.Webhook.Method + 32, // [32:32] is the sub-list for method output_type + 32, // [32:32] is the sub-list for method input_type + 32, // [32:32] is the sub-list for extension type_name + 32, // [32:32] is the sub-list for extension extendee + 0, // [0:32] is the sub-list for field type_name } func init() { file_v1_config_proto_init() } @@ -2305,22 +2486,23 @@ func file_v1_config_proto_init() { if File_v1_config_proto != nil { return } - file_v1_config_proto_msgTypes[5].OneofWrappers = []any{ + file_v1_crypto_proto_init() + file_v1_config_proto_msgTypes[6].OneofWrappers = []any{ (*RetentionPolicy_PolicyKeepLastN)(nil), (*RetentionPolicy_PolicyTimeBucketed)(nil), (*RetentionPolicy_PolicyKeepAll)(nil), } - file_v1_config_proto_msgTypes[7].OneofWrappers = []any{ + file_v1_config_proto_msgTypes[8].OneofWrappers = []any{ (*CheckPolicy_StructureOnly)(nil), (*CheckPolicy_ReadDataSubsetPercent)(nil), } - file_v1_config_proto_msgTypes[8].OneofWrappers = []any{ + file_v1_config_proto_msgTypes[9].OneofWrappers = []any{ (*Schedule_Disabled)(nil), (*Schedule_Cron)(nil), (*Schedule_MaxFrequencyDays)(nil), (*Schedule_MaxFrequencyHours)(nil), } - file_v1_config_proto_msgTypes[9].OneofWrappers = []any{ + file_v1_config_proto_msgTypes[10].OneofWrappers = []any{ (*Hook_ActionCommand)(nil), (*Hook_ActionWebhook)(nil), (*Hook_ActionDiscord)(nil), @@ -2329,7 +2511,7 @@ func file_v1_config_proto_init() { (*Hook_ActionShoutrrr)(nil), (*Hook_ActionHealthchecks)(nil), } - file_v1_config_proto_msgTypes[11].OneofWrappers = []any{ + file_v1_config_proto_msgTypes[12].OneofWrappers = []any{ (*User_PasswordBcrypt)(nil), } type x struct{} @@ -2338,7 +2520,7 @@ func file_v1_config_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_v1_config_proto_rawDesc, NumEnums: 6, - NumMessages: 21, + NumMessages: 23, NumExtensions: 0, NumServices: 0, }, diff --git a/gen/go/v1/crypto.pb.go b/gen/go/v1/crypto.pb.go new file mode 100644 index 0000000..1351008 --- /dev/null +++ b/gen/go/v1/crypto.pb.go @@ -0,0 +1,312 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// protoc (unknown) +// source: v1/crypto.proto + +package v1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SignedMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Keyid string `protobuf:"bytes,1,opt,name=keyid,proto3" json:"keyid,omitempty"` // a unique identifier generated as the SHA256 of the public key used to sign the message. + Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` // the payload + Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` // the signature of the payload +} + +func (x *SignedMessage) Reset() { + *x = SignedMessage{} + mi := &file_v1_crypto_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SignedMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SignedMessage) ProtoMessage() {} + +func (x *SignedMessage) ProtoReflect() protoreflect.Message { + mi := &file_v1_crypto_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SignedMessage.ProtoReflect.Descriptor instead. +func (*SignedMessage) Descriptor() ([]byte, []int) { + return file_v1_crypto_proto_rawDescGZIP(), []int{0} +} + +func (x *SignedMessage) GetKeyid() string { + if x != nil { + return x.Keyid + } + return "" +} + +func (x *SignedMessage) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +func (x *SignedMessage) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +type EncryptedMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` +} + +func (x *EncryptedMessage) Reset() { + *x = EncryptedMessage{} + mi := &file_v1_crypto_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EncryptedMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EncryptedMessage) ProtoMessage() {} + +func (x *EncryptedMessage) ProtoReflect() protoreflect.Message { + mi := &file_v1_crypto_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EncryptedMessage.ProtoReflect.Descriptor instead. +func (*EncryptedMessage) Descriptor() ([]byte, []int) { + return file_v1_crypto_proto_rawDescGZIP(), []int{1} +} + +func (x *EncryptedMessage) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +type PublicKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Keyid string `protobuf:"bytes,1,opt,name=keyid,proto3" json:"keyid,omitempty"` // a unique identifier generated as the SHA256 of the public key. + Ed25519 string `protobuf:"bytes,2,opt,name=ed25519,json=ed25519pub,proto3" json:"ed25519,omitempty"` // base64 encoded public key +} + +func (x *PublicKey) Reset() { + *x = PublicKey{} + mi := &file_v1_crypto_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PublicKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PublicKey) ProtoMessage() {} + +func (x *PublicKey) ProtoReflect() protoreflect.Message { + mi := &file_v1_crypto_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PublicKey.ProtoReflect.Descriptor instead. +func (*PublicKey) Descriptor() ([]byte, []int) { + return file_v1_crypto_proto_rawDescGZIP(), []int{2} +} + +func (x *PublicKey) GetKeyid() string { + if x != nil { + return x.Keyid + } + return "" +} + +func (x *PublicKey) GetEd25519() string { + if x != nil { + return x.Ed25519 + } + return "" +} + +type PrivateKey struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Keyid string `protobuf:"bytes,1,opt,name=keyid,proto3" json:"keyid,omitempty"` // a unique identifier generated as the SHA256 of the public key. + Ed25519 string `protobuf:"bytes,2,opt,name=ed25519,json=ed25519priv,proto3" json:"ed25519,omitempty"` // base64 encoded private key +} + +func (x *PrivateKey) Reset() { + *x = PrivateKey{} + mi := &file_v1_crypto_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PrivateKey) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PrivateKey) ProtoMessage() {} + +func (x *PrivateKey) ProtoReflect() protoreflect.Message { + mi := &file_v1_crypto_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PrivateKey.ProtoReflect.Descriptor instead. +func (*PrivateKey) Descriptor() ([]byte, []int) { + return file_v1_crypto_proto_rawDescGZIP(), []int{3} +} + +func (x *PrivateKey) GetKeyid() string { + if x != nil { + return x.Keyid + } + return "" +} + +func (x *PrivateKey) GetEd25519() string { + if x != nil { + return x.Ed25519 + } + return "" +} + +var File_v1_crypto_proto protoreflect.FileDescriptor + +var file_v1_crypto_proto_rawDesc = []byte{ + 0x0a, 0x0f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x02, 0x76, 0x31, 0x22, 0x5d, 0x0a, 0x0d, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6b, 0x65, 0x79, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, + 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x22, 0x2c, 0x0a, 0x10, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, + 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, + 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, + 0x61, 0x64, 0x22, 0x3e, 0x0a, 0x09, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x6b, 0x65, 0x79, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x6b, 0x65, 0x79, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x07, 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x70, + 0x75, 0x62, 0x22, 0x40, 0x0a, 0x0a, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x6b, 0x65, 0x79, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x6b, 0x65, 0x79, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x07, 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, + 0x39, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, + 0x70, 0x72, 0x69, 0x76, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, + 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, + 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_v1_crypto_proto_rawDescOnce sync.Once + file_v1_crypto_proto_rawDescData = file_v1_crypto_proto_rawDesc +) + +func file_v1_crypto_proto_rawDescGZIP() []byte { + file_v1_crypto_proto_rawDescOnce.Do(func() { + file_v1_crypto_proto_rawDescData = protoimpl.X.CompressGZIP(file_v1_crypto_proto_rawDescData) + }) + return file_v1_crypto_proto_rawDescData +} + +var file_v1_crypto_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_v1_crypto_proto_goTypes = []any{ + (*SignedMessage)(nil), // 0: v1.SignedMessage + (*EncryptedMessage)(nil), // 1: v1.EncryptedMessage + (*PublicKey)(nil), // 2: v1.PublicKey + (*PrivateKey)(nil), // 3: v1.PrivateKey +} +var file_v1_crypto_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] 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 +} + +func init() { file_v1_crypto_proto_init() } +func file_v1_crypto_proto_init() { + if File_v1_crypto_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_v1_crypto_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_v1_crypto_proto_goTypes, + DependencyIndexes: file_v1_crypto_proto_depIdxs, + MessageInfos: file_v1_crypto_proto_msgTypes, + }.Build() + File_v1_crypto_proto = out.File + file_v1_crypto_proto_rawDesc = nil + file_v1_crypto_proto_goTypes = nil + file_v1_crypto_proto_depIdxs = nil +} diff --git a/gen/go/v1/hub.pb.go b/gen/go/v1/hub.pb.go deleted file mode 100644 index a213ad3..0000000 --- a/gen/go/v1/hub.pb.go +++ /dev/null @@ -1,200 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.35.2 -// protoc (unknown) -// source: v1/hub.proto - -package v1 - -import ( - _ "github.com/garethgeorge/backrest/gen/go/types" - _ "google.golang.org/genproto/googleapis/api/annotations" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - emptypb "google.golang.org/protobuf/types/known/emptypb" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type GetInstancesResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Instances []*Instance `protobuf:"bytes,1,rep,name=instances,proto3" json:"instances,omitempty"` -} - -func (x *GetInstancesResponse) Reset() { - *x = GetInstancesResponse{} - mi := &file_v1_hub_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetInstancesResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetInstancesResponse) ProtoMessage() {} - -func (x *GetInstancesResponse) ProtoReflect() protoreflect.Message { - mi := &file_v1_hub_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetInstancesResponse.ProtoReflect.Descriptor instead. -func (*GetInstancesResponse) Descriptor() ([]byte, []int) { - return file_v1_hub_proto_rawDescGZIP(), []int{0} -} - -func (x *GetInstancesResponse) GetInstances() []*Instance { - if x != nil { - return x.Instances - } - return nil -} - -type Instance struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` -} - -func (x *Instance) Reset() { - *x = Instance{} - mi := &file_v1_hub_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *Instance) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Instance) ProtoMessage() {} - -func (x *Instance) ProtoReflect() protoreflect.Message { - mi := &file_v1_hub_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Instance.ProtoReflect.Descriptor instead. -func (*Instance) Descriptor() ([]byte, []int) { - return file_v1_hub_proto_rawDescGZIP(), []int{1} -} - -func (x *Instance) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -var File_v1_hub_proto protoreflect.FileDescriptor - -var file_v1_hub_proto_rawDesc = []byte{ - 0x0a, 0x0c, 0x76, 0x31, 0x2f, 0x68, 0x75, 0x62, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, - 0x76, 0x31, 0x1a, 0x0f, 0x76, 0x31, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x1a, 0x0f, 0x76, 0x31, 0x2f, 0x72, 0x65, 0x73, 0x74, 0x69, 0x63, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x13, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x74, 0x79, 0x70, 0x65, 0x73, - 0x2f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, - 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x42, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x49, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x2a, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x52, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x1a, 0x0a, 0x08, 0x49, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x32, 0x49, 0x0a, 0x03, 0x48, 0x75, 0x62, 0x12, 0x42, - 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 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, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x49, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x62, 0x61, - 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_v1_hub_proto_rawDescOnce sync.Once - file_v1_hub_proto_rawDescData = file_v1_hub_proto_rawDesc -) - -func file_v1_hub_proto_rawDescGZIP() []byte { - file_v1_hub_proto_rawDescOnce.Do(func() { - file_v1_hub_proto_rawDescData = protoimpl.X.CompressGZIP(file_v1_hub_proto_rawDescData) - }) - return file_v1_hub_proto_rawDescData -} - -var file_v1_hub_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_v1_hub_proto_goTypes = []any{ - (*GetInstancesResponse)(nil), // 0: v1.GetInstancesResponse - (*Instance)(nil), // 1: v1.Instance - (*emptypb.Empty)(nil), // 2: google.protobuf.Empty -} -var file_v1_hub_proto_depIdxs = []int32{ - 1, // 0: v1.GetInstancesResponse.instances:type_name -> v1.Instance - 2, // 1: v1.Hub.GetInstances:input_type -> google.protobuf.Empty - 0, // 2: v1.Hub.GetInstances:output_type -> v1.GetInstancesResponse - 2, // [2:3] is the sub-list for method output_type - 1, // [1:2] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name -} - -func init() { file_v1_hub_proto_init() } -func file_v1_hub_proto_init() { - if File_v1_hub_proto != nil { - return - } - file_v1_config_proto_init() - file_v1_restic_proto_init() - file_v1_operations_proto_init() - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_v1_hub_proto_rawDesc, - NumEnums: 0, - NumMessages: 2, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_v1_hub_proto_goTypes, - DependencyIndexes: file_v1_hub_proto_depIdxs, - MessageInfos: file_v1_hub_proto_msgTypes, - }.Build() - File_v1_hub_proto = out.File - file_v1_hub_proto_rawDesc = nil - file_v1_hub_proto_goTypes = nil - file_v1_hub_proto_depIdxs = nil -} diff --git a/gen/go/v1/hub_grpc.pb.go b/gen/go/v1/hub_grpc.pb.go deleted file mode 100644 index 0c92aac..0000000 --- a/gen/go/v1/hub_grpc.pb.go +++ /dev/null @@ -1,124 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.5.1 -// - protoc (unknown) -// source: v1/hub.proto - -package v1 - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - emptypb "google.golang.org/protobuf/types/known/emptypb" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.64.0 or later. -const _ = grpc.SupportPackageIsVersion9 - -const ( - Hub_GetInstances_FullMethodName = "/v1.Hub/GetInstances" -) - -// HubClient is the client API for Hub service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type HubClient interface { - // GetInstances returns a list of all instances - GetInstances(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetInstancesResponse, error) -} - -type hubClient struct { - cc grpc.ClientConnInterface -} - -func NewHubClient(cc grpc.ClientConnInterface) HubClient { - return &hubClient{cc} -} - -func (c *hubClient) GetInstances(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetInstancesResponse, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(GetInstancesResponse) - err := c.cc.Invoke(ctx, Hub_GetInstances_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -// HubServer is the server API for Hub service. -// All implementations must embed UnimplementedHubServer -// for forward compatibility. -type HubServer interface { - // GetInstances returns a list of all instances - GetInstances(context.Context, *emptypb.Empty) (*GetInstancesResponse, error) - mustEmbedUnimplementedHubServer() -} - -// UnimplementedHubServer must be embedded to have -// forward compatible implementations. -// -// NOTE: this should be embedded by value instead of pointer to avoid a nil -// pointer dereference when methods are called. -type UnimplementedHubServer struct{} - -func (UnimplementedHubServer) GetInstances(context.Context, *emptypb.Empty) (*GetInstancesResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetInstances not implemented") -} -func (UnimplementedHubServer) mustEmbedUnimplementedHubServer() {} -func (UnimplementedHubServer) testEmbeddedByValue() {} - -// UnsafeHubServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to HubServer will -// result in compilation errors. -type UnsafeHubServer interface { - mustEmbedUnimplementedHubServer() -} - -func RegisterHubServer(s grpc.ServiceRegistrar, srv HubServer) { - // If the following call pancis, it indicates UnimplementedHubServer was - // embedded by pointer and is nil. This will cause panics if an - // unimplemented method is ever invoked, so we test this at initialization - // time to prevent it from happening at runtime later due to I/O. - if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { - t.testEmbeddedByValue() - } - s.RegisterService(&Hub_ServiceDesc, srv) -} - -func _Hub_GetInstances_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(emptypb.Empty) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HubServer).GetInstances(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Hub_GetInstances_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HubServer).GetInstances(ctx, req.(*emptypb.Empty)) - } - return interceptor(ctx, in, info, handler) -} - -// Hub_ServiceDesc is the grpc.ServiceDesc for Hub service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var Hub_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "v1.Hub", - HandlerType: (*HubServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "GetInstances", - Handler: _Hub_GetInstances_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "v1/hub.proto", -} diff --git a/gen/go/v1/operations.pb.go b/gen/go/v1/operations.pb.go index 5d236ea..3e13fb2 100644 --- a/gen/go/v1/operations.pb.go +++ b/gen/go/v1/operations.pb.go @@ -189,11 +189,20 @@ type Operation struct { unknownFields protoimpl.UnknownFields // required, primary ID of the operation. ID is sequential based on creation time of the operation. - Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + OriginalId int64 `protobuf:"varint,13,opt,name=original_id,json=originalId,proto3" json:"original_id,omitempty"` + // modno increments with each change to the operation. This supports easy diffing. + Modno int64 `protobuf:"varint,12,opt,name=modno,proto3" json:"modno,omitempty"` // flow id groups operations together, e.g. by an execution of a plan. - FlowId int64 `protobuf:"varint,10,opt,name=flow_id,json=flowId,proto3" json:"flow_id,omitempty"` // optional, flow id if associated with a flow - RepoId string `protobuf:"bytes,2,opt,name=repo_id,json=repoId,proto3" json:"repo_id,omitempty"` - PlanId string `protobuf:"bytes,3,opt,name=plan_id,json=planId,proto3" json:"plan_id,omitempty"` + // must be unique within the context of a repo. + FlowId int64 `protobuf:"varint,10,opt,name=flow_id,json=flowId,proto3" json:"flow_id,omitempty"` + OriginalFlowId int64 `protobuf:"varint,14,opt,name=original_flow_id,json=originalFlowId,proto3" json:"original_flow_id,omitempty"` + // repo id is a string identifier for the repo, and repo_guid is the globally unique ID of the repo. + RepoId string `protobuf:"bytes,2,opt,name=repo_id,json=repoId,proto3" json:"repo_id,omitempty"` + RepoGuid string `protobuf:"bytes,15,opt,name=repo_guid,json=repoGuid,proto3" json:"repo_guid,omitempty"` + // plan id e.g. a scheduled set of operations (or system) that created this operation. + PlanId string `protobuf:"bytes,3,opt,name=plan_id,json=planId,proto3" json:"plan_id,omitempty"` + // instance ID that created the operation InstanceId string `protobuf:"bytes,11,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` // optional snapshot id if associated with a snapshot. SnapshotId string `protobuf:"bytes,8,opt,name=snapshot_id,json=snapshotId,proto3" json:"snapshot_id,omitempty"` @@ -257,6 +266,20 @@ func (x *Operation) GetId() int64 { return 0 } +func (x *Operation) GetOriginalId() int64 { + if x != nil { + return x.OriginalId + } + return 0 +} + +func (x *Operation) GetModno() int64 { + if x != nil { + return x.Modno + } + return 0 +} + func (x *Operation) GetFlowId() int64 { if x != nil { return x.FlowId @@ -264,6 +287,13 @@ func (x *Operation) GetFlowId() int64 { return 0 } +func (x *Operation) GetOriginalFlowId() int64 { + if x != nil { + return x.OriginalFlowId + } + return 0 +} + func (x *Operation) GetRepoId() string { if x != nil { return x.RepoId @@ -271,6 +301,13 @@ func (x *Operation) GetRepoId() string { return "" } +func (x *Operation) GetRepoGuid() string { + if x != nil { + return x.RepoGuid + } + return "" +} + func (x *Operation) GetPlanId() string { if x != nil { return x.PlanId @@ -1087,169 +1124,177 @@ var file_v1_operations_proto_rawDesc = []byte{ 0x0a, 0x0d, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe5, - 0x07, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, - 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x66, - 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x17, - 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, - 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, - 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x4f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x74, - 0x69, 0x6d, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0f, 0x75, 0x6e, 0x69, 0x78, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72, - 0x74, 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x10, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x74, 0x69, 0x6d, 0x65, - 0x5f, 0x65, 0x6e, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x75, - 0x6e, 0x69, 0x78, 0x54, 0x69, 0x6d, 0x65, 0x45, 0x6e, 0x64, 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x0f, - 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, - 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x18, - 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x12, 0x40, 0x0a, - 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, - 0x70, 0x18, 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x48, 0x00, 0x52, 0x0f, - 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, - 0x56, 0x0a, 0x18, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x64, - 0x65, 0x78, 0x5f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x18, 0x65, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x48, 0x00, 0x52, - 0x16, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, - 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x40, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x66, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x18, 0x66, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x12, 0x3d, 0x0a, 0x0f, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x75, 0x6e, 0x65, 0x18, 0x67, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x12, 0x43, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x68, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x48, 0x00, 0x52, 0x10, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x3d, 0x0a, - 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, - 0x18, 0x69, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x6f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x44, 0x0a, 0x12, - 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x68, 0x6f, - 0x6f, 0x6b, 0x18, 0x6a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x48, 0x6f, 0x6f, 0x6b, 0x48, 0x00, - 0x52, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x48, 0x6f, - 0x6f, 0x6b, 0x12, 0x3d, 0x0a, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x6b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, - 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x48, - 0x00, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x12, 0x4d, 0x0a, 0x15, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, - 0x75, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x6c, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x13, 0x6f, 0x70, 0x65, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x42, 0x04, 0x0a, 0x02, 0x6f, 0x70, 0x22, 0x93, 0x02, 0x0a, 0x0e, 0x4f, 0x70, 0x65, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2d, 0x0a, 0x0a, 0x6b, 0x65, 0x65, - 0x70, 0x5f, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, - 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x48, 0x00, 0x52, 0x09, 0x6b, - 0x65, 0x65, 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x42, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x42, 0x0a, 0x12, - 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x11, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x12, 0x41, 0x0a, 0x12, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, - 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, - 0x52, 0x11, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x7c, 0x0a, 0x0f, - 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, - 0x38, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, - 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x6c, - 0x61, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2f, 0x0a, 0x06, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x42, - 0x61, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0x60, 0x0a, 0x16, 0x4f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, - 0x69, 0x63, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x08, 0x73, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x22, 0x6a, 0x0a, 0x0f, - 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x12, - 0x2a, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, 0x53, 0x6e, 0x61, 0x70, 0x73, - 0x68, 0x6f, 0x74, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x12, 0x2b, 0x0a, 0x06, 0x70, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x51, 0x0a, 0x0e, 0x4f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x12, 0x1a, 0x0a, 0x06, 0x6f, 0x75, - 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, - 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, - 0x5f, 0x6c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, - 0x75, 0x74, 0x70, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x22, 0x51, 0x0a, 0x0e, 0x4f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1a, 0x0a, - 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, - 0x01, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x22, 0x80, - 0x01, 0x0a, 0x13, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x43, - 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x72, 0x65, - 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4c, - 0x6f, 0x67, 0x72, 0x65, 0x66, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, - 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x79, 0x74, 0x65, - 0x73, 0x22, 0x79, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, - 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, - 0x74, 0x12, 0x39, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, - 0x6f, 0x72, 0x65, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x0a, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x35, 0x0a, 0x0e, - 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x23, - 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, - 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, - 0x61, 0x74, 0x73, 0x22, 0x9a, 0x01, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x75, 0x6e, 0x48, 0x6f, 0x6f, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, - 0x6e, 0x74, 0x5f, 0x6f, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x61, 0x72, - 0x65, 0x6e, 0x74, 0x4f, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x12, 0x30, - 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x43, 0x6f, 0x6e, 0x64, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x2a, 0x60, 0x0a, 0x12, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, - 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x56, 0x45, - 0x4e, 0x54, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, - 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, - 0x11, 0x0a, 0x0d, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, - 0x10, 0x03, 0x2a, 0xc2, 0x01, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, - 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, - 0x41, 0x54, 0x55, 0x53, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x15, - 0x0a, 0x11, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x49, 0x4e, 0x50, 0x52, 0x4f, 0x47, 0x52, - 0x45, 0x53, 0x53, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, - 0x53, 0x55, 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, - 0x54, 0x55, 0x53, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x07, 0x12, 0x10, 0x0a, - 0x0c, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, - 0x1b, 0x0a, 0x17, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, - 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x05, 0x12, 0x19, 0x0a, 0x15, - 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x43, 0x41, 0x4e, 0x43, - 0x45, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x06, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, - 0x67, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, - 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x6e, 0x52, 0x0a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe3, + 0x08, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, + 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0a, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x6d, 0x6f, 0x64, 0x6e, 0x6f, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6d, 0x6f, + 0x64, 0x6e, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, + 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, + 0x46, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, + 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x67, 0x75, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x47, 0x75, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, + 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, + 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, + 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x2b, 0x0a, 0x12, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x6d, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x0f, 0x75, 0x6e, 0x69, 0x78, 0x54, 0x69, 0x6d, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x4d, + 0x73, 0x12, 0x27, 0x0a, 0x10, 0x75, 0x6e, 0x69, 0x78, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x65, + 0x6e, 0x64, 0x5f, 0x6d, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x75, 0x6e, 0x69, + 0x78, 0x54, 0x69, 0x6d, 0x65, 0x45, 0x6e, 0x64, 0x4d, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x69, + 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x6c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x12, 0x40, 0x0a, 0x10, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x18, + 0x64, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x48, 0x00, 0x52, 0x0f, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x56, 0x0a, + 0x18, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x5f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x18, 0x65, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x48, 0x00, 0x52, 0x16, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x40, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x66, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x18, 0x66, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6f, + 0x72, 0x67, 0x65, 0x74, 0x48, 0x00, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x12, 0x3d, 0x0a, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x75, 0x6e, 0x65, 0x18, 0x67, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, + 0x72, 0x75, 0x6e, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x12, 0x43, 0x0a, 0x11, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x68, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x14, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x48, 0x00, 0x52, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x3d, 0x0a, 0x0f, 0x6f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x69, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x44, 0x0a, 0x12, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x75, 0x6e, 0x5f, 0x68, 0x6f, 0x6f, 0x6b, + 0x18, 0x6a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x48, 0x6f, 0x6f, 0x6b, 0x48, 0x00, 0x52, 0x10, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x48, 0x6f, 0x6f, 0x6b, + 0x12, 0x3d, 0x0a, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x68, + 0x65, 0x63, 0x6b, 0x18, 0x6b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x48, 0x00, 0x52, + 0x0e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, + 0x4d, 0x0a, 0x15, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x75, 0x6e, + 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x6c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, + 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, + 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x48, 0x00, 0x52, 0x13, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x42, 0x04, + 0x0a, 0x02, 0x6f, 0x70, 0x22, 0x93, 0x02, 0x0a, 0x0e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x2d, 0x0a, 0x0a, 0x6b, 0x65, 0x65, 0x70, 0x5f, + 0x61, 0x6c, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x48, 0x00, 0x52, 0x09, 0x6b, 0x65, 0x65, + 0x70, 0x41, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x42, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, + 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x42, 0x0a, 0x12, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x11, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x41, + 0x0a, 0x12, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x11, + 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x7c, 0x0a, 0x0f, 0x4f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x38, 0x0a, + 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x50, 0x72, + 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x6c, 0x61, 0x73, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2f, 0x0a, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, + 0x52, 0x06, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x22, 0x60, 0x0a, 0x16, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, + 0x6f, 0x74, 0x12, 0x2e, 0x0a, 0x08, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, + 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x08, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, + 0x6f, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x6f, 0x74, 0x22, 0x6a, 0x0a, 0x0f, 0x4f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x12, 0x2a, 0x0a, + 0x06, 0x66, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, + 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x69, 0x63, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, + 0x74, 0x52, 0x06, 0x66, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x12, 0x2b, 0x0a, 0x06, 0x70, 0x6f, 0x6c, + 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x65, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x06, + 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x22, 0x51, 0x0a, 0x0e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x75, 0x6e, 0x65, 0x12, 0x1a, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x06, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x6c, + 0x6f, 0x67, 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x22, 0x51, 0x0a, 0x0e, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1a, 0x0a, 0x06, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, + 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x22, 0x80, 0x01, 0x0a, + 0x13, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x6d, + 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x23, + 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4c, 0x6f, 0x67, + 0x72, 0x65, 0x66, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x73, 0x69, + 0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, + 0x79, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, + 0x39, 0x0a, 0x0b, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, + 0x6c, 0x61, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x35, 0x0a, 0x0e, 0x4f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x05, + 0x73, 0x74, 0x61, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x76, 0x31, + 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, + 0x73, 0x22, 0x9a, 0x01, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x75, 0x6e, 0x48, 0x6f, 0x6f, 0x6b, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, + 0x5f, 0x6f, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x70, 0x61, 0x72, 0x65, 0x6e, + 0x74, 0x4f, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x5f, 0x6c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4c, 0x6f, 0x67, 0x72, 0x65, 0x66, 0x12, 0x30, 0x0a, 0x09, + 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x6f, 0x6b, 0x2e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x60, + 0x0a, 0x12, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x55, 0x4e, + 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x56, 0x45, 0x4e, 0x54, + 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x56, + 0x45, 0x4e, 0x54, 0x5f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, + 0x0d, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x03, + 0x2a, 0xc2, 0x01, 0x0a, 0x0f, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, + 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, + 0x55, 0x53, 0x5f, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x15, 0x0a, 0x11, + 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x49, 0x4e, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x45, 0x53, + 0x53, 0x10, 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x55, + 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x03, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x54, 0x41, 0x54, 0x55, + 0x53, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x07, 0x12, 0x10, 0x0a, 0x0c, 0x53, + 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x1b, 0x0a, + 0x17, 0x53, 0x54, 0x41, 0x54, 0x55, 0x53, 0x5f, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x5f, 0x43, + 0x41, 0x4e, 0x43, 0x45, 0x4c, 0x4c, 0x45, 0x44, 0x10, 0x05, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x54, + 0x41, 0x54, 0x55, 0x53, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, + 0x4c, 0x45, 0x44, 0x10, 0x06, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, + 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, + 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/gen/go/v1/service.pb.go b/gen/go/v1/service.pb.go index 3087b82..38dcfed 100644 --- a/gen/go/v1/service.pb.go +++ b/gen/go/v1/service.pb.go @@ -88,10 +88,11 @@ type OpSelector struct { unknownFields protoimpl.UnknownFields Ids []int64 `protobuf:"varint,1,rep,packed,name=ids,proto3" json:"ids,omitempty"` - RepoId string `protobuf:"bytes,2,opt,name=repo_id,json=repoId,proto3" json:"repo_id,omitempty"` - PlanId string `protobuf:"bytes,3,opt,name=plan_id,json=planId,proto3" json:"plan_id,omitempty"` - SnapshotId string `protobuf:"bytes,4,opt,name=snapshot_id,json=snapshotId,proto3" json:"snapshot_id,omitempty"` - FlowId int64 `protobuf:"varint,5,opt,name=flow_id,json=flowId,proto3" json:"flow_id,omitempty"` + InstanceId *string `protobuf:"bytes,6,opt,name=instance_id,json=instanceId,proto3,oneof" json:"instance_id,omitempty"` + RepoGuid *string `protobuf:"bytes,7,opt,name=repo_guid,json=repoGuid,proto3,oneof" json:"repo_guid,omitempty"` + PlanId *string `protobuf:"bytes,3,opt,name=plan_id,json=planId,proto3,oneof" json:"plan_id,omitempty"` + SnapshotId *string `protobuf:"bytes,4,opt,name=snapshot_id,json=snapshotId,proto3,oneof" json:"snapshot_id,omitempty"` + FlowId *int64 `protobuf:"varint,5,opt,name=flow_id,json=flowId,proto3,oneof" json:"flow_id,omitempty"` } func (x *OpSelector) Reset() { @@ -131,30 +132,37 @@ func (x *OpSelector) GetIds() []int64 { return nil } -func (x *OpSelector) GetRepoId() string { - if x != nil { - return x.RepoId +func (x *OpSelector) GetInstanceId() string { + if x != nil && x.InstanceId != nil { + return *x.InstanceId + } + return "" +} + +func (x *OpSelector) GetRepoGuid() string { + if x != nil && x.RepoGuid != nil { + return *x.RepoGuid } return "" } func (x *OpSelector) GetPlanId() string { - if x != nil { - return x.PlanId + if x != nil && x.PlanId != nil { + return *x.PlanId } return "" } func (x *OpSelector) GetSnapshotId() string { - if x != nil { - return x.SnapshotId + if x != nil && x.SnapshotId != nil { + return *x.SnapshotId } return "" } func (x *OpSelector) GetFlowId() int64 { - if x != nil { - return x.FlowId + if x != nil && x.FlowId != nil { + return *x.FlowId } return 0 } @@ -1122,232 +1130,240 @@ var file_v1_service_proto_rawDesc = []byte{ 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, 0x8a, 0x01, 0x0a, 0x0a, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8e, 0x02, 0x0a, 0x0a, 0x4f, 0x70, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x69, 0x64, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x03, 0x69, 0x64, 0x73, 0x12, 0x17, 0x0a, 0x07, - 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x49, 0x64, 0x12, - 0x17, 0x0a, 0x07, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x06, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x22, 0xce, 0x01, 0x0a, 0x11, 0x44, 0x6f, 0x52, - 0x65, 0x70, 0x6f, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, - 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x74, 0x61, 0x73, 0x6b, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x6f, 0x52, 0x65, 0x70, - 0x6f, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x54, 0x61, 0x73, - 0x6b, 0x52, 0x04, 0x74, 0x61, 0x73, 0x6b, 0x22, 0x70, 0x0a, 0x04, 0x54, 0x61, 0x73, 0x6b, 0x12, - 0x0d, 0x0a, 0x09, 0x54, 0x41, 0x53, 0x4b, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x18, - 0x0a, 0x14, 0x54, 0x41, 0x53, 0x4b, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, 0x5f, 0x53, 0x4e, 0x41, - 0x50, 0x53, 0x48, 0x4f, 0x54, 0x53, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x41, 0x53, 0x4b, - 0x5f, 0x50, 0x52, 0x55, 0x4e, 0x45, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x41, 0x53, 0x4b, - 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x41, 0x53, 0x4b, - 0x5f, 0x53, 0x54, 0x41, 0x54, 0x53, 0x10, 0x04, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x41, 0x53, 0x4b, - 0x5f, 0x55, 0x4e, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x05, 0x22, 0x62, 0x0a, 0x13, 0x43, 0x6c, 0x65, - 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x2a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, - 0x6f, 0x72, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, - 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0a, 0x6f, 0x6e, 0x6c, 0x79, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x22, 0x62, 0x0a, - 0x0d, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, - 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, - 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x49, - 0x64, 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, - 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, - 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x22, 0x59, 0x0a, 0x14, 0x47, - 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x53, 0x65, 0x6c, - 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, - 0x15, 0x0a, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x05, 0x6c, 0x61, 0x73, 0x74, 0x4e, 0x22, 0x97, 0x01, 0x0a, 0x16, 0x52, 0x65, 0x73, 0x74, 0x6f, - 0x72, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, - 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, - 0x6f, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, - 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, - 0x6f, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x22, 0x68, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, - 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, - 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, - 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, - 0x73, 0x68, 0x6f, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x56, 0x0a, 0x19, 0x4c, 0x69, - 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x25, 0x0a, 0x07, 0x65, - 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, - 0x65, 0x73, 0x22, 0x22, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x22, 0xd3, 0x01, 0x0a, 0x07, 0x4c, 0x73, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, - 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x10, - 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x75, 0x69, 0x64, - 0x12, 0x10, 0x0a, 0x03, 0x67, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x67, - 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x74, - 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x74, 0x69, 0x6d, 0x65, - 0x12, 0x14, 0x0a, 0x05, 0x61, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x61, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x74, 0x69, 0x6d, 0x65, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x46, 0x0a, 0x11, - 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, - 0x6d, 0x61, 0x6e, 0x64, 0x22, 0xea, 0x07, 0x0a, 0x18, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, - 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x4b, 0x0a, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, - 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, - 0x0d, 0x72, 0x65, 0x70, 0x6f, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, 0x65, 0x73, 0x12, 0x4b, - 0x0a, 0x0e, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, 0x65, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x6d, 0x6d, - 0x61, 0x72, 0x79, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x0d, 0x70, 0x6c, - 0x61, 0x6e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1b, 0x0a, 0x09, - 0x64, 0x61, 0x74, 0x61, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x64, 0x61, 0x74, 0x61, 0x50, 0x61, 0x74, 0x68, 0x1a, 0xba, 0x04, 0x0a, 0x07, 0x53, 0x75, - 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, - 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x46, 0x61, 0x69, - 0x6c, 0x65, 0x64, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x12, 0x3d, 0x0a, 0x1b, 0x62, 0x61, 0x63, - 0x6b, 0x75, 0x70, 0x73, 0x5f, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x61, 0x73, - 0x74, 0x5f, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, - 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x57, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x4c, 0x61, - 0x73, 0x74, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x12, 0x3d, 0x0a, 0x1b, 0x62, 0x61, 0x63, 0x6b, - 0x75, 0x70, 0x73, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, 0x61, 0x73, 0x74, - 0x5f, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x18, 0x62, - 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x61, 0x73, - 0x74, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x12, 0x39, 0x0a, 0x19, 0x62, 0x79, 0x74, 0x65, 0x73, - 0x5f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x33, 0x30, - 0x64, 0x61, 0x79, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x16, 0x62, 0x79, 0x74, 0x65, - 0x73, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x4c, 0x61, 0x73, 0x74, 0x33, 0x30, 0x64, 0x61, - 0x79, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x65, - 0x64, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x14, 0x62, 0x79, 0x74, 0x65, 0x73, 0x41, 0x64, 0x64, 0x65, 0x64, 0x4c, - 0x61, 0x73, 0x74, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x5f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, - 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x63, 0x61, 0x6e, - 0x6e, 0x65, 0x64, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x62, - 0x79, 0x74, 0x65, 0x73, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x41, 0x76, 0x67, 0x12, 0x26, - 0x0a, 0x0f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x76, - 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x62, 0x79, 0x74, 0x65, 0x73, 0x41, 0x64, - 0x64, 0x65, 0x64, 0x41, 0x76, 0x67, 0x12, 0x2d, 0x0a, 0x13, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x62, - 0x61, 0x63, 0x6b, 0x75, 0x70, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x73, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x10, 0x6e, 0x65, 0x78, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x54, - 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x4f, 0x0a, 0x0e, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x5f, - 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x03, 0x69, 0x64, 0x73, 0x12, 0x24, 0x0a, 0x0b, + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x88, + 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x67, 0x75, 0x69, 0x64, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x08, 0x72, 0x65, 0x70, 0x6f, 0x47, 0x75, 0x69, + 0x64, 0x88, 0x01, 0x01, 0x12, 0x1c, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x88, + 0x01, 0x01, 0x12, 0x24, 0x0a, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x1c, 0x0a, 0x07, 0x66, 0x6c, 0x6f, 0x77, + 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x48, 0x04, 0x52, 0x06, 0x66, 0x6c, 0x6f, + 0x77, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x5f, + 0x67, 0x75, 0x69, 0x64, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, + 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x69, 0x64, + 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x22, 0xce, 0x01, 0x0a, + 0x11, 0x44, 0x6f, 0x52, 0x65, 0x70, 0x6f, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x74, + 0x61, 0x73, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x6f, 0x52, 0x65, 0x70, 0x6f, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x04, 0x74, 0x61, 0x73, 0x6b, 0x22, 0x70, 0x0a, 0x04, 0x54, + 0x61, 0x73, 0x6b, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x41, 0x53, 0x4b, 0x5f, 0x4e, 0x4f, 0x4e, 0x45, + 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x41, 0x53, 0x4b, 0x5f, 0x49, 0x4e, 0x44, 0x45, 0x58, + 0x5f, 0x53, 0x4e, 0x41, 0x50, 0x53, 0x48, 0x4f, 0x54, 0x53, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, + 0x54, 0x41, 0x53, 0x4b, 0x5f, 0x50, 0x52, 0x55, 0x4e, 0x45, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, + 0x54, 0x41, 0x53, 0x4b, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, + 0x54, 0x41, 0x53, 0x4b, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x53, 0x10, 0x04, 0x12, 0x0f, 0x0a, 0x0b, + 0x54, 0x41, 0x53, 0x4b, 0x5f, 0x55, 0x4e, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x05, 0x22, 0x62, 0x0a, + 0x13, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x53, 0x65, + 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x6e, 0x6c, 0x79, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x6f, 0x6e, 0x6c, 0x79, 0x46, 0x61, 0x69, 0x6c, 0x65, + 0x64, 0x22, 0x62, 0x0a, 0x0d, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, + 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, + 0x61, 0x6e, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x49, 0x64, 0x22, 0x48, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, + 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x22, + 0x59, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x76, 0x31, 0x2e, 0x4f, + 0x70, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x12, 0x15, 0x0a, 0x06, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x61, 0x73, 0x74, 0x4e, 0x22, 0x97, 0x01, 0x0a, 0x16, 0x52, + 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x17, + 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x22, 0x68, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, + 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, + 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x22, 0x56, + 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, + 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, + 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, + 0x25, 0x0a, 0x07, 0x65, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x0b, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x65, + 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x22, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, 0x44, 0x61, 0x74, + 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x22, 0xd3, 0x01, 0x0a, 0x07, 0x4c, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, + 0x74, 0x68, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x67, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x03, 0x67, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6d, 0x6f, + 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, + 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x61, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x74, 0x69, 0x6d, 0x65, + 0x22, 0x46, 0x0a, 0x11, 0x52, 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x18, + 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x22, 0xea, 0x07, 0x0a, 0x18, 0x53, 0x75, 0x6d, + 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0e, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x73, 0x75, + 0x6d, 0x6d, 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, - 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x42, 0x61, 0x63, 0x6b, - 0x75, 0x70, 0x43, 0x68, 0x61, 0x72, 0x74, 0x52, 0x0d, 0x72, 0x65, 0x63, 0x65, 0x6e, 0x74, 0x42, - 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x1a, 0xb8, 0x01, 0x0a, 0x0b, 0x42, 0x61, 0x63, 0x6b, 0x75, - 0x70, 0x43, 0x68, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x06, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x12, - 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x6d, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x4d, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, - 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x4d, 0x73, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x65, 0x64, 0x18, - 0x05, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, 0x41, 0x64, 0x64, 0x65, - 0x64, 0x32, 0xf7, 0x08, 0x0a, 0x08, 0x42, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x12, 0x31, - 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, - 0x00, 0x12, 0x25, 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, 0x00, 0x12, 0x2f, 0x0a, 0x0f, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x52, 0x65, 0x70, 0x6f, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x12, 0x08, 0x2e, 0x76, 0x31, - 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x1a, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x42, 0x6f, - 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x21, 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, 0x00, 0x12, 0x44, 0x0a, 0x12, - 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, - 0x74, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x76, 0x31, 0x2e, - 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x00, - 0x30, 0x01, 0x12, 0x3e, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, - 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x73, 0x74, - 0x22, 0x00, 0x12, 0x43, 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, 0x00, 0x12, 0x52, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x53, - 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, - 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x76, 0x31, 0x2e, - 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, 0x6c, 0x65, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x36, 0x0a, 0x06, 0x42, - 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, - 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0a, 0x44, 0x6f, 0x52, 0x65, 0x70, 0x6f, 0x54, 0x61, 0x73, - 0x6b, 0x12, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x6f, 0x52, 0x65, 0x70, 0x6f, 0x54, 0x61, 0x73, - 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x22, 0x00, 0x12, 0x35, 0x0a, 0x06, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x12, 0x11, 0x2e, 0x76, - 0x31, 0x2e, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x75, 0x6d, 0x6d, + 0x61, 0x72, 0x79, 0x52, 0x0d, 0x72, 0x65, 0x70, 0x6f, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, + 0x65, 0x73, 0x12, 0x4b, 0x0a, 0x0e, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x52, 0x0d, 0x70, 0x6c, 0x61, 0x6e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x69, 0x65, 0x73, 0x12, + 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x61, 0x74, 0x68, + 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x50, 0x61, 0x74, 0x68, 0x1a, 0xba, 0x04, + 0x0a, 0x07, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x62, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x73, 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x5f, 0x33, 0x30, 0x64, 0x61, + 0x79, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, + 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x12, 0x3d, 0x0a, + 0x1b, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x5f, 0x77, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, + 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x18, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x57, 0x61, 0x72, 0x6e, 0x69, + 0x6e, 0x67, 0x4c, 0x61, 0x73, 0x74, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x12, 0x3d, 0x0a, 0x1b, + 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x5f, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, + 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x18, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x53, 0x75, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x4c, 0x61, 0x73, 0x74, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x12, 0x39, 0x0a, 0x19, 0x62, + 0x79, 0x74, 0x65, 0x73, 0x5f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x6c, 0x61, 0x73, + 0x74, 0x5f, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x16, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x4c, 0x61, 0x73, 0x74, + 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x12, 0x35, 0x0a, 0x17, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, + 0x61, 0x64, 0x64, 0x65, 0x64, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x33, 0x30, 0x64, 0x61, 0x79, + 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x62, 0x79, 0x74, 0x65, 0x73, 0x41, 0x64, + 0x64, 0x65, 0x64, 0x4c, 0x61, 0x73, 0x74, 0x33, 0x30, 0x64, 0x61, 0x79, 0x73, 0x12, 0x27, 0x0a, + 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, + 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x0f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x64, 0x41, + 0x76, 0x67, 0x12, 0x26, 0x0a, 0x0f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x61, 0x64, 0x64, 0x65, + 0x64, 0x5f, 0x61, 0x76, 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x62, 0x79, 0x74, + 0x65, 0x73, 0x41, 0x64, 0x64, 0x65, 0x64, 0x41, 0x76, 0x67, 0x12, 0x2d, 0x0a, 0x13, 0x6e, 0x65, + 0x78, 0x74, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, + 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6e, 0x65, 0x78, 0x74, 0x42, 0x61, 0x63, + 0x6b, 0x75, 0x70, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x73, 0x12, 0x4f, 0x0a, 0x0e, 0x72, 0x65, 0x63, + 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x28, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, + 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, + 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x43, 0x68, 0x61, 0x72, 0x74, 0x52, 0x0d, 0x72, 0x65, 0x63, + 0x65, 0x6e, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x1a, 0xb8, 0x01, 0x0a, 0x0b, 0x42, + 0x61, 0x63, 0x6b, 0x75, 0x70, 0x43, 0x68, 0x61, 0x72, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x6c, + 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x03, 0x52, 0x06, 0x66, 0x6c, 0x6f, + 0x77, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x5f, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x4d, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x6d, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x64, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x73, 0x12, 0x2b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x61, 0x64, + 0x64, 0x65, 0x64, 0x18, 0x05, 0x20, 0x03, 0x28, 0x03, 0x52, 0x0a, 0x62, 0x79, 0x74, 0x65, 0x73, + 0x41, 0x64, 0x64, 0x65, 0x64, 0x32, 0xf7, 0x08, 0x0a, 0x08, 0x42, 0x61, 0x63, 0x6b, 0x72, 0x65, + 0x73, 0x74, 0x12, 0x31, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x07, 0x52, 0x65, 0x73, - 0x74, 0x6f, 0x72, 0x65, 0x12, 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, - 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x06, 0x43, 0x61, - 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, - 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, - 0x00, 0x12, 0x34, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x12, 0x2e, 0x76, - 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x0a, 0x52, 0x75, 0x6e, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x75, 0x6e, 0x43, 0x6f, - 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x74, - 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, - 0x00, 0x12, 0x39, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, - 0x55, 0x52, 0x4c, 0x12, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x36, - 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, - 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0c, - 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x17, 0x2e, 0x76, - 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, - 0x3b, 0x0a, 0x10, 0x50, 0x61, 0x74, 0x68, 0x41, 0x75, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, - 0x65, 0x74, 0x65, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, - 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, - 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x13, - 0x47, 0x65, 0x74, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, - 0x61, 0x72, 0x64, 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, 0x1c, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2c, 0x5a, 0x2a, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, - 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x2f, - 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0a, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x22, 0x00, 0x12, 0x25, 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, 0x00, 0x12, 0x2f, 0x0a, 0x0f, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x70, 0x6f, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x12, + 0x08, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x1a, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x21, 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, 0x00, + 0x12, 0x44, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, + 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x22, 0x00, 0x30, 0x01, 0x12, 0x3e, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x18, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, + 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, 0x12, 0x43, 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, 0x00, 0x12, 0x52, 0x0a, 0x11, 0x4c, + 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, + 0x12, 0x1c, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, + 0x6f, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, + 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, + 0x46, 0x69, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x36, 0x0a, 0x06, 0x42, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0a, 0x44, 0x6f, 0x52, 0x65, 0x70, + 0x6f, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x6f, 0x52, 0x65, 0x70, + 0x6f, 0x54, 0x61, 0x73, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x06, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, + 0x12, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x3f, 0x0a, + 0x07, 0x52, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x1a, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x35, + 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x12, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, + 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, + 0x12, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x42, 0x79, 0x74, + 0x65, 0x73, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x38, 0x0a, 0x0a, 0x52, + 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x15, 0x2e, 0x76, 0x31, 0x2e, 0x52, + 0x75, 0x6e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x44, 0x6f, 0x77, 0x6e, + 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x12, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x12, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x00, + 0x12, 0x41, 0x0a, 0x0c, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, + 0x12, 0x17, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, + 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x10, 0x50, 0x61, 0x74, 0x68, 0x41, 0x75, 0x74, 0x6f, 0x63, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x11, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x00, + 0x12, 0x4d, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, + 0x73, 0x68, 0x62, 0x6f, 0x61, 0x72, 0x64, 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, + 0x1c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x44, 0x61, 0x73, 0x68, + 0x62, 0x6f, 0x61, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, + 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, + 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, + 0x65, 0x73, 0x74, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1456,6 +1472,7 @@ func file_v1_service_proto_init() { file_v1_config_proto_init() file_v1_restic_proto_init() file_v1_operations_proto_init() + file_v1_service_proto_msgTypes[0].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/gen/go/v1/syncservice.pb.go b/gen/go/v1/syncservice.pb.go new file mode 100644 index 0000000..71c8367 --- /dev/null +++ b/gen/go/v1/syncservice.pb.go @@ -0,0 +1,1140 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.2 +// protoc (unknown) +// source: v1/syncservice.proto + +package v1 + +import ( + _ "github.com/garethgeorge/backrest/gen/go/types" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SyncConnectionState int32 + +const ( + SyncConnectionState_CONNECTION_STATE_UNKNOWN SyncConnectionState = 0 + SyncConnectionState_CONNECTION_STATE_PENDING SyncConnectionState = 1 + SyncConnectionState_CONNECTION_STATE_CONNECTED SyncConnectionState = 2 + SyncConnectionState_CONNECTION_STATE_DISCONNECTED SyncConnectionState = 3 + SyncConnectionState_CONNECTION_STATE_RETRY_WAIT SyncConnectionState = 4 + SyncConnectionState_CONNECTION_STATE_ERROR_AUTH SyncConnectionState = 10 + SyncConnectionState_CONNECTION_STATE_ERROR_PROTOCOL SyncConnectionState = 11 +) + +// Enum value maps for SyncConnectionState. +var ( + SyncConnectionState_name = map[int32]string{ + 0: "CONNECTION_STATE_UNKNOWN", + 1: "CONNECTION_STATE_PENDING", + 2: "CONNECTION_STATE_CONNECTED", + 3: "CONNECTION_STATE_DISCONNECTED", + 4: "CONNECTION_STATE_RETRY_WAIT", + 10: "CONNECTION_STATE_ERROR_AUTH", + 11: "CONNECTION_STATE_ERROR_PROTOCOL", + } + SyncConnectionState_value = map[string]int32{ + "CONNECTION_STATE_UNKNOWN": 0, + "CONNECTION_STATE_PENDING": 1, + "CONNECTION_STATE_CONNECTED": 2, + "CONNECTION_STATE_DISCONNECTED": 3, + "CONNECTION_STATE_RETRY_WAIT": 4, + "CONNECTION_STATE_ERROR_AUTH": 10, + "CONNECTION_STATE_ERROR_PROTOCOL": 11, + } +) + +func (x SyncConnectionState) Enum() *SyncConnectionState { + p := new(SyncConnectionState) + *p = x + return p +} + +func (x SyncConnectionState) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SyncConnectionState) Descriptor() protoreflect.EnumDescriptor { + return file_v1_syncservice_proto_enumTypes[0].Descriptor() +} + +func (SyncConnectionState) Type() protoreflect.EnumType { + return &file_v1_syncservice_proto_enumTypes[0] +} + +func (x SyncConnectionState) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SyncConnectionState.Descriptor instead. +func (SyncConnectionState) EnumDescriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{0} +} + +type SyncStreamItem_RepoConnectionState int32 + +const ( + SyncStreamItem_CONNECTION_STATE_UNKNOWN SyncStreamItem_RepoConnectionState = 0 + SyncStreamItem_CONNECTION_STATE_PENDING SyncStreamItem_RepoConnectionState = 1 // queried, response not yet received. + SyncStreamItem_CONNECTION_STATE_CONNECTED SyncStreamItem_RepoConnectionState = 2 + SyncStreamItem_CONNECTION_STATE_UNAUTHORIZED SyncStreamItem_RepoConnectionState = 3 + SyncStreamItem_CONNECTION_STATE_NOT_FOUND SyncStreamItem_RepoConnectionState = 4 +) + +// Enum value maps for SyncStreamItem_RepoConnectionState. +var ( + SyncStreamItem_RepoConnectionState_name = map[int32]string{ + 0: "CONNECTION_STATE_UNKNOWN", + 1: "CONNECTION_STATE_PENDING", + 2: "CONNECTION_STATE_CONNECTED", + 3: "CONNECTION_STATE_UNAUTHORIZED", + 4: "CONNECTION_STATE_NOT_FOUND", + } + SyncStreamItem_RepoConnectionState_value = map[string]int32{ + "CONNECTION_STATE_UNKNOWN": 0, + "CONNECTION_STATE_PENDING": 1, + "CONNECTION_STATE_CONNECTED": 2, + "CONNECTION_STATE_UNAUTHORIZED": 3, + "CONNECTION_STATE_NOT_FOUND": 4, + } +) + +func (x SyncStreamItem_RepoConnectionState) Enum() *SyncStreamItem_RepoConnectionState { + p := new(SyncStreamItem_RepoConnectionState) + *p = x + return p +} + +func (x SyncStreamItem_RepoConnectionState) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SyncStreamItem_RepoConnectionState) Descriptor() protoreflect.EnumDescriptor { + return file_v1_syncservice_proto_enumTypes[1].Descriptor() +} + +func (SyncStreamItem_RepoConnectionState) Type() protoreflect.EnumType { + return &file_v1_syncservice_proto_enumTypes[1] +} + +func (x SyncStreamItem_RepoConnectionState) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SyncStreamItem_RepoConnectionState.Descriptor instead. +func (SyncStreamItem_RepoConnectionState) EnumDescriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{1, 0} +} + +type GetRemoteReposResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Repos []*GetRemoteReposResponse_RemoteRepoMetadata `protobuf:"bytes,1,rep,name=repos,proto3" json:"repos,omitempty"` +} + +func (x *GetRemoteReposResponse) Reset() { + *x = GetRemoteReposResponse{} + mi := &file_v1_syncservice_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRemoteReposResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRemoteReposResponse) ProtoMessage() {} + +func (x *GetRemoteReposResponse) ProtoReflect() protoreflect.Message { + mi := &file_v1_syncservice_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRemoteReposResponse.ProtoReflect.Descriptor instead. +func (*GetRemoteReposResponse) Descriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{0} +} + +func (x *GetRemoteReposResponse) GetRepos() []*GetRemoteReposResponse_RemoteRepoMetadata { + if x != nil { + return x.Repos + } + return nil +} + +type SyncStreamItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Action: + // + // *SyncStreamItem_SignedMessage + // *SyncStreamItem_Handshake + // *SyncStreamItem_DiffOperations + // *SyncStreamItem_SendOperations + // *SyncStreamItem_SendConfig + // *SyncStreamItem_EstablishSharedSecret + // *SyncStreamItem_Throttle + Action isSyncStreamItem_Action `protobuf_oneof:"action"` +} + +func (x *SyncStreamItem) Reset() { + *x = SyncStreamItem{} + mi := &file_v1_syncservice_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncStreamItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncStreamItem) ProtoMessage() {} + +func (x *SyncStreamItem) ProtoReflect() protoreflect.Message { + mi := &file_v1_syncservice_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncStreamItem.ProtoReflect.Descriptor instead. +func (*SyncStreamItem) Descriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{1} +} + +func (m *SyncStreamItem) GetAction() isSyncStreamItem_Action { + if m != nil { + return m.Action + } + return nil +} + +func (x *SyncStreamItem) GetSignedMessage() *SignedMessage { + if x, ok := x.GetAction().(*SyncStreamItem_SignedMessage); ok { + return x.SignedMessage + } + return nil +} + +func (x *SyncStreamItem) GetHandshake() *SyncStreamItem_SyncActionHandshake { + if x, ok := x.GetAction().(*SyncStreamItem_Handshake); ok { + return x.Handshake + } + return nil +} + +func (x *SyncStreamItem) GetDiffOperations() *SyncStreamItem_SyncActionDiffOperations { + if x, ok := x.GetAction().(*SyncStreamItem_DiffOperations); ok { + return x.DiffOperations + } + return nil +} + +func (x *SyncStreamItem) GetSendOperations() *SyncStreamItem_SyncActionSendOperations { + if x, ok := x.GetAction().(*SyncStreamItem_SendOperations); ok { + return x.SendOperations + } + return nil +} + +func (x *SyncStreamItem) GetSendConfig() *SyncStreamItem_SyncActionSendConfig { + if x, ok := x.GetAction().(*SyncStreamItem_SendConfig); ok { + return x.SendConfig + } + return nil +} + +func (x *SyncStreamItem) GetEstablishSharedSecret() *SyncStreamItem_SyncEstablishSharedSecret { + if x, ok := x.GetAction().(*SyncStreamItem_EstablishSharedSecret); ok { + return x.EstablishSharedSecret + } + return nil +} + +func (x *SyncStreamItem) GetThrottle() *SyncStreamItem_SyncActionThrottle { + if x, ok := x.GetAction().(*SyncStreamItem_Throttle); ok { + return x.Throttle + } + return nil +} + +type isSyncStreamItem_Action interface { + isSyncStreamItem_Action() +} + +type SyncStreamItem_SignedMessage struct { + SignedMessage *SignedMessage `protobuf:"bytes,1,opt,name=signed_message,json=signedMessage,proto3,oneof"` +} + +type SyncStreamItem_Handshake struct { + Handshake *SyncStreamItem_SyncActionHandshake `protobuf:"bytes,3,opt,name=handshake,proto3,oneof"` +} + +type SyncStreamItem_DiffOperations struct { + DiffOperations *SyncStreamItem_SyncActionDiffOperations `protobuf:"bytes,20,opt,name=diff_operations,json=diffOperations,proto3,oneof"` +} + +type SyncStreamItem_SendOperations struct { + SendOperations *SyncStreamItem_SyncActionSendOperations `protobuf:"bytes,21,opt,name=send_operations,json=sendOperations,proto3,oneof"` +} + +type SyncStreamItem_SendConfig struct { + SendConfig *SyncStreamItem_SyncActionSendConfig `protobuf:"bytes,22,opt,name=send_config,json=sendConfig,proto3,oneof"` +} + +type SyncStreamItem_EstablishSharedSecret struct { + EstablishSharedSecret *SyncStreamItem_SyncEstablishSharedSecret `protobuf:"bytes,23,opt,name=establish_shared_secret,json=establishSharedSecret,proto3,oneof"` +} + +type SyncStreamItem_Throttle struct { + Throttle *SyncStreamItem_SyncActionThrottle `protobuf:"bytes,1000,opt,name=throttle,proto3,oneof"` +} + +func (*SyncStreamItem_SignedMessage) isSyncStreamItem_Action() {} + +func (*SyncStreamItem_Handshake) isSyncStreamItem_Action() {} + +func (*SyncStreamItem_DiffOperations) isSyncStreamItem_Action() {} + +func (*SyncStreamItem_SendOperations) isSyncStreamItem_Action() {} + +func (*SyncStreamItem_SendConfig) isSyncStreamItem_Action() {} + +func (*SyncStreamItem_EstablishSharedSecret) isSyncStreamItem_Action() {} + +func (*SyncStreamItem_Throttle) isSyncStreamItem_Action() {} + +// RemoteConfig contains shareable properties from a remote backrest instance. +type RemoteConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Repos []*RemoteRepo `protobuf:"bytes,1,rep,name=repos,proto3" json:"repos,omitempty"` +} + +func (x *RemoteConfig) Reset() { + *x = RemoteConfig{} + mi := &file_v1_syncservice_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoteConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoteConfig) ProtoMessage() {} + +func (x *RemoteConfig) ProtoReflect() protoreflect.Message { + mi := &file_v1_syncservice_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoteConfig.ProtoReflect.Descriptor instead. +func (*RemoteConfig) Descriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{2} +} + +func (x *RemoteConfig) GetRepos() []*RemoteRepo { + if x != nil { + return x.Repos + } + return nil +} + +type RemoteRepo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Guid string `protobuf:"bytes,11,opt,name=guid,proto3" json:"guid,omitempty"` + Uri string `protobuf:"bytes,2,opt,name=uri,proto3" json:"uri,omitempty"` + Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` + Env []string `protobuf:"bytes,4,rep,name=env,proto3" json:"env,omitempty"` + Flags []string `protobuf:"bytes,5,rep,name=flags,proto3" json:"flags,omitempty"` +} + +func (x *RemoteRepo) Reset() { + *x = RemoteRepo{} + mi := &file_v1_syncservice_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoteRepo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoteRepo) ProtoMessage() {} + +func (x *RemoteRepo) ProtoReflect() protoreflect.Message { + mi := &file_v1_syncservice_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoteRepo.ProtoReflect.Descriptor instead. +func (*RemoteRepo) Descriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{3} +} + +func (x *RemoteRepo) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *RemoteRepo) GetGuid() string { + if x != nil { + return x.Guid + } + return "" +} + +func (x *RemoteRepo) GetUri() string { + if x != nil { + return x.Uri + } + return "" +} + +func (x *RemoteRepo) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + +func (x *RemoteRepo) GetEnv() []string { + if x != nil { + return x.Env + } + return nil +} + +func (x *RemoteRepo) GetFlags() []string { + if x != nil { + return x.Flags + } + return nil +} + +type GetRemoteReposResponse_RemoteRepoMetadata struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + InstanceId string `protobuf:"bytes,1,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` + RepoId string `protobuf:"bytes,2,opt,name=repo_id,json=repoId,proto3" json:"repo_id,omitempty"` +} + +func (x *GetRemoteReposResponse_RemoteRepoMetadata) Reset() { + *x = GetRemoteReposResponse_RemoteRepoMetadata{} + mi := &file_v1_syncservice_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRemoteReposResponse_RemoteRepoMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRemoteReposResponse_RemoteRepoMetadata) ProtoMessage() {} + +func (x *GetRemoteReposResponse_RemoteRepoMetadata) ProtoReflect() protoreflect.Message { + mi := &file_v1_syncservice_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRemoteReposResponse_RemoteRepoMetadata.ProtoReflect.Descriptor instead. +func (*GetRemoteReposResponse_RemoteRepoMetadata) Descriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *GetRemoteReposResponse_RemoteRepoMetadata) GetInstanceId() string { + if x != nil { + return x.InstanceId + } + return "" +} + +func (x *GetRemoteReposResponse_RemoteRepoMetadata) GetRepoId() string { + if x != nil { + return x.RepoId + } + return "" +} + +type SyncStreamItem_SyncActionHandshake struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProtocolVersion int64 `protobuf:"varint,1,opt,name=protocol_version,json=protocolVersion,proto3" json:"protocol_version,omitempty"` + PublicKey *PublicKey `protobuf:"bytes,2,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + InstanceId *SignedMessage `protobuf:"bytes,3,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` +} + +func (x *SyncStreamItem_SyncActionHandshake) Reset() { + *x = SyncStreamItem_SyncActionHandshake{} + mi := &file_v1_syncservice_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncStreamItem_SyncActionHandshake) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncStreamItem_SyncActionHandshake) ProtoMessage() {} + +func (x *SyncStreamItem_SyncActionHandshake) ProtoReflect() protoreflect.Message { + mi := &file_v1_syncservice_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncStreamItem_SyncActionHandshake.ProtoReflect.Descriptor instead. +func (*SyncStreamItem_SyncActionHandshake) Descriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{1, 0} +} + +func (x *SyncStreamItem_SyncActionHandshake) GetProtocolVersion() int64 { + if x != nil { + return x.ProtocolVersion + } + return 0 +} + +func (x *SyncStreamItem_SyncActionHandshake) GetPublicKey() *PublicKey { + if x != nil { + return x.PublicKey + } + return nil +} + +func (x *SyncStreamItem_SyncActionHandshake) GetInstanceId() *SignedMessage { + if x != nil { + return x.InstanceId + } + return nil +} + +type SyncStreamItem_SyncActionSendConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Config *RemoteConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` +} + +func (x *SyncStreamItem_SyncActionSendConfig) Reset() { + *x = SyncStreamItem_SyncActionSendConfig{} + mi := &file_v1_syncservice_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncStreamItem_SyncActionSendConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncStreamItem_SyncActionSendConfig) ProtoMessage() {} + +func (x *SyncStreamItem_SyncActionSendConfig) ProtoReflect() protoreflect.Message { + mi := &file_v1_syncservice_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncStreamItem_SyncActionSendConfig.ProtoReflect.Descriptor instead. +func (*SyncStreamItem_SyncActionSendConfig) Descriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{1, 1} +} + +func (x *SyncStreamItem_SyncActionSendConfig) GetConfig() *RemoteConfig { + if x != nil { + return x.Config + } + return nil +} + +type SyncStreamItem_SyncActionConnectRepo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RepoId string `protobuf:"bytes,1,opt,name=repo_id,json=repoId,proto3" json:"repo_id,omitempty"` +} + +func (x *SyncStreamItem_SyncActionConnectRepo) Reset() { + *x = SyncStreamItem_SyncActionConnectRepo{} + mi := &file_v1_syncservice_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncStreamItem_SyncActionConnectRepo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncStreamItem_SyncActionConnectRepo) ProtoMessage() {} + +func (x *SyncStreamItem_SyncActionConnectRepo) ProtoReflect() protoreflect.Message { + mi := &file_v1_syncservice_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncStreamItem_SyncActionConnectRepo.ProtoReflect.Descriptor instead. +func (*SyncStreamItem_SyncActionConnectRepo) Descriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{1, 2} +} + +func (x *SyncStreamItem_SyncActionConnectRepo) GetRepoId() string { + if x != nil { + return x.RepoId + } + return "" +} + +type SyncStreamItem_SyncActionDiffOperations struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Client connects and sends a list of "have_operations" that exist in its log. + // have_operation_ids and have_operation_modnos are the operation IDs and modnos that the client has when zip'd pairwise. + HaveOperationsSelector *OpSelector `protobuf:"bytes,1,opt,name=have_operations_selector,json=haveOperationsSelector,proto3" json:"have_operations_selector,omitempty"` + HaveOperationIds []int64 `protobuf:"varint,2,rep,packed,name=have_operation_ids,json=haveOperationIds,proto3" json:"have_operation_ids,omitempty"` + HaveOperationModnos []int64 `protobuf:"varint,3,rep,packed,name=have_operation_modnos,json=haveOperationModnos,proto3" json:"have_operation_modnos,omitempty"` + // Server sends a list of "request_operations" for any operations that it doesn't have. + RequestOperations []int64 `protobuf:"varint,4,rep,packed,name=request_operations,json=requestOperations,proto3" json:"request_operations,omitempty"` +} + +func (x *SyncStreamItem_SyncActionDiffOperations) Reset() { + *x = SyncStreamItem_SyncActionDiffOperations{} + mi := &file_v1_syncservice_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncStreamItem_SyncActionDiffOperations) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncStreamItem_SyncActionDiffOperations) ProtoMessage() {} + +func (x *SyncStreamItem_SyncActionDiffOperations) ProtoReflect() protoreflect.Message { + mi := &file_v1_syncservice_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncStreamItem_SyncActionDiffOperations.ProtoReflect.Descriptor instead. +func (*SyncStreamItem_SyncActionDiffOperations) Descriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{1, 3} +} + +func (x *SyncStreamItem_SyncActionDiffOperations) GetHaveOperationsSelector() *OpSelector { + if x != nil { + return x.HaveOperationsSelector + } + return nil +} + +func (x *SyncStreamItem_SyncActionDiffOperations) GetHaveOperationIds() []int64 { + if x != nil { + return x.HaveOperationIds + } + return nil +} + +func (x *SyncStreamItem_SyncActionDiffOperations) GetHaveOperationModnos() []int64 { + if x != nil { + return x.HaveOperationModnos + } + return nil +} + +func (x *SyncStreamItem_SyncActionDiffOperations) GetRequestOperations() []int64 { + if x != nil { + return x.RequestOperations + } + return nil +} + +type SyncStreamItem_SyncActionSendOperations struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Event *OperationEvent `protobuf:"bytes,1,opt,name=event,proto3" json:"event,omitempty"` +} + +func (x *SyncStreamItem_SyncActionSendOperations) Reset() { + *x = SyncStreamItem_SyncActionSendOperations{} + mi := &file_v1_syncservice_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncStreamItem_SyncActionSendOperations) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncStreamItem_SyncActionSendOperations) ProtoMessage() {} + +func (x *SyncStreamItem_SyncActionSendOperations) ProtoReflect() protoreflect.Message { + mi := &file_v1_syncservice_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncStreamItem_SyncActionSendOperations.ProtoReflect.Descriptor instead. +func (*SyncStreamItem_SyncActionSendOperations) Descriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{1, 4} +} + +func (x *SyncStreamItem_SyncActionSendOperations) GetEvent() *OperationEvent { + if x != nil { + return x.Event + } + return nil +} + +type SyncStreamItem_SyncActionThrottle struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DelayMs int64 `protobuf:"varint,1,opt,name=delay_ms,json=delayMs,proto3" json:"delay_ms,omitempty"` +} + +func (x *SyncStreamItem_SyncActionThrottle) Reset() { + *x = SyncStreamItem_SyncActionThrottle{} + mi := &file_v1_syncservice_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncStreamItem_SyncActionThrottle) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncStreamItem_SyncActionThrottle) ProtoMessage() {} + +func (x *SyncStreamItem_SyncActionThrottle) ProtoReflect() protoreflect.Message { + mi := &file_v1_syncservice_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncStreamItem_SyncActionThrottle.ProtoReflect.Descriptor instead. +func (*SyncStreamItem_SyncActionThrottle) Descriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{1, 5} +} + +func (x *SyncStreamItem_SyncActionThrottle) GetDelayMs() int64 { + if x != nil { + return x.DelayMs + } + return 0 +} + +type SyncStreamItem_SyncEstablishSharedSecret struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // a one-time-use ed25519 public key with a matching unshared private key. Used to perform a key exchange. + // See https://pkg.go.dev/crypto/ecdh#PrivateKey.ECDH . + Ed25519 string `protobuf:"bytes,2,opt,name=ed25519,json=ed25519pub,proto3" json:"ed25519,omitempty"` // base64 encoded public key +} + +func (x *SyncStreamItem_SyncEstablishSharedSecret) Reset() { + *x = SyncStreamItem_SyncEstablishSharedSecret{} + mi := &file_v1_syncservice_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SyncStreamItem_SyncEstablishSharedSecret) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncStreamItem_SyncEstablishSharedSecret) ProtoMessage() {} + +func (x *SyncStreamItem_SyncEstablishSharedSecret) ProtoReflect() protoreflect.Message { + mi := &file_v1_syncservice_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncStreamItem_SyncEstablishSharedSecret.ProtoReflect.Descriptor instead. +func (*SyncStreamItem_SyncEstablishSharedSecret) Descriptor() ([]byte, []int) { + return file_v1_syncservice_proto_rawDescGZIP(), []int{1, 6} +} + +func (x *SyncStreamItem_SyncEstablishSharedSecret) GetEd25519() string { + if x != nil { + return x.Ed25519 + } + return "" +} + +var File_v1_syncservice_proto protoreflect.FileDescriptor + +var file_v1_syncservice_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x76, 0x31, 0x2f, 0x73, 0x79, 0x6e, 0x63, 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, + 0x63, 0x72, 0x79, 0x70, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x0f, 0x76, 0x31, + 0x2f, 0x72, 0x65, 0x73, 0x74, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x10, 0x76, + 0x31, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x13, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x11, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0xad, 0x01, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, + 0x52, 0x65, 0x70, 0x6f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, + 0x05, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x52, + 0x65, 0x70, 0x6f, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x05, 0x72, 0x65, 0x70, + 0x6f, 0x73, 0x1a, 0x4e, 0x0a, 0x12, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, + 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, + 0x49, 0x64, 0x22, 0xc1, 0x0b, 0x0a, 0x0e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x3a, 0x0a, 0x0e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x5f, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x48, 0x00, 0x52, 0x0d, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x46, 0x0a, 0x09, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x48, 0x00, 0x52, 0x09, + 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x56, 0x0a, 0x0f, 0x64, 0x69, 0x66, + 0x66, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x14, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x44, 0x69, 0x66, 0x66, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, + 0x00, 0x52, 0x0e, 0x64, 0x69, 0x66, 0x66, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x12, 0x56, 0x0a, 0x0f, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x53, + 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x70, 0x65, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x4a, 0x0a, 0x0b, 0x73, 0x65, 0x6e, + 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, + 0x65, 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6e, + 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x0a, 0x73, 0x65, 0x6e, 0x64, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x66, 0x0a, 0x17, 0x65, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, + 0x73, 0x68, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x45, + 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x53, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x48, 0x00, 0x52, 0x15, 0x65, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x44, 0x0a, + 0x08, 0x74, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x18, 0xe8, 0x07, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x25, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, + 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, 0x48, 0x00, 0x52, 0x08, 0x74, 0x68, 0x72, 0x6f, 0x74, + 0x74, 0x6c, 0x65, 0x1a, 0xa2, 0x01, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x76, 0x31, 0x2e, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x4b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x69, 0x67, 0x6e, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0a, 0x69, 0x6e, + 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x1a, 0x40, 0x0a, 0x14, 0x53, 0x79, 0x6e, 0x63, + 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x28, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x10, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x30, 0x0a, 0x15, 0x53, 0x79, + 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, + 0x65, 0x70, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x1a, 0xf5, 0x01, 0x0a, + 0x18, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x69, 0x66, 0x66, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x48, 0x0a, 0x18, 0x68, 0x61, 0x76, + 0x65, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x73, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x76, 0x31, + 0x2e, 0x4f, 0x70, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x16, 0x68, 0x61, 0x76, + 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x6c, 0x65, 0x63, + 0x74, 0x6f, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x68, 0x61, 0x76, 0x65, 0x5f, 0x6f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x03, 0x52, + 0x10, 0x68, 0x61, 0x76, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x73, 0x12, 0x32, 0x0a, 0x15, 0x68, 0x61, 0x76, 0x65, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x6f, 0x64, 0x6e, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x03, + 0x52, 0x13, 0x68, 0x61, 0x76, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, + 0x6f, 0x64, 0x6e, 0x6f, 0x73, 0x12, 0x2d, 0x0a, 0x12, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x03, 0x52, 0x11, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x44, 0x0a, 0x18, 0x53, 0x79, 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x53, 0x65, 0x6e, 0x64, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x28, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x52, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x1a, 0x2f, 0x0a, 0x12, 0x53, 0x79, + 0x6e, 0x63, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x68, 0x72, 0x6f, 0x74, 0x74, 0x6c, 0x65, + 0x12, 0x19, 0x0a, 0x08, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x4d, 0x73, 0x1a, 0x38, 0x0a, 0x19, 0x53, + 0x79, 0x6e, 0x63, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x53, 0x68, 0x61, 0x72, + 0x65, 0x64, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x07, 0x65, 0x64, 0x32, 0x35, + 0x35, 0x31, 0x39, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x64, 0x32, 0x35, 0x35, + 0x31, 0x39, 0x70, 0x75, 0x62, 0x22, 0xb4, 0x01, 0x0a, 0x13, 0x52, 0x65, 0x70, 0x6f, 0x43, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, + 0x18, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, + 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x43, + 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, + 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, + 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x4f, + 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x43, 0x4f, 0x4e, + 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x55, 0x4e, + 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x5a, 0x45, 0x44, 0x10, 0x03, 0x12, 0x1e, 0x0a, 0x1a, + 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, + 0x5f, 0x4e, 0x4f, 0x54, 0x5f, 0x46, 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x04, 0x42, 0x08, 0x0a, 0x06, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x34, 0x0a, 0x0c, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x0a, 0x05, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, + 0x65, 0x52, 0x65, 0x70, 0x6f, 0x52, 0x05, 0x72, 0x65, 0x70, 0x6f, 0x73, 0x22, 0x86, 0x01, 0x0a, + 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x67, + 0x75, 0x69, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 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, 0x2a, 0xfb, 0x01, 0x0a, 0x13, 0x53, 0x79, 0x6e, 0x63, 0x43, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x1c, 0x0a, + 0x18, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, + 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x43, + 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, + 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x4f, 0x4e, + 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x4f, + 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x21, 0x0a, 0x1d, 0x43, 0x4f, 0x4e, + 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x44, 0x49, + 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, + 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, + 0x5f, 0x52, 0x45, 0x54, 0x52, 0x59, 0x5f, 0x57, 0x41, 0x49, 0x54, 0x10, 0x04, 0x12, 0x1f, 0x0a, + 0x1b, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, + 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x10, 0x0a, 0x12, 0x23, + 0x0a, 0x1f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, + 0x54, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, + 0x4c, 0x10, 0x0b, 0x32, 0x93, 0x01, 0x0a, 0x13, 0x42, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, + 0x53, 0x79, 0x6e, 0x63, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, 0x04, 0x53, + 0x79, 0x6e, 0x63, 0x12, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x1a, 0x12, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x79, 0x6e, + 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x74, 0x65, 0x6d, 0x22, 0x00, 0x28, 0x01, 0x30, + 0x01, 0x12, 0x46, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x52, 0x65, + 0x70, 0x6f, 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, 0x1a, 0x2e, 0x76, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6f, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x61, 0x72, 0x65, 0x74, 0x68, 0x67, 0x65, + 0x6f, 0x72, 0x67, 0x65, 0x2f, 0x62, 0x61, 0x63, 0x6b, 0x72, 0x65, 0x73, 0x74, 0x2f, 0x67, 0x65, + 0x6e, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_v1_syncservice_proto_rawDescOnce sync.Once + file_v1_syncservice_proto_rawDescData = file_v1_syncservice_proto_rawDesc +) + +func file_v1_syncservice_proto_rawDescGZIP() []byte { + file_v1_syncservice_proto_rawDescOnce.Do(func() { + file_v1_syncservice_proto_rawDescData = protoimpl.X.CompressGZIP(file_v1_syncservice_proto_rawDescData) + }) + return file_v1_syncservice_proto_rawDescData +} + +var file_v1_syncservice_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_v1_syncservice_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_v1_syncservice_proto_goTypes = []any{ + (SyncConnectionState)(0), // 0: v1.SyncConnectionState + (SyncStreamItem_RepoConnectionState)(0), // 1: v1.SyncStreamItem.RepoConnectionState + (*GetRemoteReposResponse)(nil), // 2: v1.GetRemoteReposResponse + (*SyncStreamItem)(nil), // 3: v1.SyncStreamItem + (*RemoteConfig)(nil), // 4: v1.RemoteConfig + (*RemoteRepo)(nil), // 5: v1.RemoteRepo + (*GetRemoteReposResponse_RemoteRepoMetadata)(nil), // 6: v1.GetRemoteReposResponse.RemoteRepoMetadata + (*SyncStreamItem_SyncActionHandshake)(nil), // 7: v1.SyncStreamItem.SyncActionHandshake + (*SyncStreamItem_SyncActionSendConfig)(nil), // 8: v1.SyncStreamItem.SyncActionSendConfig + (*SyncStreamItem_SyncActionConnectRepo)(nil), // 9: v1.SyncStreamItem.SyncActionConnectRepo + (*SyncStreamItem_SyncActionDiffOperations)(nil), // 10: v1.SyncStreamItem.SyncActionDiffOperations + (*SyncStreamItem_SyncActionSendOperations)(nil), // 11: v1.SyncStreamItem.SyncActionSendOperations + (*SyncStreamItem_SyncActionThrottle)(nil), // 12: v1.SyncStreamItem.SyncActionThrottle + (*SyncStreamItem_SyncEstablishSharedSecret)(nil), // 13: v1.SyncStreamItem.SyncEstablishSharedSecret + (*SignedMessage)(nil), // 14: v1.SignedMessage + (*PublicKey)(nil), // 15: v1.PublicKey + (*OpSelector)(nil), // 16: v1.OpSelector + (*OperationEvent)(nil), // 17: v1.OperationEvent + (*emptypb.Empty)(nil), // 18: google.protobuf.Empty +} +var file_v1_syncservice_proto_depIdxs = []int32{ + 6, // 0: v1.GetRemoteReposResponse.repos:type_name -> v1.GetRemoteReposResponse.RemoteRepoMetadata + 14, // 1: v1.SyncStreamItem.signed_message:type_name -> v1.SignedMessage + 7, // 2: v1.SyncStreamItem.handshake:type_name -> v1.SyncStreamItem.SyncActionHandshake + 10, // 3: v1.SyncStreamItem.diff_operations:type_name -> v1.SyncStreamItem.SyncActionDiffOperations + 11, // 4: v1.SyncStreamItem.send_operations:type_name -> v1.SyncStreamItem.SyncActionSendOperations + 8, // 5: v1.SyncStreamItem.send_config:type_name -> v1.SyncStreamItem.SyncActionSendConfig + 13, // 6: v1.SyncStreamItem.establish_shared_secret:type_name -> v1.SyncStreamItem.SyncEstablishSharedSecret + 12, // 7: v1.SyncStreamItem.throttle:type_name -> v1.SyncStreamItem.SyncActionThrottle + 5, // 8: v1.RemoteConfig.repos:type_name -> v1.RemoteRepo + 15, // 9: v1.SyncStreamItem.SyncActionHandshake.public_key:type_name -> v1.PublicKey + 14, // 10: v1.SyncStreamItem.SyncActionHandshake.instance_id:type_name -> v1.SignedMessage + 4, // 11: v1.SyncStreamItem.SyncActionSendConfig.config:type_name -> v1.RemoteConfig + 16, // 12: v1.SyncStreamItem.SyncActionDiffOperations.have_operations_selector:type_name -> v1.OpSelector + 17, // 13: v1.SyncStreamItem.SyncActionSendOperations.event:type_name -> v1.OperationEvent + 3, // 14: v1.BackrestSyncService.Sync:input_type -> v1.SyncStreamItem + 18, // 15: v1.BackrestSyncService.GetRemoteRepos:input_type -> google.protobuf.Empty + 3, // 16: v1.BackrestSyncService.Sync:output_type -> v1.SyncStreamItem + 2, // 17: v1.BackrestSyncService.GetRemoteRepos:output_type -> v1.GetRemoteReposResponse + 16, // [16:18] is the sub-list for method output_type + 14, // [14:16] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name +} + +func init() { file_v1_syncservice_proto_init() } +func file_v1_syncservice_proto_init() { + if File_v1_syncservice_proto != nil { + return + } + file_v1_config_proto_init() + file_v1_crypto_proto_init() + file_v1_restic_proto_init() + file_v1_service_proto_init() + file_v1_operations_proto_init() + file_v1_syncservice_proto_msgTypes[1].OneofWrappers = []any{ + (*SyncStreamItem_SignedMessage)(nil), + (*SyncStreamItem_Handshake)(nil), + (*SyncStreamItem_DiffOperations)(nil), + (*SyncStreamItem_SendOperations)(nil), + (*SyncStreamItem_SendConfig)(nil), + (*SyncStreamItem_EstablishSharedSecret)(nil), + (*SyncStreamItem_Throttle)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_v1_syncservice_proto_rawDesc, + NumEnums: 2, + NumMessages: 12, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_v1_syncservice_proto_goTypes, + DependencyIndexes: file_v1_syncservice_proto_depIdxs, + EnumInfos: file_v1_syncservice_proto_enumTypes, + MessageInfos: file_v1_syncservice_proto_msgTypes, + }.Build() + File_v1_syncservice_proto = out.File + file_v1_syncservice_proto_rawDesc = nil + file_v1_syncservice_proto_goTypes = nil + file_v1_syncservice_proto_depIdxs = nil +} diff --git a/gen/go/v1/syncservice_grpc.pb.go b/gen/go/v1/syncservice_grpc.pb.go new file mode 100644 index 0000000..1ceed7b --- /dev/null +++ b/gen/go/v1/syncservice_grpc.pb.go @@ -0,0 +1,155 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc (unknown) +// source: v1/syncservice.proto + +package v1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + BackrestSyncService_Sync_FullMethodName = "/v1.BackrestSyncService/Sync" + BackrestSyncService_GetRemoteRepos_FullMethodName = "/v1.BackrestSyncService/GetRemoteRepos" +) + +// BackrestSyncServiceClient is the client API for BackrestSyncService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type BackrestSyncServiceClient interface { + Sync(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SyncStreamItem, SyncStreamItem], error) + GetRemoteRepos(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetRemoteReposResponse, error) +} + +type backrestSyncServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewBackrestSyncServiceClient(cc grpc.ClientConnInterface) BackrestSyncServiceClient { + return &backrestSyncServiceClient{cc} +} + +func (c *backrestSyncServiceClient) Sync(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SyncStreamItem, SyncStreamItem], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &BackrestSyncService_ServiceDesc.Streams[0], BackrestSyncService_Sync_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[SyncStreamItem, SyncStreamItem]{ClientStream: stream} + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type BackrestSyncService_SyncClient = grpc.BidiStreamingClient[SyncStreamItem, SyncStreamItem] + +func (c *backrestSyncServiceClient) GetRemoteRepos(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*GetRemoteReposResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetRemoteReposResponse) + err := c.cc.Invoke(ctx, BackrestSyncService_GetRemoteRepos_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// BackrestSyncServiceServer is the server API for BackrestSyncService service. +// All implementations must embed UnimplementedBackrestSyncServiceServer +// for forward compatibility. +type BackrestSyncServiceServer interface { + Sync(grpc.BidiStreamingServer[SyncStreamItem, SyncStreamItem]) error + GetRemoteRepos(context.Context, *emptypb.Empty) (*GetRemoteReposResponse, error) + mustEmbedUnimplementedBackrestSyncServiceServer() +} + +// UnimplementedBackrestSyncServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedBackrestSyncServiceServer struct{} + +func (UnimplementedBackrestSyncServiceServer) Sync(grpc.BidiStreamingServer[SyncStreamItem, SyncStreamItem]) error { + return status.Errorf(codes.Unimplemented, "method Sync not implemented") +} +func (UnimplementedBackrestSyncServiceServer) GetRemoteRepos(context.Context, *emptypb.Empty) (*GetRemoteReposResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetRemoteRepos not implemented") +} +func (UnimplementedBackrestSyncServiceServer) mustEmbedUnimplementedBackrestSyncServiceServer() {} +func (UnimplementedBackrestSyncServiceServer) testEmbeddedByValue() {} + +// UnsafeBackrestSyncServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to BackrestSyncServiceServer will +// result in compilation errors. +type UnsafeBackrestSyncServiceServer interface { + mustEmbedUnimplementedBackrestSyncServiceServer() +} + +func RegisterBackrestSyncServiceServer(s grpc.ServiceRegistrar, srv BackrestSyncServiceServer) { + // If the following call pancis, it indicates UnimplementedBackrestSyncServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&BackrestSyncService_ServiceDesc, srv) +} + +func _BackrestSyncService_Sync_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(BackrestSyncServiceServer).Sync(&grpc.GenericServerStream[SyncStreamItem, SyncStreamItem]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type BackrestSyncService_SyncServer = grpc.BidiStreamingServer[SyncStreamItem, SyncStreamItem] + +func _BackrestSyncService_GetRemoteRepos_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackrestSyncServiceServer).GetRemoteRepos(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: BackrestSyncService_GetRemoteRepos_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackrestSyncServiceServer).GetRemoteRepos(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +// BackrestSyncService_ServiceDesc is the grpc.ServiceDesc for BackrestSyncService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var BackrestSyncService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "v1.BackrestSyncService", + HandlerType: (*BackrestSyncServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetRemoteRepos", + Handler: _BackrestSyncService_GetRemoteRepos_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Sync", + Handler: _BackrestSyncService_Sync_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "v1/syncservice.proto", +} diff --git a/gen/go/v1/v1connect/hub.connect.go b/gen/go/v1/v1connect/hub.connect.go deleted file mode 100644 index 27079df..0000000 --- a/gen/go/v1/v1connect/hub.connect.go +++ /dev/null @@ -1,115 +0,0 @@ -// Code generated by protoc-gen-connect-go. DO NOT EDIT. -// -// Source: v1/hub.proto - -package v1connect - -import ( - connect "connectrpc.com/connect" - context "context" - errors "errors" - v1 "github.com/garethgeorge/backrest/gen/go/v1" - emptypb "google.golang.org/protobuf/types/known/emptypb" - http "net/http" - strings "strings" -) - -// This is a compile-time assertion to ensure that this generated file and the connect package are -// compatible. If you get a compiler error that this constant is not defined, this code was -// generated with a version of connect newer than the one compiled into your binary. You can fix the -// problem by either regenerating this code with an older version of connect or updating the connect -// version compiled into your binary. -const _ = connect.IsAtLeastVersion1_13_0 - -const ( - // HubName is the fully-qualified name of the Hub service. - HubName = "v1.Hub" -) - -// These constants are the fully-qualified names of the RPCs defined in this package. They're -// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. -// -// Note that these are different from the fully-qualified method names used by -// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to -// reflection-formatted method names, remove the leading slash and convert the remaining slash to a -// period. -const ( - // HubGetInstancesProcedure is the fully-qualified name of the Hub's GetInstances RPC. - HubGetInstancesProcedure = "/v1.Hub/GetInstances" -) - -// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. -var ( - hubServiceDescriptor = v1.File_v1_hub_proto.Services().ByName("Hub") - hubGetInstancesMethodDescriptor = hubServiceDescriptor.Methods().ByName("GetInstances") -) - -// HubClient is a client for the v1.Hub service. -type HubClient interface { - // GetInstances returns a list of all instances - GetInstances(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1.GetInstancesResponse], error) -} - -// NewHubClient constructs a client for the v1.Hub service. By default, it uses the Connect protocol -// with the binary Protobuf Codec, asks for gzipped responses, and sends uncompressed requests. To -// use the gRPC or gRPC-Web protocols, supply the connect.WithGRPC() or connect.WithGRPCWeb() -// options. -// -// The URL supplied here should be the base URL for the Connect or gRPC server (for example, -// http://api.acme.com or https://acme.com/grpc). -func NewHubClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) HubClient { - baseURL = strings.TrimRight(baseURL, "/") - return &hubClient{ - getInstances: connect.NewClient[emptypb.Empty, v1.GetInstancesResponse]( - httpClient, - baseURL+HubGetInstancesProcedure, - connect.WithSchema(hubGetInstancesMethodDescriptor), - connect.WithClientOptions(opts...), - ), - } -} - -// hubClient implements HubClient. -type hubClient struct { - getInstances *connect.Client[emptypb.Empty, v1.GetInstancesResponse] -} - -// GetInstances calls v1.Hub.GetInstances. -func (c *hubClient) GetInstances(ctx context.Context, req *connect.Request[emptypb.Empty]) (*connect.Response[v1.GetInstancesResponse], error) { - return c.getInstances.CallUnary(ctx, req) -} - -// HubHandler is an implementation of the v1.Hub service. -type HubHandler interface { - // GetInstances returns a list of all instances - GetInstances(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1.GetInstancesResponse], error) -} - -// NewHubHandler builds an HTTP handler from the service implementation. It returns the path on -// which to mount the handler and the handler itself. -// -// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf -// and JSON codecs. They also support gzip compression. -func NewHubHandler(svc HubHandler, opts ...connect.HandlerOption) (string, http.Handler) { - hubGetInstancesHandler := connect.NewUnaryHandler( - HubGetInstancesProcedure, - svc.GetInstances, - connect.WithSchema(hubGetInstancesMethodDescriptor), - connect.WithHandlerOptions(opts...), - ) - return "/v1.Hub/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case HubGetInstancesProcedure: - hubGetInstancesHandler.ServeHTTP(w, r) - default: - http.NotFound(w, r) - } - }) -} - -// UnimplementedHubHandler returns CodeUnimplemented from all methods. -type UnimplementedHubHandler struct{} - -func (UnimplementedHubHandler) GetInstances(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1.GetInstancesResponse], error) { - return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1.Hub.GetInstances is not implemented")) -} diff --git a/gen/go/v1/v1connect/syncservice.connect.go b/gen/go/v1/v1connect/syncservice.connect.go new file mode 100644 index 0000000..c4821b5 --- /dev/null +++ b/gen/go/v1/v1connect/syncservice.connect.go @@ -0,0 +1,144 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: v1/syncservice.proto + +package v1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1 "github.com/garethgeorge/backrest/gen/go/v1" + emptypb "google.golang.org/protobuf/types/known/emptypb" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion1_13_0 + +const ( + // BackrestSyncServiceName is the fully-qualified name of the BackrestSyncService service. + BackrestSyncServiceName = "v1.BackrestSyncService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // BackrestSyncServiceSyncProcedure is the fully-qualified name of the BackrestSyncService's Sync + // RPC. + BackrestSyncServiceSyncProcedure = "/v1.BackrestSyncService/Sync" + // BackrestSyncServiceGetRemoteReposProcedure is the fully-qualified name of the + // BackrestSyncService's GetRemoteRepos RPC. + BackrestSyncServiceGetRemoteReposProcedure = "/v1.BackrestSyncService/GetRemoteRepos" +) + +// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. +var ( + backrestSyncServiceServiceDescriptor = v1.File_v1_syncservice_proto.Services().ByName("BackrestSyncService") + backrestSyncServiceSyncMethodDescriptor = backrestSyncServiceServiceDescriptor.Methods().ByName("Sync") + backrestSyncServiceGetRemoteReposMethodDescriptor = backrestSyncServiceServiceDescriptor.Methods().ByName("GetRemoteRepos") +) + +// BackrestSyncServiceClient is a client for the v1.BackrestSyncService service. +type BackrestSyncServiceClient interface { + Sync(context.Context) *connect.BidiStreamForClient[v1.SyncStreamItem, v1.SyncStreamItem] + GetRemoteRepos(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1.GetRemoteReposResponse], error) +} + +// NewBackrestSyncServiceClient constructs a client for the v1.BackrestSyncService service. By +// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, +// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the +// connect.WithGRPC() or connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewBackrestSyncServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) BackrestSyncServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return &backrestSyncServiceClient{ + sync: connect.NewClient[v1.SyncStreamItem, v1.SyncStreamItem]( + httpClient, + baseURL+BackrestSyncServiceSyncProcedure, + connect.WithSchema(backrestSyncServiceSyncMethodDescriptor), + connect.WithClientOptions(opts...), + ), + getRemoteRepos: connect.NewClient[emptypb.Empty, v1.GetRemoteReposResponse]( + httpClient, + baseURL+BackrestSyncServiceGetRemoteReposProcedure, + connect.WithSchema(backrestSyncServiceGetRemoteReposMethodDescriptor), + connect.WithClientOptions(opts...), + ), + } +} + +// backrestSyncServiceClient implements BackrestSyncServiceClient. +type backrestSyncServiceClient struct { + sync *connect.Client[v1.SyncStreamItem, v1.SyncStreamItem] + getRemoteRepos *connect.Client[emptypb.Empty, v1.GetRemoteReposResponse] +} + +// Sync calls v1.BackrestSyncService.Sync. +func (c *backrestSyncServiceClient) Sync(ctx context.Context) *connect.BidiStreamForClient[v1.SyncStreamItem, v1.SyncStreamItem] { + return c.sync.CallBidiStream(ctx) +} + +// GetRemoteRepos calls v1.BackrestSyncService.GetRemoteRepos. +func (c *backrestSyncServiceClient) GetRemoteRepos(ctx context.Context, req *connect.Request[emptypb.Empty]) (*connect.Response[v1.GetRemoteReposResponse], error) { + return c.getRemoteRepos.CallUnary(ctx, req) +} + +// BackrestSyncServiceHandler is an implementation of the v1.BackrestSyncService service. +type BackrestSyncServiceHandler interface { + Sync(context.Context, *connect.BidiStream[v1.SyncStreamItem, v1.SyncStreamItem]) error + GetRemoteRepos(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1.GetRemoteReposResponse], error) +} + +// NewBackrestSyncServiceHandler builds an HTTP handler from the service implementation. It returns +// the path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewBackrestSyncServiceHandler(svc BackrestSyncServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + backrestSyncServiceSyncHandler := connect.NewBidiStreamHandler( + BackrestSyncServiceSyncProcedure, + svc.Sync, + connect.WithSchema(backrestSyncServiceSyncMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + backrestSyncServiceGetRemoteReposHandler := connect.NewUnaryHandler( + BackrestSyncServiceGetRemoteReposProcedure, + svc.GetRemoteRepos, + connect.WithSchema(backrestSyncServiceGetRemoteReposMethodDescriptor), + connect.WithHandlerOptions(opts...), + ) + return "/v1.BackrestSyncService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case BackrestSyncServiceSyncProcedure: + backrestSyncServiceSyncHandler.ServeHTTP(w, r) + case BackrestSyncServiceGetRemoteReposProcedure: + backrestSyncServiceGetRemoteReposHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedBackrestSyncServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedBackrestSyncServiceHandler struct{} + +func (UnimplementedBackrestSyncServiceHandler) Sync(context.Context, *connect.BidiStream[v1.SyncStreamItem, v1.SyncStreamItem]) error { + return connect.NewError(connect.CodeUnimplemented, errors.New("v1.BackrestSyncService.Sync is not implemented")) +} + +func (UnimplementedBackrestSyncServiceHandler) GetRemoteRepos(context.Context, *connect.Request[emptypb.Empty]) (*connect.Response[v1.GetRemoteReposResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("v1.BackrestSyncService.GetRemoteRepos is not implemented")) +} diff --git a/go.mod b/go.mod index 87fe3c4..827507c 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/gitploy-io/cronexpr v0.2.2 github.com/gofrs/flock v0.12.1 github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/google/go-cmp v0.6.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/uuid v1.6.0 github.com/hashicorp/go-multierror v1.1.1 @@ -57,6 +58,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-stack/stack v1.8.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/josephspurrier/goversioninfo v1.4.1 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -72,10 +74,11 @@ require ( go.opentelemetry.io/otel/metric v1.32.0 // indirect go.opentelemetry.io/otel/trace v1.32.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect golang.org/x/image v0.22.0 // indirect + golang.org/x/mod v0.21.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.20.0 // indirect + golang.org/x/tools v0.26.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f // indirect modernc.org/libc v1.61.0 // indirect modernc.org/mathutil v1.6.0 // indirect diff --git a/go.sum b/go.sum index e9cf8e7..dc6a46d 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,6 @@ github.com/containrrr/shoutrrr v0.8.0/go.mod h1:ioyQAyu1LJY6sILuNyKaQaw+9Ttik5Qe github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f h1:OGqDDftRTwrvUoL6pOG7rYTmWsTCvyEWFsMjg+HcOaA= -github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f/go.mod h1:Dv9D0NUlAsaQcGQZa5kc5mqR9ua72SmA8VXi4cd+cBw= github.com/dchest/jsmin v1.0.0 h1:Y2hWXmGZiRxtl+VcTksyucgTlYxnhPzTozCwx9gy9zI= github.com/dchest/jsmin v1.0.0/go.mod h1:AVBIund7Mr7lKXT70hKT2YgL3XEXUaUk5iw9DZ8b0Uc= github.com/djherbis/buffer v1.1.0/go.mod h1:VwN8VdFkMY0DCALdY8o00d3IZ6Amz/UNVMWcSaJT44o= @@ -25,8 +23,6 @@ github.com/djherbis/nio/v3 v3.0.1 h1:6wxhnuppteMa6RHA4L81Dq7ThkZH8SwnDzXDYy95vB4 github.com/djherbis/nio/v3 v3.0.1/go.mod h1:Ng4h80pbZFMla1yKzm61cF0tqqilXZYrogmWgZxOcmg= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY= @@ -68,8 +64,8 @@ github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -84,6 +80,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb h1:PGufWXXDq9yaev6xX1YQauaO1MV90e6Mpoq1I7Lz/VM= github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= @@ -121,14 +119,10 @@ github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwU github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= -github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= -github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= @@ -151,17 +145,11 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo= -go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= -go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= -go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= -go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= go.opentelemetry.io/otel/trace v1.9.0/go.mod h1:2737Q0MuG8q1uILYm2YYVkAyLtOofiTNGg6VODnOiPo= -go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= -go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -176,14 +164,8 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= -golang.org/x/image v0.21.0 h1:c5qV36ajHpdj4Qi0GnE0jUc/yuo33OLFaa0d+crTD5s= -golang.org/x/image v0.21.0/go.mod h1:vUbsLavqK/W303ZroQQVKQ+Af3Yl6Uz1Ppu5J/cLz78= golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g= golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -194,14 +176,10 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -214,15 +192,11 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -234,20 +208,12 @@ golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= -google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f h1:M65LEviCfuZTfrfzwwEoxVtgvfkFkBUbFnRbxCXuXhU= google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f/go.mod h1:Yo94eF2nj7igQt+TiJ49KxjIH8ndLYPZMIRSiRcEbg0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f h1:C1QccEa9kUwvMgEUORqQD9S17QesQijxjZ84sO82mfo= google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= @@ -262,16 +228,14 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= -modernc.org/ccgo/v4 v4.21.0 h1:kKPI3dF7RIag8YcToh5ZwDcVMIv6VGa0ED5cvh0LMW4= -modernc.org/ccgo/v4 v4.21.0/go.mod h1:h6kt6H/A2+ew/3MW/p6KEoQmrq/i3pr0J/SiwiaF/g0= +modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= +modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.5.0 h1:bJ9ChznK1L1mUtAQtxi0wi5AtAs5jQuw4PrPHO5pb6M= -modernc.org/gc/v2 v2.5.0/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= -modernc.org/libc v1.61.0 h1:eGFcvWpqlnoGwzZeZe3PWJkkKbM/3SUGyk1DVZQ0TpE= -modernc.org/libc v1.61.0/go.mod h1:DvxVX89wtGTu+r72MLGhygpfi3aUGgZRdAYGCAVVud0= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= diff --git a/internal/api/backresthandler.go b/internal/api/backresthandler.go index 4e5ab87..d3c7431 100644 --- a/internal/api/backresthandler.go +++ b/internal/api/backresthandler.go @@ -9,7 +9,6 @@ import ( "io" "os" "path" - "reflect" "slices" "strings" "sync" @@ -19,6 +18,7 @@ import ( "github.com/garethgeorge/backrest/gen/go/types" v1 "github.com/garethgeorge/backrest/gen/go/v1" "github.com/garethgeorge/backrest/gen/go/v1/v1connect" + syncapi "github.com/garethgeorge/backrest/internal/api/syncapi" "github.com/garethgeorge/backrest/internal/config" "github.com/garethgeorge/backrest/internal/env" "github.com/garethgeorge/backrest/internal/logstore" @@ -36,20 +36,22 @@ import ( type BackrestHandler struct { v1connect.UnimplementedBackrestHandler - config config.ConfigStore - orchestrator *orchestrator.Orchestrator - oplog *oplog.OpLog - logStore *logstore.LogStore + config config.ConfigStore + orchestrator *orchestrator.Orchestrator + oplog *oplog.OpLog + logStore *logstore.LogStore + remoteConfigStore syncapi.RemoteConfigStore } var _ v1connect.BackrestHandler = &BackrestHandler{} -func NewBackrestHandler(config config.ConfigStore, orchestrator *orchestrator.Orchestrator, oplog *oplog.OpLog, logStore *logstore.LogStore) *BackrestHandler { +func NewBackrestHandler(config config.ConfigStore, remoteConfigStore syncapi.RemoteConfigStore, orchestrator *orchestrator.Orchestrator, oplog *oplog.OpLog, logStore *logstore.LogStore) *BackrestHandler { s := &BackrestHandler{ - config: config, - orchestrator: orchestrator, - oplog: oplog, - logStore: logStore, + config: config, + orchestrator: orchestrator, + oplog: oplog, + logStore: logStore, + remoteConfigStore: remoteConfigStore, } return s @@ -142,46 +144,98 @@ func (s *BackrestHandler) AddRepo(ctx context.Context, req *connect.Request[v1.R return nil, fmt.Errorf("failed to get config: %w", err) } + newRepo := req.Msg + // Deep copy the configuration c = proto.Clone(c).(*v1.Config) // Add or implicit update the repo - if idx := slices.IndexFunc(c.Repos, func(r *v1.Repo) bool { return r.Id == req.Msg.Id }); idx != -1 { - c.Repos[idx] = req.Msg + var oldRepo *v1.Repo + if idx := slices.IndexFunc(c.Repos, func(r *v1.Repo) bool { return r.Id == newRepo.Id }); idx != -1 { + oldRepo = c.Repos[idx] + c.Repos[idx] = newRepo } else { - c.Repos = append(c.Repos, req.Msg) + c.Repos = append(c.Repos, newRepo) + } + + // Ensure the Repo GUID is set to the correct value. + // This is derived from 'restic cat config' for local repos. + // For remote repos, the GUID is derived from the remote config's value for the repo. + if !syncapi.IsBackrestRemoteRepoURI(newRepo.Uri) { + bin, err := resticinstaller.FindOrInstallResticBinary() + if err != nil { + return nil, fmt.Errorf("failed to find or install restic binary: %w", err) + } + + r, err := repo.NewRepoOrchestrator(c, newRepo, bin) + if err != nil { + return nil, fmt.Errorf("failed to configure repo: %w", err) + } + + if err := r.Init(ctx); err != nil { + return nil, fmt.Errorf("failed to init repo: %w", err) + } + + guid, err := r.RepoGUID() + zap.S().Debugf("GUID for repo %q is %q from restic", newRepo.Id, guid) + if err != nil { + return nil, fmt.Errorf("failed to get repo config: %w", err) + } + + newRepo.Guid = guid + } else { + // It's a remote repo, let's find the configuration and guid for it. + instanceID, err := syncapi.InstanceForBackrestURI(newRepo.Uri) + if err != nil { + return nil, fmt.Errorf("failed to parse remote repo URI: %w", err) + } + + // fetch the remote config + remoteRepo, err := syncapi.GetRepoConfig(s.remoteConfigStore, instanceID, newRepo.Guid) + if err != nil { + return nil, fmt.Errorf("failed to get remote repo config: %w", err) + } + + // set the GUID from the remote config. + newRepo.Guid = remoteRepo.Guid + if newRepo.Guid == "" { + return nil, fmt.Errorf("GUID not found for repo %q", newRepo.Id) + } } if err := config.ValidateConfig(c); err != nil { return nil, fmt.Errorf("validation error: %w", err) } - bin, err := resticinstaller.FindOrInstallResticBinary() - if err != nil { - return nil, fmt.Errorf("failed to find or install restic binary: %w", err) - } - - r, err := repo.NewRepoOrchestrator(c, req.Msg, bin) - if err != nil { - return nil, fmt.Errorf("failed to configure repo: %w", err) - } - - // 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) - } - zap.L().Debug("updating config", zap.Int32("version", c.Version)) if err := s.config.Update(c); err != nil { return nil, fmt.Errorf("failed to update config: %w", err) } + // If the GUID has changed, and we just successfully updated the config in storage, then we need to migrate the oplog. + if oldRepo != nil && newRepo.Guid != oldRepo.Guid { + migratedCount := 0 + if err := s.oplog.Transform(oplog.Query{}. + SetRepoGUID(oldRepo.Guid). + SetInstanceID(c.Instance), func(op *v1.Operation) (*v1.Operation, error) { + op.RepoGuid = newRepo.Guid + migratedCount++ + return op, nil + }); err != nil { + return nil, fmt.Errorf("failed to get operations for repo: %w", err) + } + + zap.S().Infof("updated GUID for repo %q from %q to %q, migrated %d operations to reference the new GUID", newRepo.Id, oldRepo.Guid, newRepo.Guid, migratedCount) + } + zap.L().Debug("applying config", zap.Int32("version", c.Version)) - s.orchestrator.ApplyConfig(c) + if err := s.orchestrator.ApplyConfig(c); err != nil { + return nil, fmt.Errorf("failed to apply config: %w", err) + } // index snapshots for the newly added repository. zap.L().Debug("scheduling index snapshots task") - s.orchestrator.ScheduleTask(tasks.NewOneoffIndexSnapshotsTask(req.Msg.Id, time.Now()), tasks.TaskPriorityInteractive+tasks.TaskPriorityIndexSnapshots) + s.orchestrator.ScheduleTask(tasks.NewOneoffIndexSnapshotsTask(newRepo, time.Now()), tasks.TaskPriorityInteractive+tasks.TaskPriorityIndexSnapshots) zap.L().Debug("done add repo") return connect.NewResponse(c), nil @@ -322,7 +376,7 @@ func (s *BackrestHandler) GetOperationEvents(ctx context.Context, req *connect.R } func (s *BackrestHandler) GetOperations(ctx context.Context, req *connect.Request[v1.GetOperationsRequest]) (*connect.Response[v1.OperationList], error) { - q, err := opSelectorToQuery(req.Msg.Selector) + q, err := protoutil.OpSelectorToQuery(req.Msg.Selector) if req.Msg.LastN != 0 { q.Reversed = true q.Limit = int(req.Msg.LastN) @@ -354,12 +408,16 @@ func (s *BackrestHandler) GetOperations(ctx context.Context, req *connect.Reques } func (s *BackrestHandler) IndexSnapshots(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) { - _, err := s.orchestrator.GetRepo(req.Msg.Value) + // Ensure the repo is valid before scheduling the task + repo, err := s.orchestrator.GetRepo(req.Msg.Value) if err != nil { return nil, fmt.Errorf("failed to get repo %q: %w", req.Msg.Value, err) } - s.orchestrator.ScheduleTask(tasks.NewOneoffIndexSnapshotsTask(req.Msg.Value, time.Now()), tasks.TaskPriorityInteractive+tasks.TaskPriorityIndexSnapshots) + // Schedule the indexing task + if err := s.orchestrator.ScheduleTask(tasks.NewOneoffIndexSnapshotsTask(repo, time.Now()), tasks.TaskPriorityInteractive+tasks.TaskPriorityIndexSnapshots); err != nil { + return nil, fmt.Errorf("failed to schedule indexing task: %w", err) + } return connect.NewResponse(&emptypb.Empty{}), nil } @@ -367,13 +425,19 @@ func (s *BackrestHandler) IndexSnapshots(ctx context.Context, req *connect.Reque func (s *BackrestHandler) Backup(ctx context.Context, req *connect.Request[types.StringValue]) (*connect.Response[emptypb.Empty], error) { plan, err := s.orchestrator.GetPlan(req.Msg.Value) if err != nil { - return nil, fmt.Errorf("failed to get plan %q: %w", req.Msg.Value, err) + return nil, err + } + repo, err := s.orchestrator.GetRepo(plan.Repo) + if err != nil { + return nil, err } wait := make(chan struct{}) - s.orchestrator.ScheduleTask(tasks.NewOneoffBackupTask(plan, time.Now()), tasks.TaskPriorityInteractive, func(e error) { + if err := s.orchestrator.ScheduleTask(tasks.NewOneoffBackupTask(repo, plan, time.Now()), tasks.TaskPriorityInteractive, func(e error) { err = e close(wait) - }) + }); err != nil { + return nil, err + } <-wait return connect.NewResponse(&emptypb.Empty{}), err } @@ -381,23 +445,33 @@ func (s *BackrestHandler) Backup(ctx context.Context, req *connect.Request[types func (s *BackrestHandler) Forget(ctx context.Context, req *connect.Request[v1.ForgetRequest]) (*connect.Response[emptypb.Empty], error) { at := time.Now() var err error + + repo, err := s.orchestrator.GetRepo(req.Msg.RepoId) + if err != nil { + return nil, err + } + if req.Msg.SnapshotId != "" && req.Msg.PlanId != "" && req.Msg.RepoId != "" { wait := make(chan struct{}) - s.orchestrator.ScheduleTask( - tasks.NewOneoffForgetSnapshotTask(req.Msg.RepoId, req.Msg.PlanId, 0, at, req.Msg.SnapshotId), + if err := s.orchestrator.ScheduleTask( + tasks.NewOneoffForgetSnapshotTask(repo, req.Msg.PlanId, 0, at, req.Msg.SnapshotId), tasks.TaskPriorityInteractive+tasks.TaskPriorityForget, func(e error) { err = e close(wait) - }) + }); err != nil { + return nil, err + } <-wait } else if req.Msg.RepoId != "" && req.Msg.PlanId != "" { wait := make(chan struct{}) - s.orchestrator.ScheduleTask( - tasks.NewOneoffForgetTask(req.Msg.RepoId, req.Msg.PlanId, 0, at), + if err := s.orchestrator.ScheduleTask( + tasks.NewOneoffForgetTask(repo, req.Msg.PlanId, 0, at), tasks.TaskPriorityInteractive+tasks.TaskPriorityForget, func(e error) { err = e close(wait) - }) + }); err != nil { + return nil, err + } <-wait } else { return nil, errors.New("must specify repoId and planId and (optionally) snapshotId") @@ -410,23 +484,29 @@ func (s *BackrestHandler) Forget(ctx context.Context, req *connect.Request[v1.Fo func (s BackrestHandler) DoRepoTask(ctx context.Context, req *connect.Request[v1.DoRepoTaskRequest]) (*connect.Response[emptypb.Empty], error) { var task tasks.Task + + repo, err := s.orchestrator.GetRepo(req.Msg.RepoId) + if err != nil { + return nil, err + } + priority := tasks.TaskPriorityInteractive switch req.Msg.Task { case v1.DoRepoTaskRequest_TASK_CHECK: - task = tasks.NewCheckTask(req.Msg.RepoId, tasks.PlanForSystemTasks, true) + task = tasks.NewCheckTask(repo, tasks.PlanForSystemTasks, true) case v1.DoRepoTaskRequest_TASK_PRUNE: - task = tasks.NewPruneTask(req.Msg.RepoId, tasks.PlanForSystemTasks, true) + task = tasks.NewPruneTask(repo, tasks.PlanForSystemTasks, true) priority |= tasks.TaskPriorityPrune case v1.DoRepoTaskRequest_TASK_STATS: - task = tasks.NewStatsTask(req.Msg.RepoId, tasks.PlanForSystemTasks, true) + task = tasks.NewStatsTask(repo, tasks.PlanForSystemTasks, true) priority |= tasks.TaskPriorityStats case v1.DoRepoTaskRequest_TASK_INDEX_SNAPSHOTS: - task = tasks.NewOneoffIndexSnapshotsTask(req.Msg.RepoId, time.Now()) + task = tasks.NewOneoffIndexSnapshotsTask(repo, time.Now()) priority |= tasks.TaskPriorityIndexSnapshots case v1.DoRepoTaskRequest_TASK_UNLOCK: repo, err := s.orchestrator.GetRepoOrchestrator(req.Msg.RepoId) if err != nil { - return nil, fmt.Errorf("failed to get repo %q: %w", req.Msg.RepoId, err) + return nil, err } if err := repo.Unlock(ctx); err != nil { return nil, fmt.Errorf("failed to unlock repo %q: %w", req.Msg.RepoId, err) @@ -436,7 +516,6 @@ func (s BackrestHandler) DoRepoTask(ctx context.Context, req *connect.Request[v1 return nil, fmt.Errorf("unknown task %v", req.Msg.Task.String()) } - var err error wait := make(chan struct{}) if err := s.orchestrator.ScheduleTask(task, priority, func(e error) { err = e @@ -458,22 +537,39 @@ func (s *BackrestHandler) Restore(ctx context.Context, req *connect.Request[v1.R if req.Msg.Path == "" { req.Msg.Path = "/" } - // prevent restoring to a directory that already exists if _, err := os.Stat(req.Msg.Target); err == nil { return nil, fmt.Errorf("target directory %q already exists", req.Msg.Target) } + repo, err := s.orchestrator.GetRepo(req.Msg.RepoId) + if err != nil { + return nil, err + } + at := time.Now() - s.orchestrator.ScheduleTask(tasks.NewOneoffRestoreTask(req.Msg.RepoId, req.Msg.PlanId, 0 /* flowID */, at, req.Msg.SnapshotId, req.Msg.Path, req.Msg.Target), tasks.TaskPriorityInteractive+tasks.TaskPriorityDefault) + s.orchestrator.ScheduleTask(tasks.NewOneoffRestoreTask(repo, req.Msg.PlanId, 0 /* flowID */, at, req.Msg.SnapshotId, req.Msg.Path, req.Msg.Target), tasks.TaskPriorityInteractive+tasks.TaskPriorityDefault) return connect.NewResponse(&emptypb.Empty{}), nil } func (s *BackrestHandler) RunCommand(ctx context.Context, req *connect.Request[v1.RunCommandRequest]) (*connect.Response[types.Int64Value], error) { + cfg, err := s.config.Get() + if err != nil { + return nil, fmt.Errorf("failed to get config: %w", err) + } + repo := config.FindRepo(cfg, req.Msg.RepoId) + if repo == nil { + return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("repo %q not found", req.Msg.RepoId)) + } + // group commands within the last 24 hours (or 256 operations) into the same flow ID var flowID int64 - if s.oplog.Query(oplog.Query{RepoID: req.Msg.RepoId, Limit: 256, Reversed: true}, func(op *v1.Operation) error { + if s.oplog.Query(oplog.Query{}. + SetInstanceID(cfg.Instance). + SetRepoGUID(repo.GetGuid()). + SetLimit(256). + SetReversed(true), func(op *v1.Operation) error { if op.GetOperationRunCommand() != nil && time.Since(time.UnixMilli(op.UnixTimeStartMs)) < 30*time.Minute { flowID = op.FlowId } @@ -482,7 +578,7 @@ func (s *BackrestHandler) RunCommand(ctx context.Context, req *connect.Request[v return nil, fmt.Errorf("failed to query operations") } - task := tasks.NewOneoffRunCommandTask(req.Msg.RepoId, tasks.PlanForSystemTasks, flowID, time.Now(), req.Msg.Command) + task := tasks.NewOneoffRunCommandTask(repo, tasks.PlanForSystemTasks, flowID, time.Now(), req.Msg.Command) st, err := s.orchestrator.CreateUnscheduledTask(task, tasks.TaskPriorityInteractive, time.Now()) if err != nil { return nil, fmt.Errorf("failed to create task: %w", err) @@ -513,7 +609,7 @@ func (s *BackrestHandler) ClearHistory(ctx context.Context, req *connect.Request return nil } - q, err := opSelectorToQuery(req.Msg.Selector) + q, err := protoutil.OpSelectorToQuery(req.Msg.Selector) if err != nil { return nil, err } @@ -590,6 +686,9 @@ func (s *BackrestHandler) GetLogs(ctx context.Context, req *connect.Request[v1.L return nil } if err := resp.Send(&types.BytesValue{Value: data}); err != nil { + bufferMu.Lock() + buffer.Write(data) + bufferMu.Unlock() return err } case err := <-errChan: @@ -726,7 +825,11 @@ func (s *BackrestHandler) GetSummaryDashboard(ctx context.Context, req *connect. } for _, repo := range config.Repos { - resp, err := generateSummaryHelper(repo.Id, oplog.Query{RepoID: repo.Id, Reversed: true, Limit: 1000}) + resp, err := generateSummaryHelper(repo.Id, oplog.Query{}. + SetInstanceID(config.Instance). + SetRepoGUID(repo.GetGuid()). + SetReversed(true). + SetLimit(1000)) if err != nil { return nil, fmt.Errorf("summary for repo %q: %w", repo.Id, err) } @@ -735,7 +838,11 @@ func (s *BackrestHandler) GetSummaryDashboard(ctx context.Context, req *connect. } for _, plan := range config.Plans { - resp, err := generateSummaryHelper(plan.Id, oplog.Query{PlanID: plan.Id, Reversed: true, Limit: 1000}) + resp, err := generateSummaryHelper(plan.Id, oplog.Query{}. + SetInstanceID(config.Instance). + SetPlanID(plan.Id). + SetReversed(true). + SetLimit(1000)) if err != nil { return nil, fmt.Errorf("summary for plan %q: %w", plan.Id, err) } @@ -745,20 +852,3 @@ func (s *BackrestHandler) GetSummaryDashboard(ctx context.Context, req *connect. return connect.NewResponse(response), nil } - -func opSelectorToQuery(sel *v1.OpSelector) (oplog.Query, error) { - if sel == nil { - return oplog.Query{}, errors.New("empty selector") - } - q := oplog.Query{ - RepoID: sel.RepoId, - PlanID: sel.PlanId, - SnapshotID: sel.SnapshotId, - FlowID: sel.FlowId, - } - if len(sel.Ids) > 0 && !reflect.DeepEqual(q, oplog.Query{}) { - return oplog.Query{}, errors.New("cannot specify both query and ids") - } - q.OpIDs = sel.Ids - return q, nil -} diff --git a/internal/api/backresthandler_test.go b/internal/api/backresthandler_test.go index 68398f5..60a406d 100644 --- a/internal/api/backresthandler_test.go +++ b/internal/api/backresthandler_test.go @@ -18,12 +18,15 @@ import ( "connectrpc.com/connect" "github.com/garethgeorge/backrest/gen/go/types" v1 "github.com/garethgeorge/backrest/gen/go/v1" + syncapi "github.com/garethgeorge/backrest/internal/api/syncapi" "github.com/garethgeorge/backrest/internal/config" + "github.com/garethgeorge/backrest/internal/cryptoutil" "github.com/garethgeorge/backrest/internal/logstore" "github.com/garethgeorge/backrest/internal/oplog" - "github.com/garethgeorge/backrest/internal/oplog/bboltstore" + "github.com/garethgeorge/backrest/internal/oplog/sqlitestore" "github.com/garethgeorge/backrest/internal/orchestrator" "github.com/garethgeorge/backrest/internal/resticinstaller" + "github.com/garethgeorge/backrest/internal/testutil" "golang.org/x/sync/errgroup" "google.golang.org/protobuf/proto" ) @@ -77,7 +80,10 @@ func TestUpdateConfig(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - res, err := sut.handler.SetConfig(context.Background(), connect.NewRequest(tt.req)) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + res, err := sut.handler.SetConfig(ctx, connect.NewRequest(tt.req)) if (err != nil) != tt.wantErr { t.Errorf("SetConfig() error = %v, wantErr %v", err, tt.wantErr) return @@ -100,6 +106,7 @@ func TestBackup(t *testing.T) { Repos: []*v1.Repo{ { Id: "local", + Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits), Uri: t.TempDir(), Password: "test", Flags: []string{"--no-cache"}, @@ -123,28 +130,34 @@ func TestBackup(t *testing.T) { }, }) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testutil.WithDeadlineFromTest(t, context.Background()) defer cancel() go func() { sut.orch.Run(ctx) }() - _, err := sut.handler.Backup(context.Background(), connect.NewRequest(&types.StringValue{Value: "test"})) + _, err := sut.handler.Backup(ctx, connect.NewRequest(&types.StringValue{Value: "test"})) if err != nil { t.Fatalf("Backup() error = %v", err) } - // Check that there is a successful backup recorded in the log. - if slices.IndexFunc(getOperations(t, sut.oplog), func(op *v1.Operation) bool { - _, ok := op.GetOp().(*v1.Operation_OperationBackup) - return op.Status == v1.OperationStatus_STATUS_SUCCESS && ok - }) == -1 { - t.Fatalf("Expected a backup operation") + // Wait for the backup to complete. + if err := testutil.Retry(t, ctx, func() error { + ops := getOperations(t, sut.oplog) + if slices.IndexFunc(ops, func(op *v1.Operation) bool { + _, ok := op.GetOp().(*v1.Operation_OperationBackup) + return op.Status == v1.OperationStatus_STATUS_SUCCESS && ok + }) == -1 { + return fmt.Errorf("expected a backup operation, got %v", ops) + } + return nil + }); err != nil { + t.Fatalf("Couldn't find backup operation in oplog") } // Wait for the index snapshot operation to appear in the oplog. var snapshotOp *v1.Operation - if err := retry(t, 10, 1*time.Second, func() error { + if err := testutil.Retry(t, ctx, func() error { operations := getOperations(t, sut.oplog) if index := slices.IndexFunc(operations, func(op *v1.Operation) bool { _, ok := op.GetOp().(*v1.Operation_OperationIndexSnapshot) @@ -163,7 +176,7 @@ func TestBackup(t *testing.T) { } // Wait for a forget operation to appear in the oplog. - if err := retry(t, 10, 1*time.Second, func() error { + if err := testutil.Retry(t, ctx, func() error { operations := getOperations(t, sut.oplog) if index := slices.IndexFunc(operations, func(op *v1.Operation) bool { _, ok := op.GetOp().(*v1.Operation_OperationForget) @@ -191,6 +204,7 @@ func TestMultipleBackup(t *testing.T) { Repos: []*v1.Repo{ { Id: "local", + Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits), Uri: t.TempDir(), Password: "test", Flags: []string{"--no-cache"}, @@ -216,21 +230,22 @@ func TestMultipleBackup(t *testing.T) { }, }) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testutil.WithDeadlineFromTest(t, context.Background()) defer cancel() + go func() { sut.orch.Run(ctx) }() for i := 0; i < 2; i++ { - _, err := sut.handler.Backup(context.Background(), connect.NewRequest(&types.StringValue{Value: "test"})) + _, err := sut.handler.Backup(ctx, connect.NewRequest(&types.StringValue{Value: "test"})) if err != nil { t.Fatalf("Backup() error = %v", err) } } // Wait for a forget that removed 1 snapshot to appear in the oplog - if err := retry(t, 10, 1*time.Second, func() error { + if err := testutil.Retry(t, ctx, func() error { operations := getOperations(t, sut.oplog) if index := slices.IndexFunc(operations, func(op *v1.Operation) bool { forget, ok := op.GetOp().(*v1.Operation_OperationForget) @@ -266,6 +281,7 @@ func TestHookExecution(t *testing.T) { Repos: []*v1.Repo{ { Id: "local", + Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits), Uri: t.TempDir(), Password: "test", Flags: []string{"--no-cache"}, @@ -308,19 +324,19 @@ func TestHookExecution(t *testing.T) { }, }) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testutil.WithDeadlineFromTest(t, context.Background()) defer cancel() go func() { sut.orch.Run(ctx) }() - _, err := sut.handler.Backup(context.Background(), connect.NewRequest(&types.StringValue{Value: "test"})) + _, err := sut.handler.Backup(ctx, connect.NewRequest(&types.StringValue{Value: "test"})) if err != nil { t.Fatalf("Backup() error = %v", err) } // Wait for two hook operations to appear in the oplog - if err := retry(t, 10, 1*time.Second, func() error { + if err := testutil.Retry(t, ctx, func() error { hookOps := slices.DeleteFunc(getOperations(t, sut.oplog), func(op *v1.Operation) bool { _, ok := op.GetOp().(*v1.Operation_OperationRunHook) return !ok @@ -358,6 +374,7 @@ func TestHookOnErrorHandling(t *testing.T) { Repos: []*v1.Repo{ { Id: "local", + Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits), Uri: t.TempDir(), Password: "test", Flags: []string{"--no-cache"}, @@ -460,7 +477,7 @@ func TestHookOnErrorHandling(t *testing.T) { }, }) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testutil.WithDeadlineFromTest(t, context.Background()) defer cancel() go func() { sut.orch.Run(ctx) @@ -522,12 +539,12 @@ func TestHookOnErrorHandling(t *testing.T) { if !tc.noWaitForBackup { if err := errgroup.Wait(); err != nil { - t.Fatalf(err.Error()) + t.Fatalf("%s", err.Error()) } } // Wait for hook operation to be attempted in the oplog - if err := retry(t, 10, 1*time.Second, func() error { + if err := testutil.Retry(t, ctx, func() error { hookOps := slices.DeleteFunc(getOperations(t, sut.oplog), func(op *v1.Operation) bool { _, ok := op.GetOp().(*v1.Operation_OperationRunHook) return !ok @@ -573,6 +590,7 @@ func TestCancelBackup(t *testing.T) { Repos: []*v1.Repo{ { Id: "local", + Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits), Uri: t.TempDir(), Password: "test", Flags: []string{"--no-cache"}, @@ -610,26 +628,26 @@ func TestCancelBackup(t *testing.T) { }, }) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testutil.WithDeadlineFromTest(t, context.Background()) defer cancel() go func() { sut.orch.Run(ctx) }() - // Start a backup - var errgroup errgroup.Group - errgroup.Go(func() error { - backupReq := connect.NewRequest(&types.StringValue{Value: "test"}) - _, err := sut.handler.Backup(context.Background(), backupReq) - return err - }) + go func() { + backupReq := &types.StringValue{Value: "test"} + _, err := sut.handler.Backup(ctx, connect.NewRequest(backupReq)) + if err != nil { + t.Logf("Backup() error = %v", err) + } + }() - // Find the backup operation ID in the oplog + // Find the in-progress backup operation ID in the oplog, waits for the task to be in progress before attempting to cancel. var backupOpId int64 - if err := retry(t, 100, 100*time.Millisecond, func() error { + if err := testutil.Retry(t, ctx, func() error { operations := getOperations(t, sut.oplog) for _, op := range operations { - if op.GetOperationBackup() != nil { + if op.GetOperationBackup() != nil && op.Status == v1.OperationStatus_STATUS_INPROGRESS { backupOpId = op.Id return nil } @@ -643,7 +661,7 @@ func TestCancelBackup(t *testing.T) { t.Errorf("Cancel() error = %v, wantErr nil", err) } - if err := retry(t, 10, 1*time.Second, func() error { + if err := testutil.Retry(t, ctx, func() error { if slices.IndexFunc(getOperations(t, sut.oplog), func(op *v1.Operation) bool { _, ok := op.GetOp().(*v1.Operation_OperationBackup) return op.Status == v1.OperationStatus_STATUS_ERROR && ok @@ -671,6 +689,7 @@ func TestRestore(t *testing.T) { Repos: []*v1.Repo{ { Id: "local", + Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits), Uri: t.TempDir(), Password: "test", Flags: []string{"--no-cache"}, @@ -694,28 +713,34 @@ func TestRestore(t *testing.T) { }, }) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testutil.WithDeadlineFromTest(t, context.Background()) defer cancel() go func() { sut.orch.Run(ctx) }() - _, err := sut.handler.Backup(context.Background(), connect.NewRequest(&types.StringValue{Value: "test"})) + _, err := sut.handler.Backup(ctx, connect.NewRequest(&types.StringValue{Value: "test"})) if err != nil { t.Fatalf("Backup() error = %v", err) } - // Check that there is a successful backup recorded in the log. - if slices.IndexFunc(getOperations(t, sut.oplog), func(op *v1.Operation) bool { - _, ok := op.GetOp().(*v1.Operation_OperationBackup) - return op.Status == v1.OperationStatus_STATUS_SUCCESS && ok - }) == -1 { - t.Fatalf("Expected a backup operation") + // Wait for the backup to complete. + if err := testutil.Retry(t, ctx, func() error { + // Check that there is a successful backup recorded in the log. + if slices.IndexFunc(getOperations(t, sut.oplog), func(op *v1.Operation) bool { + _, ok := op.GetOp().(*v1.Operation_OperationBackup) + return op.Status == v1.OperationStatus_STATUS_SUCCESS && ok + }) == -1 { + return errors.New("Expected a backup operation") + } + return nil + }); err != nil { + t.Fatalf("Couldn't find backup operation in oplog") } // Wait for the index snapshot operation to appear in the oplog. var snapshotOp *v1.Operation - if err := retry(t, 10, 2*time.Second, func() error { + if err := testutil.Retry(t, ctx, func() error { operations := getOperations(t, sut.oplog) if index := slices.IndexFunc(operations, func(op *v1.Operation) bool { _, ok := op.GetOp().(*v1.Operation_OperationIndexSnapshot) @@ -735,7 +760,7 @@ func TestRestore(t *testing.T) { restoreTarget := t.TempDir() + "/restore" - _, err = sut.handler.Restore(context.Background(), connect.NewRequest(&v1.RestoreSnapshotRequest{ + _, err = sut.handler.Restore(ctx, connect.NewRequest(&v1.RestoreSnapshotRequest{ SnapshotId: snapshotOp.SnapshotId, PlanId: "test", RepoId: "local", @@ -746,7 +771,7 @@ func TestRestore(t *testing.T) { } // Wait for a restore operation to appear in the oplog. - if err := retry(t, 10, 2*time.Second, func() error { + if err := testutil.Retry(t, ctx, func() error { operations := getOperations(t, sut.oplog) if index := slices.IndexFunc(operations, func(op *v1.Operation) bool { _, ok := op.GetOp().(*v1.Operation_OperationRestore) @@ -788,6 +813,7 @@ func TestRunCommand(t *testing.T) { Repos: []*v1.Repo{ { Id: "local", + Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits), Uri: t.TempDir(), Password: "test", Flags: []string{"--no-cache"}, @@ -796,8 +822,11 @@ func TestRunCommand(t *testing.T) { }, }) - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := testutil.WithDeadlineFromTest(t, context.Background()) defer cancel() + go func() { + sut.orch.Run(ctx) + }() res, err := sut.handler.RunCommand(ctx, connect.NewRequest(&v1.RunCommandRequest{ RepoId: "local", @@ -844,7 +873,7 @@ func TestRunCommand(t *testing.T) { type systemUnderTest struct { handler *BackrestHandler oplog *oplog.OpLog - opstore *bboltstore.BboltStore + opstore *sqlitestore.SqliteStore orch *orchestrator.Orchestrator logStore *logstore.LogStore config *v1.Config @@ -858,13 +887,14 @@ func createSystemUnderTest(t *testing.T, config config.ConfigStore) systemUnderT t.Fatalf("Failed to get config: %v", err) } + remoteConfigStore := syncapi.NewJSONDirRemoteConfigStore(dir) resticBin, err := resticinstaller.FindOrInstallResticBinary() if err != nil { t.Fatalf("Failed to find or install restic binary: %v", err) } - opstore, err := bboltstore.NewBboltStore(filepath.Join(dir, "oplog.bbolt")) + opstore, err := sqlitestore.NewSqliteStore(filepath.Join(dir, "oplog.sqlite")) if err != nil { - t.Fatalf("Failed to create oplog store: %v", err) + t.Fatalf("Failed to create opstore: %v", err) } t.Cleanup(func() { opstore.Close() }) oplog, err := oplog.NewOpLog(opstore) @@ -894,7 +924,7 @@ func createSystemUnderTest(t *testing.T, config config.ConfigStore) systemUnderT } } - h := NewBackrestHandler(config, orch, oplog, logStore) + h := NewBackrestHandler(config, remoteConfigStore, orch, oplog, logStore) return systemUnderTest{ handler: h, @@ -906,25 +936,10 @@ func createSystemUnderTest(t *testing.T, config config.ConfigStore) systemUnderT } } -func retry(t *testing.T, times int, backoff time.Duration, f func() error) error { - t.Helper() - var err error - for i := 0; i < times; i++ { - err = f() - if err == nil { - return nil - } - time.Sleep(backoff) - } - return err -} - func getOperations(t *testing.T, log *oplog.OpLog) []*v1.Operation { - t.Logf("Reading oplog at time %v", time.Now()) operations := []*v1.Operation{} if err := log.Query(oplog.SelectAll, func(op *v1.Operation) error { operations = append(operations, op) - t.Logf("operation %t status %s", op.GetOp(), op.Status) return nil }); err != nil { t.Fatalf("Failed to read oplog: %v", err) diff --git a/internal/api/syncapi/identity.go b/internal/api/syncapi/identity.go new file mode 100644 index 0000000..b67eced --- /dev/null +++ b/internal/api/syncapi/identity.go @@ -0,0 +1,123 @@ +package syncapi + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" + "os" +) + +var ( + curve = elliptic.P256() // ed25519 +) + +type Identity struct { + InstanceID string + credentialFile string + + privateKey *ecdsa.PrivateKey + publicKey *ecdsa.PublicKey +} + +func NewIdentity(instanceID, credentialFile string) (*Identity, error) { + i := &Identity{ + InstanceID: instanceID, + credentialFile: credentialFile, + } + if err := i.loadOrGenerateKey(); err != nil { + return nil, err + } + return i, nil +} + +func (i *Identity) loadOrGenerateKey() error { + privKeyBytes, errpriv := os.ReadFile(i.credentialFile) + pubKeyBytes, errpub := os.ReadFile(i.credentialFile + ".pub") + if errpriv != nil || errpub != nil { + if os.IsNotExist(errpriv) || os.IsNotExist(errpub) { + return i.generateKeys() + } + if errpriv != nil { + return fmt.Errorf("open private key: %w", errpriv) + } + if errpub != nil { + return fmt.Errorf("open public key: %w", errpub) + } + } + + privKeyBlock, _ := pem.Decode(privKeyBytes) + if privKeyBlock == nil { + return errors.New("no private key found in pem") + } + privKey, err := x509.ParseECPrivateKey(privKeyBlock.Bytes) + if err != nil { + return fmt.Errorf("parse private key: %w", err) + } + + pubKeyBlock, _ := pem.Decode(pubKeyBytes) + if pubKeyBlock == nil { + return errors.New("no public key found in pem") + } + pubKey, err := x509.ParsePKIXPublicKey(pubKeyBytes) + if err != nil { + return fmt.Errorf("parse public key: %w", err) + } + + i.privateKey = privKey + i.publicKey = pubKey.(*ecdsa.PublicKey) + + return nil +} + +func (i *Identity) generateKeys() error { + privKey, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + return err + } + + i.privateKey = privKey + i.publicKey = &privKey.PublicKey + + privateKeyBytes, err := x509.MarshalECPrivateKey(i.privateKey) + if err != nil { + return fmt.Errorf("marshal private key: %w", err) + } + pemPrivateKeyBytes := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE", Bytes: privateKeyBytes}) + if err := os.WriteFile(i.credentialFile, pemPrivateKeyBytes, 0600); err != nil { + return fmt.Errorf("write private key: %w", err) + } + + publicKeyBytes, err := x509.MarshalPKIXPublicKey(&i.privateKey.PublicKey) + if err != nil { + return fmt.Errorf("marshal public key: %w", err) + } + pemPublicKeyBytes := pem.EncodeToMemory(&pem.Block{Type: "EC PUBLIC", Bytes: publicKeyBytes}) + if err := os.WriteFile(i.credentialFile+".pub", pemPublicKeyBytes, 0600); err != nil { + return fmt.Errorf("write public key: %w", err) + } + + return nil +} + +func (i *Identity) SignMessage(message []byte) ([]byte, error) { + hash := sha256.Sum256(message) + + sig, err := ecdsa.SignASN1(rand.Reader, i.privateKey, hash[:]) + if err != nil { + return nil, err + } + return sig, nil +} + +func (i *Identity) VerifySignature(message, sig []byte) error { + hash := sha256.Sum256(message) + if !ecdsa.VerifyASN1(i.publicKey, hash[:], sig) { + return errors.New("signature verification failed") + } + return nil +} diff --git a/internal/api/syncapi/identity_test.go b/internal/api/syncapi/identity_test.go new file mode 100644 index 0000000..4a56c58 --- /dev/null +++ b/internal/api/syncapi/identity_test.go @@ -0,0 +1,29 @@ +package syncapi + +import ( + "fmt" + "os" + "path/filepath" + "testing" +) + +func TestIdentity(t *testing.T) { + dir := t.TempDir() + + // Create a new identity + ident, err := NewIdentity("test-instance", filepath.Join(dir, "myidentity.pem")) + if err != nil { + t.Fatalf("failed to create identity: %v", err) + } + + signature, err := ident.SignMessage([]byte("hello world!")) + fmt.Printf("signed message: %x\n", signature) + + // Load and print identity file + bytes, _ := os.ReadFile(filepath.Join(dir, "myidentity.pem")) + t.Log(string(bytes)) + + // Load and print public key file + bytes, _ = os.ReadFile(filepath.Join(dir, "myidentity.pem.pub")) + t.Log(string(bytes)) +} diff --git a/internal/api/syncapi/remoteconfigstore.go b/internal/api/syncapi/remoteconfigstore.go new file mode 100644 index 0000000..49052ed --- /dev/null +++ b/internal/api/syncapi/remoteconfigstore.go @@ -0,0 +1,170 @@ +package syncapi + +import ( + "errors" + "fmt" + "hash/crc32" + "os" + "path/filepath" + "regexp" + "strings" + "sync" + + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "google.golang.org/protobuf/encoding/protojson" +) + +var ( + sanitizeFilenameRegex = regexp.MustCompile("[^a-zA-Z0-9\\-_\\.]+") + + ErrRemoteConfigNotFound = errors.New("remote config not found") +) + +type RemoteConfigStore interface { + // Get a remote config for the given instance ID. + Get(instanceID string) (*v1.RemoteConfig, error) + // Update or create a remote config for the given instance ID. + Update(instanceID string, config *v1.RemoteConfig) error + // Delete a remote config for the given instance ID. + Delete(instanceID string) error +} + +type jsonDirRemoteConfigStore struct { + mu sync.Mutex + dir string + cache map[string]*v1.RemoteConfig +} + +func NewJSONDirRemoteConfigStore(dir string) RemoteConfigStore { + return &jsonDirRemoteConfigStore{ + dir: dir, + cache: make(map[string]*v1.RemoteConfig), + } +} + +func (s *jsonDirRemoteConfigStore) Get(instanceID string) (*v1.RemoteConfig, error) { + s.mu.Lock() + defer s.mu.Unlock() + if instanceID == "" { + return nil, errors.New("instanceID is required") + } + + if config, ok := s.cache[instanceID]; ok { + return config, nil + } + + file := s.fileForInstance(instanceID) + data, err := os.ReadFile(file) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, ErrRemoteConfigNotFound + } + return nil, fmt.Errorf("read config file: %w", err) + } + + var config v1.RemoteConfig + if err = (protojson.UnmarshalOptions{DiscardUnknown: true}).Unmarshal(data, &config); err != nil { + return nil, fmt.Errorf("unmarshal config: %w", err) + } + + s.cache[instanceID] = &config + return &config, nil +} + +func (s *jsonDirRemoteConfigStore) Update(instanceID string, config *v1.RemoteConfig) error { + s.mu.Lock() + defer s.mu.Unlock() + if instanceID == "" { + return errors.New("instanceID is required") + } + + file := s.fileForInstance(instanceID) + data, err := protojson.MarshalOptions{ + Indent: " ", + Multiline: true, + }.Marshal(config) + if err != nil { + return fmt.Errorf("marshal config: %w", err) + } + err = os.MkdirAll(filepath.Dir(file), 0755) + if err != nil { + return fmt.Errorf("create config directory: %w", err) + } + + err = os.WriteFile(file, data, 0600) + if err != nil { + return fmt.Errorf("write config file: %w", err) + } + + s.cache[instanceID] = config + return nil +} + +func (s *jsonDirRemoteConfigStore) Delete(instanceID string) error { + s.mu.Lock() + defer s.mu.Unlock() + if instanceID == "" { + return errors.New("instanceID is required") + } + + file := s.fileForInstance(instanceID) + if err := os.Remove(file); err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("remove config file: %w", err) + } + + delete(s.cache, instanceID) + return nil +} + +func (s *jsonDirRemoteConfigStore) fileForInstance(instanceID string) string { + safeInstanceID := strings.Replace(instanceID, "..", ".", -1) + safeInstanceID = sanitizeFilenameRegex.ReplaceAllString(safeInstanceID, "_") + checksum := crc32.ChecksumIEEE([]byte(instanceID)) // checksum eliminates collisions in the case of replacing characters. + return filepath.Join(s.dir, fmt.Sprintf("%s-%08x.json", safeInstanceID, checksum)) +} + +type memoryConfigStore struct { + configs map[string]*v1.RemoteConfig +} + +func newMemoryConfigStore() *memoryConfigStore { + return &memoryConfigStore{ + configs: make(map[string]*v1.RemoteConfig), + } +} + +func (s *memoryConfigStore) Get(instanceID string) (*v1.RemoteConfig, error) { + if config, ok := s.configs[instanceID]; ok { + return config, nil + } + return nil, ErrRemoteConfigNotFound +} + +func (s *memoryConfigStore) Update(instanceID string, config *v1.RemoteConfig) error { + if instanceID == "" { + return errors.New("instanceID is required") + } + s.configs[instanceID] = config + return nil +} + +func (s *memoryConfigStore) Delete(instanceID string) error { + if instanceID == "" { + return errors.New("instanceID is required") + } + delete(s.configs, instanceID) + return nil +} + +func GetRepoConfig(store RemoteConfigStore, instanceID, repoID string) (*v1.RemoteRepo, error) { + config, err := store.Get(instanceID) + if err != nil { + return nil, fmt.Errorf("get %q: %w", instanceID, err) + } + for _, repo := range config.Repos { + if repo.Id == repoID { + return repo, nil + } + } + return nil, fmt.Errorf("get %q/%q: %w", instanceID, repoID, ErrRemoteConfigNotFound) +} diff --git a/internal/api/syncapi/syncapi_test.go b/internal/api/syncapi/syncapi_test.go new file mode 100644 index 0000000..29b123c --- /dev/null +++ b/internal/api/syncapi/syncapi_test.go @@ -0,0 +1,667 @@ +package syncapi + +import ( + "context" + "errors" + "fmt" + "net" + "net/http" + "path/filepath" + "slices" + "sync" + "testing" + "time" + + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/gen/go/v1/v1connect" + "github.com/garethgeorge/backrest/internal/config" + "github.com/garethgeorge/backrest/internal/cryptoutil" + "github.com/garethgeorge/backrest/internal/logstore" + "github.com/garethgeorge/backrest/internal/oplog" + "github.com/garethgeorge/backrest/internal/oplog/sqlitestore" + "github.com/garethgeorge/backrest/internal/orchestrator" + "github.com/garethgeorge/backrest/internal/resticinstaller" + "github.com/garethgeorge/backrest/internal/testutil" + "github.com/google/go-cmp/cmp" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" +) + +const ( + defaultClientID = "test-client" + defaultHostID = "test-host" + defaultRepoID = "test-repo" + defaultPlanID = "test-plan" +) + +var ( + defaultRepoGUID = cryptoutil.MustRandomID(cryptoutil.DefaultIDBits) +) + +var ( + basicHostOperationTempl = &v1.Operation{ + InstanceId: defaultHostID, + RepoId: defaultRepoID, + RepoGuid: defaultRepoGUID, + PlanId: defaultPlanID, + UnixTimeStartMs: 1234, + UnixTimeEndMs: 5678, + Status: v1.OperationStatus_STATUS_SUCCESS, + Op: &v1.Operation_OperationBackup{}, + } + + basicClientOperationTempl = &v1.Operation{ + InstanceId: defaultClientID, + RepoId: defaultRepoID, + RepoGuid: defaultRepoGUID, + PlanId: defaultPlanID, + UnixTimeStartMs: 1234, + UnixTimeEndMs: 5678, + Status: v1.OperationStatus_STATUS_SUCCESS, + Op: &v1.Operation_OperationBackup{}, + } +) + +func TestConnectionSucceeds(t *testing.T) { + testutil.InstallZapLogger(t) + ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) + + peerHostAddr := allocBindAddrForTest(t) + peerClientAddr := allocBindAddrForTest(t) + + peerHostConfig := &v1.Config{ + Instance: defaultHostID, + Repos: []*v1.Repo{}, + Multihost: &v1.Multihost{ + AuthorizedClients: []*v1.Multihost_Peer{ + { + InstanceId: defaultClientID, + }, + }, + }, + } + + peerClientConfig := &v1.Config{ + Instance: defaultClientID, + Repos: []*v1.Repo{}, + Multihost: &v1.Multihost{ + KnownHosts: []*v1.Multihost_Peer{ + { + InstanceId: defaultHostID, + InstanceUrl: fmt.Sprintf("http://%s", peerHostAddr), + }, + }, + }, + } + + peerHost := newPeerUnderTest(t, peerHostConfig) + peerClient := newPeerUnderTest(t, peerClientConfig) + + startRunningSyncAPI(t, peerHost, peerHostAddr) + startRunningSyncAPI(t, peerClient, peerClientAddr) + + tryConnect(t, ctx, peerClient, defaultHostID) +} + +func TestSyncConfigChange(t *testing.T) { + testutil.InstallZapLogger(t) + ctx, _ := context.WithTimeout(context.Background(), 10*time.Second) + + peerHostAddr := allocBindAddrForTest(t) + peerClientAddr := allocBindAddrForTest(t) + + peerHostConfig := &v1.Config{ + Instance: defaultHostID, + Repos: []*v1.Repo{ + { + Id: defaultRepoID, + Guid: defaultRepoGUID, + AllowedPeerInstanceIds: []string{defaultClientID}, + }, + { + Id: "do-not-sync", + Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits), + AllowedPeerInstanceIds: []string{"some-other-client"}, + }, + }, + Multihost: &v1.Multihost{ + AuthorizedClients: []*v1.Multihost_Peer{ + { + InstanceId: defaultClientID, + }, + }, + }, + } + + peerClientConfig := &v1.Config{ + Instance: defaultClientID, + Repos: []*v1.Repo{ + { + Id: defaultRepoID, + Guid: defaultRepoGUID, + Uri: "backrest://" + defaultHostID, // TODO: get rid of the :// requirement + }, + }, + Multihost: &v1.Multihost{ + KnownHosts: []*v1.Multihost_Peer{ + { + InstanceId: defaultHostID, + InstanceUrl: fmt.Sprintf("http://%s", peerHostAddr), + }, + }, + }, + } + + peerHost := newPeerUnderTest(t, peerHostConfig) + peerClient := newPeerUnderTest(t, peerClientConfig) + + startRunningSyncAPI(t, peerHost, peerHostAddr) + startRunningSyncAPI(t, peerClient, peerClientAddr) + + tryConnect(t, ctx, peerClient, defaultHostID) + + // wait for the initial config to propagate + tryExpectConfig(t, ctx, peerClient, defaultHostID, &v1.RemoteConfig{ + Repos: []*v1.RemoteRepo{ + { + Id: defaultRepoID, + Guid: defaultRepoGUID, + }, + }, + }) + hostConfigChanged := proto.Clone(peerHostConfig).(*v1.Config) + hostConfigChanged.Repos[0].Env = []string{"SOME_ENV=VALUE"} + peerHost.configMgr.Update(hostConfigChanged) + + tryExpectConfig(t, ctx, peerClient, defaultHostID, &v1.RemoteConfig{ + Repos: []*v1.RemoteRepo{ + { + Id: defaultRepoID, + Guid: defaultRepoGUID, + Env: []string{"SOME_ENV=VALUE"}, + }, + }, + }) +} + +func TestSimpleOperationSync(t *testing.T) { + testutil.InstallZapLogger(t) + ctx, _ := context.WithTimeout(context.Background(), 5*time.Second) + + peerHostAddr := allocBindAddrForTest(t) + peerClientAddr := allocBindAddrForTest(t) + + peerHostConfig := &v1.Config{ + Instance: defaultHostID, + Repos: []*v1.Repo{ + { + Id: defaultRepoID, + Guid: defaultRepoGUID, + AllowedPeerInstanceIds: []string{defaultClientID}, + }, + }, + Multihost: &v1.Multihost{ + AuthorizedClients: []*v1.Multihost_Peer{ + { + InstanceId: defaultClientID, + }, + }, + }, + } + + peerClientConfig := &v1.Config{ + Instance: defaultClientID, + Repos: []*v1.Repo{ + { + Id: defaultRepoID, + Guid: defaultRepoGUID, + Uri: "backrest://" + defaultHostID, // TODO: get rid of the :// requirement + }, + }, + Multihost: &v1.Multihost{ + KnownHosts: []*v1.Multihost_Peer{ + { + InstanceId: defaultHostID, + InstanceUrl: fmt.Sprintf("http://%s", peerHostAddr), + }, + }, + }, + } + + peerHost := newPeerUnderTest(t, peerHostConfig) + peerClient := newPeerUnderTest(t, peerClientConfig) + + peerHost.oplog.Add(testutil.OperationsWithDefaults(basicHostOperationTempl, []*v1.Operation{ + { + DisplayMessage: "hostop1", + }, + })...) + peerHost.oplog.Add(testutil.OperationsWithDefaults(basicClientOperationTempl, []*v1.Operation{ + { + DisplayMessage: "clientop-missing", + OriginalId: 1234, // must be an ID that doesn't exist remotely + }, + })...) + + if err := peerClient.oplog.Add(testutil.OperationsWithDefaults(basicClientOperationTempl, []*v1.Operation{ + { + DisplayMessage: "clientop1", + FlowId: 1, + }, + { + DisplayMessage: "clientop2", + FlowId: 1, + }, + { + DisplayMessage: "clientop3", + FlowId: 2, // in a different flow from the other two + }, + })...); err != nil { + t.Fatalf("failed to add operations: %v", err) + } + + startRunningSyncAPI(t, peerHost, peerHostAddr) + startRunningSyncAPI(t, peerClient, peerClientAddr) + + tryConnect(t, ctx, peerClient, defaultHostID) + + tryExpectOperationsSynced(t, ctx, peerHost, peerClient, oplog.Query{}.SetInstanceID(defaultClientID).SetRepoGUID(defaultRepoGUID), "host and client should be synced") + tryExpectExactOperations(t, ctx, peerHost, oplog.Query{}.SetInstanceID(defaultClientID).SetRepoGUID(defaultRepoGUID), + testutil.OperationsWithDefaults(basicClientOperationTempl, []*v1.Operation{ + { + Id: 3, // b/c of the already inserted host ops the sync'd ops start at 3 + FlowId: 3, + OriginalId: 1, + OriginalFlowId: 1, + DisplayMessage: "clientop1", + }, + { + Id: 4, + FlowId: 3, + OriginalId: 2, + OriginalFlowId: 1, + DisplayMessage: "clientop2", + }, + { + Id: 5, + FlowId: 5, + OriginalId: 3, + OriginalFlowId: 2, + DisplayMessage: "clientop3", + }, + }), "host and client should be synced") +} + +func TestSyncMutations(t *testing.T) { + testutil.InstallZapLogger(t) + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + peerHostAddr := allocBindAddrForTest(t) + peerClientAddr := allocBindAddrForTest(t) + + peerHostConfig := &v1.Config{ + Instance: defaultHostID, + Repos: []*v1.Repo{ + { + Id: defaultRepoID, + Guid: defaultRepoGUID, + AllowedPeerInstanceIds: []string{defaultClientID}, + }, + }, + Multihost: &v1.Multihost{ + AuthorizedClients: []*v1.Multihost_Peer{ + { + InstanceId: defaultClientID, + }, + }, + }, + } + + peerClientConfig := &v1.Config{ + Instance: defaultClientID, + Repos: []*v1.Repo{ + { + Id: defaultRepoID, + Guid: defaultRepoGUID, + Uri: "backrest://" + defaultHostID, // TODO: get rid of the :// requirement + }, + }, + Multihost: &v1.Multihost{ + KnownHosts: []*v1.Multihost_Peer{ + { + InstanceId: defaultHostID, + InstanceUrl: fmt.Sprintf("http://%s", peerHostAddr), + }, + }, + }, + } + + peerHost := newPeerUnderTest(t, peerHostConfig) + peerClient := newPeerUnderTest(t, peerClientConfig) + + op := testutil.OperationsWithDefaults(basicClientOperationTempl, []*v1.Operation{ + { + DisplayMessage: "clientop1", + }, + })[0] + + if err := peerClient.oplog.Add(op); err != nil { + t.Fatalf("failed to add operations: %v", err) + } + + syncCtx, cancelSyncCtx := context.WithCancel(ctx) + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + runSyncAPIWithCtx(syncCtx, peerHost, peerHostAddr) + }() + go func() { + defer wg.Done() + runSyncAPIWithCtx(syncCtx, peerClient, peerClientAddr) + }() + tryConnect(t, ctx, peerClient, defaultHostID) + + tryExpectOperationsSynced(t, ctx, peerClient, peerHost, oplog.Query{}.SetRepoGUID(defaultRepoGUID), "host and client should sync initially") + + op.DisplayMessage = "clientop1-mod-while-online" + if err := peerClient.oplog.Update(op); err != nil { + t.Fatalf("failed to update operation: %v", err) + } + + tryExpectExactOperations(t, ctx, peerHost, oplog.Query{}.SetRepoGUID(defaultRepoGUID), + testutil.OperationsWithDefaults(basicClientOperationTempl, []*v1.Operation{ + { + Id: 1, + DisplayMessage: "clientop1-mod-while-online", + OriginalFlowId: 1, + OriginalId: 1, + FlowId: 1, + }, + }), "host and client should sync online edits") + + // Wait for shutdown + cancelSyncCtx() + wg.Wait() + + // Now make an offline edit + op.DisplayMessage = "clientop1-mod-while-offline" + if err := peerClient.oplog.Update(op); err != nil { + t.Fatalf("failed to add operations: %v", err) + } + + // Now restart sync and check that the offline edit is applied + syncCtx, cancelSyncCtx = context.WithCancel(ctx) + wg.Add(2) + go func() { + defer wg.Done() + runSyncAPIWithCtx(syncCtx, peerHost, peerHostAddr) + }() + + go func() { + defer wg.Done() + runSyncAPIWithCtx(syncCtx, peerClient, peerClientAddr) + }() + tryConnect(t, ctx, peerClient, defaultHostID) + + // Verify all operations are synced after reconnection + tryExpectExactOperations(t, ctx, peerHost, oplog.Query{}.SetRepoGUID(defaultRepoGUID), + testutil.OperationsWithDefaults(basicClientOperationTempl, []*v1.Operation{ + { + Id: 1, + DisplayMessage: "clientop1-mod-while-offline", + OriginalFlowId: 1, + OriginalId: 1, + FlowId: 1, + }, + }), "host and client should sync offline edits") + + // Clean up + cancelSyncCtx() + wg.Wait() +} + +func getOperations(t *testing.T, oplog *oplog.OpLog, query oplog.Query) []*v1.Operation { + ops := []*v1.Operation{} + if err := oplog.Query(query, func(op *v1.Operation) error { + ops = append(ops, op) + return nil + }); err != nil { + t.Fatalf("failed to get operations: %v", err) + } + return ops +} + +func tryExpectExactOperations(t *testing.T, ctx context.Context, peer *peerUnderTest, query oplog.Query, wantOps []*v1.Operation, message string) { + err := testutil.Retry(t, ctx, func() error { + ops := getOperations(t, peer.oplog, query) + for _, op := range ops { + op.Modno = 0 + } + if diff := cmp.Diff(ops, wantOps, protocmp.Transform()); diff != "" { + return fmt.Errorf("unexpected diff: %v", diff) + } + return nil + }) + if err != nil { + opsJson, _ := protojson.MarshalOptions{Indent: " "}.Marshal(&v1.OperationList{Operations: getOperations(t, peer.oplog, query)}) + t.Logf("found operations: %v", string(opsJson)) + t.Fatalf("%v: timeout without finding wanted operations: %v", message, err) + } +} + +func tryExpectOperationsSynced(t *testing.T, ctx context.Context, peer1 *peerUnderTest, peer2 *peerUnderTest, query oplog.Query, message string) { + err := testutil.Retry(t, ctx, func() error { + peer1Ops := getOperations(t, peer1.oplog, query) + peer2Ops := getOperations(t, peer2.oplog, query) + // clear fields that we expect will be re-mapped + for _, op := range peer1Ops { + op.Id = 0 + op.FlowId = 0 + op.OriginalId = 0 + op.OriginalFlowId = 0 + } + for _, op := range peer2Ops { + op.Id = 0 + op.FlowId = 0 + op.OriginalId = 0 + op.OriginalFlowId = 0 + } + + sortFn := func(a, b *v1.Operation) int { + if a.DisplayMessage < b.DisplayMessage { + return -1 + } + return 1 + } + + slices.SortFunc(peer1Ops, sortFn) + slices.SortFunc(peer2Ops, sortFn) + + if len(peer1Ops) == 0 { + return errors.New("no operations found in peer1") + } + if len(peer2Ops) == 0 { + return errors.New("no operations found in peer2") + } + if diff := cmp.Diff(peer1Ops, peer2Ops, protocmp.Transform()); diff != "" { + return fmt.Errorf("unexpected diff: %v", diff) + } + + return nil + }) + if err != nil { + ops1Json, _ := protojson.MarshalOptions{Indent: " "}.Marshal(&v1.OperationList{Operations: getOperations(t, peer1.oplog, query)}) + ops2Json, _ := protojson.MarshalOptions{Indent: " "}.Marshal(&v1.OperationList{Operations: getOperations(t, peer2.oplog, query)}) + t.Logf("peer1 operations: %v", string(ops1Json)) + t.Logf("peer2 operations: %v", string(ops2Json)) + t.Fatalf("timeout without syncing operations: %v", err) + } +} + +func tryExpectConfig(t *testing.T, ctx context.Context, peer *peerUnderTest, instanceID string, wantCfg *v1.RemoteConfig) { + testutil.Try(t, ctx, func() error { + cfg, err := peer.manager.remoteConfigStore.Get(instanceID) + if err != nil { + return err + } + if diff := cmp.Diff(cfg, wantCfg, protocmp.Transform()); diff != "" { + return fmt.Errorf("unexpected diff: %v", diff) + } + return nil + }) +} + +func tryConnect(t *testing.T, ctx context.Context, peer *peerUnderTest, instanceID string) { + testutil.Try(t, ctx, func() error { + allClients := peer.manager.GetSyncClients() + client, ok := allClients[instanceID] + if !ok { + return fmt.Errorf("client not found, got %v", allClients) + } + state, _ := client.GetConnectionState() + if state != v1.SyncConnectionState_CONNECTION_STATE_CONNECTED { + return fmt.Errorf("expected connection state to be CONNECTED, got %v", v1.SyncConnectionState.String(state)) + } + return nil + }) +} + +func allocBindAddrForTest(t *testing.T) string { + t.Helper() + + listener, err := net.Listen("tcp", ":0") + if err != nil { + t.Fatalf("failed to listen: %v", err) + } + defer listener.Close() + + // Get the port number from the listener + _, port, err := net.SplitHostPort(listener.Addr().String()) + if err != nil { + t.Fatalf("failed to split host and port: %v", err) + } + + return "127.0.0.1:" + port +} + +func runSyncAPIWithCtx(ctx context.Context, peer *peerUnderTest, bindAddr string) { + mux := http.NewServeMux() + syncHandler := NewBackrestSyncHandler(peer.manager) + mux.Handle(v1connect.NewBackrestSyncServiceHandler(syncHandler)) + + server := &http.Server{ + Addr: bindAddr, + Handler: h2c.NewHandler(mux, &http2.Server{}), // h2c is HTTP/2 without TLS for grpc-connect support. + } + + var wg sync.WaitGroup + + go func() { + <-ctx.Done() + server.Shutdown(context.Background()) + }() + + wg.Add(1) + go func() { + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + panic(err) + } + wg.Done() + }() + + wg.Add(1) + go func() { + peer.manager.RunSync(ctx) + wg.Done() + }() + + wg.Wait() +} + +func startRunningSyncAPI(t *testing.T, peer *peerUnderTest, bindAddr string) { + t.Helper() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + go runSyncAPIWithCtx(ctx, peer, bindAddr) +} + +type peerUnderTest struct { + manager *SyncManager + oplog *oplog.OpLog + opstore oplog.OpStore + configMgr *config.ConfigManager +} + +func newPeerUnderTest(t *testing.T, initialConfig *v1.Config) *peerUnderTest { + t.Helper() + + ctx, cancel := context.WithCancel(context.Background()) + + configMgr := &config.ConfigManager{Store: &config.MemoryStore{Config: initialConfig}} + opstore, err := sqlitestore.NewMemorySqliteStore() + t.Cleanup(func() { opstore.Close() }) + if err != nil { + t.Fatalf("failed to create opstore: %v", err) + } + oplog, err := oplog.NewOpLog(opstore) + if err != nil { + t.Fatalf("failed to create oplog: %v", err) + } + + resticbin, err := resticinstaller.FindOrInstallResticBinary() + if err != nil { + t.Fatalf("failed to find or install restic binary: %v", err) + } + + tempDir := t.TempDir() + logStore, err := logstore.NewLogStore(filepath.Join(tempDir, "tasklogs")) + t.Cleanup(func() { logStore.Close() }) + if err != nil { + t.Fatalf("failed to create log store: %v", err) + } + + var wg sync.WaitGroup + orchestrator, err := orchestrator.NewOrchestrator(resticbin, initialConfig, oplog, logStore) + if err != nil { + t.Fatalf("failed to create orchestrator: %v", err) + } + wg.Add(1) + go func() { + orchestrator.Run(ctx) + wg.Done() + }() + + ch := configMgr.Watch() + + wg.Add(1) + go func() { + for range ch { + cfg, _ := configMgr.Get() + orchestrator.ApplyConfig(cfg) + } + wg.Done() + }() + t.Cleanup(func() { + configMgr.StopWatching(ch) + cancel() + wg.Wait() + }) + + remoteConfigStore := NewJSONDirRemoteConfigStore(filepath.Join(tempDir, "remoteconfig")) + + manager := NewSyncManager(configMgr, remoteConfigStore, oplog, orchestrator) + manager.syncClientRetryDelay = 250 * time.Millisecond + + return &peerUnderTest{ + manager: manager, + oplog: oplog, + opstore: opstore, + configMgr: configMgr, + } +} diff --git a/internal/api/syncapi/syncclient.go b/internal/api/syncapi/syncclient.go new file mode 100644 index 0000000..09c90d7 --- /dev/null +++ b/internal/api/syncapi/syncclient.go @@ -0,0 +1,439 @@ +package syncapi + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "io" + "net" + "net/http" + "sync" + "time" + + "connectrpc.com/connect" + "github.com/garethgeorge/backrest/gen/go/types" + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/gen/go/v1/v1connect" + "github.com/garethgeorge/backrest/internal/config" + "github.com/garethgeorge/backrest/internal/oplog" + "github.com/garethgeorge/backrest/internal/protoutil" + "go.uber.org/zap" + "golang.org/x/net/http2" + "google.golang.org/protobuf/proto" +) + +type SyncClient struct { + mgr *SyncManager + localInstanceID string + peer *v1.Multihost_Peer + oplog *oplog.OpLog + client v1connect.BackrestSyncServiceClient + reconnectDelay time.Duration + l *zap.Logger + + // mutable properties + mu sync.Mutex + remoteConfigStore RemoteConfigStore + connectionStatus v1.SyncConnectionState + connectionStatusMessage string +} + +func newInsecureClient() *http.Client { + return &http.Client{ + Transport: &http2.Transport{ + AllowHTTP: true, + DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) { + return net.Dial(network, addr) + }, + IdleConnTimeout: 300 * time.Second, + ReadIdleTimeout: 60 * time.Second, + }, + } +} + +func NewSyncClient(mgr *SyncManager, localInstanceID string, peer *v1.Multihost_Peer, oplog *oplog.OpLog) (*SyncClient, error) { + if peer.GetInstanceUrl() == "" { + return nil, errors.New("peer instance URL is required") + } + + client := v1connect.NewBackrestSyncServiceClient( + newInsecureClient(), + peer.GetInstanceUrl(), + ) + + return &SyncClient{ + mgr: mgr, + localInstanceID: localInstanceID, + peer: peer, + reconnectDelay: mgr.syncClientRetryDelay, + client: client, + oplog: oplog, + l: zap.L().Named(fmt.Sprintf("syncclient for %q", peer.GetInstanceId())), + }, nil +} + +func (c *SyncClient) setConnectionState(state v1.SyncConnectionState, message string) { + c.mu.Lock() + c.connectionStatus = state + c.connectionStatusMessage = message + c.mu.Unlock() +} + +func (c *SyncClient) GetConnectionState() (v1.SyncConnectionState, string) { + c.mu.Lock() + defer c.mu.Unlock() + return c.connectionStatus, c.connectionStatusMessage +} + +func (c *SyncClient) RunSync(ctx context.Context) { + for { + if ctx.Err() != nil { + return + } + + lastConnect := time.Now() + + c.setConnectionState(v1.SyncConnectionState_CONNECTION_STATE_PENDING, "connection pending") + + if err := c.runSyncInternal(ctx); err != nil { + c.l.Sugar().Errorf("sync error: %v", err) + c.setConnectionState(v1.SyncConnectionState_CONNECTION_STATE_DISCONNECTED, err.Error()) + } + + delay := c.reconnectDelay - time.Since(lastConnect) + c.l.Sugar().Infof("disconnected, will retry after %v", delay) + select { + case <-time.After(delay): + case <-ctx.Done(): + return + } + } +} + +func (c *SyncClient) runSyncInternal(ctx context.Context) error { + c.l.Info("connecting to sync server") + stream := c.client.Sync(ctx) + + ctx, cancelWithError := context.WithCancelCause(ctx) + + receiveError := make(chan error, 1) + receive := make(chan *v1.SyncStreamItem, 1) + send := make(chan *v1.SyncStreamItem, 100) + + go func() { + for { + item, err := stream.Receive() + if err != nil { + receiveError <- err + return + } + receive <- item + } + }() + + // Broadcast initial packet containing the protocol version and instance ID. + // TODO: do this in a header instead of as a part of the stream. + if err := stream.Send(&v1.SyncStreamItem{ + Action: &v1.SyncStreamItem_Handshake{ + Handshake: &v1.SyncStreamItem_SyncActionHandshake{ + ProtocolVersion: SyncProtocolVersion, + InstanceId: &v1.SignedMessage{ + Payload: []byte(c.localInstanceID), + Signature: []byte("TOOD: inject a valid signature"), + Keyid: "TODO: inject a valid key ID", + }, + }, + }, + }); err != nil { + // note: the error checking w/streams in connectrpc is fairly awkward. + // If write returns an EOF error, we are expected to call stream.Receive() + // to get the unmarshalled network failure. + if !errors.Is(err, io.EOF) { + c.setConnectionState(v1.SyncConnectionState_CONNECTION_STATE_ERROR_PROTOCOL, err.Error()) + return err + } else { + _, err2 := stream.Receive() + c.setConnectionState(v1.SyncConnectionState_CONNECTION_STATE_DISCONNECTED, err.Error()) + return err2 + } + } + c.setConnectionState(v1.SyncConnectionState_CONNECTION_STATE_CONNECTED, "connected") + + // Wait for the handshake packet from the server. + serverInstanceID := "" + if msg, ok := <-receive; ok { + handshake := msg.GetHandshake() + if handshake == nil { + return connect.NewError(connect.CodeInvalidArgument, errors.New("handshake packet must be sent first")) + } + + serverInstanceID = string(handshake.GetInstanceId().GetPayload()) + if serverInstanceID == "" { + return connect.NewError(connect.CodeInvalidArgument, errors.New("instance ID is required")) + } + + if handshake.GetProtocolVersion() != SyncProtocolVersion { + return connect.NewError(connect.CodeFailedPrecondition, fmt.Errorf("unsupported peer protocol version, got %d, expected %d", handshake.GetProtocolVersion(), SyncProtocolVersion)) + } + } else { + return connect.NewError(connect.CodeInvalidArgument, errors.New("no packets received")) + } + + if serverInstanceID != c.peer.InstanceId { + return connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("server instance ID %q does not match expected peer instance ID %q", serverInstanceID, c.peer.InstanceId)) + } + + // haveRunSync tracks which repo GUIDs we've initiated a sync for with the server. + // operation requests (from the server) are ignored if the GUID is not allowlisted in this map. + haveRunSync := make(map[string]struct{}) + + oplogSubscription := func(ops []*v1.Operation, event oplog.OperationEvent) { + var opsToForward []*v1.Operation + for _, op := range ops { + if _, ok := haveRunSync[op.GetRepoGuid()]; ok { + opsToForward = append(opsToForward, op) + } + } + + if len(opsToForward) == 0 { + return + } + + var eventProto *v1.OperationEvent + if event == oplog.OPERATION_ADDED { + eventProto = &v1.OperationEvent{ + Event: &v1.OperationEvent_CreatedOperations{ + CreatedOperations: &v1.OperationList{Operations: opsToForward}, + }, + } + } else if event == oplog.OPERATION_UPDATED { + eventProto = &v1.OperationEvent{ + Event: &v1.OperationEvent_UpdatedOperations{ + UpdatedOperations: &v1.OperationList{Operations: opsToForward}, + }, + } + } else if event == oplog.OPERATION_DELETED { + ids := make([]int64, len(opsToForward)) + for i, op := range opsToForward { + ids[i] = op.GetId() + } + eventProto = &v1.OperationEvent{ + Event: &v1.OperationEvent_DeletedOperations{ + DeletedOperations: &types.Int64List{Values: ids}, + }, + } + } + + select { + case send <- &v1.SyncStreamItem{ + Action: &v1.SyncStreamItem_SendOperations{ + SendOperations: &v1.SyncStreamItem_SyncActionSendOperations{ + Event: eventProto, + }, + }, + }: + default: + cancelWithError(fmt.Errorf("operation send buffer overflow")) + } + } + c.oplog.Subscribe(oplog.Query{}, &oplogSubscription) + defer c.oplog.Unsubscribe(&oplogSubscription) + + handleSyncCommand := func(item *v1.SyncStreamItem) error { + switch action := item.Action.(type) { + case *v1.SyncStreamItem_SendConfig: + c.l.Sugar().Debugf("received remote config update") + newRemoteConfig := action.SendConfig.Config + if err := c.mgr.remoteConfigStore.Update(c.peer.InstanceId, newRemoteConfig); err != nil { + return fmt.Errorf("update remote config store with latest config: %w", err) + } + + if newRemoteConfig == nil { + return fmt.Errorf("received nil remote config") + } + + // remove any repo IDs that are no longer in the config, our access has been revoked. + remoteRepoGUIDs := make(map[string]struct{}) + for _, repo := range newRemoteConfig.Repos { + remoteRepoGUIDs[repo.GetGuid()] = struct{}{} + } + for repoID := range haveRunSync { + if _, ok := remoteRepoGUIDs[repoID]; !ok { + delete(haveRunSync, repoID) + } + } + + // load the local config so that we can index the remote repos into any local repos that reference their URIs + // e.g. backrest: format URI. + localConfig, err := c.mgr.configMgr.Get() + if err != nil { + return fmt.Errorf("get local config: %w", err) + } + + for _, repo := range newRemoteConfig.Repos { + _, ok := haveRunSync[repo.GetGuid()] + if ok { + continue + } + localRepoConfig := config.FindRepoByGUID(localConfig, repo.GetGuid()) + if localRepoConfig == nil { + c.l.Sugar().Debugf("ignoring remote repo config %q/%q because no local repo has the same GUID %q", c.peer.InstanceId, repo.GetId()) + continue + } + instanceID, err := InstanceForBackrestURI(localRepoConfig.Uri) + if err != nil || instanceID != c.peer.InstanceId { + c.l.Sugar().Debugf("ignoring remote repo config %q/%q because the local repo (%q) with the same GUID specifies URI %q (instance ID %q) which does not reference the peer providing this config", c.peer.InstanceId, repo.GetId(), localRepoConfig.Id, localRepoConfig.Guid, instanceID) + continue + } + + diffSel := &v1.OpSelector{ + InstanceId: proto.String(c.localInstanceID), + RepoGuid: proto.String(repo.GetGuid()), + } + + diffQuery, err := protoutil.OpSelectorToQuery(diffSel) + if err != nil { + return fmt.Errorf("convert operation selector to query: %w", err) + } + + haveRunSync[repo.GetGuid()] = struct{}{} + + // Load operation metadata and send the initial diff state. + var opIds []int64 + var opModnos []int64 + if err := c.oplog.QueryMetadata(diffQuery, func(op oplog.OpMetadata) error { + opIds = append(opIds, op.ID) + opModnos = append(opModnos, op.Modno) + return nil + }); err != nil { + return fmt.Errorf("action sync config: query oplog for repo %q: %w", repo.GetId(), err) + } + + c.l.Sugar().Infof("initiating operation history sync for repo %q", repo.GetId()) + + if err := stream.Send(&v1.SyncStreamItem{ + Action: &v1.SyncStreamItem_DiffOperations{ + DiffOperations: &v1.SyncStreamItem_SyncActionDiffOperations{ + HaveOperationsSelector: diffSel, + HaveOperationIds: opIds, + HaveOperationModnos: opModnos, + }, + }, + }); err != nil { + return fmt.Errorf("action sync config: send diff operations: %w", err) + } + } + case *v1.SyncStreamItem_DiffOperations: + requestedOperations := action.DiffOperations.GetRequestOperations() + c.l.Sugar().Debugf("received operation request for operations: %v", requestedOperations) + + var deletedIDs []int64 + var sendOps []*v1.Operation + + sendOpsFunc := func() error { + if err := stream.Send(&v1.SyncStreamItem{ + Action: &v1.SyncStreamItem_SendOperations{ + SendOperations: &v1.SyncStreamItem_SyncActionSendOperations{ + Event: &v1.OperationEvent{ + Event: &v1.OperationEvent_CreatedOperations{ + CreatedOperations: &v1.OperationList{Operations: sendOps}, + }, + }, + }, + }, + }); err != nil { + sendOps = sendOps[:0] + return fmt.Errorf("action diff operations: send create operations: %w", err) + } + c.l.Sugar().Debugf("sent %d operations", len(sendOps)) + sendOps = sendOps[:0] + return nil + } + + sentOps := 0 + for _, opID := range requestedOperations { + op, err := c.oplog.Get(opID) + if err != nil { + if errors.Is(err, oplog.ErrNotExist) { + deletedIDs = append(deletedIDs, opID) + continue + } + c.l.Sugar().Warnf("action diff operations, failed to fetch a requested operation %d: %v", opID, err) + continue // skip this operation + } + if op.GetInstanceId() != c.localInstanceID { + c.l.Sugar().Warnf("action diff operations, requested operation %d is not from this instance, this shouldn't happen with a wellbehaved server", opID) + continue // skip operations that are not from this instance e.g. an "index snapshot" picking up snapshots created by another instance. + } + + _, ok := haveRunSync[op.RepoGuid] + if !ok { + // this should never happen if sync is working correctly. Would probably indicate oplog or our access was revoked. + // Error out and re-initiate sync. + return fmt.Errorf("remote requested operation for repo %q for which sync was never initiated", op.GetRepoId()) + } + + sendOps = append(sendOps, op) + sentOps += 1 + if len(sendOps) >= 256 { + if err := sendOpsFunc(); err != nil { + return err + } + } + } + + if len(sendOps) > 0 { + if err := sendOpsFunc(); err != nil { + return err + } + } + + if len(deletedIDs) > 0 { + if err := stream.Send(&v1.SyncStreamItem{ + Action: &v1.SyncStreamItem_SendOperations{ + SendOperations: &v1.SyncStreamItem_SyncActionSendOperations{ + Event: &v1.OperationEvent{ + Event: &v1.OperationEvent_DeletedOperations{ + DeletedOperations: &types.Int64List{Values: deletedIDs}, + }, + }, + }, + }, + }); err != nil { + return fmt.Errorf("action diff operations: send delete operations: %w", err) + } + } + + c.l.Debug("replied to an operations request", zap.Int("num_ops_requested", len(requestedOperations)), zap.Int("num_ops_sent", sentOps), zap.Int("num_ops_deleted", len(deletedIDs))) + case *v1.SyncStreamItem_Throttle: + c.reconnectDelay = time.Duration(action.Throttle.GetDelayMs()) * time.Millisecond + default: + return fmt.Errorf("unknown action: %v", action) + } + return nil + } + + for { + select { + case err := <-receiveError: + return fmt.Errorf("connection terminated with error: %w", err) + case item, ok := <-receive: + if !ok { + return nil + } + if err := handleSyncCommand(item); err != nil { + return err + } + case sendItem, ok := <-send: // note: send channel should only be used when sending from a different goroutine than the main loop + if !ok { + return nil + } + if err := stream.Send(sendItem); err != nil { + return err + } + case <-ctx.Done(): + return ctx.Err() + } + } +} diff --git a/internal/api/syncapi/synchandler.go b/internal/api/syncapi/synchandler.go new file mode 100644 index 0000000..59175c8 --- /dev/null +++ b/internal/api/syncapi/synchandler.go @@ -0,0 +1,383 @@ +package syncapi + +import ( + "context" + "errors" + "fmt" + "slices" + "sort" + + "connectrpc.com/connect" + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/gen/go/v1/v1connect" + "github.com/garethgeorge/backrest/internal/config" + "github.com/garethgeorge/backrest/internal/oplog" + "github.com/garethgeorge/backrest/internal/protoutil" + lru "github.com/hashicorp/golang-lru/v2" + "go.uber.org/zap" +) + +const SyncProtocolVersion = 1 + +type BackrestSyncHandler struct { + v1connect.UnimplementedBackrestSyncServiceHandler + mgr *SyncManager +} + +var _ v1connect.BackrestSyncServiceHandler = &BackrestSyncHandler{} + +func NewBackrestSyncHandler(mgr *SyncManager) *BackrestSyncHandler { + return &BackrestSyncHandler{ + mgr: mgr, + } +} + +func (h *BackrestSyncHandler) Sync(ctx context.Context, stream *connect.BidiStream[v1.SyncStreamItem, v1.SyncStreamItem]) error { + // TODO: this request can be very long lived, we must periodically refresh the config + // e.g. to disconnect a client if its access is revoked. + initialConfig, err := h.mgr.configMgr.Get() + if err != nil { + return err + } + + receive := make(chan *v1.SyncStreamItem, 1) + send := make(chan *v1.SyncStreamItem, 1) + go func() { + for { + item, err := stream.Receive() + if err != nil { + break + } + receive <- item + } + close(receive) + }() + + // Broadcast initial packet containing the protocol version and instance ID. + zap.S().Debugf("syncserver a client connected, broadcast handshake as %v", initialConfig.Instance) + if err := stream.Send(&v1.SyncStreamItem{ + Action: &v1.SyncStreamItem_Handshake{ + Handshake: &v1.SyncStreamItem_SyncActionHandshake{ + ProtocolVersion: SyncProtocolVersion, + InstanceId: &v1.SignedMessage{ + Payload: []byte(initialConfig.Instance), + Signature: []byte("TODO: inject a valid signature"), + Keyid: "TODO: inject a valid key ID", + }, + }, + }, + }); err != nil { + return err + } + + // Try to read the handshake packet from the client. + // TODO: perform this handshake in a header as a pre-flight before opening the stream. + clientInstanceID := "" + if msg, ok := <-receive; ok { + handshake := msg.GetHandshake() + if handshake == nil { + return connect.NewError(connect.CodeInvalidArgument, errors.New("handshake packet must be sent first")) + } + + clientInstanceID = string(handshake.GetInstanceId().GetPayload()) + if clientInstanceID == "" { + return connect.NewError(connect.CodeInvalidArgument, errors.New("instance ID is required")) + } + } else { + return connect.NewError(connect.CodeInvalidArgument, errors.New("no packets received")) + } + + var authorizedClientPeer *v1.Multihost_Peer + authorizedClientPeerIdx := slices.IndexFunc(initialConfig.Multihost.GetAuthorizedClients(), func(peer *v1.Multihost_Peer) bool { + return peer.InstanceId == clientInstanceID + }) + if authorizedClientPeerIdx == -1 { + // TODO: check the key signature of the handshake message here. + zap.S().Warnf("syncserver rejected a connection from client instance ID %q because it is not authorized", clientInstanceID) + return connect.NewError(connect.CodePermissionDenied, errors.New("client is not an authorized peer")) + } else { + authorizedClientPeer = initialConfig.Multihost.AuthorizedClients[authorizedClientPeerIdx] + } + zap.S().Infof("syncserver accepted a connection from client instance ID %q", authorizedClientPeer.InstanceId) + + opIDLru, _ := lru.New[int64, int64](128) // original ID -> local ID + flowIDLru, _ := lru.New[int64, int64](128) // original flow ID -> local flow ID + + insertOrUpdate := func(op *v1.Operation) error { + op.OriginalId = op.Id + op.OriginalFlowId = op.FlowId + var ok bool + if op.Id, ok = opIDLru.Get(op.OriginalId); !ok { + var foundOp *v1.Operation + if err := h.mgr.oplog.Query(oplog.Query{}. + SetOriginalID(op.OriginalId). + SetInstanceID(op.InstanceId), func(o *v1.Operation) error { + foundOp = o + return nil + }); err != nil { + return fmt.Errorf("mapping remote ID to local ID: %w", err) + } + if foundOp != nil { + op.Id = foundOp.Id + opIDLru.Add(foundOp.Id, foundOp.Id) + } + } + if op.FlowId, ok = flowIDLru.Get(op.OriginalFlowId); !ok { + var flowOp *v1.Operation + if err := h.mgr.oplog.Query(oplog.Query{}. + SetOriginalFlowID(op.OriginalFlowId). + SetInstanceID(op.InstanceId), func(o *v1.Operation) error { + flowOp = o + return nil + }); err != nil { + return fmt.Errorf("mapping remote flow ID to local ID: %w", err) + } + if flowOp != nil { + op.FlowId = flowOp.FlowId + flowIDLru.Add(op.OriginalFlowId, flowOp.FlowId) + } + } + + return h.mgr.oplog.Set(op) + } + + deleteByOriginalID := func(originalID int64) error { + var foundOp *v1.Operation + if err := h.mgr.oplog.Query(oplog.Query{}.SetOriginalID(originalID), func(o *v1.Operation) error { + foundOp = o + return nil + }); err != nil { + return fmt.Errorf("mapping remote ID to local ID: %w", err) + } + + if foundOp == nil { + zap.S().Debugf("syncserver received delete for non-existent operation %v", originalID) + return nil + } + + return h.mgr.oplog.Delete(foundOp.Id) + } + + sendConfigToClient := func(config *v1.Config) error { + remoteConfig := &v1.RemoteConfig{} + var allowedRepoIDs []string + for _, repo := range config.Repos { + if slices.Contains(repo.AllowedPeerInstanceIds, clientInstanceID) { + allowedRepoIDs = append(allowedRepoIDs, repo.Id) + remoteConfig.Repos = append(remoteConfig.Repos, protoutil.RepoToRemoteRepo(repo)) + } + } + + zap.S().Debugf("syncserver determined client %v is allowlisted for repos %v", clientInstanceID, allowedRepoIDs) + + // Send the config, this is the first meaningful packet the client will receive. + // Once configuration is received, the client will start sending diffs. + if err := stream.Send(&v1.SyncStreamItem{ + Action: &v1.SyncStreamItem_SendConfig{ + SendConfig: &v1.SyncStreamItem_SyncActionSendConfig{ + Config: remoteConfig, + }, + }, + }); err != nil { + return fmt.Errorf("sending config to client: %w", err) + } + return nil + } + + handleSyncCommand := func(item *v1.SyncStreamItem) error { + switch action := item.Action.(type) { + case *v1.SyncStreamItem_SendConfig: + return errors.New("clients can not push configs to server") + case *v1.SyncStreamItem_DiffOperations: + diffSel := action.DiffOperations.GetHaveOperationsSelector() + + if diffSel == nil { + return connect.NewError(connect.CodeInvalidArgument, errors.New("action DiffOperations: selector is required")) + } + + // The diff selector _must_ be scoped to the instance ID of the client. + if diffSel.GetInstanceId() != clientInstanceID { + return connect.NewError(connect.CodePermissionDenied, errors.New("action DiffOperations: instance ID mismatch in diff selector")) + } + + // The diff selector _must_ specify a repo the client has access to + repo := config.FindRepoByGUID(initialConfig, diffSel.GetRepoGuid()) + if repo == nil { + zap.S().Warnf("syncserver action DiffOperations: client %q tried to diff with repo %q that does not exist", clientInstanceID, diffSel.GetRepoGuid()) + return connect.NewError(connect.CodePermissionDenied, fmt.Errorf("action DiffOperations: repo %q not found", diffSel.GetRepoGuid())) + } + if !slices.Contains(repo.GetAllowedPeerInstanceIds(), clientInstanceID) { + zap.S().Warnf("syncserver action DiffOperations: client %q tried to diff with repo %q that they are not allowed to access", clientInstanceID, repo.Id) + return connect.NewError(connect.CodePermissionDenied, fmt.Errorf("action DiffOperations: client is not allowed to access repo %q", repo.Id)) + } + + // These are required to be the same length for a pairwise zip. + if len(action.DiffOperations.HaveOperationIds) != len(action.DiffOperations.HaveOperationModnos) { + return connect.NewError(connect.CodeInvalidArgument, errors.New("action DiffOperations: operation IDs and modnos must be the same length")) + } + + diffSelQuery, err := protoutil.OpSelectorToQuery(diffSel) + if err != nil { + return fmt.Errorf("action DiffOperations: converting diff selector to query: %w", err) + } + + localMetadata := []oplog.OpMetadata{} + if err := h.mgr.oplog.QueryMetadata(diffSelQuery, func(metadata oplog.OpMetadata) error { + if metadata.OriginalID == 0 { + return nil // skip operations that didn't come from a remote + } + localMetadata = append(localMetadata, metadata) + return nil + }); err != nil { + return fmt.Errorf("action DiffOperations: querying local metadata: %w", err) + } + sort.Slice(localMetadata, func(i, j int) bool { + return localMetadata[i].OriginalID < localMetadata[j].OriginalID + }) + + remoteMetadata := make([]oplog.OpMetadata, len(action.DiffOperations.HaveOperationIds)) + for i, id := range action.DiffOperations.HaveOperationIds { + remoteMetadata[i] = oplog.OpMetadata{ + ID: id, + Modno: action.DiffOperations.HaveOperationModnos[i], + } + } + sort.Slice(remoteMetadata, func(i, j int) bool { + return remoteMetadata[i].ID < remoteMetadata[j].ID + }) + + requestDueToModno := 0 + requestMissingRemote := 0 + requestMissingLocal := 0 + requestIDs := []int64{} + + // This is a simple O(n) diff algorithm that compares the local and remote metadata vectors. + localIndex := 0 + remoteIndex := 0 + for localIndex < len(localMetadata) && remoteIndex < len(remoteMetadata) { + local := localMetadata[localIndex] + remote := remoteMetadata[remoteIndex] + + if local.OriginalID == remote.ID { + if local.Modno != remote.Modno { + requestIDs = append(requestIDs, local.OriginalID) + requestDueToModno++ + } + localIndex++ + remoteIndex++ + } else if local.OriginalID < remote.ID { + // the ID is found locally not remotely, request it and see if we get a delete event back + // from the client indicating that the operation was deleted. + requestIDs = append(requestIDs, local.OriginalID) + localIndex++ + requestMissingLocal++ + } else { + // the ID is found remotely not locally, request it for initial sync. + requestIDs = append(requestIDs, remote.ID) + remoteIndex++ + requestMissingRemote++ + } + } + for localIndex < len(localMetadata) { + requestIDs = append(requestIDs, localMetadata[localIndex].OriginalID) + localIndex++ + requestMissingLocal++ + } + for remoteIndex < len(remoteMetadata) { + requestIDs = append(requestIDs, remoteMetadata[remoteIndex].ID) + remoteIndex++ + requestMissingRemote++ + } + + zap.L().Debug("syncserver diff operations with client metadata", + zap.String("client_instance_id", clientInstanceID), + zap.Any("query", diffSelQuery), + zap.Int("request_due_to_modno", requestDueToModno), + zap.Int("request_local_but_not_remote", requestMissingLocal), + zap.Int("request_remote_but_not_local", requestMissingRemote), + zap.Int("request_ids_total", len(requestIDs)), + ) + if len(requestIDs) > 0 { + zap.L().Debug("syncserver sending request operations to client", zap.String("client_instance_id", clientInstanceID), zap.Any("request_ids", requestIDs)) + if err := stream.Send(&v1.SyncStreamItem{ + Action: &v1.SyncStreamItem_DiffOperations{ + DiffOperations: &v1.SyncStreamItem_SyncActionDiffOperations{ + RequestOperations: requestIDs, + }, + }, + }); err != nil { + return fmt.Errorf("sending request operations: %w", err) + } + } + + return nil + case *v1.SyncStreamItem_SendOperations: + switch event := action.SendOperations.GetEvent().Event.(type) { + case *v1.OperationEvent_CreatedOperations: + zap.L().Debug("syncserver received created operations", zap.Any("operations", event.CreatedOperations.GetOperations())) + for _, op := range event.CreatedOperations.GetOperations() { + if err := insertOrUpdate(op); err != nil { + return fmt.Errorf("action SendOperations: operation event create: %w", err) + } + } + case *v1.OperationEvent_UpdatedOperations: + zap.L().Debug("syncserver received update operations", zap.Any("operations", event.UpdatedOperations.GetOperations())) + for _, op := range event.UpdatedOperations.GetOperations() { + if err := insertOrUpdate(op); err != nil { + return fmt.Errorf("action SendOperations: operation event update: %w", err) + } + } + case *v1.OperationEvent_DeletedOperations: + zap.L().Debug("syncserver received delete operations", zap.Any("operations", event.DeletedOperations.GetValues())) + for _, id := range event.DeletedOperations.GetValues() { + if err := deleteByOriginalID(id); err != nil { + return fmt.Errorf("action SendOperations: operation event delete %d: %w", id, err) + } + } + case *v1.OperationEvent_KeepAlive: + default: + return connect.NewError(connect.CodeInvalidArgument, errors.New("action SendOperations: unknown event type")) + } + default: + return connect.NewError(connect.CodeInvalidArgument, errors.New("unknown action type")) + } + + return nil + } + + // subscribe to our own configuration for changes + configWatchCh := h.mgr.configMgr.Watch() + defer h.mgr.configMgr.StopWatching(configWatchCh) + sendConfigToClient(initialConfig) + + for { + select { + case item, ok := <-receive: + if !ok { + return nil + } + + if err := handleSyncCommand(item); err != nil { + return err + } + case sendItem, ok := <-send: // note: send channel should only be used when sending from a different goroutine than the main loop + if !ok { + return nil + } + + if err := stream.Send(sendItem); err != nil { + return err + } + case <-configWatchCh: + newConfig, err := h.mgr.configMgr.Get() + if err != nil { + zap.S().Warnf("syncserver failed to get the newest config: %v", err) + continue + } + sendConfigToClient(newConfig) + case <-ctx.Done(): + zap.S().Infof("syncserver client %q disconnected", authorizedClientPeer.InstanceId) + return ctx.Err() + } + } +} diff --git a/internal/api/syncapi/syncmanager.go b/internal/api/syncapi/syncmanager.go new file mode 100644 index 0000000..132bc46 --- /dev/null +++ b/internal/api/syncapi/syncmanager.go @@ -0,0 +1,129 @@ +package syncapi + +import ( + "context" + "errors" + "fmt" + "maps" + "sync" + "time" + + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/internal/config" + "github.com/garethgeorge/backrest/internal/oplog" + "github.com/garethgeorge/backrest/internal/orchestrator" + "go.uber.org/zap" +) + +type SyncManager struct { + configMgr *config.ConfigManager + orchestrator *orchestrator.Orchestrator + oplog *oplog.OpLog + remoteConfigStore RemoteConfigStore + + // mutable properties + mu sync.Mutex + + syncClientRetryDelay time.Duration // the default retry delay for sync clients + + syncClients map[string]*SyncClient +} + +func NewSyncManager(configMgr *config.ConfigManager, remoteConfigStore RemoteConfigStore, oplog *oplog.OpLog, orchestrator *orchestrator.Orchestrator) *SyncManager { + return &SyncManager{ + configMgr: configMgr, + orchestrator: orchestrator, + oplog: oplog, + remoteConfigStore: remoteConfigStore, + + syncClientRetryDelay: 60 * time.Second, + syncClients: make(map[string]*SyncClient), + } +} + +// GetSyncClients returns a copy of the sync clients map. This makes the map safe to read from concurrently. +func (m *SyncManager) GetSyncClients() map[string]*SyncClient { + m.mu.Lock() + defer m.mu.Unlock() + return maps.Clone(m.syncClients) +} + +// Note: top level function will be called holding the lock, must kick off goroutines and then return. +func (m *SyncManager) RunSync(ctx context.Context) { + var syncWg sync.WaitGroup + var cancelLastSync context.CancelFunc + + configWatchCh := m.configMgr.Watch() + defer m.configMgr.StopWatching(configWatchCh) + + runSyncWithNewConfig := func() { + m.mu.Lock() + defer m.mu.Unlock() + + // TODO: rather than cancel the top level context, something clever e.g. diffing the set of peers could be done here. + if cancelLastSync != nil { + cancelLastSync() + zap.L().Info("syncmanager applying new config, waiting for existing sync goroutines to exit") + syncWg.Wait() + } + syncCtx, cancel := context.WithCancel(ctx) + cancelLastSync = cancel + + config, err := m.configMgr.Get() + if err != nil { + zap.S().Errorf("syncmanager failed to refresh config with latest changes so sync is stopped: %v", err) + return + } + + if len(config.Multihost.GetKnownHosts()) == 0 { + zap.L().Debug("syncmanager no known host peers declared, sync client exiting early") + return + } + + zap.S().Infof("syncmanager applying new config, starting sync goroutines for %d known peers", len(config.Multihost.GetKnownHosts())) + for _, knownHostPeer := range config.Multihost.KnownHosts { + if knownHostPeer.InstanceId == "" { + continue + } + + syncWg.Add(1) + go func(knownHostPeer *v1.Multihost_Peer) { + defer syncWg.Done() + zap.S().Debugf("syncmanager starting sync goroutine with peer %q", knownHostPeer.InstanceId) + err := m.runSyncWithPeerInternal(syncCtx, config, knownHostPeer) + if err != nil { + zap.S().Errorf("syncmanager error starting client for peer %q: %v", knownHostPeer.InstanceId, err) + } + }(knownHostPeer) + } + } + + runSyncWithNewConfig() + + for { + select { + case <-ctx.Done(): + return + case <-configWatchCh: + runSyncWithNewConfig() + } + } +} + +// runSyncWithPeerInternal starts the sync process with a single peer. It is expected to spawn a goroutine that will +// return when the context is canceled. Errors can only be returned upfront. +func (m *SyncManager) runSyncWithPeerInternal(ctx context.Context, config *v1.Config, knownHostPeer *v1.Multihost_Peer) error { + if config.Instance == "" { + return errors.New("local instance must set instance name before peersync can be enabled") + } + + newClient, err := NewSyncClient(m, config.Instance, knownHostPeer, m.oplog) + if err != nil { + return fmt.Errorf("creating sync client: %w", err) + } + m.syncClients[knownHostPeer.InstanceId] = newClient + + go newClient.RunSync(ctx) + + return nil +} diff --git a/internal/api/syncapi/uriutil.go b/internal/api/syncapi/uriutil.go new file mode 100644 index 0000000..fef31c5 --- /dev/null +++ b/internal/api/syncapi/uriutil.go @@ -0,0 +1,60 @@ +package syncapi + +import ( + "errors" + "net/url" +) + +var ErrNotBackrestURI = errors.New("not a backrest URI") + +func CreateRemoteRepoURI(instanceUrl string) (string, error) { + u, err := url.Parse(instanceUrl) + if err != nil { + return "", err + } + + if u.Scheme == "http" { + u.Scheme = "backrest" + } else if u.Scheme == "https" { + u.Scheme = "sbackrest" + } else { + return "", errors.New("unsupported scheme") + } + + return u.String(), nil +} + +func IsBackrestRemoteRepoURI(repoUri string) bool { + u, err := url.Parse(repoUri) + if err != nil { + return false + } + + return u.Scheme == "backrest" +} + +func InstanceForBackrestURI(repoUri string) (string, error) { + u, err := url.Parse(repoUri) + if err != nil { + return "", err + } + + if u.Scheme != "backrest" { + return "", errors.New("not a backrest URI") + } + + return u.Hostname(), nil +} + +func RepoForBackrestURI(repoUri string) (string, error) { + u, err := url.Parse(repoUri) + if err != nil { + return "", err + } + + if u.Scheme != "backrest" { + return "", errors.New("not a backrest URI") + } + + return u.Path, nil +} diff --git a/internal/auth/auth.go b/internal/auth/auth.go index fdbb404..c684971 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -26,6 +26,7 @@ func NewAuthenticator(key []byte, config config.ConfigStore) *Authenticator { var ErrUserNotFound = errors.New("user not found") var ErrInvalidPassword = errors.New("invalid password") +var ErrInvalidKey = errors.New("invalid key") func (a *Authenticator) Login(username, password string) (*v1.User, error) { config, err := a.config.Get() diff --git a/internal/auth/middleware.go b/internal/auth/middleware.go index 8452c4e..14ab07f 100644 --- a/internal/auth/middleware.go +++ b/internal/auth/middleware.go @@ -14,6 +14,7 @@ func (k contextKey) String() string { } const UserContextKey contextKey = "user" +const APIKeyContextKey contextKey = "api_key" func RequireAuthentication(h http.Handler, auth *Authenticator) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -38,6 +39,8 @@ func RequireAuthentication(h http.Handler, auth *Authenticator) http.Handler { } } + // TODO: process the API Key + token, err := ParseBearerToken(r.Header.Get("Authorization")) if err != nil { http.Error(w, "Unauthorized (No Authorization Header)", http.StatusUnauthorized) diff --git a/internal/config/config.go b/internal/config/config.go index 2829495..d09d9d8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -3,6 +3,7 @@ package config import ( "errors" "fmt" + "slices" "sync" v1 "github.com/garethgeorge/backrest/gen/go/v1" @@ -12,6 +13,65 @@ import ( var ErrConfigNotFound = fmt.Errorf("config not found") +type ConfigManager struct { + Store ConfigStore + + callbacksMu sync.Mutex + changeNotifyCh []chan struct{} +} + +var _ ConfigStore = &ConfigManager{} + +func (m *ConfigManager) Get() (*v1.Config, error) { + return m.Store.Get() +} + +func (m *ConfigManager) Update(config *v1.Config) error { + err := m.Store.Update(config) + if err != nil { + return err + } + + m.callbacksMu.Lock() + changeNotifyCh := slices.Clone(m.changeNotifyCh) + m.callbacksMu.Unlock() + + for _, ch := range changeNotifyCh { + select { + case ch <- struct{}{}: + default: + } + } + + return nil +} + +func (m *ConfigManager) Watch() <-chan struct{} { + m.callbacksMu.Lock() + ch := make(chan struct{}, 1) + m.changeNotifyCh = append(m.changeNotifyCh, ch) + m.callbacksMu.Unlock() + return ch +} + +func (m *ConfigManager) StopWatching(ch <-chan struct{}) bool { + m.callbacksMu.Lock() + origLen := len(m.changeNotifyCh) + + for i := range m.changeNotifyCh { + if m.changeNotifyCh[i] != ch { + continue + } + close(m.changeNotifyCh[i]) + m.changeNotifyCh[i] = m.changeNotifyCh[len(m.changeNotifyCh)-1] + m.changeNotifyCh = m.changeNotifyCh[:len(m.changeNotifyCh)-1] + break + } + + defer m.callbacksMu.Unlock() + return len(m.changeNotifyCh) != origLen +} + type ConfigStore interface { Get() (*v1.Config, error) Update(config *v1.Config) error @@ -29,6 +89,7 @@ func NewDefaultConfig() *v1.Config { } } +// TODO: merge caching validating store functions into config manager type CachingValidatingStore struct { ConfigStore mu sync.Mutex diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 868b00c..4a56053 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -6,6 +6,7 @@ import ( v1 "github.com/garethgeorge/backrest/gen/go/v1" "github.com/garethgeorge/backrest/internal/config/migrations" + "github.com/garethgeorge/backrest/internal/cryptoutil" "google.golang.org/protobuf/proto" ) @@ -27,6 +28,7 @@ func TestConfig(t *testing.T) { testRepo := &v1.Repo{ Id: "test-repo", + Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits), Uri: "/tmp/test", Password: "test", PrunePolicy: &v1.PrunePolicy{ diff --git a/internal/config/configutil.go b/internal/config/configutil.go index 70eb902..654cab2 100644 --- a/internal/config/configutil.go +++ b/internal/config/configutil.go @@ -1,6 +1,8 @@ package config -import v1 "github.com/garethgeorge/backrest/gen/go/v1" +import ( + v1 "github.com/garethgeorge/backrest/gen/go/v1" +) func FindPlan(cfg *v1.Config, planID string) *v1.Plan { for _, plan := range cfg.Plans { @@ -19,3 +21,12 @@ func FindRepo(cfg *v1.Config, repoID string) *v1.Repo { } return nil } + +func FindRepoByGUID(cfg *v1.Config, guid string) *v1.Repo { + for _, repo := range cfg.Repos { + if repo.Guid == guid { + return repo + } + } + return nil +} diff --git a/internal/config/migrations/004repoguid.go b/internal/config/migrations/004repoguid.go new file mode 100644 index 0000000..eb9bc92 --- /dev/null +++ b/internal/config/migrations/004repoguid.go @@ -0,0 +1,15 @@ +package migrations + +import ( + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/internal/cryptoutil" +) + +var migration004RepoGuid = func(config *v1.Config) { + for _, repo := range config.Repos { + if repo.Guid != "" { + continue + } + repo.Guid = cryptoutil.MustRandomID(cryptoutil.DefaultIDBits) + } +} diff --git a/internal/config/migrations/migrations.go b/internal/config/migrations/migrations.go index d072ba2..2b42239 100644 --- a/internal/config/migrations/migrations.go +++ b/internal/config/migrations/migrations.go @@ -12,6 +12,7 @@ var migrations = []*func(*v1.Config){ &noop, // migration001PrunePolicy is deprecated &noop, // migration002Schedules is deprecated &migration003RelativeScheduling, + &migration004RepoGuid, } var CurrentVersion = int32(len(migrations)) diff --git a/internal/config/validate.go b/internal/config/validate.go index 439347f..91eb0c4 100644 --- a/internal/config/validate.go +++ b/internal/config/validate.go @@ -17,10 +17,6 @@ import ( func ValidateConfig(c *v1.Config) error { var err error - if e := validateAuth(c.Auth); e != nil { - err = multierror.Append(err, fmt.Errorf("auth: %w", e)) - } - if e := validationutil.ValidateID(c.Instance, validationutil.IDMaxLen); e != nil { if errors.Is(e, validationutil.ErrEmpty) { zap.L().Warn("ACTION REQUIRED: instance ID is empty, will be required in a future update. Please open the backrest UI to set a unique instance ID. Until fixed this warning (and related errors) will print periodically.") @@ -29,6 +25,10 @@ func ValidateConfig(c *v1.Config) error { } } + if e := validateAuth(c.Auth); e != nil { + err = multierror.Append(err, fmt.Errorf("auth: %w", e)) + } + repos := make(map[string]*v1.Repo) if c.Repos != nil { for _, repo := range c.Repos { @@ -67,19 +67,32 @@ func ValidateConfig(c *v1.Config) error { }) } + if e := validateMultihost(c); e != nil { + err = multierror.Append(err, fmt.Errorf("multihost: %w", e)) + } + return err } func validateRepo(repo *v1.Repo) error { var err error + if e := validationutil.ValidateID(repo.Id, 0); e != nil { err = multierror.Append(err, fmt.Errorf("id %q invalid: %w", repo.Id, e)) } + if len(repo.Guid) != 64 { // 64 bits in hex + err = multierror.Append(err, fmt.Errorf("guid %q invalid: must be 64 characters", repo.Guid)) + } + if repo.Uri == "" { err = multierror.Append(err, errors.New("uri is required")) } + if repo.Guid == "" { + err = multierror.Append(err, errors.New("guid is required")) + } + if repo.PrunePolicy.GetSchedule() != nil { if e := protoutil.ValidateSchedule(repo.PrunePolicy.GetSchedule()); e != nil { err = multierror.Append(err, fmt.Errorf("prune policy schedule: %w", e)) @@ -162,3 +175,42 @@ func validateAuth(auth *v1.Auth) error { return nil } + +func validateMultihost(config *v1.Config) (err error) { + multihost := config.GetMultihost() + if multihost == nil { + return + } + + for _, peer := range multihost.GetAuthorizedClients() { + if e := validatePeer(peer, false); e != nil { + err = multierror.Append(err, fmt.Errorf("authorized client %q: %w", peer.GetInstanceId(), e)) + } + } + + for _, peer := range multihost.GetKnownHosts() { + if e := validatePeer(peer, true); e != nil { + err = multierror.Append(err, fmt.Errorf("known host %q: %w", peer.GetInstanceId(), e)) + } + } + + return +} + +func validatePeer(peer *v1.Multihost_Peer, isKnownHost bool) error { + if e := validationutil.ValidateID(peer.InstanceId, validationutil.IDMaxLen); e != nil { + return fmt.Errorf("id %q invalid: %w", peer.InstanceId, e) + } + + if isKnownHost { + if peer.InstanceUrl == "" { + return errors.New("instance URL is required for known hosts") + } + } + + if peer.PublicKeyVerified && peer.GetPublicKey() == nil { + return errors.New("public key cannot be marked as verified if it is unset") + } + + return nil +} diff --git a/internal/cryptoutil/idutil.go b/internal/cryptoutil/idutil.go new file mode 100644 index 0000000..9eac725 --- /dev/null +++ b/internal/cryptoutil/idutil.go @@ -0,0 +1,52 @@ +package cryptoutil + +import ( + "crypto/rand" + "encoding/binary" + "encoding/hex" +) + +var ( + DefaultIDBits = 256 +) + +func RandomUint64() (uint64, error) { + b := make([]byte, 8) + _, err := rand.Read(b) + if err != nil { + return 0, err + } + return binary.BigEndian.Uint64(b), nil +} + +func MustRandomUint64() uint64 { + id, err := RandomUint64() + if err != nil { + panic(err) + } + return id +} + +func RandomID(bits int) (string, error) { + b := make([]byte, bits/8) + _, err := rand.Read(b) + if err != nil { + return "", err + } + return hex.EncodeToString(b), nil +} + +func MustRandomID(bits int) string { + id, err := RandomID(bits) + if err != nil { + panic(err) + } + return id +} + +func TruncateID(id string, bits int) string { + if len(id)*4 < bits { + return id + } + return id[:bits/4] +} diff --git a/internal/hook/hook.go b/internal/hook/hook.go index edb1a54..344e430 100644 --- a/internal/hook/hook.go +++ b/internal/hook/hook.go @@ -31,7 +31,7 @@ func TasksTriggeredByEvent(config *v1.Config, repoID string, planID string, pare } name := fmt.Sprintf("repo/%v/hook/%v", repo.Id, idx) - task, err := newOneoffRunHookTask(name, config.Instance, repoID, planID, parentOp, time.Now(), hook, event, vars) + task, err := newOneoffRunHookTask(name, config.Instance, repo, planID, parentOp, time.Now(), hook, event, vars) if err != nil { return nil, err } @@ -45,7 +45,7 @@ func TasksTriggeredByEvent(config *v1.Config, repoID string, planID string, pare } name := fmt.Sprintf("plan/%v/hook/%v", plan.Id, idx) - task, err := newOneoffRunHookTask(name, config.Instance, repoID, planID, parentOp, time.Now(), hook, event, vars) + task, err := newOneoffRunHookTask(name, config.Instance, repo, planID, parentOp, time.Now(), hook, event, vars) if err != nil { return nil, err } @@ -55,7 +55,7 @@ func TasksTriggeredByEvent(config *v1.Config, repoID string, planID string, pare return taskSet, nil } -func newOneoffRunHookTask(title, instanceID, repoID, planID string, parentOp *v1.Operation, at time.Time, hook *v1.Hook, event v1.Hook_Condition, vars interface{}) (tasks.Task, error) { +func newOneoffRunHookTask(title, instanceID string, repo *v1.Repo, planID string, parentOp *v1.Operation, at time.Time, hook *v1.Hook, event v1.Hook_Condition, vars interface{}) (tasks.Task, error) { h, err := types.DefaultRegistry().GetHandler(hook) if err != nil { return nil, fmt.Errorf("no handler for hook type %T", hook.Action) @@ -68,17 +68,12 @@ func newOneoffRunHookTask(title, instanceID, repoID, planID string, parentOp *v1 BaseTask: tasks.BaseTask{ TaskType: "hook", TaskName: fmt.Sprintf("run hook %v", title), - TaskRepoID: repoID, + TaskRepo: repo, TaskPlanID: planID, }, FlowID: parentOp.GetFlowId(), RunAt: at, ProtoOp: &v1.Operation{ - InstanceId: instanceID, - RepoId: repoID, - PlanId: planID, - FlowId: parentOp.GetFlowId(), - DisplayMessage: fmt.Sprintf("running %v triggered by %v", title, event.String()), Op: &v1.Operation_OperationRunHook{ OperationRunHook: &v1.OperationRunHook{ diff --git a/internal/oplog/bboltstore/bboltstore.go b/internal/oplog/bboltstore/bboltstore.go index 1b6c0e1..83524f8 100644 --- a/internal/oplog/bboltstore/bboltstore.go +++ b/internal/oplog/bboltstore/bboltstore.go @@ -297,11 +297,11 @@ func (o *BboltStore) Get(id int64) (*v1.Operation, error) { // Query represents a query to the operation log. type Query struct { - RepoId string - PlanId string - SnapshotId string - FlowId int64 - InstanceId string + RepoId *string + PlanId *string + SnapshotId *string + FlowId *int64 + InstanceId *string Ids []int64 } @@ -311,6 +311,10 @@ func (o *BboltStore) Query(q oplog.Query, f func(*v1.Operation) error) error { }, true) } +func (o *BboltStore) QueryMetadata(q oplog.Query, f func(oplog.OpMetadata) error) error { + return errors.New("not implemented") +} + func (o *BboltStore) Transform(q oplog.Query, f func(*v1.Operation) (*v1.Operation, error)) error { return o.queryHelper(q, func(tx *bbolt.Tx, op *v1.Operation) error { origId := op.Id @@ -321,6 +325,7 @@ func (o *BboltStore) Transform(q oplog.Query, f func(*v1.Operation) (*v1.Operati if transformed == nil { return nil } + transformed.Modno = op.Modno + 1 if _, err := o.deleteOperationHelper(tx, origId); err != nil { return fmt.Errorf("deleting old operation: %w", err) } @@ -334,26 +339,28 @@ func (o *BboltStore) Transform(q oplog.Query, f func(*v1.Operation) (*v1.Operati func (o *BboltStore) queryHelper(query oplog.Query, do func(tx *bbolt.Tx, op *v1.Operation) error, isReadOnly bool) error { helper := func(tx *bolt.Tx) error { iterators := make([]indexutil.IndexIterator, 0, 5) - if query.RepoID != "" { - iterators = append(iterators, indexutil.IndexSearchByteValue(tx.Bucket(RepoIndexBucket), []byte(query.RepoID))) + if query.PlanID != nil { + iterators = append(iterators, indexutil.IndexSearchByteValue(tx.Bucket(PlanIndexBucket), []byte(*query.PlanID))) } - if query.PlanID != "" { - iterators = append(iterators, indexutil.IndexSearchByteValue(tx.Bucket(PlanIndexBucket), []byte(query.PlanID))) + if query.SnapshotID != nil { + iterators = append(iterators, indexutil.IndexSearchByteValue(tx.Bucket(SnapshotIndexBucket), []byte(*query.SnapshotID))) } - if query.SnapshotID != "" { - iterators = append(iterators, indexutil.IndexSearchByteValue(tx.Bucket(SnapshotIndexBucket), []byte(query.SnapshotID))) + if query.FlowID != nil { + iterators = append(iterators, indexutil.IndexSearchByteValue(tx.Bucket(FlowIdIndexBucket), serializationutil.Itob(*query.FlowID))) } - if query.FlowID != 0 { - iterators = append(iterators, indexutil.IndexSearchByteValue(tx.Bucket(FlowIdIndexBucket), serializationutil.Itob(query.FlowID))) - } - if query.InstanceID != "" { - iterators = append(iterators, indexutil.IndexSearchByteValue(tx.Bucket(InstanceIndexBucket), []byte(query.InstanceID))) + if query.InstanceID != nil { + iterators = append(iterators, indexutil.IndexSearchByteValue(tx.Bucket(InstanceIndexBucket), []byte(*query.InstanceID))) } var ids []int64 if len(iterators) == 0 && len(query.OpIDs) == 0 { if query.Limit == 0 && query.Offset == 0 && !query.Reversed { - return o.forAll(tx, func(op *v1.Operation) error { return do(tx, op) }) + return o.forAll(tx, func(op *v1.Operation) error { + if query.Match(op) { + return do(tx, op) + } + return nil + }) } else { b := tx.Bucket(OpLogBucket) c := b.Cursor() @@ -384,7 +391,10 @@ func (o *BboltStore) queryHelper(query oplog.Query, do func(tx *bbolt.Tx, op *v1 } return o.forOpsByIds(tx, ids, func(op *v1.Operation) error { - return do(tx, op) + if query.Match(op) { + return do(tx, op) + } + return nil }) } if isReadOnly { diff --git a/internal/oplog/memstore/memstore.go b/internal/oplog/memstore/memstore.go index a26affc..3225afe 100644 --- a/internal/oplog/memstore/memstore.go +++ b/internal/oplog/memstore/memstore.go @@ -80,6 +80,22 @@ func (m *MemStore) Query(q oplog.Query, f func(*v1.Operation) error) error { return nil } +func (m *MemStore) QueryMetadata(q oplog.Query, f func(meta oplog.OpMetadata) error) error { + for _, id := range m.idsForQuery(q) { + op := m.operations[id] + if err := f(oplog.OpMetadata{ + ID: op.Id, + Modno: op.Modno, + FlowID: op.FlowId, + OriginalID: op.OriginalId, + OriginalFlowID: op.OriginalFlowId, + }); err != nil { + return err + } + } + return nil +} + func (m *MemStore) Transform(q oplog.Query, f func(*v1.Operation) (*v1.Operation, error)) error { m.mu.Lock() defer m.mu.Unlock() @@ -95,6 +111,7 @@ func (m *MemStore) Transform(q oplog.Query, f func(*v1.Operation) (*v1.Operation } return err } else if op != nil { + op.Modno = oplog.NewRandomModno(m.operations[id].Modno) changes[id] = op } } @@ -142,8 +159,8 @@ func (m *MemStore) Delete(opID ...int64) ([]*v1.Operation, error) { m.mu.Lock() defer m.mu.Unlock() ops := make([]*v1.Operation, 0, len(opID)) - for idx, id := range opID { - ops[idx] = m.operations[id] + for _, id := range opID { + ops = append(ops, m.operations[id]) delete(m.operations, id) } return ops, nil diff --git a/internal/oplog/migrations.go b/internal/oplog/migrations.go index 8a77529..783f584 100644 --- a/internal/oplog/migrations.go +++ b/internal/oplog/migrations.go @@ -31,9 +31,9 @@ func ApplyMigrations(oplog *OpLog) error { } for idx := startMigration; idx < int64(len(migrations)); idx += 1 { - zap.L().Info("applying oplog migration", zap.Int64("migration_no", idx)) + zap.L().Info("oplog applying data migration", zap.Int64("migration_no", idx)) if err := migrations[idx](oplog); err != nil { - zap.L().Error("failed to apply migration", zap.Int64("migration_no", idx), zap.Error(err)) + zap.L().Error("failed to apply data migration", zap.Int64("migration_no", idx), zap.Error(err)) return fmt.Errorf("couldn't apply migration %d: %w", idx, err) } if err := oplog.store.SetVersion(idx + 1); err != nil { diff --git a/internal/oplog/oplog.go b/internal/oplog/oplog.go index 038f998..9a618b9 100644 --- a/internal/oplog/oplog.go +++ b/internal/oplog/oplog.go @@ -74,6 +74,10 @@ func (o *OpLog) Query(q Query, f func(*v1.Operation) error) error { return o.store.Query(q, f) } +func (o *OpLog) QueryMetadata(q Query, f func(OpMetadata) error) error { + return o.store.QueryMetadata(q, f) +} + func (o *OpLog) Subscribe(q Query, f *Subscription) { o.subscribers = append(o.subscribers, subAndQuery{f: f, q: q}) } @@ -97,6 +101,9 @@ func (o *OpLog) Add(ops ...*v1.Operation) error { if o.Id != 0 { return errors.New("operation already has an ID, OpLog.Add is expected to set the ID") } + if o.Modno == 0 { + o.Modno = NewRandomModno(0) + } } if err := o.store.Add(ops...); err != nil { @@ -112,6 +119,7 @@ func (o *OpLog) Update(ops ...*v1.Operation) error { if o.Id == 0 { return errors.New("operation does not have an ID, OpLog.Update is expected to have an ID") } + o.Modno = NewRandomModno(o.Modno) } if err := o.store.Update(ops...); err != nil { @@ -122,6 +130,24 @@ func (o *OpLog) Update(ops ...*v1.Operation) error { return nil } +// Set is an alias for Update that does not increment the modno, provided for use by the syncapi. +func (o *OpLog) Set(op *v1.Operation) error { + var err error + if op.Id == 0 { + err = o.store.Add(op) + } else { + err = o.store.Update(op) + if errors.Is(err, ErrNotExist) { + err = o.store.Add(op) + } + } + if err != nil { + return err + } + o.notify([]*v1.Operation{op}, OPERATION_UPDATED) + return nil +} + func (o *OpLog) Delete(opID ...int64) error { removedOps, err := o.store.Delete(opID...) if err != nil { @@ -137,71 +163,32 @@ func (o *OpLog) Transform(q Query, f func(*v1.Operation) (*v1.Operation, error)) } type OpStore interface { + // Query returns all operations that match the query. Query(q Query, f func(*v1.Operation) error) error + // QueryMetadata is like Query, but only returns metadata about the operations. + // this is useful for very high performance scans that don't deserialize the operation itself. + QueryMetadata(q Query, f func(OpMetadata) error) error + // Get returns the operation with the given ID. Get(opID int64) (*v1.Operation, error) + // Add adds the given operations to the store. Add(op ...*v1.Operation) error - Update(op ...*v1.Operation) error // returns the previous values of the updated operations OR an error - Delete(opID ...int64) ([]*v1.Operation, error) // returns the deleted operations OR an error + // Update updates the given operations in the store. + Update(op ...*v1.Operation) error + // Delete removes the operations with the given IDs from the store, and returns the removed operations. + Delete(opID ...int64) ([]*v1.Operation, error) + // Transform applies the given function to each operation that matches the query. Transform(q Query, f func(*v1.Operation) (*v1.Operation, error)) error + // Version returns the current data version Version() (int64, error) + // SetVersion sets the data version SetVersion(version int64) error } -type Query struct { - // Filter by fields - OpIDs []int64 - PlanID string - RepoID string - SnapshotID string - FlowID int64 - InstanceID string - - // Pagination - Limit int - Offset int - Reversed bool - - opIDmap map[int64]struct{} -} - -var SelectAll = Query{} - -func (q *Query) buildOpIDMap() { - if len(q.OpIDs) != len(q.opIDmap) { - q.opIDmap = make(map[int64]struct{}, len(q.OpIDs)) - for _, opID := range q.OpIDs { - q.opIDmap[opID] = struct{}{} - } - } -} - -func (q *Query) Match(op *v1.Operation) bool { - if len(q.OpIDs) > 0 { - q.buildOpIDMap() - if _, ok := q.opIDmap[op.Id]; !ok { - return false - } - } - - if q.InstanceID != "" && op.InstanceId != q.InstanceID { - return false - } - - if q.PlanID != "" && op.PlanId != q.PlanID { - return false - } - - if q.RepoID != "" && op.RepoId != q.RepoID { - return false - } - - if q.SnapshotID != "" && op.SnapshotId != q.SnapshotID { - return false - } - - if q.FlowID != 0 && op.FlowId != q.FlowID { - return false - } - - return true +// OpMetadata is a struct that contains metadata about an operation without fetching the operation itself. +type OpMetadata struct { + ID int64 + FlowID int64 + Modno int64 + OriginalID int64 + OriginalFlowID int64 } diff --git a/internal/oplog/query.go b/internal/oplog/query.go new file mode 100644 index 0000000..7c59b49 --- /dev/null +++ b/internal/oplog/query.go @@ -0,0 +1,127 @@ +package oplog + +import v1 "github.com/garethgeorge/backrest/gen/go/v1" + +type Query struct { + // Filter by fields + OpIDs []int64 + PlanID *string + RepoGUID *string + SnapshotID *string + FlowID *int64 + InstanceID *string + OriginalID *int64 + OriginalFlowID *int64 + + // Pagination + Limit int + Offset int + Reversed bool + + opIDmap map[int64]struct{} +} + +func (q Query) SetOpIDs(opIDs []int64) Query { + q.OpIDs = opIDs + return q +} + +func (q Query) SetPlanID(planID string) Query { + q.PlanID = &planID + return q +} + +func (q Query) SetRepoGUID(repoGUID string) Query { + q.RepoGUID = &repoGUID + return q +} + +func (q Query) SetSnapshotID(snapshotID string) Query { + q.SnapshotID = &snapshotID + return q +} + +func (q Query) SetFlowID(flowID int64) Query { + q.FlowID = &flowID + return q +} + +func (q Query) SetInstanceID(instanceID string) Query { + q.InstanceID = &instanceID + return q +} + +func (q Query) SetOriginalID(originalID int64) Query { + q.OriginalID = &originalID + return q +} + +func (q Query) SetOriginalFlowID(originalFlowID int64) Query { + q.OriginalFlowID = &originalFlowID + return q +} + +func (q Query) SetLimit(limit int) Query { + q.Limit = limit + return q +} + +func (q Query) SetOffset(offset int) Query { + q.Offset = offset + return q +} + +func (q Query) SetReversed(reversed bool) Query { + q.Reversed = reversed + return q +} + +var SelectAll = Query{} + +func (q *Query) buildOpIDMap() { + if len(q.OpIDs) != len(q.opIDmap) { + q.opIDmap = make(map[int64]struct{}, len(q.OpIDs)) + for _, opID := range q.OpIDs { + q.opIDmap[opID] = struct{}{} + } + } +} + +func (q *Query) Match(op *v1.Operation) bool { + if len(q.OpIDs) > 0 { + q.buildOpIDMap() + if _, ok := q.opIDmap[op.Id]; !ok { + return false + } + } + + if q.InstanceID != nil && op.InstanceId != *q.InstanceID { + return false + } + + if q.PlanID != nil && op.PlanId != *q.PlanID { + return false + } + + if q.RepoGUID != nil && op.RepoGuid != *q.RepoGUID { + return false + } + + if q.SnapshotID != nil && op.SnapshotId != *q.SnapshotID { + return false + } + + if q.FlowID != nil && op.FlowId != *q.FlowID { + return false + } + + if q.OriginalID != nil && op.OriginalId != *q.OriginalID { + return false + } + + if q.OriginalFlowID != nil && op.OriginalFlowId != *q.OriginalFlowID { + return false + } + + return true +} diff --git a/internal/oplog/randmodno.go b/internal/oplog/randmodno.go new file mode 100644 index 0000000..5a98b39 --- /dev/null +++ b/internal/oplog/randmodno.go @@ -0,0 +1,24 @@ +package oplog + +import ( + rand "math/rand/v2" + "sync" + + "github.com/garethgeorge/backrest/internal/cryptoutil" +) + +// setup a fast random number generator seeded with cryptographic randomness. +var mu sync.Mutex +var pgcRand = rand.NewPCG(cryptoutil.MustRandomUint64(), cryptoutil.MustRandomUint64()) +var randGen = rand.New(pgcRand) + +func NewRandomModno(lastModno int64) int64 { + mu.Lock() + defer mu.Unlock() + for { + modno := randGen.Int64() + if modno != lastModno { + return modno + } + } +} diff --git a/internal/oplog/sqlitestore/migrations.go b/internal/oplog/sqlitestore/migrations.go new file mode 100644 index 0000000..493d2d9 --- /dev/null +++ b/internal/oplog/sqlitestore/migrations.go @@ -0,0 +1,171 @@ +package sqlitestore + +import ( + "fmt" + + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "go.uber.org/zap" + "google.golang.org/protobuf/proto" + "zombiezen.com/go/sqlite" + "zombiezen.com/go/sqlite/sqlitex" +) + +const sqlSchemaVersion = 4 + +var sqlSchema = fmt.Sprintf(` +PRAGMA user_version = %d; + +CREATE TABLE IF NOT EXISTS system_info (version INTEGER NOT NULL); +INSERT INTO system_info (version) +SELECT 0 WHERE NOT EXISTS (SELECT 1 FROM system_info); + +CREATE TABLE operations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + ogid INTEGER NOT NULL, + original_id INTEGER NOT NULL, + original_flow_id INTEGER NOT NULL, + modno INTEGER NOT NULL, + flow_id INTEGER NOT NULL, + start_time_ms INTEGER NOT NULL, + status INTEGER NOT NULL, + snapshot_id STRING NOT NULL, + operation BLOB NOT NULL, + FOREIGN KEY (ogid) REFERENCES operation_groups (ogid) +); +CREATE INDEX operation_ogid ON operations (ogid); +CREATE INDEX operation_snapshot_id ON operations (snapshot_id); +CREATE INDEX operation_flow_id ON operations (flow_id); +CREATE INDEX operation_start_time_ms ON operations (start_time_ms); +CREATE INDEX operation_original_id ON operations (ogid, original_id); +CREATE INDEX operation_original_flow_id ON operations (ogid, original_flow_id); + +CREATE TABLE operation_groups ( + ogid INTEGER PRIMARY KEY AUTOINCREMENT, + instance_id STRING NOT NULL, + repo_guid STRING NOT NULL, + repo_id STRING NOT NULL, + plan_id STRING NOT NULL +); +CREATE INDEX group_repo_instance ON operation_groups (repo_id, instance_id); +CREATE INDEX group_repo_guid ON operation_groups (repo_guid); +CREATE INDEX group_instance ON operation_groups (instance_id); +`, sqlSchemaVersion) + +func applySqliteMigrations(store *SqliteStore, conn *sqlite.Conn) error { + var version int + if err := sqlitex.ExecuteTransient(conn, "PRAGMA user_version", &sqlitex.ExecOptions{ + ResultFunc: func(stmt *sqlite.Stmt) error { + version = stmt.ColumnInt(0) + return nil + }, + }); err != nil { + return fmt.Errorf("getting database schema version: %w", err) + } + + if version == sqlSchemaVersion { + return nil + } + + zap.S().Infof("applying oplog sqlite schema migration from storage schema %d to %d", version, sqlSchemaVersion) + + if err := withSqliteTransaction(conn, func() error { + // Check if operations table exists and rename it + var hasOperationsTable bool + if err := sqlitex.ExecuteTransient(conn, "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='operations'", &sqlitex.ExecOptions{ + ResultFunc: func(stmt *sqlite.Stmt) error { + hasOperationsTable = stmt.ColumnInt(0) > 0 + return nil + }, + }); err != nil { + return fmt.Errorf("checking for operations table: %w", err) + } + + if hasOperationsTable { + zap.S().Info("renaming existing operations table to operations_old as a backup") + if err := sqlitex.ExecuteTransient(conn, "ALTER TABLE operations RENAME TO operations_old", nil); err != nil { + return fmt.Errorf("renaming operations table: %w", err) + } + + // drop all tables that aren't operations_old + drop_tables := []string{ + "operation_groups", + "operations", + } + for _, table := range drop_tables { + if err := sqlitex.ExecuteTransient(conn, fmt.Sprintf("DROP TABLE IF EXISTS %s", table), nil); err != nil { + return fmt.Errorf("dropping table %s: %w", table, err) + } + } + + // drop all indexes + indexes := []string{} + if err := sqlitex.ExecuteTransient(conn, "SELECT name FROM sqlite_master WHERE type='index'", &sqlitex.ExecOptions{ + ResultFunc: func(stmt *sqlite.Stmt) error { + indexes = append(indexes, stmt.ColumnText(0)) + return nil + }, + }); err != nil { + return fmt.Errorf("dropping indexes: %w", err) + } + for _, index := range indexes { + if err := sqlitex.ExecuteTransient(conn, fmt.Sprintf("DROP INDEX IF EXISTS %s", index), nil); err != nil { + return fmt.Errorf("dropping index %s: %w", index, err) + } + } + } + + // Apply the new schema + if err := sqlitex.ExecuteScript(conn, sqlSchema, &sqlitex.ExecOptions{}); err != nil { + return fmt.Errorf("applying schema: %w", err) + } + + // Copy data from old table if it exists + if hasOperationsTable { + var ops []*v1.Operation + batchInsert := func() error { + if err := store.addInternal(conn, ops...); err != nil { + return err + } + ops = nil + return nil + } + + if err := sqlitex.ExecuteTransient(conn, "SELECT operation FROM operations_old", &sqlitex.ExecOptions{ + ResultFunc: func(stmt *sqlite.Stmt) error { + var op v1.Operation + bytes := make([]byte, stmt.ColumnLen(0)) + n := stmt.ColumnBytes(0, bytes) + bytes = bytes[:n] + if err := proto.Unmarshal(bytes, &op); err != nil { + return fmt.Errorf("unmarshal operation: %v", err) + } + + ops = append(ops, &op) + if len(ops) >= 512 { + if err := batchInsert(); err != nil { + return err + } + } + return nil + }, + }); err != nil { + return fmt.Errorf("copying data from old table: %w", err) + } + + if len(ops) > 0 { + if err := batchInsert(); err != nil { + return fmt.Errorf("copying data from old table: %w", err) + } + } + + if err := sqlitex.ExecuteTransient(conn, "DROP TABLE operations_old", nil); err != nil { + return fmt.Errorf("dropping old table: %w", err) + } + } + + return nil + }); err != nil { + return fmt.Errorf("migrate sqlite schema from version %d to %d: %w", version, sqlSchemaVersion, err) + } + return nil +} diff --git a/internal/oplog/sqlitestore/migrations_test.go b/internal/oplog/sqlitestore/migrations_test.go new file mode 100644 index 0000000..4c8b37e --- /dev/null +++ b/internal/oplog/sqlitestore/migrations_test.go @@ -0,0 +1,82 @@ +package sqlitestore + +import ( + "testing" + + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/internal/oplog" + "github.com/garethgeorge/backrest/internal/testutil" + "github.com/google/go-cmp/cmp" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestNewSqliteStore(t *testing.T) { + tempDir := t.TempDir() + store, err := NewSqliteStore(tempDir + "/test.sqlite") + if err != nil { + t.Fatalf("error creating sqlite store: %s", err) + } + t.Cleanup(func() { store.Close() }) +} + +func TestMigrateExisting(t *testing.T) { + tempDir := t.TempDir() + + testOps := []*v1.Operation{} + for i := 0; i < 10; i++ { + testOps = append(testOps, testutil.RandomOperation()) + } + + store, err := NewSqliteStore(tempDir + "/test.sqlite") + if err != nil { + t.Fatalf("error creating sqlite store: %s", err) + } + + // insert some test data + if err := store.Add(testOps...); err != nil { + t.Fatalf("error adding test data: %s", err) + } + + gotOps := make([]*v1.Operation, 0) + if err := store.Query(oplog.Query{}, func(op *v1.Operation) error { + gotOps = append(gotOps, op) + return nil + }); err != nil { + t.Fatalf("error querying sqlite store: %s", err) + } + + if len(gotOps) != len(testOps) { + t.Errorf("first check before migrations, expected %d operations, got %d", len(testOps), len(gotOps)) + } + + if err := store.Close(); err != nil { + t.Fatalf("error closing sqlite store: %s", err) + } + + // re-open the store + store2, err := NewSqliteStore(tempDir + "/test.sqlite") + if err != nil { + t.Fatalf("error creating sqlite store: %s", err) + } + + gotOps = gotOps[:0] + if err := store2.Query(oplog.Query{}, func(op *v1.Operation) error { + gotOps = append(gotOps, op) + return nil + }); err != nil { + t.Fatalf("error querying sqlite store: %s", err) + } + + if len(gotOps) != len(testOps) { + t.Errorf("expected %d operations, got %d", len(testOps), len(gotOps)) + } + + if diff := cmp.Diff( + &v1.OperationList{Operations: gotOps}, + &v1.OperationList{Operations: testOps}, + protocmp.Transform()); diff != "" { + t.Errorf("unexpected diff in operations back after migration: %v", diff) + } + + t.Cleanup(func() { store2.Close() }) +} diff --git a/internal/oplog/sqlitestore/sqlitestore.go b/internal/oplog/sqlitestore/sqlitestore.go index d51f6e8..19579b7 100644 --- a/internal/oplog/sqlitestore/sqlitestore.go +++ b/internal/oplog/sqlitestore/sqlitestore.go @@ -7,11 +7,16 @@ import ( "os" "path/filepath" "strings" + "sync" "sync/atomic" + "testing" v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/internal/cryptoutil" "github.com/garethgeorge/backrest/internal/oplog" "github.com/garethgeorge/backrest/internal/protoutil" + lru "github.com/hashicorp/golang-lru/v2" + "go.uber.org/zap" "google.golang.org/protobuf/proto" "github.com/gofrs/flock" @@ -23,8 +28,13 @@ var ErrLocked = errors.New("sqlite db is locked") type SqliteStore struct { dbpool *sqlitex.Pool - nextIDVal atomic.Int64 + lastIDVal atomic.Int64 dblock *flock.Flock + querymu sync.RWMutex + + ogidCache *lru.TwoQueueCache[opGroupInfo, int64] + + tidyGroupsOnce sync.Once } var _ oplog.OpStore = (*SqliteStore)(nil) @@ -40,9 +50,11 @@ func NewSqliteStore(db string) (*SqliteStore, error) { if err != nil { return nil, fmt.Errorf("open sqlite pool: %v", err) } + ogidCache, _ := lru.New2Q[opGroupInfo, int64](128) store := &SqliteStore{ - dbpool: dbpool, - dblock: flock.New(db + ".lock"), + dbpool: dbpool, + dblock: flock.New(db + ".lock"), + ogidCache: ogidCache, } if locked, err := store.dblock.TryLock(); err != nil { return nil, fmt.Errorf("lock sqlite db: %v", err) @@ -55,57 +67,54 @@ func NewSqliteStore(db string) (*SqliteStore, error) { return store, nil } +func NewMemorySqliteStore() (*SqliteStore, error) { + dbpool, err := sqlitex.NewPool("file:"+cryptoutil.MustRandomID(64)+"?mode=memory&cache=shared", sqlitex.PoolOptions{ + PoolSize: 16, + Flags: sqlite.OpenReadWrite | sqlite.OpenCreate | sqlite.OpenURI, + }) + if err != nil { + return nil, fmt.Errorf("open sqlite pool: %v", err) + } + ogidCache, _ := lru.New2Q[opGroupInfo, int64](128) + store := &SqliteStore{ + dbpool: dbpool, + ogidCache: ogidCache, + } + if err := store.init(); err != nil { + return nil, err + } + return store, nil +} + func (m *SqliteStore) Close() error { - if err := m.dblock.Unlock(); err != nil { - return fmt.Errorf("unlock sqlite db: %v", err) + if m.dblock != nil { + if err := m.dblock.Unlock(); err != nil { + return fmt.Errorf("unlock sqlite db: %v", err) + } } return m.dbpool.Close() } func (m *SqliteStore) init() error { - var script = ` -PRAGMA journal_mode=WAL; -PRAGMA page_size=4096; -CREATE TABLE IF NOT EXISTS operations ( - id INTEGER PRIMARY KEY, - start_time_ms INTEGER NOT NULL, - flow_id INTEGER NOT NULL, - instance_id STRING NOT NULL, - plan_id STRING NOT NULL, - repo_id STRING NOT NULL, - snapshot_id STRING NOT NULL, - operation BLOB NOT NULL -); -CREATE TABLE IF NOT EXISTS system_info ( - version INTEGER NOT NULL -); -CREATE INDEX IF NOT EXISTS operations_repo_id_plan_id_instance_id ON operations (repo_id, plan_id, instance_id); -CREATE INDEX IF NOT EXISTS operations_snapshot_id ON operations (snapshot_id); -CREATE INDEX IF NOT EXISTS operations_flow_id ON operations (flow_id); -CREATE INDEX IF NOT EXISTS operations_start_time_ms ON operations (start_time_ms); - -INSERT INTO system_info (version) -SELECT 0 WHERE NOT EXISTS (SELECT 1 FROM system_info); -` conn, err := m.dbpool.Take(context.Background()) if err != nil { return fmt.Errorf("init sqlite: %v", err) } defer m.dbpool.Put(conn) - if err := sqlitex.ExecScript(conn, script); err != nil { - return fmt.Errorf("init sqlite: %v", err) + + if err := applySqliteMigrations(m, conn); err != nil { + return err } - // rand init value - if err := sqlitex.ExecuteTransient(conn, "SELECT id FROM operations ORDER BY id DESC LIMIT 1", &sqlitex.ExecOptions{ + if err := sqlitex.ExecuteTransient(conn, "SELECT operations.id FROM operations ORDER BY operations.id DESC LIMIT 1", &sqlitex.ExecOptions{ ResultFunc: func(stmt *sqlite.Stmt) error { - m.nextIDVal.Store(stmt.GetInt64("id")) + m.lastIDVal.Store(stmt.GetInt64("id")) return nil }, }); err != nil { return fmt.Errorf("init sqlite: %v", err) } - m.nextIDVal.CompareAndSwap(0, 1) + return nil } @@ -143,32 +152,42 @@ func (m *SqliteStore) SetVersion(version int64) error { return nil } -func (m *SqliteStore) buildQuery(q oplog.Query, includeSelectClauses bool) (string, []any) { - query := []string{`SELECT operation FROM operations WHERE 1=1`} - args := []any{} +func (m *SqliteStore) buildQueryWhereClause(q oplog.Query, includeSelectClauses bool) (string, []any) { + query := make([]string, 0, 8) + args := make([]any, 0, 8) - if q.FlowID != 0 { - query = append(query, " AND flow_id = ?") - args = append(args, q.FlowID) + query = append(query, " 1=1 ") + + if q.PlanID != nil { + query = append(query, " AND operation_groups.plan_id = ?") + args = append(args, *q.PlanID) } - if q.InstanceID != "" { - query = append(query, " AND instance_id = ?") - args = append(args, q.InstanceID) + if q.RepoGUID != nil { + query = append(query, " AND operation_groups.repo_guid = ?") + args = append(args, *q.RepoGUID) } - if q.PlanID != "" { - query = append(query, " AND plan_id = ?") - args = append(args, q.PlanID) + if q.InstanceID != nil { + query = append(query, " AND operation_groups.instance_id = ?") + args = append(args, *q.InstanceID) } - if q.RepoID != "" { - query = append(query, " AND repo_id = ?") - args = append(args, q.RepoID) + if q.SnapshotID != nil { + query = append(query, " AND operations.snapshot_id = ?") + args = append(args, *q.SnapshotID) } - if q.SnapshotID != "" { - query = append(query, " AND snapshot_id = ?") - args = append(args, q.SnapshotID) + if q.FlowID != nil { + query = append(query, " AND operations.flow_id = ?") + args = append(args, *q.FlowID) + } + if q.OriginalID != nil { + query = append(query, " AND operations.original_id = ?") + args = append(args, *q.OriginalID) + } + if q.OriginalFlowID != nil { + query = append(query, " AND operations.original_flow_id = ?") + args = append(args, *q.OriginalFlowID) } if q.OpIDs != nil { - query = append(query, " AND id IN (") + query = append(query, " AND operations.id IN (") for i, id := range q.OpIDs { if i > 0 { query = append(query, ",") @@ -181,9 +200,9 @@ func (m *SqliteStore) buildQuery(q oplog.Query, includeSelectClauses bool) (stri if includeSelectClauses { if q.Reversed { - query = append(query, " ORDER BY start_time_ms DESC, id DESC") + query = append(query, " ORDER BY operations.start_time_ms DESC, operations.id DESC") } else { - query = append(query, " ORDER BY start_time_ms ASC, id ASC") + query = append(query, " ORDER BY operations.start_time_ms ASC, operations.id ASC") } if q.Limit > 0 { @@ -199,19 +218,20 @@ func (m *SqliteStore) buildQuery(q oplog.Query, includeSelectClauses bool) (stri } } - return strings.Join(query, ""), args + return strings.Join(query, "")[1:], args } func (m *SqliteStore) Query(q oplog.Query, f func(*v1.Operation) error) error { + m.querymu.RLock() + defer m.querymu.RUnlock() conn, err := m.dbpool.Take(context.Background()) if err != nil { return fmt.Errorf("query: %v", err) } defer m.dbpool.Put(conn) - query, args := m.buildQuery(q, true) - - if err := sqlitex.ExecuteTransient(conn, query, &sqlitex.ExecOptions{ + where, args := m.buildQueryWhereClause(q, true) + if err := sqlitex.ExecuteTransient(conn, "SELECT operations.operation FROM operations JOIN operation_groups ON operations.ogid = operation_groups.ogid WHERE "+where, &sqlitex.ExecOptions{ Args: args, ResultFunc: func(stmt *sqlite.Stmt) error { opBytes := make([]byte, stmt.ColumnLen(0)) @@ -230,17 +250,90 @@ func (m *SqliteStore) Query(q oplog.Query, f func(*v1.Operation) error) error { return nil } +func (m *SqliteStore) QueryMetadata(q oplog.Query, f func(oplog.OpMetadata) error) error { + m.querymu.RLock() + defer m.querymu.RUnlock() + conn, err := m.dbpool.Take(context.Background()) + if err != nil { + return fmt.Errorf("query metadata: %v", err) + } + defer m.dbpool.Put(conn) + + where, args := m.buildQueryWhereClause(q, false) + if err := sqlitex.ExecuteTransient(conn, "SELECT operations.id, operations.modno, operations.original_id, operations.flow_id, operations.original_flow_id FROM operations JOIN operation_groups ON operations.ogid = operation_groups.ogid WHERE "+where, &sqlitex.ExecOptions{ + Args: args, + ResultFunc: func(stmt *sqlite.Stmt) error { + return f(oplog.OpMetadata{ + ID: stmt.ColumnInt64(0), + Modno: stmt.ColumnInt64(1), + OriginalID: stmt.ColumnInt64(2), + FlowID: stmt.ColumnInt64(3), + OriginalFlowID: stmt.ColumnInt64(4), + }) + }, + }); err != nil && !errors.Is(err, oplog.ErrStopIteration) { + return err + } + return nil +} + +// tidyGroups deletes operation groups that are no longer referenced, it takes an int64 specifying the maximum group ID to consider. +// this allows ignoring newly created groups that may not yet be referenced. +func (m *SqliteStore) tidyGroups(conn *sqlite.Conn, eligibleIDsBelow int64) { + err := sqlitex.ExecuteTransient(conn, "DELETE FROM operation_groups WHERE ogid NOT IN (SELECT DISTINCT ogid FROM operations WHERE ogid < ?)", &sqlitex.ExecOptions{ + Args: []any{eligibleIDsBelow}, + }) + if err != nil { + zap.S().Warnf("tidy groups: %v", err) + } +} + +func (m *SqliteStore) findOrCreateGroup(conn *sqlite.Conn, op *v1.Operation) (ogid int64, err error) { + ogidKey := groupInfoForOp(op) + if cachedOGID, ok := m.ogidCache.Get(ogidKey); ok { + return cachedOGID, nil + } + + var found bool + if err := sqlitex.Execute(conn, "SELECT ogid FROM operation_groups WHERE instance_id = ? AND repo_id = ? AND plan_id = ? AND repo_guid = ? LIMIT 1", &sqlitex.ExecOptions{ + Args: []any{op.InstanceId, op.RepoId, op.PlanId, op.RepoGuid}, + ResultFunc: func(stmt *sqlite.Stmt) error { + ogid = stmt.ColumnInt64(0) + found = true + return nil + }, + }); err != nil { + return 0, fmt.Errorf("find operation group: %v", err) + } + + if !found { + if err := sqlitex.Execute(conn, "INSERT INTO operation_groups (instance_id, repo_id, plan_id, repo_guid) VALUES (?, ?, ?, ?) RETURNING ogid", &sqlitex.ExecOptions{ + Args: []any{op.InstanceId, op.RepoId, op.PlanId, op.RepoGuid}, + ResultFunc: func(stmt *sqlite.Stmt) error { + ogid = stmt.ColumnInt64(0) + return nil + }, + }); err != nil { + return 0, fmt.Errorf("insert operation group: %v", err) + } + } + + m.ogidCache.Add(ogidKey, ogid) + return ogid, nil +} + func (m *SqliteStore) Transform(q oplog.Query, f func(*v1.Operation) (*v1.Operation, error)) error { + m.querymu.Lock() + defer m.querymu.Unlock() conn, err := m.dbpool.Take(context.Background()) if err != nil { return fmt.Errorf("transform: %v", err) } defer m.dbpool.Put(conn) - query, args := m.buildQuery(q, true) - + where, args := m.buildQueryWhereClause(q, true) return withSqliteTransaction(conn, func() error { - return sqlitex.ExecuteTransient(conn, query, &sqlitex.ExecOptions{ + return sqlitex.ExecuteTransient(conn, "SELECT operations.operation FROM operations JOIN operation_groups ON operations.ogid = operation_groups.ogid WHERE "+where, &sqlitex.ExecOptions{ Args: args, ResultFunc: func(stmt *sqlite.Stmt) error { opBytes := make([]byte, stmt.ColumnLen(0)) @@ -259,13 +352,47 @@ func (m *SqliteStore) Transform(q oplog.Query, f func(*v1.Operation) (*v1.Operat return nil } + newOp.Modno = oplog.NewRandomModno(op.Modno) + return m.updateInternal(conn, newOp) }, }) }) } +func (m *SqliteStore) addInternal(conn *sqlite.Conn, op ...*v1.Operation) error { + for _, o := range op { + ogid, err := m.findOrCreateGroup(conn, o) + if err != nil { + return fmt.Errorf("find ogid: %v", err) + } + + query := `INSERT INTO operations + (id, ogid, original_id, original_flow_id, modno, flow_id, start_time_ms, status, snapshot_id, operation) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` + bytes, err := proto.Marshal(o) + if err != nil { + return fmt.Errorf("marshal operation: %v", err) + } + + if err := sqlitex.Execute(conn, query, &sqlitex.ExecOptions{ + Args: []any{ + o.Id, ogid, o.OriginalId, o.OriginalFlowId, o.Modno, o.FlowId, + o.UnixTimeStartMs, int64(o.Status), o.SnapshotId, bytes, + }, + }); err != nil { + if sqlite.ErrCode(err) == sqlite.ResultConstraintUnique { + return fmt.Errorf("operation already exists %v: %w", o.Id, oplog.ErrExist) + } + return fmt.Errorf("add operation: %v", err) + } + } + return nil +} + func (m *SqliteStore) Add(op ...*v1.Operation) error { + m.querymu.Lock() + defer m.querymu.Unlock() conn, err := m.dbpool.Take(context.Background()) if err != nil { return fmt.Errorf("add operation: %v", err) @@ -274,35 +401,22 @@ func (m *SqliteStore) Add(op ...*v1.Operation) error { return withSqliteTransaction(conn, func() error { for _, o := range op { - o.Id = m.nextIDVal.Add(1) + o.Id = m.lastIDVal.Add(1) if o.FlowId == 0 { o.FlowId = o.Id } if err := protoutil.ValidateOperation(o); err != nil { return err } - - query := "INSERT INTO operations (id, start_time_ms, flow_id, instance_id, plan_id, repo_id, snapshot_id, operation) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" - - bytes, err := proto.Marshal(o) - if err != nil { - return fmt.Errorf("marshal operation: %v", err) - } - - if err := sqlitex.Execute(conn, query, &sqlitex.ExecOptions{ - Args: []any{o.Id, o.UnixTimeStartMs, o.FlowId, o.InstanceId, o.PlanId, o.RepoId, o.SnapshotId, bytes}, - }); err != nil { - if sqlite.ErrCode(err) == sqlite.ResultConstraintUnique { - return fmt.Errorf("operation already exists %v: %w", o.Id, oplog.ErrExist) - } - return fmt.Errorf("add operation: %v", err) - } } - return nil + + return m.addInternal(conn, op...) }) } func (m *SqliteStore) Update(op ...*v1.Operation) error { + m.querymu.Lock() + defer m.querymu.Unlock() conn, err := m.dbpool.Take(context.Background()) if err != nil { return fmt.Errorf("update operation: %v", err) @@ -323,8 +437,14 @@ func (m *SqliteStore) updateInternal(conn *sqlite.Conn, op ...*v1.Operation) err if err != nil { return fmt.Errorf("marshal operation: %v", err) } - if err := sqlitex.Execute(conn, "UPDATE operations SET operation = ?, start_time_ms = ?, flow_id = ?, instance_id = ?, plan_id = ?, repo_id = ?, snapshot_id = ? WHERE id = ?", &sqlitex.ExecOptions{ - Args: []any{bytes, o.UnixTimeStartMs, o.FlowId, o.InstanceId, o.PlanId, o.RepoId, o.SnapshotId, o.Id}, + + ogid, err := m.findOrCreateGroup(conn, o) + if err != nil { + return fmt.Errorf("find ogid: %v", err) + } + + if err := sqlitex.Execute(conn, "UPDATE operations SET operation = ?, ogid = ?, start_time_ms = ?, flow_id = ?, snapshot_id = ?, modno = ?, original_id = ?, original_flow_id = ?, status = ? WHERE id = ?", &sqlitex.ExecOptions{ + Args: []any{bytes, ogid, o.UnixTimeStartMs, o.FlowId, o.SnapshotId, o.Modno, o.OriginalId, o.OriginalFlowId, int64(o.Status), o.Id}, }); err != nil { return fmt.Errorf("update operation: %v", err) } @@ -336,6 +456,8 @@ func (m *SqliteStore) updateInternal(conn *sqlite.Conn, op ...*v1.Operation) err } func (m *SqliteStore) Get(opID int64) (*v1.Operation, error) { + m.querymu.RLock() + defer m.querymu.RUnlock() conn, err := m.dbpool.Take(context.Background()) if err != nil { return nil, fmt.Errorf("get operation: %v", err) @@ -369,6 +491,8 @@ func (m *SqliteStore) Get(opID int64) (*v1.Operation, error) { } func (m *SqliteStore) Delete(opID ...int64) ([]*v1.Operation, error) { + m.querymu.Lock() + defer m.querymu.Unlock() conn, err := m.dbpool.Take(context.Background()) if err != nil { return nil, fmt.Errorf("delete operation: %v", err) @@ -378,7 +502,7 @@ func (m *SqliteStore) Delete(opID ...int64) ([]*v1.Operation, error) { ops := make([]*v1.Operation, 0, len(opID)) return ops, withSqliteTransaction(conn, func() error { // fetch all the operations we're about to delete - predicate := []string{"id IN ("} + predicate := []string{"operations.id IN ("} args := []any{} for i, id := range opID { if i > 0 { @@ -390,7 +514,7 @@ func (m *SqliteStore) Delete(opID ...int64) ([]*v1.Operation, error) { predicate = append(predicate, ")") predicateStr := strings.Join(predicate, "") - if err := sqlitex.ExecuteTransient(conn, "SELECT operation FROM operations WHERE "+predicateStr, &sqlitex.ExecOptions{ + if err := sqlitex.ExecuteTransient(conn, "SELECT operations.operation FROM operations JOIN operation_groups ON operations.ogid = operation_groups.ogid WHERE "+predicateStr, &sqlitex.ExecOptions{ Args: args, ResultFunc: func(stmt *sqlite.Stmt) error { opBytes := make([]byte, stmt.ColumnLen(0)) @@ -412,20 +536,43 @@ func (m *SqliteStore) Delete(opID ...int64) ([]*v1.Operation, error) { return fmt.Errorf("couldn't find all operations to delete: %w", oplog.ErrNotExist) } - // delete the operations + // Delete the operations if err := sqlitex.ExecuteTransient(conn, "DELETE FROM operations WHERE "+predicateStr, &sqlitex.ExecOptions{ Args: args, }); err != nil { return fmt.Errorf("delete operations: %v", err) } + return nil }) } -func withSqliteTransaction(conn *sqlite.Conn, f func() error) error { - var err error - endFunc := sqlitex.Transaction(conn) - err = f() - endFunc(&err) - return err +func (m *SqliteStore) ResetForTest(t *testing.T) error { + m.querymu.Lock() + defer m.querymu.Unlock() + conn, err := m.dbpool.Take(context.Background()) + if err != nil { + return fmt.Errorf("reset for test: %v", err) + } + defer m.dbpool.Put(conn) + + if err := sqlitex.Execute(conn, "DELETE FROM operations", &sqlitex.ExecOptions{}); err != nil { + return fmt.Errorf("reset for test: %v", err) + } + m.lastIDVal.Store(0) + return nil +} + +type opGroupInfo struct { + repo string + plan string + inst string +} + +func groupInfoForOp(op *v1.Operation) opGroupInfo { + return opGroupInfo{ + repo: op.RepoId, + plan: op.PlanId, + inst: op.InstanceId, + } } diff --git a/internal/oplog/sqlitestore/sqlutil.go b/internal/oplog/sqlitestore/sqlutil.go new file mode 100644 index 0000000..bf3e12b --- /dev/null +++ b/internal/oplog/sqlitestore/sqlutil.go @@ -0,0 +1,14 @@ +package sqlitestore + +import ( + "zombiezen.com/go/sqlite" + "zombiezen.com/go/sqlite/sqlitex" +) + +func withSqliteTransaction(conn *sqlite.Conn, f func() error) error { + var err error + endFunc := sqlitex.Transaction(conn) + err = f() + endFunc(&err) + return err +} diff --git a/internal/oplog/storetests/storecontract_test.go b/internal/oplog/storetests/storecontract_test.go index 890c991..a34df46 100644 --- a/internal/oplog/storetests/storecontract_test.go +++ b/internal/oplog/storetests/storecontract_test.go @@ -10,7 +10,9 @@ import ( "github.com/garethgeorge/backrest/internal/oplog/bboltstore" "github.com/garethgeorge/backrest/internal/oplog/memstore" "github.com/garethgeorge/backrest/internal/oplog/sqlitestore" + "github.com/google/go-cmp/cmp" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" ) const ( @@ -25,16 +27,23 @@ func StoresForTest(t testing.TB) map[string]oplog.OpStore { } t.Cleanup(func() { bboltstore.Close() }) - sqlitestore, err := sqlitestore.NewSqliteStore(t.TempDir() + "/test.sqlite") + sqlitestoreinst, err := sqlitestore.NewSqliteStore(t.TempDir() + "/test.sqlite") if err != nil { t.Fatalf("error creating sqlite store: %s", err) } - t.Cleanup(func() { sqlitestore.Close() }) + t.Cleanup(func() { sqlitestoreinst.Close() }) + + sqlitememstore, err := sqlitestore.NewMemorySqliteStore() + if err != nil { + t.Fatalf("error creating sqlite store: %s", err) + } + t.Cleanup(func() { sqlitememstore.Close() }) return map[string]oplog.OpStore{ - "bbolt": bboltstore, - "memory": memstore.NewMemStore(), - "sqlite": sqlitestore, + "bbolt": bboltstore, + "memory": memstore.NewMemStore(), + "sqlite": sqlitestoreinst, + "sqlitemem": sqlitememstore, } } @@ -50,6 +59,61 @@ func TestCreate(t *testing.T) { } } +func TestListAll(t *testing.T) { + // t.Parallel() + for name, store := range StoresForTest(t) { + t.Run(name, func(t *testing.T) { + store, err := oplog.NewOpLog(store) + if err != nil { + t.Fatalf("error creating oplog: %v", err) + } + + opsToAdd := []*v1.Operation{ + { + UnixTimeStartMs: 1234, + PlanId: "plan1", + RepoId: "repo1", + RepoGuid: "repo1", + InstanceId: "instance1", + Op: &v1.Operation_OperationBackup{}, + }, + { + UnixTimeStartMs: 4567, + PlanId: "plan2", + RepoId: "repo2", + RepoGuid: "repo2", + InstanceId: "instance2", + Op: &v1.Operation_OperationBackup{}, + }, + } + + for _, op := range opsToAdd { + if err := store.Add(op); err != nil { + t.Fatalf("error adding operation: %s", err) + } + } + + var ops []*v1.Operation + if err := store.Query(oplog.Query{}, func(op *v1.Operation) error { + ops = append(ops, op) + return nil + }); err != nil { + t.Fatalf("error querying operations: %s", err) + } + + if len(ops) != len(opsToAdd) { + t.Errorf("expected %d operations, got %d", len(opsToAdd), len(ops)) + } + + for i := 0; i < len(ops); i++ { + if diff := cmp.Diff(ops[i], opsToAdd[i], protocmp.Transform()); diff != "" { + t.Fatalf("unexpected diff ops[%d] != opsToAdd[%d]: %v", i, i, diff) + } + } + }) + } +} + func TestAddOperation(t *testing.T) { var tests = []struct { name string @@ -68,6 +132,7 @@ func TestAddOperation(t *testing.T) { op: &v1.Operation{ UnixTimeStartMs: 1234, RepoId: "testrepo", + RepoGuid: "testrepo", PlanId: "testplan", InstanceId: "testinstance", Op: &v1.Operation_OperationBackup{}, @@ -79,6 +144,7 @@ func TestAddOperation(t *testing.T) { op: &v1.Operation{ UnixTimeStartMs: 1234, RepoId: "testrepo", + RepoGuid: "testrepo", PlanId: "testplan", InstanceId: "testinstance", Op: &v1.Operation_OperationIndexSnapshot{ @@ -96,6 +162,7 @@ func TestAddOperation(t *testing.T) { op: &v1.Operation{ Id: 1, RepoId: "testrepo", + RepoGuid: "testrepo", PlanId: "testplan", InstanceId: "testinstance", UnixTimeStartMs: 1234, @@ -108,6 +175,7 @@ func TestAddOperation(t *testing.T) { op: &v1.Operation{ UnixTimeStartMs: 1234, RepoId: "testrepo", + RepoGuid: "testrepo", Op: &v1.Operation_OperationBackup{}, }, wantErr: true, @@ -162,30 +230,44 @@ func TestListOperation(t *testing.T) { // these should get assigned IDs 1-3 respectively by the oplog ops := []*v1.Operation{ { - UnixTimeStartMs: 1234, + InstanceId: "foo", PlanId: "plan1", RepoId: "repo1", - InstanceId: "instance1", + RepoGuid: "repo1", + UnixTimeStartMs: 1234, DisplayMessage: "op1", Op: &v1.Operation_OperationBackup{}, }, { - UnixTimeStartMs: 1234, + InstanceId: "bar", PlanId: "plan1", RepoId: "repo2", - InstanceId: "instance2", + RepoGuid: "repo2", + UnixTimeStartMs: 1234, DisplayMessage: "op2", Op: &v1.Operation_OperationBackup{}, }, { - UnixTimeStartMs: 1234, + InstanceId: "baz", PlanId: "plan2", RepoId: "repo2", - InstanceId: "instance3", + RepoGuid: "repo2", + UnixTimeStartMs: 1234, DisplayMessage: "op3", FlowId: 943, Op: &v1.Operation_OperationBackup{}, }, + { + InstanceId: "foo", + PlanId: "foo-plan", + RepoId: "foo-repo", + RepoGuid: "foo-repo-guid", + UnixTimeStartMs: 1234, + DisplayMessage: "foo-op", + Op: &v1.Operation_OperationBackup{}, + OriginalId: 4567, + OriginalFlowId: 789, + }, } tests := []struct { @@ -195,46 +277,72 @@ func TestListOperation(t *testing.T) { }{ { name: "list plan1", - query: oplog.Query{PlanID: "plan1"}, + query: oplog.Query{}.SetPlanID("plan1"), expected: []string{"op1", "op2"}, }, { name: "list plan1 with limit", - query: oplog.Query{PlanID: "plan1", Limit: 1}, + query: oplog.Query{}.SetPlanID("plan1").SetLimit(1), expected: []string{"op1"}, }, { name: "list plan1 with offset", - query: oplog.Query{PlanID: "plan1", Offset: 1}, + query: oplog.Query{}.SetPlanID("plan1").SetOffset(1), expected: []string{"op2"}, }, { name: "list plan1 reversed", - query: oplog.Query{PlanID: "plan1", Reversed: true}, + query: oplog.Query{}.SetPlanID("plan1").SetReversed(true), expected: []string{"op2", "op1"}, }, { name: "list plan2", - query: oplog.Query{PlanID: "plan2"}, + query: oplog.Query{}.SetPlanID("plan2"), expected: []string{"op3"}, }, { name: "list repo1", - query: oplog.Query{RepoID: "repo1"}, + query: oplog.Query{}.SetRepoGUID("repo1"), expected: []string{"op1"}, }, { name: "list repo2", - query: oplog.Query{RepoID: "repo2"}, + query: oplog.Query{}.SetRepoGUID("repo2"), expected: []string{"op2", "op3"}, }, { name: "list flow 943", - query: oplog.Query{FlowID: 943}, + query: oplog.Query{}.SetFlowID(943), expected: []string{ "op3", }, }, + { + name: "list original ID", + query: oplog.Query{}.SetOriginalID(4567), + expected: []string{ + "foo-op", + }, + }, + { + name: "list original flow ID", + query: oplog.Query{}.SetOriginalFlowID(789), + expected: []string{ + "foo-op", + }, + }, + { + name: "a very compound query", + query: oplog.Query{}. + SetPlanID("foo-plan"). + SetRepoGUID("foo-repo-guid"). + SetInstanceID("foo"). + SetOriginalID(4567). + SetOriginalFlowID(789), + expected: []string{ + "foo-op", + }, + }, } for name, store := range StoresForTest(t) { @@ -291,6 +399,7 @@ func TestBigIO(t *testing.T) { UnixTimeStartMs: 1234, PlanId: "plan1", RepoId: "repo1", + RepoGuid: "repo1", InstanceId: "instance1", Op: &v1.Operation_OperationBackup{}, }); err != nil { @@ -299,7 +408,7 @@ func TestBigIO(t *testing.T) { } countByPlanHelper(t, log, "plan1", count) - countByRepoHelper(t, log, "repo1", count) + countByRepoGUIDHelper(t, log, "repo1", count) }) } } @@ -311,6 +420,7 @@ func TestIndexSnapshot(t *testing.T) { UnixTimeStartMs: 1234, PlanId: "plan1", RepoId: "repo1", + RepoGuid: "repo1", InstanceId: "instance1", SnapshotId: snapshotId, Op: &v1.Operation_OperationIndexSnapshot{}, @@ -331,7 +441,7 @@ func TestIndexSnapshot(t *testing.T) { } var ops []*v1.Operation - if err := log.Query(oplog.Query{SnapshotID: snapshotId}, func(op *v1.Operation) error { + if err := log.Query(oplog.Query{}.SetSnapshotID(snapshotId), func(op *v1.Operation) error { ops = append(ops, op) return nil }); err != nil { @@ -355,6 +465,7 @@ func TestUpdateOperation(t *testing.T) { UnixTimeStartMs: 1234, PlanId: "oldplan", RepoId: "oldrepo", + RepoGuid: "oldrepo", InstanceId: "instance1", SnapshotId: snapshotId, } @@ -376,13 +487,14 @@ func TestUpdateOperation(t *testing.T) { // Validate initial values are indexed countByPlanHelper(t, log, "oldplan", 1) - countByRepoHelper(t, log, "oldrepo", 1) + countByRepoGUIDHelper(t, log, "oldrepo", 1) countBySnapshotIdHelper(t, log, snapshotId, 1) // Update indexed values op.SnapshotId = snapshotId2 op.PlanId = "myplan" op.RepoId = "myrepo" + op.RepoGuid = "myrepo" if err := log.Update(op); err != nil { t.Fatalf("error updating operation: %s", err) } @@ -393,12 +505,12 @@ func TestUpdateOperation(t *testing.T) { } countByPlanHelper(t, log, "myplan", 1) - countByRepoHelper(t, log, "myrepo", 1) + countByRepoGUIDHelper(t, log, "myrepo", 1) countBySnapshotIdHelper(t, log, snapshotId2, 1) // Validate prior values are gone countByPlanHelper(t, log, "oldplan", 0) - countByRepoHelper(t, log, "oldrepo", 0) + countByRepoGUIDHelper(t, log, "oldrepo", 0) countBySnapshotIdHelper(t, log, snapshotId, 0) }) } @@ -410,6 +522,7 @@ func TestTransform(t *testing.T) { InstanceId: "foo", PlanId: "plan1", RepoId: "repo1", + RepoGuid: "repo1", UnixTimeStartMs: 1234, UnixTimeEndMs: 5678, }, @@ -417,6 +530,7 @@ func TestTransform(t *testing.T) { InstanceId: "bar", PlanId: "plan1", RepoId: "repo1", + RepoGuid: "repo1", UnixTimeStartMs: 1234, UnixTimeEndMs: 5678, }, @@ -438,7 +552,7 @@ func TestTransform(t *testing.T) { want: ops, }, { - name: "no change by copy", + name: "modno incremented by copy", f: func(op *v1.Operation) (*v1.Operation, error) { return proto.Clone(op).(*v1.Operation), nil }, @@ -456,6 +570,7 @@ func TestTransform(t *testing.T) { InstanceId: "foo", PlanId: "oldplan", RepoId: "repo1", + RepoGuid: "repo1", UnixTimeStartMs: 1234, UnixTimeEndMs: 5678, }, @@ -465,6 +580,7 @@ func TestTransform(t *testing.T) { InstanceId: "foo", PlanId: "newplan", RepoId: "repo1", + RepoGuid: "repo1", UnixTimeStartMs: 1234, UnixTimeEndMs: 5678, }, @@ -482,12 +598,13 @@ func TestTransform(t *testing.T) { InstanceId: "foo", PlanId: "newplan", RepoId: "repo1", + RepoGuid: "repo1", UnixTimeStartMs: 1234, UnixTimeEndMs: 5678, }, ops[1], }, - query: oplog.Query{InstanceID: "foo"}, + query: oplog.Query{}.SetInstanceID("foo"), }, } for _, tc := range tcs { @@ -521,13 +638,12 @@ func TestTransform(t *testing.T) { t.Fatalf("error listing operations: %s", err) } - if slices.CompareFunc(got, tc.want, func(a, b *v1.Operation) int { - if proto.Equal(a, b) { - return 0 - } - return 1 - }) != 0 { - t.Errorf("want operations: %v, got unexpected operations: %v", tc.want, got) + for _, op := range got { + op.Modno = 0 + } + + if diff := cmp.Diff(got, tc.want, protocmp.Transform()); diff != "" { + t.Errorf("unexpected diff: %v", diff) } }) } @@ -535,6 +651,60 @@ func TestTransform(t *testing.T) { } } +func TestQueryMetadata(t *testing.T) { + t.Parallel() + for name, store := range StoresForTest(t) { + t.Run(name, func(t *testing.T) { + if name == "bbolt" { + t.Skip("bbolt does not support metadata") + } + + log, err := oplog.NewOpLog(store) + if err != nil { + t.Fatalf("error creating oplog: %v", err) + } + if err := log.Add(&v1.Operation{ + UnixTimeStartMs: 1234, + PlanId: "plan1", + RepoId: "repo1", + RepoGuid: "repo1-guid", + InstanceId: "instance1", + Op: &v1.Operation_OperationBackup{}, + FlowId: 5, + OriginalId: 3, + OriginalFlowId: 4, + }); err != nil { + t.Fatalf("error adding operation: %s", err) + } + + var metadata []oplog.OpMetadata + if err := log.QueryMetadata(oplog.Query{}.SetPlanID("plan1"), func(op oplog.OpMetadata) error { + metadata = append(metadata, op) + return nil + }); err != nil { + t.Fatalf("error listing metadata: %s", err) + } + if len(metadata) != 1 { + t.Fatalf("want 1 metadata, got %d", len(metadata)) + } + + if metadata[0].Modno == 0 { + t.Errorf("modno should not be 0") + } + metadata[0].Modno = 0 // ignore for diff since it's random + + if diff := cmp.Diff(metadata[0], oplog.OpMetadata{ + ID: metadata[0].ID, + FlowID: 5, + OriginalID: 3, + OriginalFlowID: 4, + }); diff != "" { + t.Errorf("unexpected diff: %v", diff) + } + }) + } +} + func collectMessages(ops []*v1.Operation) []string { var messages []string for _, op := range ops { @@ -543,10 +713,10 @@ func collectMessages(ops []*v1.Operation) []string { return messages } -func countByRepoHelper(t *testing.T, log *oplog.OpLog, repo string, expected int) { +func countByRepoGUIDHelper(t *testing.T, log *oplog.OpLog, repoGUID string, expected int) { t.Helper() count := 0 - if err := log.Query(oplog.Query{RepoID: repo}, func(op *v1.Operation) error { + if err := log.Query(oplog.Query{}.SetRepoGUID(repoGUID), func(op *v1.Operation) error { count += 1 return nil }); err != nil { @@ -560,7 +730,7 @@ func countByRepoHelper(t *testing.T, log *oplog.OpLog, repo string, expected int func countByPlanHelper(t *testing.T, log *oplog.OpLog, plan string, expected int) { t.Helper() count := 0 - if err := log.Query(oplog.Query{PlanID: plan}, func(op *v1.Operation) error { + if err := log.Query(oplog.Query{}.SetPlanID(plan), func(op *v1.Operation) error { count += 1 return nil }); err != nil { @@ -574,7 +744,7 @@ func countByPlanHelper(t *testing.T, log *oplog.OpLog, plan string, expected int func countBySnapshotIdHelper(t *testing.T, log *oplog.OpLog, snapshotId string, expected int) { t.Helper() count := 0 - if err := log.Query(oplog.Query{SnapshotID: snapshotId}, func(op *v1.Operation) error { + if err := log.Query(oplog.Query{}.SetSnapshotID(snapshotId), func(op *v1.Operation) error { count += 1 return nil }); err != nil { @@ -597,6 +767,7 @@ func BenchmarkAdd(b *testing.B) { UnixTimeStartMs: 1234, PlanId: "plan1", RepoId: "repo1", + RepoGuid: "repo1", InstanceId: "instance1", Op: &v1.Operation_OperationBackup{}, }) @@ -618,6 +789,7 @@ func BenchmarkList(b *testing.B) { UnixTimeStartMs: 1234, PlanId: "plan1", RepoId: "repo1", + RepoGuid: "repo1", InstanceId: "instance1", Op: &v1.Operation_OperationBackup{}, }) @@ -626,7 +798,7 @@ func BenchmarkList(b *testing.B) { b.Run(name, func(b *testing.B) { for i := 0; i < b.N; i++ { c := 0 - if err := log.Query(oplog.Query{PlanID: "plan1"}, func(op *v1.Operation) error { + if err := log.Query(oplog.Query{}.SetPlanID("plan1"), func(op *v1.Operation) error { c += 1 return nil }); err != nil { @@ -655,6 +827,7 @@ func BenchmarkGetLastItem(b *testing.B) { UnixTimeStartMs: 1234, PlanId: "plan1", RepoId: "repo1", + RepoGuid: "repo1", InstanceId: "instance1", Op: &v1.Operation_OperationBackup{}, }) @@ -663,7 +836,7 @@ func BenchmarkGetLastItem(b *testing.B) { b.Run(name, func(b *testing.B) { for i := 0; i < b.N; i++ { c := 0 - if err := log.Query(oplog.Query{PlanID: "plan1", Reversed: true}, func(op *v1.Operation) error { + if err := log.Query(oplog.Query{}.SetPlanID("plan1").SetReversed(true), func(op *v1.Operation) error { c += 1 return oplog.ErrStopIteration }); err != nil { diff --git a/internal/orchestrator/orchestrator.go b/internal/orchestrator/orchestrator.go index 2b691ea..cdd987c 100644 --- a/internal/orchestrator/orchestrator.go +++ b/internal/orchestrator/orchestrator.go @@ -160,17 +160,22 @@ func (o *Orchestrator) ApplyConfig(cfg *v1.Config) error { // rescheduleTasksIfNeeded checks if any tasks need to be rescheduled based on config changes. func (o *Orchestrator) ScheduleDefaultTasks(config *v1.Config) error { + if o.OpLog == nil { + return nil + } + zap.L().Info("scheduling default tasks, waiting for task queue reset.") removedTasks := o.taskQueue.Reset() - for _, t := range removedTasks { - if t.Op == nil { - continue - } - if err := o.OpLog.Delete(t.Op.Id); err != nil { - zap.S().Warnf("failed to cancel pending task %d: %v", t.Op.Id, err) + ids := []int64{} + for _, t := range removedTasks { + if t.Op.GetId() != 0 { + ids = append(ids, t.Op.GetId()) } } + if err := o.OpLog.Delete(ids...); err != nil { + zap.S().Warnf("failed to delete cancelled tasks from oplog: %v", err) + } zap.L().Info("reset task queue, scheduling new task set", zap.String("timezone", time.Now().Location().String())) @@ -179,9 +184,19 @@ func (o *Orchestrator) ScheduleDefaultTasks(config *v1.Config) error { return fmt.Errorf("schedule collect garbage task: %w", err) } + var repoByID = map[string]*v1.Repo{} + for _, repo := range config.Repos { + repoByID[repo.GetId()] = repo + } + for _, plan := range config.Plans { // Schedule a backup task for the plan - t := tasks.NewScheduledBackupTask(plan) + repo := repoByID[plan.Repo] + if repo == nil { + return fmt.Errorf("repo %q not found for plan %q", plan.Repo, plan.Id) + } + + t := tasks.NewScheduledBackupTask(repo, plan) if err := o.ScheduleTask(t, tasks.TaskPriorityDefault); err != nil { return fmt.Errorf("schedule backup task for plan %q: %w", plan.Id, err) } @@ -189,13 +204,13 @@ func (o *Orchestrator) ScheduleDefaultTasks(config *v1.Config) error { for _, repo := range config.Repos { // Schedule a prune task for the repo - t := tasks.NewPruneTask(repo.GetId(), tasks.PlanForSystemTasks, false) + t := tasks.NewPruneTask(repo, tasks.PlanForSystemTasks, false) if err := o.ScheduleTask(t, tasks.TaskPriorityPrune); err != nil { return fmt.Errorf("schedule prune task for repo %q: %w", repo.GetId(), err) } // Schedule a check task for the repo - t = tasks.NewCheckTask(repo.GetId(), tasks.PlanForSystemTasks, false) + t = tasks.NewCheckTask(repo, tasks.PlanForSystemTasks, false) if err := o.ScheduleTask(t, tasks.TaskPriorityCheck); err != nil { return fmt.Errorf("schedule check task for repo %q: %w", repo.GetId(), err) } @@ -329,7 +344,7 @@ func (o *Orchestrator) Run(ctx context.Context) { t.Op.DisplayMessage = fmt.Sprintf("running after %d retries", t.retryCount) // Delete any previous hook executions for this operation incase this is a retry. prevHookExecutionIDs := []int64{} - if err := o.OpLog.Query(oplog.Query{FlowID: t.Op.FlowId}, func(op *v1.Operation) error { + if err := o.OpLog.Query(oplog.Query{FlowID: &t.Op.FlowId}, func(op *v1.Operation) error { if hookOp, ok := op.Op.(*v1.Operation_OperationRunHook); ok && hookOp.OperationRunHook.GetParentOp() == t.Op.Id { prevHookExecutionIDs = append(prevHookExecutionIDs, op.Id) } @@ -508,7 +523,8 @@ func (o *Orchestrator) CreateUnscheduledTask(t tasks.Task, priority int, curTime if nextRun.Op != nil { nextRun.Op.InstanceId = o.config.Instance nextRun.Op.PlanId = t.PlanID() - nextRun.Op.RepoId = t.RepoID() + nextRun.Op.RepoId = t.Repo().GetId() + nextRun.Op.RepoGuid = t.Repo().GetGuid() nextRun.Op.Status = v1.OperationStatus_STATUS_PENDING nextRun.Op.UnixTimeStartMs = nextRun.RunAt.UnixMilli() diff --git a/internal/orchestrator/orchestrator_test.go b/internal/orchestrator/orchestrator_test.go index 4e6b0aa..50505e3 100644 --- a/internal/orchestrator/orchestrator_test.go +++ b/internal/orchestrator/orchestrator_test.go @@ -7,7 +7,9 @@ import ( "testing" "time" + v1 "github.com/garethgeorge/backrest/gen/go/v1" "github.com/garethgeorge/backrest/internal/config" + "github.com/garethgeorge/backrest/internal/cryptoutil" "github.com/garethgeorge/backrest/internal/orchestrator/tasks" ) @@ -23,7 +25,7 @@ func newTestTask(onRun func() error, onNext func(curTime time.Time) *time.Time) return &testTask{ BaseTask: tasks.BaseTask{ TaskName: "test task", - TaskRepoID: "repo", + TaskRepo: &v1.Repo{Id: "repo", Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits)}, TaskPlanID: "plan", }, onRun: onRun, diff --git a/internal/orchestrator/repo/repo.go b/internal/orchestrator/repo/repo.go index dbca9d5..d1ea83a 100644 --- a/internal/orchestrator/repo/repo.go +++ b/internal/orchestrator/repo/repo.go @@ -14,6 +14,7 @@ import ( "time" v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/internal/cryptoutil" "github.com/garethgeorge/backrest/internal/orchestrator/logging" "github.com/garethgeorge/backrest/internal/protoutil" "github.com/garethgeorge/backrest/pkg/restic" @@ -415,6 +416,13 @@ func (r *RepoOrchestrator) Config() *v1.Repo { return r.repoConfig } +func (r *RepoOrchestrator) RepoGUID() (string, error) { + r.mu.Lock() + defer r.mu.Unlock() + cfg, err := r.repo.Config(context.Background()) + return cryptoutil.TruncateID(cfg.Id, cryptoutil.DefaultIDBits), err +} + func sortSnapshotsByTime(snapshots []*restic.Snapshot) { sort.SliceStable(snapshots, func(i, j int) bool { return snapshots[i].UnixTimeMs() < snapshots[j].UnixTimeMs() diff --git a/internal/orchestrator/repo/repo_test.go b/internal/orchestrator/repo/repo_test.go index 8ccb17d..ac961ee 100644 --- a/internal/orchestrator/repo/repo_test.go +++ b/internal/orchestrator/repo/repo_test.go @@ -3,6 +3,8 @@ package repo import ( "bytes" "context" + "errors" + "fmt" "io/ioutil" "os" "path" @@ -14,6 +16,7 @@ import ( v1 "github.com/garethgeorge/backrest/gen/go/v1" "github.com/garethgeorge/backrest/test/helpers" test "github.com/garethgeorge/backrest/test/helpers" + "golang.org/x/sync/errgroup" ) var configForTest = &v1.Config{ @@ -188,60 +191,74 @@ func TestSnapshotParenting(t *testing.T) { orchestrator := initRepoHelper(t, configForTest, r) - for i := 0; i < 4; i++ { - for _, plan := range plans { - summary, err := orchestrator.Backup(context.Background(), plan, nil) - if err != nil { - t.Fatalf("failed to backup plan %s: %v", plan.Id, err) - } + var eg errgroup.Group + for _, plan := range plans { + eg.Go(func() error { + for i := 0; i < 2; i++ { + summary, err := orchestrator.Backup(context.Background(), plan, nil) + if err != nil { + return fmt.Errorf("failed to backup plan %s: %v", plan.Id, err) + } - if summary.SnapshotId == "" { - t.Errorf("expected snapshot id") - } + if summary.SnapshotId == "" { + return errors.New("expected snapshot id") + } - if summary.TotalFilesProcessed != 100 { - t.Logf("summary is: %+v", summary) - t.Errorf("expected 100 done files, got %d", summary.TotalFilesProcessed) + if summary.TotalFilesProcessed != 100 { + t.Logf("summary is: %+v", summary) + return fmt.Errorf("expected 100 done files, got %d", summary.TotalFilesProcessed) + } } - } + return nil + }) + } + + if err := eg.Wait(); err != nil { + t.Fatal(err) } for _, plan := range plans { - snapshots, err := orchestrator.SnapshotsForPlan(context.Background(), plan) - if err != nil { - t.Errorf("failed to get snapshots for plan %s: %v", plan.Id, err) - continue - } + t.Run("verify_"+plan.Id, func(t *testing.T) { + t.Parallel() + snapshots, err := orchestrator.SnapshotsForPlan(context.Background(), plan) + if err != nil { + t.Errorf("failed to get snapshots for plan %s: %v", plan.Id, err) + return + } + if len(snapshots) != 2 { + t.Errorf("expected 4 snapshots, got %d", len(snapshots)) + } + + for i := 1; i < len(snapshots); i++ { + prev := snapshots[i-1] + curr := snapshots[i] + + if prev.UnixTimeMs() >= curr.UnixTimeMs() { + t.Errorf("snapshots are out of order") + } + + if prev.Id != curr.Parent { + t.Errorf("expected snapshot %s to have parent %s, got %s", curr.Id, prev.Id, curr.Parent) + } + + if !slices.Contains(curr.Tags, TagForPlan(plan.Id)) { + t.Errorf("expected snapshot %s to have tag %s", curr.Id, TagForPlan(plan.Id)) + } + } + }) + } + + t.Run("verify_snapshot_count", func(t *testing.T) { + t.Parallel() + snapshots, err := orchestrator.Snapshots(context.Background()) + if err != nil { + t.Fatal(err) + } if len(snapshots) != 4 { t.Errorf("expected 4 snapshots, got %d", len(snapshots)) } - - for i := 1; i < len(snapshots); i++ { - prev := snapshots[i-1] - curr := snapshots[i] - - if prev.UnixTimeMs() >= curr.UnixTimeMs() { - t.Errorf("snapshots are out of order") - } - - if prev.Id != curr.Parent { - t.Errorf("expected snapshot %s to have parent %s, got %s", curr.Id, prev.Id, curr.Parent) - } - - if !slices.Contains(curr.Tags, TagForPlan(plan.Id)) { - t.Errorf("expected snapshot %s to have tag %s", curr.Id, TagForPlan(plan.Id)) - } - } - } - - snapshots, err := orchestrator.Snapshots(context.Background()) - if err != nil { - t.Fatal(err) - } - if len(snapshots) != 8 { - t.Errorf("expected 8 snapshots, got %d", len(snapshots)) - } + }) } func TestEnvVarPropagation(t *testing.T) { diff --git a/internal/orchestrator/taskrunnerimpl.go b/internal/orchestrator/taskrunnerimpl.go index 6937f46..8a6ddb8 100644 --- a/internal/orchestrator/taskrunnerimpl.go +++ b/internal/orchestrator/taskrunnerimpl.go @@ -22,7 +22,6 @@ type taskRunnerImpl struct { orchestrator *Orchestrator t tasks.Task op *v1.Operation - repo *v1.Repo // cache, populated on first call to Repo() plan *v1.Plan // cache, populated on first call to Plan() config *v1.Config // cache, populated on first call to Config() } @@ -31,21 +30,13 @@ var _ tasks.TaskRunner = &taskRunnerImpl{} func newTaskRunnerImpl(orchestrator *Orchestrator, task tasks.Task, op *v1.Operation) *taskRunnerImpl { return &taskRunnerImpl{ + config: orchestrator.config, orchestrator: orchestrator, t: task, op: op, } } -func (t *taskRunnerImpl) findRepo() (*v1.Repo, error) { - if t.repo != nil { - return t.repo, nil - } - var err error - t.repo, err = t.orchestrator.GetRepo(t.t.RepoID()) - return t.repo, err -} - func (t *taskRunnerImpl) findPlan() (*v1.Plan, error) { if t.plan != nil { return t.plan, nil @@ -55,22 +46,44 @@ func (t *taskRunnerImpl) findPlan() (*v1.Plan, error) { return t.plan, err } -func (t *taskRunnerImpl) CreateOperation(op *v1.Operation) error { - op.InstanceId = t.orchestrator.config.Instance - return t.orchestrator.OpLog.Add(op) +func (t *taskRunnerImpl) InstanceID() string { + return t.config.Instance } -func (t *taskRunnerImpl) UpdateOperation(op *v1.Operation) error { - op.InstanceId = t.orchestrator.config.Instance - return t.orchestrator.OpLog.Update(op) +func (t *taskRunnerImpl) GetOperation(id int64) (*v1.Operation, error) { + return t.orchestrator.OpLog.Get(id) +} + +func (t *taskRunnerImpl) CreateOperation(op ...*v1.Operation) error { + for _, o := range op { + if o.InstanceId != "" { + continue + } + o.InstanceId = t.InstanceID() + } + return t.orchestrator.OpLog.Add(op...) +} + +func (t *taskRunnerImpl) UpdateOperation(op ...*v1.Operation) error { + for _, o := range op { + if o.InstanceId != "" { + continue + } + o.InstanceId = t.InstanceID() + } + return t.orchestrator.OpLog.Update(op...) +} + +func (t *taskRunnerImpl) DeleteOperation(id ...int64) error { + return t.orchestrator.OpLog.Delete(id...) } func (t *taskRunnerImpl) Orchestrator() *Orchestrator { return t.orchestrator } -func (t *taskRunnerImpl) OpLog() *oplog.OpLog { - return t.orchestrator.OpLog +func (t *taskRunnerImpl) QueryOperations(q oplog.Query, fn func(*v1.Operation) error) error { + return t.orchestrator.OpLog.Query(q, fn) } func (t *taskRunnerImpl) ExecuteHooks(ctx context.Context, events []v1.Hook_Condition, vars tasks.HookVars) error { @@ -83,14 +96,8 @@ func (t *taskRunnerImpl) ExecuteHooks(ctx context.Context, events []v1.Hook_Cond repoID := t.t.RepoID() planID := t.t.PlanID() - var repo *v1.Repo var plan *v1.Plan - if repoID != "" { - var err error - repo, err = t.findRepo() - if err != nil { - return err - } + if repo := t.t.Repo(); repo != nil { vars.Repo = repo } if planID != "" { @@ -131,7 +138,7 @@ func (t *taskRunnerImpl) ExecuteHooks(ctx context.Context, events []v1.Hook_Cond func (t *taskRunnerImpl) GetRepo(repoID string) (*v1.Repo, error) { if repoID == t.t.RepoID() { - return t.findRepo() // optimization for the common case of the current repo + return t.t.Repo(), nil } return t.orchestrator.GetRepo(repoID) } diff --git a/internal/orchestrator/tasks/flowidutil.go b/internal/orchestrator/tasks/flowidutil.go index f48e2bf..4f012ce 100644 --- a/internal/orchestrator/tasks/flowidutil.go +++ b/internal/orchestrator/tasks/flowidutil.go @@ -8,9 +8,9 @@ import ( ) // FlowIDForSnapshotID returns the flow ID associated with the backup task that created snapshot ID or 0 if not found. -func FlowIDForSnapshotID(log *oplog.OpLog, snapshotID string) (int64, error) { +func FlowIDForSnapshotID(runner TaskRunner, snapshotID string) (int64, error) { var flowID int64 - if err := log.Query(oplog.Query{SnapshotID: snapshotID}, func(op *v1.Operation) error { + if err := runner.QueryOperations(oplog.Query{SnapshotID: &snapshotID}, func(op *v1.Operation) error { if _, ok := op.Op.(*v1.Operation_OperationBackup); !ok { return nil } diff --git a/internal/orchestrator/tasks/scheduling_test.go b/internal/orchestrator/tasks/scheduling_test.go index 832d797..9f41373 100644 --- a/internal/orchestrator/tasks/scheduling_test.go +++ b/internal/orchestrator/tasks/scheduling_test.go @@ -8,8 +8,9 @@ import ( v1 "github.com/garethgeorge/backrest/gen/go/v1" "github.com/garethgeorge/backrest/internal/config" + "github.com/garethgeorge/backrest/internal/cryptoutil" "github.com/garethgeorge/backrest/internal/oplog" - "github.com/garethgeorge/backrest/internal/oplog/memstore" + "github.com/garethgeorge/backrest/internal/oplog/sqlitestore" ) func TestScheduling(t *testing.T) { @@ -21,9 +22,15 @@ func TestScheduling(t *testing.T) { defer os.Unsetenv("TZ") cfg := &v1.Config{ + Instance: "instance1", Repos: []*v1.Repo{ { - Id: "repo-absolute", + Id: "repo1", + Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits), + }, + { + Id: "repo-absolute", + Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits), CheckPolicy: &v1.CheckPolicy{ Schedule: &v1.Schedule{ Schedule: &v1.Schedule_MaxFrequencyHours{ @@ -40,7 +47,8 @@ func TestScheduling(t *testing.T) { }, }, { - Id: "repo-relative", + Id: "repo-relative", + Guid: cryptoutil.MustRandomID(cryptoutil.DefaultIDBits), CheckPolicy: &v1.CheckPolicy{ Schedule: &v1.Schedule{ Schedule: &v1.Schedule_MaxFrequencyHours{ @@ -88,8 +96,7 @@ func TestScheduling(t *testing.T) { }, }, { - Id: "plan-max-frequency-days", - Repo: "repo1", + Id: "plan-max-frequency-days", Schedule: &v1.Schedule{ Schedule: &v1.Schedule_MaxFrequencyDays{ MaxFrequencyDays: 1, @@ -98,8 +105,7 @@ func TestScheduling(t *testing.T) { }, }, { - Id: "plan-min-days-since-last-run", - Repo: "repo1", + Id: "plan-min-days-since-last-run", Schedule: &v1.Schedule{ Schedule: &v1.Schedule_MaxFrequencyDays{ MaxFrequencyDays: 1, @@ -108,8 +114,7 @@ func TestScheduling(t *testing.T) { }, }, { - Id: "plan-max-frequency-hours", - Repo: "repo1", + Id: "plan-max-frequency-hours", Schedule: &v1.Schedule{ Schedule: &v1.Schedule_MaxFrequencyHours{ MaxFrequencyHours: 1, @@ -117,8 +122,7 @@ func TestScheduling(t *testing.T) { }, }, { - Id: "plan-min-hours-since-last-run", - Repo: "repo1", + Id: "plan-min-hours-since-last-run", Schedule: &v1.Schedule{ Schedule: &v1.Schedule_MaxFrequencyHours{ MaxFrequencyHours: 1, @@ -129,6 +133,13 @@ func TestScheduling(t *testing.T) { }, } + repo1 := config.FindRepo(cfg, "repo1") + repoAbsolute := config.FindRepo(cfg, "repo-absolute") + repoRelative := config.FindRepo(cfg, "repo-relative") + if repoAbsolute == nil || repoRelative == nil || repo1 == nil { + t.Fatalf("test config declaration error") + } + now := time.Unix(100000, 0) // 1000 seconds after the epoch as an arbitrary time for the test farFuture := time.Unix(999999, 0) @@ -140,11 +151,12 @@ func TestScheduling(t *testing.T) { }{ { name: "backup schedule max frequency days", - task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-max-frequency-days")), + task: NewScheduledBackupTask(config.FindRepo(cfg, "repo1"), config.FindPlan(cfg, "plan-max-frequency-days")), ops: []*v1.Operation{ { InstanceId: "instance1", RepoId: "repo1", + RepoGuid: repo1.Guid, PlanId: "plan-max-frequency-days", Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, @@ -157,11 +169,12 @@ func TestScheduling(t *testing.T) { }, { name: "backup schedule min days since last run", - task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-min-days-since-last-run")), + task: NewScheduledBackupTask(config.FindRepo(cfg, "repo1"), config.FindPlan(cfg, "plan-min-days-since-last-run")), ops: []*v1.Operation{ { InstanceId: "instance1", RepoId: "repo1", + RepoGuid: repo1.Guid, PlanId: "plan-min-days-since-last-run", Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, @@ -174,11 +187,12 @@ func TestScheduling(t *testing.T) { }, { name: "backup schedule max frequency hours", - task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-max-frequency-hours")), + task: NewScheduledBackupTask(config.FindRepo(cfg, "repo1"), config.FindPlan(cfg, "plan-max-frequency-hours")), ops: []*v1.Operation{ { InstanceId: "instance1", RepoId: "repo1", + RepoGuid: repo1.Guid, PlanId: "plan-max-frequency-hours", Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, @@ -191,11 +205,12 @@ func TestScheduling(t *testing.T) { }, { name: "backup schedule min hours since last run", - task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-min-hours-since-last-run")), + task: NewScheduledBackupTask(config.FindRepo(cfg, "repo1"), config.FindPlan(cfg, "plan-min-hours-since-last-run")), ops: []*v1.Operation{ { InstanceId: "instance1", RepoId: "repo1", + RepoGuid: repo1.Guid, PlanId: "plan-min-hours-since-last-run", Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, @@ -208,11 +223,12 @@ func TestScheduling(t *testing.T) { }, { name: "backup schedule cron", - task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-cron")), + task: NewScheduledBackupTask(config.FindRepo(cfg, "repo1"), config.FindPlan(cfg, "plan-cron")), ops: []*v1.Operation{ { InstanceId: "instance1", RepoId: "repo1", + RepoGuid: repo1.Guid, PlanId: "plan-cron", Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, @@ -225,11 +241,12 @@ func TestScheduling(t *testing.T) { }, { name: "backup schedule cron utc", - task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-cron-utc")), + task: NewScheduledBackupTask(config.FindRepo(cfg, "repo1"), config.FindPlan(cfg, "plan-cron-utc")), ops: []*v1.Operation{ { InstanceId: "instance1", RepoId: "repo1", + RepoGuid: repo1.Guid, PlanId: "plan-cron-utc", Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, @@ -242,11 +259,12 @@ func TestScheduling(t *testing.T) { }, { name: "backup schedule cron since last run", - task: NewScheduledBackupTask(config.FindPlan(cfg, "plan-cron-since-last-run")), + task: NewScheduledBackupTask(config.FindRepo(cfg, "repo1"), config.FindPlan(cfg, "plan-cron-since-last-run")), ops: []*v1.Operation{ { InstanceId: "instance1", RepoId: "repo1", + RepoGuid: repo1.Guid, PlanId: "plan-cron-since-last-run", Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, @@ -259,11 +277,12 @@ func TestScheduling(t *testing.T) { }, { name: "check schedule absolute", - task: NewCheckTask("repo-absolute", "_system_", false), + task: NewCheckTask(repoAbsolute, "_system_", false), ops: []*v1.Operation{ { InstanceId: "instance1", RepoId: "repo-absolute", + RepoGuid: repoAbsolute.Guid, PlanId: "_system_", Op: &v1.Operation_OperationCheck{ OperationCheck: &v1.OperationCheck{}, @@ -276,11 +295,12 @@ func TestScheduling(t *testing.T) { }, { name: "check schedule relative no backup yet", - task: NewCheckTask("repo-relative", "_system_", false), + task: NewCheckTask(repoRelative, "_system_", false), ops: []*v1.Operation{ { InstanceId: "instance1", RepoId: "repo-relative", + RepoGuid: repoRelative.Guid, PlanId: "_system_", Op: &v1.Operation_OperationCheck{ OperationCheck: &v1.OperationCheck{}, @@ -293,11 +313,12 @@ func TestScheduling(t *testing.T) { }, { name: "check schedule relative", - task: NewCheckTask("repo-relative", "_system_", false), + task: NewCheckTask(repoRelative, "_system_", false), ops: []*v1.Operation{ { InstanceId: "instance1", RepoId: "repo-relative", + RepoGuid: repoRelative.Guid, PlanId: "_system_", Op: &v1.Operation_OperationCheck{ OperationCheck: &v1.OperationCheck{}, @@ -308,6 +329,7 @@ func TestScheduling(t *testing.T) { { InstanceId: "instance1", RepoId: "repo-relative", + RepoGuid: repoRelative.Guid, PlanId: "_system_", Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, @@ -320,11 +342,12 @@ func TestScheduling(t *testing.T) { }, { name: "prune schedule absolute", - task: NewPruneTask("repo-absolute", "_system_", false), + task: NewPruneTask(repoAbsolute, "_system_", false), ops: []*v1.Operation{ { InstanceId: "instance1", RepoId: "repo-absolute", + RepoGuid: repoAbsolute.Guid, PlanId: "_system_", Op: &v1.Operation_OperationPrune{ OperationPrune: &v1.OperationPrune{}, @@ -337,11 +360,12 @@ func TestScheduling(t *testing.T) { }, { name: "prune schedule relative no backup yet", - task: NewPruneTask("repo-relative", "_system_", false), + task: NewPruneTask(repoRelative, "_system_", false), ops: []*v1.Operation{ { InstanceId: "instance1", RepoId: "repo-relative", + RepoGuid: repoRelative.Guid, PlanId: "_system_", Op: &v1.Operation_OperationPrune{ OperationPrune: &v1.OperationPrune{}, @@ -354,11 +378,12 @@ func TestScheduling(t *testing.T) { }, { name: "prune schedule relative", - task: NewPruneTask("repo-relative", "_system_", false), + task: NewPruneTask(repoRelative, "_system_", false), ops: []*v1.Operation{ { InstanceId: "instance1", RepoId: "repo-relative", + RepoGuid: repoRelative.Guid, PlanId: "_system_", Op: &v1.Operation_OperationPrune{ OperationPrune: &v1.OperationPrune{}, @@ -369,6 +394,7 @@ func TestScheduling(t *testing.T) { { InstanceId: "instance1", RepoId: "repo-relative", + RepoGuid: repoRelative.Guid, PlanId: "_system_", Op: &v1.Operation_OperationBackup{ OperationBackup: &v1.OperationBackup{}, @@ -386,7 +412,10 @@ func TestScheduling(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - opstore := memstore.NewMemStore() + opstore, err := sqlitestore.NewMemorySqliteStore() + if err != nil { + t.Fatalf("failed to create opstore: %v", err) + } for _, op := range tc.ops { if err := opstore.Add(op); err != nil { t.Fatalf("failed to add operation to opstore: %v", err) diff --git a/internal/orchestrator/tasks/task.go b/internal/orchestrator/tasks/task.go index 4020c66..eac2471 100644 --- a/internal/orchestrator/tasks/task.go +++ b/internal/orchestrator/tasks/task.go @@ -33,14 +33,20 @@ const ( // TaskRunner is an interface for running tasks. It is used by tasks to create operations and write logs. type TaskRunner interface { + // InstanceID returns the instance ID executing this task. + InstanceID() string + // GetOperation returns the operation with the given ID. + GetOperation(id int64) (*v1.Operation, error) // CreateOperation creates the operation in storage and sets the operation ID in the task. - CreateOperation(*v1.Operation) error + CreateOperation(...*v1.Operation) error // UpdateOperation updates the operation in storage. It must be called after CreateOperation. - UpdateOperation(*v1.Operation) error + UpdateOperation(...*v1.Operation) error + // DeleteOperation deletes the operation from storage. + DeleteOperation(...int64) error + // QueryOperations queries the operation log. + QueryOperations(oplog.Query, func(*v1.Operation) error) error // ExecuteHooks ExecuteHooks(ctx context.Context, events []v1.Hook_Condition, vars HookVars) error - // OpLog returns the oplog for the operations. - OpLog() *oplog.OpLog // GetRepo returns the repo with the given ID. GetRepo(repoID string) (*v1.Repo, error) // GetPlan returns the plan with the given ID. @@ -87,13 +93,14 @@ type Task interface { Run(ctx context.Context, st ScheduledTask, runner TaskRunner) error // run the task. PlanID() string // the ID of the plan this task is associated with. RepoID() string // the ID of the repo this task is associated with. + Repo() *v1.Repo // the repo this task is associated with. } type BaseTask struct { TaskType string TaskName string TaskPlanID string - TaskRepoID string + TaskRepo *v1.Repo } func (b BaseTask) Type() string { @@ -109,7 +116,14 @@ func (b BaseTask) PlanID() string { } func (b BaseTask) RepoID() string { - return b.TaskRepoID + if b.TaskRepo == nil { + return "" + } + return b.TaskRepo.Id +} + +func (b BaseTask) Repo() *v1.Repo { + return b.TaskRepo } type OneoffTask struct { @@ -128,9 +142,13 @@ func (o *OneoffTask) Next(now time.Time, runner TaskRunner) (ScheduledTask, erro var op *v1.Operation if o.ProtoOp != nil { + if o.TaskRepo == nil { + return NeverScheduledTask, errors.New("task.repo must be provided if task.protoOp is provided") + } op = proto.Clone(o.ProtoOp).(*v1.Operation) - op.RepoId = o.RepoID() - op.PlanId = o.PlanID() + op.RepoId = o.TaskRepo.Id + op.RepoGuid = o.TaskRepo.Guid + op.PlanId = o.TaskPlanID op.FlowId = o.FlowID op.UnixTimeStartMs = timeToUnixMillis(o.RunAt) // TODO: this should be updated before Run is called. op.Status = v1.OperationStatus_STATUS_PENDING @@ -173,20 +191,47 @@ func newTestTaskRunner(_ testing.TB, config *v1.Config, oplog *oplog.OpLog) *tes } } -func (t *testTaskRunner) CreateOperation(op *v1.Operation) error { - panic("not implemented") +func (t *testTaskRunner) InstanceID() string { + return t.config.Instance } -func (t *testTaskRunner) UpdateOperation(op *v1.Operation) error { - panic("not implemented") +func (t *testTaskRunner) GetOperation(id int64) (*v1.Operation, error) { + return t.oplog.Get(id) +} + +func (t *testTaskRunner) CreateOperation(op ...*v1.Operation) error { + for _, o := range op { + if o.InstanceId != "" { + continue + } + o.InstanceId = t.InstanceID() + } + return t.oplog.Add(op...) +} + +func (t *testTaskRunner) UpdateOperation(op ...*v1.Operation) error { + for _, o := range op { + if o.InstanceId != "" { + continue + } + o.InstanceId = t.InstanceID() + } + return t.oplog.Update(op...) +} + +func (t *testTaskRunner) DeleteOperation(id ...int64) error { + return t.oplog.Delete(id...) } func (t *testTaskRunner) ExecuteHooks(ctx context.Context, events []v1.Hook_Condition, vars HookVars) error { panic("not implemented") } -func (t *testTaskRunner) OpLog() *oplog.OpLog { - return t.oplog +func (t *testTaskRunner) QueryOperations(q oplog.Query, fn func(*v1.Operation) error) error { + if q.InstanceID == nil { + q.SetInstanceID(t.InstanceID()) + } + return t.oplog.Query(q, fn) } func (t *testTaskRunner) GetRepo(repoID string) (*v1.Repo, error) { diff --git a/internal/orchestrator/tasks/taskbackup.go b/internal/orchestrator/tasks/taskbackup.go index ec1e4ec..f158f1a 100644 --- a/internal/orchestrator/tasks/taskbackup.go +++ b/internal/orchestrator/tasks/taskbackup.go @@ -27,23 +27,23 @@ type BackupTask struct { var _ Task = &BackupTask{} -func NewScheduledBackupTask(plan *v1.Plan) *BackupTask { +func NewScheduledBackupTask(repo *v1.Repo, plan *v1.Plan) *BackupTask { return &BackupTask{ BaseTask: BaseTask{ TaskType: "backup", TaskName: fmt.Sprintf("backup for plan %q", plan.Id), - TaskRepoID: plan.Repo, + TaskRepo: repo, TaskPlanID: plan.Id, }, } } -func NewOneoffBackupTask(plan *v1.Plan, at time.Time) *BackupTask { +func NewOneoffBackupTask(repo *v1.Repo, plan *v1.Plan, at time.Time) *BackupTask { return &BackupTask{ BaseTask: BaseTask{ TaskType: "backup", TaskName: fmt.Sprintf("backup for plan %q", plan.Id), - TaskRepoID: plan.Repo, + TaskRepo: repo, TaskPlanID: plan.Id, }, force: true, @@ -75,7 +75,11 @@ func (t *BackupTask) Next(now time.Time, runner TaskRunner) (ScheduledTask, erro } var lastRan time.Time - if err := runner.OpLog().Query(oplog.Query{RepoID: t.RepoID(), PlanID: t.PlanID(), Reversed: true}, func(op *v1.Operation) error { + if err := runner.QueryOperations(oplog.Query{}. + SetInstanceID(runner.InstanceID()). + SetRepoGUID(t.Repo().GetGuid()). + SetPlanID(t.PlanID()). + SetReversed(true), func(op *v1.Operation) error { if op.Status == v1.OperationStatus_STATUS_PENDING || op.Status == v1.OperationStatus_STATUS_SYSTEM_CANCELLED { return nil } @@ -237,11 +241,11 @@ func (t *BackupTask) Run(ctx context.Context, st ScheduledTask, runner TaskRunne // schedule followup tasks if a snapshot was added at := time.Now() if _, ok := plan.Retention.GetPolicy().(*v1.RetentionPolicy_PolicyKeepAll); plan.Retention != nil && !ok { - if err := runner.ScheduleTask(NewOneoffForgetTask(t.RepoID(), t.PlanID(), op.FlowId, at), TaskPriorityForget); err != nil { + if err := runner.ScheduleTask(NewOneoffForgetTask(t.Repo(), t.PlanID(), op.FlowId, at), TaskPriorityForget); err != nil { return fmt.Errorf("failed to schedule forget task: %w", err) } } - if err := runner.ScheduleTask(NewOneoffIndexSnapshotsTask(t.RepoID(), at), TaskPriorityIndexSnapshots); err != nil { + if err := runner.ScheduleTask(NewOneoffIndexSnapshotsTask(t.Repo(), at), TaskPriorityIndexSnapshots); err != nil { return fmt.Errorf("failed to schedule index snapshots task: %w", err) } } diff --git a/internal/orchestrator/tasks/taskcheck.go b/internal/orchestrator/tasks/taskcheck.go index c00c77e..ca0464e 100644 --- a/internal/orchestrator/tasks/taskcheck.go +++ b/internal/orchestrator/tasks/taskcheck.go @@ -17,12 +17,12 @@ type CheckTask struct { didRun bool } -func NewCheckTask(repoID, planID string, force bool) Task { +func NewCheckTask(repo *v1.Repo, planID string, force bool) Task { return &CheckTask{ BaseTask: BaseTask{ TaskType: "check", - TaskName: fmt.Sprintf("check for repo %q", repoID), - TaskRepoID: repoID, + TaskName: fmt.Sprintf("check for repo %q", repo.Id), + TaskRepo: repo, TaskPlanID: planID, }, force: force, @@ -56,7 +56,10 @@ func (t *CheckTask) Next(now time.Time, runner TaskRunner) (ScheduledTask, error var lastRan time.Time var foundBackup bool - if err := runner.OpLog().Query(oplog.Query{RepoID: t.RepoID(), Reversed: true}, func(op *v1.Operation) error { + if err := runner.QueryOperations(oplog.Query{}. + SetInstanceID(runner.InstanceID()). // note: this means that check tasks run by remote instances are ignored. + SetRepoGUID(t.Repo().GetGuid()). + SetReversed(true), func(op *v1.Operation) error { if op.Status == v1.OperationStatus_STATUS_PENDING || op.Status == v1.OperationStatus_STATUS_SYSTEM_CANCELLED { return nil } diff --git a/internal/orchestrator/tasks/taskcollectgarbage.go b/internal/orchestrator/tasks/taskcollectgarbage.go index 3ebd468..866b666 100644 --- a/internal/orchestrator/tasks/taskcollectgarbage.go +++ b/internal/orchestrator/tasks/taskcollectgarbage.go @@ -19,9 +19,11 @@ type gcSettingsForType struct { } type groupByKey struct { - Repo string - Plan string - Type reflect.Type + RepoID string + RepoGUID string + PlanID string + InstanceID string + Type reflect.Type } const ( @@ -89,19 +91,17 @@ func (t *CollectGarbageTask) Next(now time.Time, runner TaskRunner) (ScheduledTa } func (t *CollectGarbageTask) Run(ctx context.Context, st ScheduledTask, runner TaskRunner) error { - oplog := runner.OpLog() - - if err := t.gcOperations(oplog); err != nil { + if err := t.gcOperations(runner); err != nil { return fmt.Errorf("collecting garbage: %w", err) } return nil } -func (t *CollectGarbageTask) gcOperations(log *oplog.OpLog) error { +func (t *CollectGarbageTask) gcOperations(runner TaskRunner) error { // snapshotForgottenForFlow returns whether the snapshot associated with the flow is forgotten snapshotForgottenForFlow := make(map[int64]bool) - if err := log.Query(oplog.SelectAll, func(op *v1.Operation) error { + if err := runner.QueryOperations(oplog.SelectAll, func(op *v1.Operation) error { if snapshotOp, ok := op.Op.(*v1.Operation_OperationIndexSnapshot); ok { snapshotForgottenForFlow[op.FlowId] = snapshotOp.OperationIndexSnapshot.Forgot } @@ -119,7 +119,7 @@ func (t *CollectGarbageTask) gcOperations(log *oplog.OpLog) error { deletedByType := make(map[string]int) stats := make(map[groupByKey]gcSettingsForType) - if err := log.Query(oplog.Query{Reversed: true}, func(op *v1.Operation) error { + if err := runner.QueryOperations(oplog.Query{}.SetReversed(true), func(op *v1.Operation) error { validIDs[op.Id] = struct{}{} forgot, ok := snapshotForgottenForFlow[op.FlowId] @@ -134,9 +134,11 @@ func (t *CollectGarbageTask) gcOperations(log *oplog.OpLog) error { } key := groupByKey{ - Repo: op.RepoId, - Plan: op.PlanId, - Type: reflect.TypeOf(op.Op), + RepoGUID: op.RepoGuid, + RepoID: op.RepoId, + PlanID: op.PlanId, + InstanceID: op.InstanceId, + Type: reflect.TypeOf(op.Op), } st, ok := stats[key] @@ -174,7 +176,7 @@ func (t *CollectGarbageTask) gcOperations(log *oplog.OpLog) error { return fmt.Errorf("identifying gc eligible operations: %w", err) } - if err := log.Delete(forgetIDs...); err != nil { + if err := runner.DeleteOperation(forgetIDs...); err != nil { return fmt.Errorf("removing gc eligible operations: %w", err) } for _, id := range forgetIDs { // update validIDs with respect to the just deleted operations diff --git a/internal/orchestrator/tasks/taskforget.go b/internal/orchestrator/tasks/taskforget.go index f72b796..5a27190 100644 --- a/internal/orchestrator/tasks/taskforget.go +++ b/internal/orchestrator/tasks/taskforget.go @@ -12,13 +12,13 @@ import ( "go.uber.org/zap" ) -func NewOneoffForgetTask(repoID, planID string, flowID int64, at time.Time) Task { +func NewOneoffForgetTask(repo *v1.Repo, planID string, flowID int64, at time.Time) Task { return &GenericOneoffTask{ OneoffTask: OneoffTask{ BaseTask: BaseTask{ TaskType: "forget", - TaskName: fmt.Sprintf("forget for plan %q in repo %q", repoID, planID), - TaskRepoID: repoID, + TaskName: fmt.Sprintf("forget for plan %q in repo %q", repo.Id, planID), + TaskRepo: repo, TaskPlanID: planID, }, FlowID: flowID, @@ -50,7 +50,6 @@ func NewOneoffForgetTask(repoID, planID string, flowID int64, at time.Time) Task func forgetHelper(ctx context.Context, st ScheduledTask, taskRunner TaskRunner) error { t := st.Task - log := taskRunner.OpLog() l := taskRunner.Logger(ctx) r, err := taskRunner.GetRepoOrchestrator(t.RepoID()) @@ -69,7 +68,7 @@ func forgetHelper(ctx context.Context, st ScheduledTask, taskRunner TaskRunner) } tags := []string{repo.TagForPlan(t.PlanID())} - if compat, err := useLegacyCompatMode(l, log, t.RepoID(), t.PlanID()); err != nil { + if compat, err := useLegacyCompatMode(l, taskRunner, t.Repo().GetGuid(), t.PlanID()); err != nil { return fmt.Errorf("check legacy compat mode: %w", err) } else if !compat { tags = append(tags, repo.TagForInstance(taskRunner.Config().Instance)) @@ -95,7 +94,9 @@ func forgetHelper(ctx context.Context, st ScheduledTask, taskRunner TaskRunner) var ops []*v1.Operation for _, forgot := range forgot { - if e := taskRunner.OpLog().Query(oplog.Query{SnapshotID: forgot.Id}, func(op *v1.Operation) error { + if e := taskRunner.QueryOperations(oplog.Query{}. + SetRepoGUID(t.Repo().GetGuid()). + SetSnapshotID(forgot.Id), func(op *v1.Operation) error { ops = append(ops, op) return nil }); e != nil { @@ -120,9 +121,9 @@ func forgetHelper(ctx context.Context, st ScheduledTask, taskRunner TaskRunner) // useLegacyCompatMode checks if there are any snapshots that were created without a `created-by` tag still exist in the repo. // The property is overridden if mixed `created-by` tag values are found. -func useLegacyCompatMode(l *zap.Logger, log *oplog.OpLog, repoID, planID string) (bool, error) { +func useLegacyCompatMode(l *zap.Logger, taskRunner TaskRunner, repoGUID, planID string) (bool, error) { instanceIDs := make(map[string]struct{}) - if err := log.Query(oplog.Query{RepoID: repoID, PlanID: planID}, func(op *v1.Operation) error { + if err := taskRunner.QueryOperations(oplog.Query{}.SetRepoGUID(repoGUID).SetPlanID(planID).SetReversed(true), func(op *v1.Operation) error { if snapshotOp, ok := op.Op.(*v1.Operation_OperationIndexSnapshot); ok && !snapshotOp.OperationIndexSnapshot.GetForgot() { tags := snapshotOp.OperationIndexSnapshot.GetSnapshot().GetTags() instanceIDs[repo.InstanceIDFromTags(tags)] = struct{}{} diff --git a/internal/orchestrator/tasks/taskforgetsnapshot.go b/internal/orchestrator/tasks/taskforgetsnapshot.go index ef3174f..f60328b 100644 --- a/internal/orchestrator/tasks/taskforgetsnapshot.go +++ b/internal/orchestrator/tasks/taskforgetsnapshot.go @@ -8,13 +8,13 @@ import ( v1 "github.com/garethgeorge/backrest/gen/go/v1" ) -func NewOneoffForgetSnapshotTask(repoID, planID string, flowID int64, at time.Time, snapshotID string) Task { +func NewOneoffForgetSnapshotTask(repo *v1.Repo, planID string, flowID int64, at time.Time, snapshotID string) Task { return &GenericOneoffTask{ OneoffTask: OneoffTask{ BaseTask: BaseTask{ TaskType: "forget_snapshot", - TaskName: fmt.Sprintf("forget snapshot %q for plan %q in repo %q", snapshotID, planID, repoID), - TaskRepoID: repoID, + TaskName: fmt.Sprintf("forget snapshot %q for plan %q in repo %q", snapshotID, planID, repo.Id), + TaskRepo: repo, TaskPlanID: planID, }, FlowID: flowID, @@ -60,8 +60,8 @@ func forgetSnapshotHelper(ctx context.Context, st ScheduledTask, taskRunner Task return fmt.Errorf("forget %q: %w", snapshotID, err) } - taskRunner.ScheduleTask(NewOneoffIndexSnapshotsTask(t.RepoID(), time.Now()), TaskPriorityIndexSnapshots) - taskRunner.OpLog().Delete(st.Op.Id) + taskRunner.ScheduleTask(NewOneoffIndexSnapshotsTask(t.Repo(), time.Now()), TaskPriorityIndexSnapshots) + taskRunner.DeleteOperation(st.Op.Id) st.Op = nil return err } diff --git a/internal/orchestrator/tasks/taskindexsnapshots.go b/internal/orchestrator/tasks/taskindexsnapshots.go index a9f9eab..5a0fd16 100644 --- a/internal/orchestrator/tasks/taskindexsnapshots.go +++ b/internal/orchestrator/tasks/taskindexsnapshots.go @@ -15,13 +15,13 @@ import ( "go.uber.org/zap" ) -func NewOneoffIndexSnapshotsTask(repoID string, at time.Time) Task { +func NewOneoffIndexSnapshotsTask(repo *v1.Repo, at time.Time) Task { return &GenericOneoffTask{ OneoffTask: OneoffTask{ BaseTask: BaseTask{ - TaskType: "index_snapshots", - TaskName: fmt.Sprintf("index snapshots for repo %q", repoID), - TaskRepoID: repoID, + TaskType: "index_snapshots", + TaskName: fmt.Sprintf("index snapshots for repo %q", repo.Id), + TaskRepo: repo, }, RunAt: at, ProtoOp: nil, @@ -47,7 +47,6 @@ func NewOneoffIndexSnapshotsTask(repoID string, at time.Time) Task { // - If an index snapshot operation is found for a snapshot that is not returned by the repo, it is marked as forgotten. func indexSnapshotsHelper(ctx context.Context, st ScheduledTask, taskRunner TaskRunner) error { t := st.Task - oplog := taskRunner.OpLog() l := taskRunner.Logger(ctx) repo, err := taskRunner.GetRepoOrchestrator(t.RepoID()) @@ -62,27 +61,11 @@ func indexSnapshotsHelper(ctx context.Context, st ScheduledTask, taskRunner Task } // collect all current snapshot IDs. - currentIds, err := indexCurrentSnapshotIdsForRepo(taskRunner.OpLog(), t.RepoID()) + currentIds, err := indexCurrentSnapshotIdsForRepo(taskRunner, t.Repo().GetGuid()) if err != nil { return fmt.Errorf("get known snapshot IDs for repo %q: %w", t.RepoID(), err) } - // check if any migrations are required - // if migrated, err := tryMigrate(ctx, repo, config, snapshots); err != nil { - // return fmt.Errorf("migrate snapshots for repo %q: %w", t.RepoID(), err) - // } else if migrated { - // // Delete snapshot operations - // if err := oplog.Delete(maps.Values(currentIds)...); err != nil { - // return fmt.Errorf("delete prior indexed operations: %w", err) - // } - - // snapshots, err = repo.Snapshots(ctx) - // if err != nil { - // return fmt.Errorf("get snapshots for repo %q: %w", t.RepoID(), err) - // } - // currentIds = nil - // } - foundIds := make(map[string]struct{}) // Index newly found operations @@ -94,14 +77,15 @@ func indexSnapshotsHelper(ctx context.Context, st ScheduledTask, taskRunner Task } snapshotProto := protoutil.SnapshotToProto(snapshot) - flowID, err := FlowIDForSnapshotID(taskRunner.OpLog(), snapshot.Id) + flowID, err := FlowIDForSnapshotID(taskRunner, snapshot.Id) if err != nil { return fmt.Errorf("get flow ID for snapshot %q: %w", snapshot.Id, err) } planId := planForSnapshot(snapshotProto) instanceID := instanceIDForSnapshot(snapshotProto) indexOps = append(indexOps, &v1.Operation{ - RepoId: t.RepoID(), + RepoId: t.Repo().Id, + RepoGuid: t.Repo().Guid, PlanId: planId, FlowId: flowID, InstanceId: instanceID, @@ -120,8 +104,8 @@ func indexSnapshotsHelper(ctx context.Context, st ScheduledTask, taskRunner Task l.Sugar().Debugf("adding %v new snapshots to the oplog", len(indexOps)) l.Sugar().Debugf("found %v snapshots already indexed", len(foundIds)) - if err := taskRunner.OpLog().Add(indexOps...); err != nil { - return fmt.Errorf("BulkAdd snapshot operations: %w", err) + if err := taskRunner.CreateOperation(indexOps...); err != nil { + return fmt.Errorf("Create snapshot operations: %w", err) } // Mark missing operations as newly forgotten. @@ -132,7 +116,7 @@ func indexSnapshotsHelper(ctx context.Context, st ScheduledTask, taskRunner Task } // mark snapshot forgotten. - op, err := oplog.Get(opId) + op, err := taskRunner.GetOperation(opId) if err != nil { // should only be possible in the case of a data race (e.g. operation was somehow deleted). return fmt.Errorf("get operation %v: %w", opId, err) @@ -144,23 +128,22 @@ func indexSnapshotsHelper(ctx context.Context, st ScheduledTask, taskRunner Task } snapshotOp.OperationIndexSnapshot.Forgot = true - if err := oplog.Update(op); err != nil { + if err := taskRunner.UpdateOperation(op); err != nil { return fmt.Errorf("mark index snapshot operation %v as forgotten: %w", opId, err) } } l.Sugar().Debugf("marked %v snapshots as forgotten", len(currentIds)-len(foundIds)) - l.Sugar().Debugf("done indexing %v for repo %v, took %v", len(foundIds), t.RepoID()) return err } // returns a map of current (e.g. not forgotten) snapshot IDs for the plan. -func indexCurrentSnapshotIdsForRepo(log *oplog.OpLog, repoId string) (map[string]int64, error) { +func indexCurrentSnapshotIdsForRepo(taskRunner TaskRunner, repoGUID string) (map[string]int64, error) { knownIds := make(map[string]int64) startTime := time.Now() - if err := log.Query(oplog.Query{RepoID: repoId}, func(op *v1.Operation) error { + if err := taskRunner.QueryOperations(oplog.Query{}.SetRepoGUID(repoGUID), func(op *v1.Operation) error { if snapshotOp, ok := op.Op.(*v1.Operation_OperationIndexSnapshot); ok { if !snapshotOp.OperationIndexSnapshot.Forgot { knownIds[snapshotOp.OperationIndexSnapshot.Snapshot.Id] = op.Id @@ -170,7 +153,7 @@ func indexCurrentSnapshotIdsForRepo(log *oplog.OpLog, repoId string) (map[string }); err != nil { return nil, err } - zap.S().Debugf("found %v known snapshot IDs for repo %v in %v", len(knownIds), repoId, time.Since(startTime)) + zap.S().Debugf("found %v known snapshot IDs for repo %v in %v", len(knownIds), repoGUID, time.Since(startTime)) return knownIds, nil } diff --git a/internal/orchestrator/tasks/taskprune.go b/internal/orchestrator/tasks/taskprune.go index 6cc7281..345d231 100644 --- a/internal/orchestrator/tasks/taskprune.go +++ b/internal/orchestrator/tasks/taskprune.go @@ -18,12 +18,12 @@ type PruneTask struct { didRun bool } -func NewPruneTask(repoID, planID string, force bool) Task { +func NewPruneTask(repo *v1.Repo, planID string, force bool) Task { return &PruneTask{ BaseTask: BaseTask{ TaskType: "prune", - TaskName: fmt.Sprintf("prune repo %q", repoID), - TaskRepoID: repoID, + TaskName: fmt.Sprintf("prune repo %q", repo.Id), + TaskRepo: repo, TaskPlanID: planID, }, force: force, @@ -56,7 +56,10 @@ func (t *PruneTask) Next(now time.Time, runner TaskRunner) (ScheduledTask, error var lastRan time.Time var foundBackup bool - if err := runner.OpLog().Query(oplog.Query{RepoID: t.RepoID(), Reversed: true}, func(op *v1.Operation) error { + if err := runner.QueryOperations(oplog.Query{}. + SetInstanceID(runner.InstanceID()). // note: this means that prune tasks run by remote instances are ignored. + SetRepoGUID(repo.GetGuid()). + SetReversed(true), func(op *v1.Operation) error { if op.Status == v1.OperationStatus_STATUS_PENDING || op.Status == v1.OperationStatus_STATUS_SYSTEM_CANCELLED { return nil } @@ -142,7 +145,7 @@ func (t *PruneTask) Run(ctx context.Context, st ScheduledTask, runner TaskRunner } // Run a stats task after a successful prune - if err := runner.ScheduleTask(NewStatsTask(t.RepoID(), PlanForSystemTasks, false), TaskPriorityStats); err != nil { + if err := runner.ScheduleTask(NewStatsTask(t.Repo(), PlanForSystemTasks, false), TaskPriorityStats); err != nil { zap.L().Error("schedule stats task", zap.Error(err)) } diff --git a/internal/orchestrator/tasks/taskrestore.go b/internal/orchestrator/tasks/taskrestore.go index db3f331..5f1ff1f 100644 --- a/internal/orchestrator/tasks/taskrestore.go +++ b/internal/orchestrator/tasks/taskrestore.go @@ -11,13 +11,13 @@ import ( "go.uber.org/zap" ) -func NewOneoffRestoreTask(repoID, planID string, flowID int64, at time.Time, snapshotID, path, target string) Task { +func NewOneoffRestoreTask(repo *v1.Repo, planID string, flowID int64, at time.Time, snapshotID, path, target string) Task { return &GenericOneoffTask{ OneoffTask: OneoffTask{ BaseTask: BaseTask{ TaskType: "restore", - TaskName: fmt.Sprintf("restore snapshot %q in repo %q", snapshotID, repoID), - TaskRepoID: repoID, + TaskName: fmt.Sprintf("restore snapshot %q in repo %q", snapshotID, repo.Id), + TaskRepo: repo, TaskPlanID: planID, }, FlowID: flowID, @@ -49,7 +49,6 @@ func NewOneoffRestoreTask(repoID, planID string, flowID int64, at time.Time, sna func restoreHelper(ctx context.Context, st ScheduledTask, taskRunner TaskRunner, snapshotID, path, target string) error { t := st.Task - oplog := taskRunner.OpLog() op := st.Op if snapshotID == "" || path == "" || target == "" { @@ -79,7 +78,7 @@ func restoreHelper(ctx context.Context, st ScheduledTask, taskRunner TaskRunner, sendWg.Add(1) go func() { - if err := oplog.Update(op); err != nil { + if err := taskRunner.UpdateOperation(op); err != nil { zap.S().Errorf("failed to update oplog with progress for restore: %v", err) } sendWg.Done() diff --git a/internal/orchestrator/tasks/taskruncommand.go b/internal/orchestrator/tasks/taskruncommand.go index 18d46e0..3a5c066 100644 --- a/internal/orchestrator/tasks/taskruncommand.go +++ b/internal/orchestrator/tasks/taskruncommand.go @@ -9,13 +9,13 @@ import ( "github.com/garethgeorge/backrest/internal/ioutil" ) -func NewOneoffRunCommandTask(repoID string, planID string, flowID int64, at time.Time, command string) Task { +func NewOneoffRunCommandTask(repo *v1.Repo, planID string, flowID int64, at time.Time, command string) Task { return &GenericOneoffTask{ OneoffTask: OneoffTask{ BaseTask: BaseTask{ TaskType: "run_command", - TaskName: fmt.Sprintf("run command in repo %q", repoID), - TaskRepoID: repoID, + TaskName: fmt.Sprintf("run command in repo %q", repo.Id), + TaskRepo: repo, TaskPlanID: planID, }, FlowID: flowID, diff --git a/internal/orchestrator/tasks/taskstats.go b/internal/orchestrator/tasks/taskstats.go index adf68cf..948fc33 100644 --- a/internal/orchestrator/tasks/taskstats.go +++ b/internal/orchestrator/tasks/taskstats.go @@ -15,12 +15,12 @@ type StatsTask struct { didRun bool } -func NewStatsTask(repoID, planID string, force bool) Task { +func NewStatsTask(repo *v1.Repo, planID string, force bool) Task { return &StatsTask{ BaseTask: BaseTask{ TaskType: "stats", - TaskName: fmt.Sprintf("stats for repo %q", repoID), - TaskRepoID: repoID, + TaskName: fmt.Sprintf("stats for repo %q", repo.Id), + TaskRepo: repo, TaskPlanID: planID, }, force: force, @@ -44,7 +44,10 @@ func (t *StatsTask) Next(now time.Time, runner TaskRunner) (ScheduledTask, error // check last stats time var lastRan time.Time - if err := runner.OpLog().Query(oplog.Query{RepoID: t.RepoID(), Reversed: true}, func(op *v1.Operation) error { + if err := runner.QueryOperations(oplog.Query{}. + SetInstanceID(runner.InstanceID()). // note: this means that stats tasks run by remote instances are ignored. + SetRepoGUID(t.Repo().GetGuid()). + SetReversed(true), func(op *v1.Operation) error { if op.Status == v1.OperationStatus_STATUS_PENDING || op.Status == v1.OperationStatus_STATUS_SYSTEM_CANCELLED { return nil } diff --git a/internal/protoutil/opselector.go b/internal/protoutil/opselector.go new file mode 100644 index 0000000..fdb4846 --- /dev/null +++ b/internal/protoutil/opselector.go @@ -0,0 +1,28 @@ +package protoutil + +import ( + "errors" + "reflect" + + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/internal/oplog" +) + +func OpSelectorToQuery(sel *v1.OpSelector) (oplog.Query, error) { + if sel == nil { + return oplog.Query{}, errors.New("empty selector") + } + + q := oplog.Query{ + RepoGUID: sel.RepoGuid, + PlanID: sel.PlanId, + SnapshotID: sel.SnapshotId, + FlowID: sel.FlowId, + InstanceID: sel.InstanceId, + } + if len(sel.Ids) > 0 && !reflect.DeepEqual(q, oplog.Query{}) { + return oplog.Query{}, errors.New("cannot specify both query and ids") + } + q.OpIDs = sel.Ids + return q, nil +} diff --git a/internal/protoutil/syncconversion.go b/internal/protoutil/syncconversion.go new file mode 100644 index 0000000..7a6f6b0 --- /dev/null +++ b/internal/protoutil/syncconversion.go @@ -0,0 +1,33 @@ +package protoutil + +import ( + v1 "github.com/garethgeorge/backrest/gen/go/v1" +) + +func RepoToRemoteRepo(r *v1.Repo) *v1.RemoteRepo { + if r == nil { + return nil + } + return &v1.RemoteRepo{ + Id: r.Id, + Guid: r.Guid, + Uri: r.Uri, + Password: r.Password, + Env: r.Env, + Flags: r.Flags, + } +} + +func RemoteRepoToRepo(r *v1.RemoteRepo) *v1.Repo { + if r == nil { + return nil + } + return &v1.Repo{ + Id: r.Id, + Guid: r.Guid, + Uri: r.Uri, + Password: r.Password, + Env: r.Env, + Flags: r.Flags, + } +} diff --git a/internal/protoutil/syncconversion_test.go b/internal/protoutil/syncconversion_test.go new file mode 100644 index 0000000..e4f8f08 --- /dev/null +++ b/internal/protoutil/syncconversion_test.go @@ -0,0 +1,96 @@ +package protoutil + +import ( + "reflect" + "testing" + + v1 "github.com/garethgeorge/backrest/gen/go/v1" +) + +func TestRepoToRemoteRepo(t *testing.T) { + tests := []struct { + name string + repo *v1.Repo + want *v1.RemoteRepo + }{ + { + name: "basic conversion", + repo: &v1.Repo{ + Id: "1", + Uri: "http://example.com", + Password: "password", + Env: []string{"FOO=BAR"}, + Flags: []string{"flag1", "flag2"}, + }, + want: &v1.RemoteRepo{ + Id: "1", + Uri: "http://example.com", + Password: "password", + Env: []string{"FOO=BAR"}, + Flags: []string{"flag1", "flag2"}, + }, + }, + { + name: "empty repo", + repo: &v1.Repo{}, + want: &v1.RemoteRepo{}, + }, + { + name: "nil repo", + repo: nil, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := RepoToRemoteRepo(tt.repo); !reflect.DeepEqual(got, tt.want) { + t.Errorf("RepoToRemoteRepo() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRemoteRepoToRepo(t *testing.T) { + tests := []struct { + name string + remoteRepo *v1.RemoteRepo + want *v1.Repo + }{ + { + name: "basic conversion", + remoteRepo: &v1.RemoteRepo{ + Id: "1", + Uri: "http://example.com", + Password: "password", + Env: []string{"FOO=BAR"}, + Flags: []string{"flag1", "flag2"}, + }, + want: &v1.Repo{ + Id: "1", + Uri: "http://example.com", + Password: "password", + Env: []string{"FOO=BAR"}, + Flags: []string{"flag1", "flag2"}, + }, + }, + { + name: "empty remote repo", + remoteRepo: &v1.RemoteRepo{}, + want: &v1.Repo{}, + }, + { + name: "nil remote repo", + remoteRepo: nil, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := RemoteRepoToRepo(tt.remoteRepo); !reflect.DeepEqual(got, tt.want) { + t.Errorf("RemoteRepoToRepo() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/protoutil/validation.go b/internal/protoutil/validation.go index 0979770..feea127 100644 --- a/internal/protoutil/validation.go +++ b/internal/protoutil/validation.go @@ -12,6 +12,7 @@ var ( errIDRequired = errors.New("id is required") errFlowIDRequired = errors.New("flow_id is required") errRepoIDRequired = errors.New("repo_id is required") + errRepoGUIDRequired = errors.New("repo_guid is required") errPlanIDRequired = errors.New("plan_id is required") errInstanceIDRequired = errors.New("instance_id is required") errUnixTimeStartMsRequired = errors.New("unix_time_start_ms must be non-zero") @@ -22,6 +23,9 @@ func ValidateOperation(op *v1.Operation) error { if op.Id == 0 { return errIDRequired } + if op.RepoGuid == "" { + return errRepoGUIDRequired + } if op.FlowId == 0 { return errFlowIDRequired } diff --git a/internal/testutil/deadline.go b/internal/testutil/deadline.go new file mode 100644 index 0000000..120d3d0 --- /dev/null +++ b/internal/testutil/deadline.go @@ -0,0 +1,16 @@ +package testutil + +import ( + "context" + "testing" + "time" +) + +var defaultDeadlineMargin = 5 * time.Second + +func WithDeadlineFromTest(t *testing.T, ctx context.Context) (context.Context, context.CancelFunc) { + if deadline, ok := t.Deadline(); ok { + return context.WithDeadline(ctx, deadline.Add(-defaultDeadlineMargin)) + } + return ctx, func() {} +} diff --git a/internal/testutil/logging.go b/internal/testutil/logging.go new file mode 100644 index 0000000..f6f2a92 --- /dev/null +++ b/internal/testutil/logging.go @@ -0,0 +1,28 @@ +package testutil + +import ( + "strings" + "testing" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type testLogger struct { + t *testing.T +} + +func (l *testLogger) Write(p []byte) (n int, err error) { + l.t.Log("global log: " + strings.Trim(string(p), "\n")) + return len(p), nil +} + +func InstallZapLogger(t *testing.T) { + t.Helper() + logger := zap.New(zapcore.NewCore( + zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig()), + zapcore.AddSync(&testLogger{t: t}), + zapcore.DebugLevel, + )) + zap.ReplaceGlobals(logger) +} diff --git a/internal/testutil/operations.go b/internal/testutil/operations.go new file mode 100644 index 0000000..126636b --- /dev/null +++ b/internal/testutil/operations.go @@ -0,0 +1,64 @@ +package testutil + +import ( + "crypto/rand" + "encoding/base64" + "encoding/binary" + "sync/atomic" + + v1 "github.com/garethgeorge/backrest/gen/go/v1" + "github.com/garethgeorge/backrest/internal/cryptoutil" + "google.golang.org/protobuf/proto" +) + +var nextRandomOperationTimeMillis atomic.Int64 + +func OperationsWithDefaults(op *v1.Operation, ops []*v1.Operation) []*v1.Operation { + var newOps []*v1.Operation + for _, o := range ops { + copy := proto.Clone(o).(*v1.Operation) + proto.Merge(copy, op) + newOps = append(newOps, copy) + } + + return newOps +} + +func RandomOperation() *v1.Operation { + randomPlanID := "plan" + randomString(5) + randomRepoID := "repo" + randomString(5) + randomRepoGUID := cryptoutil.MustRandomID(cryptoutil.DefaultIDBits) + randomInstanceID := "instance" + randomString(5) + + return &v1.Operation{ + UnixTimeStartMs: nextRandomOperationTimeMillis.Add(1000), + PlanId: randomPlanID, + RepoId: randomRepoID, + RepoGuid: randomRepoGUID, + InstanceId: randomInstanceID, + Op: &v1.Operation_OperationBackup{}, + FlowId: randomInt(), + OriginalId: randomInt(), + OriginalFlowId: randomInt(), + Modno: randomInt(), + Status: v1.OperationStatus_STATUS_INPROGRESS, + } +} + +func randomString(length int) string { + randomBytes := make([]byte, length) + _, err := rand.Read(randomBytes) + if err != nil { + panic(err) + } + return base64.URLEncoding.EncodeToString(randomBytes) +} + +func randomInt() int64 { + randBytes := make([]byte, 8) + _, err := rand.Read(randBytes) + if err != nil { + panic(err) + } + return int64(binary.LittleEndian.Uint64(randBytes) & 0x7FFFFFFFFFFFFFFF) +} diff --git a/internal/testutil/try.go b/internal/testutil/try.go new file mode 100644 index 0000000..252935e --- /dev/null +++ b/internal/testutil/try.go @@ -0,0 +1,50 @@ +package testutil + +import ( + "context" + "testing" + "time" +) + +func tryHelper(t *testing.T, ctx context.Context, f func() error) error { + ctx, cancel := WithDeadlineFromTest(t, ctx) + defer cancel() + + var err error + interval := 10 * time.Millisecond + for { + timer := time.NewTimer(interval) + interval += 10 * time.Millisecond + select { + case <-ctx.Done(): + timer.Stop() + return err + case <-timer.C: + timer.Stop() + err = f() + if err == nil { + return nil + } + } + } +} + +// try is a helper that spins until the condition becomes true OR the context is done. +func Try(t *testing.T, ctx context.Context, f func() error) { + t.Helper() + if err := tryHelper(t, ctx, f); err != nil { + t.Fatalf("timeout before OK: %v", err) + } +} + +func TryNonfatal(t *testing.T, ctx context.Context, f func() error) { + t.Helper() + if err := tryHelper(t, ctx, f); err != nil { + t.Errorf("timeout before OK: %v", err) + } +} + +func Retry(t *testing.T, ctx context.Context, f func() error) error { + t.Helper() + return tryHelper(t, ctx, f) +} diff --git a/pkg/restic/outputs.go b/pkg/restic/outputs.go index 014d533..52b5724 100644 --- a/pkg/restic/outputs.go +++ b/pkg/restic/outputs.go @@ -306,3 +306,9 @@ type RepoStats struct { TotalBlobCount int64 `json:"total_blob_count"` SnapshotsCount int64 `json:"snapshots_count"` } + +type RepoConfig struct { + Version int `json:"version"` + Id string `json:"id"` + ChunkerPolynomial string `json:"chunker_polynomial"` +} diff --git a/pkg/restic/restic.go b/pkg/restic/restic.go index 0b373a8..3a8f910 100644 --- a/pkg/restic/restic.go +++ b/pkg/restic/restic.go @@ -33,6 +33,7 @@ type Repo struct { checkExists sync.Once initialized error // nil or errAlreadyInitialized if initialized, error if initialization failed. shouldInitialize sync.Once + repoConfig RepoConfig // set by init (which calls Exists) } // NewRepo instantiates a new repository. @@ -99,6 +100,8 @@ func (r *Repo) Exists(ctx context.Context, opts ...GenericOption) error { r.pipeCmdOutputToWriter(cmd, output) if err := cmd.Run(); err != nil { r.exists = newCmdError(ctx, cmd, newErrorWithOutput(err, output.String())) + } else if err := json.Unmarshal(output.Bytes(), &r.repoConfig); err != nil { + r.exists = newCmdError(ctx, cmd, newErrorWithOutput(fmt.Errorf("command output is not valid JSON: %w", err), output.String())) } else { r.exists = nil } @@ -123,6 +126,11 @@ func (r *Repo) init(ctx context.Context, opts ...GenericOption) error { } else { r.initialized = newCmdError(ctx, cmd, newCmdError(ctx, cmd, newErrorWithOutput(err, output.String()))) } + } else { + if err := json.Unmarshal(output.Bytes(), &r.repoConfig); err != nil { + r.initialized = newCmdError(ctx, cmd, newErrorWithOutput(fmt.Errorf("command output is not valid JSON: %w", err), output.String())) + } + r.exists = r.initialized } }) @@ -136,6 +144,13 @@ func (r *Repo) Init(ctx context.Context, opts ...GenericOption) error { return nil } +func (r *Repo) Config(ctx context.Context, opts ...GenericOption) (RepoConfig, error) { + if err := r.Exists(ctx, opts...); err != nil { + return RepoConfig{}, err + } + return r.repoConfig, nil +} + func (r *Repo) Backup(ctx context.Context, paths []string, progressCallback func(*BackupProgressEntry), opts ...GenericOption) (*BackupProgressEntry, error) { for _, p := range paths { if _, err := os.Stat(p); err != nil { diff --git a/pkg/restic/restic_test.go b/pkg/restic/restic_test.go index dd69e05..26cb7cc 100644 --- a/pkg/restic/restic_test.go +++ b/pkg/restic/restic_test.go @@ -28,6 +28,53 @@ func TestResticInit(t *testing.T) { } } +func TestResticExists(t *testing.T) { + t.Parallel() + repo := t.TempDir() + + r := NewRepo(helpers.ResticBinary(t), repo, WithFlags("--no-cache"), WithEnv("RESTIC_PASSWORD=test")) + if err := r.Exists(context.Background()); err == nil { + t.Fatalf("expected repo not to exist") + } + if err := r.Init(context.Background()); err != nil { + t.Fatalf("failed to init repo: %v", err) + } + r2 := NewRepo(helpers.ResticBinary(t), repo, WithFlags("--no-cache"), WithEnv("RESTIC_PASSWORD=test")) + if err := r2.Exists(context.Background()); err != nil { + t.Fatalf("expected repo to exist, got error: %v", err) + } +} + +func TestResticConfig(t *testing.T) { + t.Parallel() + repo := t.TempDir() + + r := NewRepo(helpers.ResticBinary(t), repo, WithFlags("--no-cache"), WithEnv("RESTIC_PASSWORD=test")) + if err := r.Exists(context.Background()); err == nil { + t.Fatalf("expected repo not to exist") + } + if err := r.Init(context.Background()); err != nil { + t.Fatalf("failed to init repo: %v", err) + } + r2 := NewRepo(helpers.ResticBinary(t), repo, WithFlags("--no-cache"), WithEnv("RESTIC_PASSWORD=test")) + if err := r2.Exists(context.Background()); err != nil { + t.Fatalf("expected repo to exist, got error: %v", err) + } + cfg, err := r2.Config(context.Background()) + if err != nil { + t.Fatalf("failed to get repo config: %v", err) + } + if cfg.Id == "" { + t.Errorf("expected repo id to be set, got: %s", cfg.Id) + } + if cfg.ChunkerPolynomial == "" { + t.Errorf("expected chunker polynomial to be set, got: %s", cfg.ChunkerPolynomial) + } + if cfg.Version == 0 { + t.Errorf("expected version to be set, got: %d", cfg.Version) + } +} + func TestResticBackup(t *testing.T) { t.Parallel() repo := t.TempDir() @@ -37,6 +84,9 @@ func TestResticBackup(t *testing.T) { if err := r.Init(context.Background()); err != nil { t.Fatalf("failed to init repo: %v", err) } + if err := r.Exists(context.Background()); err != nil { + t.Fatalf("expected repo to exist, got error: %v", err) + } testData := helpers.CreateTestData(t) testData2 := helpers.CreateTestData(t) @@ -293,14 +343,6 @@ func checkSnapshotFieldsHelper(t *testing.T, snapshot *Snapshot) { if snapshot.UnixTimeMs() == 0 { t.Errorf("wanted snapshot time to be non-zero, got: %v", snapshot.UnixTimeMs()) } - if runtime.GOOS != "windows" { // flaky on windows; unclear why. - if snapshot.SnapshotSummary.TreeBlobs == 0 { - t.Errorf("wanted snapshot tree blobs to be non-zero, got: %v", snapshot.SnapshotSummary.TreeBlobs) - } - if snapshot.SnapshotSummary.DataAdded == 0 { - t.Errorf("wanted snapshot data added to be non-zero, got: %v", snapshot.SnapshotSummary.DataAdded) - } - } if snapshot.SnapshotSummary.TotalFilesProcessed == 0 { t.Errorf("wanted snapshot total files processed to be non-zero, got: %v", snapshot.SnapshotSummary.TotalFilesProcessed) } diff --git a/proto/v1/config.proto b/proto/v1/config.proto index 60f9d5a..5fbadb2 100644 --- a/proto/v1/config.proto +++ b/proto/v1/config.proto @@ -5,6 +5,7 @@ package v1; option go_package = "github.com/garethgeorge/backrest/gen/go/v1"; import "google/protobuf/empty.proto"; +import "v1/crypto.proto"; message HubConfig { repeated InstanceInfo instances = 1 [json_name="instances"]; @@ -29,11 +30,27 @@ message Config { repeated Repo repos = 3 [json_name="repos"]; repeated Plan plans = 4 [json_name="plans"]; Auth auth = 5 [json_name="auth"]; + Multihost multihost = 7 [json_name="sync"]; +} + +message Multihost { + repeated Peer known_hosts = 1 [json_name="knownHosts"]; + repeated Peer authorized_clients = 2 [json_name="authorizedClients"]; + + message Peer { + string instance_id = 1 [json_name="instanceId"]; // instance ID of the peer. + PublicKey public_key = 3 [json_name="publicKey"]; // public key of the peer. If changed, the peer must re-verify the public key. + bool public_key_verified = 4 [json_name="publicKeyVerified"]; // whether the public key is verified. This must be set for a host to authenticate a client. Clients implicitly validate the first key they see on initial connection. + + // Known host only fields + string instance_url = 2 [json_name="instanceUrl"]; // instance URL, required for a known host. Otherwise meaningless. + } } message Repo { string id = 1 [json_name="id"]; // unique but human readable ID for this repo. - string uri = 2 [json_name="uri"]; // restic repo URI + string uri = 2 [json_name="uri"]; // URI of the repo. + string guid = 11 [json_name="guid"]; // a globally unique ID for this repo. Should be derived as the 'id' field in `restic cat config --json`. string password = 3 [json_name="password"]; // plaintext password repeated string env = 4 [json_name="env"]; // extra environment variables to set for restic. repeated string flags = 5 [json_name="flags"]; // extra flags set on the restic command. @@ -42,6 +59,7 @@ message Repo { repeated Hook hooks = 7 [json_name="hooks"]; // hooks to run on events for this repo. bool auto_unlock = 8 [json_name="autoUnlock"]; // automatically unlock the repo when needed. CommandPrefix command_prefix = 10 [json_name="commandPrefix"]; // modifiers for the restic commands + repeated string allowed_peer_instance_ids = 100 [json_name="allowedPeers"]; // list of peer instance IDs allowed to access this repo. } message Plan { diff --git a/proto/v1/crypto.proto b/proto/v1/crypto.proto new file mode 100644 index 0000000..2ab61d3 --- /dev/null +++ b/proto/v1/crypto.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package v1; + +option go_package = "github.com/garethgeorge/backrest/gen/go/v1"; + +message SignedMessage { + string keyid = 1; // a unique identifier generated as the SHA256 of the public key used to sign the message. + bytes payload = 2; // the payload + bytes signature = 3; // the signature of the payload +} + +message EncryptedMessage { + bytes payload = 1; +} + +message PublicKey { + string keyid = 1 [json_name="keyid"]; // a unique identifier generated as the SHA256 of the public key. + string ed25519 = 2 [json_name="ed25519pub"]; // base64 encoded public key +} + +message PrivateKey { + string keyid = 1 [json_name="keyid"]; // a unique identifier generated as the SHA256 of the public key. + string ed25519 = 2 [json_name="ed25519priv"]; // base64 encoded private key +} diff --git a/proto/v1/hub.proto b/proto/v1/hub.proto deleted file mode 100644 index d5f8d5c..0000000 --- a/proto/v1/hub.proto +++ /dev/null @@ -1,25 +0,0 @@ -syntax = "proto3"; - -package v1; - -option go_package = "github.com/garethgeorge/backrest/gen/go/v1"; - -import "v1/config.proto"; -import "v1/restic.proto"; -import "v1/operations.proto"; -import "types/value.proto"; -import "google/protobuf/empty.proto"; -import "google/api/annotations.proto"; - -service Hub { - // GetInstances returns a list of all instances - rpc GetInstances(google.protobuf.Empty) returns (GetInstancesResponse) {} -} - -message GetInstancesResponse { - repeated Instance instances = 1; -} - -message Instance { - string id = 1; -} \ No newline at end of file diff --git a/proto/v1/operations.proto b/proto/v1/operations.proto index ef7a2bc..1a9dda9 100644 --- a/proto/v1/operations.proto +++ b/proto/v1/operations.proto @@ -15,11 +15,20 @@ message OperationList { message Operation { // required, primary ID of the operation. ID is sequential based on creation time of the operation. int64 id = 1; + int64 original_id = 13; + // modno increments with each change to the operation. This supports easy diffing. + int64 modno = 12; // flow id groups operations together, e.g. by an execution of a plan. - int64 flow_id = 10; // optional, flow id if associated with a flow - string repo_id = 2; + // must be unique within the context of a repo. + int64 flow_id = 10; + int64 original_flow_id = 14; + // repo id is a string identifier for the repo, and repo_guid is the globally unique ID of the repo. + string repo_id = 2; + string repo_guid = 15; + // plan id e.g. a scheduled set of operations (or system) that created this operation. string plan_id = 3; - string instance_id = 11; + // instance ID that created the operation + string instance_id = 11; // optional snapshot id if associated with a snapshot. string snapshot_id = 8; OperationStatus status = 4; diff --git a/proto/v1/service.proto b/proto/v1/service.proto index f2063f3..e45f32b 100644 --- a/proto/v1/service.proto +++ b/proto/v1/service.proto @@ -65,10 +65,11 @@ service Backrest { // OpSelector is a message that can be used to select operations e.g. by query. message OpSelector { repeated int64 ids = 1; - string repo_id = 2; - string plan_id = 3; - string snapshot_id = 4; - int64 flow_id = 5; + optional string instance_id = 6; + optional string repo_guid = 7; + optional string plan_id = 3; + optional string snapshot_id = 4; + optional int64 flow_id = 5; } message DoRepoTaskRequest { @@ -176,4 +177,4 @@ message SummaryDashboardResponse { repeated OperationStatus status = 4; repeated int64 bytes_added = 5; } -} \ No newline at end of file +} diff --git a/proto/v1/syncservice.proto b/proto/v1/syncservice.proto new file mode 100644 index 0000000..0f08a74 --- /dev/null +++ b/proto/v1/syncservice.proto @@ -0,0 +1,113 @@ +syntax = "proto3"; + +package v1; + +option go_package = "github.com/garethgeorge/backrest/gen/go/v1"; + +import "v1/config.proto"; +import "v1/crypto.proto"; +import "v1/restic.proto"; +import "v1/service.proto"; +import "v1/operations.proto"; +import "types/value.proto"; +import "google/protobuf/empty.proto"; +import "google/api/annotations.proto"; + + +service BackrestSyncService { + rpc Sync(stream SyncStreamItem) returns (stream SyncStreamItem) {} + rpc GetRemoteRepos(google.protobuf.Empty) returns (GetRemoteReposResponse) {} +} + +message GetRemoteReposResponse { + message RemoteRepoMetadata { + string instance_id = 1; + string repo_id = 2; + } + + repeated RemoteRepoMetadata repos = 1; +} + +enum SyncConnectionState { + CONNECTION_STATE_UNKNOWN = 0; + CONNECTION_STATE_PENDING = 1; + CONNECTION_STATE_CONNECTED = 2; + CONNECTION_STATE_DISCONNECTED = 3; + CONNECTION_STATE_RETRY_WAIT = 4; + CONNECTION_STATE_ERROR_AUTH = 10; + CONNECTION_STATE_ERROR_PROTOCOL = 11; +} + +message SyncStreamItem { + oneof action { + SignedMessage signed_message = 1; + SyncActionHandshake handshake = 3; + + SyncActionDiffOperations diff_operations = 20; + SyncActionSendOperations send_operations = 21; + SyncActionSendConfig send_config = 22; + SyncEstablishSharedSecret establish_shared_secret = 23; + + SyncActionThrottle throttle = 1000; + } + + message SyncActionHandshake { + int64 protocol_version = 1; + PublicKey public_key = 2; + SignedMessage instance_id = 3; + } + + message SyncActionSendConfig { + RemoteConfig config = 1; + } + + message SyncActionConnectRepo { + string repo_id = 1; + } + + enum RepoConnectionState { + CONNECTION_STATE_UNKNOWN = 0; + CONNECTION_STATE_PENDING = 1; // queried, response not yet received. + CONNECTION_STATE_CONNECTED = 2; + CONNECTION_STATE_UNAUTHORIZED = 3; + CONNECTION_STATE_NOT_FOUND = 4; + } + + message SyncActionDiffOperations { + // Client connects and sends a list of "have_operations" that exist in its log. + // have_operation_ids and have_operation_modnos are the operation IDs and modnos that the client has when zip'd pairwise. + OpSelector have_operations_selector = 1; + repeated int64 have_operation_ids = 2; + repeated int64 have_operation_modnos = 3; + // Server sends a list of "request_operations" for any operations that it doesn't have. + repeated int64 request_operations = 4; + } + + message SyncActionSendOperations { + OperationEvent event = 1; + } + + message SyncActionThrottle { + int64 delay_ms = 1; + } + + message SyncEstablishSharedSecret { + // a one-time-use ed25519 public key with a matching unshared private key. Used to perform a key exchange. + // See https://pkg.go.dev/crypto/ecdh#PrivateKey.ECDH . + string ed25519 = 2 [json_name="ed25519pub"]; // base64 encoded public key + } +} + +// RemoteConfig contains shareable properties from a remote backrest instance. +message RemoteConfig { + repeated RemoteRepo repos = 1; +} + +message RemoteRepo { + string id = 1; + string guid = 11; + string uri = 2; + string password = 3; + repeated string env = 4; + repeated string flags = 5; +} diff --git a/scripts/testing/ramdisk-mount.sh b/scripts/testing/ramdisk-mount.sh index 978ae42..41b0b6b 100644 --- a/scripts/testing/ramdisk-mount.sh +++ b/scripts/testing/ramdisk-mount.sh @@ -21,14 +21,14 @@ if [ "$(uname)" = "Darwin" ]; then sudo diskutil erasevolume HFS+ RAM_Disk_512MB $(hdiutil attach -nomount ram://1048576) fi export TMPDIR="/Volumes/RAM_Disk_512MB" - export XDG_CACHE_HOME="$TEMPDIR/.cache" + export RESTIC_CACHE_DIR="$TMPDIR/.cache" echo "Created 512MB RAM disk at /Volumes/RAM_Disk_512MB" echo "TMPDIR=$TMPDIR" - echo "XDG_CACHE_HOME=$XDG_CACHE_HOME" + echo "RESTIC_CACHE_DIR=$RESTIC_CACHE_DIR" elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then # Create ramdisk sudo mkdir -p /mnt/ramdisk sudo mount -t tmpfs -o size=512M tmpfs /mnt/ramdisk export TMPDIR="/mnt/ramdisk" - export XDG_CACHE_HOME="$TEMPDIR/.cache" + export RESTIC_CACHE_DIR="$TMPDIR/.cache" fi diff --git a/scripts/testing/run-fresh.sh b/scripts/testing/run-fresh.sh old mode 100755 new mode 100644 diff --git a/webui/gen/ts/v1/config_pb.ts b/webui/gen/ts/v1/config_pb.ts index 61f2c12..bdafe7f 100644 --- a/webui/gen/ts/v1/config_pb.ts +++ b/webui/gen/ts/v1/config_pb.ts @@ -5,13 +5,15 @@ import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; import { file_google_protobuf_empty } from "@bufbuild/protobuf/wkt"; +import type { PublicKey } from "./crypto_pb"; +import { file_v1_crypto } from "./crypto_pb"; import type { Message } from "@bufbuild/protobuf"; /** * Describes the file v1/config.proto. */ export const file_v1_config: GenFile = /*@__PURE__*/ - fileDesc("Cg92MS9jb25maWcucHJvdG8SAnYxImYKCUh1YkNvbmZpZxItCglpbnN0YW5jZXMYASADKAsyGi52MS5IdWJDb25maWcuSW5zdGFuY2VJbmZvGioKDEluc3RhbmNlSW5mbxIKCgJpZBgBIAEoCRIOCgZzZWNyZXQYAiABKAkihAEKBkNvbmZpZxINCgVtb2RubxgBIAEoBRIPCgd2ZXJzaW9uGAYgASgFEhAKCGluc3RhbmNlGAIgASgJEhcKBXJlcG9zGAMgAygLMggudjEuUmVwbxIXCgVwbGFucxgEIAMoCzIILnYxLlBsYW4SFgoEYXV0aBgFIAEoCzIILnYxLkF1dGgi9AEKBFJlcG8SCgoCaWQYASABKAkSCwoDdXJpGAIgASgJEhAKCHBhc3N3b3JkGAMgASgJEgsKA2VudhgEIAMoCRINCgVmbGFncxgFIAMoCRIlCgxwcnVuZV9wb2xpY3kYBiABKAsyDy52MS5QcnVuZVBvbGljeRIlCgxjaGVja19wb2xpY3kYCSABKAsyDy52MS5DaGVja1BvbGljeRIXCgVob29rcxgHIAMoCzIILnYxLkhvb2sSEwoLYXV0b191bmxvY2sYCCABKAgSKQoOY29tbWFuZF9wcmVmaXgYCiABKAsyES52MS5Db21tYW5kUHJlZml4IoYCCgRQbGFuEgoKAmlkGAEgASgJEgwKBHJlcG8YAiABKAkSDQoFcGF0aHMYBCADKAkSEAoIZXhjbHVkZXMYBSADKAkSEQoJaWV4Y2x1ZGVzGAkgAygJEh4KCHNjaGVkdWxlGAwgASgLMgwudjEuU2NoZWR1bGUSJgoJcmV0ZW50aW9uGAcgASgLMhMudjEuUmV0ZW50aW9uUG9saWN5EhcKBWhvb2tzGAggAygLMggudjEuSG9vaxIiCgxiYWNrdXBfZmxhZ3MYCiADKAlSDGJhY2t1cF9mbGFncxIZChFza2lwX2lmX3VuY2hhbmdlZBgNIAEoCEoECAMQBEoECAYQB0oECAsQDCKKAgoNQ29tbWFuZFByZWZpeBIuCgdpb19uaWNlGAEgASgOMh0udjEuQ29tbWFuZFByZWZpeC5JT05pY2VMZXZlbBIwCghjcHVfbmljZRgCIAEoDjIeLnYxLkNvbW1hbmRQcmVmaXguQ1BVTmljZUxldmVsIlsKC0lPTmljZUxldmVsEg4KCklPX0RFRkFVTFQQABIWChJJT19CRVNUX0VGRk9SVF9MT1cQARIXChNJT19CRVNUX0VGRk9SVF9ISUdIEAISCwoHSU9fSURMRRADIjoKDENQVU5pY2VMZXZlbBIPCgtDUFVfREVGQVVMVBAAEgwKCENQVV9ISUdIEAESCwoHQ1BVX0xPVxACIoICCg9SZXRlbnRpb25Qb2xpY3kSHAoScG9saWN5X2tlZXBfbGFzdF9uGAogASgFSAASRgoUcG9saWN5X3RpbWVfYnVja2V0ZWQYCyABKAsyJi52MS5SZXRlbnRpb25Qb2xpY3kuVGltZUJ1Y2tldGVkQ291bnRzSAASGQoPcG9saWN5X2tlZXBfYWxsGAwgASgISAAaZAoSVGltZUJ1Y2tldGVkQ291bnRzEg4KBmhvdXJseRgBIAEoBRINCgVkYWlseRgCIAEoBRIOCgZ3ZWVrbHkYAyABKAUSDwoHbW9udGhseRgEIAEoBRIOCgZ5ZWFybHkYBSABKAVCCAoGcG9saWN5ImMKC1BydW5lUG9saWN5Eh4KCHNjaGVkdWxlGAIgASgLMgwudjEuU2NoZWR1bGUSGAoQbWF4X3VudXNlZF9ieXRlcxgDIAEoAxIaChJtYXhfdW51c2VkX3BlcmNlbnQYBCABKAEicwoLQ2hlY2tQb2xpY3kSHgoIc2NoZWR1bGUYASABKAsyDC52MS5TY2hlZHVsZRIYCg5zdHJ1Y3R1cmVfb25seRhkIAEoCEgAEiIKGHJlYWRfZGF0YV9zdWJzZXRfcGVyY2VudBhlIAEoAUgAQgYKBG1vZGUi6wEKCFNjaGVkdWxlEhIKCGRpc2FibGVkGAEgASgISAASDgoEY3JvbhgCIAEoCUgAEhoKEG1heEZyZXF1ZW5jeURheXMYAyABKAVIABIbChFtYXhGcmVxdWVuY3lIb3VycxgEIAEoBUgAEiEKBWNsb2NrGAUgASgOMhIudjEuU2NoZWR1bGUuQ2xvY2siUwoFQ2xvY2sSEQoNQ0xPQ0tfREVGQVVMVBAAEg8KC0NMT0NLX0xPQ0FMEAESDQoJQ0xPQ0tfVVRDEAISFwoTQ0xPQ0tfTEFTVF9SVU5fVElNRRADQgoKCHNjaGVkdWxlIqULCgRIb29rEiYKCmNvbmRpdGlvbnMYASADKA4yEi52MS5Ib29rLkNvbmRpdGlvbhIiCghvbl9lcnJvchgCIAEoDjIQLnYxLkhvb2suT25FcnJvchIqCg5hY3Rpb25fY29tbWFuZBhkIAEoCzIQLnYxLkhvb2suQ29tbWFuZEgAEioKDmFjdGlvbl93ZWJob29rGGUgASgLMhAudjEuSG9vay5XZWJob29rSAASKgoOYWN0aW9uX2Rpc2NvcmQYZiABKAsyEC52MS5Ib29rLkRpc2NvcmRIABIoCg1hY3Rpb25fZ290aWZ5GGcgASgLMg8udjEuSG9vay5Hb3RpZnlIABImCgxhY3Rpb25fc2xhY2sYaCABKAsyDi52MS5Ib29rLlNsYWNrSAASLAoPYWN0aW9uX3Nob3V0cnJyGGkgASgLMhEudjEuSG9vay5TaG91dHJyckgAEjQKE2FjdGlvbl9oZWFsdGhjaGVja3MYaiABKAsyFS52MS5Ib29rLkhlYWx0aGNoZWNrc0gAGhoKB0NvbW1hbmQSDwoHY29tbWFuZBgBIAEoCRqDAQoHV2ViaG9vaxITCgt3ZWJob29rX3VybBgBIAEoCRInCgZtZXRob2QYAiABKA4yFy52MS5Ib29rLldlYmhvb2suTWV0aG9kEhAKCHRlbXBsYXRlGGQgASgJIigKBk1ldGhvZBILCgdVTktOT1dOEAASBwoDR0VUEAESCAoEUE9TVBACGjAKB0Rpc2NvcmQSEwoLd2ViaG9va191cmwYASABKAkSEAoIdGVtcGxhdGUYAiABKAkaUwoGR290aWZ5EhAKCGJhc2VfdXJsGAEgASgJEg0KBXRva2VuGAMgASgJEhAKCHRlbXBsYXRlGGQgASgJEhYKDnRpdGxlX3RlbXBsYXRlGGUgASgJGi4KBVNsYWNrEhMKC3dlYmhvb2tfdXJsGAEgASgJEhAKCHRlbXBsYXRlGAIgASgJGjIKCFNob3V0cnJyEhQKDHNob3V0cnJyX3VybBgBIAEoCRIQCgh0ZW1wbGF0ZRgCIAEoCRo1CgxIZWFsdGhjaGVja3MSEwoLd2ViaG9va191cmwYASABKAkSEAoIdGVtcGxhdGUYAiABKAkinAMKCUNvbmRpdGlvbhIVChFDT05ESVRJT05fVU5LTk9XThAAEhcKE0NPTkRJVElPTl9BTllfRVJST1IQARIcChhDT05ESVRJT05fU05BUFNIT1RfU1RBUlQQAhIaChZDT05ESVRJT05fU05BUFNIT1RfRU5EEAMSHAoYQ09ORElUSU9OX1NOQVBTSE9UX0VSUk9SEAQSHgoaQ09ORElUSU9OX1NOQVBTSE9UX1dBUk5JTkcQBRIeChpDT05ESVRJT05fU05BUFNIT1RfU1VDQ0VTUxAGEh4KGkNPTkRJVElPTl9TTkFQU0hPVF9TS0lQUEVEEAcSGQoVQ09ORElUSU9OX1BSVU5FX1NUQVJUEGQSGQoVQ09ORElUSU9OX1BSVU5FX0VSUk9SEGUSGwoXQ09ORElUSU9OX1BSVU5FX1NVQ0NFU1MQZhIaChVDT05ESVRJT05fQ0hFQ0tfU1RBUlQQyAESGgoVQ09ORElUSU9OX0NIRUNLX0VSUk9SEMkBEhwKF0NPTkRJVElPTl9DSEVDS19TVUNDRVNTEMoBIqkBCgdPbkVycm9yEhMKD09OX0VSUk9SX0lHTk9SRRAAEhMKD09OX0VSUk9SX0NBTkNFTBABEhIKDk9OX0VSUk9SX0ZBVEFMEAISGgoWT05fRVJST1JfUkVUUllfMU1JTlVURRBkEhwKGE9OX0VSUk9SX1JFVFJZXzEwTUlOVVRFUxBlEiYKIk9OX0VSUk9SX1JFVFJZX0VYUE9ORU5USUFMX0JBQ0tPRkYQZ0IICgZhY3Rpb24iMQoEQXV0aBIQCghkaXNhYmxlZBgBIAEoCBIXCgV1c2VycxgCIAMoCzIILnYxLlVzZXIiOwoEVXNlchIMCgRuYW1lGAEgASgJEhkKD3Bhc3N3b3JkX2JjcnlwdBgCIAEoCUgAQgoKCHBhc3N3b3JkQixaKmdpdGh1Yi5jb20vZ2FyZXRoZ2VvcmdlL2JhY2tyZXN0L2dlbi9nby92MWIGcHJvdG8z", [file_google_protobuf_empty]); + fileDesc("Cg92MS9jb25maWcucHJvdG8SAnYxImYKCUh1YkNvbmZpZxItCglpbnN0YW5jZXMYASADKAsyGi52MS5IdWJDb25maWcuSW5zdGFuY2VJbmZvGioKDEluc3RhbmNlSW5mbxIKCgJpZBgBIAEoCRIOCgZzZWNyZXQYAiABKAkirAEKBkNvbmZpZxINCgVtb2RubxgBIAEoBRIPCgd2ZXJzaW9uGAYgASgFEhAKCGluc3RhbmNlGAIgASgJEhcKBXJlcG9zGAMgAygLMggudjEuUmVwbxIXCgVwbGFucxgEIAMoCzIILnYxLlBsYW4SFgoEYXV0aBgFIAEoCzIILnYxLkF1dGgSJgoJbXVsdGlob3N0GAcgASgLMg0udjEuTXVsdGlob3N0UgRzeW5jItcBCglNdWx0aWhvc3QSJwoLa25vd25faG9zdHMYASADKAsyEi52MS5NdWx0aWhvc3QuUGVlchIuChJhdXRob3JpemVkX2NsaWVudHMYAiADKAsyEi52MS5NdWx0aWhvc3QuUGVlchpxCgRQZWVyEhMKC2luc3RhbmNlX2lkGAEgASgJEiEKCnB1YmxpY19rZXkYAyABKAsyDS52MS5QdWJsaWNLZXkSGwoTcHVibGljX2tleV92ZXJpZmllZBgEIAEoCBIUCgxpbnN0YW5jZV91cmwYAiABKAkiswIKBFJlcG8SCgoCaWQYASABKAkSCwoDdXJpGAIgASgJEgwKBGd1aWQYCyABKAkSEAoIcGFzc3dvcmQYAyABKAkSCwoDZW52GAQgAygJEg0KBWZsYWdzGAUgAygJEiUKDHBydW5lX3BvbGljeRgGIAEoCzIPLnYxLlBydW5lUG9saWN5EiUKDGNoZWNrX3BvbGljeRgJIAEoCzIPLnYxLkNoZWNrUG9saWN5EhcKBWhvb2tzGAcgAygLMggudjEuSG9vaxITCgthdXRvX3VubG9jaxgIIAEoCBIpCg5jb21tYW5kX3ByZWZpeBgKIAEoCzIRLnYxLkNvbW1hbmRQcmVmaXgSLwoZYWxsb3dlZF9wZWVyX2luc3RhbmNlX2lkcxhkIAMoCVIMYWxsb3dlZFBlZXJzIoYCCgRQbGFuEgoKAmlkGAEgASgJEgwKBHJlcG8YAiABKAkSDQoFcGF0aHMYBCADKAkSEAoIZXhjbHVkZXMYBSADKAkSEQoJaWV4Y2x1ZGVzGAkgAygJEh4KCHNjaGVkdWxlGAwgASgLMgwudjEuU2NoZWR1bGUSJgoJcmV0ZW50aW9uGAcgASgLMhMudjEuUmV0ZW50aW9uUG9saWN5EhcKBWhvb2tzGAggAygLMggudjEuSG9vaxIiCgxiYWNrdXBfZmxhZ3MYCiADKAlSDGJhY2t1cF9mbGFncxIZChFza2lwX2lmX3VuY2hhbmdlZBgNIAEoCEoECAMQBEoECAYQB0oECAsQDCKKAgoNQ29tbWFuZFByZWZpeBIuCgdpb19uaWNlGAEgASgOMh0udjEuQ29tbWFuZFByZWZpeC5JT05pY2VMZXZlbBIwCghjcHVfbmljZRgCIAEoDjIeLnYxLkNvbW1hbmRQcmVmaXguQ1BVTmljZUxldmVsIlsKC0lPTmljZUxldmVsEg4KCklPX0RFRkFVTFQQABIWChJJT19CRVNUX0VGRk9SVF9MT1cQARIXChNJT19CRVNUX0VGRk9SVF9ISUdIEAISCwoHSU9fSURMRRADIjoKDENQVU5pY2VMZXZlbBIPCgtDUFVfREVGQVVMVBAAEgwKCENQVV9ISUdIEAESCwoHQ1BVX0xPVxACIoICCg9SZXRlbnRpb25Qb2xpY3kSHAoScG9saWN5X2tlZXBfbGFzdF9uGAogASgFSAASRgoUcG9saWN5X3RpbWVfYnVja2V0ZWQYCyABKAsyJi52MS5SZXRlbnRpb25Qb2xpY3kuVGltZUJ1Y2tldGVkQ291bnRzSAASGQoPcG9saWN5X2tlZXBfYWxsGAwgASgISAAaZAoSVGltZUJ1Y2tldGVkQ291bnRzEg4KBmhvdXJseRgBIAEoBRINCgVkYWlseRgCIAEoBRIOCgZ3ZWVrbHkYAyABKAUSDwoHbW9udGhseRgEIAEoBRIOCgZ5ZWFybHkYBSABKAVCCAoGcG9saWN5ImMKC1BydW5lUG9saWN5Eh4KCHNjaGVkdWxlGAIgASgLMgwudjEuU2NoZWR1bGUSGAoQbWF4X3VudXNlZF9ieXRlcxgDIAEoAxIaChJtYXhfdW51c2VkX3BlcmNlbnQYBCABKAEicwoLQ2hlY2tQb2xpY3kSHgoIc2NoZWR1bGUYASABKAsyDC52MS5TY2hlZHVsZRIYCg5zdHJ1Y3R1cmVfb25seRhkIAEoCEgAEiIKGHJlYWRfZGF0YV9zdWJzZXRfcGVyY2VudBhlIAEoAUgAQgYKBG1vZGUi6wEKCFNjaGVkdWxlEhIKCGRpc2FibGVkGAEgASgISAASDgoEY3JvbhgCIAEoCUgAEhoKEG1heEZyZXF1ZW5jeURheXMYAyABKAVIABIbChFtYXhGcmVxdWVuY3lIb3VycxgEIAEoBUgAEiEKBWNsb2NrGAUgASgOMhIudjEuU2NoZWR1bGUuQ2xvY2siUwoFQ2xvY2sSEQoNQ0xPQ0tfREVGQVVMVBAAEg8KC0NMT0NLX0xPQ0FMEAESDQoJQ0xPQ0tfVVRDEAISFwoTQ0xPQ0tfTEFTVF9SVU5fVElNRRADQgoKCHNjaGVkdWxlIqULCgRIb29rEiYKCmNvbmRpdGlvbnMYASADKA4yEi52MS5Ib29rLkNvbmRpdGlvbhIiCghvbl9lcnJvchgCIAEoDjIQLnYxLkhvb2suT25FcnJvchIqCg5hY3Rpb25fY29tbWFuZBhkIAEoCzIQLnYxLkhvb2suQ29tbWFuZEgAEioKDmFjdGlvbl93ZWJob29rGGUgASgLMhAudjEuSG9vay5XZWJob29rSAASKgoOYWN0aW9uX2Rpc2NvcmQYZiABKAsyEC52MS5Ib29rLkRpc2NvcmRIABIoCg1hY3Rpb25fZ290aWZ5GGcgASgLMg8udjEuSG9vay5Hb3RpZnlIABImCgxhY3Rpb25fc2xhY2sYaCABKAsyDi52MS5Ib29rLlNsYWNrSAASLAoPYWN0aW9uX3Nob3V0cnJyGGkgASgLMhEudjEuSG9vay5TaG91dHJyckgAEjQKE2FjdGlvbl9oZWFsdGhjaGVja3MYaiABKAsyFS52MS5Ib29rLkhlYWx0aGNoZWNrc0gAGhoKB0NvbW1hbmQSDwoHY29tbWFuZBgBIAEoCRqDAQoHV2ViaG9vaxITCgt3ZWJob29rX3VybBgBIAEoCRInCgZtZXRob2QYAiABKA4yFy52MS5Ib29rLldlYmhvb2suTWV0aG9kEhAKCHRlbXBsYXRlGGQgASgJIigKBk1ldGhvZBILCgdVTktOT1dOEAASBwoDR0VUEAESCAoEUE9TVBACGjAKB0Rpc2NvcmQSEwoLd2ViaG9va191cmwYASABKAkSEAoIdGVtcGxhdGUYAiABKAkaUwoGR290aWZ5EhAKCGJhc2VfdXJsGAEgASgJEg0KBXRva2VuGAMgASgJEhAKCHRlbXBsYXRlGGQgASgJEhYKDnRpdGxlX3RlbXBsYXRlGGUgASgJGi4KBVNsYWNrEhMKC3dlYmhvb2tfdXJsGAEgASgJEhAKCHRlbXBsYXRlGAIgASgJGjIKCFNob3V0cnJyEhQKDHNob3V0cnJyX3VybBgBIAEoCRIQCgh0ZW1wbGF0ZRgCIAEoCRo1CgxIZWFsdGhjaGVja3MSEwoLd2ViaG9va191cmwYASABKAkSEAoIdGVtcGxhdGUYAiABKAkinAMKCUNvbmRpdGlvbhIVChFDT05ESVRJT05fVU5LTk9XThAAEhcKE0NPTkRJVElPTl9BTllfRVJST1IQARIcChhDT05ESVRJT05fU05BUFNIT1RfU1RBUlQQAhIaChZDT05ESVRJT05fU05BUFNIT1RfRU5EEAMSHAoYQ09ORElUSU9OX1NOQVBTSE9UX0VSUk9SEAQSHgoaQ09ORElUSU9OX1NOQVBTSE9UX1dBUk5JTkcQBRIeChpDT05ESVRJT05fU05BUFNIT1RfU1VDQ0VTUxAGEh4KGkNPTkRJVElPTl9TTkFQU0hPVF9TS0lQUEVEEAcSGQoVQ09ORElUSU9OX1BSVU5FX1NUQVJUEGQSGQoVQ09ORElUSU9OX1BSVU5FX0VSUk9SEGUSGwoXQ09ORElUSU9OX1BSVU5FX1NVQ0NFU1MQZhIaChVDT05ESVRJT05fQ0hFQ0tfU1RBUlQQyAESGgoVQ09ORElUSU9OX0NIRUNLX0VSUk9SEMkBEhwKF0NPTkRJVElPTl9DSEVDS19TVUNDRVNTEMoBIqkBCgdPbkVycm9yEhMKD09OX0VSUk9SX0lHTk9SRRAAEhMKD09OX0VSUk9SX0NBTkNFTBABEhIKDk9OX0VSUk9SX0ZBVEFMEAISGgoWT05fRVJST1JfUkVUUllfMU1JTlVURRBkEhwKGE9OX0VSUk9SX1JFVFJZXzEwTUlOVVRFUxBlEiYKIk9OX0VSUk9SX1JFVFJZX0VYUE9ORU5USUFMX0JBQ0tPRkYQZ0IICgZhY3Rpb24iMQoEQXV0aBIQCghkaXNhYmxlZBgBIAEoCBIXCgV1c2VycxgCIAMoCzIILnYxLlVzZXIiOwoEVXNlchIMCgRuYW1lGAEgASgJEhkKD3Bhc3N3b3JkX2JjcnlwdBgCIAEoCUgAQgoKCHBhc3N3b3JkQixaKmdpdGh1Yi5jb20vZ2FyZXRoZ2VvcmdlL2JhY2tyZXN0L2dlbi9nby92MWIGcHJvdG8z", [file_google_protobuf_empty, file_v1_crypto]); /** * @generated from message v1.HubConfig @@ -96,6 +98,11 @@ export type Config = Message<"v1.Config"> & { * @generated from field: v1.Auth auth = 5; */ auth?: Auth; + + /** + * @generated from field: v1.Multihost multihost = 7 [json_name = "sync"]; + */ + multihost?: Multihost; }; /** @@ -105,6 +112,70 @@ export type Config = Message<"v1.Config"> & { export const ConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_v1_config, 1); +/** + * @generated from message v1.Multihost + */ +export type Multihost = Message<"v1.Multihost"> & { + /** + * @generated from field: repeated v1.Multihost.Peer known_hosts = 1; + */ + knownHosts: Multihost_Peer[]; + + /** + * @generated from field: repeated v1.Multihost.Peer authorized_clients = 2; + */ + authorizedClients: Multihost_Peer[]; +}; + +/** + * Describes the message v1.Multihost. + * Use `create(MultihostSchema)` to create a new message. + */ +export const MultihostSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_config, 2); + +/** + * @generated from message v1.Multihost.Peer + */ +export type Multihost_Peer = Message<"v1.Multihost.Peer"> & { + /** + * instance ID of the peer. + * + * @generated from field: string instance_id = 1; + */ + instanceId: string; + + /** + * public key of the peer. If changed, the peer must re-verify the public key. + * + * @generated from field: v1.PublicKey public_key = 3; + */ + publicKey?: PublicKey; + + /** + * whether the public key is verified. This must be set for a host to authenticate a client. Clients implicitly validate the first key they see on initial connection. + * + * @generated from field: bool public_key_verified = 4; + */ + publicKeyVerified: boolean; + + /** + * Known host only fields + * + * instance URL, required for a known host. Otherwise meaningless. + * + * @generated from field: string instance_url = 2; + */ + instanceUrl: string; +}; + +/** + * Describes the message v1.Multihost.Peer. + * Use `create(Multihost_PeerSchema)` to create a new message. + */ +export const Multihost_PeerSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_config, 2, 0); + /** * @generated from message v1.Repo */ @@ -117,12 +188,19 @@ export type Repo = Message<"v1.Repo"> & { id: string; /** - * restic repo URI + * URI of the repo. * * @generated from field: string uri = 2; */ uri: string; + /** + * a globally unique ID for this repo. Should be derived as the 'id' field in `restic cat config --json`. + * + * @generated from field: string guid = 11; + */ + guid: string; + /** * plaintext password * @@ -178,6 +256,13 @@ export type Repo = Message<"v1.Repo"> & { * @generated from field: v1.CommandPrefix command_prefix = 10; */ commandPrefix?: CommandPrefix; + + /** + * list of peer instance IDs allowed to access this repo. + * + * @generated from field: repeated string allowed_peer_instance_ids = 100 [json_name = "allowedPeers"]; + */ + allowedPeerInstanceIds: string[]; }; /** @@ -185,7 +270,7 @@ export type Repo = Message<"v1.Repo"> & { * Use `create(RepoSchema)` to create a new message. */ export const RepoSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 2); + messageDesc(file_v1_config, 3); /** * @generated from message v1.Plan @@ -267,7 +352,7 @@ export type Plan = Message<"v1.Plan"> & { * Use `create(PlanSchema)` to create a new message. */ export const PlanSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 3); + messageDesc(file_v1_config, 4); /** * @generated from message v1.CommandPrefix @@ -293,7 +378,7 @@ export type CommandPrefix = Message<"v1.CommandPrefix"> & { * Use `create(CommandPrefixSchema)` to create a new message. */ export const CommandPrefixSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 4); + messageDesc(file_v1_config, 5); /** * @generated from enum v1.CommandPrefix.IONiceLevel @@ -324,7 +409,7 @@ export enum CommandPrefix_IONiceLevel { * Describes the enum v1.CommandPrefix.IONiceLevel. */ export const CommandPrefix_IONiceLevelSchema: GenEnum = /*@__PURE__*/ - enumDesc(file_v1_config, 4, 0); + enumDesc(file_v1_config, 5, 0); /** * @generated from enum v1.CommandPrefix.CPUNiceLevel @@ -350,7 +435,7 @@ export enum CommandPrefix_CPUNiceLevel { * Describes the enum v1.CommandPrefix.CPUNiceLevel. */ export const CommandPrefix_CPUNiceLevelSchema: GenEnum = /*@__PURE__*/ - enumDesc(file_v1_config, 4, 1); + enumDesc(file_v1_config, 5, 1); /** * @generated from message v1.RetentionPolicy @@ -385,7 +470,7 @@ export type RetentionPolicy = Message<"v1.RetentionPolicy"> & { * Use `create(RetentionPolicySchema)` to create a new message. */ export const RetentionPolicySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 5); + messageDesc(file_v1_config, 6); /** * @generated from message v1.RetentionPolicy.TimeBucketedCounts @@ -432,7 +517,7 @@ export type RetentionPolicy_TimeBucketedCounts = Message<"v1.RetentionPolicy.Tim * Use `create(RetentionPolicy_TimeBucketedCountsSchema)` to create a new message. */ export const RetentionPolicy_TimeBucketedCountsSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 5, 0); + messageDesc(file_v1_config, 6, 0); /** * @generated from message v1.PrunePolicy @@ -463,7 +548,7 @@ export type PrunePolicy = Message<"v1.PrunePolicy"> & { * Use `create(PrunePolicySchema)` to create a new message. */ export const PrunePolicySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 6); + messageDesc(file_v1_config, 7); /** * @generated from message v1.CheckPolicy @@ -501,7 +586,7 @@ export type CheckPolicy = Message<"v1.CheckPolicy"> & { * Use `create(CheckPolicySchema)` to create a new message. */ export const CheckPolicySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 7); + messageDesc(file_v1_config, 8); /** * @generated from message v1.Schedule @@ -557,7 +642,7 @@ export type Schedule = Message<"v1.Schedule"> & { * Use `create(ScheduleSchema)` to create a new message. */ export const ScheduleSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 8); + messageDesc(file_v1_config, 9); /** * @generated from enum v1.Schedule.Clock @@ -590,7 +675,7 @@ export enum Schedule_Clock { * Describes the enum v1.Schedule.Clock. */ export const Schedule_ClockSchema: GenEnum = /*@__PURE__*/ - enumDesc(file_v1_config, 8, 0); + enumDesc(file_v1_config, 9, 0); /** * @generated from message v1.Hook @@ -659,7 +744,7 @@ export type Hook = Message<"v1.Hook"> & { * Use `create(HookSchema)` to create a new message. */ export const HookSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 9); + messageDesc(file_v1_config, 10); /** * @generated from message v1.Hook.Command @@ -676,7 +761,7 @@ export type Hook_Command = Message<"v1.Hook.Command"> & { * Use `create(Hook_CommandSchema)` to create a new message. */ export const Hook_CommandSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 9, 0); + messageDesc(file_v1_config, 10, 0); /** * @generated from message v1.Hook.Webhook @@ -703,7 +788,7 @@ export type Hook_Webhook = Message<"v1.Hook.Webhook"> & { * Use `create(Hook_WebhookSchema)` to create a new message. */ export const Hook_WebhookSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 9, 1); + messageDesc(file_v1_config, 10, 1); /** * @generated from enum v1.Hook.Webhook.Method @@ -729,7 +814,7 @@ export enum Hook_Webhook_Method { * Describes the enum v1.Hook.Webhook.Method. */ export const Hook_Webhook_MethodSchema: GenEnum = /*@__PURE__*/ - enumDesc(file_v1_config, 9, 1, 0); + enumDesc(file_v1_config, 10, 1, 0); /** * @generated from message v1.Hook.Discord @@ -753,7 +838,7 @@ export type Hook_Discord = Message<"v1.Hook.Discord"> & { * Use `create(Hook_DiscordSchema)` to create a new message. */ export const Hook_DiscordSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 9, 2); + messageDesc(file_v1_config, 10, 2); /** * @generated from message v1.Hook.Gotify @@ -789,7 +874,7 @@ export type Hook_Gotify = Message<"v1.Hook.Gotify"> & { * Use `create(Hook_GotifySchema)` to create a new message. */ export const Hook_GotifySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 9, 3); + messageDesc(file_v1_config, 10, 3); /** * @generated from message v1.Hook.Slack @@ -813,7 +898,7 @@ export type Hook_Slack = Message<"v1.Hook.Slack"> & { * Use `create(Hook_SlackSchema)` to create a new message. */ export const Hook_SlackSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 9, 4); + messageDesc(file_v1_config, 10, 4); /** * @generated from message v1.Hook.Shoutrrr @@ -835,7 +920,7 @@ export type Hook_Shoutrrr = Message<"v1.Hook.Shoutrrr"> & { * Use `create(Hook_ShoutrrrSchema)` to create a new message. */ export const Hook_ShoutrrrSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 9, 5); + messageDesc(file_v1_config, 10, 5); /** * @generated from message v1.Hook.Healthchecks @@ -857,7 +942,7 @@ export type Hook_Healthchecks = Message<"v1.Hook.Healthchecks"> & { * Use `create(Hook_HealthchecksSchema)` to create a new message. */ export const Hook_HealthchecksSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 9, 6); + messageDesc(file_v1_config, 10, 6); /** * @generated from enum v1.Hook.Condition @@ -968,7 +1053,7 @@ export enum Hook_Condition { * Describes the enum v1.Hook.Condition. */ export const Hook_ConditionSchema: GenEnum = /*@__PURE__*/ - enumDesc(file_v1_config, 9, 0); + enumDesc(file_v1_config, 10, 0); /** * @generated from enum v1.Hook.OnError @@ -1019,7 +1104,7 @@ export enum Hook_OnError { * Describes the enum v1.Hook.OnError. */ export const Hook_OnErrorSchema: GenEnum = /*@__PURE__*/ - enumDesc(file_v1_config, 9, 1); + enumDesc(file_v1_config, 10, 1); /** * @generated from message v1.Auth @@ -1045,7 +1130,7 @@ export type Auth = Message<"v1.Auth"> & { * Use `create(AuthSchema)` to create a new message. */ export const AuthSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 10); + messageDesc(file_v1_config, 11); /** * @generated from message v1.User @@ -1073,5 +1158,5 @@ export type User = Message<"v1.User"> & { * Use `create(UserSchema)` to create a new message. */ export const UserSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_config, 11); + messageDesc(file_v1_config, 12); diff --git a/webui/gen/ts/v1/crypto_pb.ts b/webui/gen/ts/v1/crypto_pb.ts new file mode 100644 index 0000000..0ab3ff1 --- /dev/null +++ b/webui/gen/ts/v1/crypto_pb.ts @@ -0,0 +1,116 @@ +// @generated by protoc-gen-es v2.2.2 with parameter "target=ts" +// @generated from file v1/crypto.proto (package v1, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; +import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; +import type { Message } from "@bufbuild/protobuf"; + +/** + * Describes the file v1/crypto.proto. + */ +export const file_v1_crypto: GenFile = /*@__PURE__*/ + fileDesc("Cg92MS9jcnlwdG8ucHJvdG8SAnYxIkIKDVNpZ25lZE1lc3NhZ2USDQoFa2V5aWQYASABKAkSDwoHcGF5bG9hZBgCIAEoDBIRCglzaWduYXR1cmUYAyABKAwiIwoQRW5jcnlwdGVkTWVzc2FnZRIPCgdwYXlsb2FkGAEgASgMIjcKCVB1YmxpY0tleRINCgVrZXlpZBgBIAEoCRIbCgdlZDI1NTE5GAIgASgJUgplZDI1NTE5cHViIjkKClByaXZhdGVLZXkSDQoFa2V5aWQYASABKAkSHAoHZWQyNTUxORgCIAEoCVILZWQyNTUxOXByaXZCLFoqZ2l0aHViLmNvbS9nYXJldGhnZW9yZ2UvYmFja3Jlc3QvZ2VuL2dvL3YxYgZwcm90bzM"); + +/** + * @generated from message v1.SignedMessage + */ +export type SignedMessage = Message<"v1.SignedMessage"> & { + /** + * a unique identifier generated as the SHA256 of the public key used to sign the message. + * + * @generated from field: string keyid = 1; + */ + keyid: string; + + /** + * the payload + * + * @generated from field: bytes payload = 2; + */ + payload: Uint8Array; + + /** + * the signature of the payload + * + * @generated from field: bytes signature = 3; + */ + signature: Uint8Array; +}; + +/** + * Describes the message v1.SignedMessage. + * Use `create(SignedMessageSchema)` to create a new message. + */ +export const SignedMessageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_crypto, 0); + +/** + * @generated from message v1.EncryptedMessage + */ +export type EncryptedMessage = Message<"v1.EncryptedMessage"> & { + /** + * @generated from field: bytes payload = 1; + */ + payload: Uint8Array; +}; + +/** + * Describes the message v1.EncryptedMessage. + * Use `create(EncryptedMessageSchema)` to create a new message. + */ +export const EncryptedMessageSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_crypto, 1); + +/** + * @generated from message v1.PublicKey + */ +export type PublicKey = Message<"v1.PublicKey"> & { + /** + * a unique identifier generated as the SHA256 of the public key. + * + * @generated from field: string keyid = 1; + */ + keyid: string; + + /** + * base64 encoded public key + * + * @generated from field: string ed25519 = 2 [json_name = "ed25519pub"]; + */ + ed25519: string; +}; + +/** + * Describes the message v1.PublicKey. + * Use `create(PublicKeySchema)` to create a new message. + */ +export const PublicKeySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_crypto, 2); + +/** + * @generated from message v1.PrivateKey + */ +export type PrivateKey = Message<"v1.PrivateKey"> & { + /** + * a unique identifier generated as the SHA256 of the public key. + * + * @generated from field: string keyid = 1; + */ + keyid: string; + + /** + * base64 encoded private key + * + * @generated from field: string ed25519 = 2 [json_name = "ed25519priv"]; + */ + ed25519: string; +}; + +/** + * Describes the message v1.PrivateKey. + * Use `create(PrivateKeySchema)` to create a new message. + */ +export const PrivateKeySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_crypto, 3); + diff --git a/webui/gen/ts/v1/hub_pb.ts b/webui/gen/ts/v1/hub_pb.ts deleted file mode 100644 index 980a812..0000000 --- a/webui/gen/ts/v1/hub_pb.ts +++ /dev/null @@ -1,72 +0,0 @@ -// @generated by protoc-gen-es v2.2.2 with parameter "target=ts" -// @generated from file v1/hub.proto (package v1, syntax proto3) -/* eslint-disable */ - -import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv1"; -import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv1"; -import { file_v1_config } from "./config_pb"; -import { file_v1_restic } from "./restic_pb"; -import { file_v1_operations } from "./operations_pb"; -import { file_types_value } from "../types/value_pb"; -import type { EmptySchema } from "@bufbuild/protobuf/wkt"; -import { file_google_protobuf_empty } from "@bufbuild/protobuf/wkt"; -import { file_google_api_annotations } from "../google/api/annotations_pb"; -import type { Message } from "@bufbuild/protobuf"; - -/** - * Describes the file v1/hub.proto. - */ -export const file_v1_hub: GenFile = /*@__PURE__*/ - fileDesc("Cgx2MS9odWIucHJvdG8SAnYxIjcKFEdldEluc3RhbmNlc1Jlc3BvbnNlEh8KCWluc3RhbmNlcxgBIAMoCzIMLnYxLkluc3RhbmNlIhYKCEluc3RhbmNlEgoKAmlkGAEgASgJMkkKA0h1YhJCCgxHZXRJbnN0YW5jZXMSFi5nb29nbGUucHJvdG9idWYuRW1wdHkaGC52MS5HZXRJbnN0YW5jZXNSZXNwb25zZSIAQixaKmdpdGh1Yi5jb20vZ2FyZXRoZ2VvcmdlL2JhY2tyZXN0L2dlbi9nby92MWIGcHJvdG8z", [file_v1_config, file_v1_restic, file_v1_operations, file_types_value, file_google_protobuf_empty, file_google_api_annotations]); - -/** - * @generated from message v1.GetInstancesResponse - */ -export type GetInstancesResponse = Message<"v1.GetInstancesResponse"> & { - /** - * @generated from field: repeated v1.Instance instances = 1; - */ - instances: Instance[]; -}; - -/** - * Describes the message v1.GetInstancesResponse. - * Use `create(GetInstancesResponseSchema)` to create a new message. - */ -export const GetInstancesResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_hub, 0); - -/** - * @generated from message v1.Instance - */ -export type Instance = Message<"v1.Instance"> & { - /** - * @generated from field: string id = 1; - */ - id: string; -}; - -/** - * Describes the message v1.Instance. - * Use `create(InstanceSchema)` to create a new message. - */ -export const InstanceSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_v1_hub, 1); - -/** - * @generated from service v1.Hub - */ -export const Hub: GenService<{ - /** - * GetInstances returns a list of all instances - * - * @generated from rpc v1.Hub.GetInstances - */ - getInstances: { - methodKind: "unary"; - input: typeof EmptySchema; - output: typeof GetInstancesResponseSchema; - }, -}> = /*@__PURE__*/ - serviceDesc(file_v1_hub, 0); - diff --git a/webui/gen/ts/v1/operations_pb.ts b/webui/gen/ts/v1/operations_pb.ts index 5a9fcfb..6ca31f0 100644 --- a/webui/gen/ts/v1/operations_pb.ts +++ b/webui/gen/ts/v1/operations_pb.ts @@ -16,7 +16,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file v1/operations.proto. */ export const file_v1_operations: GenFile = /*@__PURE__*/ - fileDesc("ChN2MS9vcGVyYXRpb25zLnByb3RvEgJ2MSIyCg1PcGVyYXRpb25MaXN0EiEKCm9wZXJhdGlvbnMYASADKAsyDS52MS5PcGVyYXRpb24izgUKCU9wZXJhdGlvbhIKCgJpZBgBIAEoAxIPCgdmbG93X2lkGAogASgDEg8KB3JlcG9faWQYAiABKAkSDwoHcGxhbl9pZBgDIAEoCRITCgtpbnN0YW5jZV9pZBgLIAEoCRITCgtzbmFwc2hvdF9pZBgIIAEoCRIjCgZzdGF0dXMYBCABKA4yEy52MS5PcGVyYXRpb25TdGF0dXMSGgoSdW5peF90aW1lX3N0YXJ0X21zGAUgASgDEhgKEHVuaXhfdGltZV9lbmRfbXMYBiABKAMSFwoPZGlzcGxheV9tZXNzYWdlGAcgASgJEg4KBmxvZ3JlZhgJIAEoCRIvChBvcGVyYXRpb25fYmFja3VwGGQgASgLMhMudjEuT3BlcmF0aW9uQmFja3VwSAASPgoYb3BlcmF0aW9uX2luZGV4X3NuYXBzaG90GGUgASgLMhoudjEuT3BlcmF0aW9uSW5kZXhTbmFwc2hvdEgAEi8KEG9wZXJhdGlvbl9mb3JnZXQYZiABKAsyEy52MS5PcGVyYXRpb25Gb3JnZXRIABItCg9vcGVyYXRpb25fcHJ1bmUYZyABKAsyEi52MS5PcGVyYXRpb25QcnVuZUgAEjEKEW9wZXJhdGlvbl9yZXN0b3JlGGggASgLMhQudjEuT3BlcmF0aW9uUmVzdG9yZUgAEi0KD29wZXJhdGlvbl9zdGF0cxhpIAEoCzISLnYxLk9wZXJhdGlvblN0YXRzSAASMgoSb3BlcmF0aW9uX3J1bl9ob29rGGogASgLMhQudjEuT3BlcmF0aW9uUnVuSG9va0gAEi0KD29wZXJhdGlvbl9jaGVjaxhrIAEoCzISLnYxLk9wZXJhdGlvbkNoZWNrSAASOAoVb3BlcmF0aW9uX3J1bl9jb21tYW5kGGwgASgLMhcudjEuT3BlcmF0aW9uUnVuQ29tbWFuZEgAQgQKAm9wIs8BCg5PcGVyYXRpb25FdmVudBIiCgprZWVwX2FsaXZlGAEgASgLMgwudHlwZXMuRW1wdHlIABIvChJjcmVhdGVkX29wZXJhdGlvbnMYAiABKAsyES52MS5PcGVyYXRpb25MaXN0SAASLwoSdXBkYXRlZF9vcGVyYXRpb25zGAMgASgLMhEudjEuT3BlcmF0aW9uTGlzdEgAEi4KEmRlbGV0ZWRfb3BlcmF0aW9ucxgEIAEoCzIQLnR5cGVzLkludDY0TGlzdEgAQgcKBWV2ZW50ImgKD09wZXJhdGlvbkJhY2t1cBIsCgtsYXN0X3N0YXR1cxgDIAEoCzIXLnYxLkJhY2t1cFByb2dyZXNzRW50cnkSJwoGZXJyb3JzGAQgAygLMhcudjEuQmFja3VwUHJvZ3Jlc3NFcnJvciJOChZPcGVyYXRpb25JbmRleFNuYXBzaG90EiQKCHNuYXBzaG90GAIgASgLMhIudjEuUmVzdGljU25hcHNob3QSDgoGZm9yZ290GAMgASgIIloKD09wZXJhdGlvbkZvcmdldBIiCgZmb3JnZXQYASADKAsyEi52MS5SZXN0aWNTbmFwc2hvdBIjCgZwb2xpY3kYAiABKAsyEy52MS5SZXRlbnRpb25Qb2xpY3kiOwoOT3BlcmF0aW9uUHJ1bmUSEgoGb3V0cHV0GAEgASgJQgIYARIVCg1vdXRwdXRfbG9ncmVmGAIgASgJIjsKDk9wZXJhdGlvbkNoZWNrEhIKBm91dHB1dBgBIAEoCUICGAESFQoNb3V0cHV0X2xvZ3JlZhgCIAEoCSJYChNPcGVyYXRpb25SdW5Db21tYW5kEg8KB2NvbW1hbmQYASABKAkSFQoNb3V0cHV0X2xvZ3JlZhgCIAEoCRIZChFvdXRwdXRfc2l6ZV9ieXRlcxgDIAEoAyJfChBPcGVyYXRpb25SZXN0b3JlEgwKBHBhdGgYASABKAkSDgoGdGFyZ2V0GAIgASgJEi0KC2xhc3Rfc3RhdHVzGAMgASgLMhgudjEuUmVzdG9yZVByb2dyZXNzRW50cnkiLgoOT3BlcmF0aW9uU3RhdHMSHAoFc3RhdHMYASABKAsyDS52MS5SZXBvU3RhdHMicQoQT3BlcmF0aW9uUnVuSG9vaxIRCglwYXJlbnRfb3AYBCABKAMSDAoEbmFtZRgBIAEoCRIVCg1vdXRwdXRfbG9ncmVmGAIgASgJEiUKCWNvbmRpdGlvbhgDIAEoDjISLnYxLkhvb2suQ29uZGl0aW9uKmAKEk9wZXJhdGlvbkV2ZW50VHlwZRIRCg1FVkVOVF9VTktOT1dOEAASEQoNRVZFTlRfQ1JFQVRFRBABEhEKDUVWRU5UX1VQREFURUQQAhIRCg1FVkVOVF9ERUxFVEVEEAMqwgEKD09wZXJhdGlvblN0YXR1cxISCg5TVEFUVVNfVU5LTk9XThAAEhIKDlNUQVRVU19QRU5ESU5HEAESFQoRU1RBVFVTX0lOUFJPR1JFU1MQAhISCg5TVEFUVVNfU1VDQ0VTUxADEhIKDlNUQVRVU19XQVJOSU5HEAcSEAoMU1RBVFVTX0VSUk9SEAQSGwoXU1RBVFVTX1NZU1RFTV9DQU5DRUxMRUQQBRIZChVTVEFUVVNfVVNFUl9DQU5DRUxMRUQQBkIsWipnaXRodWIuY29tL2dhcmV0aGdlb3JnZS9iYWNrcmVzdC9nZW4vZ28vdjFiBnByb3RvMw", [file_v1_restic, file_v1_config, file_types_value]); + fileDesc("ChN2MS9vcGVyYXRpb25zLnByb3RvEgJ2MSIyCg1PcGVyYXRpb25MaXN0EiEKCm9wZXJhdGlvbnMYASADKAsyDS52MS5PcGVyYXRpb24inwYKCU9wZXJhdGlvbhIKCgJpZBgBIAEoAxITCgtvcmlnaW5hbF9pZBgNIAEoAxINCgVtb2RubxgMIAEoAxIPCgdmbG93X2lkGAogASgDEhgKEG9yaWdpbmFsX2Zsb3dfaWQYDiABKAMSDwoHcmVwb19pZBgCIAEoCRIRCglyZXBvX2d1aWQYDyABKAkSDwoHcGxhbl9pZBgDIAEoCRITCgtpbnN0YW5jZV9pZBgLIAEoCRITCgtzbmFwc2hvdF9pZBgIIAEoCRIjCgZzdGF0dXMYBCABKA4yEy52MS5PcGVyYXRpb25TdGF0dXMSGgoSdW5peF90aW1lX3N0YXJ0X21zGAUgASgDEhgKEHVuaXhfdGltZV9lbmRfbXMYBiABKAMSFwoPZGlzcGxheV9tZXNzYWdlGAcgASgJEg4KBmxvZ3JlZhgJIAEoCRIvChBvcGVyYXRpb25fYmFja3VwGGQgASgLMhMudjEuT3BlcmF0aW9uQmFja3VwSAASPgoYb3BlcmF0aW9uX2luZGV4X3NuYXBzaG90GGUgASgLMhoudjEuT3BlcmF0aW9uSW5kZXhTbmFwc2hvdEgAEi8KEG9wZXJhdGlvbl9mb3JnZXQYZiABKAsyEy52MS5PcGVyYXRpb25Gb3JnZXRIABItCg9vcGVyYXRpb25fcHJ1bmUYZyABKAsyEi52MS5PcGVyYXRpb25QcnVuZUgAEjEKEW9wZXJhdGlvbl9yZXN0b3JlGGggASgLMhQudjEuT3BlcmF0aW9uUmVzdG9yZUgAEi0KD29wZXJhdGlvbl9zdGF0cxhpIAEoCzISLnYxLk9wZXJhdGlvblN0YXRzSAASMgoSb3BlcmF0aW9uX3J1bl9ob29rGGogASgLMhQudjEuT3BlcmF0aW9uUnVuSG9va0gAEi0KD29wZXJhdGlvbl9jaGVjaxhrIAEoCzISLnYxLk9wZXJhdGlvbkNoZWNrSAASOAoVb3BlcmF0aW9uX3J1bl9jb21tYW5kGGwgASgLMhcudjEuT3BlcmF0aW9uUnVuQ29tbWFuZEgAQgQKAm9wIs8BCg5PcGVyYXRpb25FdmVudBIiCgprZWVwX2FsaXZlGAEgASgLMgwudHlwZXMuRW1wdHlIABIvChJjcmVhdGVkX29wZXJhdGlvbnMYAiABKAsyES52MS5PcGVyYXRpb25MaXN0SAASLwoSdXBkYXRlZF9vcGVyYXRpb25zGAMgASgLMhEudjEuT3BlcmF0aW9uTGlzdEgAEi4KEmRlbGV0ZWRfb3BlcmF0aW9ucxgEIAEoCzIQLnR5cGVzLkludDY0TGlzdEgAQgcKBWV2ZW50ImgKD09wZXJhdGlvbkJhY2t1cBIsCgtsYXN0X3N0YXR1cxgDIAEoCzIXLnYxLkJhY2t1cFByb2dyZXNzRW50cnkSJwoGZXJyb3JzGAQgAygLMhcudjEuQmFja3VwUHJvZ3Jlc3NFcnJvciJOChZPcGVyYXRpb25JbmRleFNuYXBzaG90EiQKCHNuYXBzaG90GAIgASgLMhIudjEuUmVzdGljU25hcHNob3QSDgoGZm9yZ290GAMgASgIIloKD09wZXJhdGlvbkZvcmdldBIiCgZmb3JnZXQYASADKAsyEi52MS5SZXN0aWNTbmFwc2hvdBIjCgZwb2xpY3kYAiABKAsyEy52MS5SZXRlbnRpb25Qb2xpY3kiOwoOT3BlcmF0aW9uUHJ1bmUSEgoGb3V0cHV0GAEgASgJQgIYARIVCg1vdXRwdXRfbG9ncmVmGAIgASgJIjsKDk9wZXJhdGlvbkNoZWNrEhIKBm91dHB1dBgBIAEoCUICGAESFQoNb3V0cHV0X2xvZ3JlZhgCIAEoCSJYChNPcGVyYXRpb25SdW5Db21tYW5kEg8KB2NvbW1hbmQYASABKAkSFQoNb3V0cHV0X2xvZ3JlZhgCIAEoCRIZChFvdXRwdXRfc2l6ZV9ieXRlcxgDIAEoAyJfChBPcGVyYXRpb25SZXN0b3JlEgwKBHBhdGgYASABKAkSDgoGdGFyZ2V0GAIgASgJEi0KC2xhc3Rfc3RhdHVzGAMgASgLMhgudjEuUmVzdG9yZVByb2dyZXNzRW50cnkiLgoOT3BlcmF0aW9uU3RhdHMSHAoFc3RhdHMYASABKAsyDS52MS5SZXBvU3RhdHMicQoQT3BlcmF0aW9uUnVuSG9vaxIRCglwYXJlbnRfb3AYBCABKAMSDAoEbmFtZRgBIAEoCRIVCg1vdXRwdXRfbG9ncmVmGAIgASgJEiUKCWNvbmRpdGlvbhgDIAEoDjISLnYxLkhvb2suQ29uZGl0aW9uKmAKEk9wZXJhdGlvbkV2ZW50VHlwZRIRCg1FVkVOVF9VTktOT1dOEAASEQoNRVZFTlRfQ1JFQVRFRBABEhEKDUVWRU5UX1VQREFURUQQAhIRCg1FVkVOVF9ERUxFVEVEEAMqwgEKD09wZXJhdGlvblN0YXR1cxISCg5TVEFUVVNfVU5LTk9XThAAEhIKDlNUQVRVU19QRU5ESU5HEAESFQoRU1RBVFVTX0lOUFJPR1JFU1MQAhISCg5TVEFUVVNfU1VDQ0VTUxADEhIKDlNUQVRVU19XQVJOSU5HEAcSEAoMU1RBVFVTX0VSUk9SEAQSGwoXU1RBVFVTX1NZU1RFTV9DQU5DRUxMRUQQBRIZChVTVEFUVVNfVVNFUl9DQU5DRUxMRUQQBkIsWipnaXRodWIuY29tL2dhcmV0aGdlb3JnZS9iYWNrcmVzdC9nZW4vZ28vdjFiBnByb3RvMw", [file_v1_restic, file_v1_config, file_types_value]); /** * @generated from message v1.OperationList @@ -47,25 +47,52 @@ export type Operation = Message<"v1.Operation"> & { id: bigint; /** - * flow id groups operations together, e.g. by an execution of a plan. + * @generated from field: int64 original_id = 13; + */ + originalId: bigint; + + /** + * modno increments with each change to the operation. This supports easy diffing. * - * optional, flow id if associated with a flow + * @generated from field: int64 modno = 12; + */ + modno: bigint; + + /** + * flow id groups operations together, e.g. by an execution of a plan. + * must be unique within the context of a repo. * * @generated from field: int64 flow_id = 10; */ flowId: bigint; /** + * @generated from field: int64 original_flow_id = 14; + */ + originalFlowId: bigint; + + /** + * repo id is a string identifier for the repo, and repo_guid is the globally unique ID of the repo. + * * @generated from field: string repo_id = 2; */ repoId: string; /** + * @generated from field: string repo_guid = 15; + */ + repoGuid: string; + + /** + * plan id e.g. a scheduled set of operations (or system) that created this operation. + * * @generated from field: string plan_id = 3; */ planId: string; /** + * instance ID that created the operation + * * @generated from field: string instance_id = 11; */ instanceId: string; diff --git a/webui/gen/ts/v1/service_pb.ts b/webui/gen/ts/v1/service_pb.ts index 33f1519..2469edd 100644 --- a/webui/gen/ts/v1/service_pb.ts +++ b/webui/gen/ts/v1/service_pb.ts @@ -21,7 +21,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file v1/service.proto. */ export const file_v1_service: GenFile = /*@__PURE__*/ - fileDesc("ChB2MS9zZXJ2aWNlLnByb3RvEgJ2MSJhCgpPcFNlbGVjdG9yEgsKA2lkcxgBIAMoAxIPCgdyZXBvX2lkGAIgASgJEg8KB3BsYW5faWQYAyABKAkSEwoLc25hcHNob3RfaWQYBCABKAkSDwoHZmxvd19pZBgFIAEoAyLAAQoRRG9SZXBvVGFza1JlcXVlc3QSDwoHcmVwb19pZBgBIAEoCRIoCgR0YXNrGAIgASgOMhoudjEuRG9SZXBvVGFza1JlcXVlc3QuVGFzayJwCgRUYXNrEg0KCVRBU0tfTk9ORRAAEhgKFFRBU0tfSU5ERVhfU05BUFNIT1RTEAESDgoKVEFTS19QUlVORRACEg4KClRBU0tfQ0hFQ0sQAxIOCgpUQVNLX1NUQVRTEAQSDwoLVEFTS19VTkxPQ0sQBSJMChNDbGVhckhpc3RvcnlSZXF1ZXN0EiAKCHNlbGVjdG9yGAEgASgLMg4udjEuT3BTZWxlY3RvchITCgtvbmx5X2ZhaWxlZBgCIAEoCCJGCg1Gb3JnZXRSZXF1ZXN0Eg8KB3JlcG9faWQYASABKAkSDwoHcGxhbl9pZBgCIAEoCRITCgtzbmFwc2hvdF9pZBgDIAEoCSI4ChRMaXN0U25hcHNob3RzUmVxdWVzdBIPCgdyZXBvX2lkGAEgASgJEg8KB3BsYW5faWQYAiABKAkiSAoUR2V0T3BlcmF0aW9uc1JlcXVlc3QSIAoIc2VsZWN0b3IYASABKAsyDi52MS5PcFNlbGVjdG9yEg4KBmxhc3RfbhgCIAEoAyJtChZSZXN0b3JlU25hcHNob3RSZXF1ZXN0Eg8KB3BsYW5faWQYASABKAkSDwoHcmVwb19pZBgFIAEoCRITCgtzbmFwc2hvdF9pZBgCIAEoCRIMCgRwYXRoGAMgASgJEg4KBnRhcmdldBgEIAEoCSJOChhMaXN0U25hcHNob3RGaWxlc1JlcXVlc3QSDwoHcmVwb19pZBgBIAEoCRITCgtzbmFwc2hvdF9pZBgCIAEoCRIMCgRwYXRoGAMgASgJIkcKGUxpc3RTbmFwc2hvdEZpbGVzUmVzcG9uc2USDAoEcGF0aBgBIAEoCRIcCgdlbnRyaWVzGAIgAygLMgsudjEuTHNFbnRyeSIdCg5Mb2dEYXRhUmVxdWVzdBILCgNyZWYYASABKAkilgEKB0xzRW50cnkSDAoEbmFtZRgBIAEoCRIMCgR0eXBlGAIgASgJEgwKBHBhdGgYAyABKAkSCwoDdWlkGAQgASgDEgsKA2dpZBgFIAEoAxIMCgRzaXplGAYgASgDEgwKBG1vZGUYByABKAMSDQoFbXRpbWUYCCABKAkSDQoFYXRpbWUYCSABKAkSDQoFY3RpbWUYCiABKAkiNQoRUnVuQ29tbWFuZFJlcXVlc3QSDwoHcmVwb19pZBgBIAEoCRIPCgdjb21tYW5kGAIgASgJIrUFChhTdW1tYXJ5RGFzaGJvYXJkUmVzcG9uc2USPAoOcmVwb19zdW1tYXJpZXMYASADKAsyJC52MS5TdW1tYXJ5RGFzaGJvYXJkUmVzcG9uc2UuU3VtbWFyeRI8Cg5wbGFuX3N1bW1hcmllcxgCIAMoCzIkLnYxLlN1bW1hcnlEYXNoYm9hcmRSZXNwb25zZS5TdW1tYXJ5EhMKC2NvbmZpZ19wYXRoGAogASgJEhEKCWRhdGFfcGF0aBgLIAEoCRruAgoHU3VtbWFyeRIKCgJpZBgBIAEoCRIdChViYWNrdXBzX2ZhaWxlZF8zMGRheXMYAiABKAMSIwobYmFja3Vwc193YXJuaW5nX2xhc3RfMzBkYXlzGAMgASgDEiMKG2JhY2t1cHNfc3VjY2Vzc19sYXN0XzMwZGF5cxgEIAEoAxIhChlieXRlc19zY2FubmVkX2xhc3RfMzBkYXlzGAUgASgDEh8KF2J5dGVzX2FkZGVkX2xhc3RfMzBkYXlzGAYgASgDEhcKD3RvdGFsX3NuYXBzaG90cxgHIAEoAxIZChFieXRlc19zY2FubmVkX2F2ZxgIIAEoAxIXCg9ieXRlc19hZGRlZF9hdmcYCSABKAMSGwoTbmV4dF9iYWNrdXBfdGltZV9tcxgKIAEoAxJACg5yZWNlbnRfYmFja3VwcxgLIAEoCzIoLnYxLlN1bW1hcnlEYXNoYm9hcmRSZXNwb25zZS5CYWNrdXBDaGFydBqDAQoLQmFja3VwQ2hhcnQSDwoHZmxvd19pZBgBIAMoAxIUCgx0aW1lc3RhbXBfbXMYAiADKAMSEwoLZHVyYXRpb25fbXMYAyADKAMSIwoGc3RhdHVzGAQgAygOMhMudjEuT3BlcmF0aW9uU3RhdHVzEhMKC2J5dGVzX2FkZGVkGAUgAygDMvcICghCYWNrcmVzdBIxCglHZXRDb25maWcSFi5nb29nbGUucHJvdG9idWYuRW1wdHkaCi52MS5Db25maWciABIlCglTZXRDb25maWcSCi52MS5Db25maWcaCi52MS5Db25maWciABIvCg9DaGVja1JlcG9FeGlzdHMSCC52MS5SZXBvGhAudHlwZXMuQm9vbFZhbHVlIgASIQoHQWRkUmVwbxIILnYxLlJlcG8aCi52MS5Db25maWciABJEChJHZXRPcGVyYXRpb25FdmVudHMSFi5nb29nbGUucHJvdG9idWYuRW1wdHkaEi52MS5PcGVyYXRpb25FdmVudCIAMAESPgoNR2V0T3BlcmF0aW9ucxIYLnYxLkdldE9wZXJhdGlvbnNSZXF1ZXN0GhEudjEuT3BlcmF0aW9uTGlzdCIAEkMKDUxpc3RTbmFwc2hvdHMSGC52MS5MaXN0U25hcHNob3RzUmVxdWVzdBoWLnYxLlJlc3RpY1NuYXBzaG90TGlzdCIAElIKEUxpc3RTbmFwc2hvdEZpbGVzEhwudjEuTGlzdFNuYXBzaG90RmlsZXNSZXF1ZXN0Gh0udjEuTGlzdFNuYXBzaG90RmlsZXNSZXNwb25zZSIAEjYKBkJhY2t1cBISLnR5cGVzLlN0cmluZ1ZhbHVlGhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5IgASPQoKRG9SZXBvVGFzaxIVLnYxLkRvUmVwb1Rhc2tSZXF1ZXN0GhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5IgASNQoGRm9yZ2V0EhEudjEuRm9yZ2V0UmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSIAEj8KB1Jlc3RvcmUSGi52MS5SZXN0b3JlU25hcHNob3RSZXF1ZXN0GhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5IgASNQoGQ2FuY2VsEhEudHlwZXMuSW50NjRWYWx1ZRoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSIAEjQKB0dldExvZ3MSEi52MS5Mb2dEYXRhUmVxdWVzdBoRLnR5cGVzLkJ5dGVzVmFsdWUiADABEjgKClJ1bkNvbW1hbmQSFS52MS5SdW5Db21tYW5kUmVxdWVzdBoRLnR5cGVzLkludDY0VmFsdWUiABI5Cg5HZXREb3dubG9hZFVSTBIRLnR5cGVzLkludDY0VmFsdWUaEi50eXBlcy5TdHJpbmdWYWx1ZSIAEkEKDENsZWFySGlzdG9yeRIXLnYxLkNsZWFySGlzdG9yeVJlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiABI7ChBQYXRoQXV0b2NvbXBsZXRlEhIudHlwZXMuU3RyaW5nVmFsdWUaES50eXBlcy5TdHJpbmdMaXN0IgASTQoTR2V0U3VtbWFyeURhc2hib2FyZBIWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eRocLnYxLlN1bW1hcnlEYXNoYm9hcmRSZXNwb25zZSIAQixaKmdpdGh1Yi5jb20vZ2FyZXRoZ2VvcmdlL2JhY2tyZXN0L2dlbi9nby92MWIGcHJvdG8z", [file_v1_config, file_v1_restic, file_v1_operations, file_types_value, file_google_protobuf_empty, file_google_api_annotations]); + fileDesc("ChB2MS9zZXJ2aWNlLnByb3RvEgJ2MSLXAQoKT3BTZWxlY3RvchILCgNpZHMYASADKAMSGAoLaW5zdGFuY2VfaWQYBiABKAlIAIgBARIWCglyZXBvX2d1aWQYByABKAlIAYgBARIUCgdwbGFuX2lkGAMgASgJSAKIAQESGAoLc25hcHNob3RfaWQYBCABKAlIA4gBARIUCgdmbG93X2lkGAUgASgDSASIAQFCDgoMX2luc3RhbmNlX2lkQgwKCl9yZXBvX2d1aWRCCgoIX3BsYW5faWRCDgoMX3NuYXBzaG90X2lkQgoKCF9mbG93X2lkIsABChFEb1JlcG9UYXNrUmVxdWVzdBIPCgdyZXBvX2lkGAEgASgJEigKBHRhc2sYAiABKA4yGi52MS5Eb1JlcG9UYXNrUmVxdWVzdC5UYXNrInAKBFRhc2sSDQoJVEFTS19OT05FEAASGAoUVEFTS19JTkRFWF9TTkFQU0hPVFMQARIOCgpUQVNLX1BSVU5FEAISDgoKVEFTS19DSEVDSxADEg4KClRBU0tfU1RBVFMQBBIPCgtUQVNLX1VOTE9DSxAFIkwKE0NsZWFySGlzdG9yeVJlcXVlc3QSIAoIc2VsZWN0b3IYASABKAsyDi52MS5PcFNlbGVjdG9yEhMKC29ubHlfZmFpbGVkGAIgASgIIkYKDUZvcmdldFJlcXVlc3QSDwoHcmVwb19pZBgBIAEoCRIPCgdwbGFuX2lkGAIgASgJEhMKC3NuYXBzaG90X2lkGAMgASgJIjgKFExpc3RTbmFwc2hvdHNSZXF1ZXN0Eg8KB3JlcG9faWQYASABKAkSDwoHcGxhbl9pZBgCIAEoCSJIChRHZXRPcGVyYXRpb25zUmVxdWVzdBIgCghzZWxlY3RvchgBIAEoCzIOLnYxLk9wU2VsZWN0b3ISDgoGbGFzdF9uGAIgASgDIm0KFlJlc3RvcmVTbmFwc2hvdFJlcXVlc3QSDwoHcGxhbl9pZBgBIAEoCRIPCgdyZXBvX2lkGAUgASgJEhMKC3NuYXBzaG90X2lkGAIgASgJEgwKBHBhdGgYAyABKAkSDgoGdGFyZ2V0GAQgASgJIk4KGExpc3RTbmFwc2hvdEZpbGVzUmVxdWVzdBIPCgdyZXBvX2lkGAEgASgJEhMKC3NuYXBzaG90X2lkGAIgASgJEgwKBHBhdGgYAyABKAkiRwoZTGlzdFNuYXBzaG90RmlsZXNSZXNwb25zZRIMCgRwYXRoGAEgASgJEhwKB2VudHJpZXMYAiADKAsyCy52MS5Mc0VudHJ5Ih0KDkxvZ0RhdGFSZXF1ZXN0EgsKA3JlZhgBIAEoCSKWAQoHTHNFbnRyeRIMCgRuYW1lGAEgASgJEgwKBHR5cGUYAiABKAkSDAoEcGF0aBgDIAEoCRILCgN1aWQYBCABKAMSCwoDZ2lkGAUgASgDEgwKBHNpemUYBiABKAMSDAoEbW9kZRgHIAEoAxINCgVtdGltZRgIIAEoCRINCgVhdGltZRgJIAEoCRINCgVjdGltZRgKIAEoCSI1ChFSdW5Db21tYW5kUmVxdWVzdBIPCgdyZXBvX2lkGAEgASgJEg8KB2NvbW1hbmQYAiABKAkitQUKGFN1bW1hcnlEYXNoYm9hcmRSZXNwb25zZRI8Cg5yZXBvX3N1bW1hcmllcxgBIAMoCzIkLnYxLlN1bW1hcnlEYXNoYm9hcmRSZXNwb25zZS5TdW1tYXJ5EjwKDnBsYW5fc3VtbWFyaWVzGAIgAygLMiQudjEuU3VtbWFyeURhc2hib2FyZFJlc3BvbnNlLlN1bW1hcnkSEwoLY29uZmlnX3BhdGgYCiABKAkSEQoJZGF0YV9wYXRoGAsgASgJGu4CCgdTdW1tYXJ5EgoKAmlkGAEgASgJEh0KFWJhY2t1cHNfZmFpbGVkXzMwZGF5cxgCIAEoAxIjChtiYWNrdXBzX3dhcm5pbmdfbGFzdF8zMGRheXMYAyABKAMSIwobYmFja3Vwc19zdWNjZXNzX2xhc3RfMzBkYXlzGAQgASgDEiEKGWJ5dGVzX3NjYW5uZWRfbGFzdF8zMGRheXMYBSABKAMSHwoXYnl0ZXNfYWRkZWRfbGFzdF8zMGRheXMYBiABKAMSFwoPdG90YWxfc25hcHNob3RzGAcgASgDEhkKEWJ5dGVzX3NjYW5uZWRfYXZnGAggASgDEhcKD2J5dGVzX2FkZGVkX2F2ZxgJIAEoAxIbChNuZXh0X2JhY2t1cF90aW1lX21zGAogASgDEkAKDnJlY2VudF9iYWNrdXBzGAsgASgLMigudjEuU3VtbWFyeURhc2hib2FyZFJlc3BvbnNlLkJhY2t1cENoYXJ0GoMBCgtCYWNrdXBDaGFydBIPCgdmbG93X2lkGAEgAygDEhQKDHRpbWVzdGFtcF9tcxgCIAMoAxITCgtkdXJhdGlvbl9tcxgDIAMoAxIjCgZzdGF0dXMYBCADKA4yEy52MS5PcGVyYXRpb25TdGF0dXMSEwoLYnl0ZXNfYWRkZWQYBSADKAMy9wgKCEJhY2tyZXN0EjEKCUdldENvbmZpZxIWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eRoKLnYxLkNvbmZpZyIAEiUKCVNldENvbmZpZxIKLnYxLkNvbmZpZxoKLnYxLkNvbmZpZyIAEi8KD0NoZWNrUmVwb0V4aXN0cxIILnYxLlJlcG8aEC50eXBlcy5Cb29sVmFsdWUiABIhCgdBZGRSZXBvEggudjEuUmVwbxoKLnYxLkNvbmZpZyIAEkQKEkdldE9wZXJhdGlvbkV2ZW50cxIWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eRoSLnYxLk9wZXJhdGlvbkV2ZW50IgAwARI+Cg1HZXRPcGVyYXRpb25zEhgudjEuR2V0T3BlcmF0aW9uc1JlcXVlc3QaES52MS5PcGVyYXRpb25MaXN0IgASQwoNTGlzdFNuYXBzaG90cxIYLnYxLkxpc3RTbmFwc2hvdHNSZXF1ZXN0GhYudjEuUmVzdGljU25hcHNob3RMaXN0IgASUgoRTGlzdFNuYXBzaG90RmlsZXMSHC52MS5MaXN0U25hcHNob3RGaWxlc1JlcXVlc3QaHS52MS5MaXN0U25hcHNob3RGaWxlc1Jlc3BvbnNlIgASNgoGQmFja3VwEhIudHlwZXMuU3RyaW5nVmFsdWUaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiABI9CgpEb1JlcG9UYXNrEhUudjEuRG9SZXBvVGFza1JlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiABI1CgZGb3JnZXQSES52MS5Gb3JnZXRSZXF1ZXN0GhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5IgASPwoHUmVzdG9yZRIaLnYxLlJlc3RvcmVTbmFwc2hvdFJlcXVlc3QaFi5nb29nbGUucHJvdG9idWYuRW1wdHkiABI1CgZDYW5jZWwSES50eXBlcy5JbnQ2NFZhbHVlGhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5IgASNAoHR2V0TG9ncxISLnYxLkxvZ0RhdGFSZXF1ZXN0GhEudHlwZXMuQnl0ZXNWYWx1ZSIAMAESOAoKUnVuQ29tbWFuZBIVLnYxLlJ1bkNvbW1hbmRSZXF1ZXN0GhEudHlwZXMuSW50NjRWYWx1ZSIAEjkKDkdldERvd25sb2FkVVJMEhEudHlwZXMuSW50NjRWYWx1ZRoSLnR5cGVzLlN0cmluZ1ZhbHVlIgASQQoMQ2xlYXJIaXN0b3J5EhcudjEuQ2xlYXJIaXN0b3J5UmVxdWVzdBoWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eSIAEjsKEFBhdGhBdXRvY29tcGxldGUSEi50eXBlcy5TdHJpbmdWYWx1ZRoRLnR5cGVzLlN0cmluZ0xpc3QiABJNChNHZXRTdW1tYXJ5RGFzaGJvYXJkEhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5GhwudjEuU3VtbWFyeURhc2hib2FyZFJlc3BvbnNlIgBCLFoqZ2l0aHViLmNvbS9nYXJldGhnZW9yZ2UvYmFja3Jlc3QvZ2VuL2dvL3YxYgZwcm90bzM", [file_v1_config, file_v1_restic, file_v1_operations, file_types_value, file_google_protobuf_empty, file_google_api_annotations]); /** * OpSelector is a message that can be used to select operations e.g. by query. @@ -35,24 +35,29 @@ export type OpSelector = Message<"v1.OpSelector"> & { ids: bigint[]; /** - * @generated from field: string repo_id = 2; + * @generated from field: optional string instance_id = 6; */ - repoId: string; + instanceId?: string; /** - * @generated from field: string plan_id = 3; + * @generated from field: optional string repo_guid = 7; */ - planId: string; + repoGuid?: string; /** - * @generated from field: string snapshot_id = 4; + * @generated from field: optional string plan_id = 3; */ - snapshotId: string; + planId?: string; /** - * @generated from field: int64 flow_id = 5; + * @generated from field: optional string snapshot_id = 4; */ - flowId: bigint; + snapshotId?: string; + + /** + * @generated from field: optional int64 flow_id = 5; + */ + flowId?: bigint; }; /** diff --git a/webui/gen/ts/v1/syncservice_pb.ts b/webui/gen/ts/v1/syncservice_pb.ts new file mode 100644 index 0000000..72d3ca4 --- /dev/null +++ b/webui/gen/ts/v1/syncservice_pb.ts @@ -0,0 +1,446 @@ +// @generated by protoc-gen-es v2.2.2 with parameter "target=ts" +// @generated from file v1/syncservice.proto (package v1, syntax proto3) +/* eslint-disable */ + +import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv1"; +import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv1"; +import { file_v1_config } from "./config_pb"; +import type { PublicKey, SignedMessage } from "./crypto_pb"; +import { file_v1_crypto } from "./crypto_pb"; +import { file_v1_restic } from "./restic_pb"; +import type { OpSelector } from "./service_pb"; +import { file_v1_service } from "./service_pb"; +import type { OperationEvent } from "./operations_pb"; +import { file_v1_operations } from "./operations_pb"; +import { file_types_value } from "../types/value_pb"; +import type { EmptySchema } from "@bufbuild/protobuf/wkt"; +import { file_google_protobuf_empty } from "@bufbuild/protobuf/wkt"; +import { file_google_api_annotations } from "../google/api/annotations_pb"; +import type { Message } from "@bufbuild/protobuf"; + +/** + * Describes the file v1/syncservice.proto. + */ +export const file_v1_syncservice: GenFile = /*@__PURE__*/ + fileDesc("ChR2MS9zeW5jc2VydmljZS5wcm90bxICdjEikgEKFkdldFJlbW90ZVJlcG9zUmVzcG9uc2USPAoFcmVwb3MYASADKAsyLS52MS5HZXRSZW1vdGVSZXBvc1Jlc3BvbnNlLlJlbW90ZVJlcG9NZXRhZGF0YRo6ChJSZW1vdGVSZXBvTWV0YWRhdGESEwoLaW5zdGFuY2VfaWQYASABKAkSDwoHcmVwb19pZBgCIAEoCSK/CQoOU3luY1N0cmVhbUl0ZW0SKwoOc2lnbmVkX21lc3NhZ2UYASABKAsyES52MS5TaWduZWRNZXNzYWdlSAASOwoJaGFuZHNoYWtlGAMgASgLMiYudjEuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvbkhhbmRzaGFrZUgAEkYKD2RpZmZfb3BlcmF0aW9ucxgUIAEoCzIrLnYxLlN5bmNTdHJlYW1JdGVtLlN5bmNBY3Rpb25EaWZmT3BlcmF0aW9uc0gAEkYKD3NlbmRfb3BlcmF0aW9ucxgVIAEoCzIrLnYxLlN5bmNTdHJlYW1JdGVtLlN5bmNBY3Rpb25TZW5kT3BlcmF0aW9uc0gAEj4KC3NlbmRfY29uZmlnGBYgASgLMicudjEuU3luY1N0cmVhbUl0ZW0uU3luY0FjdGlvblNlbmRDb25maWdIABJPChdlc3RhYmxpc2hfc2hhcmVkX3NlY3JldBgXIAEoCzIsLnYxLlN5bmNTdHJlYW1JdGVtLlN5bmNFc3RhYmxpc2hTaGFyZWRTZWNyZXRIABI6Cgh0aHJvdHRsZRjoByABKAsyJS52MS5TeW5jU3RyZWFtSXRlbS5TeW5jQWN0aW9uVGhyb3R0bGVIABp6ChNTeW5jQWN0aW9uSGFuZHNoYWtlEhgKEHByb3RvY29sX3ZlcnNpb24YASABKAMSIQoKcHVibGljX2tleRgCIAEoCzINLnYxLlB1YmxpY0tleRImCgtpbnN0YW5jZV9pZBgDIAEoCzIRLnYxLlNpZ25lZE1lc3NhZ2UaOAoUU3luY0FjdGlvblNlbmRDb25maWcSIAoGY29uZmlnGAEgASgLMhAudjEuUmVtb3RlQ29uZmlnGigKFVN5bmNBY3Rpb25Db25uZWN0UmVwbxIPCgdyZXBvX2lkGAEgASgJGqMBChhTeW5jQWN0aW9uRGlmZk9wZXJhdGlvbnMSMAoYaGF2ZV9vcGVyYXRpb25zX3NlbGVjdG9yGAEgASgLMg4udjEuT3BTZWxlY3RvchIaChJoYXZlX29wZXJhdGlvbl9pZHMYAiADKAMSHQoVaGF2ZV9vcGVyYXRpb25fbW9kbm9zGAMgAygDEhoKEnJlcXVlc3Rfb3BlcmF0aW9ucxgEIAMoAxo9ChhTeW5jQWN0aW9uU2VuZE9wZXJhdGlvbnMSIQoFZXZlbnQYASABKAsyEi52MS5PcGVyYXRpb25FdmVudBomChJTeW5jQWN0aW9uVGhyb3R0bGUSEAoIZGVsYXlfbXMYASABKAMaOAoZU3luY0VzdGFibGlzaFNoYXJlZFNlY3JldBIbCgdlZDI1NTE5GAIgASgJUgplZDI1NTE5cHViIrQBChNSZXBvQ29ubmVjdGlvblN0YXRlEhwKGENPTk5FQ1RJT05fU1RBVEVfVU5LTk9XThAAEhwKGENPTk5FQ1RJT05fU1RBVEVfUEVORElORxABEh4KGkNPTk5FQ1RJT05fU1RBVEVfQ09OTkVDVEVEEAISIQodQ09OTkVDVElPTl9TVEFURV9VTkFVVEhPUklaRUQQAxIeChpDT05ORUNUSU9OX1NUQVRFX05PVF9GT1VORBAEQggKBmFjdGlvbiItCgxSZW1vdGVDb25maWcSHQoFcmVwb3MYASADKAsyDi52MS5SZW1vdGVSZXBvImEKClJlbW90ZVJlcG8SCgoCaWQYASABKAkSDAoEZ3VpZBgLIAEoCRILCgN1cmkYAiABKAkSEAoIcGFzc3dvcmQYAyABKAkSCwoDZW52GAQgAygJEg0KBWZsYWdzGAUgAygJKvsBChNTeW5jQ29ubmVjdGlvblN0YXRlEhwKGENPTk5FQ1RJT05fU1RBVEVfVU5LTk9XThAAEhwKGENPTk5FQ1RJT05fU1RBVEVfUEVORElORxABEh4KGkNPTk5FQ1RJT05fU1RBVEVfQ09OTkVDVEVEEAISIQodQ09OTkVDVElPTl9TVEFURV9ESVNDT05ORUNURUQQAxIfChtDT05ORUNUSU9OX1NUQVRFX1JFVFJZX1dBSVQQBBIfChtDT05ORUNUSU9OX1NUQVRFX0VSUk9SX0FVVEgQChIjCh9DT05ORUNUSU9OX1NUQVRFX0VSUk9SX1BST1RPQ09MEAsykwEKE0JhY2tyZXN0U3luY1NlcnZpY2USNAoEU3luYxISLnYxLlN5bmNTdHJlYW1JdGVtGhIudjEuU3luY1N0cmVhbUl0ZW0iACgBMAESRgoOR2V0UmVtb3RlUmVwb3MSFi5nb29nbGUucHJvdG9idWYuRW1wdHkaGi52MS5HZXRSZW1vdGVSZXBvc1Jlc3BvbnNlIgBCLFoqZ2l0aHViLmNvbS9nYXJldGhnZW9yZ2UvYmFja3Jlc3QvZ2VuL2dvL3YxYgZwcm90bzM", [file_v1_config, file_v1_crypto, file_v1_restic, file_v1_service, file_v1_operations, file_types_value, file_google_protobuf_empty, file_google_api_annotations]); + +/** + * @generated from message v1.GetRemoteReposResponse + */ +export type GetRemoteReposResponse = Message<"v1.GetRemoteReposResponse"> & { + /** + * @generated from field: repeated v1.GetRemoteReposResponse.RemoteRepoMetadata repos = 1; + */ + repos: GetRemoteReposResponse_RemoteRepoMetadata[]; +}; + +/** + * Describes the message v1.GetRemoteReposResponse. + * Use `create(GetRemoteReposResponseSchema)` to create a new message. + */ +export const GetRemoteReposResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_syncservice, 0); + +/** + * @generated from message v1.GetRemoteReposResponse.RemoteRepoMetadata + */ +export type GetRemoteReposResponse_RemoteRepoMetadata = Message<"v1.GetRemoteReposResponse.RemoteRepoMetadata"> & { + /** + * @generated from field: string instance_id = 1; + */ + instanceId: string; + + /** + * @generated from field: string repo_id = 2; + */ + repoId: string; +}; + +/** + * Describes the message v1.GetRemoteReposResponse.RemoteRepoMetadata. + * Use `create(GetRemoteReposResponse_RemoteRepoMetadataSchema)` to create a new message. + */ +export const GetRemoteReposResponse_RemoteRepoMetadataSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_syncservice, 0, 0); + +/** + * @generated from message v1.SyncStreamItem + */ +export type SyncStreamItem = Message<"v1.SyncStreamItem"> & { + /** + * @generated from oneof v1.SyncStreamItem.action + */ + action: { + /** + * @generated from field: v1.SignedMessage signed_message = 1; + */ + value: SignedMessage; + case: "signedMessage"; + } | { + /** + * @generated from field: v1.SyncStreamItem.SyncActionHandshake handshake = 3; + */ + value: SyncStreamItem_SyncActionHandshake; + case: "handshake"; + } | { + /** + * @generated from field: v1.SyncStreamItem.SyncActionDiffOperations diff_operations = 20; + */ + value: SyncStreamItem_SyncActionDiffOperations; + case: "diffOperations"; + } | { + /** + * @generated from field: v1.SyncStreamItem.SyncActionSendOperations send_operations = 21; + */ + value: SyncStreamItem_SyncActionSendOperations; + case: "sendOperations"; + } | { + /** + * @generated from field: v1.SyncStreamItem.SyncActionSendConfig send_config = 22; + */ + value: SyncStreamItem_SyncActionSendConfig; + case: "sendConfig"; + } | { + /** + * @generated from field: v1.SyncStreamItem.SyncEstablishSharedSecret establish_shared_secret = 23; + */ + value: SyncStreamItem_SyncEstablishSharedSecret; + case: "establishSharedSecret"; + } | { + /** + * @generated from field: v1.SyncStreamItem.SyncActionThrottle throttle = 1000; + */ + value: SyncStreamItem_SyncActionThrottle; + case: "throttle"; + } | { case: undefined; value?: undefined }; +}; + +/** + * Describes the message v1.SyncStreamItem. + * Use `create(SyncStreamItemSchema)` to create a new message. + */ +export const SyncStreamItemSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_syncservice, 1); + +/** + * @generated from message v1.SyncStreamItem.SyncActionHandshake + */ +export type SyncStreamItem_SyncActionHandshake = Message<"v1.SyncStreamItem.SyncActionHandshake"> & { + /** + * @generated from field: int64 protocol_version = 1; + */ + protocolVersion: bigint; + + /** + * @generated from field: v1.PublicKey public_key = 2; + */ + publicKey?: PublicKey; + + /** + * @generated from field: v1.SignedMessage instance_id = 3; + */ + instanceId?: SignedMessage; +}; + +/** + * Describes the message v1.SyncStreamItem.SyncActionHandshake. + * Use `create(SyncStreamItem_SyncActionHandshakeSchema)` to create a new message. + */ +export const SyncStreamItem_SyncActionHandshakeSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_syncservice, 1, 0); + +/** + * @generated from message v1.SyncStreamItem.SyncActionSendConfig + */ +export type SyncStreamItem_SyncActionSendConfig = Message<"v1.SyncStreamItem.SyncActionSendConfig"> & { + /** + * @generated from field: v1.RemoteConfig config = 1; + */ + config?: RemoteConfig; +}; + +/** + * Describes the message v1.SyncStreamItem.SyncActionSendConfig. + * Use `create(SyncStreamItem_SyncActionSendConfigSchema)` to create a new message. + */ +export const SyncStreamItem_SyncActionSendConfigSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_syncservice, 1, 1); + +/** + * @generated from message v1.SyncStreamItem.SyncActionConnectRepo + */ +export type SyncStreamItem_SyncActionConnectRepo = Message<"v1.SyncStreamItem.SyncActionConnectRepo"> & { + /** + * @generated from field: string repo_id = 1; + */ + repoId: string; +}; + +/** + * Describes the message v1.SyncStreamItem.SyncActionConnectRepo. + * Use `create(SyncStreamItem_SyncActionConnectRepoSchema)` to create a new message. + */ +export const SyncStreamItem_SyncActionConnectRepoSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_syncservice, 1, 2); + +/** + * @generated from message v1.SyncStreamItem.SyncActionDiffOperations + */ +export type SyncStreamItem_SyncActionDiffOperations = Message<"v1.SyncStreamItem.SyncActionDiffOperations"> & { + /** + * Client connects and sends a list of "have_operations" that exist in its log. + * have_operation_ids and have_operation_modnos are the operation IDs and modnos that the client has when zip'd pairwise. + * + * @generated from field: v1.OpSelector have_operations_selector = 1; + */ + haveOperationsSelector?: OpSelector; + + /** + * @generated from field: repeated int64 have_operation_ids = 2; + */ + haveOperationIds: bigint[]; + + /** + * @generated from field: repeated int64 have_operation_modnos = 3; + */ + haveOperationModnos: bigint[]; + + /** + * Server sends a list of "request_operations" for any operations that it doesn't have. + * + * @generated from field: repeated int64 request_operations = 4; + */ + requestOperations: bigint[]; +}; + +/** + * Describes the message v1.SyncStreamItem.SyncActionDiffOperations. + * Use `create(SyncStreamItem_SyncActionDiffOperationsSchema)` to create a new message. + */ +export const SyncStreamItem_SyncActionDiffOperationsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_syncservice, 1, 3); + +/** + * @generated from message v1.SyncStreamItem.SyncActionSendOperations + */ +export type SyncStreamItem_SyncActionSendOperations = Message<"v1.SyncStreamItem.SyncActionSendOperations"> & { + /** + * @generated from field: v1.OperationEvent event = 1; + */ + event?: OperationEvent; +}; + +/** + * Describes the message v1.SyncStreamItem.SyncActionSendOperations. + * Use `create(SyncStreamItem_SyncActionSendOperationsSchema)` to create a new message. + */ +export const SyncStreamItem_SyncActionSendOperationsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_syncservice, 1, 4); + +/** + * @generated from message v1.SyncStreamItem.SyncActionThrottle + */ +export type SyncStreamItem_SyncActionThrottle = Message<"v1.SyncStreamItem.SyncActionThrottle"> & { + /** + * @generated from field: int64 delay_ms = 1; + */ + delayMs: bigint; +}; + +/** + * Describes the message v1.SyncStreamItem.SyncActionThrottle. + * Use `create(SyncStreamItem_SyncActionThrottleSchema)` to create a new message. + */ +export const SyncStreamItem_SyncActionThrottleSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_syncservice, 1, 5); + +/** + * @generated from message v1.SyncStreamItem.SyncEstablishSharedSecret + */ +export type SyncStreamItem_SyncEstablishSharedSecret = Message<"v1.SyncStreamItem.SyncEstablishSharedSecret"> & { + /** + * a one-time-use ed25519 public key with a matching unshared private key. Used to perform a key exchange. + * See https://pkg.go.dev/crypto/ecdh#PrivateKey.ECDH . + * + * base64 encoded public key + * + * @generated from field: string ed25519 = 2 [json_name = "ed25519pub"]; + */ + ed25519: string; +}; + +/** + * Describes the message v1.SyncStreamItem.SyncEstablishSharedSecret. + * Use `create(SyncStreamItem_SyncEstablishSharedSecretSchema)` to create a new message. + */ +export const SyncStreamItem_SyncEstablishSharedSecretSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_syncservice, 1, 6); + +/** + * @generated from enum v1.SyncStreamItem.RepoConnectionState + */ +export enum SyncStreamItem_RepoConnectionState { + /** + * @generated from enum value: CONNECTION_STATE_UNKNOWN = 0; + */ + CONNECTION_STATE_UNKNOWN = 0, + + /** + * queried, response not yet received. + * + * @generated from enum value: CONNECTION_STATE_PENDING = 1; + */ + CONNECTION_STATE_PENDING = 1, + + /** + * @generated from enum value: CONNECTION_STATE_CONNECTED = 2; + */ + CONNECTION_STATE_CONNECTED = 2, + + /** + * @generated from enum value: CONNECTION_STATE_UNAUTHORIZED = 3; + */ + CONNECTION_STATE_UNAUTHORIZED = 3, + + /** + * @generated from enum value: CONNECTION_STATE_NOT_FOUND = 4; + */ + CONNECTION_STATE_NOT_FOUND = 4, +} + +/** + * Describes the enum v1.SyncStreamItem.RepoConnectionState. + */ +export const SyncStreamItem_RepoConnectionStateSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_v1_syncservice, 1, 0); + +/** + * RemoteConfig contains shareable properties from a remote backrest instance. + * + * @generated from message v1.RemoteConfig + */ +export type RemoteConfig = Message<"v1.RemoteConfig"> & { + /** + * @generated from field: repeated v1.RemoteRepo repos = 1; + */ + repos: RemoteRepo[]; +}; + +/** + * Describes the message v1.RemoteConfig. + * Use `create(RemoteConfigSchema)` to create a new message. + */ +export const RemoteConfigSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_syncservice, 2); + +/** + * @generated from message v1.RemoteRepo + */ +export type RemoteRepo = Message<"v1.RemoteRepo"> & { + /** + * @generated from field: string id = 1; + */ + id: string; + + /** + * @generated from field: string guid = 11; + */ + guid: string; + + /** + * @generated from field: string uri = 2; + */ + uri: string; + + /** + * @generated from field: string password = 3; + */ + password: string; + + /** + * @generated from field: repeated string env = 4; + */ + env: string[]; + + /** + * @generated from field: repeated string flags = 5; + */ + flags: string[]; +}; + +/** + * Describes the message v1.RemoteRepo. + * Use `create(RemoteRepoSchema)` to create a new message. + */ +export const RemoteRepoSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_v1_syncservice, 3); + +/** + * @generated from enum v1.SyncConnectionState + */ +export enum SyncConnectionState { + /** + * @generated from enum value: CONNECTION_STATE_UNKNOWN = 0; + */ + CONNECTION_STATE_UNKNOWN = 0, + + /** + * @generated from enum value: CONNECTION_STATE_PENDING = 1; + */ + CONNECTION_STATE_PENDING = 1, + + /** + * @generated from enum value: CONNECTION_STATE_CONNECTED = 2; + */ + CONNECTION_STATE_CONNECTED = 2, + + /** + * @generated from enum value: CONNECTION_STATE_DISCONNECTED = 3; + */ + CONNECTION_STATE_DISCONNECTED = 3, + + /** + * @generated from enum value: CONNECTION_STATE_RETRY_WAIT = 4; + */ + CONNECTION_STATE_RETRY_WAIT = 4, + + /** + * @generated from enum value: CONNECTION_STATE_ERROR_AUTH = 10; + */ + CONNECTION_STATE_ERROR_AUTH = 10, + + /** + * @generated from enum value: CONNECTION_STATE_ERROR_PROTOCOL = 11; + */ + CONNECTION_STATE_ERROR_PROTOCOL = 11, +} + +/** + * Describes the enum v1.SyncConnectionState. + */ +export const SyncConnectionStateSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_v1_syncservice, 0); + +/** + * @generated from service v1.BackrestSyncService + */ +export const BackrestSyncService: GenService<{ + /** + * @generated from rpc v1.BackrestSyncService.Sync + */ + sync: { + methodKind: "bidi_streaming"; + input: typeof SyncStreamItemSchema; + output: typeof SyncStreamItemSchema; + }, + /** + * @generated from rpc v1.BackrestSyncService.GetRemoteRepos + */ + getRemoteRepos: { + methodKind: "unary"; + input: typeof EmptySchema; + output: typeof GetRemoteReposResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_v1_syncservice, 0); + diff --git a/webui/src/components/OperationTree.tsx b/webui/src/components/OperationTree.tsx index 694a5e8..45c88c6 100644 --- a/webui/src/components/OperationTree.tsx +++ b/webui/src/components/OperationTree.tsx @@ -246,10 +246,11 @@ const buildTree = ( ); } + const uniqueKey = value[0].planID + "\x01" + value[0].instanceID + "\x01"; // use \x01 as delimiter return { - key: "p" + value[0].planID + "\x01" + value[0].instanceID, // use \x01 as separator + key: uniqueKey, title, - children: buildTreeDay(key, value), + children: buildTreeDay(uniqueKey, value), }; }); entries.sort(sortByKeyReverse); diff --git a/webui/src/components/StatsPanel.tsx b/webui/src/components/StatsPanel.tsx index ee79841..164d01b 100644 --- a/webui/src/components/StatsPanel.tsx +++ b/webui/src/components/StatsPanel.tsx @@ -22,7 +22,13 @@ import { import _ from "lodash"; import { create } from "@bufbuild/protobuf"; -const StatsPanel = ({ repoId }: { repoId: string }) => { +const StatsPanel = ({ + instanceId, + repoId, +}: { + instanceId: string; + repoId: string; +}) => { const [operations, setOperations] = useState([]); const alertApi = useAlertApi(); @@ -33,7 +39,8 @@ const StatsPanel = ({ repoId }: { repoId: string }) => { const req = create(GetOperationsRequestSchema, { selector: { - repoId: repoId, + repoId, + instanceId, }, }); diff --git a/webui/src/state/logstate.ts b/webui/src/state/logstate.ts index fe5c815..019f684 100644 --- a/webui/src/state/logstate.ts +++ b/webui/src/state/logstate.ts @@ -141,6 +141,13 @@ export class OplogState { flowIDsRemoved.add(op.flowId); this.removeHelper(op); } else { + const flow = this.byFlowID.get(op.flowId); + if (op.op.case === "operationIndexSnapshot" && flow && + flow.find((o) => o.op.case === "operationIndexSnapshot" && o.snapshotId === op.snapshotId)) { + // don't add a second index snapshot for the same flow, this can happen in multihost mode + continue; + } + ids.push(op.id); flowIDs.add(op.flowId); this.addHelper(op); diff --git a/webui/src/state/oplog.ts b/webui/src/state/oplog.ts index c2d645d..b016d6e 100644 --- a/webui/src/state/oplog.ts +++ b/webui/src/state/oplog.ts @@ -6,8 +6,7 @@ import { } from "../../gen/ts/v1/operations_pb"; import { GetOperationsRequest, OpSelector } from "../../gen/ts/v1/service_pb"; import { BackupProgressEntry, ResticSnapshot, RestoreProgressEntry } from "../../gen/ts/v1/restic_pb"; -import _, { flow } from "lodash"; -import { formatDuration, formatTime } from "../lib/formatting"; +import _ from "lodash"; import { backrestService } from "../api"; const subscribers: ((event?: OperationEvent, err?: Error) => void)[] = []; diff --git a/webui/src/views/AddRepoModal.tsx b/webui/src/views/AddRepoModal.tsx index a52e915..4b90c6f 100644 --- a/webui/src/views/AddRepoModal.tsx +++ b/webui/src/views/AddRepoModal.tsx @@ -19,7 +19,9 @@ import { import React, { useEffect, useState } from "react"; import { useShowModal } from "../components/ModalManager"; import { + CommandPrefix_CPUNiceLevel, CommandPrefix_CPUNiceLevelSchema, + CommandPrefix_IONiceLevel, CommandPrefix_IONiceLevelSchema, type Repo, RepoSchema, @@ -64,6 +66,10 @@ const repoDefaults = create(RepoSchema, { clock: Schedule_Clock.LAST_RUN_TIME, }, }, + commandPrefix: { + ioNice: CommandPrefix_IONiceLevel.IO_DEFAULT, + cpuNice: CommandPrefix_CPUNiceLevel.CPU_DEFAULT, + }, }); export const AddRepoModal = ({ template }: { template: Repo | null }) => { diff --git a/webui/src/views/App.tsx b/webui/src/views/App.tsx index edfdbd9..923c107 100644 --- a/webui/src/views/App.tsx +++ b/webui/src/views/App.tsx @@ -297,7 +297,13 @@ const getSidenavItems = (config: Config | null): MenuProps["items"] => { ...configPlans.map((plan) => { return { key: "p-" + plan.id, - icon: , + icon: ( + + ), label: (
{ ...configRepos.map((repo) => { return { key: "r-" + repo.id, - icon: , + icon: , label: (
{ }; const IconForResource = ({ + instanceId, planId, repoId, }: { + instanceId: string; planId?: string; repoId?: string; }) => { @@ -402,7 +410,9 @@ const IconForResource = ({ useEffect(() => { const load = async () => { setStatus( - await getStatusForSelector(create(OpSelectorSchema, { planId, repoId })) + await getStatusForSelector( + create(OpSelectorSchema, { instanceId, planId, repoId }) + ) ); }; load(); diff --git a/webui/src/views/PlanView.tsx b/webui/src/views/PlanView.tsx index cb302eb..a751b57 100644 --- a/webui/src/views/PlanView.tsx +++ b/webui/src/views/PlanView.tsx @@ -15,10 +15,13 @@ import { import { SpinButton } from "../components/SpinButton"; import { useShowModal } from "../components/ModalManager"; import { create } from "@bufbuild/protobuf"; +import { useConfig } from "../components/ConfigProvider"; export const PlanView = ({ plan }: React.PropsWithChildren<{ plan: Plan }>) => { + const [config, _] = useConfig(); const alertsApi = useAlertApi()!; const showModal = useShowModal(); + const repo = config?.repos.find((r) => r.id === plan.repo); const handleBackupNow = async () => { try { @@ -62,6 +65,16 @@ export const PlanView = ({ plan }: React.PropsWithChildren<{ plan: Plan }>) => { } }; + if (!repo) { + return ( + <> + + Repo {plan.repo} for plan {plan.id} not found + + + ); + } + return ( <> @@ -104,7 +117,8 @@ export const PlanView = ({ plan }: React.PropsWithChildren<{ plan: Plan }>) => { ) => { ) => { ) => { ) => { label: "Stats", children: ( Loading...
}> - + ), destroyInactiveTabPane: true, diff --git a/webui/src/views/RunCommandModal.tsx b/webui/src/views/RunCommandModal.tsx index b2e71b7..b41b192 100644 --- a/webui/src/views/RunCommandModal.tsx +++ b/webui/src/views/RunCommandModal.tsx @@ -13,6 +13,7 @@ import { } from "../../gen/ts/v1/service_pb"; import { OperationList } from "../components/OperationList"; import { create } from "@bufbuild/protobuf"; +import { useConfig } from "../components/ConfigProvider"; interface Invocation { command: string; @@ -21,6 +22,7 @@ interface Invocation { } export const RunCommandModal = ({ repoId }: { repoId: string }) => { + const [config, _] = useConfig(); const showModal = useShowModal(); const alertApi = useAlertApi()!; const [command, setCommand] = React.useState(""); @@ -83,6 +85,7 @@ export const RunCommandModal = ({ repoId }: { repoId: string }) => {