From 7985fa573e5c3e2e23ece6ba455ee0becec3dc64 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 19 Nov 2024 16:47:59 +0300 Subject: [PATCH] UnifiedStorage: Add ResourceTable format (#96506) --- pkg/storage/unified/resource/resource.pb.go | 957 +++++++++++++----- pkg/storage/unified/resource/resource.proto | 148 ++- pkg/storage/unified/resource/table.go | 549 ++++++++++ pkg/storage/unified/resource/table_test.go | 343 +++++++ .../resource/testdata/simple-table.json | 72 ++ 5 files changed, 1825 insertions(+), 244 deletions(-) create mode 100644 pkg/storage/unified/resource/table.go create mode 100644 pkg/storage/unified/resource/table_test.go create mode 100644 pkg/storage/unified/resource/testdata/simple-table.json diff --git a/pkg/storage/unified/resource/resource.pb.go b/pkg/storage/unified/resource/resource.pb.go index 1fa3f1e6c33..a247d97232c 100644 --- a/pkg/storage/unified/resource/resource.pb.go +++ b/pkg/storage/unified/resource/resource.pb.go @@ -176,6 +176,81 @@ func (HealthCheckResponse_ServingStatus) EnumDescriptor() ([]byte, []int) { return file_resource_proto_rawDescGZIP(), []int{30, 0} } +// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more. +// When converted to a k8s Table, this will become two fields: type and format +type ResourceTableColumnDefinition_ColumnType int32 + +const ( + ResourceTableColumnDefinition_UNKNOWN_TYPE ResourceTableColumnDefinition_ColumnType = 0 + ResourceTableColumnDefinition_STRING ResourceTableColumnDefinition_ColumnType = 1 + ResourceTableColumnDefinition_BOOLEAN ResourceTableColumnDefinition_ColumnType = 2 + ResourceTableColumnDefinition_INT32 ResourceTableColumnDefinition_ColumnType = 3 + ResourceTableColumnDefinition_INT64 ResourceTableColumnDefinition_ColumnType = 4 + ResourceTableColumnDefinition_FLOAT ResourceTableColumnDefinition_ColumnType = 5 + ResourceTableColumnDefinition_DOUBLE ResourceTableColumnDefinition_ColumnType = 6 + ResourceTableColumnDefinition_DATE ResourceTableColumnDefinition_ColumnType = 7 + ResourceTableColumnDefinition_DATE_TIME ResourceTableColumnDefinition_ColumnType = 8 + ResourceTableColumnDefinition_BINARY ResourceTableColumnDefinition_ColumnType = 9 + ResourceTableColumnDefinition_OBJECT ResourceTableColumnDefinition_ColumnType = 10 // map[string]any +) + +// Enum value maps for ResourceTableColumnDefinition_ColumnType. +var ( + ResourceTableColumnDefinition_ColumnType_name = map[int32]string{ + 0: "UNKNOWN_TYPE", + 1: "STRING", + 2: "BOOLEAN", + 3: "INT32", + 4: "INT64", + 5: "FLOAT", + 6: "DOUBLE", + 7: "DATE", + 8: "DATE_TIME", + 9: "BINARY", + 10: "OBJECT", + } + ResourceTableColumnDefinition_ColumnType_value = map[string]int32{ + "UNKNOWN_TYPE": 0, + "STRING": 1, + "BOOLEAN": 2, + "INT32": 3, + "INT64": 4, + "FLOAT": 5, + "DOUBLE": 6, + "DATE": 7, + "DATE_TIME": 8, + "BINARY": 9, + "OBJECT": 10, + } +) + +func (x ResourceTableColumnDefinition_ColumnType) Enum() *ResourceTableColumnDefinition_ColumnType { + p := new(ResourceTableColumnDefinition_ColumnType) + *p = x + return p +} + +func (x ResourceTableColumnDefinition_ColumnType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ResourceTableColumnDefinition_ColumnType) Descriptor() protoreflect.EnumDescriptor { + return file_resource_proto_enumTypes[3].Descriptor() +} + +func (ResourceTableColumnDefinition_ColumnType) Type() protoreflect.EnumType { + return &file_resource_proto_enumTypes[3] +} + +func (x ResourceTableColumnDefinition_ColumnType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ResourceTableColumnDefinition_ColumnType.Descriptor instead. +func (ResourceTableColumnDefinition_ColumnType) EnumDescriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{32, 0} +} + type PutBlobRequest_Method int32 const ( @@ -208,11 +283,11 @@ func (x PutBlobRequest_Method) String() string { } func (PutBlobRequest_Method) Descriptor() protoreflect.EnumDescriptor { - return file_resource_proto_enumTypes[3].Descriptor() + return file_resource_proto_enumTypes[4].Descriptor() } func (PutBlobRequest_Method) Type() protoreflect.EnumType { - return &file_resource_proto_enumTypes[3] + return &file_resource_proto_enumTypes[4] } func (x PutBlobRequest_Method) Number() protoreflect.EnumNumber { @@ -221,7 +296,7 @@ func (x PutBlobRequest_Method) Number() protoreflect.EnumNumber { // Deprecated: Use PutBlobRequest_Method.Descriptor instead. func (PutBlobRequest_Method) EnumDescriptor() ([]byte, []int) { - return file_resource_proto_rawDescGZIP(), []int{31, 0} + return file_resource_proto_rawDescGZIP(), []int{34, 0} } type ResourceKey struct { @@ -2377,6 +2452,275 @@ func (x *HealthCheckResponse) GetStatus() HealthCheckResponse_ServingStatus { return HealthCheckResponse_UNKNOWN } +// ResourceTable is a protobuf variation of the kubernetes Table object. +// This format allows specifying a flexible set of columns related to a given resource +type ResourceTable struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Columns describes each column in the returned items array. The number of cells per row + // will always match the number of column definitions. + Columns []*ResourceTableColumnDefinition `protobuf:"bytes,1,rep,name=columns,proto3" json:"columns,omitempty"` + // rows is the list of items in the table. + Rows []*ResourceTableRow `protobuf:"bytes,2,rep,name=rows,proto3" json:"rows,omitempty"` + // When more results exist, pass this in the next request + NextPageToken string `protobuf:"bytes,3,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + // ResourceVersion of the list response + // +optional + ResourceVersion int64 `protobuf:"varint,4,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // remainingItemCount is the number of subsequent items in the list which are not included in this + // list response. If the list request contained label or field selectors, then the number of + // remaining items is unknown and the field will be left unset and omitted during serialization. + // If the list is complete (either because it is not chunking or because this is the last chunk), + // then there are no more remaining items and this field will be left unset and omitted during + // serialization. + // + // The intended use of the remainingItemCount is *estimating* the size of a collection. Clients + // should not rely on the remainingItemCount to be set or to be exact. + // +optional + RemainingItemCount int64 `protobuf:"varint,5,opt,name=remaining_item_count,json=remainingItemCount,proto3" json:"remaining_item_count,omitempty"` +} + +func (x *ResourceTable) Reset() { + *x = ResourceTable{} + mi := &file_resource_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResourceTable) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceTable) ProtoMessage() {} + +func (x *ResourceTable) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceTable.ProtoReflect.Descriptor instead. +func (*ResourceTable) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{31} +} + +func (x *ResourceTable) GetColumns() []*ResourceTableColumnDefinition { + if x != nil { + return x.Columns + } + return nil +} + +func (x *ResourceTable) GetRows() []*ResourceTableRow { + if x != nil { + return x.Rows + } + return nil +} + +func (x *ResourceTable) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +func (x *ResourceTable) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *ResourceTable) GetRemainingItemCount() int64 { + if x != nil { + return x.RemainingItemCount + } + return 0 +} + +// TableColumnDefinition contains information about a column returned in the Table. +type ResourceTableColumnDefinition struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // name is a human readable name for the column. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // Defines the column type. In k8s, this will resolve into both the type and format fields + Type ResourceTableColumnDefinition_ColumnType `protobuf:"varint,2,opt,name=type,proto3,enum=resource.ResourceTableColumnDefinition_ColumnType" json:"type,omitempty"` + // The value is an arry of given type + IsArray bool `protobuf:"varint,3,opt,name=is_array,json=isArray,proto3" json:"is_array,omitempty"` + // description is a human readable description of this column. + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` + // Properties about this column (helpful for indexing and search) + Properties *ResourceTableColumnDefinition_Properties `protobuf:"bytes,5,opt,name=properties,proto3" json:"properties,omitempty"` + // priority is an integer defining the relative importance of this column compared to others. Lower + // numbers are considered higher priority. Columns that may be omitted in limited space scenarios + // should be given a higher priority. + Priority int32 `protobuf:"varint,6,opt,name=priority,proto3" json:"priority,omitempty"` +} + +func (x *ResourceTableColumnDefinition) Reset() { + *x = ResourceTableColumnDefinition{} + mi := &file_resource_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResourceTableColumnDefinition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceTableColumnDefinition) ProtoMessage() {} + +func (x *ResourceTableColumnDefinition) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceTableColumnDefinition.ProtoReflect.Descriptor instead. +func (*ResourceTableColumnDefinition) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{32} +} + +func (x *ResourceTableColumnDefinition) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ResourceTableColumnDefinition) GetType() ResourceTableColumnDefinition_ColumnType { + if x != nil { + return x.Type + } + return ResourceTableColumnDefinition_UNKNOWN_TYPE +} + +func (x *ResourceTableColumnDefinition) GetIsArray() bool { + if x != nil { + return x.IsArray + } + return false +} + +func (x *ResourceTableColumnDefinition) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *ResourceTableColumnDefinition) GetProperties() *ResourceTableColumnDefinition_Properties { + if x != nil { + return x.Properties + } + return nil +} + +func (x *ResourceTableColumnDefinition) GetPriority() int32 { + if x != nil { + return x.Priority + } + return 0 +} + +// TableRow is an individual row in a table. +type ResourceTableRow struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The resource referenced by this row + Key *ResourceKey `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // The resource version for the given values + ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // Cells will be as wide as the column definitions array + // Numeric values will be encoded using big endian bytes + // All arrays will be JSON encoded + Cells [][]byte `protobuf:"bytes,3,rep,name=cells,proto3" json:"cells,omitempty"` + // This field may contains the additional information about each object based on the request. + // The value will be at least a partial object metadata, and perhaps the full object metadata. + // When this value exists, it should include both the key and the resource_version otherwise + // they may be lost in the conversion to k8s resource + // +optional + Object []byte `protobuf:"bytes,4,opt,name=object,proto3" json:"object,omitempty"` +} + +func (x *ResourceTableRow) Reset() { + *x = ResourceTableRow{} + mi := &file_resource_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResourceTableRow) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceTableRow) ProtoMessage() {} + +func (x *ResourceTableRow) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[33] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceTableRow.ProtoReflect.Descriptor instead. +func (*ResourceTableRow) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{33} +} + +func (x *ResourceTableRow) GetKey() *ResourceKey { + if x != nil { + return x.Key + } + return nil +} + +func (x *ResourceTableRow) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *ResourceTableRow) GetCells() [][]byte { + if x != nil { + return x.Cells + } + return nil +} + +func (x *ResourceTableRow) GetObject() []byte { + if x != nil { + return x.Object + } + return nil +} + type PutBlobRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2396,7 +2740,7 @@ type PutBlobRequest struct { func (x *PutBlobRequest) Reset() { *x = PutBlobRequest{} - mi := &file_resource_proto_msgTypes[31] + mi := &file_resource_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2408,7 +2752,7 @@ func (x *PutBlobRequest) String() string { func (*PutBlobRequest) ProtoMessage() {} func (x *PutBlobRequest) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[31] + mi := &file_resource_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2421,7 +2765,7 @@ func (x *PutBlobRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PutBlobRequest.ProtoReflect.Descriptor instead. func (*PutBlobRequest) Descriptor() ([]byte, []int) { - return file_resource_proto_rawDescGZIP(), []int{31} + return file_resource_proto_rawDescGZIP(), []int{34} } func (x *PutBlobRequest) GetResource() *ResourceKey { @@ -2475,7 +2819,7 @@ type PutBlobResponse struct { func (x *PutBlobResponse) Reset() { *x = PutBlobResponse{} - mi := &file_resource_proto_msgTypes[32] + mi := &file_resource_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2487,7 +2831,7 @@ func (x *PutBlobResponse) String() string { func (*PutBlobResponse) ProtoMessage() {} func (x *PutBlobResponse) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[32] + mi := &file_resource_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2500,7 +2844,7 @@ func (x *PutBlobResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PutBlobResponse.ProtoReflect.Descriptor instead. func (*PutBlobResponse) Descriptor() ([]byte, []int) { - return file_resource_proto_rawDescGZIP(), []int{32} + return file_resource_proto_rawDescGZIP(), []int{35} } func (x *PutBlobResponse) GetError() *ErrorResult { @@ -2566,7 +2910,7 @@ type GetBlobRequest struct { func (x *GetBlobRequest) Reset() { *x = GetBlobRequest{} - mi := &file_resource_proto_msgTypes[33] + mi := &file_resource_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2578,7 +2922,7 @@ func (x *GetBlobRequest) String() string { func (*GetBlobRequest) ProtoMessage() {} func (x *GetBlobRequest) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[33] + mi := &file_resource_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2591,7 +2935,7 @@ func (x *GetBlobRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetBlobRequest.ProtoReflect.Descriptor instead. func (*GetBlobRequest) Descriptor() ([]byte, []int) { - return file_resource_proto_rawDescGZIP(), []int{33} + return file_resource_proto_rawDescGZIP(), []int{36} } func (x *GetBlobRequest) GetResource() *ResourceKey { @@ -2634,7 +2978,7 @@ type GetBlobResponse struct { func (x *GetBlobResponse) Reset() { *x = GetBlobResponse{} - mi := &file_resource_proto_msgTypes[34] + mi := &file_resource_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2646,7 +2990,7 @@ func (x *GetBlobResponse) String() string { func (*GetBlobResponse) ProtoMessage() {} func (x *GetBlobResponse) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[34] + mi := &file_resource_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2659,7 +3003,7 @@ func (x *GetBlobResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetBlobResponse.ProtoReflect.Descriptor instead. func (*GetBlobResponse) Descriptor() ([]byte, []int) { - return file_resource_proto_rawDescGZIP(), []int{34} + return file_resource_proto_rawDescGZIP(), []int{37} } func (x *GetBlobResponse) GetError() *ErrorResult { @@ -2701,7 +3045,7 @@ type WatchEvent_Resource struct { func (x *WatchEvent_Resource) Reset() { *x = WatchEvent_Resource{} - mi := &file_resource_proto_msgTypes[35] + mi := &file_resource_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2713,7 +3057,7 @@ func (x *WatchEvent_Resource) String() string { func (*WatchEvent_Resource) ProtoMessage() {} func (x *WatchEvent_Resource) ProtoReflect() protoreflect.Message { - mi := &file_resource_proto_msgTypes[35] + mi := &file_resource_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2743,6 +3087,92 @@ func (x *WatchEvent_Resource) GetValue() []byte { return nil } +// These values are not part of standard k8s format +// however these are useful when indexing and analyzing results +type ResourceTableColumnDefinition_Properties struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // All values in this columns should be unique + UniqueValues bool `protobuf:"varint,1,opt,name=unique_values,json=uniqueValues,proto3" json:"unique_values,omitempty"` + // The string value is free text; using text analyzers is appropriate + FreeText bool `protobuf:"varint,2,opt,name=free_text,json=freeText,proto3" json:"free_text,omitempty"` + // The value(s) are reasonable to use for search refinement + // When indexing, these values would be good to add to an index + Filterable bool `protobuf:"varint,3,opt,name=filterable,proto3" json:"filterable,omitempty"` + // When true, every value should exist + // not_null with a nil default_value should be an error + NotNull bool `protobuf:"varint,4,opt,name=not_null,json=notNull,proto3" json:"not_null,omitempty"` + // When missing, this value can be used + DefaultValue []byte `protobuf:"bytes,5,opt,name=default_value,json=defaultValue,proto3" json:"default_value,omitempty"` +} + +func (x *ResourceTableColumnDefinition_Properties) Reset() { + *x = ResourceTableColumnDefinition_Properties{} + mi := &file_resource_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResourceTableColumnDefinition_Properties) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceTableColumnDefinition_Properties) ProtoMessage() {} + +func (x *ResourceTableColumnDefinition_Properties) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[39] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceTableColumnDefinition_Properties.ProtoReflect.Descriptor instead. +func (*ResourceTableColumnDefinition_Properties) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{32, 0} +} + +func (x *ResourceTableColumnDefinition_Properties) GetUniqueValues() bool { + if x != nil { + return x.UniqueValues + } + return false +} + +func (x *ResourceTableColumnDefinition_Properties) GetFreeText() bool { + if x != nil { + return x.FreeText + } + return false +} + +func (x *ResourceTableColumnDefinition_Properties) GetFilterable() bool { + if x != nil { + return x.Filterable + } + return false +} + +func (x *ResourceTableColumnDefinition_Properties) GetNotNull() bool { + if x != nil { + return x.NotNull + } + return false +} + +func (x *ResourceTableColumnDefinition_Properties) GetDefaultValue() []byte { + if x != nil { + return x.DefaultValue + } + return nil +} + var File_resource_proto protoreflect.FileDescriptor var file_resource_proto_rawDesc = []byte{ @@ -3028,109 +3458,174 @@ var file_resource_proto_rawDesc = []byte{ 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x52, - 0x56, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x22, 0xd3, - 0x01, 0x0a, 0x0e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x31, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, - 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x21, 0x0a, - 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x1c, 0x0a, 0x06, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x12, 0x08, 0x0a, 0x04, 0x47, 0x52, 0x50, 0x43, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, - 0x54, 0x50, 0x10, 0x01, 0x22, 0xc1, 0x01, 0x0a, 0x0f, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, - 0x68, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, - 0x0a, 0x07, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x22, 0x98, 0x01, 0x0a, 0x0e, 0x47, 0x65, 0x74, - 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x4b, 0x65, 0x79, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x29, - 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x6d, 0x75, 0x73, - 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6d, 0x75, 0x73, 0x74, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x42, 0x79, - 0x74, 0x65, 0x73, 0x22, 0x89, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, - 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2a, - 0x33, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x4f, 0x6c, - 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x78, 0x61, - 0x63, 0x74, 0x10, 0x01, 0x32, 0xed, 0x02, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x15, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, - 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x15, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x57, - 0x61, 0x74, 0x63, 0x68, 0x12, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, - 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x30, 0x01, 0x32, 0xc9, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x3b, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, - 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, - 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x18, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x12, 0x17, 0x2e, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x32, 0x8b, 0x01, 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x3e, - 0x0a, 0x07, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x56, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x22, 0x87, + 0x02, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, + 0x12, 0x41, 0x0a, 0x07, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x27, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, + 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x63, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x73, 0x12, 0x2e, 0x0a, 0x04, 0x72, 0x6f, 0x77, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, 0x77, 0x52, 0x04, 0x72, + 0x6f, 0x77, 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, 0x12, 0x29, 0x0a, 0x10, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x14, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, + 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x49, + 0x74, 0x65, 0x6d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xf1, 0x04, 0x0a, 0x1d, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, + 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x46, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, 0x79, 0x70, 0x65, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, 0x5f, 0x61, 0x72, 0x72, + 0x61, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, 0x41, 0x72, 0x72, 0x61, + 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x52, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, + 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x32, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, + 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x52, 0x0a, 0x70, 0x72, 0x6f, + 0x70, 0x65, 0x72, 0x74, 0x69, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x1a, 0xae, 0x01, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x69, + 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x75, 0x6e, 0x69, 0x71, 0x75, + 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x72, 0x65, 0x65, 0x5f, + 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x66, 0x72, 0x65, 0x65, + 0x54, 0x65, 0x78, 0x74, 0x12, 0x1e, 0x0a, 0x0a, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x61, 0x62, + 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x61, 0x62, 0x6c, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x74, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6e, 0x6f, 0x74, 0x4e, 0x75, 0x6c, 0x6c, 0x12, + 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x22, 0x95, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x10, 0x0a, 0x0c, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x54, + 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, + 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x4f, 0x4f, 0x4c, 0x45, 0x41, 0x4e, 0x10, 0x02, 0x12, 0x09, + 0x0a, 0x05, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x49, 0x4e, 0x54, + 0x36, 0x34, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x10, 0x05, 0x12, + 0x0a, 0x0a, 0x06, 0x44, 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x10, 0x06, 0x12, 0x08, 0x0a, 0x04, 0x44, + 0x41, 0x54, 0x45, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x54, 0x49, + 0x4d, 0x45, 0x10, 0x08, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x10, 0x09, + 0x12, 0x0a, 0x0a, 0x06, 0x4f, 0x42, 0x4a, 0x45, 0x43, 0x54, 0x10, 0x0a, 0x22, 0x94, 0x01, 0x0a, + 0x10, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x6f, + 0x77, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0c, 0x52, 0x05, 0x63, 0x65, 0x6c, 0x6c, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x22, 0xd3, 0x01, 0x0a, 0x0e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, + 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x06, 0x6d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, - 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, - 0x0a, 0x07, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, - 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x57, - 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x48, 0x0a, - 0x09, 0x49, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x1c, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, - 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, - 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, + 0x6f, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x1c, 0x0a, 0x06, 0x4d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x08, 0x0a, 0x04, 0x47, 0x52, 0x50, 0x43, 0x10, 0x00, 0x12, + 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x01, 0x22, 0xc1, 0x01, 0x0a, 0x0f, 0x50, 0x75, + 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, + 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, + 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, + 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, + 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6d, 0x65, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x22, 0x98, 0x01, + 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x31, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x28, + 0x0a, 0x10, 0x6d, 0x75, 0x73, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x62, 0x79, 0x74, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6d, 0x75, 0x73, 0x74, 0x50, 0x72, + 0x6f, 0x78, 0x79, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x89, 0x01, 0x0a, 0x0f, 0x47, 0x65, 0x74, + 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, + 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x2a, 0x33, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x10, 0x0a, 0x0c, + 0x4e, 0x6f, 0x74, 0x4f, 0x6c, 0x64, 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x00, 0x12, 0x09, + 0x0a, 0x05, 0x45, 0x78, 0x61, 0x63, 0x74, 0x10, 0x01, 0x32, 0xed, 0x02, 0x0a, 0x0d, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x52, + 0x65, 0x61, 0x64, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, + 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x3b, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x4c, 0x69, 0x73, + 0x74, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x37, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, + 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x32, 0xc9, 0x01, 0x0a, 0x0d, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x3b, 0x0a, 0x06, 0x53, + 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x48, 0x69, 0x73, 0x74, + 0x6f, 0x72, 0x79, 0x12, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x4f, 0x72, 0x69, 0x67, + 0x69, 0x6e, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4f, 0x72, + 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x8b, 0x01, 0x0a, 0x09, 0x42, 0x6c, 0x6f, 0x62, 0x53, 0x74, + 0x6f, 0x72, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x18, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, + 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x50, 0x75, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x18, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, + 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x32, 0x57, 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, + 0x63, 0x73, 0x12, 0x48, 0x0a, 0x09, 0x49, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, + 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x39, 0x5a, 0x37, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, + 0x6e, 0x61, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2f, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -3145,117 +3640,127 @@ func file_resource_proto_rawDescGZIP() []byte { return file_resource_proto_rawDescData } -var file_resource_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_resource_proto_msgTypes = make([]protoimpl.MessageInfo, 36) +var file_resource_proto_enumTypes = make([]protoimpl.EnumInfo, 5) +var file_resource_proto_msgTypes = make([]protoimpl.MessageInfo, 40) var file_resource_proto_goTypes = []any{ - (ResourceVersionMatch)(0), // 0: resource.ResourceVersionMatch - (WatchEvent_Type)(0), // 1: resource.WatchEvent.Type - (HealthCheckResponse_ServingStatus)(0), // 2: resource.HealthCheckResponse.ServingStatus - (PutBlobRequest_Method)(0), // 3: resource.PutBlobRequest.Method - (*ResourceKey)(nil), // 4: resource.ResourceKey - (*ResourceWrapper)(nil), // 5: resource.ResourceWrapper - (*ResourceMeta)(nil), // 6: resource.ResourceMeta - (*ErrorResult)(nil), // 7: resource.ErrorResult - (*ErrorDetails)(nil), // 8: resource.ErrorDetails - (*ErrorCause)(nil), // 9: resource.ErrorCause - (*CreateRequest)(nil), // 10: resource.CreateRequest - (*CreateResponse)(nil), // 11: resource.CreateResponse - (*UpdateRequest)(nil), // 12: resource.UpdateRequest - (*UpdateResponse)(nil), // 13: resource.UpdateResponse - (*DeleteRequest)(nil), // 14: resource.DeleteRequest - (*DeleteResponse)(nil), // 15: resource.DeleteResponse - (*ReadRequest)(nil), // 16: resource.ReadRequest - (*ReadResponse)(nil), // 17: resource.ReadResponse - (*Requirement)(nil), // 18: resource.Requirement - (*ListOptions)(nil), // 19: resource.ListOptions - (*ListRequest)(nil), // 20: resource.ListRequest - (*ListResponse)(nil), // 21: resource.ListResponse - (*WatchRequest)(nil), // 22: resource.WatchRequest - (*WatchEvent)(nil), // 23: resource.WatchEvent - (*SearchRequest)(nil), // 24: resource.SearchRequest - (*GroupBy)(nil), // 25: resource.GroupBy - (*Group)(nil), // 26: resource.Group - (*SearchResponse)(nil), // 27: resource.SearchResponse - (*HistoryRequest)(nil), // 28: resource.HistoryRequest - (*HistoryResponse)(nil), // 29: resource.HistoryResponse - (*OriginRequest)(nil), // 30: resource.OriginRequest - (*ResourceOriginInfo)(nil), // 31: resource.ResourceOriginInfo - (*OriginResponse)(nil), // 32: resource.OriginResponse - (*HealthCheckRequest)(nil), // 33: resource.HealthCheckRequest - (*HealthCheckResponse)(nil), // 34: resource.HealthCheckResponse - (*PutBlobRequest)(nil), // 35: resource.PutBlobRequest - (*PutBlobResponse)(nil), // 36: resource.PutBlobResponse - (*GetBlobRequest)(nil), // 37: resource.GetBlobRequest - (*GetBlobResponse)(nil), // 38: resource.GetBlobResponse - (*WatchEvent_Resource)(nil), // 39: resource.WatchEvent.Resource + (ResourceVersionMatch)(0), // 0: resource.ResourceVersionMatch + (WatchEvent_Type)(0), // 1: resource.WatchEvent.Type + (HealthCheckResponse_ServingStatus)(0), // 2: resource.HealthCheckResponse.ServingStatus + (ResourceTableColumnDefinition_ColumnType)(0), // 3: resource.ResourceTableColumnDefinition.ColumnType + (PutBlobRequest_Method)(0), // 4: resource.PutBlobRequest.Method + (*ResourceKey)(nil), // 5: resource.ResourceKey + (*ResourceWrapper)(nil), // 6: resource.ResourceWrapper + (*ResourceMeta)(nil), // 7: resource.ResourceMeta + (*ErrorResult)(nil), // 8: resource.ErrorResult + (*ErrorDetails)(nil), // 9: resource.ErrorDetails + (*ErrorCause)(nil), // 10: resource.ErrorCause + (*CreateRequest)(nil), // 11: resource.CreateRequest + (*CreateResponse)(nil), // 12: resource.CreateResponse + (*UpdateRequest)(nil), // 13: resource.UpdateRequest + (*UpdateResponse)(nil), // 14: resource.UpdateResponse + (*DeleteRequest)(nil), // 15: resource.DeleteRequest + (*DeleteResponse)(nil), // 16: resource.DeleteResponse + (*ReadRequest)(nil), // 17: resource.ReadRequest + (*ReadResponse)(nil), // 18: resource.ReadResponse + (*Requirement)(nil), // 19: resource.Requirement + (*ListOptions)(nil), // 20: resource.ListOptions + (*ListRequest)(nil), // 21: resource.ListRequest + (*ListResponse)(nil), // 22: resource.ListResponse + (*WatchRequest)(nil), // 23: resource.WatchRequest + (*WatchEvent)(nil), // 24: resource.WatchEvent + (*SearchRequest)(nil), // 25: resource.SearchRequest + (*GroupBy)(nil), // 26: resource.GroupBy + (*Group)(nil), // 27: resource.Group + (*SearchResponse)(nil), // 28: resource.SearchResponse + (*HistoryRequest)(nil), // 29: resource.HistoryRequest + (*HistoryResponse)(nil), // 30: resource.HistoryResponse + (*OriginRequest)(nil), // 31: resource.OriginRequest + (*ResourceOriginInfo)(nil), // 32: resource.ResourceOriginInfo + (*OriginResponse)(nil), // 33: resource.OriginResponse + (*HealthCheckRequest)(nil), // 34: resource.HealthCheckRequest + (*HealthCheckResponse)(nil), // 35: resource.HealthCheckResponse + (*ResourceTable)(nil), // 36: resource.ResourceTable + (*ResourceTableColumnDefinition)(nil), // 37: resource.ResourceTableColumnDefinition + (*ResourceTableRow)(nil), // 38: resource.ResourceTableRow + (*PutBlobRequest)(nil), // 39: resource.PutBlobRequest + (*PutBlobResponse)(nil), // 40: resource.PutBlobResponse + (*GetBlobRequest)(nil), // 41: resource.GetBlobRequest + (*GetBlobResponse)(nil), // 42: resource.GetBlobResponse + (*WatchEvent_Resource)(nil), // 43: resource.WatchEvent.Resource + (*ResourceTableColumnDefinition_Properties)(nil), // 44: resource.ResourceTableColumnDefinition.Properties } var file_resource_proto_depIdxs = []int32{ - 8, // 0: resource.ErrorResult.details:type_name -> resource.ErrorDetails - 9, // 1: resource.ErrorDetails.causes:type_name -> resource.ErrorCause - 4, // 2: resource.CreateRequest.key:type_name -> resource.ResourceKey - 7, // 3: resource.CreateResponse.error:type_name -> resource.ErrorResult - 4, // 4: resource.UpdateRequest.key:type_name -> resource.ResourceKey - 7, // 5: resource.UpdateResponse.error:type_name -> resource.ErrorResult - 4, // 6: resource.DeleteRequest.key:type_name -> resource.ResourceKey - 7, // 7: resource.DeleteResponse.error:type_name -> resource.ErrorResult - 4, // 8: resource.ReadRequest.key:type_name -> resource.ResourceKey - 7, // 9: resource.ReadResponse.error:type_name -> resource.ErrorResult - 4, // 10: resource.ListOptions.key:type_name -> resource.ResourceKey - 18, // 11: resource.ListOptions.labels:type_name -> resource.Requirement - 18, // 12: resource.ListOptions.fields:type_name -> resource.Requirement + 9, // 0: resource.ErrorResult.details:type_name -> resource.ErrorDetails + 10, // 1: resource.ErrorDetails.causes:type_name -> resource.ErrorCause + 5, // 2: resource.CreateRequest.key:type_name -> resource.ResourceKey + 8, // 3: resource.CreateResponse.error:type_name -> resource.ErrorResult + 5, // 4: resource.UpdateRequest.key:type_name -> resource.ResourceKey + 8, // 5: resource.UpdateResponse.error:type_name -> resource.ErrorResult + 5, // 6: resource.DeleteRequest.key:type_name -> resource.ResourceKey + 8, // 7: resource.DeleteResponse.error:type_name -> resource.ErrorResult + 5, // 8: resource.ReadRequest.key:type_name -> resource.ResourceKey + 8, // 9: resource.ReadResponse.error:type_name -> resource.ErrorResult + 5, // 10: resource.ListOptions.key:type_name -> resource.ResourceKey + 19, // 11: resource.ListOptions.labels:type_name -> resource.Requirement + 19, // 12: resource.ListOptions.fields:type_name -> resource.Requirement 0, // 13: resource.ListRequest.version_match:type_name -> resource.ResourceVersionMatch - 19, // 14: resource.ListRequest.options:type_name -> resource.ListOptions - 5, // 15: resource.ListResponse.items:type_name -> resource.ResourceWrapper - 7, // 16: resource.ListResponse.error:type_name -> resource.ErrorResult - 19, // 17: resource.WatchRequest.options:type_name -> resource.ListOptions + 20, // 14: resource.ListRequest.options:type_name -> resource.ListOptions + 6, // 15: resource.ListResponse.items:type_name -> resource.ResourceWrapper + 8, // 16: resource.ListResponse.error:type_name -> resource.ErrorResult + 20, // 17: resource.WatchRequest.options:type_name -> resource.ListOptions 1, // 18: resource.WatchEvent.type:type_name -> resource.WatchEvent.Type - 39, // 19: resource.WatchEvent.resource:type_name -> resource.WatchEvent.Resource - 39, // 20: resource.WatchEvent.previous:type_name -> resource.WatchEvent.Resource - 25, // 21: resource.SearchRequest.groupBy:type_name -> resource.GroupBy - 5, // 22: resource.SearchResponse.items:type_name -> resource.ResourceWrapper - 26, // 23: resource.SearchResponse.groups:type_name -> resource.Group - 4, // 24: resource.HistoryRequest.key:type_name -> resource.ResourceKey - 6, // 25: resource.HistoryResponse.items:type_name -> resource.ResourceMeta - 7, // 26: resource.HistoryResponse.error:type_name -> resource.ErrorResult - 4, // 27: resource.OriginRequest.key:type_name -> resource.ResourceKey - 4, // 28: resource.ResourceOriginInfo.key:type_name -> resource.ResourceKey - 31, // 29: resource.OriginResponse.items:type_name -> resource.ResourceOriginInfo - 7, // 30: resource.OriginResponse.error:type_name -> resource.ErrorResult + 43, // 19: resource.WatchEvent.resource:type_name -> resource.WatchEvent.Resource + 43, // 20: resource.WatchEvent.previous:type_name -> resource.WatchEvent.Resource + 26, // 21: resource.SearchRequest.groupBy:type_name -> resource.GroupBy + 6, // 22: resource.SearchResponse.items:type_name -> resource.ResourceWrapper + 27, // 23: resource.SearchResponse.groups:type_name -> resource.Group + 5, // 24: resource.HistoryRequest.key:type_name -> resource.ResourceKey + 7, // 25: resource.HistoryResponse.items:type_name -> resource.ResourceMeta + 8, // 26: resource.HistoryResponse.error:type_name -> resource.ErrorResult + 5, // 27: resource.OriginRequest.key:type_name -> resource.ResourceKey + 5, // 28: resource.ResourceOriginInfo.key:type_name -> resource.ResourceKey + 32, // 29: resource.OriginResponse.items:type_name -> resource.ResourceOriginInfo + 8, // 30: resource.OriginResponse.error:type_name -> resource.ErrorResult 2, // 31: resource.HealthCheckResponse.status:type_name -> resource.HealthCheckResponse.ServingStatus - 4, // 32: resource.PutBlobRequest.resource:type_name -> resource.ResourceKey - 3, // 33: resource.PutBlobRequest.method:type_name -> resource.PutBlobRequest.Method - 7, // 34: resource.PutBlobResponse.error:type_name -> resource.ErrorResult - 4, // 35: resource.GetBlobRequest.resource:type_name -> resource.ResourceKey - 7, // 36: resource.GetBlobResponse.error:type_name -> resource.ErrorResult - 16, // 37: resource.ResourceStore.Read:input_type -> resource.ReadRequest - 10, // 38: resource.ResourceStore.Create:input_type -> resource.CreateRequest - 12, // 39: resource.ResourceStore.Update:input_type -> resource.UpdateRequest - 14, // 40: resource.ResourceStore.Delete:input_type -> resource.DeleteRequest - 20, // 41: resource.ResourceStore.List:input_type -> resource.ListRequest - 22, // 42: resource.ResourceStore.Watch:input_type -> resource.WatchRequest - 24, // 43: resource.ResourceIndex.Search:input_type -> resource.SearchRequest - 28, // 44: resource.ResourceIndex.History:input_type -> resource.HistoryRequest - 30, // 45: resource.ResourceIndex.Origin:input_type -> resource.OriginRequest - 35, // 46: resource.BlobStore.PutBlob:input_type -> resource.PutBlobRequest - 37, // 47: resource.BlobStore.GetBlob:input_type -> resource.GetBlobRequest - 33, // 48: resource.Diagnostics.IsHealthy:input_type -> resource.HealthCheckRequest - 17, // 49: resource.ResourceStore.Read:output_type -> resource.ReadResponse - 11, // 50: resource.ResourceStore.Create:output_type -> resource.CreateResponse - 13, // 51: resource.ResourceStore.Update:output_type -> resource.UpdateResponse - 15, // 52: resource.ResourceStore.Delete:output_type -> resource.DeleteResponse - 21, // 53: resource.ResourceStore.List:output_type -> resource.ListResponse - 23, // 54: resource.ResourceStore.Watch:output_type -> resource.WatchEvent - 27, // 55: resource.ResourceIndex.Search:output_type -> resource.SearchResponse - 29, // 56: resource.ResourceIndex.History:output_type -> resource.HistoryResponse - 32, // 57: resource.ResourceIndex.Origin:output_type -> resource.OriginResponse - 36, // 58: resource.BlobStore.PutBlob:output_type -> resource.PutBlobResponse - 38, // 59: resource.BlobStore.GetBlob:output_type -> resource.GetBlobResponse - 34, // 60: resource.Diagnostics.IsHealthy:output_type -> resource.HealthCheckResponse - 49, // [49:61] is the sub-list for method output_type - 37, // [37:49] is the sub-list for method input_type - 37, // [37:37] is the sub-list for extension type_name - 37, // [37:37] is the sub-list for extension extendee - 0, // [0:37] is the sub-list for field type_name + 37, // 32: resource.ResourceTable.columns:type_name -> resource.ResourceTableColumnDefinition + 38, // 33: resource.ResourceTable.rows:type_name -> resource.ResourceTableRow + 3, // 34: resource.ResourceTableColumnDefinition.type:type_name -> resource.ResourceTableColumnDefinition.ColumnType + 44, // 35: resource.ResourceTableColumnDefinition.properties:type_name -> resource.ResourceTableColumnDefinition.Properties + 5, // 36: resource.ResourceTableRow.key:type_name -> resource.ResourceKey + 5, // 37: resource.PutBlobRequest.resource:type_name -> resource.ResourceKey + 4, // 38: resource.PutBlobRequest.method:type_name -> resource.PutBlobRequest.Method + 8, // 39: resource.PutBlobResponse.error:type_name -> resource.ErrorResult + 5, // 40: resource.GetBlobRequest.resource:type_name -> resource.ResourceKey + 8, // 41: resource.GetBlobResponse.error:type_name -> resource.ErrorResult + 17, // 42: resource.ResourceStore.Read:input_type -> resource.ReadRequest + 11, // 43: resource.ResourceStore.Create:input_type -> resource.CreateRequest + 13, // 44: resource.ResourceStore.Update:input_type -> resource.UpdateRequest + 15, // 45: resource.ResourceStore.Delete:input_type -> resource.DeleteRequest + 21, // 46: resource.ResourceStore.List:input_type -> resource.ListRequest + 23, // 47: resource.ResourceStore.Watch:input_type -> resource.WatchRequest + 25, // 48: resource.ResourceIndex.Search:input_type -> resource.SearchRequest + 29, // 49: resource.ResourceIndex.History:input_type -> resource.HistoryRequest + 31, // 50: resource.ResourceIndex.Origin:input_type -> resource.OriginRequest + 39, // 51: resource.BlobStore.PutBlob:input_type -> resource.PutBlobRequest + 41, // 52: resource.BlobStore.GetBlob:input_type -> resource.GetBlobRequest + 34, // 53: resource.Diagnostics.IsHealthy:input_type -> resource.HealthCheckRequest + 18, // 54: resource.ResourceStore.Read:output_type -> resource.ReadResponse + 12, // 55: resource.ResourceStore.Create:output_type -> resource.CreateResponse + 14, // 56: resource.ResourceStore.Update:output_type -> resource.UpdateResponse + 16, // 57: resource.ResourceStore.Delete:output_type -> resource.DeleteResponse + 22, // 58: resource.ResourceStore.List:output_type -> resource.ListResponse + 24, // 59: resource.ResourceStore.Watch:output_type -> resource.WatchEvent + 28, // 60: resource.ResourceIndex.Search:output_type -> resource.SearchResponse + 30, // 61: resource.ResourceIndex.History:output_type -> resource.HistoryResponse + 33, // 62: resource.ResourceIndex.Origin:output_type -> resource.OriginResponse + 40, // 63: resource.BlobStore.PutBlob:output_type -> resource.PutBlobResponse + 42, // 64: resource.BlobStore.GetBlob:output_type -> resource.GetBlobResponse + 35, // 65: resource.Diagnostics.IsHealthy:output_type -> resource.HealthCheckResponse + 54, // [54:66] is the sub-list for method output_type + 42, // [42:54] is the sub-list for method input_type + 42, // [42:42] is the sub-list for extension type_name + 42, // [42:42] is the sub-list for extension extendee + 0, // [0:42] is the sub-list for field type_name } func init() { file_resource_proto_init() } @@ -3268,8 +3773,8 @@ func file_resource_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_resource_proto_rawDesc, - NumEnums: 4, - NumMessages: 36, + NumEnums: 5, + NumMessages: 40, NumExtensions: 0, NumServices: 4, }, diff --git a/pkg/storage/unified/resource/resource.proto b/pkg/storage/unified/resource/resource.proto index d403865d03c..9c89f86ef1a 100644 --- a/pkg/storage/unified/resource/resource.proto +++ b/pkg/storage/unified/resource/resource.proto @@ -108,24 +108,24 @@ message ErrorDetails { } message ErrorCause { - // A machine-readable description of the cause of the error. If this value is - // empty there is no information available. - string reason = 1; - // A human-readable description of the cause of the error. This field may be - // presented as-is to a reader. - // +optional - string message = 2; - // The field of the resource that has caused this error, as named by its JSON - // serialization. May include dot and postfix notation for nested attributes. - // Arrays are zero-indexed. Fields may appear more than once in an array of - // causes due to fields having multiple errors. - // Optional. - // - // Examples: - // "name" - the field "name" on the current resource - // "items[0].name" - the field "name" on the first array entry in "items" - // +optional - string field = 3; + // A machine-readable description of the cause of the error. If this value is + // empty there is no information available. + string reason = 1; + // A human-readable description of the cause of the error. This field may be + // presented as-is to a reader. + // +optional + string message = 2; + // The field of the resource that has caused this error, as named by its JSON + // serialization. May include dot and postfix notation for nested attributes. + // Arrays are zero-indexed. Fields may appear more than once in an array of + // causes due to fields having multiple errors. + // Optional. + // + // Examples: + // "name" - the field "name" on the current resource + // "items[0].name" - the field "name" on the first array entry in "items" + // +optional + string field = 3; } // ---------------------------------- @@ -448,6 +448,118 @@ message HealthCheckResponse { ServingStatus status = 1; } +// ResourceTable is a protobuf variation of the kubernetes Table object. +// This format allows specifying a flexible set of columns related to a given resource +message ResourceTable { + // Columns describes each column in the returned items array. The number of cells per row + // will always match the number of column definitions. + repeated ResourceTableColumnDefinition columns = 1; + + // rows is the list of items in the table. + repeated ResourceTableRow rows = 2; + + // When more results exist, pass this in the next request + string next_page_token = 3; + + // ResourceVersion of the list response + // +optional + int64 resource_version = 4; + + // remainingItemCount is the number of subsequent items in the list which are not included in this + // list response. If the list request contained label or field selectors, then the number of + // remaining items is unknown and the field will be left unset and omitted during serialization. + // If the list is complete (either because it is not chunking or because this is the last chunk), + // then there are no more remaining items and this field will be left unset and omitted during + // serialization. + // + // The intended use of the remainingItemCount is *estimating* the size of a collection. Clients + // should not rely on the remainingItemCount to be set or to be exact. + // +optional + int64 remaining_item_count = 5; +} + +// TableColumnDefinition contains information about a column returned in the Table. +message ResourceTableColumnDefinition { + // See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more. + // When converted to a k8s Table, this will become two fields: type and format + enum ColumnType { + UNKNOWN_TYPE = 0; + STRING = 1; + BOOLEAN = 2; + INT32 = 3; + INT64 = 4; + FLOAT = 5; + DOUBLE = 6; + DATE = 7; + DATE_TIME = 8; + BINARY = 9; + OBJECT = 10; // map[string]any + } + + // These values are not part of standard k8s format + // however these are useful when indexing and analyzing results + message Properties { + // All values in this columns should be unique + bool unique_values = 1; + + // The string value is free text; using text analyzers is appropriate + bool free_text = 2; + + // The value(s) are reasonable to use for search refinement + // When indexing, these values would be good to add to an index + bool filterable = 3; + + // When true, every value should exist + // not_null with a nil default_value should be an error + bool not_null = 4; + + // When missing, this value can be used + bytes default_value = 5; + } + + // name is a human readable name for the column. + string name = 1; + + // Defines the column type. In k8s, this will resolve into both the type and format fields + ColumnType type = 2; + + // The value is an arry of given type + bool is_array = 3; + + // description is a human readable description of this column. + string description = 4; + + // Properties about this column (helpful for indexing and search) + Properties properties = 5; + + // priority is an integer defining the relative importance of this column compared to others. Lower + // numbers are considered higher priority. Columns that may be omitted in limited space scenarios + // should be given a higher priority. + int32 priority = 6; +} + +// TableRow is an individual row in a table. +message ResourceTableRow { + // The resource referenced by this row + ResourceKey key = 1; + + // The resource version for the given values + int64 resource_version = 2; + + // Cells will be as wide as the column definitions array + // Numeric values will be encoded using big endian bytes + // All arrays will be JSON encoded + repeated bytes cells = 3; + + // This field may contains the additional information about each object based on the request. + // The value will be at least a partial object metadata, and perhaps the full object metadata. + // When this value exists, it should include both the key and the resource_version otherwise + // they may be lost in the conversion to k8s resource + // +optional + bytes object = 4; +} + + //---------------------------- // Blob Support //---------------------------- diff --git a/pkg/storage/unified/resource/table.go b/pkg/storage/unified/resource/table.go new file mode 100644 index 00000000000..a0f6574c07e --- /dev/null +++ b/pkg/storage/unified/resource/table.go @@ -0,0 +1,549 @@ +package resource + +import ( + "bytes" + "encoding/base64" + "encoding/binary" + "encoding/json" + "fmt" + "io" + reflect "reflect" + "strconv" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/grafana/grafana-plugin-sdk-go/data/utils/jsoniter" +) + +// Convert the the protobuf model into k8s (will decode each value) +func (x *ResourceTable) ToK8s() (metav1.Table, error) { + table := metav1.Table{ + ListMeta: metav1.ListMeta{ + Continue: x.NextPageToken, + }, + } + if x.RemainingItemCount > 0 { + table.RemainingItemCount = &x.RemainingItemCount + } + if x.ResourceVersion > 0 { + table.ResourceVersion = strconv.FormatInt(x.ResourceVersion, 10) + } + + columnCount := len(x.Columns) + columns := make([]resourceTableColumn, columnCount) + table.ColumnDefinitions = make([]metav1.TableColumnDefinition, columnCount) + for i, c := range x.Columns { + col, err := newResourceTableColumn(c, i) + if err != nil { + return table, err + } + columns[i] = *col + table.ColumnDefinitions[i] = metav1.TableColumnDefinition{ + Name: c.Name, + Description: c.Description, + Priority: c.Priority, + Type: col.OpenAPIType, + Format: col.OpenAPIFormat, + } + } + + var err error + table.Rows = make([]metav1.TableRow, len(x.Rows)) + for i, r := range x.Rows { + row := metav1.TableRow{ + Cells: make([]interface{}, len(r.Cells)), + } + if len(r.Cells) != columnCount { + return table, fmt.Errorf("invalid cells size (have=%d, expect=%d)", len(r.Cells), columnCount) + } + + for j, v := range r.Cells { + row.Cells[j], err = columns[j].Decode(v) + if err != nil { + col := columns[j] + return table, fmt.Errorf("error decoding (row=%d, column=%d, type=%s) %w", i, j, col.def.Type.String(), err) + } + } + + // The raw object value + if r.Object != nil { + row.Object = runtime.RawExtension{ + Raw: r.Object, + } + } else if r.Key != nil { + obj := &metav1.PartialObjectMetadata{ + ObjectMeta: metav1.ObjectMeta{ + Name: r.Key.Name, + Namespace: r.Key.Namespace, + }, + } + if r.ResourceVersion > 0 { + obj.ResourceVersion = strconv.FormatInt(r.ResourceVersion, 10) + } + row.Object.Object = obj + row.Object.Raw, err = json.Marshal(obj) + if err != nil { + return table, err + } + } + table.Rows[i] = row + } + return table, err +} + +type TableBuilder struct { + ResourceTable + + lookup map[string]*resourceTableColumn + + // Just keep track of it + hasDuplicateNames bool +} + +func NewTableBuilder(cols []*ResourceTableColumnDefinition) (*TableBuilder, error) { + table := &TableBuilder{ + ResourceTable: ResourceTable{ + Columns: cols, + }, + + lookup: make(map[string]*resourceTableColumn, len(cols)), + } + var err error + for i, v := range cols { + if table.lookup[v.Name] != nil { + table.hasDuplicateNames = true + continue + } + table.lookup[v.Name], err = newResourceTableColumn(v, i) + if err != nil { + return nil, err + } + } + return table, err +} + +func (x *TableBuilder) AddRow(key *ResourceKey, rv int64, vals map[string]any) error { + row := &ResourceTableRow{ + Key: key, + ResourceVersion: rv, + Cells: make([][]byte, len(x.Columns)), + } + + for k, v := range vals { + column, ok := x.lookup[k] + if !ok { + return fmt.Errorf("unknown column: %s", k) + } + b, err := column.Encode(v) + if err != nil { + return err + } + row.Cells[column.index] = b + } + + x.Rows = append(x.Rows, row) + return nil +} + +type resourceTableColumn struct { + def *ResourceTableColumnDefinition + index int + + // Used for array indexing + reader func(iter *jsoniter.Iterator) (any, error) + writer func(v any, stream *jsoniter.Stream) error + + OpenAPIType string + OpenAPIFormat string +} + +// nolint:gocyclo +func newResourceTableColumn(def *ResourceTableColumnDefinition, index int) (*resourceTableColumn, error) { + col := &resourceTableColumn{def: def, index: index} + + // Initially ignore the array property, we wil wrap that at the end + switch def.Type { + case ResourceTableColumnDefinition_UNKNOWN_TYPE: + return nil, fmt.Errorf("unknown column type") + + case ResourceTableColumnDefinition_STRING: + col.OpenAPIType = "string" + col.reader = func(iter *jsoniter.Iterator) (any, error) { + return iter.ReadString() + } + + case ResourceTableColumnDefinition_BOOLEAN: + col.OpenAPIType = "boolean" + col.reader = func(iter *jsoniter.Iterator) (any, error) { + return iter.ReadBool() + } + + case ResourceTableColumnDefinition_INT32: + col.OpenAPIType = "number" + col.OpenAPIFormat = "int32" + col.reader = func(iter *jsoniter.Iterator) (any, error) { + return iter.ReadInt32() + } + + case ResourceTableColumnDefinition_INT64: + col.OpenAPIType = "number" + col.OpenAPIFormat = "int64" + col.reader = func(iter *jsoniter.Iterator) (any, error) { + return iter.ReadInt64() + } + + case ResourceTableColumnDefinition_DOUBLE: + col.OpenAPIType = "number" + col.OpenAPIFormat = "double" + col.reader = func(iter *jsoniter.Iterator) (any, error) { + return iter.ReadFloat64() + } + + case ResourceTableColumnDefinition_FLOAT: + col.OpenAPIType = "number" + col.OpenAPIFormat = "float" + col.reader = func(iter *jsoniter.Iterator) (any, error) { + return iter.ReadFloat32() + } + + // Encode everything we can -- the lower conversion can happen later? + case ResourceTableColumnDefinition_DATE, ResourceTableColumnDefinition_DATE_TIME: + col.OpenAPIType = "string" + col.OpenAPIFormat = "date" + if def.Type == ResourceTableColumnDefinition_DATE_TIME { + col.OpenAPIFormat = "date_time" + } + col.writer = func(v any, stream *jsoniter.Stream) error { + var t time.Time + + switch typed := v.(type) { + case time.Time: + t = typed + case *time.Time: + t = *typed + case int64: + t = time.UnixMilli(typed) + default: + return fmt.Errorf("unsupported date conversion (%t)", v) + } + + // encode as millis has fastest parsing + stream.WriteInt64(t.UnixMilli()) + return stream.Error + } + col.reader = func(iter *jsoniter.Iterator) (any, error) { + nxt, err := iter.WhatIsNext() + if err != nil { + return nil, err + } + switch nxt { + case jsoniter.NumberValue: + ts, err := iter.ReadInt64() + if err != nil { + return nil, err + } + return time.UnixMilli(ts).UTC(), nil + + default: + return nil, fmt.Errorf("unexpected JSON for date: %+v", nxt) + } + } + + case ResourceTableColumnDefinition_BINARY: + col.OpenAPIType = "binary" + col.writer = func(v any, stream *jsoniter.Stream) error { + b, ok := v.([]byte) + if !ok { + return fmt.Errorf("unexpected binary type, found: %t", v) + } + str := base64.StdEncoding.EncodeToString(b) + stream.WriteString(str) + return stream.Error + } + col.reader = func(iter *jsoniter.Iterator) (any, error) { + str, err := iter.ReadString() + if err != nil { + return nil, err + } + return base64.StdEncoding.DecodeString(str) + } + + case ResourceTableColumnDefinition_OBJECT: + col.OpenAPIType = "string" + col.OpenAPIFormat = "json" + + col.reader = func(iter *jsoniter.Iterator) (any, error) { + return iter.Read() + } + } + + return col, nil +} + +func (x *resourceTableColumn) IsNotNil() bool { + if x.def.Properties != nil { + return x.def.Properties.NotNull + } + return false +} + +// nolint:gocyclo +func (x *resourceTableColumn) Encode(v any) ([]byte, error) { + if v == nil { + if x.IsNotNil() { + return nil, fmt.Errorf("expecting non-null value") + } + return nil, nil // no types to write + } + + // Arrays will always use JSON formatting + if !x.def.IsArray { + switch x.def.Type { + case ResourceTableColumnDefinition_STRING: + { + s, ok := v.(string) + if !ok { + return nil, fmt.Errorf("expecting a string field") + } + return []byte(s), nil + } + case ResourceTableColumnDefinition_BINARY: + { + s, ok := v.([]byte) + if !ok { + return nil, fmt.Errorf("expecting a byte array") + } + return s, nil + } + case ResourceTableColumnDefinition_BOOLEAN: + { + b, ok := v.(bool) + if !ok { + switch typed := v.(type) { + case *bool: + b = *typed + case int: + b = typed != 0 + case int32: + b = typed != 0 + case int64: + b = typed != 0 + default: + return nil, fmt.Errorf("unexpected input for double field: %t", v) + } + } + if b { + return []byte{1}, nil + } + return []byte{0}, nil + } + case ResourceTableColumnDefinition_DATE_TIME, ResourceTableColumnDefinition_DATE: + { + f, ok := v.(time.Time) + if !ok { + switch typed := v.(type) { + case *time.Time: + f = *typed + case metav1.Time: + f = typed.Time + case *metav1.Time: + f = typed.Time + case int64: + f = time.UnixMilli(typed) + default: + return nil, fmt.Errorf("unexpected input for time field: %t", v) + } + } + ts := f.UnixMilli() + var buf bytes.Buffer + err := binary.Write(&buf, binary.BigEndian, ts) + return buf.Bytes(), err + } + case ResourceTableColumnDefinition_DOUBLE: + { + f, ok := v.(float64) + if !ok { + switch typed := v.(type) { + case int: + f = float64(typed) + case int64: + f = float64(typed) + case float32: + f = float64(typed) + case uint64: + f = float64(typed) + case uint: + f = float64(typed) + default: + return nil, fmt.Errorf("unexpected input for double field: %t", v) + } + } + var buf bytes.Buffer + err := binary.Write(&buf, binary.BigEndian, f) + return buf.Bytes(), err + } + case ResourceTableColumnDefinition_INT64: + { + f, ok := v.(int64) + if !ok { + switch typed := v.(type) { + case int: + f = int64(typed) + case int32: + f = int64(typed) + case float32: + f = int64(typed) + case uint64: + f = int64(typed) + case uint: + f = int64(typed) + default: + return nil, fmt.Errorf("unexpected input for int64 field: %t", v) + } + } + var buf bytes.Buffer + err := binary.Write(&buf, binary.BigEndian, f) + return buf.Bytes(), err + } + default: + // use JSON encoding below + } + } + + buff := bytes.NewBuffer(make([]byte, 0, 128)) + cfg := jsoniter.ConfigCompatibleWithStandardLibrary + stream := cfg.BorrowStream(buff) + defer cfg.ReturnStream(stream) + var err error + + writer := func(v any) error { + if v == nil { + stream.WriteNil() // only happens in an array + } else if x.writer != nil { + return x.writer(v, stream) + } else { + stream.WriteVal(v) + } + return stream.Error + } + + if x.def.IsArray { + stream.WriteArrayStart() + + switch reflect.TypeOf(v).Kind() { + case reflect.Slice, reflect.Array: + s := reflect.ValueOf(v) + for i := 0; i < s.Len(); i++ { + if i > 0 { + stream.WriteMore() + } + sub := s.Index(i).Interface() + err = writer(sub) + if err != nil { + return nil, err + } + } + default: + // single value? just write it and we will see? + err = writer(v) + if err != nil { + return nil, err + } + } + + stream.WriteArrayEnd() + } else { + err = writer(v) + } + if err != nil { + return nil, err + } + if stream.Error != nil { + return nil, stream.Error + } + + err = stream.Flush() + if err != nil { + return nil, err + } + return json.RawMessage(buff.Bytes()), nil +} + +// nolint:gocyclo +func (x *resourceTableColumn) Decode(buff []byte) (any, error) { + if len(buff) == 0 { + return nil, nil + } + if !x.def.IsArray { + switch x.def.Type { + case ResourceTableColumnDefinition_STRING: + return string(buff), nil + case ResourceTableColumnDefinition_BINARY: + return buff, nil + case ResourceTableColumnDefinition_BOOLEAN: + if len(buff) == 1 { + return buff[0] != 0, nil + } + case ResourceTableColumnDefinition_DOUBLE: + { + var f float64 + count, err := binary.Decode(buff, binary.BigEndian, &f) + if count == 8 && err == nil { + return f, nil + } + } + case ResourceTableColumnDefinition_INT64: + { + var f int64 + count, err := binary.Decode(buff, binary.BigEndian, &f) + if count == 8 && err == nil { + return f, nil + } + } + case ResourceTableColumnDefinition_DATE_TIME, ResourceTableColumnDefinition_DATE: + { + var f int64 + count, err := binary.Decode(buff, binary.BigEndian, &f) + if count == 8 && err == nil { + return time.UnixMilli(f).UTC(), nil + } + } + default: + // use JSON decoding below + } + } + + iter, err := jsoniter.ParseBytes(jsoniter.ConfigDefault, buff) + if err != nil { + return nil, err + } + + if x.def.IsArray { + vals := []any{} // it may have nulls + + for more, err := iter.ReadArray(); more; more, err = iter.ReadArray() { + if err != nil { + return nil, err + } + v, err := x.reader(iter) + //nolint:errorlint + if err != nil && err != io.EOF { // EOF is normal when jsoniter is done + return nil, err + } + vals = append(vals, v) + } + + return vals, iter.ReadError() + } + + v, err := x.reader(iter) + //nolint:errorlint + if err == io.EOF { + err = nil + } else if err != nil { + return nil, err + } + return v, err +} diff --git a/pkg/storage/unified/resource/table_test.go b/pkg/storage/unified/resource/table_test.go new file mode 100644 index 00000000000..17c110ea18f --- /dev/null +++ b/pkg/storage/unified/resource/table_test.go @@ -0,0 +1,343 @@ +package resource + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// AssertTableSnapshot will match a ResourceTable vs the saved value +func AssertTableSnapshot(t *testing.T, path string, table *ResourceTable) { + t.Helper() + + k8sTable, err := table.ToK8s() + require.NoError(t, err, "unable to create table response", path) + actual, err := json.MarshalIndent(k8sTable, "", " ") + require.NoError(t, err, "unable to write table json", path) + + // Safe to disable, this is a test. + // nolint:gosec + expected, err := os.ReadFile(path) + if err != nil || len(expected) < 1 { + assert.Fail(t, "missing file") + } else if assert.JSONEq(t, string(expected), string(actual)) { + return // everything is OK + } + + // Write the snapshot + // Safe to disable, this is a test. + // nolint:gosec + err = os.WriteFile(path, actual, 0600) + require.NoError(t, err) + fmt.Printf("Updated table snapshot: %s\n", path) +} + +func TestTableFormat(t *testing.T) { + columns := []*ResourceTableColumnDefinition{ + { + Name: "title", + Type: ResourceTableColumnDefinition_STRING, + }, + { + Name: "stats.count", + Type: ResourceTableColumnDefinition_INT64, + }, + { + Name: "number", + Type: ResourceTableColumnDefinition_DOUBLE, + + Description: "float64 value", + }, + { + Name: "tags", + Type: ResourceTableColumnDefinition_STRING, + IsArray: true, + }, + } + + var err error + builder, err := NewTableBuilder(columns) + require.NoError(t, err) + + err = builder.AddRow(&ResourceKey{ + Namespace: "default", + Group: "ggg", + Resource: "xyz", // does not have a home in table! + Name: "aaa", + }, 10, map[string]any{ + "title": "AAA", + "number": 12345, + "tags": "one", // becomes an array + }) + require.NoError(t, err) + + err = builder.AddRow(&ResourceKey{ + Namespace: "default", + Group: "ggg", + Resource: "xyz", // does not have a home in table! + Name: "bbb", + }, 10, map[string]any{ + "title": "BBB", + "stats.count": 12345, + "tags": []string{"one", "two"}, // becomes an array + }) + require.NoError(t, err) + + // Check the snapshot + AssertTableSnapshot(t, filepath.Join("testdata", "simple-table.json"), &builder.ResourceTable) +} + +func TestColumnEncoding(t *testing.T) { + tests := []struct { + // The table definition + def *ResourceTableColumnDefinition + + // Passed to the encode function + input any + + // Expected error from input + input_err error + + // Skip the encode step + raw []byte + + // Expected output from decode + output any + + // Expected error from decode + output_err error + }{ + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_STRING, + }, + input: "aaa", // expects output to match + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_STRING, + IsArray: true, + }, + input: "bbb", + output: []any{"bbb"}, + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_INT64, + }, + input: 12345, + output: int64(12345), + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_INT64, + IsArray: true, + }, + input: 12345, + output: []any{int64(12345)}, + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_DOUBLE, + }, + input: 12345, + output: float64(12345), + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_DOUBLE, + IsArray: true, + }, + input: 12345, + output: []any{float64(12345)}, + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_BOOLEAN, + }, + input: true, + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_BOOLEAN, + IsArray: true, + }, + input: []any{true, false, true}, + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_FLOAT, + }, + input: 23.4, + output: float32(23.4), + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_FLOAT, + IsArray: true, + }, + input: 23.4, + output: []any{float32(23.4)}, + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_INT32, + }, + input: 56, + output: int32(56), + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_INT32, + IsArray: true, + }, + input: 56, + output: []any{int32(56)}, + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_DATE_TIME, + }, + input: time.UnixMilli(946674000000).UTC(), + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_DATE_TIME, + IsArray: true, + }, + input: time.UnixMilli(946674000000).UTC(), + output: []any{ + time.UnixMilli(946674000000).UTC(), + }, + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_DATE, + }, + input: time.UnixMilli(946674000000).UTC(), + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_DATE, + IsArray: true, + }, + input: time.UnixMilli(946674000000).UTC(), + output: []any{ + time.UnixMilli(946674000000).UTC(), + }, + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_BINARY, + }, + input: []byte{1, 2, 3, 4}, + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_BINARY, + IsArray: true, + }, + input: []any{ + []byte{1, 2, 3, 4}, + }, + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_OBJECT, + }, + input: map[string]any{ + "hello": "world", + }, + }, + { + def: &ResourceTableColumnDefinition{ + Type: ResourceTableColumnDefinition_OBJECT, + IsArray: true, + }, + input: map[string]any{ + "hello": "world", + }, + output: []any{ + map[string]any{ + "hello": "world", + }, + }, + }, + } + + // Keep track of the types that have tests + testedTypes := make(map[ResourceTableColumnDefinition_ColumnType]bool) + testedArrays := make(map[ResourceTableColumnDefinition_ColumnType]bool) + for _, test := range tests { + var sb strings.Builder + if test.def.IsArray { + sb.WriteString("[]") + testedArrays[test.def.Type] = true + } else { + testedTypes[test.def.Type] = true + } + sb.WriteString(test.def.Type.String()) + if test.def.Name != "" { + sb.WriteString("(") + sb.WriteString(test.def.Name) + sb.WriteString(")") + } + sb.WriteString("=") + sb.WriteString(fmt.Sprintf("%v", test.input)) + + t.Run(sb.String(), func(t *testing.T) { + t.Parallel() + + col, err := newResourceTableColumn(test.def, 0) + require.NoError(t, err) + + buff := test.raw + if buff == nil { + buff, err = col.Encode(test.input) + if test.input_err != nil { + require.Equal(t, test.input_err, err) + } else { + require.NoError(t, err) + } + } + + out, err := col.Decode(buff) + if test.output_err != nil { + require.Equal(t, test.output_err, err) + } else { + require.NoError(t, err) + } + + if test.output != nil { + require.Equal(t, test.output, out) + } else { + require.Equal(t, test.input, out) + } + }) + } + + t.Run("ensure type coverage", func(t *testing.T) { + missingTypes := []string{} + missingArrays := []string{} + + // Make sure we have at least one test for each type + for i := ResourceTableColumnDefinition_STRING; i <= ResourceTableColumnDefinition_OBJECT; i++ { + if !testedTypes[i] { + missingTypes = append(missingTypes, i.String()) + } + if !testedArrays[i] { + missingArrays = append(missingArrays, i.String()) + } + } + + require.Empty(t, missingTypes, "missing tests for types") + require.Empty(t, missingArrays, "missing array tests for types") + }) +} diff --git a/pkg/storage/unified/resource/testdata/simple-table.json b/pkg/storage/unified/resource/testdata/simple-table.json new file mode 100644 index 00000000000..d95379968b7 --- /dev/null +++ b/pkg/storage/unified/resource/testdata/simple-table.json @@ -0,0 +1,72 @@ +{ + "metadata": {}, + "columnDefinitions": [ + { + "name": "title", + "type": "string", + "format": "", + "description": "", + "priority": 0 + }, + { + "name": "stats.count", + "type": "number", + "format": "int64", + "description": "", + "priority": 0 + }, + { + "name": "number", + "type": "number", + "format": "double", + "description": "float64 value", + "priority": 0 + }, + { + "name": "tags", + "type": "string", + "format": "", + "description": "", + "priority": 0 + } + ], + "rows": [ + { + "cells": [ + "AAA", + null, + 12345, + [ + "one" + ] + ], + "object": { + "metadata": { + "name": "aaa", + "namespace": "default", + "resourceVersion": "10", + "creationTimestamp": null + } + } + }, + { + "cells": [ + "BBB", + 12345, + null, + [ + "one", + "two" + ] + ], + "object": { + "metadata": { + "name": "bbb", + "namespace": "default", + "resourceVersion": "10", + "creationTimestamp": null + } + } + } + ] +} \ No newline at end of file