adding package

This commit is contained in:
Ryan McKinley 2024-06-12 14:44:21 +03:00
parent f8a2a83d59
commit ad79d44db8
12 changed files with 4438 additions and 0 deletions

4
pkg/storage/README.md Normal file
View File

@ -0,0 +1,4 @@
This includes two packages
api = the protobuf and common helpers useful for both client and server
server = the server support....

View File

@ -0,0 +1,10 @@
version: v1
plugins:
- plugin: go
out: pkg/storage/api
opt: paths=source_relative
- plugin: go-grpc
out: pkg/storage/api
opt:
- paths=source_relative
- require_unimplemented_servers=false

7
pkg/storage/api/buf.yaml Normal file
View File

@ -0,0 +1,7 @@
version: v1
breaking:
use:
- FILE
lint:
use:
- DEFAULT

219
pkg/storage/api/event.go Normal file
View File

@ -0,0 +1,219 @@
package api
import (
"context"
"encoding/json"
"fmt"
"net/http"
"sync/atomic"
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"
)
type WriteEvent struct {
EventID int64
Key *ResourceKey // the request key
Requester identity.Requester
Operation ResourceOperation
PreviousRV int64 // only for Update+Delete
Value []byte
Object utils.GrafanaMetaAccessor
OldObject utils.GrafanaMetaAccessor
// Change metadata
FolderChanged bool
// The status will be populated for any error
Status *StatusResult
Error error
}
func (e *WriteEvent) BadRequest(err error, message string, a ...any) *WriteEvent {
e.Error = err
e.Status = &StatusResult{
Status: "Failure",
Message: fmt.Sprintf(message, a...),
Code: http.StatusBadRequest,
}
return e
}
// Verify that all required fields are set, and the user has permission to set the common metadata fields
type EventValidator interface {
PrepareCreate(ctx context.Context, req *CreateRequest) (*WriteEvent, error)
PrepareUpdate(ctx context.Context, req *UpdateRequest, current []byte) (*WriteEvent, error)
}
type EventValidatorOptions struct {
// Get the next EventID
NextEventID func() int64
// Check if a user has access to write folders
// When this is nil, no resources can have folders configured
FolderAccess func(ctx context.Context, user identity.Requester, uid string) bool
// When configured, this will make sure a user is allowed to save to a given origin
OriginAccess func(ctx context.Context, user identity.Requester, origin string) bool
}
type eventValidator struct {
opts EventValidatorOptions
}
func NewEventValidator(opts EventValidatorOptions) EventValidator {
if opts.NextEventID == nil {
counter := atomic.Int64{}
opts.NextEventID = func() int64 {
return counter.Add(1)
}
}
return &eventValidator{opts}
}
type dummyObject struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
}
var _ EventValidator = &eventValidator{}
func (v *eventValidator) newEvent(ctx context.Context, key *ResourceKey, value, oldValue []byte) *WriteEvent {
var err error
event := &WriteEvent{
EventID: v.opts.NextEventID(),
Key: key,
Value: value,
}
event.Requester, err = appcontext.User(ctx)
if err != nil {
return event.BadRequest(err, "unable to get user")
}
dummy := &dummyObject{}
err = json.Unmarshal(value, dummy)
if err != nil {
return event.BadRequest(err, "error reading json")
}
obj, err := utils.MetaAccessor(dummy)
if err != nil {
return event.BadRequest(err, "invalid object in json")
}
if obj.GetUID() == "" {
return event.BadRequest(nil, "the UID must be set")
}
if obj.GetGenerateName() != "" {
return event.BadRequest(nil, "can not save value with generate name")
}
if obj.GetKind() == "" {
return event.BadRequest(nil, "expecting resources with a kind in the body")
}
if obj.GetName() != key.Name {
return event.BadRequest(nil, "key name does not match the name in the body")
}
if obj.GetNamespace() != key.Namespace {
return event.BadRequest(nil, "key namespace does not match the namespace in the body")
}
folder := obj.GetFolder()
if folder != "" {
if v.opts.FolderAccess == nil {
return event.BadRequest(err, "folders are not supported")
} else if !v.opts.FolderAccess(ctx, event.Requester, folder) {
return event.BadRequest(err, "unable to add resource to folder") // 403?
}
}
origin, err := obj.GetOriginInfo()
if err != nil {
return event.BadRequest(err, "invalid origin info")
}
if origin != nil && v.opts.OriginAccess != nil {
if !v.opts.OriginAccess(ctx, event.Requester, origin.Name) {
return event.BadRequest(err, "not allowed to write resource to origin (%s)", origin.Name)
}
}
event.Object = obj
// This is an update
if oldValue != nil {
dummy := &dummyObject{}
err = json.Unmarshal(oldValue, dummy)
if err != nil {
return event.BadRequest(err, "error reading old json value")
}
old, err := utils.MetaAccessor(dummy)
if err != nil {
return event.BadRequest(err, "invalid object inside old json")
}
if key.Name != old.GetName() {
return event.BadRequest(err, "the old value has a different name (%s != %s)", key.Name, old.GetName())
}
// Can not change creation timestamps+user
if obj.GetCreatedBy() != old.GetCreatedBy() {
return event.BadRequest(err, "can not change the created by metadata (%s != %s)", obj.GetCreatedBy(), old.GetCreatedBy())
}
if obj.GetCreationTimestamp() != old.GetCreationTimestamp() {
return event.BadRequest(err, "can not change the CreationTimestamp metadata (%v != %v)", obj.GetCreationTimestamp(), old.GetCreationTimestamp())
}
oldFolder := obj.GetFolder()
if oldFolder != folder {
event.FolderChanged = true
}
event.OldObject = old
} else if folder != "" {
event.FolderChanged = true
}
return event
}
func (v *eventValidator) PrepareCreate(ctx context.Context, req *CreateRequest) (*WriteEvent, error) {
event := v.newEvent(ctx, req.Key, req.Value, nil)
event.Operation = ResourceOperation_CREATED
if event.Status != nil {
return event, nil
}
// Make sure the created by user is accurate
//----------------------------------------
val := event.Object.GetCreatedBy()
if val != "" && val != event.Requester.GetUID().String() {
return event.BadRequest(nil, "created by annotation does not match: metadata.annotations#"+utils.AnnoKeyCreatedBy), nil
}
// Create can not have updated properties
//----------------------------------------
if event.Object.GetUpdatedBy() != "" {
return event.BadRequest(nil, "unexpected metadata.annotations#"+utils.AnnoKeyCreatedBy), nil
}
ts, err := event.Object.GetUpdatedTimestamp()
if err != nil {
return event.BadRequest(nil, fmt.Sprintf("invalid timestamp: %s", err)), nil
}
if ts != nil {
return event.BadRequest(nil, "unexpected metadata.annotations#"+utils.AnnoKeyUpdatedTimestamp), nil
}
return event, nil
}
func (v *eventValidator) PrepareUpdate(ctx context.Context, req *UpdateRequest, current []byte) (*WriteEvent, error) {
event := v.newEvent(ctx, req.Key, req.Value, current)
event.Operation = ResourceOperation_UPDATED
if event.Status != nil {
return event, nil
}
// Make sure the update user is accurate
//----------------------------------------
val := event.Object.GetUpdatedBy()
if val != "" && val != event.Requester.GetUID().String() {
return event.BadRequest(nil, "created by annotation does not match: metadata.annotations#"+utils.AnnoKeyUpdatedBy), nil
}
return event, nil
}

15
pkg/storage/api/go.mod Normal file
View File

@ -0,0 +1,15 @@
module github.com/grafana/grafana/pkg/storage/api
go 1.21.10
require (
google.golang.org/grpc v1.64.0
google.golang.org/protobuf v1.34.1
)
require (
golang.org/x/net v0.26.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 // indirect
)

7
pkg/storage/api/go.sum Normal file
View File

@ -0,0 +1,7 @@
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 h1:Q2RxlXqh1cgzzUgV261vBO2jI5R/3DD1J2pM0nI4NhU=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=

View File

@ -0,0 +1,50 @@
package api
import (
"bytes"
"fmt"
)
// NamespacedPath is a path that can be used to isolate tenant data
// NOTE: this strategy does not allow quickly searching across namespace boundaries with a prefix
func (x *ResourceKey) NamespacedPath() string {
var buffer bytes.Buffer
if x.Namespace == "" {
buffer.WriteString("__cluster__")
} else {
buffer.WriteString(x.Namespace)
}
if x.Group == "" {
return buffer.String()
}
buffer.WriteString("/")
buffer.WriteString(x.Group)
if x.Resource == "" {
return buffer.String()
}
buffer.WriteString("/")
buffer.WriteString(x.Resource)
if x.Name == "" {
return buffer.String()
}
buffer.WriteString("/")
buffer.WriteString(x.Name)
if x.ResourceVersion > 0 {
buffer.WriteString("/")
buffer.WriteString(fmt.Sprintf("%.20d", x.ResourceVersion))
}
return buffer.String()
}
// Return a copy without the resource version
func (x *ResourceKey) WithoutResourceVersion() *ResourceKey {
return &ResourceKey{
Namespace: x.Namespace,
Group: x.Group,
Resource: x.Resource,
Name: x.Name,
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,415 @@
syntax = "proto3";
package api;
option go_package = "github.com/grafana/grafana/pkg/storage/api";
message ResourceKey {
// Namespace (tenant)
string namespace = 2;
// Resource Group
string group = 1;
// The resource type
string resource = 3;
// Resource identifier (unique within namespace+group+resource)
string name = 4;
// 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
ResourceKey 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
ResourceKey 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 {
ResourceKey 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 {
ResourceKey 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 {
ResourceKey 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
ResourceKey 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
ResourceKey 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
ResourceKey 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
ResourceKey key = 3;
// List the deleted values (eg, show trash)
string origin = 4;
}
message ResourceOriginInfo {
// The resource
ResourceKey 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 for any resource
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);
}

View File

@ -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 api
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 = "/api.ResourceStore/GetResource"
ResourceStore_Create_FullMethodName = "/api.ResourceStore/Create"
ResourceStore_Update_FullMethodName = "/api.ResourceStore/Update"
ResourceStore_Delete_FullMethodName = "/api.ResourceStore/Delete"
ResourceStore_List_FullMethodName = "/api.ResourceStore/List"
ResourceStore_Watch_FullMethodName = "/api.ResourceStore/Watch"
ResourceStore_GetBlob_FullMethodName = "/api.ResourceStore/GetBlob"
ResourceStore_History_FullMethodName = "/api.ResourceStore/History"
ResourceStore_Origin_FullMethodName = "/api.ResourceStore/Origin"
ResourceStore_IsHealthy_FullMethodName = "/api.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 for any resource
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 for any resource
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: "api.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",
}

View File

@ -0,0 +1,25 @@
{
"apiVersion": "playlist.grafana.app/v0alpha1",
"kind": "Playlist",
"metadata": {
"name": "fdgsv37qslr0ga",
"namespace": "default",
"annotations": {
"grafana.app/originName": "elsewhere",
"grafana.app/originPath": "path/to/item",
"grafana.app/originTimestamp": "2024-02-02T00:00:00Z"
},
"creationTimestamp": "2024-03-03T00:00:00Z",
"uid": "8tGrXJgGbFI0"
},
"spec": {
"title": "hello",
"interval": "5m",
"items": [
{
"type": "dashboard_by_uid",
"value": "vmie2cmWz"
}
]
}
}

156
pkg/storage/server/go.mod Normal file
View File

@ -0,0 +1,156 @@
module github.com/grafana/grafana/pkg/storage/server
go 1.21.10
require (
github.com/bwmarrin/snowflake v0.3.0
github.com/gorilla/mux v1.8.1
github.com/grafana/grafana-plugin-sdk-go v0.234.0
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f
github.com/prometheus/client_golang v1.19.0
github.com/stretchr/testify v1.9.0
go.opentelemetry.io/otel/trace v1.26.0
golang.org/x/mod v0.18.0
k8s.io/apimachinery v0.29.3
k8s.io/apiserver v0.29.2
k8s.io/client-go v0.29.3
k8s.io/component-base v0.29.2
k8s.io/klog/v2 v2.120.1
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340
)
require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/NYTimes/gziphandler v1.1.1 // indirect
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect
github.com/apache/arrow/go/v15 v15.0.2 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/chromedp/cdproto v0.0.0-20240426225625-909263490071 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/getkin/kin-openapi v0.124.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.4 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/cel-go v0.17.7 // indirect
github.com/google/flatbuffers v24.3.25+incompatible // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20240416155748-26353dc0451f // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-plugin v1.6.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/yaml v0.3.1 // indirect
github.com/jonboulle/clockwork v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 // indirect
github.com/klauspost/compress v1.17.8 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/magefile/mage v1.15.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattetti/filebuffer v1.0.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.53.0 // indirect
github.com/prometheus/procfs v0.14.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/spf13/cobra v1.8.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect
github.com/unknwon/com v1.0.1 // indirect
github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a // indirect
github.com/urfave/cli v1.22.15 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect
go.etcd.io/etcd/api/v3 v3.5.10 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.10 // indirect
go.etcd.io/etcd/client/v3 v3.5.10 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.51.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
go.opentelemetry.io/contrib/propagators/jaeger v1.26.0 // indirect
go.opentelemetry.io/contrib/samplers/jaegerremote v0.20.0 // indirect
go.opentelemetry.io/otel v1.26.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 // indirect
go.opentelemetry.io/otel/metric v1.26.0 // indirect
go.opentelemetry.io/otel/sdk v1.26.0 // indirect
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/crypto v0.24.0 // indirect
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/oauth2 v0.20.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 // indirect
google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.29.3 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)