Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve hook UX and execution model #357

Merged
merged 15 commits into from
Jul 11, 2024
Merged
72 changes: 41 additions & 31 deletions gen/go/v1/operations.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

93 changes: 93 additions & 0 deletions internal/api/backresthandler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/garethgeorge/backrest/internal/config"
"github.com/garethgeorge/backrest/internal/oplog"
"github.com/garethgeorge/backrest/internal/orchestrator"
"github.com/garethgeorge/backrest/internal/orchestrator/tasks"
"github.com/garethgeorge/backrest/internal/resticinstaller"
"github.com/garethgeorge/backrest/internal/rotatinglog"
"golang.org/x/sync/errgroup"
Expand Down Expand Up @@ -334,6 +335,98 @@ func TestHookExecution(t *testing.T) {
}
}

func TestHookCancellation(t *testing.T) {
t.Parallel()

if runtime.GOOS == "windows" {
t.Skip("skipping test on windows")
}

sut := createSystemUnderTest(t, &config.MemoryStore{
Config: &v1.Config{
Modno: 1234,
Instance: "test",
Repos: []*v1.Repo{
{
Id: "local",
Uri: t.TempDir(),
Password: "test",
},
},
Plans: []*v1.Plan{
{
Id: "test",
Repo: "local",
Paths: []string{
t.TempDir(),
},
Schedule: &v1.Schedule{
Schedule: &v1.Schedule_Disabled{Disabled: true},
},
Hooks: []*v1.Hook{
{
Conditions: []v1.Hook_Condition{
v1.Hook_CONDITION_SNAPSHOT_START,
},
Action: &v1.Hook_ActionCommand{
ActionCommand: &v1.Hook_Command{
Command: "exit 123",
},
},
OnError: v1.Hook_ON_ERROR_CANCEL,
},
},
},
},
},
})

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
sut.orch.Run(ctx)
}()

_, err := sut.handler.Backup(context.Background(), connect.NewRequest(&types.StringValue{Value: "test"}))
if !errors.Is(err, tasks.ErrTaskCancelled) {
t.Fatalf("Backup() error = %v, want errors.Is(err, tasks.ErrTaskCancelled)", err)
}

// Wait for a hook operation to appear in the oplog
if err := retry(t, 10, 2*time.Second, func() error {
hookOps := slices.DeleteFunc(getOperations(t, sut.oplog), func(op *v1.Operation) bool {
_, ok := op.GetOp().(*v1.Operation_OperationRunHook)
return !ok
})
if len(hookOps) != 1 {
return fmt.Errorf("expected 1 hook operations, got %d", len(hookOps))
}
if hookOps[0].Status != v1.OperationStatus_STATUS_ERROR {
return fmt.Errorf("expected hook operation error status, got %v", hookOps[0].Status)
}
return nil
}); err != nil {
t.Fatalf("Couldn't find hooks in oplog: %v", err)
}

// assert that the backup operation is in the log and is cancelled
if err := retry(t, 10, 2*time.Second, func() error {
backupOps := slices.DeleteFunc(getOperations(t, sut.oplog), func(op *v1.Operation) bool {
_, ok := op.GetOp().(*v1.Operation_OperationBackup)
return !ok
})
if len(backupOps) != 1 {
return fmt.Errorf("expected 1 backup operation, got %d", len(backupOps))
}
if backupOps[0].Status != v1.OperationStatus_STATUS_USER_CANCELLED {
return fmt.Errorf("expected backup operation cancelled status, got %v", backupOps[0].Status)
}
return nil
}); err != nil {
t.Fatalf("Couldn't find hooks in oplog: %v", err)
}
}

func TestCancelBackup(t *testing.T) {
t.Parallel()

Expand Down
21 changes: 21 additions & 0 deletions internal/config/configutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package config

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

func FindPlan(cfg *v1.Config, planID string) *v1.Plan {
for _, plan := range cfg.Plans {
if plan.Id == planID {
return plan
}
}
return nil
}

func FindRepo(cfg *v1.Config, repoID string) *v1.Repo {
for _, repo := range cfg.Repos {
if repo.Id == repoID {
return repo
}
}
return nil
}
Loading
Loading