syntax = "proto3"; package resource; option go_package = "github.com/grafana/grafana/pkg/storage/unified/resource"; message ResourceKey { // Namespace (tenant) string namespace = 2; // Resource Group string group = 1; // The resource type string resource = 3; // Resource identifier (unique within namespace+group+resource) string name = 4; } message ResourceWrapper { // The resource version int64 resource_version = 1; // Full kubernetes json bytes (although the resource version may not be accurate) bytes value = 2; } // Status structure is copied from: // https://github.com/kubernetes/apimachinery/blob/v0.30.1/pkg/apis/meta/v1/generated.proto#L979 // However, this is only used for error handling, never for succesful results message ErrorResult { // A human-readable description of the status of this operation. // +optional string message = 1; // A machine-readable description of why this operation is in the // "Failure" status. If this value is empty there // is no information available. A Reason clarifies an HTTP status // code but does not override it. // +optional string reason = 2; // Extended data associated with the reason. Each reason may define its // own extended details. This field is optional and the data returned // is not guaranteed to conform to any schema except that defined by // the reason type. // +optional // +listType=atomic ErrorDetails details = 3; // Suggested HTTP return code for this status, 0 if not set. // +optional int32 code = 4; } // ErrorDetails is a set of additional properties that MAY be set by the // server to provide additional information about a response. The Reason // field of a Status object defines what attributes will be set. Clients // must ignore fields that do not match the defined type of each attribute, // and should assume that any attribute may be empty, invalid, or under // defined. message ErrorDetails { // The name attribute of the resource associated with the status StatusReason // (when there is a single name which can be described). // +optional string name = 1; // The group attribute of the resource associated with the status StatusReason. // +optional string group = 2; // The kind attribute of the resource associated with the status StatusReason. // On some operations may differ from the requested resource Kind. // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds // +optional string kind = 3; // UID of the resource. // (when there is a single resource which can be described). // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids // +optional string uid = 6; // The Causes array includes more details associated with the StatusReason // failure. Not all StatusReasons may provide detailed causes. // +optional // +listType=atomic repeated ErrorCause causes = 4; // If specified, the time in seconds before the operation should be retried. Some errors may indicate // the client must take an alternate action - for those errors this field may indicate how long to wait // before taking the alternate action. // +optional int32 retryAfterSeconds = 5; } 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; } // ---------------------------------- // CRUD Objects // ---------------------------------- message CreateRequest { // Requires group+resource to be configuired // If name is not set, a unique name will be generated // The resourceVersion should not be set ResourceKey key = 1; // The resource JSON. bytes value = 2; } message CreateResponse { // Error details ErrorResult error = 1; // The updated resource version int64 resource_version = 2; } message UpdateRequest { // Full key must be set ResourceKey key = 1; // The current resource version int64 resource_version = 2; // The resource JSON. bytes value = 3; } message UpdateResponse { // Error details ErrorResult error = 1; // The updated resource version int64 resource_version = 2; } message DeleteRequest { ResourceKey key = 1; // The current resource version int64 resource_version = 2; // Preconditions: make sure the uid matches the current saved value // +optional string uid = 3; } message DeleteResponse { // Error details ErrorResult error = 1; // The resource version for the deletion marker int64 resource_version = 2; } message ReadRequest { ResourceKey key = 1; // Optionally pick an explicit resource version int64 resource_version = 2; // Optionally decide to return the latest RV if deleted bool include_deleted = 3; } message ReadResponse { // Error details ErrorResult error = 1; // The new resource version int64 resource_version = 2; // The properties bytes value = 3; } // ---------------------------------- // List Request/Response // ---------------------------------- // The label filtering requirements: // https://github.com/kubernetes/kubernetes/blob/v1.30.1/staging/src/k8s.io/apimachinery/pkg/labels/selector.go#L141 message Requirement { string key = 1; string operator = 2; // See https://github.com/kubernetes/kubernetes/blob/v1.30.1/staging/src/k8s.io/apimachinery/pkg/selection/operator.go#L21 repeated string values = 3; // typically one value, but depends on the operator } message ListOptions { // Group+Namespace+Resource (not name) ResourceKey key = 1; // (best effort) Match label // Allowed to send more results than actually match because the filter will be applied // to the results again in the client. That time with the full field selector repeated Requirement labels = 2; // (best effort) fields matcher // Allowed to send more results than actually match because the filter will be applied // to the results again in the client. That time with the full field selector repeated Requirement fields = 3; } enum ResourceVersionMatch { NotOlderThan = 0; Exact = 1; } message ListRequest { enum Source { STORE = 0; // the standard place HISTORY = 1; TRASH = 2; } // Starting from the requested page (other query parameters must match!) string next_page_token = 1; // The resource version int64 resource_version = 2; // List options ResourceVersionMatch version_match = 3; // Maximum number of items to return // NOTE responses will also be limited by the response payload size int64 limit = 4; // Filtering ListOptions options = 5; // Select values from history or trash Source source = 6; } message ListResponse { repeated ResourceWrapper items = 1; // When more results exist, pass this in the next request string next_page_token = 2; // ResourceVersion of the list response int64 resource_version = 3; // 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 = 4; // 0 won't be set either (no next page token) // Error details ErrorResult error = 5; } message WatchRequest { // ResourceVersion of last changes. Empty will default to full history int64 since = 1; // Additional options ListOptions options = 3; // Return initial events bool send_initial_events = 4; // When done with initial events, send a bookmark event bool allow_watch_bookmarks = 5; } message WatchEvent { enum Type { UNKNOWN = 0; ADDED = 1; MODIFIED = 2; DELETED = 3; BOOKMARK = 4; ERROR = 5; } message Resource { int64 version = 1; bytes value = 2; } // Timestamp the event was sent int64 timestamp = 1; // The event type Type type = 2; // Resource version for the object Resource resource = 3; // Previous resource version (for update+delete) Resource previous = 4; } message BulkRequest { enum Action { // will be an error UNKNOWN = 0; // Matches Watch event enum ADDED = 1; MODIFIED = 2; DELETED = 3; } // NOTE everything in the same stream must share the same Namespace/Group/Resource ResourceKey key = 1; // Requested action Action action = 2; // The resource value bytes value = 3; // Hint that a new version will be written on-top of this string folder = 4; } message BulkResponse { message Summary { string namespace = 1; string group = 2; string resource = 3; int64 count = 4; int64 history = 5; int64 resource_version = 6; // The max saved RV // The previous count int64 previous_count = 7; int64 previous_history = 8; } // Collect a few invalid messages message Rejected { ResourceKey key = 1; BulkRequest.Action action = 2; string error = 3; } // Error details ErrorResult error = 1; // Total events processed int64 processed = 2; // Summary status for the processed values repeated Summary summary = 3; // Rejected repeated Rejected rejected = 4; } // Get statistics across multiple resources // For these queries, we do not need authorization to see the actual values message ResourceStatsRequest { // Namespace (tenant) string namespace = 1; // An optional list of group/resource identifiers // when empty, we assume searching across everything // NOTE, this query may need to federate across a few storage instances repeated string kinds = 2; // Limit the stats within a folder (not recursive!) string folder = 3; } message ResourceStatsResponse { message Stats { // Resource group string group = 1; // Resource name string resource = 2; // Number of items int64 count = 3; } // Error details ErrorResult error = 1; // All results exist within this key repeated Stats stats = 2; } // Search within a single resource message ResourceSearchRequest { message Sort { string field = 1; bool desc = 2; // defaults to ascending } message Facet { string field = 1; int64 limit = 2; // For now, only term queries, eventually? // numeric queries // date queries } // The key must include namespace + group + resource ListOptions options = 1; // To search additional resource types, add additional keys to this list // NOTE: queries will only support federation across kinds with common fields repeated ResourceKey federated = 2; // When a query exists, it is parsed and used to influence // query string for chosen implementation (currently just bleve) // The score is only relevant when a query exists string query = 3; // max results int64 limit = 4; // where to start the query (eg, From) int64 offset = 5; // sorting repeated Sort sortBy = 6; // calculate field statistics map facet = 7; // the return fields (empty will return everything) repeated string fields = 8; // explain each result (added to the each row) bool explain = 9; bool is_deleted = 10; int64 page = 11; int64 permission = 12; } message ResourceSearchResponse { message Facet { string field = 1; // The distinct terms int64 total = 2; // The number of documents that do *not* have this field int64 missing = 3; // Top term stats repeated TermFacet terms = 4; // numeric range // date range facets } message TermFacet { string term = 1; int64 count = 2; } // Error details ErrorResult error = 1; // All results exist within this key ResourceKey key = 2; // Query results ResourceTable results = 3; // The total hit count int64 total_hits = 4; // indicates how expensive was the query with respect to bytes read double query_cost = 5; // maximum score across all fields double max_score = 6; // Facet results map facet = 7; } // List items within a resource type & repository name // Access control is managed above this request message ListRepositoryObjectsRequest { // Starting from the requested page (other query parameters must match!) string next_page_token = 1; // Namespace (tenant) string namespace = 2; // The name of the repository string name = 3; } message ListRepositoryObjectsResponse { message Item { // The resource object key ResourceKey object = 1; // Hash for the resource string path = 2; // Verification hash from the origin string hash = 3; // Change time from the origin int64 time = 5; // Title inside the payload string title = 6; // The name of the folder in metadata string folder = 7; } // Item iterator repeated Item items = 1; // More results exist... pass this in the next request string next_page_token = 2; // Error details ErrorResult error = 3; } // Count the items that exist with message CountRepositoryObjectsRequest { // Namespace (tenant) string namespace = 1; // The name of the repository // empty to count across all repositories string name = 2; } // Count the items that exist with message CountRepositoryObjectsResponse { message ResourceCount { string repository = 1; string group = 2; string resource = 3; int64 count = 4; } // Resource counts repeated ResourceCount items = 1; // Error details ErrorResult error = 2; } message HealthCheckRequest { string service = 1; } message HealthCheckResponse { enum ServingStatus { UNKNOWN = 0; SERVING = 1; NOT_SERVING = 2; SERVICE_UNKNOWN = 3; // Used only by the Watch method. } 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 array 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; } //---------------------------- // Restore Support //---------------------------- message RestoreRequest { // Full key must be set ResourceKey key = 1; // The resource version to restore int64 resource_version = 2; } message RestoreResponse { // Error details ErrorResult error = 1; // The updated resource version int64 resource_version = 2; } //---------------------------- // Blob Support //---------------------------- message PutBlobRequest { enum Method { // Use the inline raw []byte GRPC = 0; // Get a signed URL and PUT the value HTTP = 1; } // The resource that will use this blob // NOTE: the name may not yet exist, but group+resource are required ResourceKey resource = 1; // How to upload Method method = 2; // Content type header string content_type = 3; // Raw value to write // Not valid when method == HTTP bytes value = 4; } message PutBlobResponse { // Error details ErrorResult error = 1; // The blob uid. This must be saved into the resource to support access string uid = 2; // The URL where this value can be PUT string url = 3; // Size of the uploaded blob int64 size = 4; // Content hash used for an etag string hash = 5; // Validated mimetype (from content_type) string mime_type = 6; // Validated charset (from content_type) string charset = 7; } message GetBlobRequest { ResourceKey resource = 1; // The new resource version int64 resource_version = 2; // Do not return a pre-signed URL (when possible) bool must_proxy_bytes = 3; // The blob UID -- when empty, the value is loaded from annotations in the matching resource string uid = 4; } message GetBlobResponse { // Error details ErrorResult error = 1; // (optional) When possible, the system will return a presigned URL // that can be used to actually read the full blob+metadata // When this is set, neither info nor value will be set string url = 2; // Content type string content_type = 3; // The raw object value bytes value = 4; } // This provides the CRUD+List+Watch support needed for a k8s apiserver // The semantics and behaviors of this service are constrained by kubernetes // This does not understand the resource schemas, only deals with json bytes // Clients should not use this interface directly; it is for use in API Servers service ResourceStore { rpc Read(ReadRequest) returns (ReadResponse); rpc Create(CreateRequest) returns (CreateResponse); rpc Update(UpdateRequest) returns (UpdateResponse); rpc Delete(DeleteRequest) returns (DeleteResponse); rpc Restore(RestoreRequest) returns (RestoreResponse); // The results *may* include values that should not be returned to the user // This will perform best-effort filtering to increase performace. // NOTE: storage.Interface is ultimatly responsible for the final filtering rpc List(ListRequest) returns (ListResponse); // The results *may* include values that should not be returned to the user // This will perform best-effort filtering to increase performace. // NOTE: storage.Interface is ultimatly responsible for the final filtering rpc Watch(WatchRequest) returns (stream WatchEvent); } service BulkStore { // Write multiple resources to the same Namespace/Group/Resource // Events will not be sent until the stream is complete // Only the *create* permissions is checked rpc BulkProcess(stream BulkRequest) returns (BulkResponse); } // Unlike the ResourceStore, this service can be exposed to clients directly // It should be implemented with efficient indexes and does not need read-after-write semantics service ResourceIndex { rpc Search(ResourceSearchRequest) returns (ResourceSearchResponse); // Get the resource stats rpc GetStats(ResourceStatsRequest) returns (ResourceStatsResponse); } // Query repository info from the search index. // Results access control is based on access to the repository *not* the items service RepositoryIndex { // Describe how many resources of each type exist within a repository rpc CountRepositoryObjects(CountRepositoryObjectsRequest) returns (CountRepositoryObjectsResponse); // List the resources of a specific kind within a repository rpc ListRepositoryObjects(ListRepositoryObjectsRequest) returns (ListRepositoryObjectsResponse); } service BlobStore { // Upload a blob that will be saved in a resource rpc PutBlob(PutBlobRequest) returns (PutBlobResponse); // Get blob contents. When possible, this will return a signed URL // For large payloads, signed URLs are required to avoid protobuf message size limits rpc GetBlob(GetBlobRequest) returns (GetBlobResponse); // NOTE: there is no direct access to delete blobs // >> cleanup will be managed via garbage collection or direct access to the underlying storage } // Clients can use this service directly // NOTE: This is read only, and no read afer write guarantees service Diagnostics { // Check if the service is healthy rpc IsHealthy(HealthCheckRequest) returns (HealthCheckResponse); }