Restores in app platform (#97582)
This commit is contained in:
@@ -160,6 +160,9 @@ func (b *backend) WriteEvent(ctx context.Context, event resource.WriteEvent) (in
|
||||
// TODO: validate key ?
|
||||
switch event.Type {
|
||||
case resource.WatchEvent_ADDED:
|
||||
if event.ObjectOld != nil {
|
||||
return b.restore(ctx, event)
|
||||
}
|
||||
return b.create(ctx, event)
|
||||
case resource.WatchEvent_MODIFIED:
|
||||
return b.update(ctx, event)
|
||||
@@ -349,6 +352,83 @@ func (b *backend) delete(ctx context.Context, event resource.WriteEvent) (int64,
|
||||
return newVersion, err
|
||||
}
|
||||
|
||||
func (b *backend) restore(ctx context.Context, event resource.WriteEvent) (int64, error) {
|
||||
ctx, span := b.tracer.Start(ctx, tracePrefix+"Restore")
|
||||
defer span.End()
|
||||
var newVersion int64
|
||||
guid := uuid.New().String()
|
||||
err := b.db.WithTx(ctx, ReadCommitted, func(ctx context.Context, tx db.Tx) error {
|
||||
folder := ""
|
||||
if event.Object != nil {
|
||||
folder = event.Object.GetFolder()
|
||||
}
|
||||
|
||||
// 1. Re-create resource
|
||||
// Note: we may want to replace the write event with a create event, tbd.
|
||||
if _, err := dbutil.Exec(ctx, tx, sqlResourceInsert, sqlResourceRequest{
|
||||
SQLTemplate: sqltemplate.New(b.dialect),
|
||||
WriteEvent: event,
|
||||
Folder: folder,
|
||||
GUID: guid,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("insert into resource: %w", err)
|
||||
}
|
||||
|
||||
// 2. Insert into resource history
|
||||
if _, err := dbutil.Exec(ctx, tx, sqlResourceHistoryInsert, sqlResourceRequest{
|
||||
SQLTemplate: sqltemplate.New(b.dialect),
|
||||
WriteEvent: event,
|
||||
Folder: folder,
|
||||
GUID: guid,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("insert into resource history: %w", err)
|
||||
}
|
||||
|
||||
// 3. TODO: Rebuild the whole folder tree structure if we're creating a folder
|
||||
|
||||
// 4. Atomically increment resource version for this kind
|
||||
rv, err := b.resourceVersionAtomicInc(ctx, tx, event.Key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("increment resource version: %w", err)
|
||||
}
|
||||
|
||||
// 5. Update the RV in both resource and resource_history
|
||||
if _, err = dbutil.Exec(ctx, tx, sqlResourceHistoryUpdateRV, sqlResourceUpdateRVRequest{
|
||||
SQLTemplate: sqltemplate.New(b.dialect),
|
||||
GUID: guid,
|
||||
ResourceVersion: rv,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("update history rv: %w", err)
|
||||
}
|
||||
|
||||
if _, err = dbutil.Exec(ctx, tx, sqlResourceUpdateRV, sqlResourceUpdateRVRequest{
|
||||
SQLTemplate: sqltemplate.New(b.dialect),
|
||||
GUID: guid,
|
||||
ResourceVersion: rv,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("update resource rv: %w", err)
|
||||
}
|
||||
|
||||
// 6. Update all resource history entries with the new UID
|
||||
// Note: we do not update any history entries that have a deletion timestamp included. This will become
|
||||
// important once we start using finalizers, as the initial delete will show up as an update with a deletion timestamp included.
|
||||
if _, err = dbutil.Exec(ctx, tx, sqlResoureceHistoryUpdateUid, sqlResourceHistoryUpdateRequest{
|
||||
SQLTemplate: sqltemplate.New(b.dialect),
|
||||
WriteEvent: event,
|
||||
OldUID: string(event.ObjectOld.GetUID()),
|
||||
NewUID: string(event.Object.GetUID()),
|
||||
}); err != nil {
|
||||
return fmt.Errorf("update history uid: %w", err)
|
||||
}
|
||||
|
||||
newVersion = rv
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return newVersion, err
|
||||
}
|
||||
|
||||
func (b *backend) ReadResource(ctx context.Context, req *resource.ReadRequest) *resource.BackendReadResponse {
|
||||
_, span := b.tracer.Start(ctx, tracePrefix+".Read")
|
||||
defer span.End()
|
||||
@@ -371,8 +451,20 @@ func (b *backend) ReadResource(ctx context.Context, req *resource.ReadRequest) *
|
||||
err := b.db.WithTx(ctx, ReadCommittedRO, func(ctx context.Context, tx db.Tx) error {
|
||||
var err error
|
||||
res, err = dbutil.QueryRow(ctx, tx, sr, readReq)
|
||||
// if not found, look for latest deleted version (if requested)
|
||||
if errors.Is(err, sql.ErrNoRows) && req.IncludeDeleted {
|
||||
sr = sqlResourceHistoryRead
|
||||
readReq2 := &sqlResourceReadRequest{
|
||||
SQLTemplate: sqltemplate.New(b.dialect),
|
||||
Request: req,
|
||||
Response: NewReadResponse(),
|
||||
}
|
||||
res, err = dbutil.QueryRow(ctx, tx, sr, readReq2)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return &resource.BackendReadResponse{
|
||||
Error: resource.NewNotFoundError(req.Key),
|
||||
|
||||
Reference in New Issue
Block a user