syntax = "proto3";
package resource;

option go_package = "github.com/grafana/grafana/pkg/storage/unified/resourcepb";

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;
}

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 {
  // Deprecated, use ResourceVersionMatch V2
  DEPRECATED_NotOlderThan = 0;
  DEPRECATED_Exact = 1;
}

enum ResourceVersionMatchV2 {
  UNKNOWN = 0;
  Unset = 1;
  Exact = 2;
  NotOlderThan = 3;
}


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
  // DEPRECATED - use version_match_v2
  optional 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;

  ResourceVersionMatchV2 version_match_v2 = 7;
}

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;
}

// List items within a resource type & repository name
// Access control is managed above this request
message ListManagedObjectsRequest {
  // Starting from the requested page (other query parameters must match!)
  string next_page_token = 1;

  // Namespace (tenant)
  string namespace = 2;

  // The manager type (eg, terraform vs repo)
  string kind = 3;

  // The name of the manager
  string id = 4;
}

message ListManagedObjectsResponse {
  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 CountManagedObjectsRequest {
  // Namespace (tenant)
  string namespace = 1;

  // Manager kind: terraform, plugin, kubectl, repo
  string kind = 2;

  // Name of the manager (meaningful inside kind)
  string id = 3;
}

// Count the items that exist with
message CountManagedObjectsResponse {
  message ResourceCount {
    string kind = 1;
    string id = 2;
    string group = 3;
    string resource = 4;
    int64 count = 5;
  }

  // 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;
}

message QuotaUsageRequest {
  // Namespace (tenant)
  ResourceKey key = 1;
}

message QuotaUsageResponse {
  // Error details
  ErrorResult error = 1;

  // Current usage
  int64 usage = 2;

  // Current limit
  int64 limit = 3;
}

// 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);

  // 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);
}

// Query managed objects
// Results access control is based on access to the repository *not* the items
service ManagedObjectIndex {
  // Describe how many resources of each type exist within a repository
  rpc CountManagedObjects(CountManagedObjectsRequest) returns (CountManagedObjectsResponse);

  // List the resources of a specific kind within a repository
  rpc ListManagedObjects(ListManagedObjectsRequest) returns (ListManagedObjectsResponse);
}

// 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);
}

service Quotas {
  // Get current quota usage and limits
  rpc GetQuotaUsage(QuotaUsageRequest) returns (QuotaUsageResponse);
}
