Skip to content

Commit

Permalink
feat: support --skip-if-unchanged
Browse files Browse the repository at this point in the history
  • Loading branch information
garethgeorge committed Oct 20, 2024
1 parent ec89cfd commit afcecae
Show file tree
Hide file tree
Showing 13 changed files with 303 additions and 231 deletions.
429 changes: 223 additions & 206 deletions gen/go/v1/config.pb.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions internal/orchestrator/repo/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,13 @@ func (r *RepoOrchestrator) Backup(ctx context.Context, plan *v1.Plan, progressCa

ctx, flush := forwardResticLogs(ctx)
defer flush()
l.Debug("starting backup", zap.String("repo", r.repoConfig.Id), zap.String("plan", plan.Id))
l.Debug("starting backup", zap.String("plan", plan.Id))
summary, err := r.repo.Backup(ctx, plan.Paths, progressCallback, opts...)
if err != nil {
return summary, fmt.Errorf("failed to backup: %w", err)
}

l.Debug("backup completed", zap.String("repo", r.repoConfig.Id), zap.Duration("duration", time.Since(startTime)))
l.Debug("backup completed", zap.Duration("duration", time.Since(startTime)))
return summary, nil
}

Expand Down
2 changes: 2 additions & 0 deletions internal/orchestrator/tasks/hookvars.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ func (v HookVars) EventName(cond v1.Hook_Condition) string {
return "snapshot warning"
case v1.Hook_CONDITION_SNAPSHOT_SUCCESS:
return "snapshot success"
case v1.Hook_CONDITION_SNAPSHOT_SKIPPED:
return "snapshot skipped"
case v1.Hook_CONDITION_CHECK_START:
return "check start"
case v1.Hook_CONDITION_CHECK_ERROR:
Expand Down
23 changes: 23 additions & 0 deletions internal/orchestrator/tasks/hookvars_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package tasks

import (
"testing"

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

func TestHookVarsEventName(t *testing.T) {
// for every value of condition, check that the event name is correct
values := v1.Hook_Condition(0).Descriptor().Values()
for i := 0; i < values.Len(); i++ {
condition := v1.Hook_Condition(values.Get(i).Number())
if condition == v1.Hook_CONDITION_UNKNOWN {
continue
}

vars := HookVars{}
if vars.EventName(condition) == "unknown" {
t.Errorf("unexpected event name for condition %v", condition)
}
}
}
34 changes: 21 additions & 13 deletions internal/orchestrator/tasks/taskbackup.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,6 @@ func (t *BackupTask) Run(ctx context.Context, st ScheduledTask, runner TaskRunne
v1.Hook_CONDITION_SNAPSHOT_WARNING,
v1.Hook_CONDITION_SNAPSHOT_END,
}, vars)
} else {
runner.ExecuteHooks(ctx, []v1.Hook_Condition{
v1.Hook_CONDITION_SNAPSHOT_SUCCESS,
v1.Hook_CONDITION_SNAPSHOT_END,
}, vars)
}

op.SnapshotId = summary.SnapshotId
Expand All @@ -227,15 +222,28 @@ func (t *BackupTask) Run(ctx context.Context, st ScheduledTask, runner TaskRunne

l.Info("backup complete", zap.String("plan", plan.Id), zap.Duration("duration", time.Since(startTime)), zap.Any("summary", backupOp.OperationBackup.LastStatus))

// schedule followup tasks
at := time.Now()
if _, ok := plan.Retention.GetPolicy().(*v1.RetentionPolicy_PolicyKeepAll); plan.Retention != nil && !ok {
if err := runner.ScheduleTask(NewOneoffForgetTask(t.RepoID(), t.PlanID(), op.FlowId, at), TaskPriorityForget); err != nil {
return fmt.Errorf("failed to schedule forget task: %w", err)
if summary.SnapshotId == "" { // support --skip-if-unchanged which returns an operation with an empty snapshot ID
op.DisplayMessage = "No snapshot added, possibly due to no changes in the source data."
runner.ExecuteHooks(ctx, []v1.Hook_Condition{
v1.Hook_CONDITION_SNAPSHOT_SKIPPED,
v1.Hook_CONDITION_SNAPSHOT_END,
}, vars)
} else {
runner.ExecuteHooks(ctx, []v1.Hook_Condition{
v1.Hook_CONDITION_SNAPSHOT_SUCCESS,
v1.Hook_CONDITION_SNAPSHOT_END,
}, vars)

// schedule followup tasks if a snapshot was added
at := time.Now()
if _, ok := plan.Retention.GetPolicy().(*v1.RetentionPolicy_PolicyKeepAll); plan.Retention != nil && !ok {
if err := runner.ScheduleTask(NewOneoffForgetTask(t.RepoID(), t.PlanID(), op.FlowId, at), TaskPriorityForget); err != nil {
return fmt.Errorf("failed to schedule forget task: %w", err)
}
}
if err := runner.ScheduleTask(NewOneoffIndexSnapshotsTask(t.RepoID(), at), TaskPriorityIndexSnapshots); err != nil {
return fmt.Errorf("failed to schedule index snapshots task: %w", err)
}
}
if err := runner.ScheduleTask(NewOneoffIndexSnapshotsTask(t.RepoID(), at), TaskPriorityIndexSnapshots); err != nil {
return fmt.Errorf("failed to schedule index snapshots task: %w", err)
}

return nil
Expand Down
5 changes: 1 addition & 4 deletions pkg/restic/outputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,7 @@ type BackupProgressEntry struct {
}

func (b *BackupProgressEntry) Validate() error {
if b.MessageType == "summary" {
if b.SnapshotId == "" {
return errors.New("summary message must have snapshot_id")
}
if b.MessageType == "summary" && b.SnapshotId != "" {
if err := ValidateSnapshotId(b.SnapshotId); err != nil {
return err
}
Expand Down
3 changes: 3 additions & 0 deletions proto/v1/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ message Plan {
RetentionPolicy retention = 7 [json_name="retention"]; // retention policy for snapshots.
repeated Hook hooks = 8 [json_name="hooks"]; // hooks to run on events for this plan.
repeated string backup_flags = 10 [json_name="backup_flags"]; // extra flags to set when running a backup command.
bool skip_if_unchanged = 13 [json_name="skipIfUnchanged"]; // skip the backup if no changes are detected.
reserved 3, 6, 11; // deprecated
}

message CommandPrefix {
Expand Down Expand Up @@ -132,6 +134,7 @@ message Hook {
CONDITION_SNAPSHOT_ERROR = 4; // snapshot failed.
CONDITION_SNAPSHOT_WARNING = 5; // snapshot completed with warnings.
CONDITION_SNAPSHOT_SUCCESS = 6; // snapshot succeeded.
CONDITION_SNAPSHOT_SKIPPED = 7; // snapshot was skipped e.g. due to no changes.

// prune conditions
CONDITION_PRUNE_START = 100; // prune started.
Expand Down
16 changes: 16 additions & 0 deletions webui/gen/ts/v1/config_pb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,13 @@ export class Plan extends Message<Plan> {
*/
backupFlags: string[] = [];

/**
* skip the backup if no changes are detected.
*
* @generated from field: bool skip_if_unchanged = 13;
*/
skipIfUnchanged = false;

constructor(data?: PartialMessage<Plan>) {
super();
proto3.util.initPartial(data, this);
Expand All @@ -359,6 +366,7 @@ export class Plan extends Message<Plan> {
{ no: 7, name: "retention", kind: "message", T: RetentionPolicy },
{ no: 8, name: "hooks", kind: "message", T: Hook, repeated: true },
{ no: 10, name: "backup_flags", jsonName: "backup_flags", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true },
{ no: 13, name: "skip_if_unchanged", kind: "scalar", T: 8 /* ScalarType.BOOL */ },
]);

static fromBinary(bytes: Uint8Array, options?: Partial<BinaryReadOptions>): Plan {
Expand Down Expand Up @@ -986,6 +994,13 @@ export enum Hook_Condition {
*/
SNAPSHOT_SUCCESS = 6,

/**
* snapshot was skipped e.g. due to no changes.
*
* @generated from enum value: CONDITION_SNAPSHOT_SKIPPED = 7;
*/
SNAPSHOT_SKIPPED = 7,

/**
* prune conditions
*
Expand Down Expand Up @@ -1041,6 +1056,7 @@ proto3.util.setEnumType(Hook_Condition, "v1.Hook.Condition", [
{ no: 4, name: "CONDITION_SNAPSHOT_ERROR" },
{ no: 5, name: "CONDITION_SNAPSHOT_WARNING" },
{ no: 6, name: "CONDITION_SNAPSHOT_SUCCESS" },
{ no: 7, name: "CONDITION_SNAPSHOT_SKIPPED" },
{ no: 100, name: "CONDITION_PRUNE_START" },
{ no: 101, name: "CONDITION_PRUNE_ERROR" },
{ no: 102, name: "CONDITION_PRUNE_SUCCESS" },
Expand Down
4 changes: 3 additions & 1 deletion webui/src/components/OperationRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,9 @@ const BackupOperationStatus = ({
<>
<Typography.Text>
<Typography.Text strong>Snapshot ID: </Typography.Text>
{normalizeSnapshotId(sum.snapshotId!)}
{sum.snapshotId !== ""
? normalizeSnapshotId(sum.snapshotId!)
: "No Snapshot Created"}
</Typography.Text>
<Row gutter={16}>
<Col span={8}>
Expand Down
9 changes: 6 additions & 3 deletions webui/src/components/OperationTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,12 @@ export const OperationTree = ({
event === OperationEventType.EVENT_UPDATED
) {
for (const flowID of flowIDs) {
const displayInfo = displayInfoForFlow(
logState.getByFlowID(flowID) || []
);
const ops = logState.getByFlowID(flowID);
if (!ops || ops[0].op.case === "operationRunHook") {
continue;
}

const displayInfo = displayInfoForFlow(ops);
if (!displayInfo.hidden) {
backupInfoByFlowID.set(flowID, displayInfo);
} else {
Expand Down
1 change: 1 addition & 0 deletions webui/src/state/oplog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const unsubscribeFromOperations = (
export const shouldHideOperation = (operation: Operation) => {
return (
operation.op.case === "operationStats" ||
(operation.status === OperationStatus.STATUS_SUCCESS && operation.op.case === "operationBackup" && !operation.snapshotId) ||
shouldHideStatus(operation.status)
);
};
Expand Down
2 changes: 1 addition & 1 deletion webui/src/views/PlanView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export const PlanView = ({ plan }: React.PropsWithChildren<{ plan: Plan }>) => {
},
{
key: "2",
label: "Operation List",
label: "Full Operation History",
children: (
<>
<h2>Backup Action History</h2>
Expand Down
2 changes: 1 addition & 1 deletion webui/src/views/RepoView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export const RepoView = ({ repo }: React.PropsWithChildren<{ repo: Repo }>) => {
},
{
key: "2",
label: "Operation List",
label: "Full Operation History",
children: (
<>
<h3>Backup Action History</h3>
Expand Down

0 comments on commit afcecae

Please sign in to comment.