diff --git a/pkg/services/export/object_store.go b/pkg/services/export/object_store.go index 10d3ec96719..19508aebb90 100644 --- a/pkg/services/export/object_store.go +++ b/pkg/services/export/object_store.go @@ -125,9 +125,8 @@ func (e *objectStoreJob) start(ctx context.Context) { _, err = e.store.AdminWrite(ctx, &object.AdminWriteObjectRequest{ GRN: &object.GRN{ - Scope: models.ObjectStoreScopeEntity, - UID: dash.UID, - Kind: models.StandardKindDashboard, + UID: dash.UID, + Kind: models.StandardKindDashboard, }, ClearHistory: true, Version: fmt.Sprintf("%d", dash.Version), @@ -135,9 +134,11 @@ func (e *objectStoreJob) start(ctx context.Context) { UpdatedAt: dash.Updated.UnixMilli(), UpdatedBy: fmt.Sprintf("user:%d", dash.UpdatedBy), CreatedBy: fmt.Sprintf("user:%d", dash.CreatedBy), - Origin: "export-from-sql", Body: dash.Data, Comment: "(exported from SQL)", + Origin: &object.ObjectOriginInfo{ + Source: "export-from-sql", + }, }) if err != nil { e.status.Status = "error: " + err.Error() @@ -175,9 +176,8 @@ func (e *objectStoreJob) start(ctx context.Context) { _, err = e.store.Write(ctx, &object.WriteObjectRequest{ GRN: &object.GRN{ - Scope: models.ObjectStoreScopeEntity, - UID: playlist.Uid, - Kind: models.StandardKindPlaylist, + UID: playlist.Uid, + Kind: models.StandardKindPlaylist, }, Body: prettyJSON(playlist), Comment: "export from playlists", @@ -240,9 +240,8 @@ func (e *objectStoreJob) start(ctx context.Context) { _, err = e.store.Write(ctx, &object.WriteObjectRequest{ GRN: &object.GRN{ - Scope: models.ObjectStoreScopeEntity, - UID: dto.Key, - Kind: models.StandardKindSnapshot, + UID: dto.Key, + Kind: models.StandardKindSnapshot, }, Body: prettyJSON(m), Comment: "export from snapshtts", diff --git a/pkg/services/playlist/playlistimpl/object_store.go b/pkg/services/playlist/playlistimpl/object_store.go index ad7b867c0a0..81688aae78b 100644 --- a/pkg/services/playlist/playlistimpl/object_store.go +++ b/pkg/services/playlist/playlistimpl/object_store.go @@ -60,7 +60,6 @@ func (s *objectStoreImpl) sync() { TenantId: info.OrgID, UID: info.UID, Kind: models.StandardKindPlaylist, - Scope: models.ObjectStoreScopeEntity, }, Body: body, }) @@ -76,9 +75,8 @@ func (s *objectStoreImpl) Create(ctx context.Context, cmd *playlist.CreatePlayli } _, err = s.objectstore.Write(ctx, &object.WriteObjectRequest{ GRN: &object.GRN{ - Scope: models.ObjectStoreScopeEntity, - Kind: models.StandardKindPlaylist, - UID: rsp.UID, + Kind: models.StandardKindPlaylist, + UID: rsp.UID, }, Body: body, }) @@ -98,9 +96,8 @@ func (s *objectStoreImpl) Update(ctx context.Context, cmd *playlist.UpdatePlayli } _, err = s.objectstore.Write(ctx, &object.WriteObjectRequest{ GRN: &object.GRN{ - UID: rsp.Uid, - Kind: models.StandardKindPlaylist, - Scope: models.ObjectStoreScopeEntity, + UID: rsp.Uid, + Kind: models.StandardKindPlaylist, }, Body: body, }) @@ -116,9 +113,8 @@ func (s *objectStoreImpl) Delete(ctx context.Context, cmd *playlist.DeletePlayli if err == nil { _, err = s.objectstore.Delete(ctx, &object.DeleteObjectRequest{ GRN: &object.GRN{ - UID: cmd.UID, - Kind: models.StandardKindPlaylist, - Scope: models.ObjectStoreScopeEntity, + UID: cmd.UID, + Kind: models.StandardKindPlaylist, }, }) if err != nil { @@ -148,9 +144,8 @@ func (s *objectStoreImpl) GetWithoutItems(ctx context.Context, q *playlist.GetPl func (s *objectStoreImpl) Get(ctx context.Context, q *playlist.GetPlaylistByUidQuery) (*playlist.PlaylistDTO, error) { rsp, err := s.objectstore.Read(ctx, &object.ReadObjectRequest{ GRN: &object.GRN{ - UID: q.UID, - Kind: models.StandardKindPlaylist, - Scope: models.ObjectStoreScopeEntity, + UID: q.UID, + Kind: models.StandardKindPlaylist, }, WithBody: true, }) diff --git a/pkg/services/sqlstore/migrations/object_store_mig.go b/pkg/services/sqlstore/migrations/object_store_mig.go index 6f87b1d6265..b05d91ea9b7 100644 --- a/pkg/services/sqlstore/migrations/object_store_mig.go +++ b/pkg/services/sqlstore/migrations/object_store_mig.go @@ -8,33 +8,34 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -func getKeyColumn(name string, isPrimaryKey bool) *migrator.Column { +func getLatinPathColumn(name string) *migrator.Column { return &migrator.Column{ - Name: name, - Type: migrator.DB_NVarchar, - Length: 1024, - Nullable: false, - IsPrimaryKey: isPrimaryKey, - IsLatin: true, // only used in MySQL + Name: name, + Type: migrator.DB_NVarchar, + Length: 1024, + Nullable: false, + IsLatin: true, // only used in MySQL } } func addObjectStorageMigrations(mg *migrator.Migrator) { + grnLength := 256 // len(tenant)~8 + len(kind)!16 + len(kind)~128 = 256 tables := []migrator.Table{} tables = append(tables, migrator.Table{ - Name: "object", + Name: "entity", Columns: []*migrator.Column{ - // Object path contains everything required to make it unique across all instances - // orgId + scope + kind + uid - getKeyColumn("path", true), + // Object ID (OID) will be unique across all objects/instances + // uuid5( tenant_id, kind + uid ) + {Name: "grn", Type: migrator.DB_NVarchar, Length: grnLength, Nullable: false, IsPrimaryKey: true}, - // This is an optimization for listing everything at the same level in the object store - getKeyColumn("parent_folder_path", false), - - // The object type + // The entity identifier + {Name: "tenant_id", Type: migrator.DB_BigInt, Nullable: false}, {Name: "kind", Type: migrator.DB_NVarchar, Length: 255, Nullable: false}, + {Name: "uid", Type: migrator.DB_NVarchar, Length: 40, Nullable: false}, + {Name: "folder", Type: migrator.DB_NVarchar, Length: 40, Nullable: false}, + {Name: "slug", Type: migrator.DB_NVarchar, Length: 189, Nullable: false}, // from title - // The raw object body (any byte array) + // The raw entity body (any byte array) {Name: "body", Type: migrator.DB_LongBlob, Nullable: false}, {Name: "size", Type: migrator.DB_BigInt, Nullable: false}, {Name: "etag", Type: migrator.DB_NVarchar, Length: 32, Nullable: false, IsLatin: true}, // md5(body) @@ -47,7 +48,8 @@ func addObjectStorageMigrations(mg *migrator.Migrator) { {Name: "created_by", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, // Mark objects with origin metadata - {Name: "origin", Type: migrator.DB_Text, Nullable: true}, + {Name: "origin", Type: migrator.DB_NVarchar, Length: 40, Nullable: false}, + getLatinPathColumn("origin_key"), // index with length 1024 {Name: "origin_ts", Type: migrator.DB_BigInt, Nullable: false}, // Summary data (always extracted from the `body` column) @@ -58,28 +60,46 @@ func addObjectStorageMigrations(mg *migrator.Migrator) { {Name: "errors", Type: migrator.DB_Text, Nullable: true}, // JSON object }, Indices: []*migrator.Index{ - {Cols: []string{"parent_folder_path"}}, // list in folder - {Cols: []string{"kind"}}, // filter by type + {Cols: []string{"kind"}}, + {Cols: []string{"folder"}}, + {Cols: []string{"uid"}}, + + {Cols: []string{"tenant_id", "kind", "uid"}, Type: migrator.UniqueIndex}, + // {Cols: []string{"tenant_id", "folder", "slug"}, Type: migrator.UniqueIndex}, + }, + }) + + // when saving a folder, keep a path version cached + tables = append(tables, migrator.Table{ + Name: "entity_folder", + Columns: []*migrator.Column{ + {Name: "grn", Type: migrator.DB_NVarchar, Length: grnLength, Nullable: false}, + getLatinPathColumn("path"), // slug/slug/slug/... + {Name: "depth", Type: migrator.DB_Int, Nullable: false}, + {Name: "tree", Type: migrator.DB_Text, Nullable: false}, // JSON array from root + }, + Indices: []*migrator.Index{ + {Cols: []string{"path"}, Type: migrator.UniqueIndex}, }, }) tables = append(tables, migrator.Table{ - Name: "object_labels", + Name: "entity_labels", Columns: []*migrator.Column{ - getKeyColumn("path", false), + {Name: "grn", Type: migrator.DB_NVarchar, Length: grnLength, Nullable: false}, {Name: "label", Type: migrator.DB_NVarchar, Length: 191, Nullable: false}, {Name: "value", Type: migrator.DB_NVarchar, Length: 1024, Nullable: false}, }, Indices: []*migrator.Index{ - {Cols: []string{"path", "label"}, Type: migrator.UniqueIndex}, + {Cols: []string{"grn", "label"}, Type: migrator.UniqueIndex}, }, }) tables = append(tables, migrator.Table{ - Name: "object_ref", + Name: "entity_ref", Columns: []*migrator.Column{ // Source: - getKeyColumn("path", false), + {Name: "grn", Type: migrator.DB_NVarchar, Length: grnLength, Nullable: false}, // Address (defined in the body, not resolved, may be invalid and change) {Name: "kind", Type: migrator.DB_NVarchar, Length: 255, Nullable: false}, @@ -88,21 +108,21 @@ func addObjectStorageMigrations(mg *migrator.Migrator) { // Runtime calcs (will depend on the system state) {Name: "resolved_ok", Type: migrator.DB_Bool, Nullable: false}, - getKeyColumn("resolved_to", false), + {Name: "resolved_to", Type: migrator.DB_NVarchar, Length: 40, Nullable: false}, {Name: "resolved_warning", Type: migrator.DB_NVarchar, Length: 255, Nullable: false}, {Name: "resolved_time", Type: migrator.DB_DateTime, Nullable: false}, // resolution cache timestamp }, Indices: []*migrator.Index{ - {Cols: []string{"path"}, Type: migrator.IndexType}, + {Cols: []string{"grn"}, Type: migrator.IndexType}, {Cols: []string{"kind"}, Type: migrator.IndexType}, {Cols: []string{"resolved_to"}, Type: migrator.IndexType}, }, }) tables = append(tables, migrator.Table{ - Name: "object_history", + Name: "entity_history", Columns: []*migrator.Column{ - getKeyColumn("path", false), + {Name: "grn", Type: migrator.DB_NVarchar, Length: grnLength, Nullable: false}, {Name: "version", Type: migrator.DB_NVarchar, Length: 128, Nullable: false}, // Raw bytes @@ -118,7 +138,7 @@ func addObjectStorageMigrations(mg *migrator.Migrator) { {Name: "message", Type: migrator.DB_Text, Nullable: false}, // defaults to empty string }, Indices: []*migrator.Index{ - {Cols: []string{"path", "version"}, Type: migrator.UniqueIndex}, + {Cols: []string{"grn", "version"}, Type: migrator.UniqueIndex}, {Cols: []string{"updated_by"}, Type: migrator.IndexType}, }, }) @@ -134,25 +154,28 @@ func addObjectStorageMigrations(mg *migrator.Migrator) { // Migration cleanups: given that this is a complex setup // that requires a lot of testing before we are ready to push out of dev // this script lets us easy wipe previous changes and initialize clean tables - suffix := " (v5)" // change this when we want to wipe and reset the object tables - mg.AddMigration("ObjectStore init: cleanup"+suffix, migrator.NewRawSQLMigration(strings.TrimSpace(` + suffix := " (v8)" // change this when we want to wipe and reset the object tables + mg.AddMigration("EntityStore init: cleanup"+suffix, migrator.NewRawSQLMigration(strings.TrimSpace(` + DELETE FROM migration_log WHERE migration_id LIKE 'EntityStore init%'; + `))) + // for a while this was called "ObjectStore"... this can be removed before we remove the dev only flags + mg.AddMigration("EntityStore init: object cleanup"+suffix, migrator.NewRawSQLMigration(strings.TrimSpace(` DELETE FROM migration_log WHERE migration_id LIKE 'ObjectStore init%'; `))) // Initialize all tables for t := range tables { - mg.AddMigration("ObjectStore init: drop "+tables[t].Name+suffix, migrator.NewRawSQLMigration( + mg.AddMigration("EntityStore init: drop "+tables[t].Name+suffix, migrator.NewRawSQLMigration( fmt.Sprintf("DROP TABLE IF EXISTS %s", tables[t].Name), )) - mg.AddMigration("ObjectStore init: table "+tables[t].Name+suffix, migrator.NewAddTableMigration(tables[t])) + mg.AddMigration("EntityStore init: table "+tables[t].Name+suffix, migrator.NewAddTableMigration(tables[t])) for i := range tables[t].Indices { - mg.AddMigration(fmt.Sprintf("ObjectStore init: index %s[%d]"+suffix, tables[t].Name, i), migrator.NewAddIndexMigration(tables[t], tables[t].Indices[i])) + mg.AddMigration(fmt.Sprintf("EntityStore init: index %s[%d]"+suffix, tables[t].Name, i), migrator.NewAddIndexMigration(tables[t], tables[t].Indices[i])) } } - // TODO: add collation support to `migrator.Column` - mg.AddMigration("ObjectStore init: set path collation in object tables"+suffix, migrator.NewRawSQLMigration(""). + mg.AddMigration("EntityStore init: set path collation in entity tables"+suffix, migrator.NewRawSQLMigration(""). // MySQL `utf8mb4_unicode_ci` collation is set in `mysql_dialect.go` // SQLite uses a `BINARY` collation by default - Postgres("ALTER TABLE object ALTER COLUMN path TYPE VARCHAR(1024) COLLATE \"C\";")) // Collate C - sorting done based on character code byte values + Postgres("ALTER TABLE entity_folder ALTER COLUMN path TYPE VARCHAR(1024) COLLATE \"C\";")) // Collate C - sorting done based on character code byte values } diff --git a/pkg/services/store/object/grn.go b/pkg/services/store/object/grn.go index 28d48fc3a12..ad6c7044547 100644 --- a/pkg/services/store/object/grn.go +++ b/pkg/services/store/object/grn.go @@ -1,5 +1,9 @@ package object +import ( + "fmt" +) + // Check if the two GRNs reference to the same object // we can not use simple `*x == *b` because of the internal settings func (x *GRN) Equals(b *GRN) bool { @@ -7,9 +11,11 @@ func (x *GRN) Equals(b *GRN) bool { return false } return x == b || (x.TenantId == b.TenantId && - x.Scope == b.Scope && x.Kind == b.Kind && x.UID == b.UID) } -// TODO: this should interpoerate with the GRN string flavor +// Set an OID based on the GRN +func (x *GRN) ToGRNString() string { + return fmt.Sprintf("grn:%d/%s/%s", x.TenantId, x.Kind, x.UID) +} diff --git a/pkg/services/store/object/httpobjectstore/service.go b/pkg/services/store/object/httpobjectstore/service.go index 29bb7c6734f..1b5e585795f 100644 --- a/pkg/services/store/object/httpobjectstore/service.go +++ b/pkg/services/store/object/httpobjectstore/service.go @@ -12,7 +12,6 @@ import ( "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/services/store/kind" "github.com/grafana/grafana/pkg/services/store/object" - "github.com/grafana/grafana/pkg/services/store/router" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" @@ -27,18 +26,16 @@ type HTTPObjectStore interface { } type httpObjectStore struct { - store object.ObjectStoreServer - log log.Logger - kinds kind.KindRegistry - router router.ObjectStoreRouter + store object.ObjectStoreServer + log log.Logger + kinds kind.KindRegistry } func ProvideHTTPObjectStore(store object.ObjectStoreServer, kinds kind.KindRegistry) HTTPObjectStore { return &httpObjectStore{ - store: store, - log: log.New("http-object-store"), - kinds: kinds, - router: router.NewObjectStoreRouter(kinds), + store: store, + log: log.New("http-object-store"), + kinds: kinds, } } @@ -48,16 +45,16 @@ func (s *httpObjectStore) RegisterHTTPRoutes(route routing.RouteRegister) { reqGrafanaAdmin := middleware.ReqSignedIn //.ReqGrafanaAdmin // Every * must parse to a GRN (uid+kind) - route.Get("/store/*", reqGrafanaAdmin, routing.Wrap(s.doGetObject)) - route.Post("/store/*", reqGrafanaAdmin, routing.Wrap(s.doWriteObject)) - route.Delete("/store/*", reqGrafanaAdmin, routing.Wrap(s.doDeleteObject)) - route.Get("/raw/*", reqGrafanaAdmin, routing.Wrap(s.doGetRawObject)) - route.Get("/history/*", reqGrafanaAdmin, routing.Wrap(s.doGetHistory)) - route.Get("/list/*", reqGrafanaAdmin, routing.Wrap(s.doListFolder)) // Simplified version of search -- path is prefix + route.Get("/store/:kind/:uid", reqGrafanaAdmin, routing.Wrap(s.doGetObject)) + route.Post("/store/:kind/:uid", reqGrafanaAdmin, routing.Wrap(s.doWriteObject)) + route.Delete("/store/:kind/:uid", reqGrafanaAdmin, routing.Wrap(s.doDeleteObject)) + route.Get("/raw/:kind/:uid", reqGrafanaAdmin, routing.Wrap(s.doGetRawObject)) + route.Get("/history/:kind/:uid", reqGrafanaAdmin, routing.Wrap(s.doGetHistory)) + route.Get("/list/:uid", reqGrafanaAdmin, routing.Wrap(s.doListFolder)) // Simplified version of search -- path is prefix route.Get("/search", reqGrafanaAdmin, routing.Wrap(s.doSearch)) // File upload - route.Post("/upload/:scope", reqGrafanaAdmin, routing.Wrap(s.doUpload)) + route.Post("/upload", reqGrafanaAdmin, routing.Wrap(s.doUpload)) } // This function will extract UID+Kind from the requested path "*" in our router @@ -65,12 +62,6 @@ func (s *httpObjectStore) RegisterHTTPRoutes(route routing.RouteRegister) { // This will quickly be revisited as we explore how to encode UID+Kind in a "GRN" format func (s *httpObjectStore) getGRNFromRequest(c *models.ReqContext) (*object.GRN, map[string]string, error) { params := web.Params(c.Req) - info, err := s.router.RouteFromKey(c.Req.Context(), - fmt.Sprintf("%d/%s", c.OrgID, params["*"])) - if err != nil { - return nil, params, err - } - // Read parameters that are encoded in the URL vals := c.Req.URL.Query() for k, v := range vals { @@ -78,7 +69,11 @@ func (s *httpObjectStore) getGRNFromRequest(c *models.ReqContext) (*object.GRN, params[k] = v[0] } } - return info.GRN, params, nil + return &object.GRN{ + TenantId: c.OrgID, + Kind: params[":kind"], + UID: params[":uid"], + }, params, nil } func (s *httpObjectStore) doGetObject(c *models.ReqContext) response.Response { @@ -182,6 +177,7 @@ func (s *httpObjectStore) doWriteObject(c *models.ReqContext) response.Response rsp, err := s.store.Write(c.Req.Context(), &object.WriteObjectRequest{ GRN: grn, Body: b, + Folder: params["folder"], Comment: params["comment"], PreviousVersion: params["previousVersion"], }) @@ -233,10 +229,6 @@ func (s *httpObjectStore) doUpload(c *models.ReqContext) response.Response { if len(fileinfo) < 1 { return response.Error(400, "missing files", nil) } - scope := web.Params(c.Req)[":scope"] - if scope == "" { - return response.Error(400, "invalid scope", nil) - } var rsp []*object.WriteObjectResponse @@ -257,12 +249,7 @@ func (s *httpObjectStore) doUpload(c *models.ReqContext) response.Response { if err != nil || kind.ID == "" { return response.Error(400, "Unsupported kind: "+fileHeader.Filename, err) } - uid := folder - if uid != "" { - uid = uid + "/" + fileHeader.Filename[:idx] - } else { - uid = fileHeader.Filename[:idx] - } + uid := fileHeader.Filename[:idx] file, err := fileHeader.Open() if err != nil { @@ -278,9 +265,9 @@ func (s *httpObjectStore) doUpload(c *models.ReqContext) response.Response { } grn := &object.GRN{ - Scope: scope, - UID: uid, - Kind: kind.ID, + UID: uid, + Kind: kind.ID, + TenantId: c.OrgID, } if !overwriteExistingFile { @@ -301,6 +288,7 @@ func (s *httpObjectStore) doUpload(c *models.ReqContext) response.Response { GRN: grn, Body: data, Comment: message, + Folder: folder, // PreviousVersion: params["previousVersion"], }) diff --git a/pkg/services/store/object/json.go b/pkg/services/store/object/json.go index b3c4e20d839..887ceddc2e0 100644 --- a/pkg/services/store/object/json.go +++ b/pkg/services/store/object/json.go @@ -96,6 +96,16 @@ func (codec *rawObjectCodec) Encode(ptr unsafe.Pointer, stream *jsoniter.Stream) stream.WriteObjectField("etag") stream.WriteString(obj.ETag) } + if obj.Folder != "" { + stream.WriteMore() + stream.WriteObjectField("folder") + stream.WriteString(obj.Folder) + } + if obj.Slug != "" { + stream.WriteMore() + stream.WriteObjectField("slug") + stream.WriteString(obj.Slug) + } if obj.Size > 0 { stream.WriteMore() stream.WriteObjectField("size") @@ -135,6 +145,10 @@ func readRawObject(iter *jsoniter.Iterator, raw *RawObject) { raw.Size = iter.ReadInt64() case "etag": raw.ETag = iter.ReadString() + case "folder": + raw.Folder = iter.ReadString() + case "slug": + raw.Slug = iter.ReadString() case "version": raw.Version = iter.ReadString() case "origin": diff --git a/pkg/services/store/object/object.pb.go b/pkg/services/store/object/object.pb.go index b37ab17330d..0b8443619e1 100644 --- a/pkg/services/store/object/object.pb.go +++ b/pkg/services/store/object/object.pb.go @@ -80,15 +80,11 @@ type GRN struct { // the tenant/org id TenantId int64 `protobuf:"varint,1,opt,name=tenant_id,json=tenantId,proto3" json:"tenant_id,omitempty"` - // mabybe "namespace"? valid values include - // * entity - // * drive - // * service/xyz - Scope string `protobuf:"bytes,2,opt,name=scope,proto3" json:"scope,omitempty"` // Identify the object kind. This kind will be used to apply a schema to the body and // will trigger additional indexing behavior. Kind string `protobuf:"bytes,3,opt,name=kind,proto3" json:"kind,omitempty"` // Unique ID + // less then 40, no slashes or other special characters UID string `protobuf:"bytes,4,opt,name=UID,proto3" json:"UID,omitempty"` } @@ -131,13 +127,6 @@ func (x *GRN) GetTenantId() int64 { return 0 } -func (x *GRN) GetScope() string { - if x != nil { - return x.Scope - } - return "" -} - func (x *GRN) GetKind() string { if x != nil { return x.Kind @@ -174,12 +163,16 @@ type RawObject struct { ETag string `protobuf:"bytes,7,opt,name=ETag,proto3" json:"ETag,omitempty"` // Raw bytes of the storage object. The kind will determine what is a valid payload Body []byte `protobuf:"bytes,8,opt,name=body,proto3" json:"body,omitempty"` + // Folder UID + Folder string `protobuf:"bytes,9,opt,name=folder,proto3" json:"folder,omitempty"` + // Unique slug within folder (may be UID) + Slug string `protobuf:"bytes,10,opt,name=slug,proto3" json:"slug,omitempty"` // The version will change when the object is saved. It is not necessarily sortable // // NOTE: currently managed by the dashboard+dashboard_version tables - Version string `protobuf:"bytes,9,opt,name=version,proto3" json:"version,omitempty"` + Version string `protobuf:"bytes,11,opt,name=version,proto3" json:"version,omitempty"` // External location info - Origin *ObjectOriginInfo `protobuf:"bytes,10,opt,name=origin,proto3" json:"origin,omitempty"` + Origin *ObjectOriginInfo `protobuf:"bytes,12,opt,name=origin,proto3" json:"origin,omitempty"` } func (x *RawObject) Reset() { @@ -270,6 +263,20 @@ func (x *RawObject) GetBody() []byte { return nil } +func (x *RawObject) GetFolder() string { + if x != nil { + return x.Folder + } + return "" +} + +func (x *RawObject) GetSlug() string { + if x != nil { + return x.Slug + } + return "" +} + func (x *RawObject) GetVersion() string { if x != nil { return x.Version @@ -291,8 +298,10 @@ type ObjectOriginInfo struct { // NOTE: currently managed by the dashboard_provisioning table Source string `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` + // Key in the upstream system + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` // Time in epoch milliseconds that the object was last synced with an external system (provisioning/git) - Time int64 `protobuf:"varint,2,opt,name=time,proto3" json:"time,omitempty"` + Time int64 `protobuf:"varint,3,opt,name=time,proto3" json:"time,omitempty"` } func (x *ObjectOriginInfo) Reset() { @@ -334,6 +343,13 @@ func (x *ObjectOriginInfo) GetSource() string { return "" } +func (x *ObjectOriginInfo) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + func (x *ObjectOriginInfo) GetTime() int64 { if x != nil { return x.Time @@ -738,12 +754,14 @@ type WriteObjectRequest struct { // Object identifier GRN *GRN `protobuf:"bytes,1,opt,name=GRN,proto3" json:"GRN,omitempty"` + // Where to save the object (empty will leave it unchanged) + Folder string `protobuf:"bytes,2,opt,name=folder,proto3" json:"folder,omitempty"` // The raw object body - Body []byte `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"` + Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"` // Message that can be seen when exploring object history - Comment string `protobuf:"bytes,3,opt,name=comment,proto3" json:"comment,omitempty"` + Comment string `protobuf:"bytes,4,opt,name=comment,proto3" json:"comment,omitempty"` // Used for optimistic locking. If missing, the previous version will be replaced regardless - PreviousVersion string `protobuf:"bytes,4,opt,name=previous_version,json=previousVersion,proto3" json:"previous_version,omitempty"` + PreviousVersion string `protobuf:"bytes,5,opt,name=previous_version,json=previousVersion,proto3" json:"previous_version,omitempty"` } func (x *WriteObjectRequest) Reset() { @@ -785,6 +803,13 @@ func (x *WriteObjectRequest) GetGRN() *GRN { return nil } +func (x *WriteObjectRequest) GetFolder() string { + if x != nil { + return x.Folder + } + return "" +} + func (x *WriteObjectRequest) GetBody() []byte { if x != nil { return x.Body @@ -816,33 +841,35 @@ type AdminWriteObjectRequest struct { // Object identifier GRN *GRN `protobuf:"bytes,1,opt,name=GRN,proto3" json:"GRN,omitempty"` + // Where to save the object (empty will leave it unchanged) + Folder string `protobuf:"bytes,2,opt,name=folder,proto3" json:"folder,omitempty"` // The raw object body - Body []byte `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"` + Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"` // Message that can be seen when exploring object history - Comment string `protobuf:"bytes,3,opt,name=comment,proto3" json:"comment,omitempty"` + Comment string `protobuf:"bytes,4,opt,name=comment,proto3" json:"comment,omitempty"` // Time in epoch milliseconds that the object was created // Optional, if 0 it will use the current time - CreatedAt int64 `protobuf:"varint,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + CreatedAt int64 `protobuf:"varint,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` // Time in epoch milliseconds that the object was updated // Optional, if empty it will use the current user - UpdatedAt int64 `protobuf:"varint,5,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + UpdatedAt int64 `protobuf:"varint,6,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` // Who created the object // Optional, if 0 it will use the current time - CreatedBy string `protobuf:"bytes,6,opt,name=created_by,json=createdBy,proto3" json:"created_by,omitempty"` + CreatedBy string `protobuf:"bytes,7,opt,name=created_by,json=createdBy,proto3" json:"created_by,omitempty"` // Who updated the object // Optional, if empty it will use the current user - UpdatedBy string `protobuf:"bytes,7,opt,name=updated_by,json=updatedBy,proto3" json:"updated_by,omitempty"` + UpdatedBy string `protobuf:"bytes,8,opt,name=updated_by,json=updatedBy,proto3" json:"updated_by,omitempty"` // An explicit version identifier // Optional, if set, this will overwrite/define an explicit version - Version string `protobuf:"bytes,8,opt,name=version,proto3" json:"version,omitempty"` + Version string `protobuf:"bytes,9,opt,name=version,proto3" json:"version,omitempty"` // Used for optimistic locking. If missing, the previous version will be replaced regardless // This may not be used along with an explicit version in the request - PreviousVersion string `protobuf:"bytes,9,opt,name=previous_version,json=previousVersion,proto3" json:"previous_version,omitempty"` + PreviousVersion string `protobuf:"bytes,10,opt,name=previous_version,json=previousVersion,proto3" json:"previous_version,omitempty"` // Request that all previous versions are removed from the history // This will make sense for systems that manage history explicitly externallay - ClearHistory bool `protobuf:"varint,10,opt,name=clear_history,json=clearHistory,proto3" json:"clear_history,omitempty"` + ClearHistory bool `protobuf:"varint,11,opt,name=clear_history,json=clearHistory,proto3" json:"clear_history,omitempty"` // Optionally define where the object came from - Origin string `protobuf:"bytes,11,opt,name=origin,proto3" json:"origin,omitempty"` + Origin *ObjectOriginInfo `protobuf:"bytes,12,opt,name=origin,proto3" json:"origin,omitempty"` } func (x *AdminWriteObjectRequest) Reset() { @@ -884,6 +911,13 @@ func (x *AdminWriteObjectRequest) GetGRN() *GRN { return nil } +func (x *AdminWriteObjectRequest) GetFolder() string { + if x != nil { + return x.Folder + } + return "" +} + func (x *AdminWriteObjectRequest) GetBody() []byte { if x != nil { return x.Body @@ -947,11 +981,11 @@ func (x *AdminWriteObjectRequest) GetClearHistory() bool { return false } -func (x *AdminWriteObjectRequest) GetOrigin() string { +func (x *AdminWriteObjectRequest) GetOrigin() *ObjectOriginInfo { if x != nil { return x.Origin } - return "" + return nil } type WriteObjectResponse struct { @@ -1427,10 +1461,14 @@ type ObjectSearchResult struct { Description string `protobuf:"bytes,8,opt,name=description,proto3" json:"description,omitempty"` // The structured labels Labels map[string]string `protobuf:"bytes,9,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Folder UID + Folder string `protobuf:"bytes,10,opt,name=folder,proto3" json:"folder,omitempty"` + // Slugified name + Slug string `protobuf:"bytes,11,opt,name=slug,proto3" json:"slug,omitempty"` // Optionally include extracted JSON - FieldsJson []byte `protobuf:"bytes,10,opt,name=fields_json,json=fieldsJson,proto3" json:"fields_json,omitempty"` + FieldsJson []byte `protobuf:"bytes,12,opt,name=fields_json,json=fieldsJson,proto3" json:"fields_json,omitempty"` // ObjectErrorInfo in json - ErrorJson []byte `protobuf:"bytes,11,opt,name=error_json,json=errorJson,proto3" json:"error_json,omitempty"` + ErrorJson []byte `protobuf:"bytes,13,opt,name=error_json,json=errorJson,proto3" json:"error_json,omitempty"` } func (x *ObjectSearchResult) Reset() { @@ -1528,6 +1566,20 @@ func (x *ObjectSearchResult) GetLabels() map[string]string { return nil } +func (x *ObjectSearchResult) GetFolder() string { + if x != nil { + return x.Folder + } + return "" +} + +func (x *ObjectSearchResult) GetSlug() string { + if x != nil { + return x.Slug + } + return "" +} + func (x *ObjectSearchResult) GetFieldsJson() []byte { if x != nil { return x.FieldsJson @@ -1602,252 +1654,261 @@ var File_object_proto protoreflect.FileDescriptor var file_object_proto_rawDesc = []byte{ 0x0a, 0x0c, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x5e, 0x0a, 0x03, 0x47, 0x52, 0x4e, 0x12, 0x1b, 0x0a, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x48, 0x0a, 0x03, 0x47, 0x52, 0x4e, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, - 0x6f, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, - 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x55, 0x49, 0x44, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x55, 0x49, 0x44, 0x22, 0xae, 0x02, 0x0a, 0x09, 0x52, 0x61, 0x77, 0x4f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1d, 0x0a, 0x03, 0x47, 0x52, 0x4e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x52, 0x4e, 0x52, 0x03, - 0x47, 0x52, 0x4e, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, - 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, - 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, - 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, - 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x45, 0x54, 0x61, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x45, 0x54, 0x61, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, - 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x22, 0x3e, 0x0a, 0x10, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x62, 0x0a, 0x0f, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, - 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x18, - 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x74, 0x61, - 0x69, 0x6c, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, - 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x4a, 0x73, 0x6f, 0x6e, 0x22, 0xad, 0x01, 0x0a, 0x11, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, - 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x52, 0x08, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, + 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x10, + 0x0a, 0x03, 0x55, 0x49, 0x44, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x49, 0x44, + 0x22, 0xda, 0x02, 0x0a, 0x09, 0x52, 0x61, 0x77, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x1d, + 0x0a, 0x03, 0x47, 0x52, 0x4e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x52, 0x4e, 0x52, 0x03, 0x47, 0x52, 0x4e, 0x12, 0x1d, 0x0a, + 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x45, 0x54, 0x61, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x45, 0x54, 0x61, - 0x67, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x8c, 0x01, 0x0a, 0x11, - 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1d, 0x0a, 0x03, 0x47, 0x52, 0x4e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, - 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x52, 0x4e, 0x52, 0x03, 0x47, 0x52, 0x4e, - 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x69, - 0x74, 0x68, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x77, - 0x69, 0x74, 0x68, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x69, 0x74, 0x68, 0x5f, - 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x77, - 0x69, 0x74, 0x68, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x22, 0x62, 0x0a, 0x12, 0x52, 0x65, - 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x29, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x11, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x52, 0x61, 0x77, 0x4f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, - 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0b, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x4a, 0x73, 0x6f, 0x6e, 0x22, 0x49, - 0x0a, 0x16, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x05, 0x62, 0x61, 0x74, 0x63, - 0x68, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x2e, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x52, 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x22, 0x4f, 0x0a, 0x17, 0x42, 0x61, 0x74, - 0x63, 0x68, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x52, - 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x12, 0x57, - 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1d, 0x0a, 0x03, 0x47, 0x52, 0x4e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, - 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x52, 0x4e, 0x52, 0x03, 0x47, 0x52, 0x4e, - 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x45, 0x54, 0x61, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x45, 0x54, 0x61, + 0x67, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, + 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, + 0x67, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x06, 0x6f, + 0x72, 0x69, 0x67, 0x69, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4f, 0x72, 0x69, 0x67, 0x69, + 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x22, 0x50, 0x0a, + 0x10, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, + 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x22, + 0x62, 0x0a, 0x0f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, + 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x4a, + 0x73, 0x6f, 0x6e, 0x22, 0xad, 0x01, 0x0a, 0x11, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, + 0x79, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x45, 0x54, 0x61, 0x67, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x45, 0x54, 0x61, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, + 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, + 0x65, 0x6e, 0x74, 0x22, 0x8c, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x03, 0x47, 0x52, 0x4e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, + 0x47, 0x52, 0x4e, 0x52, 0x03, 0x47, 0x52, 0x4e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x77, 0x69, 0x74, 0x68, 0x42, 0x6f, 0x64, 0x79, 0x12, + 0x21, 0x0a, 0x0c, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x77, 0x69, 0x74, 0x68, 0x53, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x79, 0x22, 0x62, 0x0a, 0x12, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x29, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x2e, 0x52, 0x61, 0x77, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x06, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x6a, + 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x79, 0x4a, 0x73, 0x6f, 0x6e, 0x22, 0x49, 0x0a, 0x16, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, + 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x2f, 0x0a, 0x05, 0x62, 0x61, 0x74, 0x63, 0x68, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x05, 0x62, 0x61, 0x74, 0x63, + 0x68, 0x22, 0x4f, 0x0a, 0x17, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x07, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x73, 0x22, 0xa4, 0x01, 0x0a, 0x12, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x03, 0x47, 0x52, 0x4e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, + 0x47, 0x52, 0x4e, 0x52, 0x03, 0x47, 0x52, 0x4e, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, + 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, + 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x29, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, - 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xe4, 0x02, 0x0a, 0x17, 0x41, 0x64, + 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, + 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x96, 0x03, 0x0a, 0x17, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x03, 0x47, 0x52, 0x4e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x52, 0x4e, 0x52, - 0x03, 0x47, 0x52, 0x4e, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, - 0x65, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, - 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, - 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, - 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x07, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x18, - 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x72, 0x65, 0x76, - 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x68, 0x69, 0x73, - 0x74, 0x6f, 0x72, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x63, 0x6c, 0x65, 0x61, - 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x72, 0x69, 0x67, - 0x69, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, - 0x22, 0xb3, 0x02, 0x0a, 0x13, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x03, 0x47, 0x52, 0x4e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x52, - 0x4e, 0x52, 0x03, 0x47, 0x52, 0x4e, 0x12, 0x31, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, - 0x6f, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0b, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x3a, 0x0a, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3c, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x0b, 0x0a, - 0x07, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x50, - 0x44, 0x41, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x43, 0x48, 0x41, - 0x4e, 0x47, 0x45, 0x44, 0x10, 0x03, 0x22, 0x5f, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, - 0x03, 0x47, 0x52, 0x4e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x2e, 0x47, 0x52, 0x4e, 0x52, 0x03, 0x47, 0x52, 0x4e, 0x12, 0x29, 0x0a, 0x10, - 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x26, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x0e, 0x0a, 0x02, 0x4f, 0x4b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x4f, 0x4b, 0x22, - 0x73, 0x0a, 0x14, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x03, 0x47, 0x52, 0x4e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x52, - 0x4e, 0x52, 0x03, 0x47, 0x52, 0x4e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x0a, 0x0f, - 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x95, 0x01, 0x0a, 0x15, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, - 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1d, - 0x0a, 0x03, 0x47, 0x52, 0x4e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x52, 0x4e, 0x52, 0x03, 0x47, 0x52, 0x4e, 0x12, 0x35, 0x0a, - 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x19, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, - 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x84, 0x03, 0x0a, - 0x13, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, - 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, - 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, - 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, - 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x16, 0x0a, 0x06, - 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, - 0x6c, 0x64, 0x65, 0x72, 0x12, 0x3f, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x06, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, - 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x07, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x69, 0x74, - 0x68, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x77, 0x69, - 0x74, 0x68, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x6c, - 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x77, 0x69, 0x74, - 0x68, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x69, 0x74, 0x68, 0x5f, - 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x77, 0x69, - 0x74, 0x68, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, - 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, - 0x02, 0x38, 0x01, 0x22, 0xa4, 0x03, 0x0a, 0x12, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x65, - 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1d, 0x0a, 0x03, 0x47, 0x52, - 0x4e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, - 0x2e, 0x47, 0x52, 0x4e, 0x52, 0x03, 0x47, 0x52, 0x4e, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x62, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, - 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x3e, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x26, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, - 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, - 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, - 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x0a, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x4a, 0x73, 0x6f, 0x6e, - 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x0b, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4a, 0x73, 0x6f, 0x6e, 0x1a, - 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x74, 0x0a, 0x14, 0x4f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x34, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, - 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, - 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, - 0x32, 0xfa, 0x03, 0x0a, 0x0b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, - 0x12, 0x3d, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x19, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x52, 0x65, 0x61, - 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x4c, 0x0a, 0x09, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, 0x64, 0x12, 0x1e, 0x2e, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, 0x64, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, 0x64, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, - 0x05, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x1a, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, - 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x57, 0x72, 0x69, 0x74, - 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x43, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x07, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, - 0x1c, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, - 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x69, 0x73, - 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x06, - 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x1b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, + 0x03, 0x47, 0x52, 0x4e, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, + 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, + 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x29, 0x0a, 0x10, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x65, 0x76, + 0x69, 0x6f, 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x63, + 0x6c, 0x65, 0x61, 0x72, 0x5f, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x0b, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0c, 0x63, 0x6c, 0x65, 0x61, 0x72, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, + 0x12, 0x30, 0x0a, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x18, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x6f, 0x72, 0x69, 0x67, + 0x69, 0x6e, 0x22, 0xb3, 0x02, 0x0a, 0x13, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x03, 0x47, 0x52, 0x4e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, + 0x47, 0x52, 0x4e, 0x52, 0x03, 0x47, 0x52, 0x4e, 0x12, 0x31, 0x0a, 0x06, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, + 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x73, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0b, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x3a, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, + 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3c, 0x0a, 0x06, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, + 0x0b, 0x0a, 0x07, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, + 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x43, + 0x48, 0x41, 0x4e, 0x47, 0x45, 0x44, 0x10, 0x03, 0x22, 0x5f, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x1d, 0x0a, 0x03, 0x47, 0x52, 0x4e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x52, 0x4e, 0x52, 0x03, 0x47, 0x52, 0x4e, 0x12, 0x29, + 0x0a, 0x10, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, + 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x26, 0x0a, 0x14, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x4f, 0x4b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x02, 0x4f, + 0x4b, 0x22, 0x73, 0x0a, 0x14, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, + 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x03, 0x47, 0x52, 0x4e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, + 0x47, 0x52, 0x4e, 0x52, 0x03, 0x47, 0x52, 0x4e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x26, + 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, + 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x95, 0x01, 0x0a, 0x15, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x1d, 0x0a, 0x03, 0x47, 0x52, 0x4e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x47, 0x52, 0x4e, 0x52, 0x03, 0x47, 0x52, 0x4e, 0x12, + 0x35, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x84, + 0x03, 0x0a, 0x13, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, + 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, + 0x6e, 0x64, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x3f, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, + 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x4a, 0x0a, 0x0a, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, - 0x1f, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x57, 0x72, + 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, + 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x77, + 0x69, 0x74, 0x68, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, + 0x77, 0x69, 0x74, 0x68, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x69, 0x74, 0x68, + 0x5f, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x77, + 0x69, 0x74, 0x68, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x69, 0x74, + 0x68, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, + 0x77, 0x69, 0x74, 0x68, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xd0, 0x03, 0x0a, 0x12, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x1d, 0x0a, 0x03, + 0x47, 0x52, 0x4e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x2e, 0x47, 0x52, 0x4e, 0x52, 0x03, 0x47, 0x52, 0x4e, 0x12, 0x18, 0x0a, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x3e, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x26, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x2e, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x73, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, + 0x67, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x1f, 0x0a, + 0x0b, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0a, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x4a, 0x73, 0x6f, 0x6e, 0x12, 0x1d, + 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4a, 0x73, 0x6f, 0x6e, 0x1a, 0x39, 0x0a, + 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x74, 0x0a, 0x14, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x34, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x32, 0xfa, + 0x03, 0x0a, 0x0b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x3d, + 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x19, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, + 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1a, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x4f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4c, 0x0a, + 0x09, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, 0x64, 0x12, 0x1e, 0x2e, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x61, 0x64, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x40, 0x0a, 0x05, 0x57, + 0x72, 0x69, 0x74, 0x65, 0x12, 0x1a, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x5e, 0x0a, - 0x10, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x41, 0x64, 0x6d, 0x69, - 0x6e, 0x12, 0x4a, 0x0a, 0x0a, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, - 0x1f, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x57, 0x72, - 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0b, 0x5a, - 0x09, 0x2e, 0x2f, 0x3b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, + 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x46, 0x0a, 0x07, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1c, 0x2e, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x69, 0x73, + 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f, + 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x53, 0x65, + 0x61, 0x72, 0x63, 0x68, 0x12, 0x1b, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1c, 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x4a, 0x0a, 0x0a, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x1f, 0x2e, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x57, 0x72, 0x69, 0x74, + 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, + 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x5e, 0x0a, 0x10, 0x4f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x12, + 0x4a, 0x0a, 0x0a, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x1f, 0x2e, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x57, 0x72, 0x69, 0x74, + 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, + 0x2e, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x57, 0x72, 0x69, 0x74, 0x65, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0b, 0x5a, 0x09, 0x2e, + 0x2f, 0x3b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1897,39 +1958,40 @@ var file_object_proto_depIdxs = []int32{ 7, // 5: object.BatchReadObjectResponse.results:type_name -> object.ReadObjectResponse 1, // 6: object.WriteObjectRequest.GRN:type_name -> object.GRN 1, // 7: object.AdminWriteObjectRequest.GRN:type_name -> object.GRN - 4, // 8: object.WriteObjectResponse.error:type_name -> object.ObjectErrorInfo - 1, // 9: object.WriteObjectResponse.GRN:type_name -> object.GRN - 5, // 10: object.WriteObjectResponse.object:type_name -> object.ObjectVersionInfo - 0, // 11: object.WriteObjectResponse.status:type_name -> object.WriteObjectResponse.Status - 1, // 12: object.DeleteObjectRequest.GRN:type_name -> object.GRN - 1, // 13: object.ObjectHistoryRequest.GRN:type_name -> object.GRN - 1, // 14: object.ObjectHistoryResponse.GRN:type_name -> object.GRN - 5, // 15: object.ObjectHistoryResponse.versions:type_name -> object.ObjectVersionInfo - 20, // 16: object.ObjectSearchRequest.labels:type_name -> object.ObjectSearchRequest.LabelsEntry - 1, // 17: object.ObjectSearchResult.GRN:type_name -> object.GRN - 21, // 18: object.ObjectSearchResult.labels:type_name -> object.ObjectSearchResult.LabelsEntry - 18, // 19: object.ObjectSearchResponse.results:type_name -> object.ObjectSearchResult - 6, // 20: object.ObjectStore.Read:input_type -> object.ReadObjectRequest - 8, // 21: object.ObjectStore.BatchRead:input_type -> object.BatchReadObjectRequest - 10, // 22: object.ObjectStore.Write:input_type -> object.WriteObjectRequest - 13, // 23: object.ObjectStore.Delete:input_type -> object.DeleteObjectRequest - 15, // 24: object.ObjectStore.History:input_type -> object.ObjectHistoryRequest - 17, // 25: object.ObjectStore.Search:input_type -> object.ObjectSearchRequest - 11, // 26: object.ObjectStore.AdminWrite:input_type -> object.AdminWriteObjectRequest - 11, // 27: object.ObjectStoreAdmin.AdminWrite:input_type -> object.AdminWriteObjectRequest - 7, // 28: object.ObjectStore.Read:output_type -> object.ReadObjectResponse - 9, // 29: object.ObjectStore.BatchRead:output_type -> object.BatchReadObjectResponse - 12, // 30: object.ObjectStore.Write:output_type -> object.WriteObjectResponse - 14, // 31: object.ObjectStore.Delete:output_type -> object.DeleteObjectResponse - 16, // 32: object.ObjectStore.History:output_type -> object.ObjectHistoryResponse - 19, // 33: object.ObjectStore.Search:output_type -> object.ObjectSearchResponse - 12, // 34: object.ObjectStore.AdminWrite:output_type -> object.WriteObjectResponse - 12, // 35: object.ObjectStoreAdmin.AdminWrite:output_type -> object.WriteObjectResponse - 28, // [28:36] is the sub-list for method output_type - 20, // [20:28] is the sub-list for method input_type - 20, // [20:20] is the sub-list for extension type_name - 20, // [20:20] is the sub-list for extension extendee - 0, // [0:20] is the sub-list for field type_name + 3, // 8: object.AdminWriteObjectRequest.origin:type_name -> object.ObjectOriginInfo + 4, // 9: object.WriteObjectResponse.error:type_name -> object.ObjectErrorInfo + 1, // 10: object.WriteObjectResponse.GRN:type_name -> object.GRN + 5, // 11: object.WriteObjectResponse.object:type_name -> object.ObjectVersionInfo + 0, // 12: object.WriteObjectResponse.status:type_name -> object.WriteObjectResponse.Status + 1, // 13: object.DeleteObjectRequest.GRN:type_name -> object.GRN + 1, // 14: object.ObjectHistoryRequest.GRN:type_name -> object.GRN + 1, // 15: object.ObjectHistoryResponse.GRN:type_name -> object.GRN + 5, // 16: object.ObjectHistoryResponse.versions:type_name -> object.ObjectVersionInfo + 20, // 17: object.ObjectSearchRequest.labels:type_name -> object.ObjectSearchRequest.LabelsEntry + 1, // 18: object.ObjectSearchResult.GRN:type_name -> object.GRN + 21, // 19: object.ObjectSearchResult.labels:type_name -> object.ObjectSearchResult.LabelsEntry + 18, // 20: object.ObjectSearchResponse.results:type_name -> object.ObjectSearchResult + 6, // 21: object.ObjectStore.Read:input_type -> object.ReadObjectRequest + 8, // 22: object.ObjectStore.BatchRead:input_type -> object.BatchReadObjectRequest + 10, // 23: object.ObjectStore.Write:input_type -> object.WriteObjectRequest + 13, // 24: object.ObjectStore.Delete:input_type -> object.DeleteObjectRequest + 15, // 25: object.ObjectStore.History:input_type -> object.ObjectHistoryRequest + 17, // 26: object.ObjectStore.Search:input_type -> object.ObjectSearchRequest + 11, // 27: object.ObjectStore.AdminWrite:input_type -> object.AdminWriteObjectRequest + 11, // 28: object.ObjectStoreAdmin.AdminWrite:input_type -> object.AdminWriteObjectRequest + 7, // 29: object.ObjectStore.Read:output_type -> object.ReadObjectResponse + 9, // 30: object.ObjectStore.BatchRead:output_type -> object.BatchReadObjectResponse + 12, // 31: object.ObjectStore.Write:output_type -> object.WriteObjectResponse + 14, // 32: object.ObjectStore.Delete:output_type -> object.DeleteObjectResponse + 16, // 33: object.ObjectStore.History:output_type -> object.ObjectHistoryResponse + 19, // 34: object.ObjectStore.Search:output_type -> object.ObjectSearchResponse + 12, // 35: object.ObjectStore.AdminWrite:output_type -> object.WriteObjectResponse + 12, // 36: object.ObjectStoreAdmin.AdminWrite:output_type -> object.WriteObjectResponse + 29, // [29:37] is the sub-list for method output_type + 21, // [21:29] is the sub-list for method input_type + 21, // [21:21] is the sub-list for extension type_name + 21, // [21:21] is the sub-list for extension extendee + 0, // [0:21] is the sub-list for field type_name } func init() { file_object_proto_init() } diff --git a/pkg/services/store/object/object.proto b/pkg/services/store/object/object.proto index 152681bafec..47ca38c7564 100644 --- a/pkg/services/store/object/object.proto +++ b/pkg/services/store/object/object.proto @@ -7,17 +7,12 @@ message GRN { // the tenant/org id int64 tenant_id = 1; - // mabybe "namespace"? valid values include - // * entity - // * drive - // * service/xyz - string scope = 2; - // Identify the object kind. This kind will be used to apply a schema to the body and // will trigger additional indexing behavior. string kind = 3; // Unique ID + // 40 characters or less, no slashes or other special characters string UID = 4; } @@ -47,21 +42,30 @@ message RawObject { // Raw bytes of the storage object. The kind will determine what is a valid payload bytes body = 8; + // Folder UID + string folder = 9; + + // Unique slug within folder (may be UID) + string slug = 10; + // The version will change when the object is saved. It is not necessarily sortable // // NOTE: currently managed by the dashboard+dashboard_version tables - string version = 9; + string version = 11; // External location info - ObjectOriginInfo origin = 10; + ObjectOriginInfo origin = 12; } message ObjectOriginInfo { // NOTE: currently managed by the dashboard_provisioning table string source = 1; + // Key in the upstream system + string key = 2; + // Time in epoch milliseconds that the object was last synced with an external system (provisioning/git) - int64 time = 2; + int64 time = 3; } // Report error while working with objects @@ -146,14 +150,17 @@ message WriteObjectRequest { // Object identifier GRN GRN = 1; + // Where to save the object (empty will leave it unchanged) + string folder = 2; + // The raw object body - bytes body = 2; + bytes body = 3; // Message that can be seen when exploring object history - string comment = 3; + string comment = 4; // Used for optimistic locking. If missing, the previous version will be replaced regardless - string previous_version = 4; + string previous_version = 5; } // This operation is useful when syncing a resource from external sources @@ -163,42 +170,45 @@ message AdminWriteObjectRequest { // Object identifier GRN GRN = 1; + // Where to save the object (empty will leave it unchanged) + string folder = 2; + // The raw object body - bytes body = 2; + bytes body = 3; // Message that can be seen when exploring object history - string comment = 3; + string comment = 4; // Time in epoch milliseconds that the object was created // Optional, if 0 it will use the current time - int64 created_at = 4; + int64 created_at = 5; // Time in epoch milliseconds that the object was updated // Optional, if empty it will use the current user - int64 updated_at = 5; + int64 updated_at = 6; // Who created the object // Optional, if 0 it will use the current time - string created_by = 6; + string created_by = 7; // Who updated the object // Optional, if empty it will use the current user - string updated_by = 7; + string updated_by = 8; // An explicit version identifier // Optional, if set, this will overwrite/define an explicit version - string version = 8; + string version = 9; // Used for optimistic locking. If missing, the previous version will be replaced regardless // This may not be used along with an explicit version in the request - string previous_version = 9; + string previous_version = 10; // Request that all previous versions are removed from the history // This will make sense for systems that manage history explicitly externallay - bool clear_history = 10; + bool clear_history = 11; // Optionally define where the object came from - string origin = 11; + ObjectOriginInfo origin = 12; } message WriteObjectResponse { @@ -338,11 +348,17 @@ message ObjectSearchResult { // The structured labels map labels = 9; + // Folder UID + string folder = 10; + + // Slugified name + string slug = 11; + // Optionally include extracted JSON - bytes fields_json = 10; + bytes fields_json = 12; // ObjectErrorInfo in json - bytes error_json = 11; + bytes error_json = 13; } message ObjectSearchResponse { diff --git a/pkg/services/store/object/sqlstash/querybuilder.go b/pkg/services/store/object/sqlstash/querybuilder.go index 73e13a66c70..ac13956f2af 100644 --- a/pkg/services/store/object/sqlstash/querybuilder.go +++ b/pkg/services/store/object/sqlstash/querybuilder.go @@ -12,7 +12,7 @@ type selectQuery struct { args []interface{} } -func (q *selectQuery) addWhere(f string, val string) { +func (q *selectQuery) addWhere(f string, val interface{}) { q.args = append(q.args, val) q.where = append(q.where, f+"=?") } @@ -37,11 +37,6 @@ func (q *selectQuery) addWhereIn(f string, vals []string) { } } -func (q *selectQuery) addWherePrefix(f string, v string) { - q.args = append(q.args, v+"%") - q.where = append(q.where, f+" LIKE ?") -} - func (q *selectQuery) toQuery() (string, []interface{}) { args := q.args sb := strings.Builder{} diff --git a/pkg/services/store/object/sqlstash/sql_storage_server.go b/pkg/services/store/object/sqlstash/sql_storage_server.go index 29e23cec334..996eb98a3fd 100644 --- a/pkg/services/store/object/sqlstash/sql_storage_server.go +++ b/pkg/services/store/object/sqlstash/sql_storage_server.go @@ -11,15 +11,12 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/grpcserver" "github.com/grafana/grafana/pkg/services/sqlstore/session" "github.com/grafana/grafana/pkg/services/store" "github.com/grafana/grafana/pkg/services/store/kind" - "github.com/grafana/grafana/pkg/services/store/kind/folder" "github.com/grafana/grafana/pkg/services/store/object" "github.com/grafana/grafana/pkg/services/store/resolver" - "github.com/grafana/grafana/pkg/services/store/router" "github.com/grafana/grafana/pkg/setting" ) @@ -33,7 +30,6 @@ func ProvideSQLObjectServer(db db.DB, cfg *setting.Cfg, grpcServerProvider grpcs log: log.New("sql-object-server"), kinds: kinds, resolver: resolver, - router: router.NewObjectStoreRouter(kinds), } object.RegisterObjectStoreServer(grpcServerProvider.GetServer(), objectServer) return objectServer @@ -44,16 +40,16 @@ type sqlObjectServer struct { sess *session.SessionDB kinds kind.KindRegistry resolver resolver.ObjectReferenceResolver - router router.ObjectStoreRouter } func getReadSelect(r *object.ReadObjectRequest) string { fields := []string{ - "path", "kind", "version", + "tenant_id", "kind", "uid", // The PK + "version", "slug", "folder", "size", "etag", "errors", // errors are always returned "created_at", "created_by", "updated_at", "updated_by", - "origin", "origin_ts"} + "origin", "origin_key", "origin_ts"} if r.WithBody { fields = append(fields, `body`) @@ -61,24 +57,24 @@ func getReadSelect(r *object.ReadObjectRequest) string { if r.WithSummary { fields = append(fields, `name`, `description`, `labels`, `fields`) } - return "SELECT " + strings.Join(fields, ",") + " FROM object WHERE " + return "SELECT " + strings.Join(fields, ",") + " FROM entity WHERE " } func (s *sqlObjectServer) rowToReadObjectResponse(ctx context.Context, rows *sql.Rows, r *object.ReadObjectRequest) (*object.ReadObjectResponse, error) { - path := "" // string (extract UID?) - var origin sql.NullString - originTime := int64(0) raw := &object.RawObject{ - GRN: &object.GRN{}, + GRN: &object.GRN{}, + Origin: &object.ObjectOriginInfo{}, } + slug := "" summaryjson := &summarySupport{} args := []interface{}{ - &path, &raw.GRN.Kind, &raw.Version, + &raw.GRN.TenantId, &raw.GRN.Kind, &raw.GRN.UID, + &raw.Version, &slug, &raw.Folder, &raw.Size, &raw.ETag, &summaryjson.errors, &raw.CreatedAt, &raw.CreatedBy, &raw.UpdatedAt, &raw.UpdatedBy, - &origin, &originTime, + &raw.Origin.Source, &raw.Origin.Key, &raw.Origin.Time, } if r.WithBody { args = append(args, &raw.Body) @@ -92,17 +88,8 @@ func (s *sqlObjectServer) rowToReadObjectResponse(ctx context.Context, rows *sql return nil, err } - if origin.Valid { - raw.Origin = &object.ObjectOriginInfo{ - Source: origin.String, - Time: originTime, - } - } - - // Get the GRN from key. TODO? save each part as a column? - info, _ := s.router.RouteFromKey(ctx, path) - if info.GRN != nil { - raw.GRN = info.GRN + if raw.Origin.Source == "" { + raw.Origin = nil } rsp := &object.ReadObjectResponse{ @@ -124,35 +111,43 @@ func (s *sqlObjectServer) rowToReadObjectResponse(ctx context.Context, rows *sql return rsp, nil } -func (s *sqlObjectServer) getObjectKey(ctx context.Context, grn *object.GRN) (router.ResourceRouteInfo, error) { +func (s *sqlObjectServer) validateGRN(ctx context.Context, grn *object.GRN) (*object.GRN, error) { if grn == nil { - return router.ResourceRouteInfo{}, fmt.Errorf("missing grn") + return nil, fmt.Errorf("missing GRN") } user := store.UserFromContext(ctx) - if user == nil { - return router.ResourceRouteInfo{}, fmt.Errorf("can not find user in context") - } - if user.OrgID != grn.TenantId { - if grn.TenantId > 0 { - return router.ResourceRouteInfo{}, fmt.Errorf("invalid user (wrong tenant id)") - } + if grn.TenantId == 0 { grn.TenantId = user.OrgID + } else if grn.TenantId != user.OrgID { + return nil, fmt.Errorf("tenant ID does not match userID") } - return s.router.Route(ctx, grn) + + if grn.Kind == "" { + return nil, fmt.Errorf("GRN missing kind") + } + if grn.UID == "" { + return nil, fmt.Errorf("GRN missing UID") + } + if len(grn.UID) > 40 { + return nil, fmt.Errorf("GRN UID is too long (>40)") + } + if strings.ContainsAny(grn.UID, "/#$@?") { + return nil, fmt.Errorf("invalid character in GRN") + } + return grn, nil } func (s *sqlObjectServer) Read(ctx context.Context, r *object.ReadObjectRequest) (*object.ReadObjectResponse, error) { if r.Version != "" { return s.readFromHistory(ctx, r) } - - route, err := s.getObjectKey(ctx, r.GRN) + grn, err := s.validateGRN(ctx, r.GRN) if err != nil { return nil, err } - args := []interface{}{route.Key} - where := "path=?" + args := []interface{}{grn.ToGRNString()} + where := "grn=?" rows, err := s.sess.Query(ctx, getReadSelect(r)+where, args...) if err != nil { @@ -168,10 +163,11 @@ func (s *sqlObjectServer) Read(ctx context.Context, r *object.ReadObjectRequest) } func (s *sqlObjectServer) readFromHistory(ctx context.Context, r *object.ReadObjectRequest) (*object.ReadObjectResponse, error) { - route, err := s.getObjectKey(ctx, r.GRN) + grn, err := s.validateGRN(ctx, r.GRN) if err != nil { return nil, err } + oid := grn.ToGRNString() fields := []string{ "body", "size", "etag", @@ -180,7 +176,7 @@ func (s *sqlObjectServer) readFromHistory(ctx context.Context, r *object.ReadObj rows, err := s.sess.Query(ctx, "SELECT "+strings.Join(fields, ",")+ - " FROM object_history WHERE path=? AND version=?", route.Key, r.Version) + " FROM entity_history WHERE grn=? AND version=?", oid, r.Version) if err != nil { return nil, err } @@ -243,13 +239,13 @@ func (s *sqlObjectServer) BatchRead(ctx context.Context, b *object.BatchReadObje return nil, fmt.Errorf("requests must want the same things") } - route, err := s.getObjectKey(ctx, r.GRN) + grn, err := s.validateGRN(ctx, r.GRN) if err != nil { return nil, err } - where := "path=?" - args = append(args, route.Key) + where := "grn=?" + args = append(args, grn.ToGRNString()) if r.Version != "" { return nil, fmt.Errorf("version not supported for batch read (yet?)") } @@ -282,14 +278,11 @@ func (s *sqlObjectServer) Write(ctx context.Context, r *object.WriteObjectReques //nolint:gocyclo func (s *sqlObjectServer) AdminWrite(ctx context.Context, r *object.AdminWriteObjectRequest) (*object.WriteObjectResponse, error) { - route, err := s.getObjectKey(ctx, r.GRN) + grn, err := s.validateGRN(ctx, r.GRN) if err != nil { return nil, err } - grn := route.GRN - if grn == nil { - return nil, fmt.Errorf("invalid grn") - } + oid := grn.ToGRNString() timestamp := time.Now().UnixMilli() createdAt := r.CreatedAt @@ -312,20 +305,15 @@ func (s *sqlObjectServer) AdminWrite(ctx context.Context, r *object.AdminWriteOb return nil, err } + slug := slugifyTitle(summary.name, r.GRN.UID) etag := createContentsHash(body) - path := route.Key - rsp := &object.WriteObjectResponse{ GRN: grn, Status: object.WriteObjectResponse_CREATED, // Will be changed if not true } - - // Make sure all parent folders exist - if grn.Scope == models.ObjectStoreScopeDrive { - err = s.ensureFolders(ctx, grn) - if err != nil { - return nil, err - } + origin := r.Origin + if origin == nil { + origin = &object.ObjectOriginInfo{} } err = s.sess.WithTransaction(ctx, func(tx *session.SessionTx) error { @@ -334,18 +322,18 @@ func (s *sqlObjectServer) AdminWrite(ctx context.Context, r *object.AdminWriteOb if r.ClearHistory { // Optionally keep the original creation time information if createdAt < 1000 || createdBy == "" { - err = s.fillCreationInfo(ctx, tx, path, &createdAt, &createdBy) + err = s.fillCreationInfo(ctx, tx, oid, &createdAt, &createdBy) if err != nil { return err } } - _, err = doDelete(ctx, tx, path) + _, err = doDelete(ctx, tx, oid) if err != nil { return err } versionInfo = &object.ObjectVersionInfo{} } else { - versionInfo, err = s.selectForUpdate(ctx, tx, path) + versionInfo, err = s.selectForUpdate(ctx, tx, oid) if err != nil { return err } @@ -385,25 +373,25 @@ func (s *sqlObjectServer) AdminWrite(ctx context.Context, r *object.AdminWriteOb if isUpdate { // Clear the labels+refs - if _, err := tx.Exec(ctx, "DELETE FROM object_labels WHERE path=?", path); err != nil { + if _, err := tx.Exec(ctx, "DELETE FROM entity_labels WHERE grn=?", oid); err != nil { return err } - if _, err := tx.Exec(ctx, "DELETE FROM object_ref WHERE path=?", path); err != nil { + if _, err := tx.Exec(ctx, "DELETE FROM entity_ref WHERE grn=?", oid); err != nil { return err } } - // 1. Add the `object_history` values + // 1. Add the `entity_history` values versionInfo.Size = int64(len(body)) versionInfo.ETag = etag versionInfo.UpdatedAt = updatedAt versionInfo.UpdatedBy = updatedBy - _, err = tx.Exec(ctx, `INSERT INTO object_history (`+ - "path, version, message, "+ + _, err = tx.Exec(ctx, `INSERT INTO entity_history (`+ + "grn, version, message, "+ "size, body, etag, "+ "updated_at, updated_by) "+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?)", - path, versionInfo.Version, versionInfo.Comment, + oid, versionInfo.Version, versionInfo.Comment, versionInfo.Size, body, versionInfo.ETag, updatedAt, versionInfo.UpdatedBy, ) @@ -414,10 +402,10 @@ func (s *sqlObjectServer) AdminWrite(ctx context.Context, r *object.AdminWriteOb // 2. Add the labels rows for k, v := range summary.model.Labels { _, err = tx.Exec(ctx, - `INSERT INTO object_labels `+ - "(path, label, value) "+ + `INSERT INTO entity_labels `+ + "(grn, label, value) "+ `VALUES (?, ?, ?)`, - path, k, v, + oid, k, v, ) if err != nil { return err @@ -430,11 +418,11 @@ func (s *sqlObjectServer) AdminWrite(ctx context.Context, r *object.AdminWriteOb if err != nil { return err } - _, err = tx.Exec(ctx, `INSERT INTO object_ref (`+ - "path, kind, type, uid, "+ + _, err = tx.Exec(ctx, `INSERT INTO entity_ref (`+ + "grn, kind, type, uid, "+ "resolved_ok, resolved_to, resolved_warning, resolved_time) "+ `VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, - path, ref.Kind, ref.Type, ref.UID, + oid, ref.Kind, ref.Type, ref.UID, resolved.OK, resolved.Key, resolved.Warning, resolved.Timestamp, ) if err != nil { @@ -446,19 +434,19 @@ func (s *sqlObjectServer) AdminWrite(ctx context.Context, r *object.AdminWriteOb rsp.Object = versionInfo if isUpdate { rsp.Status = object.WriteObjectResponse_UPDATED - _, err = tx.Exec(ctx, "UPDATE object SET "+ + _, err = tx.Exec(ctx, "UPDATE entity SET "+ "body=?, size=?, etag=?, version=?, "+ "updated_at=?, updated_by=?,"+ "name=?, description=?,"+ "labels=?, fields=?, errors=?, "+ - "origin=?, origin_ts=? "+ - "WHERE path=?", + "origin=?, origin_key=?, origin_ts=? "+ + "WHERE grn=?", body, versionInfo.Size, etag, versionInfo.Version, updatedAt, versionInfo.UpdatedBy, summary.model.Name, summary.model.Description, summary.labels, summary.fields, summary.errors, - r.Origin, timestamp, - path, + origin.Source, origin.Key, timestamp, + oid, ) return err } @@ -470,16 +458,25 @@ func (s *sqlObjectServer) AdminWrite(ctx context.Context, r *object.AdminWriteOb createdBy = updatedBy } - _, err = tx.Exec(ctx, "INSERT INTO object ("+ - "path, parent_folder_path, kind, size, body, etag, version, "+ + _, err = tx.Exec(ctx, "INSERT INTO entity ("+ + "grn, tenant_id, kind, uid, folder, "+ + "size, body, etag, version, "+ "updated_at, updated_by, created_at, created_by, "+ - "name, description, origin, origin_ts, "+ - "labels, fields, errors) "+ - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - path, getParentFolderPath(grn.Kind, path), grn.Kind, versionInfo.Size, body, etag, versionInfo.Version, + "name, description, slug, "+ + "labels, fields, errors, "+ + "origin, origin_key, origin_ts) "+ + "VALUES (?, ?, ?, ?, ?, "+ + " ?, ?, ?, ?, "+ + " ?, ?, ?, ?, "+ + " ?, ?, ?, "+ + " ?, ?, ?, "+ + " ?, ?, ?)", + oid, grn.TenantId, grn.Kind, grn.UID, r.Folder, + versionInfo.Size, body, etag, versionInfo.Version, updatedAt, createdBy, createdAt, createdBy, // created + updated are the same - summary.model.Name, summary.model.Description, r.Origin, timestamp, + summary.model.Name, summary.model.Description, slug, summary.labels, summary.fields, summary.errors, + origin.Source, origin.Key, origin.Time, ) return err }) @@ -490,7 +487,7 @@ func (s *sqlObjectServer) AdminWrite(ctx context.Context, r *object.AdminWriteOb return rsp, err } -func (s *sqlObjectServer) fillCreationInfo(ctx context.Context, tx *session.SessionTx, path string, createdAt *int64, createdBy *string) error { +func (s *sqlObjectServer) fillCreationInfo(ctx context.Context, tx *session.SessionTx, grn string, createdAt *int64, createdBy *string) error { if *createdAt > 1000 { ignore := int64(0) createdAt = &ignore @@ -500,7 +497,7 @@ func (s *sqlObjectServer) fillCreationInfo(ctx context.Context, tx *session.Sess createdBy = &ignore } - rows, err := tx.Query(ctx, "SELECT created_at,created_by FROM object WHERE path=?", path) + rows, err := tx.Query(ctx, "SELECT created_at,created_by FROM entity WHERE grn=?", grn) if err == nil { if rows.Next() { err = rows.Scan(&createdAt, &createdBy) @@ -512,12 +509,12 @@ func (s *sqlObjectServer) fillCreationInfo(ctx context.Context, tx *session.Sess return err } -func (s *sqlObjectServer) selectForUpdate(ctx context.Context, tx *session.SessionTx, path string) (*object.ObjectVersionInfo, error) { - q := "SELECT etag,version,updated_at,size FROM object WHERE path=?" +func (s *sqlObjectServer) selectForUpdate(ctx context.Context, tx *session.SessionTx, grn string) (*object.ObjectVersionInfo, error) { + q := "SELECT etag,version,updated_at,size FROM entity WHERE grn=?" if false { // TODO, MYSQL/PosgreSQL can lock the row " FOR UPDATE" q += " FOR UPDATE" } - rows, err := tx.Query(ctx, q, path) + rows, err := tx.Query(ctx, q, grn) if err != nil { return nil, err } @@ -551,22 +548,21 @@ func (s *sqlObjectServer) prepare(ctx context.Context, r *object.AdminWriteObjec } func (s *sqlObjectServer) Delete(ctx context.Context, r *object.DeleteObjectRequest) (*object.DeleteObjectResponse, error) { - route, err := s.getObjectKey(ctx, r.GRN) + grn, err := s.validateGRN(ctx, r.GRN) if err != nil { return nil, err } - path := route.Key rsp := &object.DeleteObjectResponse{} err = s.sess.WithTransaction(ctx, func(tx *session.SessionTx) error { - rsp.OK, err = doDelete(ctx, tx, path) + rsp.OK, err = doDelete(ctx, tx, grn.ToGRNString()) return err }) return rsp, err } -func doDelete(ctx context.Context, tx *session.SessionTx, path string) (bool, error) { - results, err := tx.Exec(ctx, "DELETE FROM object WHERE path=?", path) +func doDelete(ctx context.Context, tx *session.SessionTx, grn string) (bool, error) { + results, err := tx.Exec(ctx, "DELETE FROM entity WHERE grn=?", grn) if err != nil { return false, err } @@ -576,21 +572,21 @@ func doDelete(ctx context.Context, tx *session.SessionTx, path string) (bool, er } // TODO: keep history? would need current version bump, and the "write" would have to get from history - _, _ = tx.Exec(ctx, "DELETE FROM object_history WHERE path=?", path) - _, _ = tx.Exec(ctx, "DELETE FROM object_labels WHERE path=?", path) - _, _ = tx.Exec(ctx, "DELETE FROM object_ref WHERE path=?", path) + _, _ = tx.Exec(ctx, "DELETE FROM entity_history WHERE grn=?", grn) + _, _ = tx.Exec(ctx, "DELETE FROM entity_labels WHERE grn=?", grn) + _, _ = tx.Exec(ctx, "DELETE FROM entity_ref WHERE grn=?", grn) return rows > 0, err } func (s *sqlObjectServer) History(ctx context.Context, r *object.ObjectHistoryRequest) (*object.ObjectHistoryResponse, error) { - route, err := s.getObjectKey(ctx, r.GRN) + grn, err := s.validateGRN(ctx, r.GRN) if err != nil { return nil, err } - path := route.Key + oid := grn.ToGRNString() page := "" - args := []interface{}{path} + args := []interface{}{oid} if r.NextPageToken != "" { // args = append(args, r.NextPageToken) // TODO, need to get time from the version // page = "AND updated <= ?" @@ -598,8 +594,8 @@ func (s *sqlObjectServer) History(ctx context.Context, r *object.ObjectHistoryRe } query := "SELECT version,size,etag,updated_at,updated_by,message \n" + - " FROM object_history \n" + - " WHERE path=? " + page + "\n" + + " FROM entity_history \n" + + " WHERE grn=? " + page + "\n" + " ORDER BY updated_at DESC LIMIT 100" rows, err := s.sess.Query(ctx, query, args...) @@ -608,7 +604,7 @@ func (s *sqlObjectServer) History(ctx context.Context, r *object.ObjectHistoryRe } defer func() { _ = rows.Close() }() rsp := &object.ObjectHistoryResponse{ - GRN: route.GRN, + GRN: r.GRN, } for rows.Next() { v := &object.ObjectVersionInfo{} @@ -632,7 +628,8 @@ func (s *sqlObjectServer) Search(ctx context.Context, r *object.ObjectSearchRequ } fields := []string{ - "path", "kind", "version", "errors", // errors are always returned + "grn", "tenant_id", "kind", "uid", + "version", "folder", "slug", "errors", // errors are always returned "size", "updated_at", "updated_by", "name", "description", // basic summary } @@ -649,31 +646,20 @@ func (s *sqlObjectServer) Search(ctx context.Context, r *object.ObjectSearchRequ selectQuery := selectQuery{ fields: fields, - from: "object", // the table + from: "entity", // the table args: []interface{}{}, limit: int(r.Limit), oneExtra: true, // request one more than the limit (and show next token if it exists) } + selectQuery.addWhere("tenant_id", user.OrgID) if len(r.Kind) > 0 { selectQuery.addWhereIn("kind", r.Kind) } - // Locked to a folder or prefix + // Folder UID or OID? if r.Folder != "" { - if strings.HasSuffix(r.Folder, "/") { - return nil, fmt.Errorf("folder should not end with slash") - } - if strings.HasSuffix(r.Folder, "*") { - keyPrefix := fmt.Sprintf("%d/%s", user.OrgID, strings.ReplaceAll(r.Folder, "*", "")) - selectQuery.addWherePrefix("path", keyPrefix) - } else { - keyPrefix := fmt.Sprintf("%d/%s", user.OrgID, r.Folder) - selectQuery.addWhere("parent_folder_path", keyPrefix) - } - } else { - keyPrefix := fmt.Sprintf("%d/", user.OrgID) - selectQuery.addWherePrefix("path", keyPrefix) + selectQuery.addWhere("folder", r.Folder) } query, args := selectQuery.toQuery() @@ -688,7 +674,7 @@ func (s *sqlObjectServer) Search(ctx context.Context, r *object.ObjectSearchRequ return nil, err } defer func() { _ = rows.Close() }() - key := "" + oid := "" rsp := &object.ObjectSearchResponse{} for rows.Next() { result := &object.ObjectSearchResult{ @@ -697,7 +683,8 @@ func (s *sqlObjectServer) Search(ctx context.Context, r *object.ObjectSearchRequ summaryjson := summarySupport{} args := []interface{}{ - &key, &result.GRN.Kind, &result.Version, &summaryjson.errors, + &oid, &result.GRN.TenantId, &result.GRN.Kind, &result.GRN.UID, + &result.Version, &result.Folder, &result.Slug, &summaryjson.errors, &result.Size, &result.UpdatedAt, &result.UpdatedBy, &result.Name, &summaryjson.description, } @@ -716,16 +703,10 @@ func (s *sqlObjectServer) Search(ctx context.Context, r *object.ObjectSearchRequ return rsp, err } - info, err := s.router.RouteFromKey(ctx, key) - if err != nil { - return rsp, err - } - result.GRN = info.GRN - // found one more than requested if len(rsp.Results) >= selectQuery.limit { // TODO? should this encode start+offset? - rsp.NextPageToken = key + rsp.NextPageToken = oid break } @@ -753,54 +734,3 @@ func (s *sqlObjectServer) Search(ctx context.Context, r *object.ObjectSearchRequ } return rsp, err } - -func (s *sqlObjectServer) ensureFolders(ctx context.Context, objectgrn *object.GRN) error { - uid := objectgrn.UID - idx := strings.LastIndex(uid, "/") - var missing []*object.GRN - - for idx > 0 { - parent := uid[:idx] - grn := &object.GRN{ - TenantId: objectgrn.TenantId, - Scope: objectgrn.Scope, - Kind: models.StandardKindFolder, - UID: parent, - } - fr, err := s.router.Route(ctx, grn) - if err != nil { - return err - } - - // Not super efficient, but maybe it is OK? - results := []int64{} - err = s.sess.Select(ctx, &results, "SELECT 1 from object WHERE path=?", fr.Key) - if err != nil { - return err - } - if len(results) == 0 { - missing = append([]*object.GRN{grn}, missing...) - } - idx = strings.LastIndex(parent, "/") - } - - // walk though each missing element - for _, grn := range missing { - f := &folder.Model{ - Name: store.GuessNameFromUID(grn.UID), - } - fmt.Printf("CREATE Folder: %s\n", grn.UID) - body, err := json.Marshal(f) - if err != nil { - return err - } - _, err = s.Write(ctx, &object.WriteObjectRequest{ - GRN: grn, - Body: body, - }) - if err != nil { - return err - } - } - return nil -} diff --git a/pkg/services/store/object/sqlstash/utils.go b/pkg/services/store/object/sqlstash/utils.go index a75ad845615..e5c92159093 100644 --- a/pkg/services/store/object/sqlstash/utils.go +++ b/pkg/services/store/object/sqlstash/utils.go @@ -2,10 +2,11 @@ package sqlstash import ( "crypto/md5" + "encoding/base64" "encoding/hex" "strings" - "github.com/grafana/grafana/pkg/models" + "github.com/gosimple/slug" ) func createContentsHash(contents []byte) string { @@ -13,15 +14,20 @@ func createContentsHash(contents []byte) string { return hex.EncodeToString(hash[:]) } -func getParentFolderPath(kind string, key string) string { - idx := strings.LastIndex(key, "/") - if idx < 0 { - return "" // ? +func slugifyTitle(title string, fallback string) string { + if title == "" { + title = fallback } - - // folder should have a parent up one directory - if kind == models.StandardKindFolder { - idx = strings.LastIndex(key[:idx], "/") + s := slug.Make(strings.ToLower(title)) + if s == "" { + // If the dashboard name is only characters outside of the + // sluggable characters, the slug creation will return an + // empty string which will mess up URLs. This failsafe picks + // that up and creates the slug as a base64 identifier instead. + s = base64.RawURLEncoding.EncodeToString([]byte(title)) + if slug.MaxLength != 0 && len(s) > slug.MaxLength { + s = s[:slug.MaxLength] + } } - return key[:idx] + return s } diff --git a/pkg/services/store/object/tests/server_integration_test.go b/pkg/services/store/object/tests/server_integration_test.go index 46607d1186f..4697fbde5f7 100644 --- a/pkg/services/store/object/tests/server_integration_test.go +++ b/pkg/services/store/object/tests/server_integration_test.go @@ -48,9 +48,6 @@ func requireObjectMatch(t *testing.T, obj *object.RawObject, m rawObjectMatcher) if m.grn.TenantId > 0 && m.grn.TenantId != obj.GRN.TenantId { mismatches += fmt.Sprintf("expected tenant: %d, actual: %d\n", m.grn.TenantId, obj.GRN.TenantId) } - if m.grn.Scope != "" && m.grn.Scope != obj.GRN.Scope { - mismatches += fmt.Sprintf("expected Scope: %s, actual: %s\n", m.grn.Scope, obj.GRN.Scope) - } if m.grn.Kind != "" && m.grn.Kind != obj.GRN.Kind { mismatches += fmt.Sprintf("expected Kind: %s, actual: %s\n", m.grn.Kind, obj.GRN.Kind) } @@ -125,9 +122,8 @@ func TestIntegrationObjectServer(t *testing.T) { firstVersion := "1" kind := models.StandardKindJSONObj grn := &object.GRN{ - Kind: kind, - UID: "my-test-entity", - Scope: models.ObjectStoreScopeEntity, + Kind: kind, + UID: "my-test-entity", } body := []byte("{\"name\":\"John\"}") @@ -171,7 +167,6 @@ func TestIntegrationObjectServer(t *testing.T) { foundGRN := readResp.Object.GRN require.NotNil(t, foundGRN) require.Equal(t, testCtx.user.OrgID, foundGRN.TenantId) // orgId becomes the tenant id when not set - require.Equal(t, grn.Scope, foundGRN.Scope) require.Equal(t, grn.Kind, foundGRN.Kind) require.Equal(t, grn.UID, foundGRN.UID) @@ -205,9 +200,8 @@ func TestIntegrationObjectServer(t *testing.T) { t.Run("should be able to update an object", func(t *testing.T) { before := time.Now() grn := &object.GRN{ - Kind: kind, - UID: util.GenerateShortUID(), - Scope: models.ObjectStoreScopeEntity, + Kind: kind, + UID: util.GenerateShortUID(), } writeReq1 := &object.WriteObjectRequest{ @@ -316,9 +310,8 @@ func TestIntegrationObjectServer(t *testing.T) { w2, err := testCtx.client.Write(ctx, &object.WriteObjectRequest{ GRN: &object.GRN{ - UID: uid2, - Kind: kind, - Scope: grn.Scope, + UID: uid2, + Kind: kind, }, Body: body, }) @@ -326,9 +319,8 @@ func TestIntegrationObjectServer(t *testing.T) { w3, err := testCtx.client.Write(ctx, &object.WriteObjectRequest{ GRN: &object.GRN{ - UID: uid3, - Kind: kind2, - Scope: grn.Scope, + UID: uid3, + Kind: kind2, }, Body: body, }) @@ -336,9 +328,8 @@ func TestIntegrationObjectServer(t *testing.T) { w4, err := testCtx.client.Write(ctx, &object.WriteObjectRequest{ GRN: &object.GRN{ - UID: uid4, - Kind: kind2, - Scope: grn.Scope, + UID: uid4, + Kind: kind2, }, Body: body, }) diff --git a/pkg/services/store/object/utils.go b/pkg/services/store/object/utils.go index 9aac9eb8e29..91b965601c8 100644 --- a/pkg/services/store/object/utils.go +++ b/pkg/services/store/object/utils.go @@ -5,6 +5,7 @@ func ToAdminWriteObjectRequest(req *WriteObjectRequest) *AdminWriteObjectRequest return &AdminWriteObjectRequest{ GRN: req.GRN, Body: req.Body, + Folder: req.Folder, Comment: req.Comment, PreviousVersion: req.PreviousVersion, } diff --git a/pkg/services/store/router/router.go b/pkg/services/store/router/router.go deleted file mode 100644 index ec2ae9818a2..00000000000 --- a/pkg/services/store/router/router.go +++ /dev/null @@ -1,173 +0,0 @@ -package router - -import ( - "context" - "fmt" - "strconv" - "strings" - - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/store/kind" - "github.com/grafana/grafana/pkg/services/store/object" -) - -type ResourceRouteInfo struct { - // The resource identifier - GRN *object.GRN - - // Raw key used in storage engine - Key string -} - -type ObjectStoreRouter interface { - // This will throw exceptions for unsupported - Route(ctx context.Context, grn *object.GRN) (ResourceRouteInfo, error) - - // Parse a key to get the GRN and storage information - RouteFromKey(ctx context.Context, key string) (ResourceRouteInfo, error) -} - -type standardStoreRouter struct { - kinds kind.KindRegistry -} - -func NewObjectStoreRouter(kinds kind.KindRegistry) ObjectStoreRouter { - return &standardStoreRouter{kinds: kinds} -} - -var _ ObjectStoreRouter = &standardStoreRouter{} - -func (r *standardStoreRouter) Route(ctx context.Context, grn *object.GRN) (ResourceRouteInfo, error) { - info := ResourceRouteInfo{ - GRN: grn, - } - - if grn == nil { - return info, fmt.Errorf("missing GRN") - } - - // Make sure the orgID is set - if grn.TenantId < 1 { - return info, fmt.Errorf("missing TenantId") - } - if grn.Kind == "" { - return info, fmt.Errorf("missing Kind") - } - if grn.UID == "" { - return info, fmt.Errorf("missing UID") - } - - kind, err := r.kinds.GetInfo(grn.Kind) - if err != nil { - return info, fmt.Errorf("unknown Kind: " + grn.Kind) - } - - if grn.Scope == "" { - return info, fmt.Errorf("missing Scope") - } - - switch grn.Scope { - case models.ObjectStoreScopeEntity: - { - info.Key = fmt.Sprintf("%d/%s/%s/%s", grn.TenantId, grn.Scope, grn.Kind, grn.UID) - } - case models.ObjectStoreScopeDrive: - { - // Special folder handling in drive - if grn.Kind == models.StandardKindFolder { - info.Key = fmt.Sprintf("%d/%s/%s/__folder.json", grn.TenantId, grn.Scope, grn.UID) - return info, nil - } - if kind.FileExtension != "" { - info.Key = fmt.Sprintf("%d/%s/%s.%s", grn.TenantId, grn.Scope, grn.UID, kind.FileExtension) - } else { - info.Key = fmt.Sprintf("%d/%s/%s-%s.json", grn.TenantId, grn.Scope, grn.UID, grn.Kind) - } - } - default: - return info, fmt.Errorf("unsupported scope: " + grn.Scope) - } - - return info, nil -} - -func (r *standardStoreRouter) RouteFromKey(ctx context.Context, key string) (ResourceRouteInfo, error) { - info := ResourceRouteInfo{ - Key: key, - GRN: &object.GRN{}, - } - // {orgID}/{scope}/.... - - idx := strings.Index(key, "/") - if idx <= 0 { - return info, fmt.Errorf("can not find orgID") - } - p0 := key[:idx] - key = key[idx+1:] - idx = strings.Index(key, "/") - if idx <= 0 { - return info, fmt.Errorf("can not find namespace") - } - p2 := key[:idx] - key = key[idx+1:] - - tenantID, err := strconv.ParseInt(p0, 10, 64) - if err != nil { - return info, fmt.Errorf("error parsing orgID") - } - info.GRN.TenantId = tenantID - info.GRN.Scope = p2 - - switch info.GRN.Scope { - case models.ObjectStoreScopeDrive: - { - idx := strings.LastIndex(key, ".") - if idx > 0 { - ext := key[idx+1:] - if ext == "json" { - sdx := strings.LastIndex(key, "/") - idx = strings.LastIndex(key, "-") - if idx > sdx { - ddx := strings.LastIndex(key, ".") // .json - info.GRN.UID = key[:idx] - info.GRN.Kind = key[idx+1 : ddx] - } else { - switch key[sdx+1:] { - case "__folder.json": - { - info.GRN.UID = key[:sdx] - info.GRN.Kind = models.StandardKindFolder - } - default: - return info, fmt.Errorf("unable to parse drive path") - } - } - } else { - info.GRN.UID = key[:idx] - k, err := r.kinds.GetFromExtension(ext) - if err != nil { - return info, err - } - info.GRN.Kind = k.ID - } - } else { - idx = strings.Index(key, "/") - - info.GRN.Kind = key[:idx] - info.GRN.UID = key[idx+1:] - } - } - - case models.ObjectStoreScopeEntity: - { - idx = strings.Index(key, "/") - - info.GRN.Kind = key[:idx] - info.GRN.UID = key[idx+1:] - } - - default: - return info, fmt.Errorf("unsupported scope") - } - return info, nil -} diff --git a/pkg/services/store/router/router_test.go b/pkg/services/store/router/router_test.go deleted file mode 100644 index c47d92ab575..00000000000 --- a/pkg/services/store/router/router_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package router - -import ( - "context" - "fmt" - "testing" - - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/services/store/kind" - "github.com/grafana/grafana/pkg/services/store/object" - "github.com/stretchr/testify/require" -) - -func TestSimpleRouter(t *testing.T) { - ctx := context.Background() - router := &standardStoreRouter{ - kinds: kind.NewKindRegistry(), - } - info, err := router.Route(ctx, &object.GRN{ - UID: "path/to/file", - }) - require.Error(t, err) // needs OrgID - - type routeScenario struct { - GRN *object.GRN - Error string - Key string - } - - scenarios := []routeScenario{{ - Error: "missing TenantId", - GRN: &object.GRN{Scope: "x"}, - }, { - Error: "unknown Kind: xyz", - GRN: &object.GRN{ - TenantId: 11, - Scope: models.ObjectStoreScopeDrive, - UID: "path/to/file", - Kind: "xyz", - }, - }, { - Key: "11/drive/path/to/file-dashboard.json", - GRN: &object.GRN{ - TenantId: 11, - Scope: models.ObjectStoreScopeDrive, - UID: "path/to/file", - Kind: "dashboard", - }, - }, { - Key: "11/drive/path/to/folder/__folder.json", - GRN: &object.GRN{ - TenantId: 11, - Scope: models.ObjectStoreScopeDrive, - UID: "path/to/folder", - Kind: "folder", - }, - }, { - Key: "10/drive/path/to/file.png", - GRN: &object.GRN{ - TenantId: 10, - Scope: models.ObjectStoreScopeDrive, - UID: "path/to/file", - Kind: "png", - }, - }, { - Key: "10/entity/playlist/aaaaa", // ?.json better or not? - GRN: &object.GRN{ - TenantId: 10, - Scope: models.ObjectStoreScopeEntity, - UID: "aaaaa", - Kind: "playlist", - }, - }} - - for idx, check := range scenarios { - testID := fmt.Sprintf("[%d] %s", idx, check.Key) - - // Read the key from the GRN - info, err = router.Route(ctx, check.GRN) - if check.Error == "" { - require.NoError(t, err, testID) - } else { - require.Error(t, err, testID) - require.Equal(t, check.Error, err.Error(), testID) - continue - } - // Check that the key matched - require.Equal(t, check.Key, info.Key, testID) - - // Now try to parse the same key again - out, err := router.RouteFromKey(ctx, info.Key) - require.NoError(t, err, testID) - require.Equal(t, check.GRN, out.GRN, testID) - } -}