Restores in app platform (#97582)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -192,7 +192,9 @@ message ReadRequest {
|
||||
ResourceKey key = 1;
|
||||
|
||||
// Optionally pick an explicit resource version
|
||||
int64 resource_version = 3;
|
||||
int64 resource_version = 2;
|
||||
// Optionally decide to return the latest RV if deleted
|
||||
bool include_deleted = 3;
|
||||
}
|
||||
|
||||
message ReadResponse {
|
||||
@@ -643,6 +645,25 @@ message ResourceTableRow {
|
||||
bytes object = 4;
|
||||
}
|
||||
|
||||
//----------------------------
|
||||
// Restore Support
|
||||
//----------------------------
|
||||
|
||||
message RestoreRequest {
|
||||
// Full key must be set
|
||||
ResourceKey key = 1;
|
||||
|
||||
// The resource version to restore
|
||||
int64 resource_version = 2;
|
||||
}
|
||||
|
||||
message RestoreResponse {
|
||||
// Error details
|
||||
ErrorResult error = 1;
|
||||
|
||||
// The updated resource version
|
||||
int64 resource_version = 2;
|
||||
}
|
||||
|
||||
//----------------------------
|
||||
// Blob Support
|
||||
@@ -730,6 +751,7 @@ service ResourceStore {
|
||||
rpc Create(CreateRequest) returns (CreateResponse);
|
||||
rpc Update(UpdateRequest) returns (UpdateResponse);
|
||||
rpc Delete(DeleteRequest) returns (DeleteResponse);
|
||||
rpc Restore(RestoreRequest) returns (RestoreResponse);
|
||||
|
||||
// The results *may* include values that should not be returned to the user
|
||||
// This will perform best-effort filtering to increase performace.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.4.0
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc (unknown)
|
||||
// source: resource.proto
|
||||
|
||||
@@ -15,16 +15,17 @@ import (
|
||||
|
||||
// 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
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
ResourceStore_Read_FullMethodName = "/resource.ResourceStore/Read"
|
||||
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_Read_FullMethodName = "/resource.ResourceStore/Read"
|
||||
ResourceStore_Create_FullMethodName = "/resource.ResourceStore/Create"
|
||||
ResourceStore_Update_FullMethodName = "/resource.ResourceStore/Update"
|
||||
ResourceStore_Delete_FullMethodName = "/resource.ResourceStore/Delete"
|
||||
ResourceStore_Restore_FullMethodName = "/resource.ResourceStore/Restore"
|
||||
ResourceStore_List_FullMethodName = "/resource.ResourceStore/List"
|
||||
ResourceStore_Watch_FullMethodName = "/resource.ResourceStore/Watch"
|
||||
)
|
||||
|
||||
// ResourceStoreClient is the client API for ResourceStore service.
|
||||
@@ -40,6 +41,7 @@ type ResourceStoreClient interface {
|
||||
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)
|
||||
Restore(ctx context.Context, in *RestoreRequest, opts ...grpc.CallOption) (*RestoreResponse, error)
|
||||
// The results *may* include values that should not be returned to the user
|
||||
// This will perform best-effort filtering to increase performace.
|
||||
// NOTE: storage.Interface is ultimatly responsible for the final filtering
|
||||
@@ -47,7 +49,7 @@ type ResourceStoreClient interface {
|
||||
// The results *may* include values that should not be returned to the user
|
||||
// This will perform best-effort filtering to increase performace.
|
||||
// NOTE: storage.Interface is ultimatly responsible for the final filtering
|
||||
Watch(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (ResourceStore_WatchClient, error)
|
||||
Watch(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[WatchEvent], error)
|
||||
}
|
||||
|
||||
type resourceStoreClient struct {
|
||||
@@ -98,6 +100,16 @@ func (c *resourceStoreClient) Delete(ctx context.Context, in *DeleteRequest, opt
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *resourceStoreClient) Restore(ctx context.Context, in *RestoreRequest, opts ...grpc.CallOption) (*RestoreResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(RestoreResponse)
|
||||
err := c.cc.Invoke(ctx, ResourceStore_Restore_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)
|
||||
@@ -108,13 +120,13 @@ func (c *resourceStoreClient) List(ctx context.Context, in *ListRequest, opts ..
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *resourceStoreClient) Watch(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (ResourceStore_WatchClient, error) {
|
||||
func (c *resourceStoreClient) Watch(ctx context.Context, in *WatchRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[WatchEvent], 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}
|
||||
x := &grpc.GenericClientStream[WatchRequest, WatchEvent]{ClientStream: stream}
|
||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -124,26 +136,12 @@ func (c *resourceStoreClient) Watch(ctx context.Context, in *WatchRequest, opts
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type ResourceStore_WatchClient interface {
|
||||
Recv() (*WatchEvent, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type resourceStoreWatchClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *resourceStoreWatchClient) Recv() (*WatchEvent, error) {
|
||||
m := new(WatchEvent)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type ResourceStore_WatchClient = grpc.ServerStreamingClient[WatchEvent]
|
||||
|
||||
// ResourceStoreServer is the server API for ResourceStore service.
|
||||
// All implementations should embed UnimplementedResourceStoreServer
|
||||
// for forward compatibility
|
||||
// for forward compatibility.
|
||||
//
|
||||
// This provides the CRUD+List+Watch support needed for a k8s apiserver
|
||||
// The semantics and behaviors of this service are constrained by kubernetes
|
||||
@@ -154,6 +152,7 @@ type ResourceStoreServer interface {
|
||||
Create(context.Context, *CreateRequest) (*CreateResponse, error)
|
||||
Update(context.Context, *UpdateRequest) (*UpdateResponse, error)
|
||||
Delete(context.Context, *DeleteRequest) (*DeleteResponse, error)
|
||||
Restore(context.Context, *RestoreRequest) (*RestoreResponse, error)
|
||||
// The results *may* include values that should not be returned to the user
|
||||
// This will perform best-effort filtering to increase performace.
|
||||
// NOTE: storage.Interface is ultimatly responsible for the final filtering
|
||||
@@ -161,12 +160,15 @@ type ResourceStoreServer interface {
|
||||
// The results *may* include values that should not be returned to the user
|
||||
// This will perform best-effort filtering to increase performace.
|
||||
// NOTE: storage.Interface is ultimatly responsible for the final filtering
|
||||
Watch(*WatchRequest, ResourceStore_WatchServer) error
|
||||
Watch(*WatchRequest, grpc.ServerStreamingServer[WatchEvent]) error
|
||||
}
|
||||
|
||||
// UnimplementedResourceStoreServer should be embedded to have forward compatible implementations.
|
||||
type UnimplementedResourceStoreServer struct {
|
||||
}
|
||||
// UnimplementedResourceStoreServer should be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedResourceStoreServer struct{}
|
||||
|
||||
func (UnimplementedResourceStoreServer) Read(context.Context, *ReadRequest) (*ReadResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Read not implemented")
|
||||
@@ -180,12 +182,16 @@ func (UnimplementedResourceStoreServer) Update(context.Context, *UpdateRequest)
|
||||
func (UnimplementedResourceStoreServer) Delete(context.Context, *DeleteRequest) (*DeleteResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented")
|
||||
}
|
||||
func (UnimplementedResourceStoreServer) Restore(context.Context, *RestoreRequest) (*RestoreResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Restore 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 {
|
||||
func (UnimplementedResourceStoreServer) Watch(*WatchRequest, grpc.ServerStreamingServer[WatchEvent]) error {
|
||||
return status.Errorf(codes.Unimplemented, "method Watch not implemented")
|
||||
}
|
||||
func (UnimplementedResourceStoreServer) testEmbeddedByValue() {}
|
||||
|
||||
// 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
|
||||
@@ -195,6 +201,13 @@ type UnsafeResourceStoreServer interface {
|
||||
}
|
||||
|
||||
func RegisterResourceStoreServer(s grpc.ServiceRegistrar, srv ResourceStoreServer) {
|
||||
// If the following call pancis, it indicates UnimplementedResourceStoreServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&ResourceStore_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
@@ -270,6 +283,24 @@ func _ResourceStore_Delete_Handler(srv interface{}, ctx context.Context, dec fun
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ResourceStore_Restore_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(RestoreRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ResourceStoreServer).Restore(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: ResourceStore_Restore_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ResourceStoreServer).Restore(ctx, req.(*RestoreRequest))
|
||||
}
|
||||
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 {
|
||||
@@ -293,21 +324,11 @@ func _ResourceStore_Watch_Handler(srv interface{}, stream grpc.ServerStream) err
|
||||
if err := stream.RecvMsg(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return srv.(ResourceStoreServer).Watch(m, &resourceStoreWatchServer{ServerStream: stream})
|
||||
return srv.(ResourceStoreServer).Watch(m, &grpc.GenericServerStream[WatchRequest, WatchEvent]{ServerStream: stream})
|
||||
}
|
||||
|
||||
type ResourceStore_WatchServer interface {
|
||||
Send(*WatchEvent) error
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type resourceStoreWatchServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *resourceStoreWatchServer) Send(m *WatchEvent) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
||||
type ResourceStore_WatchServer = grpc.ServerStreamingServer[WatchEvent]
|
||||
|
||||
// ResourceStore_ServiceDesc is the grpc.ServiceDesc for ResourceStore service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
@@ -332,6 +353,10 @@ var ResourceStore_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "Delete",
|
||||
Handler: _ResourceStore_Delete_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "Restore",
|
||||
Handler: _ResourceStore_Restore_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "List",
|
||||
Handler: _ResourceStore_List_Handler,
|
||||
@@ -420,7 +445,7 @@ func (c *resourceIndexClient) Origin(ctx context.Context, in *OriginRequest, opt
|
||||
|
||||
// ResourceIndexServer is the server API for ResourceIndex service.
|
||||
// All implementations should embed UnimplementedResourceIndexServer
|
||||
// for forward compatibility
|
||||
// for forward compatibility.
|
||||
//
|
||||
// Unlike the ResourceStore, this service can be exposed to clients directly
|
||||
// It should be implemented with efficient indexes and does not need read-after-write semantics
|
||||
@@ -434,9 +459,12 @@ type ResourceIndexServer interface {
|
||||
Origin(context.Context, *OriginRequest) (*OriginResponse, error)
|
||||
}
|
||||
|
||||
// UnimplementedResourceIndexServer should be embedded to have forward compatible implementations.
|
||||
type UnimplementedResourceIndexServer struct {
|
||||
}
|
||||
// UnimplementedResourceIndexServer should be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedResourceIndexServer struct{}
|
||||
|
||||
func (UnimplementedResourceIndexServer) Search(context.Context, *ResourceSearchRequest) (*ResourceSearchResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Search not implemented")
|
||||
@@ -450,6 +478,7 @@ func (UnimplementedResourceIndexServer) History(context.Context, *HistoryRequest
|
||||
func (UnimplementedResourceIndexServer) Origin(context.Context, *OriginRequest) (*OriginResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Origin not implemented")
|
||||
}
|
||||
func (UnimplementedResourceIndexServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeResourceIndexServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to ResourceIndexServer will
|
||||
@@ -459,6 +488,13 @@ type UnsafeResourceIndexServer interface {
|
||||
}
|
||||
|
||||
func RegisterResourceIndexServer(s grpc.ServiceRegistrar, srv ResourceIndexServer) {
|
||||
// If the following call pancis, it indicates UnimplementedResourceIndexServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&ResourceIndex_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
@@ -608,7 +644,7 @@ func (c *blobStoreClient) GetBlob(ctx context.Context, in *GetBlobRequest, opts
|
||||
|
||||
// BlobStoreServer is the server API for BlobStore service.
|
||||
// All implementations should embed UnimplementedBlobStoreServer
|
||||
// for forward compatibility
|
||||
// for forward compatibility.
|
||||
type BlobStoreServer interface {
|
||||
// Upload a blob that will be saved in a resource
|
||||
PutBlob(context.Context, *PutBlobRequest) (*PutBlobResponse, error)
|
||||
@@ -617,9 +653,12 @@ type BlobStoreServer interface {
|
||||
GetBlob(context.Context, *GetBlobRequest) (*GetBlobResponse, error)
|
||||
}
|
||||
|
||||
// UnimplementedBlobStoreServer should be embedded to have forward compatible implementations.
|
||||
type UnimplementedBlobStoreServer struct {
|
||||
}
|
||||
// UnimplementedBlobStoreServer should be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedBlobStoreServer struct{}
|
||||
|
||||
func (UnimplementedBlobStoreServer) PutBlob(context.Context, *PutBlobRequest) (*PutBlobResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method PutBlob not implemented")
|
||||
@@ -627,6 +666,7 @@ func (UnimplementedBlobStoreServer) PutBlob(context.Context, *PutBlobRequest) (*
|
||||
func (UnimplementedBlobStoreServer) GetBlob(context.Context, *GetBlobRequest) (*GetBlobResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetBlob not implemented")
|
||||
}
|
||||
func (UnimplementedBlobStoreServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeBlobStoreServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to BlobStoreServer will
|
||||
@@ -636,6 +676,13 @@ type UnsafeBlobStoreServer interface {
|
||||
}
|
||||
|
||||
func RegisterBlobStoreServer(s grpc.ServiceRegistrar, srv BlobStoreServer) {
|
||||
// If the following call pancis, it indicates UnimplementedBlobStoreServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&BlobStore_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
@@ -730,7 +777,7 @@ func (c *diagnosticsClient) IsHealthy(ctx context.Context, in *HealthCheckReques
|
||||
|
||||
// DiagnosticsServer is the server API for Diagnostics service.
|
||||
// All implementations should embed UnimplementedDiagnosticsServer
|
||||
// for forward compatibility
|
||||
// for forward compatibility.
|
||||
//
|
||||
// Clients can use this service directly
|
||||
// NOTE: This is read only, and no read afer write guarantees
|
||||
@@ -739,13 +786,17 @@ type DiagnosticsServer interface {
|
||||
IsHealthy(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error)
|
||||
}
|
||||
|
||||
// UnimplementedDiagnosticsServer should be embedded to have forward compatible implementations.
|
||||
type UnimplementedDiagnosticsServer struct {
|
||||
}
|
||||
// UnimplementedDiagnosticsServer should be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedDiagnosticsServer struct{}
|
||||
|
||||
func (UnimplementedDiagnosticsServer) IsHealthy(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method IsHealthy not implemented")
|
||||
}
|
||||
func (UnimplementedDiagnosticsServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeDiagnosticsServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to DiagnosticsServer will
|
||||
@@ -755,6 +806,13 @@ type UnsafeDiagnosticsServer interface {
|
||||
}
|
||||
|
||||
func RegisterDiagnosticsServer(s grpc.ServiceRegistrar, srv DiagnosticsServer) {
|
||||
// If the following call pancis, it indicates UnimplementedDiagnosticsServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&Diagnostics_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,14 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/grafana/authlib/authz"
|
||||
"github.com/grafana/authlib/claims"
|
||||
@@ -378,7 +380,6 @@ func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *Resour
|
||||
if oldValue == nil {
|
||||
event.Type = WatchEvent_ADDED
|
||||
} else {
|
||||
event.Type = WatchEvent_MODIFIED
|
||||
check.Verb = "update"
|
||||
|
||||
temp := &unstructured.Unstructured{}
|
||||
@@ -390,6 +391,13 @@ func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *Resour
|
||||
if err != nil {
|
||||
return nil, AsErrorResult(err)
|
||||
}
|
||||
|
||||
// restores will restore with a different k8s uid
|
||||
if event.ObjectOld.GetUID() != obj.GetUID() {
|
||||
event.Type = WatchEvent_ADDED
|
||||
} else {
|
||||
event.Type = WatchEvent_MODIFIED
|
||||
}
|
||||
}
|
||||
|
||||
if key.Namespace != obj.GetNamespace() {
|
||||
@@ -749,6 +757,125 @@ func (s *server) List(ctx context.Context, req *ListRequest) (*ListResponse, err
|
||||
return rsp, err
|
||||
}
|
||||
|
||||
func (s *server) Restore(ctx context.Context, req *RestoreRequest) (*RestoreResponse, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "storage_server.List")
|
||||
defer span.End()
|
||||
|
||||
// check that the user has access
|
||||
user, ok := claims.From(ctx)
|
||||
if !ok || user == nil {
|
||||
return &RestoreResponse{
|
||||
Error: &ErrorResult{
|
||||
Message: "no user found in context",
|
||||
Code: http.StatusUnauthorized,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
if err := s.Init(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
checker, err := s.access.Compile(ctx, user, authz.ListRequest{
|
||||
Group: req.Key.Group,
|
||||
Resource: req.Key.Resource,
|
||||
Namespace: req.Key.Namespace,
|
||||
})
|
||||
if err != nil {
|
||||
return &RestoreResponse{Error: AsErrorResult(err)}, nil
|
||||
}
|
||||
if checker == nil {
|
||||
return &RestoreResponse{Error: &ErrorResult{
|
||||
Code: http.StatusForbidden,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
// get the asked for resource version to restore
|
||||
readRsp, err := s.Read(ctx, &ReadRequest{
|
||||
Key: req.Key,
|
||||
ResourceVersion: req.ResourceVersion,
|
||||
IncludeDeleted: true,
|
||||
})
|
||||
if err != nil || readRsp == nil || readRsp.Error != nil {
|
||||
return &RestoreResponse{
|
||||
Error: &ErrorResult{
|
||||
Code: http.StatusNotFound,
|
||||
Message: fmt.Sprintf("could not find old resource: %s", readRsp.Error.Message),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// generate a new k8s UID when restoring. The name will remain the same
|
||||
// (for dashboards, this will be the dashboard uid), but since controllers
|
||||
// will see this as a create event, we do not want the same k8s UID, or
|
||||
// there may be unintended behavior
|
||||
newUid := types.UID(uuid.NewString())
|
||||
tmp := &unstructured.Unstructured{}
|
||||
err = tmp.UnmarshalJSON(readRsp.Value)
|
||||
if err != nil {
|
||||
return &RestoreResponse{
|
||||
Error: &ErrorResult{
|
||||
Code: http.StatusNotFound,
|
||||
Message: fmt.Sprintf("could not unmarhsal: %s", err.Error()),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
obj, err := utils.MetaAccessor(tmp)
|
||||
if err != nil {
|
||||
return &RestoreResponse{
|
||||
Error: &ErrorResult{
|
||||
Code: http.StatusNotFound,
|
||||
Message: fmt.Sprintf("could not get object: %s", err.Error()),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
obj.SetUID(newUid)
|
||||
|
||||
rtObj, ok := obj.GetRuntimeObject()
|
||||
if !ok {
|
||||
return &RestoreResponse{
|
||||
Error: &ErrorResult{
|
||||
Code: http.StatusNotFound,
|
||||
Message: "could not get runtime object",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
newObj, err := json.Marshal(rtObj)
|
||||
if err != nil {
|
||||
return &RestoreResponse{
|
||||
Error: &ErrorResult{
|
||||
Code: http.StatusNotFound,
|
||||
Message: fmt.Sprintf("could not marshal object: %s", err.Error()),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// finally, send to the backend to create & update the history of the restored object
|
||||
event, errRes := s.newEvent(ctx, user, req.Key, newObj, readRsp.Value)
|
||||
if errRes != nil {
|
||||
return &RestoreResponse{
|
||||
Error: &ErrorResult{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: fmt.Sprintf("could not create restore resource event: %s", errRes.Message),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
rv, err := s.backend.WriteEvent(ctx, *event)
|
||||
if err != nil {
|
||||
return &RestoreResponse{
|
||||
Error: &ErrorResult{
|
||||
Code: http.StatusInternalServerError,
|
||||
Message: fmt.Sprintf("could not restore resource: %s", err.Error()),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &RestoreResponse{
|
||||
Error: nil,
|
||||
ResourceVersion: rv,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *server) initWatcher() error {
|
||||
var err error
|
||||
s.broadcaster, err = NewBroadcaster(s.ctx, func(out chan<- *WrittenEvent) error {
|
||||
|
||||
@@ -220,4 +220,79 @@ func TestSimpleServer(t *testing.T) {
|
||||
ResourceVersion: created.ResourceVersion})
|
||||
require.ErrorIs(t, err, ErrOptimisticLockingFailed)
|
||||
})
|
||||
|
||||
t.Run("playlist restore", func(t *testing.T) {
|
||||
uid := "zzz"
|
||||
raw := []byte(`{
|
||||
"apiVersion": "playlist.grafana.app/v0alpha1",
|
||||
"kind": "Playlist",
|
||||
"metadata": {
|
||||
"name": "fdgsv37qslr0ga",
|
||||
"namespace": "default",
|
||||
"uid": "` + uid + `",
|
||||
"annotations": {
|
||||
"grafana.app/repoName": "elsewhere",
|
||||
"grafana.app/repoPath": "path/to/item",
|
||||
"grafana.app/repoTimestamp": "2024-02-02T00:00:00Z"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"title": "hello",
|
||||
"interval": "5m",
|
||||
"items": [
|
||||
{
|
||||
"type": "dashboard_by_uid",
|
||||
"value": "vmie2cmWz"
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
key := &ResourceKey{
|
||||
Group: "playlist.grafana.app",
|
||||
Resource: "rrrr",
|
||||
Namespace: "default",
|
||||
Name: "fdgsv37qslr0ga",
|
||||
}
|
||||
|
||||
// create
|
||||
created, err := server.Create(ctx, &CreateRequest{
|
||||
Value: raw,
|
||||
Key: key,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// make sure it exists
|
||||
found, err := server.Read(ctx, &ReadRequest{Key: key})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, found.Error)
|
||||
fmt.Println(found.ResourceVersion)
|
||||
|
||||
// delete it
|
||||
deleted, err := server.Delete(ctx, &DeleteRequest{Key: key, ResourceVersion: created.ResourceVersion})
|
||||
require.NoError(t, err)
|
||||
require.True(t, deleted.ResourceVersion > created.ResourceVersion)
|
||||
|
||||
// restore it
|
||||
restored, err := server.Restore(ctx, &RestoreRequest{
|
||||
Key: key,
|
||||
ResourceVersion: found.ResourceVersion,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, restored.Error)
|
||||
require.True(t, restored.ResourceVersion > deleted.ResourceVersion)
|
||||
|
||||
// ensure it exists now
|
||||
found, err = server.Read(ctx, &ReadRequest{Key: key})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, found.Error)
|
||||
require.Equal(t, restored.ResourceVersion, found.ResourceVersion)
|
||||
foundUnstructured := &unstructured.Unstructured{}
|
||||
err = foundUnstructured.UnmarshalJSON(found.Value)
|
||||
require.NoError(t, err)
|
||||
foundObj, err := utils.MetaAccessor(foundUnstructured)
|
||||
require.NoError(t, err)
|
||||
// the UID should be different now
|
||||
require.NotEqual(t, uid, string(foundObj.GetUID()))
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user