diff --git a/pkg/services/apiserver/service.go b/pkg/services/apiserver/service.go index 2a61456d206..0cac8c2d8c7 100644 --- a/pkg/services/apiserver/service.go +++ b/pkg/services/apiserver/service.go @@ -259,6 +259,11 @@ func (s *service) start(ctx context.Context) error { return err } + err = eDB.Init() + if err != nil { + return err + } + storeServer, err := sqlstash.ProvideSQLEntityServer(eDB, s.tracing) if err != nil { return err diff --git a/pkg/services/apiserver/utils/meta.go b/pkg/services/apiserver/utils/meta.go index e62828c1eb0..ced68d13162 100644 --- a/pkg/services/apiserver/utils/meta.go +++ b/pkg/services/apiserver/utils/meta.go @@ -7,6 +7,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" ) // Annotation keys @@ -46,6 +47,9 @@ type ResourceOriginInfo struct { // Accessor functions for k8s objects type GrafanaResourceMetaAccessor interface { + metav1.Object + metav1.Type + GetUpdatedTimestamp() (*time.Time, error) SetUpdatedTimestamp(v *time.Time) SetUpdatedTimestampMillis(unix int64) @@ -79,6 +83,7 @@ var _ GrafanaResourceMetaAccessor = (*grafanaResourceMetaAccessor)(nil) type grafanaResourceMetaAccessor struct { raw interface{} // the original object (it implements metav1.Object) obj metav1.Object + typ metav1.Type } // Accessor takes an arbitrary object pointer and returns meta.Interface. @@ -90,7 +95,18 @@ func MetaAccessor(raw interface{}) (GrafanaResourceMetaAccessor, error) { if err != nil { return nil, err } - return &grafanaResourceMetaAccessor{raw, obj}, nil + typ, ok := raw.(metav1.Type) + if !ok { + typ, ok = obj.(metav1.Type) + } + if !ok { + return nil, fmt.Errorf("expecting the object to be a type") + } + return &grafanaResourceMetaAccessor{raw, obj, typ}, nil +} + +func (m *grafanaResourceMetaAccessor) Object() metav1.Object { + return m.obj } func (m *grafanaResourceMetaAccessor) set(key string, val string) { @@ -240,6 +256,172 @@ func (m *grafanaResourceMetaAccessor) GetOriginTimestamp() (*time.Time, error) { return &t, nil } +// GetAnnotations implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) GetAnnotations() map[string]string { + return m.obj.GetAnnotations() +} + +// GetCreationTimestamp implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) GetCreationTimestamp() metav1.Time { + return m.obj.GetCreationTimestamp() +} + +// GetDeletionGracePeriodSeconds implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) GetDeletionGracePeriodSeconds() *int64 { + return m.obj.GetDeletionGracePeriodSeconds() +} + +// GetDeletionTimestamp implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) GetDeletionTimestamp() *metav1.Time { + return m.obj.GetDeletionTimestamp() +} + +// GetFinalizers implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) GetFinalizers() []string { + return m.obj.GetFinalizers() +} + +// GetGenerateName implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) GetGenerateName() string { + return m.obj.GetGenerateName() +} + +// GetGeneration implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) GetGeneration() int64 { + return m.obj.GetGeneration() +} + +// GetLabels implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) GetLabels() map[string]string { + return m.obj.GetLabels() +} + +// GetManagedFields implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) GetManagedFields() []metav1.ManagedFieldsEntry { + return m.obj.GetManagedFields() +} + +// GetName implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) GetName() string { + return m.obj.GetName() +} + +// GetNamespace implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) GetNamespace() string { + return m.obj.GetNamespace() +} + +// GetOwnerReferences implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) GetOwnerReferences() []metav1.OwnerReference { + return m.obj.GetOwnerReferences() +} + +// GetResourceVersion implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) GetResourceVersion() string { + return m.obj.GetResourceVersion() +} + +// GetSelfLink implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) GetSelfLink() string { + return m.obj.GetSelfLink() +} + +// GetUID implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) GetUID() types.UID { + return m.obj.GetUID() +} + +// SetAnnotations implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) SetAnnotations(annotations map[string]string) { + m.obj.SetAnnotations(annotations) +} + +// SetCreationTimestamp implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) SetCreationTimestamp(timestamp metav1.Time) { + m.obj.SetCreationTimestamp(timestamp) +} + +// SetDeletionGracePeriodSeconds implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) SetDeletionGracePeriodSeconds(v *int64) { + m.obj.SetDeletionGracePeriodSeconds(v) +} + +// SetDeletionTimestamp implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) SetDeletionTimestamp(timestamp *metav1.Time) { + m.obj.SetDeletionTimestamp(timestamp) +} + +// SetFinalizers implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) SetFinalizers(finalizers []string) { + m.obj.SetFinalizers(finalizers) +} + +// SetGenerateName implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) SetGenerateName(name string) { + m.obj.SetGenerateName(name) +} + +// SetGeneration implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) SetGeneration(generation int64) { + m.obj.SetGeneration(generation) +} + +// SetLabels implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) SetLabels(labels map[string]string) { + m.obj.SetLabels(labels) +} + +// SetManagedFields implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) SetManagedFields(managedFields []metav1.ManagedFieldsEntry) { + m.obj.SetManagedFields(managedFields) +} + +// SetName implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) SetName(name string) { + m.obj.SetName(name) +} + +// SetNamespace implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) SetNamespace(namespace string) { + m.obj.SetNamespace(namespace) +} + +// SetOwnerReferences implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) SetOwnerReferences(v []metav1.OwnerReference) { + m.obj.SetOwnerReferences(v) +} + +// SetResourceVersion implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) SetResourceVersion(version string) { + m.obj.SetResourceVersion(version) +} + +// SetSelfLink implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) SetSelfLink(selfLink string) { + m.obj.SetSelfLink(selfLink) +} + +// SetUID implements GrafanaResourceMetaAccessor. +func (m *grafanaResourceMetaAccessor) SetUID(uid types.UID) { + m.obj.SetUID(uid) +} + +func (m *grafanaResourceMetaAccessor) GetAPIVersion() string { + return m.typ.GetAPIVersion() +} + +func (m *grafanaResourceMetaAccessor) SetAPIVersion(version string) { + m.typ.SetAPIVersion(version) +} + +func (m *grafanaResourceMetaAccessor) GetKind() string { + return m.typ.GetKind() +} + +func (m *grafanaResourceMetaAccessor) SetKind(kind string) { + m.typ.SetKind(kind) +} + func (m *grafanaResourceMetaAccessor) FindTitle(defaultTitle string) string { // look for Spec.Title or Spec.Name r := reflect.ValueOf(m.raw) diff --git a/pkg/services/store/entity/db/migrations/migrator.go b/pkg/services/store/entity/db/migrations/migrator.go index 78dddd396d1..a6564b38405 100644 --- a/pkg/services/store/entity/db/migrations/migrator.go +++ b/pkg/services/store/entity/db/migrations/migrator.go @@ -16,9 +16,19 @@ func MigrateEntityStore(engine *xorm.Engine, cfg *setting.Cfg, features featurem mg := migrator.NewScopedMigrator(engine, cfg, "entity") mg.AddCreateMigration() - initEntityTables(mg) + // Only in development for now!!! When we are ready, we can drop entity and use this + if cfg.Env == setting.Dev { + m2 := migrator.NewScopedMigrator(engine, cfg, "resource") + m2.AddCreateMigration() + initResourceTables(m2) + err := m2.Start(true, 0) + if err != nil { + return err + } + } + // since it's a new feature enable migration locking by default return mg.Start(true, 0) } diff --git a/pkg/services/store/entity/db/migrations/resource_mig.go b/pkg/services/store/entity/db/migrations/resource_mig.go new file mode 100644 index 00000000000..fe0d17b1b5b --- /dev/null +++ b/pkg/services/store/entity/db/migrations/resource_mig.go @@ -0,0 +1,151 @@ +package migrations + +import ( + "fmt" + + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" +) + +func initResourceTables(mg *migrator.Migrator) string { + marker := "Initialize resource tables (v0)" // changing this key wipe+rewrite everything + mg.AddMigration(marker, &migrator.RawSQLMigration{}) + + tables := []migrator.Table{} + + // This table helps support incrementing the resource version within a group+resource + tables = append(tables, migrator.Table{ + Name: "resource_version", + Columns: []*migrator.Column{ + {Name: "group", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, + {Name: "resource", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, + {Name: "rv", Type: migrator.DB_BigInt, Nullable: false}, // resource version + }, + Indices: []*migrator.Index{ + {Cols: []string{"group", "resource"}, Type: migrator.UniqueIndex}, + }, + }) + + tables = append(tables, migrator.Table{ + Name: "resource", // write only log? all events + Columns: []*migrator.Column{ + // SnowflakeID -- Each Create/Update/Delete call is an event + // Using snowflake ID doubles this field as an approximate timestamp + {Name: "event", Type: migrator.DB_BigInt, Nullable: false, IsPrimaryKey: true}, + + // This will be null on insert, and then updated once we are ready to commit the transaction + {Name: "rv", Type: migrator.DB_BigInt, Nullable: true}, + {Name: "previous_rv", Type: migrator.DB_BigInt, Nullable: true}, // needed? + + // Allows fast search for the first page in any query. + // Subsequent pages must use MAX(rv) AND is_compacted=false GROUP ... + {Name: "is_current", Type: migrator.DB_Bool, Nullable: false}, + + // Indicates that this is no longer the current version + // This value is updated every few minutes and makes the paged queries more efficient + {Name: "is_compacted", Type: migrator.DB_Bool, Nullable: false}, + + // Properties that exist in path/key (and duplicated in the json value) + {Name: "group", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, + {Name: "api_version", Type: migrator.DB_NVarchar, Length: 32, Nullable: false}, + {Name: "namespace", Type: migrator.DB_NVarchar, Length: 63, Nullable: true}, // namespace is not required (cluster scope) + {Name: "resource", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, + {Name: "name", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, + + // The operation that wrote this resource version + // 1: created, 2: updated, 3: deleted + {Name: "operation", Type: migrator.DB_Int, Nullable: false}, + + // Optional Commit message (currently only used for dashboards) + {Name: "message", Type: migrator.DB_Text, Nullable: false}, // defaults to empty string + + // The k8s resource JSON text (without the resourceVersion populated) + {Name: "value", Type: migrator.DB_MediumText, Nullable: false}, + + // Content hash -- this is appropriate to use for an etag value + {Name: "hash", Type: migrator.DB_NVarchar, Length: 32, Nullable: false}, + + // Path to linked blob (or null). This blob may be saved in SQL, or in an object store + {Name: "blob_path", Type: migrator.DB_NVarchar, Length: 1024, Nullable: true}, + }, + Indices: []*migrator.Index{ + {Cols: []string{"rv"}, Type: migrator.UniqueIndex}, + {Cols: []string{"is_current"}, Type: migrator.IndexType}, + {Cols: []string{"is_compacted"}, Type: migrator.IndexType}, + {Cols: []string{"operation"}, Type: migrator.IndexType}, + {Cols: []string{"namespace"}, Type: migrator.IndexType}, + {Cols: []string{"group", "resource", "name"}, Type: migrator.IndexType}, + {Cols: []string{"blob_path"}, Type: migrator.IndexType}, + }, + }) + + // The values in this table are created by parsing the the value JSON and writing these as searchable columns + // These *could* be in the same table, but this structure allows us to replace the table by first + // building a parallel structure, then swapping them... maybe :) + tables = append(tables, migrator.Table{ + Name: "resource_meta", // write only log? all events + Columns: []*migrator.Column{ + {Name: "event", Type: migrator.DB_BigInt, Nullable: false, IsPrimaryKey: true}, + + // Hashed label set + {Name: "label_set", Type: migrator.DB_NVarchar, Length: 64, Nullable: true}, // null is no labels + + // Helpful filters + {Name: "folder", Type: migrator.DB_NVarchar, Length: 190, Nullable: true}, // uid of folder + + // For sorting values come from metadata.annotations#grafana.app/* + {Name: "created_at", Type: migrator.DB_BigInt, Nullable: false}, + {Name: "updated_at", Type: migrator.DB_BigInt, Nullable: false}, + + // Origin metadata helps implement efficient provisioning checks + {Name: "origin", Type: migrator.DB_NVarchar, Length: 64, Nullable: true}, // The origin name + {Name: "origin_path", Type: migrator.DB_Text, Nullable: true}, // Path to resource + {Name: "origin_hash", Type: migrator.DB_NVarchar, Length: 128, Nullable: true}, // Origin hash + {Name: "origin_ts", Type: migrator.DB_BigInt, Nullable: true}, // Origin timestamp + }, + Indices: []*migrator.Index{ + {Cols: []string{"event"}, Type: migrator.IndexType}, + {Cols: []string{"folder"}, Type: migrator.IndexType}, + {Cols: []string{"created_at"}, Type: migrator.IndexType}, + {Cols: []string{"updated_at"}, Type: migrator.IndexType}, + {Cols: []string{"origin"}, Type: migrator.IndexType}, + }, + }) + + // This table is optional -- values can be saved in blob storage + tables = append(tables, migrator.Table{ + Name: "resource_blob", // even things that failed? + Columns: []*migrator.Column{ + {Name: "path", Type: migrator.DB_NVarchar, Length: 1024, Nullable: false, IsPrimaryKey: true}, + {Name: "body", Type: migrator.DB_Blob, Nullable: false}, + {Name: "etag", Type: migrator.DB_NVarchar, Length: 64, Nullable: false}, + {Name: "size", Type: migrator.DB_BigInt, Nullable: false}, + {Name: "content_type", Type: migrator.DB_NVarchar, Length: 255, Nullable: false}, + }, + Indices: []*migrator.Index{ + {Cols: []string{"path"}, Type: migrator.UniqueIndex}, + }, + }) + + tables = append(tables, migrator.Table{ + Name: "resource_label_set", + Columns: []*migrator.Column{ + {Name: "label_set", Type: migrator.DB_NVarchar, Length: 64, Nullable: false}, + {Name: "label", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, + {Name: "value", Type: migrator.DB_Text, Nullable: false}, + }, + Indices: []*migrator.Index{ + {Cols: []string{"label_set", "label"}, Type: migrator.UniqueIndex}, + }, + }) + + // Initialize all tables + for t := range tables { + mg.AddMigration("drop table "+tables[t].Name, migrator.NewDropTableMigration(tables[t].Name)) + mg.AddMigration("create table "+tables[t].Name, migrator.NewAddTableMigration(tables[t])) + for i := range tables[t].Indices { + mg.AddMigration(fmt.Sprintf("create table %s, index: %d", tables[t].Name, i), migrator.NewAddIndexMigration(tables[t], tables[t].Indices[i])) + } + } + + return marker +} diff --git a/pkg/services/store/entity/sqlstash/sql_storage_server.go b/pkg/services/store/entity/sqlstash/sql_storage_server.go index 76024024dae..2004cb1ad92 100644 --- a/pkg/services/store/entity/sqlstash/sql_storage_server.go +++ b/pkg/services/store/entity/sqlstash/sql_storage_server.go @@ -106,6 +106,11 @@ func (s *sqlEntityServer) init() error { return errors.New("missing db") } + err := s.db.Init() + if err != nil { + return err + } + sqlDB, err := s.db.GetDB() if err != nil { return err diff --git a/pkg/services/store/resource/buf.gen.yaml b/pkg/services/store/resource/buf.gen.yaml new file mode 100644 index 00000000000..8d4748d3a2f --- /dev/null +++ b/pkg/services/store/resource/buf.gen.yaml @@ -0,0 +1,10 @@ +version: v1 +plugins: + - plugin: go + out: pkg/services/store/resource + opt: paths=source_relative + - plugin: go-grpc + out: pkg/services/store/resource + opt: + - paths=source_relative + - require_unimplemented_servers=false diff --git a/pkg/services/store/resource/buf.yaml b/pkg/services/store/resource/buf.yaml new file mode 100644 index 00000000000..1a5194568a9 --- /dev/null +++ b/pkg/services/store/resource/buf.yaml @@ -0,0 +1,7 @@ +version: v1 +breaking: + use: + - FILE +lint: + use: + - DEFAULT diff --git a/pkg/services/store/resource/resource.pb.go b/pkg/services/store/resource/resource.pb.go new file mode 100644 index 00000000000..fa3d6000eb4 --- /dev/null +++ b/pkg/services/store/resource/resource.pb.go @@ -0,0 +1,3062 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.1 +// protoc (unknown) +// source: resource.proto + +package resource + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ResourceOperation int32 + +const ( + ResourceOperation_UNKNOWN ResourceOperation = 0 + ResourceOperation_CREATED ResourceOperation = 1 + ResourceOperation_UPDATED ResourceOperation = 2 + ResourceOperation_DELETED ResourceOperation = 3 + ResourceOperation_BOOKMARK ResourceOperation = 4 +) + +// Enum value maps for ResourceOperation. +var ( + ResourceOperation_name = map[int32]string{ + 0: "UNKNOWN", + 1: "CREATED", + 2: "UPDATED", + 3: "DELETED", + 4: "BOOKMARK", + } + ResourceOperation_value = map[string]int32{ + "UNKNOWN": 0, + "CREATED": 1, + "UPDATED": 2, + "DELETED": 3, + "BOOKMARK": 4, + } +) + +func (x ResourceOperation) Enum() *ResourceOperation { + p := new(ResourceOperation) + *p = x + return p +} + +func (x ResourceOperation) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ResourceOperation) Descriptor() protoreflect.EnumDescriptor { + return file_resource_proto_enumTypes[0].Descriptor() +} + +func (ResourceOperation) Type() protoreflect.EnumType { + return &file_resource_proto_enumTypes[0] +} + +func (x ResourceOperation) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ResourceOperation.Descriptor instead. +func (ResourceOperation) EnumDescriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{0} +} + +type Sort_Order int32 + +const ( + Sort_ASC Sort_Order = 0 + Sort_DESC Sort_Order = 1 +) + +// Enum value maps for Sort_Order. +var ( + Sort_Order_name = map[int32]string{ + 0: "ASC", + 1: "DESC", + } + Sort_Order_value = map[string]int32{ + "ASC": 0, + "DESC": 1, + } +) + +func (x Sort_Order) Enum() *Sort_Order { + p := new(Sort_Order) + *p = x + return p +} + +func (x Sort_Order) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Sort_Order) Descriptor() protoreflect.EnumDescriptor { + return file_resource_proto_enumTypes[1].Descriptor() +} + +func (Sort_Order) Type() protoreflect.EnumType { + return &file_resource_proto_enumTypes[1] +} + +func (x Sort_Order) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Sort_Order.Descriptor instead. +func (Sort_Order) EnumDescriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{17, 0} +} + +type HealthCheckResponse_ServingStatus int32 + +const ( + HealthCheckResponse_UNKNOWN HealthCheckResponse_ServingStatus = 0 + HealthCheckResponse_SERVING HealthCheckResponse_ServingStatus = 1 + HealthCheckResponse_NOT_SERVING HealthCheckResponse_ServingStatus = 2 + HealthCheckResponse_SERVICE_UNKNOWN HealthCheckResponse_ServingStatus = 3 // Used only by the Watch method. +) + +// Enum value maps for HealthCheckResponse_ServingStatus. +var ( + HealthCheckResponse_ServingStatus_name = map[int32]string{ + 0: "UNKNOWN", + 1: "SERVING", + 2: "NOT_SERVING", + 3: "SERVICE_UNKNOWN", + } + HealthCheckResponse_ServingStatus_value = map[string]int32{ + "UNKNOWN": 0, + "SERVING": 1, + "NOT_SERVING": 2, + "SERVICE_UNKNOWN": 3, + } +) + +func (x HealthCheckResponse_ServingStatus) Enum() *HealthCheckResponse_ServingStatus { + p := new(HealthCheckResponse_ServingStatus) + *p = x + return p +} + +func (x HealthCheckResponse_ServingStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (HealthCheckResponse_ServingStatus) Descriptor() protoreflect.EnumDescriptor { + return file_resource_proto_enumTypes[2].Descriptor() +} + +func (HealthCheckResponse_ServingStatus) Type() protoreflect.EnumType { + return &file_resource_proto_enumTypes[2] +} + +func (x HealthCheckResponse_ServingStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use HealthCheckResponse_ServingStatus.Descriptor instead. +func (HealthCheckResponse_ServingStatus) EnumDescriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{29, 0} +} + +type ResourceIdentifier struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Namespace (tenant) + Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + // Resource Group + Group string `protobuf:"bytes,1,opt,name=group,proto3" json:"group,omitempty"` + // The resource type + Resource string `protobuf:"bytes,3,opt,name=resource,proto3" json:"resource,omitempty"` + // Resource identifier (unique within namespace+group+resource) + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"` + // The resource version + ResourceVersion int64 `protobuf:"varint,5,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` +} + +func (x *ResourceIdentifier) Reset() { + *x = ResourceIdentifier{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResourceIdentifier) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceIdentifier) ProtoMessage() {} + +func (x *ResourceIdentifier) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceIdentifier.ProtoReflect.Descriptor instead. +func (*ResourceIdentifier) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{0} +} + +func (x *ResourceIdentifier) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +func (x *ResourceIdentifier) GetGroup() string { + if x != nil { + return x.Group + } + return "" +} + +func (x *ResourceIdentifier) GetResource() string { + if x != nil { + return x.Resource + } + return "" +} + +func (x *ResourceIdentifier) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ResourceIdentifier) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +type ResourceWrapper struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The resource version + ResourceVersion int64 `protobuf:"varint,1,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // Full kubernetes json bytes (although the resource version may not be accurate) + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + // Operation + Operation ResourceOperation `protobuf:"varint,3,opt,name=operation,proto3,enum=resource.ResourceOperation" json:"operation,omitempty"` + // The resource has an attached blob + HasBlob bool `protobuf:"varint,4,opt,name=has_blob,json=hasBlob,proto3" json:"has_blob,omitempty"` +} + +func (x *ResourceWrapper) Reset() { + *x = ResourceWrapper{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResourceWrapper) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceWrapper) ProtoMessage() {} + +func (x *ResourceWrapper) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceWrapper.ProtoReflect.Descriptor instead. +func (*ResourceWrapper) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{1} +} + +func (x *ResourceWrapper) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *ResourceWrapper) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +func (x *ResourceWrapper) GetOperation() ResourceOperation { + if x != nil { + return x.Operation + } + return ResourceOperation_UNKNOWN +} + +func (x *ResourceWrapper) GetHasBlob() bool { + if x != nil { + return x.HasBlob + } + return false +} + +// The history and trash commands need access to commit messages +type ResourceMeta struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The resource version + ResourceVersion int64 `protobuf:"varint,1,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // The optional commit message + Operation ResourceOperation `protobuf:"varint,2,opt,name=operation,proto3,enum=resource.ResourceOperation" json:"operation,omitempty"` + // Size of the full resource body + Size int32 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"` + // Hash for the resource + Hash string `protobuf:"bytes,4,opt,name=hash,proto3" json:"hash,omitempty"` + // The optional commit message + Message string `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` + // The kubernetes metadata section (not the full resource) + // https://github.com/kubernetes/kubernetes/blob/v1.30.1/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go#L111 + ObjectMeta []byte `protobuf:"bytes,6,opt,name=object_meta,json=objectMeta,proto3" json:"object_meta,omitempty"` + // The resource has an attached blob + HasBlob bool `protobuf:"varint,7,opt,name=has_blob,json=hasBlob,proto3" json:"has_blob,omitempty"` +} + +func (x *ResourceMeta) Reset() { + *x = ResourceMeta{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResourceMeta) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceMeta) ProtoMessage() {} + +func (x *ResourceMeta) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceMeta.ProtoReflect.Descriptor instead. +func (*ResourceMeta) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{2} +} + +func (x *ResourceMeta) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *ResourceMeta) GetOperation() ResourceOperation { + if x != nil { + return x.Operation + } + return ResourceOperation_UNKNOWN +} + +func (x *ResourceMeta) GetSize() int32 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *ResourceMeta) GetHash() string { + if x != nil { + return x.Hash + } + return "" +} + +func (x *ResourceMeta) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *ResourceMeta) GetObjectMeta() []byte { + if x != nil { + return x.ObjectMeta + } + return nil +} + +func (x *ResourceMeta) GetHasBlob() bool { + if x != nil { + return x.HasBlob + } + return false +} + +// Basic blob metadata +type BlobInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Content Length + Size int64 `protobuf:"varint,1,opt,name=size,proto3" json:"size,omitempty"` + // MD5 digest of the body + ETag string `protobuf:"bytes,2,opt,name=ETag,proto3" json:"ETag,omitempty"` + // Content type header + ContentType string `protobuf:"bytes,3,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"` +} + +func (x *BlobInfo) Reset() { + *x = BlobInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlobInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlobInfo) ProtoMessage() {} + +func (x *BlobInfo) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlobInfo.ProtoReflect.Descriptor instead. +func (*BlobInfo) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{3} +} + +func (x *BlobInfo) GetSize() int64 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *BlobInfo) GetETag() string { + if x != nil { + return x.ETag + } + return "" +} + +func (x *BlobInfo) GetContentType() string { + if x != nil { + return x.ContentType + } + return "" +} + +// Status structure is copied from: +// https://github.com/kubernetes/apimachinery/blob/v0.30.1/pkg/apis/meta/v1/generated.proto#L979 +type StatusResult struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Status of the operation. + // One of: "Success" or "Failure". + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + // A human-readable description of the status of this operation. + // +optional + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + // 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 + Reason string `protobuf:"bytes,3,opt,name=reason,proto3" json:"reason,omitempty"` + // Suggested HTTP return code for this status, 0 if not set. + // +optional + Code int32 `protobuf:"varint,4,opt,name=code,proto3" json:"code,omitempty"` +} + +func (x *StatusResult) Reset() { + *x = StatusResult{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StatusResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StatusResult) ProtoMessage() {} + +func (x *StatusResult) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StatusResult.ProtoReflect.Descriptor instead. +func (*StatusResult) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{4} +} + +func (x *StatusResult) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *StatusResult) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *StatusResult) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *StatusResult) GetCode() int32 { + if x != nil { + return x.Code + } + return 0 +} + +// TODO? support PresignedUrls for upload? +type CreateBlob struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Content type header + ContentType string `protobuf:"bytes,1,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"` + // Raw value to write + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *CreateBlob) Reset() { + *x = CreateBlob{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateBlob) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateBlob) ProtoMessage() {} + +func (x *CreateBlob) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateBlob.ProtoReflect.Descriptor instead. +func (*CreateBlob) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{5} +} + +func (x *CreateBlob) GetContentType() string { + if x != nil { + return x.ContentType + } + return "" +} + +func (x *CreateBlob) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +type CreateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Requires group+resource to be configuired + // If name is not set, a unique name will be generated + // The resourceVersion should not be set + Key *ResourceIdentifier `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // The resource JSON. + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + // Optional commit message + Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` + // Optionally include a large binary object + Blob *CreateBlob `protobuf:"bytes,4,opt,name=blob,proto3" json:"blob,omitempty"` +} + +func (x *CreateRequest) Reset() { + *x = CreateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateRequest) ProtoMessage() {} + +func (x *CreateRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateRequest.ProtoReflect.Descriptor instead. +func (*CreateRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{6} +} + +func (x *CreateRequest) GetKey() *ResourceIdentifier { + if x != nil { + return x.Key + } + return nil +} + +func (x *CreateRequest) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +func (x *CreateRequest) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *CreateRequest) GetBlob() *CreateBlob { + if x != nil { + return x.Blob + } + return nil +} + +type CreateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Status code + Status *StatusResult `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + // The updated resource version + ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` +} + +func (x *CreateResponse) Reset() { + *x = CreateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateResponse) ProtoMessage() {} + +func (x *CreateResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateResponse.ProtoReflect.Descriptor instead. +func (*CreateResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{7} +} + +func (x *CreateResponse) GetStatus() *StatusResult { + if x != nil { + return x.Status + } + return nil +} + +func (x *CreateResponse) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +type UpdateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Full key must be set + Key *ResourceIdentifier `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // The resource JSON. + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + // Optional commit message + // +optional + Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` + // Optionally link a resource object + Blob *CreateBlob `protobuf:"bytes,4,opt,name=blob,proto3" json:"blob,omitempty"` +} + +func (x *UpdateRequest) Reset() { + *x = UpdateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateRequest) ProtoMessage() {} + +func (x *UpdateRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateRequest.ProtoReflect.Descriptor instead. +func (*UpdateRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{8} +} + +func (x *UpdateRequest) GetKey() *ResourceIdentifier { + if x != nil { + return x.Key + } + return nil +} + +func (x *UpdateRequest) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +func (x *UpdateRequest) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *UpdateRequest) GetBlob() *CreateBlob { + if x != nil { + return x.Blob + } + return nil +} + +type UpdateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Status code + Status *StatusResult `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + // The updated resource version + ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` +} + +func (x *UpdateResponse) Reset() { + *x = UpdateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateResponse) ProtoMessage() {} + +func (x *UpdateResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateResponse.ProtoReflect.Descriptor instead. +func (*UpdateResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{9} +} + +func (x *UpdateResponse) GetStatus() *StatusResult { + if x != nil { + return x.Status + } + return nil +} + +func (x *UpdateResponse) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +type DeleteRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key *ResourceIdentifier `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // Preconditions: make sure the uid matches the current saved value + // +optional + Uid string `protobuf:"bytes,2,opt,name=uid,proto3" json:"uid,omitempty"` +} + +func (x *DeleteRequest) Reset() { + *x = DeleteRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRequest) ProtoMessage() {} + +func (x *DeleteRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRequest.ProtoReflect.Descriptor instead. +func (*DeleteRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{10} +} + +func (x *DeleteRequest) GetKey() *ResourceIdentifier { + if x != nil { + return x.Key + } + return nil +} + +func (x *DeleteRequest) GetUid() string { + if x != nil { + return x.Uid + } + return "" +} + +type DeleteResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Status code + Status *StatusResult `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + // The new resource version + ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` +} + +func (x *DeleteResponse) Reset() { + *x = DeleteResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteResponse) ProtoMessage() {} + +func (x *DeleteResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteResponse.ProtoReflect.Descriptor instead. +func (*DeleteResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{11} +} + +func (x *DeleteResponse) GetStatus() *StatusResult { + if x != nil { + return x.Status + } + return nil +} + +func (x *DeleteResponse) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +type GetResourceRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key *ResourceIdentifier `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` +} + +func (x *GetResourceRequest) Reset() { + *x = GetResourceRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetResourceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetResourceRequest) ProtoMessage() {} + +func (x *GetResourceRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetResourceRequest.ProtoReflect.Descriptor instead. +func (*GetResourceRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{12} +} + +func (x *GetResourceRequest) GetKey() *ResourceIdentifier { + if x != nil { + return x.Key + } + return nil +} + +type GetResourceResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Status code + Status *StatusResult `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + // The new resource version + ResourceVersion int64 `protobuf:"varint,2,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` + // The properties + Value []byte `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` + // A Signed URL that will let you fetch the blob + // If this value starts with # you must read the bytes using the GetResourceBlob request + BlobUrl string `protobuf:"bytes,4,opt,name=blob_url,json=blobUrl,proto3" json:"blob_url,omitempty"` +} + +func (x *GetResourceResponse) Reset() { + *x = GetResourceResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetResourceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetResourceResponse) ProtoMessage() {} + +func (x *GetResourceResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetResourceResponse.ProtoReflect.Descriptor instead. +func (*GetResourceResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{13} +} + +func (x *GetResourceResponse) GetStatus() *StatusResult { + if x != nil { + return x.Status + } + return nil +} + +func (x *GetResourceResponse) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *GetResourceResponse) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +func (x *GetResourceResponse) GetBlobUrl() string { + if x != nil { + return x.BlobUrl + } + return "" +} + +type GetBlobRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key *ResourceIdentifier `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` +} + +func (x *GetBlobRequest) Reset() { + *x = GetBlobRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBlobRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBlobRequest) ProtoMessage() {} + +func (x *GetBlobRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBlobRequest.ProtoReflect.Descriptor instead. +func (*GetBlobRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{14} +} + +func (x *GetBlobRequest) GetKey() *ResourceIdentifier { + if x != nil { + return x.Key + } + return nil +} + +type GetBlobResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Status code + Status *StatusResult `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + // Headers + Info *BlobInfo `protobuf:"bytes,2,opt,name=info,proto3" json:"info,omitempty"` + // The raw object value + Value []byte `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *GetBlobResponse) Reset() { + *x = GetBlobResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetBlobResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBlobResponse) ProtoMessage() {} + +func (x *GetBlobResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBlobResponse.ProtoReflect.Descriptor instead. +func (*GetBlobResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{15} +} + +func (x *GetBlobResponse) GetStatus() *StatusResult { + if x != nil { + return x.Status + } + return nil +} + +func (x *GetBlobResponse) GetInfo() *BlobInfo { + if x != nil { + return x.Info + } + return nil +} + +func (x *GetBlobResponse) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +// The label filtering requirements: +// https://github.com/kubernetes/kubernetes/blob/v1.30.1/staging/src/k8s.io/apimachinery/pkg/labels/selector.go#L141 +type Requirement struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Operator string `protobuf:"bytes,2,opt,name=operator,proto3" json:"operator,omitempty"` // See https://github.com/kubernetes/kubernetes/blob/v1.30.1/staging/src/k8s.io/apimachinery/pkg/selection/operator.go#L21 + Values []string `protobuf:"bytes,3,rep,name=values,proto3" json:"values,omitempty"` // typically one value, but depends on the operator +} + +func (x *Requirement) Reset() { + *x = Requirement{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Requirement) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Requirement) ProtoMessage() {} + +func (x *Requirement) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Requirement.ProtoReflect.Descriptor instead. +func (*Requirement) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{16} +} + +func (x *Requirement) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *Requirement) GetOperator() string { + if x != nil { + return x.Operator + } + return "" +} + +func (x *Requirement) GetValues() []string { + if x != nil { + return x.Values + } + return nil +} + +type Sort struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Field string `protobuf:"bytes,1,opt,name=field,proto3" json:"field,omitempty"` + Order Sort_Order `protobuf:"varint,2,opt,name=order,proto3,enum=resource.Sort_Order" json:"order,omitempty"` +} + +func (x *Sort) Reset() { + *x = Sort{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Sort) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Sort) ProtoMessage() {} + +func (x *Sort) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Sort.ProtoReflect.Descriptor instead. +func (*Sort) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{17} +} + +func (x *Sort) GetField() string { + if x != nil { + return x.Field + } + return "" +} + +func (x *Sort) GetOrder() Sort_Order { + if x != nil { + return x.Order + } + return Sort_ASC +} + +type ListOptions struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Maximum number of items to return + // NOTE responses will also be limited by the response payload size + Limit int64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + // Namespace+Group+Resource+etc + Key *ResourceIdentifier `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"` + // Match label + Labels []*Requirement `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty"` + // Match fields (not yet supported) + Fields []*Requirement `protobuf:"bytes,5,rep,name=fields,proto3" json:"fields,omitempty"` + // Limit results to items in a specific folder (not a query for everything under that folder!) + Folder string `protobuf:"bytes,6,opt,name=folder,proto3" json:"folder,omitempty"` +} + +func (x *ListOptions) Reset() { + *x = ListOptions{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListOptions) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListOptions) ProtoMessage() {} + +func (x *ListOptions) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListOptions.ProtoReflect.Descriptor instead. +func (*ListOptions) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{18} +} + +func (x *ListOptions) GetLimit() int64 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListOptions) GetKey() *ResourceIdentifier { + if x != nil { + return x.Key + } + return nil +} + +func (x *ListOptions) GetLabels() []*Requirement { + if x != nil { + return x.Labels + } + return nil +} + +func (x *ListOptions) GetFields() []*Requirement { + if x != nil { + return x.Fields + } + return nil +} + +func (x *ListOptions) GetFolder() string { + if x != nil { + return x.Folder + } + return "" +} + +type ListRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Starting from the requested page (other query parameters must match!) + NextPageToken string `protobuf:"bytes,1,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + // Filtering + Options *ListOptions `protobuf:"bytes,2,opt,name=options,proto3" json:"options,omitempty"` + // Sorting instructions `field ASC/DESC` + Sort []*Sort `protobuf:"bytes,3,rep,name=sort,proto3" json:"sort,omitempty"` +} + +func (x *ListRequest) Reset() { + *x = ListRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRequest) ProtoMessage() {} + +func (x *ListRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRequest.ProtoReflect.Descriptor instead. +func (*ListRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{19} +} + +func (x *ListRequest) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +func (x *ListRequest) GetOptions() *ListOptions { + if x != nil { + return x.Options + } + return nil +} + +func (x *ListRequest) GetSort() []*Sort { + if x != nil { + return x.Sort + } + return nil +} + +type ListResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Items []*ResourceWrapper `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + // When more results exist, pass this in the next request + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + // ResourceVersion of the list response + ResourceVersion int64 `protobuf:"varint,3,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,4,opt,name=remaining_item_count,json=remainingItemCount,proto3" json:"remaining_item_count,omitempty"` // 0 won't be set either (no next page token) +} + +func (x *ListResponse) Reset() { + *x = ListResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListResponse) ProtoMessage() {} + +func (x *ListResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListResponse.ProtoReflect.Descriptor instead. +func (*ListResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{20} +} + +func (x *ListResponse) GetItems() []*ResourceWrapper { + if x != nil { + return x.Items + } + return nil +} + +func (x *ListResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +func (x *ListResponse) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +func (x *ListResponse) GetRemainingItemCount() int64 { + if x != nil { + return x.RemainingItemCount + } + return 0 +} + +type WatchRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // ResourceVersion of last changes. Empty will default to full history + Since int64 `protobuf:"varint,1,opt,name=since,proto3" json:"since,omitempty"` + // Watch specific entities + Key *ResourceIdentifier `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + // Additional options + Options *ListOptions `protobuf:"bytes,3,opt,name=options,proto3" json:"options,omitempty"` + // Return initial events + SendInitialEvents bool `protobuf:"varint,4,opt,name=send_initial_events,json=sendInitialEvents,proto3" json:"send_initial_events,omitempty"` + // When done with initial events, send a bookmark event + AllowWatchBookmarks bool `protobuf:"varint,5,opt,name=allow_watch_bookmarks,json=allowWatchBookmarks,proto3" json:"allow_watch_bookmarks,omitempty"` +} + +func (x *WatchRequest) Reset() { + *x = WatchRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WatchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchRequest) ProtoMessage() {} + +func (x *WatchRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchRequest.ProtoReflect.Descriptor instead. +func (*WatchRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{21} +} + +func (x *WatchRequest) GetSince() int64 { + if x != nil { + return x.Since + } + return 0 +} + +func (x *WatchRequest) GetKey() *ResourceIdentifier { + if x != nil { + return x.Key + } + return nil +} + +func (x *WatchRequest) GetOptions() *ListOptions { + if x != nil { + return x.Options + } + return nil +} + +func (x *WatchRequest) GetSendInitialEvents() bool { + if x != nil { + return x.SendInitialEvents + } + return false +} + +func (x *WatchRequest) GetAllowWatchBookmarks() bool { + if x != nil { + return x.AllowWatchBookmarks + } + return false +} + +type WatchResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Timestamp the event was sent + Timestamp int64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // Entity that was created, updated, or deleted + Resource *ResourceWrapper `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` + // previous version of the entity (in update+delete events) + Previous *ResourceWrapper `protobuf:"bytes,3,opt,name=previous,proto3" json:"previous,omitempty"` +} + +func (x *WatchResponse) Reset() { + *x = WatchResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WatchResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WatchResponse) ProtoMessage() {} + +func (x *WatchResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WatchResponse.ProtoReflect.Descriptor instead. +func (*WatchResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{22} +} + +func (x *WatchResponse) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *WatchResponse) GetResource() *ResourceWrapper { + if x != nil { + return x.Resource + } + return nil +} + +func (x *WatchResponse) GetPrevious() *ResourceWrapper { + if x != nil { + return x.Previous + } + return nil +} + +type HistoryRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Starting from the requested page (other query parameters must match!) + NextPageToken string `protobuf:"bytes,1,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + // Maximum number of items to return + Limit int64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + // Resource identifier + Key *ResourceIdentifier `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"` + // List the deleted values (eg, show trash) + ShowDeleted bool `protobuf:"varint,4,opt,name=show_deleted,json=showDeleted,proto3" json:"show_deleted,omitempty"` +} + +func (x *HistoryRequest) Reset() { + *x = HistoryRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HistoryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HistoryRequest) ProtoMessage() {} + +func (x *HistoryRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HistoryRequest.ProtoReflect.Descriptor instead. +func (*HistoryRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{23} +} + +func (x *HistoryRequest) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +func (x *HistoryRequest) GetLimit() int64 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *HistoryRequest) GetKey() *ResourceIdentifier { + if x != nil { + return x.Key + } + return nil +} + +func (x *HistoryRequest) GetShowDeleted() bool { + if x != nil { + return x.ShowDeleted + } + return false +} + +type HistoryResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Items []*ResourceMeta `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + // More results exist... pass this in the next request + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + // ResourceVersion of the list response + ResourceVersion int64 `protobuf:"varint,3,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` +} + +func (x *HistoryResponse) Reset() { + *x = HistoryResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HistoryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HistoryResponse) ProtoMessage() {} + +func (x *HistoryResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HistoryResponse.ProtoReflect.Descriptor instead. +func (*HistoryResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{24} +} + +func (x *HistoryResponse) GetItems() []*ResourceMeta { + if x != nil { + return x.Items + } + return nil +} + +func (x *HistoryResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +func (x *HistoryResponse) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +type OriginRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Starting from the requested page (other query parameters must match!) + NextPageToken string `protobuf:"bytes,1,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + // Maximum number of items to return + Limit int64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + // Resource identifier + Key *ResourceIdentifier `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"` + // List the deleted values (eg, show trash) + Origin string `protobuf:"bytes,4,opt,name=origin,proto3" json:"origin,omitempty"` +} + +func (x *OriginRequest) Reset() { + *x = OriginRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OriginRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OriginRequest) ProtoMessage() {} + +func (x *OriginRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OriginRequest.ProtoReflect.Descriptor instead. +func (*OriginRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{25} +} + +func (x *OriginRequest) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +func (x *OriginRequest) GetLimit() int64 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *OriginRequest) GetKey() *ResourceIdentifier { + if x != nil { + return x.Key + } + return nil +} + +func (x *OriginRequest) GetOrigin() string { + if x != nil { + return x.Origin + } + return "" +} + +type ResourceOriginInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The resource + Key *ResourceIdentifier `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // Size of the full resource body + ResourceSize int32 `protobuf:"varint,2,opt,name=resource_size,json=resourceSize,proto3" json:"resource_size,omitempty"` + // Hash for the resource + ResourceHash string `protobuf:"bytes,3,opt,name=resource_hash,json=resourceHash,proto3" json:"resource_hash,omitempty"` + // The origin name + Origin string `protobuf:"bytes,4,opt,name=origin,proto3" json:"origin,omitempty"` + // Path on the origin + Path string `protobuf:"bytes,5,opt,name=path,proto3" json:"path,omitempty"` + // Verification hash from the origin + Hash string `protobuf:"bytes,6,opt,name=hash,proto3" json:"hash,omitempty"` + // Change time from the origin + Timestamp int64 `protobuf:"varint,7,opt,name=timestamp,proto3" json:"timestamp,omitempty"` +} + +func (x *ResourceOriginInfo) Reset() { + *x = ResourceOriginInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResourceOriginInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceOriginInfo) ProtoMessage() {} + +func (x *ResourceOriginInfo) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceOriginInfo.ProtoReflect.Descriptor instead. +func (*ResourceOriginInfo) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{26} +} + +func (x *ResourceOriginInfo) GetKey() *ResourceIdentifier { + if x != nil { + return x.Key + } + return nil +} + +func (x *ResourceOriginInfo) GetResourceSize() int32 { + if x != nil { + return x.ResourceSize + } + return 0 +} + +func (x *ResourceOriginInfo) GetResourceHash() string { + if x != nil { + return x.ResourceHash + } + return "" +} + +func (x *ResourceOriginInfo) GetOrigin() string { + if x != nil { + return x.Origin + } + return "" +} + +func (x *ResourceOriginInfo) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *ResourceOriginInfo) GetHash() string { + if x != nil { + return x.Hash + } + return "" +} + +func (x *ResourceOriginInfo) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +type OriginResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Items []*ResourceOriginInfo `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + // More results exist... pass this in the next request + NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + // ResourceVersion of the list response + ResourceVersion int64 `protobuf:"varint,3,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` +} + +func (x *OriginResponse) Reset() { + *x = OriginResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OriginResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OriginResponse) ProtoMessage() {} + +func (x *OriginResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OriginResponse.ProtoReflect.Descriptor instead. +func (*OriginResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{27} +} + +func (x *OriginResponse) GetItems() []*ResourceOriginInfo { + if x != nil { + return x.Items + } + return nil +} + +func (x *OriginResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +func (x *OriginResponse) GetResourceVersion() int64 { + if x != nil { + return x.ResourceVersion + } + return 0 +} + +type HealthCheckRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` +} + +func (x *HealthCheckRequest) Reset() { + *x = HealthCheckRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HealthCheckRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthCheckRequest) ProtoMessage() {} + +func (x *HealthCheckRequest) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HealthCheckRequest.ProtoReflect.Descriptor instead. +func (*HealthCheckRequest) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{28} +} + +func (x *HealthCheckRequest) GetService() string { + if x != nil { + return x.Service + } + return "" +} + +type HealthCheckResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Status HealthCheckResponse_ServingStatus `protobuf:"varint,1,opt,name=status,proto3,enum=resource.HealthCheckResponse_ServingStatus" json:"status,omitempty"` +} + +func (x *HealthCheckResponse) Reset() { + *x = HealthCheckResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_resource_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HealthCheckResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HealthCheckResponse) ProtoMessage() {} + +func (x *HealthCheckResponse) ProtoReflect() protoreflect.Message { + mi := &file_resource_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HealthCheckResponse.ProtoReflect.Descriptor instead. +func (*HealthCheckResponse) Descriptor() ([]byte, []int) { + return file_resource_proto_rawDescGZIP(), []int{29} +} + +func (x *HealthCheckResponse) GetStatus() HealthCheckResponse_ServingStatus { + if x != nil { + return x.Status + } + return HealthCheckResponse_UNKNOWN +} + +var File_resource_proto protoreflect.FileDescriptor + +var file_resource_proto_rawDesc = []byte{ + 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0xa3, 0x01, 0x0a, 0x12, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, + 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x22, 0xa8, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x72, 0x61, + 0x70, 0x70, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 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, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x39, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x70, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x07, 0x68, 0x61, 0x73, 0x42, 0x6c, 0x6f, 0x62, 0x22, 0xf2, 0x01, 0x0a, 0x0c, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x29, 0x0a, 0x10, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6d, + 0x65, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x62, 0x6c, 0x6f, + 0x62, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x61, 0x73, 0x42, 0x6c, 0x6f, 0x62, + 0x22, 0x55, 0x0a, 0x08, 0x42, 0x6c, 0x6f, 0x62, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x45, 0x54, 0x61, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x45, 0x54, 0x61, 0x67, 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, 0x22, 0x6c, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, 0x45, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, + 0x6c, 0x6f, 0x62, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x01, 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, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x99, 0x01, 0x0a, + 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x28, + 0x0a, 0x04, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x6c, + 0x6f, 0x62, 0x52, 0x04, 0x62, 0x6c, 0x6f, 0x62, 0x22, 0x6b, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 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, 0x22, 0x99, 0x01, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, + 0x65, 0x72, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x62, 0x6c, 0x6f, 0x62, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x04, 0x62, 0x6c, 0x6f, + 0x62, 0x22, 0x6b, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 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, 0x22, 0x51, + 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2e, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, + 0x64, 0x22, 0x6b, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 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, 0x22, 0x44, + 0x0a, 0x12, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x22, 0xa1, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 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, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x19, 0x0a, + 0x08, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x62, 0x6c, 0x6f, 0x62, 0x55, 0x72, 0x6c, 0x22, 0x40, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, + 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2e, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x7f, 0x0a, 0x0f, 0x47, 0x65, + 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x26, 0x0a, + 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x53, 0x0a, 0x0b, 0x52, + 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, + 0x22, 0x64, 0x0a, 0x04, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x2a, + 0x0a, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x2e, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x1a, 0x0a, 0x05, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x53, 0x43, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, + 0x44, 0x45, 0x53, 0x43, 0x10, 0x01, 0x22, 0xc9, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2e, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x06, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x2d, 0x0a, 0x06, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, + 0x6e, 0x74, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, + 0x6c, 0x64, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, + 0x65, 0x72, 0x22, 0x8a, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, + 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x2f, 0x0a, 0x07, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x22, 0x0a, 0x04, 0x73, + 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x22, + 0xc4, 0x01, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x2f, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, + 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, + 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 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, 0x04, 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, 0xe9, 0x01, 0x0a, 0x0c, 0x57, 0x61, 0x74, 0x63, 0x68, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x2e, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, + 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2e, + 0x0a, 0x13, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x73, 0x65, 0x6e, + 0x64, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x32, + 0x0a, 0x15, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x77, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x62, 0x6f, + 0x6f, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, + 0x6c, 0x6c, 0x6f, 0x77, 0x57, 0x61, 0x74, 0x63, 0x68, 0x42, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, 0x72, + 0x6b, 0x73, 0x22, 0x9b, 0x01, 0x0a, 0x0d, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x12, 0x35, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, + 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x70, 0x72, 0x65, + 0x76, 0x69, 0x6f, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, + 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x08, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, + 0x22, 0xa1, 0x01, 0x0a, 0x0e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, + 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, + 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, + 0x74, 0x12, 0x2e, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x68, 0x6f, 0x77, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x73, 0x68, 0x6f, 0x77, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x64, 0x22, 0x92, 0x01, 0x0a, 0x0f, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, + 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, + 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, + 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x95, 0x01, 0x0a, 0x0d, 0x4f, 0x72, + 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, + 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x2e, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x66, 0x69, 0x65, 0x72, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x72, 0x69, + 0x67, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, + 0x6e, 0x22, 0xec, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x72, + 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2e, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, + 0x69, 0x65, 0x72, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x61, + 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, + 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, + 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, + 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x22, 0x97, 0x01, 0x0a, 0x0e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, + 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, + 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x2e, 0x0a, 0x12, 0x48, 0x65, + 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0xab, 0x01, 0x0a, 0x13, 0x48, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x2b, 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, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4f, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 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, 0x2a, 0x55, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, + 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x52, + 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x50, 0x44, 0x41, 0x54, + 0x45, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, + 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x4f, 0x4f, 0x4b, 0x4d, 0x41, 0x52, 0x4b, 0x10, 0x04, 0x32, + 0x8c, 0x05, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x6f, 0x72, + 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x12, 0x1c, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 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, 0x3a, 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, 0x17, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 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, 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, 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, 0x38, + 0x5a, 0x36, 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, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_resource_proto_rawDescOnce sync.Once + file_resource_proto_rawDescData = file_resource_proto_rawDesc +) + +func file_resource_proto_rawDescGZIP() []byte { + file_resource_proto_rawDescOnce.Do(func() { + file_resource_proto_rawDescData = protoimpl.X.CompressGZIP(file_resource_proto_rawDescData) + }) + return file_resource_proto_rawDescData +} + +var file_resource_proto_enumTypes = make([]protoimpl.EnumInfo, 3) +var file_resource_proto_msgTypes = make([]protoimpl.MessageInfo, 30) +var file_resource_proto_goTypes = []interface{}{ + (ResourceOperation)(0), // 0: resource.ResourceOperation + (Sort_Order)(0), // 1: resource.Sort.Order + (HealthCheckResponse_ServingStatus)(0), // 2: resource.HealthCheckResponse.ServingStatus + (*ResourceIdentifier)(nil), // 3: resource.ResourceIdentifier + (*ResourceWrapper)(nil), // 4: resource.ResourceWrapper + (*ResourceMeta)(nil), // 5: resource.ResourceMeta + (*BlobInfo)(nil), // 6: resource.BlobInfo + (*StatusResult)(nil), // 7: resource.StatusResult + (*CreateBlob)(nil), // 8: resource.CreateBlob + (*CreateRequest)(nil), // 9: resource.CreateRequest + (*CreateResponse)(nil), // 10: resource.CreateResponse + (*UpdateRequest)(nil), // 11: resource.UpdateRequest + (*UpdateResponse)(nil), // 12: resource.UpdateResponse + (*DeleteRequest)(nil), // 13: resource.DeleteRequest + (*DeleteResponse)(nil), // 14: resource.DeleteResponse + (*GetResourceRequest)(nil), // 15: resource.GetResourceRequest + (*GetResourceResponse)(nil), // 16: resource.GetResourceResponse + (*GetBlobRequest)(nil), // 17: resource.GetBlobRequest + (*GetBlobResponse)(nil), // 18: resource.GetBlobResponse + (*Requirement)(nil), // 19: resource.Requirement + (*Sort)(nil), // 20: resource.Sort + (*ListOptions)(nil), // 21: resource.ListOptions + (*ListRequest)(nil), // 22: resource.ListRequest + (*ListResponse)(nil), // 23: resource.ListResponse + (*WatchRequest)(nil), // 24: resource.WatchRequest + (*WatchResponse)(nil), // 25: resource.WatchResponse + (*HistoryRequest)(nil), // 26: resource.HistoryRequest + (*HistoryResponse)(nil), // 27: resource.HistoryResponse + (*OriginRequest)(nil), // 28: resource.OriginRequest + (*ResourceOriginInfo)(nil), // 29: resource.ResourceOriginInfo + (*OriginResponse)(nil), // 30: resource.OriginResponse + (*HealthCheckRequest)(nil), // 31: resource.HealthCheckRequest + (*HealthCheckResponse)(nil), // 32: resource.HealthCheckResponse +} +var file_resource_proto_depIdxs = []int32{ + 0, // 0: resource.ResourceWrapper.operation:type_name -> resource.ResourceOperation + 0, // 1: resource.ResourceMeta.operation:type_name -> resource.ResourceOperation + 3, // 2: resource.CreateRequest.key:type_name -> resource.ResourceIdentifier + 8, // 3: resource.CreateRequest.blob:type_name -> resource.CreateBlob + 7, // 4: resource.CreateResponse.status:type_name -> resource.StatusResult + 3, // 5: resource.UpdateRequest.key:type_name -> resource.ResourceIdentifier + 8, // 6: resource.UpdateRequest.blob:type_name -> resource.CreateBlob + 7, // 7: resource.UpdateResponse.status:type_name -> resource.StatusResult + 3, // 8: resource.DeleteRequest.key:type_name -> resource.ResourceIdentifier + 7, // 9: resource.DeleteResponse.status:type_name -> resource.StatusResult + 3, // 10: resource.GetResourceRequest.key:type_name -> resource.ResourceIdentifier + 7, // 11: resource.GetResourceResponse.status:type_name -> resource.StatusResult + 3, // 12: resource.GetBlobRequest.key:type_name -> resource.ResourceIdentifier + 7, // 13: resource.GetBlobResponse.status:type_name -> resource.StatusResult + 6, // 14: resource.GetBlobResponse.info:type_name -> resource.BlobInfo + 1, // 15: resource.Sort.order:type_name -> resource.Sort.Order + 3, // 16: resource.ListOptions.key:type_name -> resource.ResourceIdentifier + 19, // 17: resource.ListOptions.labels:type_name -> resource.Requirement + 19, // 18: resource.ListOptions.fields:type_name -> resource.Requirement + 21, // 19: resource.ListRequest.options:type_name -> resource.ListOptions + 20, // 20: resource.ListRequest.sort:type_name -> resource.Sort + 4, // 21: resource.ListResponse.items:type_name -> resource.ResourceWrapper + 3, // 22: resource.WatchRequest.key:type_name -> resource.ResourceIdentifier + 21, // 23: resource.WatchRequest.options:type_name -> resource.ListOptions + 4, // 24: resource.WatchResponse.resource:type_name -> resource.ResourceWrapper + 4, // 25: resource.WatchResponse.previous:type_name -> resource.ResourceWrapper + 3, // 26: resource.HistoryRequest.key:type_name -> resource.ResourceIdentifier + 5, // 27: resource.HistoryResponse.items:type_name -> resource.ResourceMeta + 3, // 28: resource.OriginRequest.key:type_name -> resource.ResourceIdentifier + 3, // 29: resource.ResourceOriginInfo.key:type_name -> resource.ResourceIdentifier + 29, // 30: resource.OriginResponse.items:type_name -> resource.ResourceOriginInfo + 2, // 31: resource.HealthCheckResponse.status:type_name -> resource.HealthCheckResponse.ServingStatus + 15, // 32: resource.ResourceStore.GetResource:input_type -> resource.GetResourceRequest + 9, // 33: resource.ResourceStore.Create:input_type -> resource.CreateRequest + 11, // 34: resource.ResourceStore.Update:input_type -> resource.UpdateRequest + 13, // 35: resource.ResourceStore.Delete:input_type -> resource.DeleteRequest + 22, // 36: resource.ResourceStore.List:input_type -> resource.ListRequest + 24, // 37: resource.ResourceStore.Watch:input_type -> resource.WatchRequest + 17, // 38: resource.ResourceStore.GetBlob:input_type -> resource.GetBlobRequest + 26, // 39: resource.ResourceStore.History:input_type -> resource.HistoryRequest + 28, // 40: resource.ResourceStore.Origin:input_type -> resource.OriginRequest + 31, // 41: resource.ResourceStore.IsHealthy:input_type -> resource.HealthCheckRequest + 16, // 42: resource.ResourceStore.GetResource:output_type -> resource.GetResourceResponse + 10, // 43: resource.ResourceStore.Create:output_type -> resource.CreateResponse + 12, // 44: resource.ResourceStore.Update:output_type -> resource.UpdateResponse + 14, // 45: resource.ResourceStore.Delete:output_type -> resource.DeleteResponse + 23, // 46: resource.ResourceStore.List:output_type -> resource.ListResponse + 25, // 47: resource.ResourceStore.Watch:output_type -> resource.WatchResponse + 18, // 48: resource.ResourceStore.GetBlob:output_type -> resource.GetBlobResponse + 27, // 49: resource.ResourceStore.History:output_type -> resource.HistoryResponse + 30, // 50: resource.ResourceStore.Origin:output_type -> resource.OriginResponse + 32, // 51: resource.ResourceStore.IsHealthy:output_type -> resource.HealthCheckResponse + 42, // [42:52] is the sub-list for method output_type + 32, // [32:42] is the sub-list for method input_type + 32, // [32:32] is the sub-list for extension type_name + 32, // [32:32] is the sub-list for extension extendee + 0, // [0:32] is the sub-list for field type_name +} + +func init() { file_resource_proto_init() } +func file_resource_proto_init() { + if File_resource_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_resource_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResourceIdentifier); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResourceWrapper); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResourceMeta); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlobInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StatusResult); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateBlob); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetResourceRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetResourceResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlobRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetBlobResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Requirement); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Sort); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListOptions); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WatchRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WatchResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HistoryRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HistoryResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OriginRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResourceOriginInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OriginResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HealthCheckRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_resource_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HealthCheckResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_resource_proto_rawDesc, + NumEnums: 3, + NumMessages: 30, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_resource_proto_goTypes, + DependencyIndexes: file_resource_proto_depIdxs, + EnumInfos: file_resource_proto_enumTypes, + MessageInfos: file_resource_proto_msgTypes, + }.Build() + File_resource_proto = out.File + file_resource_proto_rawDesc = nil + file_resource_proto_goTypes = nil + file_resource_proto_depIdxs = nil +} diff --git a/pkg/services/store/resource/resource.proto b/pkg/services/store/resource/resource.proto new file mode 100644 index 00000000000..75b5b81f83f --- /dev/null +++ b/pkg/services/store/resource/resource.proto @@ -0,0 +1,416 @@ +syntax = "proto3"; +package resource; + +option go_package = "github.com/grafana/grafana/pkg/services/store/resource"; + + +message ResourceIdentifier { + // 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; + // The resource version + int64 resource_version = 5; +} + +enum ResourceOperation { + UNKNOWN = 0; + CREATED = 1; + UPDATED = 2; + DELETED = 3; + BOOKMARK = 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; + + // Operation + ResourceOperation operation = 3; + + // The resource has an attached blob + bool has_blob = 4; +} + +// The history and trash commands need access to commit messages +message ResourceMeta { + // The resource version + int64 resource_version = 1; + + // The optional commit message + ResourceOperation operation = 2; + + // Size of the full resource body + int32 size = 3; + + // Hash for the resource + string hash = 4; + + // The optional commit message + string message = 5; + + // The kubernetes metadata section (not the full resource) + // https://github.com/kubernetes/kubernetes/blob/v1.30.1/staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/types.go#L111 + bytes object_meta = 6; + + // The resource has an attached blob + bool has_blob = 7; +} + +// Basic blob metadata +message BlobInfo { + // Content Length + int64 size = 1; + + // MD5 digest of the body + string ETag = 2; + + // Content type header + string content_type = 3; +} + +// Status structure is copied from: +// https://github.com/kubernetes/apimachinery/blob/v0.30.1/pkg/apis/meta/v1/generated.proto#L979 +message StatusResult { + // Status of the operation. + // One of: "Success" or "Failure". + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional + string status = 1; + // A human-readable description of the status of this operation. + // +optional + string message = 2; + // 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 = 3; + // Suggested HTTP return code for this status, 0 if not set. + // +optional + int32 code = 4; +} + +// TODO? support PresignedUrls for upload? +message CreateBlob { + // Content type header + string content_type = 1; + + // Raw value to write + bytes value = 2; +} + +// ---------------------------------- +// 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 + ResourceIdentifier key = 1; + + // The resource JSON. + bytes value = 2; + + // Optional commit message + string message = 3; + + // Optionally include a large binary object + CreateBlob blob = 4; +} + +message CreateResponse { + // Status code + StatusResult status = 1; + + // The updated resource version + int64 resource_version = 2; +} + +message UpdateRequest { + // Full key must be set + ResourceIdentifier key = 1; + + // The resource JSON. + bytes value = 2; + + // Optional commit message + // +optional + string message = 3; + + // Optionally link a resource object + CreateBlob blob = 4; +} + +message UpdateResponse { + // Status code + StatusResult status = 1; + + // The updated resource version + int64 resource_version = 2; +} + +message DeleteRequest { + ResourceIdentifier key = 1; + + // Preconditions: make sure the uid matches the current saved value + // +optional + string uid = 2; +} + +message DeleteResponse { + // Status code + StatusResult status = 1; + + // The new resource version + int64 resource_version = 2; +} + +message GetResourceRequest { + ResourceIdentifier key = 1; +} + +message GetResourceResponse { + // Status code + StatusResult status = 1; + + // The new resource version + int64 resource_version = 2; + + // The properties + bytes value = 3; + + // A Signed URL that will let you fetch the blob + // If this value starts with # you must read the bytes using the GetResourceBlob request + string blob_url = 4; +} + +message GetBlobRequest { + ResourceIdentifier key = 1; +} + +message GetBlobResponse { + // Status code + StatusResult status = 1; + + // Headers + BlobInfo info = 2; + + // The raw object value + 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 Sort { + enum Order { + ASC = 0; + DESC = 1; + } + string field = 1; + Order order = 2; +} + +message ListOptions { + // Maximum number of items to return + // NOTE responses will also be limited by the response payload size + int64 limit = 2; + + // Namespace+Group+Resource+etc + ResourceIdentifier key = 3; + + // Match label + repeated Requirement labels = 4; + + // Match fields (not yet supported) + repeated Requirement fields = 5; + + // Limit results to items in a specific folder (not a query for everything under that folder!) + string folder = 6; +} + +message ListRequest { + // Starting from the requested page (other query parameters must match!) + string next_page_token = 1; + + // Filtering + ListOptions options = 2; + + // Sorting instructions `field ASC/DESC` + repeated Sort sort = 3; +} + +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) +} + +message WatchRequest { + // ResourceVersion of last changes. Empty will default to full history + int64 since = 1; + + // Watch specific entities + ResourceIdentifier key = 2; + + // 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 WatchResponse { + // Timestamp the event was sent + int64 timestamp = 1; + + // Entity that was created, updated, or deleted + ResourceWrapper resource = 2; + + // previous version of the entity (in update+delete events) + ResourceWrapper previous = 3; +} + +message HistoryRequest { + // Starting from the requested page (other query parameters must match!) + string next_page_token = 1; + + // Maximum number of items to return + int64 limit = 2; + + // Resource identifier + ResourceIdentifier key = 3; + + // List the deleted values (eg, show trash) + bool show_deleted = 4; +} + +message HistoryResponse { + repeated ResourceMeta items = 1; + + // More results exist... pass this in the next request + string next_page_token = 2; + + // ResourceVersion of the list response + int64 resource_version = 3; +} + +message OriginRequest { + // Starting from the requested page (other query parameters must match!) + string next_page_token = 1; + + // Maximum number of items to return + int64 limit = 2; + + // Resource identifier + ResourceIdentifier key = 3; + + // List the deleted values (eg, show trash) + string origin = 4; +} + +message ResourceOriginInfo { + // The resource + ResourceIdentifier key = 1; + + // Size of the full resource body + int32 resource_size = 2; + + // Hash for the resource + string resource_hash = 3; + + // The origin name + string origin = 4; + + // Path on the origin + string path = 5; + + // Verification hash from the origin + string hash = 6; + + // Change time from the origin + int64 timestamp = 7; +} + +message OriginResponse { + repeated ResourceOriginInfo items = 1; + + // More results exist... pass this in the next request + string next_page_token = 2; + + // ResourceVersion of the list response + int64 resource_version = 3; +} + +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; +} + +// The entity store provides a basic CRUD (+watch eventually) interface for generic entities +service ResourceStore { + rpc GetResource(GetResourceRequest) returns (GetResourceResponse); + rpc Create(CreateRequest) returns (CreateResponse); + rpc Update(UpdateRequest) returns (UpdateResponse); + rpc Delete(DeleteRequest) returns (DeleteResponse); + rpc List(ListRequest) returns (ListResponse); + rpc Watch(WatchRequest) returns (stream WatchResponse); + + // Get the raw blob bytes and metadata + rpc GetBlob(GetBlobRequest) returns (GetBlobResponse); + + // Show resource history (and trash) + rpc History(HistoryRequest) returns (HistoryResponse); + + // Used for efficient provisioning + rpc Origin(OriginRequest) returns (OriginResponse); + + // Check if the service is healthy + rpc IsHealthy(HealthCheckRequest) returns (HealthCheckResponse); +} diff --git a/pkg/services/store/resource/resource_grpc.pb.go b/pkg/services/store/resource/resource_grpc.pb.go new file mode 100644 index 00000000000..9c774814f57 --- /dev/null +++ b/pkg/services/store/resource/resource_grpc.pb.go @@ -0,0 +1,490 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.4.0 +// - protoc (unknown) +// source: resource.proto + +package resource + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 + +const ( + ResourceStore_GetResource_FullMethodName = "/resource.ResourceStore/GetResource" + ResourceStore_Create_FullMethodName = "/resource.ResourceStore/Create" + ResourceStore_Update_FullMethodName = "/resource.ResourceStore/Update" + ResourceStore_Delete_FullMethodName = "/resource.ResourceStore/Delete" + ResourceStore_List_FullMethodName = "/resource.ResourceStore/List" + ResourceStore_Watch_FullMethodName = "/resource.ResourceStore/Watch" + ResourceStore_GetBlob_FullMethodName = "/resource.ResourceStore/GetBlob" + ResourceStore_History_FullMethodName = "/resource.ResourceStore/History" + ResourceStore_Origin_FullMethodName = "/resource.ResourceStore/Origin" + ResourceStore_IsHealthy_FullMethodName = "/resource.ResourceStore/IsHealthy" +) + +// ResourceStoreClient is the client API for ResourceStore service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// The entity store provides a basic CRUD (+watch eventually) interface for generic entities +type ResourceStoreClient interface { + GetResource(ctx context.Context, in *GetResourceRequest, opts ...grpc.CallOption) (*GetResourceResponse, error) + Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) + Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*UpdateResponse, error) + Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*DeleteResponse, error) + List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) + Watch(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (ResourceStore_WatchClient, error) + // Get the raw blob bytes and metadata + GetBlob(ctx context.Context, in *GetBlobRequest, opts ...grpc.CallOption) (*GetBlobResponse, error) + // Show resource history (and trash) + History(ctx context.Context, in *HistoryRequest, opts ...grpc.CallOption) (*HistoryResponse, error) + // Used for efficient provisioning + Origin(ctx context.Context, in *OriginRequest, opts ...grpc.CallOption) (*OriginResponse, error) + // Check if the service is healthy + IsHealthy(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) +} + +type resourceStoreClient struct { + cc grpc.ClientConnInterface +} + +func NewResourceStoreClient(cc grpc.ClientConnInterface) ResourceStoreClient { + return &resourceStoreClient{cc} +} + +func (c *resourceStoreClient) GetResource(ctx context.Context, in *GetResourceRequest, opts ...grpc.CallOption) (*GetResourceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetResourceResponse) + err := c.cc.Invoke(ctx, ResourceStore_GetResource_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *resourceStoreClient) Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateResponse) + err := c.cc.Invoke(ctx, ResourceStore_Create_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *resourceStoreClient) Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*UpdateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateResponse) + err := c.cc.Invoke(ctx, ResourceStore_Update_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *resourceStoreClient) Delete(ctx context.Context, in *DeleteRequest, opts ...grpc.CallOption) (*DeleteResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteResponse) + err := c.cc.Invoke(ctx, ResourceStore_Delete_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *resourceStoreClient) List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListResponse) + err := c.cc.Invoke(ctx, ResourceStore_List_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *resourceStoreClient) Watch(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (ResourceStore_WatchClient, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &ResourceStore_ServiceDesc.Streams[0], ResourceStore_Watch_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &resourceStoreWatchClient{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type ResourceStore_WatchClient interface { + Recv() (*WatchResponse, error) + grpc.ClientStream +} + +type resourceStoreWatchClient struct { + grpc.ClientStream +} + +func (x *resourceStoreWatchClient) Recv() (*WatchResponse, error) { + m := new(WatchResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *resourceStoreClient) GetBlob(ctx context.Context, in *GetBlobRequest, opts ...grpc.CallOption) (*GetBlobResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetBlobResponse) + err := c.cc.Invoke(ctx, ResourceStore_GetBlob_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *resourceStoreClient) History(ctx context.Context, in *HistoryRequest, opts ...grpc.CallOption) (*HistoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(HistoryResponse) + err := c.cc.Invoke(ctx, ResourceStore_History_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *resourceStoreClient) Origin(ctx context.Context, in *OriginRequest, opts ...grpc.CallOption) (*OriginResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(OriginResponse) + err := c.cc.Invoke(ctx, ResourceStore_Origin_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *resourceStoreClient) IsHealthy(ctx context.Context, in *HealthCheckRequest, opts ...grpc.CallOption) (*HealthCheckResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(HealthCheckResponse) + err := c.cc.Invoke(ctx, ResourceStore_IsHealthy_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ResourceStoreServer is the server API for ResourceStore service. +// All implementations should embed UnimplementedResourceStoreServer +// for forward compatibility +// +// The entity store provides a basic CRUD (+watch eventually) interface for generic entities +type ResourceStoreServer interface { + GetResource(context.Context, *GetResourceRequest) (*GetResourceResponse, error) + Create(context.Context, *CreateRequest) (*CreateResponse, error) + Update(context.Context, *UpdateRequest) (*UpdateResponse, error) + Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) + List(context.Context, *ListRequest) (*ListResponse, error) + Watch(*WatchRequest, ResourceStore_WatchServer) error + // Get the raw blob bytes and metadata + GetBlob(context.Context, *GetBlobRequest) (*GetBlobResponse, error) + // Show resource history (and trash) + History(context.Context, *HistoryRequest) (*HistoryResponse, error) + // Used for efficient provisioning + Origin(context.Context, *OriginRequest) (*OriginResponse, error) + // Check if the service is healthy + IsHealthy(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) +} + +// UnimplementedResourceStoreServer should be embedded to have forward compatible implementations. +type UnimplementedResourceStoreServer struct { +} + +func (UnimplementedResourceStoreServer) GetResource(context.Context, *GetResourceRequest) (*GetResourceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetResource not implemented") +} +func (UnimplementedResourceStoreServer) Create(context.Context, *CreateRequest) (*CreateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Create not implemented") +} +func (UnimplementedResourceStoreServer) Update(context.Context, *UpdateRequest) (*UpdateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Update not implemented") +} +func (UnimplementedResourceStoreServer) Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented") +} +func (UnimplementedResourceStoreServer) List(context.Context, *ListRequest) (*ListResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method List not implemented") +} +func (UnimplementedResourceStoreServer) Watch(*WatchRequest, ResourceStore_WatchServer) error { + return status.Errorf(codes.Unimplemented, "method Watch not implemented") +} +func (UnimplementedResourceStoreServer) GetBlob(context.Context, *GetBlobRequest) (*GetBlobResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetBlob not implemented") +} +func (UnimplementedResourceStoreServer) History(context.Context, *HistoryRequest) (*HistoryResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method History not implemented") +} +func (UnimplementedResourceStoreServer) Origin(context.Context, *OriginRequest) (*OriginResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Origin not implemented") +} +func (UnimplementedResourceStoreServer) IsHealthy(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method IsHealthy not implemented") +} + +// UnsafeResourceStoreServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ResourceStoreServer will +// result in compilation errors. +type UnsafeResourceStoreServer interface { + mustEmbedUnimplementedResourceStoreServer() +} + +func RegisterResourceStoreServer(s grpc.ServiceRegistrar, srv ResourceStoreServer) { + s.RegisterService(&ResourceStore_ServiceDesc, srv) +} + +func _ResourceStore_GetResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetResourceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceStoreServer).GetResource(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceStore_GetResource_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceStoreServer).GetResource(ctx, req.(*GetResourceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ResourceStore_Create_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceStoreServer).Create(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceStore_Create_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceStoreServer).Create(ctx, req.(*CreateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ResourceStore_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceStoreServer).Update(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceStore_Update_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceStoreServer).Update(ctx, req.(*UpdateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ResourceStore_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceStoreServer).Delete(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceStore_Delete_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceStoreServer).Delete(ctx, req.(*DeleteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ResourceStore_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceStoreServer).List(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceStore_List_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceStoreServer).List(ctx, req.(*ListRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ResourceStore_Watch_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(WatchRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(ResourceStoreServer).Watch(m, &resourceStoreWatchServer{ServerStream: stream}) +} + +type ResourceStore_WatchServer interface { + Send(*WatchResponse) error + grpc.ServerStream +} + +type resourceStoreWatchServer struct { + grpc.ServerStream +} + +func (x *resourceStoreWatchServer) Send(m *WatchResponse) error { + return x.ServerStream.SendMsg(m) +} + +func _ResourceStore_GetBlob_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetBlobRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceStoreServer).GetBlob(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceStore_GetBlob_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceStoreServer).GetBlob(ctx, req.(*GetBlobRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ResourceStore_History_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HistoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceStoreServer).History(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceStore_History_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceStoreServer).History(ctx, req.(*HistoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ResourceStore_Origin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OriginRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceStoreServer).Origin(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceStore_Origin_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceStoreServer).Origin(ctx, req.(*OriginRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ResourceStore_IsHealthy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HealthCheckRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceStoreServer).IsHealthy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceStore_IsHealthy_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceStoreServer).IsHealthy(ctx, req.(*HealthCheckRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ResourceStore_ServiceDesc is the grpc.ServiceDesc for ResourceStore service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ResourceStore_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "resource.ResourceStore", + HandlerType: (*ResourceStoreServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetResource", + Handler: _ResourceStore_GetResource_Handler, + }, + { + MethodName: "Create", + Handler: _ResourceStore_Create_Handler, + }, + { + MethodName: "Update", + Handler: _ResourceStore_Update_Handler, + }, + { + MethodName: "Delete", + Handler: _ResourceStore_Delete_Handler, + }, + { + MethodName: "List", + Handler: _ResourceStore_List_Handler, + }, + { + MethodName: "GetBlob", + Handler: _ResourceStore_GetBlob_Handler, + }, + { + MethodName: "History", + Handler: _ResourceStore_History_Handler, + }, + { + MethodName: "Origin", + Handler: _ResourceStore_Origin_Handler, + }, + { + MethodName: "IsHealthy", + Handler: _ResourceStore_IsHealthy_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Watch", + Handler: _ResourceStore_Watch_Handler, + ServerStreams: true, + }, + }, + Metadata: "resource.proto", +} diff --git a/pkg/services/store/resource/sqlstash/data/common.sql b/pkg/services/store/resource/sqlstash/data/common.sql new file mode 100644 index 00000000000..867061825d2 --- /dev/null +++ b/pkg/services/store/resource/sqlstash/data/common.sql @@ -0,0 +1,61 @@ +{{/* + This is the list of all the fields in *entity.Entity, in a way that is + suitable to be imported by other templates that need to select these fields + from either the "entity" or the "entity_history" tables. + + Example usage: + + SELECT {{ template "common_entity_select_into" . }} + FROM {{ .Ident "entity" }} AS e + +*/}} +{{ define "common_entity_select_into" }} + + e.{{ .Ident "guid" | .Into .Entity.Guid }}, + e.{{ .Ident "resource_version" | .Into .Entity.ResourceVersion }}, + + e.{{ .Ident "key" | .Into .Entity.Key }}, + + e.{{ .Ident "group" | .Into .Entity.Group }}, + e.{{ .Ident "group_version" | .Into .Entity.GroupVersion }}, + e.{{ .Ident "resource" | .Into .Entity.Resource }}, + e.{{ .Ident "namespace" | .Into .Entity.Namespace }}, + e.{{ .Ident "name" | .Into .Entity.Name }}, + + e.{{ .Ident "folder" | .Into .Entity.Folder }}, + + e.{{ .Ident "meta" | .Into .Entity.Meta }}, + e.{{ .Ident "body" | .Into .Entity.Body }}, + e.{{ .Ident "status" | .Into .Entity.Status }}, + + e.{{ .Ident "size" | .Into .Entity.Size }}, + e.{{ .Ident "etag" | .Into .Entity.ETag }}, + + e.{{ .Ident "created_at" | .Into .Entity.CreatedAt }}, + e.{{ .Ident "created_by" | .Into .Entity.CreatedBy }}, + e.{{ .Ident "updated_at" | .Into .Entity.UpdatedAt }}, + e.{{ .Ident "updated_by" | .Into .Entity.UpdatedBy }}, + + e.{{ .Ident "origin" | .Into .Entity.Origin.Source }}, + e.{{ .Ident "origin_key" | .Into .Entity.Origin.Key }}, + e.{{ .Ident "origin_ts" | .Into .Entity.Origin.Time }}, + + e.{{ .Ident "title" | .Into .Entity.Title }}, + e.{{ .Ident "slug" | .Into .Entity.Slug }}, + e.{{ .Ident "description" | .Into .Entity.Description }}, + + e.{{ .Ident "message" | .Into .Entity.Message }}, + e.{{ .Ident "labels" | .Into .Entity.Labels }}, + e.{{ .Ident "fields" | .Into .Entity.Fields }}, + e.{{ .Ident "errors" | .Into .Entity.Errors }}, + + e.{{ .Ident "action" | .Into .Entity.Action }} +{{ end }} + +{{/* Build an ORDER BY clause from a []SortBy contained in a .Sort field */}} +{{ define "common_order_by" }} + {{ $comma := listSep ", " }} + {{ range .Sort }} + {{- call $comma -}} {{ $.Ident .Field }} {{ .Direction.String }} + {{ end }} +{{ end }} diff --git a/pkg/services/store/resource/sqlstash/data/resource_get.sql b/pkg/services/store/resource/sqlstash/data/resource_get.sql new file mode 100644 index 00000000000..03e51b057a9 --- /dev/null +++ b/pkg/services/store/resource/sqlstash/data/resource_get.sql @@ -0,0 +1,37 @@ +SELECT + {{ .Ident "rv" | .Into .Resource.Version }}, + {{ .Ident "value" | .Into .Resource.Value }}, + {{ .Ident "blob" | .Into .Resource.Blob }}, + +FROM "resource" + + WHERE 1 = 1 + AND {{ .Ident "namespace" }} = {{ .Arg .Key.Namespace }} + AND {{ .Ident "group" }} = {{ .Arg .Key.Group }} + AND {{ .Ident "resource" }} = {{ .Arg .Key.Resource }} + AND {{ .Ident "name" }} = {{ .Arg .Key.Name }} + + {{/* + Resource versions work like snapshots at the kind level. Thus, a request + to retrieve a specific resource version should be interpreted as asking + for a resource as of how it existed at that point in time. This is why we + request matching entities with at most the provided resource version, and + return only the one with the highest resource version. In the case of not + specifying a resource version (i.e. resource version zero), it is + interpreted as the latest version of the given entity, thus we instead + query the "entity" table (which holds only the latest version of + non-deleted entities) and we don't need to specify anything else. The + "entity" table has a unique constraint on (namespace, group, resource, + name), so we're guaranteed to have at most one matching row. + */}} + {{ if gt .ResourceVersion 0 }} + AND {{ .Ident "rv" }} <= {{ .Arg .ResourceVersion }} + + ORDER BY {{ .Ident "rv" }} DESC + LIMIT 1 + {{ end }} + + {{ if .SelectForUpdate }} + {{ .SelectFor "UPDATE NOWAIT" }} + {{ end }} +; diff --git a/pkg/services/store/resource/sqlstash/data/resource_insert.sql b/pkg/services/store/resource/sqlstash/data/resource_insert.sql new file mode 100644 index 00000000000..5497041466e --- /dev/null +++ b/pkg/services/store/resource/sqlstash/data/resource_insert.sql @@ -0,0 +1,31 @@ +INSERT INTO "resource" + {{/* Explicitly specify fields that will be set */}} + ( + {{ .Ident "event" }}, + {{ .Ident "group" }}, + {{ .Ident "api_version" }}, + {{ .Ident "namespace" }}, + {{ .Ident "resource" }}, + {{ .Ident "name" }}, + {{ .Ident "operation" }}, + {{ .Ident "message" }}, + {{ .Ident "value" }}, + {{ .Ident "hash" }}, + {{ .Ident "blob" }}, + ) + + {{/* Provide the values */}} + VALUES ( + {{ .Arg .Event.ID }}, + {{ .Arg .Event.Group }}, + {{ .Arg .Event.ApiVersion }}, + {{ .Arg .Event.Namespace }}, + {{ .Arg .Event.Resource }}, + {{ .Arg .Event.Name }}, + {{ .Arg .Event.Operation }}, + {{ .Arg .Event.Message }}, + {{ .Arg .Event.Value }}, + {{ .Arg .Event.Hash }}, + {{ .Arg .Event.Blob }}, + ) +; diff --git a/pkg/services/store/resource/sqlstash/data/rv_get.sql b/pkg/services/store/resource/sqlstash/data/rv_get.sql new file mode 100644 index 00000000000..2a49c64bb05 --- /dev/null +++ b/pkg/services/store/resource/sqlstash/data/rv_get.sql @@ -0,0 +1,8 @@ +SELECT + {{ .Ident "rv" | .Into .ResourceVersion }} + + FROM {{ .Ident "resource_version" }} + WHERE 1 = 1 + AND {{ .Ident "group" }} = {{ .Arg .Group }} + AND {{ .Ident "resource" }} = {{ .Arg .Resource }} +; diff --git a/pkg/services/store/resource/sqlstash/data/rv_inc.sql b/pkg/services/store/resource/sqlstash/data/rv_inc.sql new file mode 100644 index 00000000000..da824f71323 --- /dev/null +++ b/pkg/services/store/resource/sqlstash/data/rv_inc.sql @@ -0,0 +1,9 @@ +UPDATE {{ .Ident "resource_version" }} + SET + {{ .Ident "rv" }} = {{ .Arg .ResourceVersion }} + 1, + + WHERE 1 = 1 + AND {{ .Ident "group" }} = {{ .Arg .Group }} + AND {{ .Ident "resource" }} = {{ .Arg .Resource }} + AND {{ .Ident "rv" }} = {{ .Arg .ResourceVersion }} +; diff --git a/pkg/services/store/resource/sqlstash/data/rv_insert.sql b/pkg/services/store/resource/sqlstash/data/rv_insert.sql new file mode 100644 index 00000000000..88d3cc62866 --- /dev/null +++ b/pkg/services/store/resource/sqlstash/data/rv_insert.sql @@ -0,0 +1,13 @@ +INSERT INTO {{ .Ident "resource_version" }} + ( + {{ .Ident "group" }}, + {{ .Ident "resource" }}, + {{ .Ident "rv" }}, + ) + + VALUES ( + {{ .Arg .Group }}, + {{ .Arg .Resource }}, + 1, + ) +; diff --git a/pkg/services/store/resource/sqlstash/data/rv_lock.sql b/pkg/services/store/resource/sqlstash/data/rv_lock.sql new file mode 100644 index 00000000000..773a9208e0e --- /dev/null +++ b/pkg/services/store/resource/sqlstash/data/rv_lock.sql @@ -0,0 +1,7 @@ +SELECT {{ .Ident "rv" | .Into .ResourceVersion }} + FROM {{ .Ident "resource_version" }} + WHERE 1 = 1 + AND {{ .Ident "group" }} = {{ .Arg .Group }} + AND {{ .Ident "resource" }} = {{ .Arg .Resource }} + {{ .SelectFor "UPDATE" }} +; diff --git a/pkg/services/store/resource/sqlstash/queries.go b/pkg/services/store/resource/sqlstash/queries.go new file mode 100644 index 00000000000..2777702ce9c --- /dev/null +++ b/pkg/services/store/resource/sqlstash/queries.go @@ -0,0 +1,222 @@ +package sqlstash + +import ( + "context" + "database/sql" + "embed" + "errors" + "fmt" + "strings" + "text/template" + + "github.com/grafana/grafana/pkg/services/store/entity/db" + "github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate" +) + +// Templates setup. +var ( + //go:embed data/*.sql + sqlTemplatesFS embed.FS + + // all templates + helpers = template.FuncMap{ + "listSep": helperListSep, + "join": helperJoin, + } + sqlTemplates = template.Must(template.New("sql").Funcs(helpers).ParseFS(sqlTemplatesFS, `data/*.sql`)) +) + +func mustTemplate(filename string) *template.Template { + if t := sqlTemplates.Lookup(filename); t != nil { + return t + } + panic(fmt.Sprintf("template file not found: %s", filename)) +} + +// Templates. +var ( + sqlResourceVersionGet = mustTemplate("rv_get.sql") + sqlResourceVersionInc = mustTemplate("rv_inc.sql") + sqlResourceVersionInsert = mustTemplate("rv_insert.sql") + sqlResourceVersionLock = mustTemplate("rv_lock.sql") + + sqlResourceInsert = mustTemplate("resource_insert.sql") + sqlResourceGet = mustTemplate("resource_get.sql") +) + +// TxOptions. +var ( + ReadCommitted = &sql.TxOptions{ + Isolation: sql.LevelReadCommitted, + } + ReadCommittedRO = &sql.TxOptions{ + Isolation: sql.LevelReadCommitted, + ReadOnly: true, + } +) + +// SQLError is an error returned by the database, which includes additionally +// debugging information about what was sent to the database. +type SQLError struct { + Err error + CallType string // either Query, QueryRow or Exec + TemplateName string + Query string + RawQuery string + ScanDest []any + + // potentially regulated information is not exported and only directly + // available for local testing and local debugging purposes, making sure it + // is never marshaled to JSON or any other serialization. + + arguments []any +} + +func (e SQLError) Unwrap() error { + return e.Err +} + +func (e SQLError) Error() string { + return fmt.Sprintf("%s: %s with %d input arguments and %d output "+ + "destination arguments: %v", e.TemplateName, e.CallType, + len(e.arguments), len(e.ScanDest), e.Err) +} + +//------------------------------------------------------------------------ +// Resource Version table support +//------------------------------------------------------------------------ + +type returnsResourceVersion struct { + ResourceVersion int64 +} + +func (r *returnsResourceVersion) Results() (*returnsResourceVersion, error) { + return r, nil +} + +type sqlResourceVersionGetRequest struct { + *sqltemplate.SQLTemplate + Group string + Resource string + *returnsResourceVersion +} + +func (r sqlResourceVersionGetRequest) Validate() error { + return nil // TODO +} + +type sqlResourceVersionLockRequest struct { + *sqltemplate.SQLTemplate + Group string + Resource string + *returnsResourceVersion +} + +func (r sqlResourceVersionLockRequest) Validate() error { + return nil // TODO +} + +type sqlResourceVersionIncRequest struct { + *sqltemplate.SQLTemplate + Group string + Resource string + ResourceVersion int64 +} + +func (r sqlResourceVersionIncRequest) Validate() error { + return nil // TODO +} + +type sqlResourceVersionInsertRequest struct { + *sqltemplate.SQLTemplate + Group string + Resource string +} + +func (r sqlResourceVersionInsertRequest) Validate() error { + return nil // TODO +} + +// resourceVersionAtomicInc atomically increases the version of a kind within a +// transaction. +func resourceVersionAtomicInc(ctx context.Context, x db.ContextExecer, d sqltemplate.Dialect, group, resource string) (newVersion int64, err error) { + // 1. Lock the kind and get the latest version + lockReq := sqlResourceVersionLockRequest{ + SQLTemplate: sqltemplate.New(d), + Group: group, + Resource: resource, + returnsResourceVersion: new(returnsResourceVersion), + } + kindv, err := queryRow(ctx, x, sqlResourceVersionLock, lockReq) + + // if there wasn't a row associated with the given kind, we create one with + // version 1 + if errors.Is(err, sql.ErrNoRows) { + // NOTE: there is a marginal chance that we race with another writer + // trying to create the same row. This is only possible when onboarding + // a new (Group, Resource) to the cell, which should be very unlikely, + // and the workaround is simply retrying. The alternative would be to + // use INSERT ... ON CONFLICT DO UPDATE ..., but that creates a + // requirement for support in Dialect only for this marginal case, and + // we would rather keep Dialect as small as possible. Another + // alternative is to simply check if the INSERT returns a DUPLICATE KEY + // error and then retry the original SELECT, but that also adds some + // complexity to the code. That would be preferrable to changing + // Dialect, though. The current alternative, just retrying, seems to be + // enough for now. + insReq := sqlResourceVersionInsertRequest{ + SQLTemplate: sqltemplate.New(d), + Group: group, + Resource: resource, + } + if _, err = exec(ctx, x, sqlResourceVersionInsert, insReq); err != nil { + return 0, fmt.Errorf("insert into kind_version: %w", err) + } + + return 1, nil + } + + if err != nil { + return 0, fmt.Errorf("lock kind: %w", err) + } + + incReq := sqlResourceVersionIncRequest{ + SQLTemplate: sqltemplate.New(d), + Group: group, + Resource: resource, + ResourceVersion: kindv.ResourceVersion, + } + if _, err = exec(ctx, x, sqlResourceVersionInc, incReq); err != nil { + return 0, fmt.Errorf("increase kind version: %w", err) + } + + return kindv.ResourceVersion + 1, nil +} + +// Template helpers. + +// helperListSep is a helper that helps writing simpler loops in SQL templates. +// Example usage: +// +// {{ $comma := listSep ", " }} +// {{ range .Values }} +// {{/* here we put "-" on each end to remove extra white space */}} +// {{- call $comma -}} +// {{ .Value }} +// {{ end }} +func helperListSep(sep string) func() string { + var addSep bool + + return func() string { + if addSep { + return sep + } + addSep = true + + return "" + } +} + +func helperJoin(sep string, elems ...string) string { + return strings.Join(elems, sep) +} diff --git a/pkg/services/store/resource/sqlstash/sql_storage_server.go b/pkg/services/store/resource/sqlstash/sql_storage_server.go new file mode 100644 index 00000000000..22a15be8959 --- /dev/null +++ b/pkg/services/store/resource/sqlstash/sql_storage_server.go @@ -0,0 +1,323 @@ +package sqlstash + +import ( + "context" + "database/sql" + "errors" + "fmt" + "strings" + "sync" + + "github.com/prometheus/client_golang/prometheus" + "go.opentelemetry.io/otel/trace" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/services/sqlstore/session" + "github.com/grafana/grafana/pkg/services/store/entity/db" + "github.com/grafana/grafana/pkg/services/store/entity/sqlstash" + "github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate" + "github.com/grafana/grafana/pkg/services/store/resource" +) + +const resoruceTable = "resource" +const resourceVersionTable = "resource_version" + +// Package-level errors. +var ( + ErrNotFound = errors.New("entity not found") + ErrOptimisticLockingFailed = errors.New("optimistic locking failed") + ErrUserNotFoundInContext = errors.New("user not found in context") + ErrNextPageTokenNotSupported = errors.New("nextPageToken not yet supported") + ErrLimitNotSupported = errors.New("limit not yet supported") + ErrNotImplementedYet = errors.New("not implemented yet") +) + +// Make sure we implement correct interfaces +var _ resource.ResourceStoreServer = &sqlResourceServer{} + +func ProvideSQLEntityServer(db db.EntityDBInterface, tracer tracing.Tracer) (SqlResourceServer, error) { + ctx, cancel := context.WithCancel(context.Background()) + + server := &sqlResourceServer{ + db: db, + log: log.New("sql-resource-server"), + ctx: ctx, + cancel: cancel, + tracer: tracer, + } + + if err := prometheus.Register(sqlstash.NewStorageMetrics()); err != nil { + server.log.Warn("error registering storage server metrics", "error", err) + } + + return server, nil +} + +type SqlResourceServer interface { + resource.ResourceStoreServer + + Init() error + Stop() +} + +type sqlResourceServer struct { + log log.Logger + db db.EntityDBInterface // needed to keep xorm engine in scope + sess *session.SessionDB + dialect migrator.Dialect + broadcaster sqlstash.Broadcaster[*resource.WatchResponse] + ctx context.Context // TODO: remove + cancel context.CancelFunc + stream chan *resource.WatchResponse + tracer trace.Tracer + validator resource.RequestValidator + + once sync.Once + initErr error + + sqlDB db.DB + sqlDialect sqltemplate.Dialect +} + +func (s *sqlResourceServer) Init() error { + s.once.Do(func() { + s.initErr = s.init() + }) + + if s.initErr != nil { + return fmt.Errorf("initialize Entity Server: %w", s.initErr) + } + + return s.initErr +} + +func (s *sqlResourceServer) init() error { + if s.sess != nil { + return nil + } + + if s.db == nil { + return errors.New("missing db") + } + + err := s.db.Init() + if err != nil { + return err + } + + sqlDB, err := s.db.GetDB() + if err != nil { + return err + } + s.sqlDB = sqlDB + + driverName := sqlDB.DriverName() + driverName = strings.TrimSuffix(driverName, "WithHooks") + switch driverName { + case db.DriverMySQL: + s.sqlDialect = sqltemplate.MySQL + case db.DriverPostgres: + s.sqlDialect = sqltemplate.PostgreSQL + case db.DriverSQLite, db.DriverSQLite3: + s.sqlDialect = sqltemplate.SQLite + default: + return fmt.Errorf("no dialect for driver %q", driverName) + } + + sess, err := s.db.GetSession() + if err != nil { + return err + } + + engine, err := s.db.GetEngine() + if err != nil { + return err + } + + s.sess = sess + s.dialect = migrator.NewDialect(engine.DriverName()) + s.validator = resource.NewSimpleValidator() + + // set up the broadcaster + s.broadcaster, err = sqlstash.NewBroadcaster(s.ctx, func(stream chan *resource.WatchResponse) error { + s.stream = stream + + // start the poller + go s.poller(stream) + + return nil + }) + if err != nil { + return err + } + + return nil +} + +func (s *sqlResourceServer) IsHealthy(ctx context.Context, r *resource.HealthCheckRequest) (*resource.HealthCheckResponse, error) { + ctxLogger := s.log.FromContext(log.WithContextualAttributes(ctx, []any{"method", "isHealthy"})) + if err := s.Init(); err != nil { + ctxLogger.Error("init error", "error", err) + return nil, err + } + + if err := s.sqlDB.PingContext(ctx); err != nil { + return nil, err + } + // TODO: check the status of the watcher implementation as well + return &resource.HealthCheckResponse{Status: resource.HealthCheckResponse_SERVING}, nil +} + +func (s *sqlResourceServer) Stop() { + s.cancel() +} + +func (s *sqlResourceServer) GetResource(ctx context.Context, req *resource.GetResourceRequest) (*resource.GetResourceResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.GetResource") + defer span.End() + + if err := s.Init(); err != nil { + return nil, err + } + + fmt.Printf("TODO, GET: %+v", req.Key) + + return nil, ErrNotImplementedYet +} + +func (s *sqlResourceServer) Create(ctx context.Context, req *resource.CreateRequest) (*resource.CreateResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.Create") + defer span.End() + + if err := s.Init(); err != nil { + return nil, err + } + + obj, err := s.validator.ValidateCreate(ctx, req) + if err != nil { + return nil, err + } + + fmt.Printf("TODO, CREATE: %v", obj.GetName()) + + return nil, ErrNotImplementedYet +} + +func (s *sqlResourceServer) Update(ctx context.Context, req *resource.UpdateRequest) (*resource.UpdateResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.Update") + defer span.End() + + if err := s.Init(); err != nil { + return nil, err + } + + old, err := s.GetResource(ctx, &resource.GetResourceRequest{ + Key: req.Key, + }) + if err != nil { + return nil, err + } + if old == nil { + return nil, fmt.Errorf("could not get the old value") + } + + obj, err := s.validator.ValidateUpdate(ctx, req, old) + if err != nil { + return nil, err + } + + fmt.Printf("TODO, UPDATE: %+v", obj.GetName()) + + return nil, ErrNotImplementedYet +} + +func (s *sqlResourceServer) Delete(ctx context.Context, req *resource.DeleteRequest) (*resource.DeleteResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.Delete") + defer span.End() + + if err := s.Init(); err != nil { + return nil, err + } + + fmt.Printf("TODO, DELETE: %+v", req.Key) + + return nil, ErrNotImplementedYet +} + +func (s *sqlResourceServer) List(ctx context.Context, req *resource.ListRequest) (*resource.ListResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.List") + defer span.End() + + if err := s.Init(); err != nil { + return nil, err + } + + var rv int64 + err := s.sqlDB.WithTx(ctx, ReadCommitted, func(ctx context.Context, tx db.Tx) error { + req := sqlResourceVersionGetRequest{ + SQLTemplate: sqltemplate.New(s.sqlDialect), + Group: req.Options.Key.Group, + Resource: req.Options.Key.Resource, + returnsResourceVersion: new(returnsResourceVersion), + } + res, err := queryRow(ctx, tx, sqlResourceVersionGet, req) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return err + } + if res != nil { + rv = res.ResourceVersion + } + return nil + }) + if err != nil { + return nil, err + } + + fmt.Printf("TODO, LIST: %+v // %d", req.Options.Key, rv) + + return nil, ErrNotImplementedYet +} + +// Get the raw blob bytes and metadata +func (s *sqlResourceServer) GetBlob(ctx context.Context, req *resource.GetBlobRequest) (*resource.GetBlobResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.List") + defer span.End() + + if err := s.Init(); err != nil { + return nil, err + } + + fmt.Printf("TODO, GET BLOB: %+v", req.Key) + + return nil, ErrNotImplementedYet +} + +// Show resource history (and trash) +func (s *sqlResourceServer) History(ctx context.Context, req *resource.HistoryRequest) (*resource.HistoryResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.History") + defer span.End() + + if err := s.Init(); err != nil { + return nil, err + } + + fmt.Printf("TODO, GET History: %+v", req.Key) + + return nil, ErrNotImplementedYet +} + +// Used for efficient provisioning +func (s *sqlResourceServer) Origin(ctx context.Context, req *resource.OriginRequest) (*resource.OriginResponse, error) { + ctx, span := s.tracer.Start(ctx, "storage_server.History") + defer span.End() + + if err := s.Init(); err != nil { + return nil, err + } + + fmt.Printf("TODO, GET History: %+v", req.Key) + + return nil, ErrNotImplementedYet +} diff --git a/pkg/services/store/resource/sqlstash/utils.go b/pkg/services/store/resource/sqlstash/utils.go new file mode 100644 index 00000000000..84768b3f06d --- /dev/null +++ b/pkg/services/store/resource/sqlstash/utils.go @@ -0,0 +1,152 @@ +package sqlstash + +import ( + "context" + "crypto/md5" + "database/sql" + "encoding/hex" + "fmt" + "text/template" + + "github.com/grafana/grafana/pkg/infra/appcontext" + "github.com/grafana/grafana/pkg/services/store" + "github.com/grafana/grafana/pkg/services/store/entity/db" + "github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate" +) + +func createETag(body []byte, meta []byte, status []byte) string { + // TODO: can we change this to something more modern like sha256? + h := md5.New() + _, _ = h.Write(meta) + _, _ = h.Write(body) + _, _ = h.Write(status) + hash := h.Sum(nil) + + return hex.EncodeToString(hash[:]) +} + +// getCurrentUser returns a string identifying the user making a request with +// the given context. +func getCurrentUser(ctx context.Context) (string, error) { + user, err := appcontext.User(ctx) + if err != nil || user == nil { + return "", fmt.Errorf("%w: %w", ErrUserNotFoundInContext, err) + } + + return store.GetUserIDString(user), nil +} + +// ptrOr returns the first non-nil pointer in the list or a new non-nil pointer. +func ptrOr[P ~*E, E any](ps ...P) P { + for _, p := range ps { + if p != nil { + return p + } + } + + return P(new(E)) +} + +// sliceOr returns the first slice that has at least one element, or a new empty +// slice. +func sliceOr[S ~[]E, E comparable](vals ...S) S { + for _, s := range vals { + if len(s) > 0 { + return s + } + } + + return S{} +} + +// mapOr returns the first map that has at least one element, or a new empty +// map. +func mapOr[M ~map[K]V, K comparable, V any](vals ...M) M { + for _, m := range vals { + if len(m) > 0 { + return m + } + } + + return M{} +} + +// queryRow uses `req` as input and output for a single-row returning query +// generated with `tmpl`, and executed in `x`. +func queryRow[T any](ctx context.Context, x db.ContextExecer, tmpl *template.Template, req sqltemplate.WithResults[T]) (T, error) { + var zero T + + if err := req.Validate(); err != nil { + return zero, fmt.Errorf("query: invalid request for template %q: %w", + tmpl.Name(), err) + } + + rawQuery, err := sqltemplate.Execute(tmpl, req) + if err != nil { + return zero, fmt.Errorf("execute template: %w", err) + } + query := sqltemplate.FormatSQL(rawQuery) + + row := x.QueryRowContext(ctx, query, req.GetArgs()...) + if err := row.Err(); err != nil { + return zero, SQLError{ + Err: err, + CallType: "QueryRow", + TemplateName: tmpl.Name(), + arguments: req.GetArgs(), + ScanDest: req.GetScanDest(), + Query: query, + RawQuery: rawQuery, + } + } + + return scanRow(row, req) +} + +type scanner interface { + Scan(dest ...any) error +} + +// scanRow is used on *sql.Row and *sql.Rows, and is factored out here not to +// improving code reuse, but rather for ease of testing. +func scanRow[T any](sc scanner, req sqltemplate.WithResults[T]) (zero T, err error) { + if err = sc.Scan(req.GetScanDest()...); err != nil { + return zero, fmt.Errorf("row scan: %w", err) + } + + res, err := req.Results() + if err != nil { + return zero, fmt.Errorf("row results: %w", err) + } + + return res, nil +} + +// exec uses `req` as input for a non-data returning query generated with +// `tmpl`, and executed in `x`. +func exec(ctx context.Context, x db.ContextExecer, tmpl *template.Template, req sqltemplate.SQLTemplateIface) (sql.Result, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("exec: invalid request for template %q: %w", + tmpl.Name(), err) + } + + rawQuery, err := sqltemplate.Execute(tmpl, req) + if err != nil { + return nil, fmt.Errorf("execute template: %w", err) + } + query := sqltemplate.FormatSQL(rawQuery) + + res, err := x.ExecContext(ctx, query, req.GetArgs()...) + if err != nil { + return nil, SQLError{ + Err: err, + CallType: "Exec", + TemplateName: tmpl.Name(), + arguments: req.GetArgs(), + Query: query, + RawQuery: rawQuery, + } + } + + return res, nil +} diff --git a/pkg/services/store/resource/sqlstash/utils_test.go b/pkg/services/store/resource/sqlstash/utils_test.go new file mode 100644 index 00000000000..70d02263623 --- /dev/null +++ b/pkg/services/store/resource/sqlstash/utils_test.go @@ -0,0 +1,525 @@ +package sqlstash + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "io" + "regexp" + "strings" + "testing" + "text/template" + + sqlmock "github.com/DATA-DOG/go-sqlmock" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/services/store/entity/db" + "github.com/grafana/grafana/pkg/services/store/entity/db/dbimpl" + "github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate" + sqltemplateMocks "github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate/mocks" + "github.com/grafana/grafana/pkg/util/testutil" +) + +// newMockDBNopSQL returns a db.DB and a sqlmock.Sqlmock that doesn't validates +// SQL. This is only meant to be used to test wrapping utilities exec, query and +// queryRow, where the actual SQL is not relevant to the unit tests, but rather +// how the possible derived error conditions handled. +func newMockDBNopSQL(t *testing.T) (db.DB, sqlmock.Sqlmock) { + t.Helper() + + db, mock, err := sqlmock.New( + sqlmock.MonitorPingsOption(true), + sqlmock.QueryMatcherOption(sqlmock.QueryMatcherFunc( + func(expectedSQL, actualSQL string) error { + return nil + }, + )), + ) + + return newUnitTestDB(t, db, mock, err) +} + +// newMockDBMatchWords returns a db.DB and a sqlmock.Sqlmock that will match SQL +// by splitting the expected SQL string into words, and then try to find all of +// them in the actual SQL, in the given order, case insensitively. Prepend a +// word with a `!` to say that word should not be found. +func newMockDBMatchWords(t *testing.T) (db.DB, sqlmock.Sqlmock) { + t.Helper() + + db, mock, err := sqlmock.New( + sqlmock.MonitorPingsOption(true), + sqlmock.QueryMatcherOption( + sqlmock.QueryMatcherFunc(func(expectedSQL, actualSQL string) error { + actualSQL = strings.ToLower(sqltemplate.FormatSQL(actualSQL)) + expectedSQL = strings.ToLower(expectedSQL) + + var offset int + for _, vv := range mockDBMatchWordsRE.FindAllStringSubmatch(expectedSQL, -1) { + v := vv[1] + + var shouldNotMatch bool + if v != "" && v[0] == '!' { + v = v[1:] + shouldNotMatch = true + } + if v == "" { + return fmt.Errorf("invalid expected word %q in %q", v, + expectedSQL) + } + + reWord, err := regexp.Compile(`\b` + regexp.QuoteMeta(v) + `\b`) + if err != nil { + return fmt.Errorf("compile word %q from expected SQL: %s", v, + expectedSQL) + } + + if shouldNotMatch { + if reWord.MatchString(actualSQL[offset:]) { + return fmt.Errorf("actual SQL fragent should not cont"+ + "ain %q but it does\n\tFragment: %s\n\tFull SQL: %s", + v, actualSQL[offset:], actualSQL) + } + } else { + loc := reWord.FindStringIndex(actualSQL[offset:]) + if len(loc) == 0 { + return fmt.Errorf("actual SQL fragment should contain "+ + "%q but it doesn't\n\tFragment: %s\n\tFull SQL: %s", + v, actualSQL[offset:], actualSQL) + } + offset = loc[1] // advance the offset + } + } + + return nil + }, + ), + ), + ) + + return newUnitTestDB(t, db, mock, err) +} + +var mockDBMatchWordsRE = regexp.MustCompile(`(?:\W|\A)(!?\w+)\b`) + +func newUnitTestDB(t *testing.T, db *sql.DB, mock sqlmock.Sqlmock, err error) (db.DB, sqlmock.Sqlmock) { + t.Helper() + + require.NoError(t, err) + + return dbimpl.NewDB(db, "sqlmock"), mock +} + +// mockResults aids in testing code paths with queries returning large number of +// values, like those returning *entity.Entity. This is because we want to +// emulate returning the same row columns and row values the same as a real +// database would do. This utility the same template SQL that is expected to be +// used to help populate all the expected fields. +// fileds +type mockResults[T any] struct { + t *testing.T + tmpl *template.Template + data sqltemplate.WithResults[T] + rows *sqlmock.Rows +} + +// newMockResults returns a new *mockResults. If you want to emulate a call +// returning zero rows, then immediately call the Row method afterward. +func newMockResults[T any](t *testing.T, mock sqlmock.Sqlmock, tmpl *template.Template, data sqltemplate.WithResults[T]) *mockResults[T] { + t.Helper() + + data.Reset() + err := tmpl.Execute(io.Discard, data) + require.NoError(t, err) + rows := mock.NewRows(data.GetColNames()) + + return &mockResults[T]{ + t: t, + tmpl: tmpl, + data: data, + rows: rows, + } +} + +// AddCurrentData uses the values contained in the `data` argument used during +// creation to populate a new expected row. It will access `data` with pointers, +// so you should replace the internal values of `data` with freshly allocated +// results to return different rows. +func (r *mockResults[T]) AddCurrentData() *mockResults[T] { + r.t.Helper() + + r.data.Reset() + err := r.tmpl.Execute(io.Discard, r.data) + require.NoError(r.t, err) + + d := r.data.GetScanDest() + dv := make([]driver.Value, len(d)) + for i, v := range d { + dv[i] = v + } + r.rows.AddRow(dv...) + + return r +} + +// Rows returns the *sqlmock.Rows object built. +func (r *mockResults[T]) Rows() *sqlmock.Rows { + return r.rows +} + +func TestCreateETag(t *testing.T) { + t.Parallel() + + v := createETag(nil, nil, nil) + require.Equal(t, "d41d8cd98f00b204e9800998ecf8427e", v) +} + +func TestGetCurrentUser(t *testing.T) { + t.Parallel() + + ctx := testutil.NewDefaultTestContext(t) + username, err := getCurrentUser(ctx) + require.NotEmpty(t, username) + require.NoError(t, err) + + ctx = ctx.WithUser(nil) + username, err = getCurrentUser(ctx) + require.Empty(t, username) + require.Error(t, err) + require.ErrorIs(t, err, ErrUserNotFoundInContext) +} + +func TestPtrOr(t *testing.T) { + t.Parallel() + + p := ptrOr[*int]() + require.NotNil(t, p) + require.Zero(t, *p) + + p = ptrOr[*int](nil, nil, nil, nil, nil, nil) + require.NotNil(t, p) + require.Zero(t, *p) + + v := 42 + v2 := 5 + p = ptrOr(nil, nil, nil, &v, nil, &v2, nil, nil) + require.NotNil(t, p) + require.Equal(t, v, *p) + + p = ptrOr(nil, nil, nil, &v) + require.NotNil(t, p) + require.Equal(t, v, *p) +} + +func TestSliceOr(t *testing.T) { + t.Parallel() + + p := sliceOr[[]int]() + require.NotNil(t, p) + require.Len(t, p, 0) + + p = sliceOr[[]int](nil, nil, nil, nil) + require.NotNil(t, p) + require.Len(t, p, 0) + + p = sliceOr([]int{}, []int{}, []int{}, []int{}) + require.NotNil(t, p) + require.Len(t, p, 0) + + v := []int{1, 2} + p = sliceOr([]int{}, nil, []int{}, v, nil, []int{}, []int{10}, nil) + require.NotNil(t, p) + require.Equal(t, v, p) + + p = sliceOr([]int{}, nil, []int{}, v) + require.NotNil(t, p) + require.Equal(t, v, p) +} + +func TestMapOr(t *testing.T) { + t.Parallel() + + p := mapOr[map[string]int]() + require.NotNil(t, p) + require.Len(t, p, 0) + + p = mapOr(nil, map[string]int(nil), nil, map[string]int{}, nil) + require.NotNil(t, p) + require.Len(t, p, 0) + + v := map[string]int{"a": 0, "b": 1} + v2 := map[string]int{"c": 2, "d": 3} + + p = mapOr(nil, map[string]int(nil), v, v2, nil, map[string]int{}, nil) + require.NotNil(t, p) + require.Equal(t, v, p) + + p = mapOr(nil, map[string]int(nil), v) + require.NotNil(t, p) + require.Equal(t, v, p) +} + +var ( + validTestTmpl = template.Must(template.New("test").Parse("nothing special")) + invalidTestTmpl = template.New("no definition should fail to exec") + errTest = errors.New("because of reasons") +) + +// expectRows is a testing helper to keep mocks in sync when adding rows to a +// mocked SQL result. This is a helper to test `query` and `queryRow`. +type expectRows[T any] struct { + *sqlmock.Rows + ExpectedResults []T + + req *sqltemplateMocks.WithResults[T] +} + +func newReturnsRow[T any](dbmock sqlmock.Sqlmock, req *sqltemplateMocks.WithResults[T]) *expectRows[T] { + return &expectRows[T]{ + Rows: dbmock.NewRows(nil), + req: req, + } +} + +// Add adds a new value that should be returned by the `query` or `queryRow` +// operation. +func (r *expectRows[T]) Add(value T, err error) *expectRows[T] { + r.req.EXPECT().GetScanDest().Return(nil).Once() + r.req.EXPECT().Results().Return(value, err).Once() + r.Rows.AddRow() + r.ExpectedResults = append(r.ExpectedResults, value) + + return r +} + +func TestQueryRow(t *testing.T) { + t.Parallel() + + t.Run("happy path", func(t *testing.T) { + t.Parallel() + + // test declarations + ctx := testutil.NewDefaultTestContext(t) + req := sqltemplateMocks.NewWithResults[int64](t) + db, dbmock := newMockDBNopSQL(t) + rows := newReturnsRow(dbmock, req) + + // setup expectations + req.EXPECT().Validate().Return(nil).Once() + req.EXPECT().GetArgs().Return(nil).Once() + rows.Add(1, nil) + dbmock.ExpectQuery("").WillReturnRows(rows.Rows) + + // execute and assert + res, err := queryRow(ctx, db, validTestTmpl, req) + require.NoError(t, err) + require.Equal(t, rows.ExpectedResults[0], res) + }) + + t.Run("invalid request", func(t *testing.T) { + t.Parallel() + + // test declarations + ctx := testutil.NewDefaultTestContext(t) + req := sqltemplateMocks.NewWithResults[int64](t) + db, _ := newMockDBNopSQL(t) + + // setup expectations + req.EXPECT().Validate().Return(errTest).Once() + + // execute and assert + res, err := queryRow(ctx, db, invalidTestTmpl, req) + require.Zero(t, res) + require.Error(t, err) + require.ErrorContains(t, err, "invalid request") + }) + + t.Run("error executing template", func(t *testing.T) { + t.Parallel() + + // test declarations + ctx := testutil.NewDefaultTestContext(t) + req := sqltemplateMocks.NewWithResults[int64](t) + db, _ := newMockDBNopSQL(t) + + // setup expectations + req.EXPECT().Validate().Return(nil).Once() + + // execute and assert + res, err := queryRow(ctx, db, invalidTestTmpl, req) + require.Zero(t, res) + require.Error(t, err) + require.ErrorContains(t, err, "execute template") + }) + + t.Run("error executing query", func(t *testing.T) { + t.Parallel() + + // test declarations + ctx := testutil.NewDefaultTestContext(t) + req := sqltemplateMocks.NewWithResults[int64](t) + db, dbmock := newMockDBNopSQL(t) + + // setup expectations + req.EXPECT().Validate().Return(nil).Once() + req.EXPECT().GetArgs().Return(nil) + req.EXPECT().GetScanDest().Return(nil).Maybe() + dbmock.ExpectQuery("").WillReturnError(errTest) + + // execute and assert + res, err := queryRow(ctx, db, validTestTmpl, req) + require.Zero(t, res) + require.Error(t, err) + require.ErrorAs(t, err, new(SQLError)) + }) +} + +// scannerFunc is an adapter for the `scanner` interface. +type scannerFunc func(dest ...any) error + +func (f scannerFunc) Scan(dest ...any) error { + return f(dest...) +} + +func TestScanRow(t *testing.T) { + t.Parallel() + + const value int64 = 1 + + t.Run("happy path", func(t *testing.T) { + t.Parallel() + + // test declarations + req := sqltemplateMocks.NewWithResults[int64](t) + sc := scannerFunc(func(dest ...any) error { + return nil + }) + + // setup expectations + req.EXPECT().GetScanDest().Return(nil).Once() + req.EXPECT().Results().Return(value, nil).Once() + + // execute and assert + res, err := scanRow(sc, req) + require.NoError(t, err) + require.Equal(t, value, res) + }) + + t.Run("scan error", func(t *testing.T) { + t.Parallel() + + // test declarations + req := sqltemplateMocks.NewWithResults[int64](t) + sc := scannerFunc(func(dest ...any) error { + return errTest + }) + + // setup expectations + req.EXPECT().GetScanDest().Return(nil).Once() + + // execute and assert + res, err := scanRow(sc, req) + require.Zero(t, res) + require.Error(t, err) + require.ErrorIs(t, err, errTest) + }) + + t.Run("results error", func(t *testing.T) { + t.Parallel() + + // test declarations + req := sqltemplateMocks.NewWithResults[int64](t) + sc := scannerFunc(func(dest ...any) error { + return nil + }) + + // setup expectations + req.EXPECT().GetScanDest().Return(nil).Once() + req.EXPECT().Results().Return(0, errTest).Once() + + // execute and assert + res, err := scanRow(sc, req) + require.Zero(t, res) + require.Error(t, err) + require.ErrorIs(t, err, errTest) + }) +} + +func TestExec(t *testing.T) { + t.Parallel() + + t.Run("happy path", func(t *testing.T) { + t.Parallel() + + // test declarations + ctx := testutil.NewDefaultTestContext(t) + req := sqltemplateMocks.NewSQLTemplateIface(t) + db, dbmock := newMockDBNopSQL(t) + + // setup expectations + req.EXPECT().Validate().Return(nil).Once() + req.EXPECT().GetArgs().Return(nil).Once() + dbmock.ExpectExec("").WillReturnResult(sqlmock.NewResult(0, 0)) + + // execute and assert + res, err := exec(ctx, db, validTestTmpl, req) + require.NoError(t, err) + require.NotNil(t, res) + }) + + t.Run("invalid request", func(t *testing.T) { + t.Parallel() + + // test declarations + ctx := testutil.NewDefaultTestContext(t) + req := sqltemplateMocks.NewSQLTemplateIface(t) + db, _ := newMockDBNopSQL(t) + + // setup expectations + req.EXPECT().Validate().Return(errTest).Once() + + // execute and assert + res, err := exec(ctx, db, invalidTestTmpl, req) + require.Nil(t, res) + require.Error(t, err) + require.ErrorContains(t, err, "invalid request") + }) + + t.Run("error executing template", func(t *testing.T) { + t.Parallel() + + // test declarations + ctx := testutil.NewDefaultTestContext(t) + req := sqltemplateMocks.NewSQLTemplateIface(t) + db, _ := newMockDBNopSQL(t) + + // setup expectations + req.EXPECT().Validate().Return(nil).Once() + + // execute and assert + res, err := exec(ctx, db, invalidTestTmpl, req) + require.Nil(t, res) + require.Error(t, err) + require.ErrorContains(t, err, "execute template") + }) + + t.Run("error executing SQL", func(t *testing.T) { + t.Parallel() + + // test declarations + ctx := testutil.NewDefaultTestContext(t) + req := sqltemplateMocks.NewSQLTemplateIface(t) + db, dbmock := newMockDBNopSQL(t) + + // setup expectations + req.EXPECT().Validate().Return(nil).Once() + req.EXPECT().GetArgs().Return(nil) + dbmock.ExpectExec("").WillReturnError(errTest) + + // execute and assert + res, err := exec(ctx, db, validTestTmpl, req) + require.Nil(t, res) + require.Error(t, err) + require.ErrorAs(t, err, new(SQLError)) + }) +} diff --git a/pkg/services/store/resource/sqlstash/watch.go b/pkg/services/store/resource/sqlstash/watch.go new file mode 100644 index 00000000000..3c25a8f7ce4 --- /dev/null +++ b/pkg/services/store/resource/sqlstash/watch.go @@ -0,0 +1,60 @@ +package sqlstash + +import ( + "time" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/store/resource" +) + +func (s *sqlResourceServer) Watch(*resource.WatchRequest, resource.ResourceStore_WatchServer) error { + return ErrNotImplementedYet +} + +func (s *sqlResourceServer) poller(stream chan *resource.WatchResponse) { + var err error + + since := int64(0) + interval := 1 * time.Second + + t := time.NewTicker(interval) + defer t.Stop() + + for { + select { + case <-s.ctx.Done(): + return + case <-t.C: + since, err = s.poll(since, stream) + if err != nil { + s.log.Error("watch error", "err", err) + } + t.Reset(interval) + } + } +} + +func (s *sqlResourceServer) poll(since int64, out chan *resource.WatchResponse) (int64, error) { + ctx, span := s.tracer.Start(s.ctx, "storage_server.poll") + defer span.End() + ctxLogger := s.log.FromContext(log.WithContextualAttributes(ctx, []any{"method", "poll"})) + + for hasmore := true; hasmore; { + err := func() error { + if false { + // TODO + out <- &resource.WatchResponse{} + } + + // TODO, copy from entity store + hasmore = false + return nil + }() + if err != nil { + ctxLogger.Error("poll error", "error", err) + return since, err + } + } + + return since, nil +} diff --git a/pkg/services/store/resource/validator.go b/pkg/services/store/resource/validator.go new file mode 100644 index 00000000000..dbd842fb177 --- /dev/null +++ b/pkg/services/store/resource/validator.go @@ -0,0 +1,167 @@ +package resource + +import ( + "context" + "encoding/json" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/grafana/grafana/pkg/infra/appcontext" + "github.com/grafana/grafana/pkg/services/apiserver/utils" + "github.com/grafana/grafana/pkg/services/auth/identity" +) + +// Verify that all required fields are set, and the user has permission to set the common metadata fields +type RequestValidator interface { + ValidateCreate(ctx context.Context, req *CreateRequest) (utils.GrafanaResourceMetaAccessor, error) + ValidateUpdate(ctx context.Context, req *UpdateRequest, current *GetResourceResponse) (utils.GrafanaResourceMetaAccessor, error) +} + +type simpleValidator struct { + folderAccess func(ctx context.Context, user identity.Requester, uid string) bool + originAccess func(ctx context.Context, user identity.Requester, origin string) bool +} + +func NewSimpleValidator() RequestValidator { + return &simpleValidator{ + // folderAccess: func(ctx context.Context, user identity.Requester, uid string) bool { + // return true // for now you can right anything to any folder + // }, + } +} + +type dummyObject struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` +} + +var _ RequestValidator = &simpleValidator{} + +func readValue(ctx context.Context, value []byte) (identity.Requester, utils.GrafanaResourceMetaAccessor, error) { + // TODO -- we just need Identity not a full user! + user, err := appcontext.User(ctx) + if err != nil { + return nil, nil, err + } + + dummy := &dummyObject{} + err = json.Unmarshal(value, dummy) + if err != nil { + return nil, nil, err + } + + obj, err := utils.MetaAccessor(dummy) + return user, obj, err +} + +// This is the validation that happens for both CREATE and UPDATE +func (v *simpleValidator) validate(ctx context.Context, user identity.Requester, obj utils.GrafanaResourceMetaAccessor) (utils.GrafanaResourceMetaAccessor, error) { + // To avoid confusion, lets not include the resource version in the saved value + // This is a little weird, but it means there won't be confusion that the saved value + // is likely the previous resource version! + if obj.GetResourceVersion() != "" { + return obj, fmt.Errorf("do not save the resource version in the value") + } + + // Make sure all common fields are populated + if obj.GetName() == "" { + return obj, fmt.Errorf("missing name") + } + if obj.GetAPIVersion() == "" { + return obj, fmt.Errorf("missing apiversion") + } + if obj.GetUID() == "" { + return obj, fmt.Errorf("the uid is not configured") + } + + // Check folder access + folder := obj.GetFolder() + if folder != "" { + if v.folderAccess == nil { + return obj, fmt.Errorf("folder access not supported") + } else if !v.folderAccess(ctx, user, folder) { + return obj, fmt.Errorf("not allowed to write resource to folder") + } + } + + // Make sure you can write values to this origin + origin, err := obj.GetOriginInfo() + if err != nil { + return nil, err + } + if origin != nil && v.originAccess != nil && !v.originAccess(ctx, user, origin.Name) { + return obj, fmt.Errorf("not allowed to write values to this origin") + } + + return obj, nil +} + +func (v *simpleValidator) ValidateCreate(ctx context.Context, req *CreateRequest) (utils.GrafanaResourceMetaAccessor, error) { + user, obj, err := readValue(ctx, req.Value) + if err != nil { + return nil, err + } + if obj.GetKind() != req.Key.Resource { + return obj, fmt.Errorf("expected resource kind") + } + if req.Key.ResourceVersion > 0 { + return obj, fmt.Errorf("create key must not include a resource version") + } + + // Make sure the created by user is accurate + //---------------------------------------- + val := obj.GetCreatedBy() + if val != "" && val != user.GetUID().String() { + return obj, fmt.Errorf("created by annotation does not match: metadata.annotations#" + utils.AnnoKeyCreatedBy) + } + + // Create can not have updated properties + //---------------------------------------- + if obj.GetUpdatedBy() != "" { + return obj, fmt.Errorf("unexpected metadata.annotations#" + utils.AnnoKeyCreatedBy) + } + ts, err := obj.GetUpdatedTimestamp() + if err != nil { + return obj, nil + } + if ts != nil { + return obj, fmt.Errorf("unexpected metadata.annotations#" + utils.AnnoKeyUpdatedTimestamp) + } + + return v.validate(ctx, user, obj) +} + +func (v *simpleValidator) ValidateUpdate(ctx context.Context, req *UpdateRequest, current *GetResourceResponse) (utils.GrafanaResourceMetaAccessor, error) { + user, obj, err := readValue(ctx, req.Value) + if err != nil { + return nil, err + } + if obj.GetKind() != req.Key.Resource { + return obj, fmt.Errorf("expected resource kind") + } + + if req.Key.ResourceVersion > 0 && req.Key.ResourceVersion != current.ResourceVersion { + return obj, fmt.Errorf("resource version does not match (optimistic locking)") + } + + _, oldobj, err := readValue(ctx, current.Value) + if err != nil { + return nil, err + } + if obj.GetCreatedBy() != oldobj.GetCreatedBy() { + return obj, fmt.Errorf(utils.AnnoKeyCreatedBy + " value has changed") + } + if obj.GetCreationTimestamp() != oldobj.GetCreationTimestamp() { + return obj, fmt.Errorf("creation time changed") + } + + // Make sure the update user is accurate + //---------------------------------------- + val := obj.GetUpdatedBy() + if val != "" && val != user.GetUID().String() { + return obj, fmt.Errorf("created by annotation does not match: metadata.annotations#" + utils.AnnoKeyUpdatedBy) + } + + return v.validate(ctx, user, obj) +}