Skip to content

Commit

Permalink
initial long running command support
Browse files Browse the repository at this point in the history
  • Loading branch information
garethgeorge committed Oct 12, 2024
1 parent 2876278 commit f455a17
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 5 deletions.
2 changes: 1 addition & 1 deletion internal/api/backresthandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ func (s *BackrestHandler) RunCommand(ctx context.Context, req *connect.Request[v
// group commands within the last 24 hours (or 256 operations) into the same flow ID
var flowID int64
if s.oplog.Query(oplog.Query{RepoID: req.Msg.RepoId, Limit: 256, Reversed: true}, func(op *v1.Operation) error {
if op.GetOperationRunCommand() != nil && time.Since(time.UnixMilli(op.UnixTimeStartMs)) < 24*time.Hour {
if op.GetOperationRunCommand() != nil && time.Since(time.UnixMilli(op.UnixTimeStartMs)) < 30*time.Minute {
flowID = op.FlowId
}
return nil
Expand Down
67 changes: 67 additions & 0 deletions internal/orchestrator/tasks/taskruncommand.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package tasks

import (
"context"
"fmt"
"time"

v1 "github.com/garethgeorge/backrest/gen/go/v1"
)

func NewOneoffForgetSnapshotTask(repoID, planID string, flowID int64, at time.Time, snapshotID string) Task {

Check failure on line 11 in internal/orchestrator/tasks/taskruncommand.go

View workflow job for this annotation

GitHub Actions / build

NewOneoffForgetSnapshotTask redeclared in this block

Check failure on line 11 in internal/orchestrator/tasks/taskruncommand.go

View workflow job for this annotation

GitHub Actions / test-win

NewOneoffForgetSnapshotTask redeclared in this block

Check failure on line 11 in internal/orchestrator/tasks/taskruncommand.go

View workflow job for this annotation

GitHub Actions / test-nix

NewOneoffForgetSnapshotTask redeclared in this block
return &GenericOneoffTask{
OneoffTask: OneoffTask{
BaseTask: BaseTask{
TaskType: "forget_snapshot",
TaskName: fmt.Sprintf("forget snapshot %q for plan %q in repo %q", snapshotID, planID, repoID),
TaskRepoID: repoID,
TaskPlanID: planID,
},
FlowID: flowID,
RunAt: at,
ProtoOp: &v1.Operation{
Op: &v1.Operation_OperationForget{},
},
},
Do: func(ctx context.Context, st ScheduledTask, taskRunner TaskRunner) error {
op := st.Op
forgetOp := op.GetOperationForget()
if forgetOp == nil {
panic("forget task with non-forget operation")
}

if err := forgetSnapshotHelper(ctx, st, taskRunner, snapshotID); err != nil {
taskRunner.ExecuteHooks(ctx, []v1.Hook_Condition{
v1.Hook_CONDITION_ANY_ERROR,
}, HookVars{
Error: err.Error(),
})
return err
}
return nil
},
}
}

func runCommandHelper(ctx context.Context, st ScheduledTask, taskRunner TaskRunner, snapshotID string) error {
t := st.Task

repo, err := taskRunner.GetRepoOrchestrator(t.RepoID())
if err != nil {
return fmt.Errorf("get repo %q: %w", t.RepoID(), err)
}

err = repo.UnlockIfAutoEnabled(ctx)
if err != nil {
return fmt.Errorf("auto unlock repo %q: %w", t.RepoID(), err)
}

if err := repo.ForgetSnapshot(ctx, snapshotID); err != nil {
return fmt.Errorf("forget %q: %w", snapshotID, err)
}

taskRunner.ScheduleTask(NewOneoffIndexSnapshotsTask(t.RepoID(), time.Now()), TaskPriorityIndexSnapshots)
taskRunner.OpLog().Delete(st.Op.Id)
st.Op = nil
return err
}
2 changes: 1 addition & 1 deletion webui/src/components/OperationTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,7 @@ const BackupView = ({ backup }: { backup?: FlowDisplayInfo }) => {
backrestService.clearHistory(
new ClearHistoryRequest({
selector: new OpSelector({
ids: backup.operations.map((op) => op.id),
flowId: backup.flowID,
}),
})
);
Expand Down
15 changes: 12 additions & 3 deletions webui/src/views/RunCommandModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,17 @@ export const RunCommandModal = ({ repoId }: { repoId: string }) => {
};

const doExecute = async () => {
if (running) return;
if (!command) return;
setRunning(true);

const toRun = command.trim();
setCommand("");

try {
const opID = await backrestService.runCommand(
new RunCommandRequest({
repoId,
command: command,
command: toRun,
})
);
} catch (e: any) {
Expand All @@ -56,18 +59,24 @@ export const RunCommandModal = ({ repoId }: { repoId: string }) => {
<Space.Compact style={{ width: "100%" }}>
<Input
placeholder="Run a restic comamnd e.g. 'help' to print help text"
value={command}
onChange={(e) => setCommand(e.target.value)}
onKeyUp={(e) => {
if (e.key === "Enter") {
doExecute();
}
}}
disabled={running}
/>
<SpinButton type="primary" onClickAsync={doExecute}>
Execute
</SpinButton>
</Space.Compact>
{running && command ? (
<em style={{ color: "gray" }}>
Warning: another command is already running. Wait for it to finish
before running another operation that requires the repo lock.
</em>
) : null}
<OperationList
req={
new GetOperationsRequest({
Expand Down

0 comments on commit f455a17

Please sign in to comment.