From ba9e6dcf63749303b36997db5865763414b82db8 Mon Sep 17 00:00:00 2001 From: Omar Polo Date: Sun, 21 Jun 2026 13:25:09 +0000 Subject: [PATCH 1/2] wip -- merge Parse() & Execute() just register a single function that takes params & do the work. --- main.go | 20 ++- subcommands/archive/archive.go | 56 ++++----- subcommands/backup/backup_extra_test.go | 153 ++++++----------------- subcommands/backup/backup_test.go | 128 +++++++++---------- subcommands/cached/cached.go | 47 +++---- subcommands/cat/cat.go | 40 +++--- subcommands/check/check.go | 64 ++++------ subcommands/config/config.go | 12 +- subcommands/config/config_extra_test.go | 61 +++------ subcommands/config/config_test.go | 37 +----- subcommands/config/destination.go | 19 +-- subcommands/config/policy.go | 19 +-- subcommands/config/source.go | 19 +-- subcommands/config/store.go | 19 +-- subcommands/create/create.go | 70 +++++------ subcommands/diag/blob.go | 46 +++---- subcommands/diag/blob_search.go | 37 ++---- subcommands/diag/chunks.go | 28 ++--- subcommands/diag/contenttype.go | 30 ++--- subcommands/diag/diag.go | 28 ++--- subcommands/diag/diag_test.go | 151 +++++----------------- subcommands/diag/dirpack.go | 60 ++++----- subcommands/diag/locks.go | 17 +-- subcommands/diag/object.go | 34 ++--- subcommands/diag/packfile.go | 33 ++--- subcommands/diag/repository.go | 22 +--- subcommands/diag/search.go | 36 ++---- subcommands/diag/snapshot.go | 22 +--- subcommands/diag/state.go | 34 ++--- subcommands/diag/vfs.go | 40 ++---- subcommands/diag/xattrs.go | 35 ++---- subcommands/diff/diff.go | 86 ++++++------- subcommands/digest/digest.go | 56 ++++----- subcommands/dup/dup.go | 20 +-- subcommands/help/help.go | 38 +++--- subcommands/info/errors.go | 12 +- subcommands/info/info.go | 33 ++--- subcommands/info/repository.go | 8 +- subcommands/info/snapshot.go | 8 +- subcommands/locate/locate.go | 52 ++++---- subcommands/login/login.go | 84 ++++++------- subcommands/login/logout.go | 17 +-- subcommands/login/token-create.go | 15 +-- subcommands/ls/ls.go | 68 ++++------ subcommands/maintenance/maintenance.go | 57 ++++----- subcommands/mount/fuse/fuse.go | 16 +-- subcommands/mount/fuse/fuse_dragonfly.go | 4 +- subcommands/mount/fuse/fuse_freebsd.go | 4 +- subcommands/mount/fuse/fuse_netbsd.go | 4 +- subcommands/mount/fuse/fuse_openbsd.go | 4 +- subcommands/mount/fuse/fuse_windows.go | 4 +- subcommands/mount/http/http.go | 8 +- subcommands/mount/http/http_test.go | 19 +-- subcommands/mount/mount.go | 58 ++++----- subcommands/prune/prune.go | 99 +++++++-------- subcommands/ptar/factory_test.go | 3 +- subcommands/ptar/ptar.go | 142 ++++++++++----------- subcommands/ptar/ptar_test.go | 20 +-- subcommands/repair/repair.go | 94 ++++++-------- subcommands/restore/restore.go | 125 ++++++++---------- subcommands/rm/rm.go | 45 +++---- subcommands/server/server.go | 51 +++----- subcommands/service/add.go | 34 ++--- subcommands/service/disable.go | 24 +--- subcommands/service/enable.go | 24 +--- subcommands/service/list.go | 18 +-- subcommands/service/rm.go | 27 ++-- subcommands/service/service.go | 30 ++--- subcommands/service/service_test.go | 26 ++-- subcommands/service/set.go | 40 +++--- subcommands/service/show.go | 46 +++---- subcommands/service/status.go | 24 +--- subcommands/service/unset.go | 38 +++--- subcommands/subcommands.go | 55 ++++---- subcommands/sync/factory_test.go | 3 +- subcommands/sync/sync.go | 129 ++++++++----------- subcommands/sync/sync_test.go | 16 +-- subcommands/version/version.go | 14 +-- task/task.go | 26 +--- testing/cached.go | 8 +- 80 files changed, 1171 insertions(+), 2082 deletions(-) diff --git a/main.go b/main.go index b45b1108d..12d1ac2a1 100644 --- a/main.go +++ b/main.go @@ -376,7 +376,7 @@ func entryPoint() int { return 1 } - cmd, _, args := subcommands.Lookup(args) + cmd, cmdflags, _, args := subcommands.Lookup(args) if cmd == nil { logger.Stderr("command not found: %s\n", args[0]) return 1 @@ -396,9 +396,9 @@ func entryPoint() int { var store storage.Store var repo *repository.Repository - if cmd.GetFlags()&subcommands.BeforeRepositoryOpen != 0 { + if cmdflags&subcommands.BeforeRepositoryOpen != 0 { // store and repo can stay nil - } else if cmd.GetFlags()&subcommands.BeforeRepositoryWithStorage != 0 { + } else if cmdflags&subcommands.BeforeRepositoryWithStorage != 0 { repo, err = repository.Inexistent(ctx.GetInner(), storeConfig) if err != nil { logger.Stderr("%s: %s\n", flag.CommandLine.Name(), err) @@ -441,12 +441,6 @@ func entryPoint() int { ctx.StoreConfig = storeConfig - t0 := time.Now() - if err := cmd.Parse(ctx, args); err != nil { - logger.Stderr("%s: %s\n", flag.CommandLine.Name(), err) - return 1 - } - c := make(chan os.Signal, 1) go func() { <-c @@ -462,16 +456,16 @@ func entryPoint() int { } }() - var status int + t0 := time.Now() // If we are working on a repo, rebuild the state. - if cmd.GetFlags()&subcommands.BeforeRepositoryOpen == 0 && cmd.GetFlags()&subcommands.BeforeRepositoryWithStorage == 0 { + if cmdflags&subcommands.BeforeRepositoryOpen == 0 && cmdflags&subcommands.BeforeRepositoryWithStorage == 0 { _, err = cached.RebuildStateFromStore(ctx, repo.Configuration().RepositoryID, storeConfig, false) if err == nil { - status, err = task.RunCommand(ctx, cmd, repo, "@agentless") + err = task.RunCommand(ctx, cmd, repo, "@agentless") } } else { - status, err = task.RunCommand(ctx, cmd, repo, "@agentless") + err = task.RunCommand(ctx, cmd, repo, "@agentless") } t1 := time.Since(t0) diff --git a/subcommands/archive/archive.go b/subcommands/archive/archive.go index 6d79d4c27..51ff5f7c0 100644 --- a/subcommands/archive/archive.go +++ b/subcommands/archive/archive.go @@ -29,10 +29,16 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &Archive{} }, 0, "archive") + subcommands.Register(Archive, 0, "archive") } -func (cmd *Archive) Parse(ctx *appcontext.AppContext, args []string) error { +func Archive(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + rebase bool + output string + format string + ) + flags := flag.NewFlagSet("archive", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s [OPTIONS] [SNAPSHOT[:PATH]]\n", flags.Name()) @@ -40,60 +46,48 @@ func (cmd *Archive) Parse(ctx *appcontext.AppContext, args []string) error { flags.PrintDefaults() } - flags.StringVar(&cmd.Output, "output", "", "archive pathname") - flags.BoolVar(&cmd.Rebase, "rebase", false, "strip pathname when pulling") - flags.StringVar(&cmd.Format, "format", "tarball", "archive format: tar, tarball, zip") + flags.StringVar(&output, "output", "", "archive pathname") + flags.BoolVar(&rebase, "rebase", false, "strip pathname when pulling") + flags.StringVar(&format, "format", "tarball", "archive format: tar, tarball, zip") flags.Parse(args) if flags.NArg() == 0 { return fmt.Errorf("need at least one snapshot ID to pull") } - cmd.SnapshotPrefix = flags.Arg(0) + + prefix := flags.Arg(0) supportedFormats := map[string]string{ "tar": "tar", "tarball": "tar.gz", "zip": "zip", } - if _, ok := supportedFormats[cmd.Format]; !ok { - return fmt.Errorf("unsupported format %s", cmd.Format) + if _, ok := supportedFormats[format]; !ok { + return fmt.Errorf("unsupported format %s", format) } - if cmd.Output == "" { - cmd.Output = fmt.Sprintf("plakar-%s.%s", time.Now().UTC().Format(time.RFC3339), supportedFormats[cmd.Format]) + if output == "" { + output = fmt.Sprintf("plakar-%s.%s", time.Now().UTC().Format(time.RFC3339), supportedFormats[format]) } - return nil -} - -type Archive struct { - subcommands.SubcommandBase - - Rebase bool - Output string - Format string - SnapshotPrefix string -} - -func (cmd *Archive) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - snap, pathname, err := locate.OpenSnapshotByPath(repo, cmd.SnapshotPrefix) + snap, pathname, err := locate.OpenSnapshotByPath(repo, prefix) if err != nil { - return 1, fmt.Errorf("archive: could not open snapshot: %s", cmd.SnapshotPrefix) + return fmt.Errorf("archive: could not open snapshot: %s", prefix) } defer snap.Close() out := os.Stdout - if cmd.Output != "-" { - out, err = os.Create(cmd.Output) + if output != "-" { + out, err = os.Create(output) if err != nil { - return 1, fmt.Errorf("failed to create %s: %w", cmd.Output, err) + return fmt.Errorf("failed to create %s: %w", output, err) } defer out.Close() } - if err = snap.Archive(out, cmd.Format, []string{pathname}, cmd.Rebase); err != nil { - return 1, err + if err = snap.Archive(out, format, []string{pathname}, rebase); err != nil { + return err } - return 0, nil + return nil } diff --git a/subcommands/backup/backup_extra_test.go b/subcommands/backup/backup_extra_test.go index dcdaaf458..e11940511 100644 --- a/subcommands/backup/backup_extra_test.go +++ b/subcommands/backup/backup_extra_test.go @@ -8,6 +8,9 @@ import ( "testing" "time" + "github.com/PlakarKorp/kloset/locate" + "github.com/PlakarKorp/kloset/repository" + "github.com/PlakarKorp/kloset/snapshot" "github.com/PlakarKorp/plakar/appcontext" "github.com/PlakarKorp/plakar/ui/stdio" "github.com/stretchr/testify/require" @@ -19,7 +22,7 @@ import ( // // The stdio renderer is started here too, mirroring the production wiring: // without it nothing drains the event bus and Backup.Execute deadlocks. -func runBackup(t *testing.T, args []string, mutate func(*Backup)) (int, error, *bytes.Buffer, *appcontext.AppContext) { +func runBackup(t *testing.T, args []string) (error, *bytes.Buffer, *repository.Repository, *appcontext.AppContext) { t.Helper() bufOut := bytes.NewBuffer(nil) bufErr := bytes.NewBuffer(nil) @@ -36,15 +39,7 @@ func runBackup(t *testing.T, args []string, mutate func(*Backup)) (int, error, * ctx.Stderr = bufErr allArgs := append(args, tmpBackupDir) - cmd := &Backup{} - if err := cmd.Parse(ctx, allArgs); err != nil { - return 0, err, bufOut, ctx - } - if mutate != nil { - mutate(cmd) - } - status, err := cmd.Execute(ctx, repo) - return status, err, bufOut, ctx + return Backup(ctx, repo, allArgs), bufOut, repo, ctx } func TestBackupDryRunProducesNoSnapshot(t *testing.T) { @@ -58,12 +53,7 @@ func TestBackupDryRunProducesNoSnapshot(t *testing.T) { t.Cleanup(ctx.Close) ctx.MaxConcurrency = 1 - cmd := &Backup{} - require.NoError(t, cmd.Parse(ctx, []string{"-dry-run", tmpBackupDir})) - require.True(t, cmd.DryRun, "DryRun flag should be parsed") - - status, _, _, _ := cmd.DoBackup(ctx, repo) - require.Equal(t, 0, status) + require.NoError(t, Backup(ctx, repo, []string{"-dry-run", tmpBackupDir})) // Sanity: the snapshot listing should be empty after a dry run. count := 0 @@ -75,59 +65,51 @@ func TestBackupDryRunProducesNoSnapshot(t *testing.T) { } func TestBackupNoXattrPropagates(t *testing.T) { - status, err, _, _ := runBackup(t, []string{"-no-xattr"}, nil) + err, _, _, _ := runBackup(t, []string{"-no-xattr"}) require.NoError(t, err) - require.Equal(t, 0, status) } func TestBackupNameAndMetadataParseFlags(t *testing.T) { - bufOut := bytes.NewBuffer(nil) - bufErr := bytes.NewBuffer(nil) - _, tmpBackupDir, ctx := generateFixtures(t, bufOut, bufErr) - t.Cleanup(ctx.Close) - args := []string{ "-name", "snap1", "-category", "weekly", "-environment", "prod", "-perimeter", "datacenter-a", "-job", "job-42", - tmpBackupDir, } - cmd := &Backup{} - require.NoError(t, cmd.Parse(ctx, args)) - require.Equal(t, "snap1", cmd.Name) - require.Equal(t, "weekly", cmd.Category) - require.Equal(t, "prod", cmd.Environment) - require.Equal(t, "datacenter-a", cmd.Perimeter) - require.Equal(t, "job-42", cmd.Job) + err, _, repo, _ := runBackup(t, args) + require.NoError(t, err) + + locateopts := locate.NewDefaultLocateOptions(locate.WithLatest(true)) + + require.NoError(t, repo.RebuildState()) + snapshotIDs, err := locate.LocateSnapshotIDs(repo, locateopts) + require.NoError(t, err, "LocateSnapshotIDs failed") + require.Len(t, snapshotIDs, 1) + + snap, err := snapshot.Load(repo, snapshotIDs[0]) + require.NoError(t, err) + + require.Equal(t, "snap1", snap.Header.Name) + require.Equal(t, "weekly", snap.Header.Category) + require.Equal(t, "prod", snap.Header.Environment) + require.Equal(t, "datacenter-a", snap.Header.Perimeter) + require.Equal(t, "job-42", snap.Header.Job) } func TestBackupForcedTimestampInPastIsAccepted(t *testing.T) { past := time.Now().Add(-24 * time.Hour).UTC().Format(time.RFC3339) - status, err, _, _ := runBackup(t, []string{"-force-timestamp", past}, nil) + err, _, _, _ := runBackup(t, []string{"-force-timestamp", past}) require.NoError(t, err) - require.Equal(t, 0, status) } func TestBackupForcedTimestampInFutureRejected(t *testing.T) { future := time.Now().Add(24 * time.Hour).UTC().Format(time.RFC3339) - _, err, _, _ := runBackup(t, []string{"-force-timestamp", future}, nil) + err, _, _, _ := runBackup(t, []string{"-force-timestamp", future}) require.Error(t, err) require.Contains(t, err.Error(), "future") } -func TestBackupTagViaFlag(t *testing.T) { - bufOut := bytes.NewBuffer(nil) - bufErr := bytes.NewBuffer(nil) - _, tmpBackupDir, ctx := generateFixtures(t, bufOut, bufErr) - - t.Cleanup(ctx.Close) - cmd := &Backup{} - require.NoError(t, cmd.Parse(ctx, []string{"-tag", "alpha,beta", tmpBackupDir})) - require.Equal(t, []string{"alpha", "beta"}, cmd.Tags) -} - func TestBackupIgnoreFileFlag(t *testing.T) { bufOut := bytes.NewBuffer(nil) bufErr := bytes.NewBuffer(nil) @@ -143,16 +125,7 @@ func TestBackupIgnoreFileFlag(t *testing.T) { // One real entry plus a comment and a blank line to exercise both filters. require.NoError(t, os.WriteFile(ignoreFile, []byte("# a comment\n\n**/subdir\n"), 0o600)) - cmd := &Backup{} - require.NoError(t, cmd.Parse(ctx, []string{"-ignore-file", ignoreFile, tmpBackupDir})) - require.Contains(t, cmd.Excludes, "**/subdir") - // Comments and blanks must not leak into the rule set. - require.NotContains(t, cmd.Excludes, "# a comment") - require.NotContains(t, cmd.Excludes, "") - - status, err := cmd.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + require.NoError(t, Backup(ctx, repo, []string{"-ignore-file", ignoreFile, tmpBackupDir})) require.NotContains(t, bufOut.String(), "/subdir/") } @@ -173,18 +146,12 @@ func TestBackupMultipleIgnoreFileFlags(t *testing.T) { require.NoError(t, os.WriteFile(macOSIgnoreFile, []byte(".DS_Store\n"), 0o600)) require.NoError(t, os.WriteFile(sourceIgnoreFile, []byte("**/subdir\n"), 0o600)) - cmd := &Backup{} - require.NoError(t, cmd.Parse(ctx, []string{ + require.NoError(t, Backup(ctx, repo, []string{ "-ignore-file", macOSIgnoreFile, "-ignore-file", sourceIgnoreFile, "-ignore", "**/another_subdir", tmpBackupDir, })) - require.Equal(t, []string{".DS_Store", "**/subdir", "**/another_subdir"}, cmd.Excludes) - - status, err := cmd.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) require.NotContains(t, bufOut.String(), "/subdir/") require.NotContains(t, bufOut.String(), "/another_subdir/") } @@ -194,8 +161,7 @@ func TestBackupIgnoreFileMissing(t *testing.T) { bufErr := bytes.NewBuffer(nil) _, tmpBackupDir, ctx := generateFixtures(t, bufOut, bufErr) t.Cleanup(ctx.Close) - cmd := &Backup{} - err := cmd.Parse(ctx, []string{"-ignore-file", "/this/does/not/exist", tmpBackupDir}) + err := Backup(ctx, nil, []string{"-ignore-file", "/this/does/not/exist", tmpBackupDir}) require.Error(t, err) } @@ -221,13 +187,10 @@ func TestBackupPreHookFailureAbortsBackup(t *testing.T) { t.Cleanup(ctx.Close) ctx.MaxConcurrency = 1 - cmd := &Backup{} - require.NoError(t, cmd.Parse(ctx, []string{tmpBackupDir})) - cmd.PreHook = "exit 7" + err := Backup(ctx, repo, []string{"-pre-hook", "exit 7", tmpBackupDir}) + require.Error(t, err) - status, err := cmd.Execute(ctx, repo) require.Error(t, err) - require.Equal(t, 1, status) require.Contains(t, err.Error(), "pre-backup hook failed") } @@ -242,63 +205,17 @@ func TestBackupPostHookFailureIsNotFatal(t *testing.T) { t.Cleanup(ctx.Close) ctx.MaxConcurrency = 1 - cmd := &Backup{} - require.NoError(t, cmd.Parse(ctx, []string{tmpBackupDir})) - cmd.PostHook = "exit 9" - - status, err := cmd.Execute(ctx, repo) - // Post-hook failure must not flip the overall result. - require.NoError(t, err) - require.Equal(t, 0, status) - // The hook was at least attempted. + require.NoError(t, Backup(ctx, repo, []string{"-post-hook", "exit 9", tmpBackupDir})) require.Contains(t, bufOut.String(), "executing hook: exit 9") } -func TestBackupEmptySourcesUsesCWD(t *testing.T) { - bufOut := bytes.NewBuffer(nil) - bufErr := bytes.NewBuffer(nil) - _, _, ctx := generateFixtures(t, bufOut, bufErr) - t.Cleanup(ctx.Close) - - ctx.CWD = "/var/empty" - cmd := &Backup{} - require.NoError(t, cmd.Parse(ctx, []string{})) - require.Equal(t, []string{"fs:/var/empty"}, cmd.Sources) -} - -func TestBackupCheckFlagParses(t *testing.T) { - // We cannot exercise the full -check end-to-end without a real cached - // daemon: the fake cached server returns OK without actually rebuilding - // state, and the subsequent integrity check fails with "blob not found". - // So just pin down that the flag is parsed. - bufOut := bytes.NewBuffer(nil) - bufErr := bytes.NewBuffer(nil) - _, tmpBackupDir, ctx := generateFixtures(t, bufOut, bufErr) - t.Cleanup(ctx.Close) - cmd := &Backup{} - require.NoError(t, cmd.Parse(ctx, []string{"-check", tmpBackupDir})) - require.True(t, cmd.OptCheck) -} - func TestBackupPackfilesMemory(t *testing.T) { - status, err, _, _ := runBackup(t, []string{"-packfiles", "memory"}, nil) + err, _, _, _ := runBackup(t, []string{"-packfiles", "memory"}) require.NoError(t, err) - require.Equal(t, 0, status) -} - -func TestBackupParsesMultipleIgnoreFlags(t *testing.T) { - bufOut := bytes.NewBuffer(nil) - bufErr := bytes.NewBuffer(nil) - _, tmpBackupDir, ctx := generateFixtures(t, bufOut, bufErr) - t.Cleanup(ctx.Close) - cmd := &Backup{} - require.NoError(t, cmd.Parse(ctx, []string{"-ignore", "*.tmp", "-ignore", "*.log", tmpBackupDir})) - require.Contains(t, cmd.Excludes, "*.tmp") - require.Contains(t, cmd.Excludes, "*.log") } func TestBackupOutputMentionsCompletion(t *testing.T) { - _, _, bufOut, _ := runBackup(t, nil, nil) + _, bufOut, _, _ := runBackup(t, nil) out := bufOut.String() require.True(t, strings.Contains(out, "backup completed"), "missing 'backup completed' line in:\n%s", out) } diff --git a/subcommands/backup/backup_test.go b/subcommands/backup/backup_test.go index 208e3be01..661348928 100644 --- a/subcommands/backup/backup_test.go +++ b/subcommands/backup/backup_test.go @@ -14,10 +14,12 @@ import ( "github.com/PlakarKorp/kloset/caching/pebble" "github.com/PlakarKorp/kloset/connectors/storage" "github.com/PlakarKorp/kloset/hashing" + "github.com/PlakarKorp/kloset/locate" "github.com/PlakarKorp/kloset/logging" "github.com/PlakarKorp/kloset/objects" "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/kloset/resources" + "github.com/PlakarKorp/kloset/snapshot" "github.com/PlakarKorp/kloset/versioning" "github.com/PlakarKorp/plakar/appcontext" "github.com/PlakarKorp/plakar/ui/stdio" @@ -120,14 +122,8 @@ func TestExecuteCmdCreateDefault(t *testing.T) { ctx.Stderr = bufErr args := []string{tmpBackupDir} - subcommand := &Backup{} - err := subcommand.Parse(ctx, args) + err := Backup(ctx, repo, args) require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err := subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) // output should be something like: // info: 9a383818: OK ✓ /tmp/tmp_to_backup2103009254/subdir/foo.txt @@ -162,20 +158,15 @@ func TestExecuteCmdCreateWithHooks(t *testing.T) { ctx.MaxConcurrency = 1 ctx.Stdout = bufOut ctx.Stderr = bufErr - args := []string{tmpBackupDir} - - subcommand := &Backup{} - err := subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - // Set hooks - subcommand.PreHook = "echo 'pre-hook executed'" - subcommand.PostHook = "echo 'post-hook executed'" + args := []string{ + "-pre-hook", "echo 'pre-hook executed'", + "-post-hook", "echo 'post-hook executed'", + tmpBackupDir, + } - status, err := subcommand.Execute(ctx, repo) + err := Backup(ctx, repo, args) require.NoError(t, err) - require.Equal(t, 0, status) output := bufOut.String() require.Contains(t, output, "executing hook: echo 'pre-hook executed'") @@ -203,17 +194,20 @@ func TestBackupWithPlakarTagsEnv(t *testing.T) { ctx.MaxConcurrency = 1 ctx.Stdout = bufOut ctx.Stderr = bufErr - args := []string{tmpBackupDir} - subcommand := &Backup{} - err := subcommand.Parse(ctx, args) + err := Backup(ctx, repo, []string{tmpBackupDir}) require.NoError(t, err) - require.Equal(t, []string{"daily", "important"}, subcommand.Tags) + locateopts := locate.NewDefaultLocateOptions(locate.WithLatest(true)) + + require.NoError(t, repo.RebuildState()) + snapshotIDs, err := locate.LocateSnapshotIDs(repo, locateopts) + require.NoError(t, err, "LocateSnapshotIDs failed") + require.Len(t, snapshotIDs, 1) - status, err := subcommand.Execute(ctx, repo) + snap, err := snapshot.Load(repo, snapshotIDs[0]) require.NoError(t, err) - require.Equal(t, 0, status) + require.Equal(t, snap.Header.Tags, []string{"daily", "important"}) } func TestBackupTagFlagOverridesEnv(t *testing.T) { @@ -234,18 +228,20 @@ func TestBackupTagFlagOverridesEnv(t *testing.T) { ctx.MaxConcurrency = 1 ctx.Stdout = bufOut ctx.Stderr = bufErr - args := []string{"-tag", "cli-tag", tmpBackupDir} - subcommand := &Backup{} - err := subcommand.Parse(ctx, args) + err := Backup(ctx, repo, []string{"-tag", "cli-tag", tmpBackupDir}) require.NoError(t, err) - // CLI flag should win over env var - require.Equal(t, []string{"cli-tag"}, subcommand.Tags) + locateopts := locate.NewDefaultLocateOptions(locate.WithLatest(true)) - status, err := subcommand.Execute(ctx, repo) + require.NoError(t, repo.RebuildState()) + snapshotIDs, err := locate.LocateSnapshotIDs(repo, locateopts) + require.NoError(t, err, "LocateSnapshotIDs failed") + require.Len(t, snapshotIDs, 1) + + snap, err := snapshot.Load(repo, snapshotIDs[0]) require.NoError(t, err) - require.Equal(t, 0, status) + require.Equal(t, snap.Header.Tags, []string{"cli-tag"}) } func TestBackupEmptyPlakarTagsEnv(t *testing.T) { @@ -266,18 +262,20 @@ func TestBackupEmptyPlakarTagsEnv(t *testing.T) { ctx.MaxConcurrency = 1 ctx.Stdout = bufOut ctx.Stderr = bufErr - args := []string{tmpBackupDir} - subcommand := &Backup{} - err := subcommand.Parse(ctx, args) + err := Backup(ctx, repo, []string{tmpBackupDir}) require.NoError(t, err) - // No tags should be set - require.Equal(t, []string{}, subcommand.Tags) + locateopts := locate.NewDefaultLocateOptions(locate.WithLatest(true)) - status, err := subcommand.Execute(ctx, repo) + require.NoError(t, repo.RebuildState()) + snapshotIDs, err := locate.LocateSnapshotIDs(repo, locateopts) + require.NoError(t, err, "LocateSnapshotIDs failed") + require.Len(t, snapshotIDs, 1) + + snap, err := snapshot.Load(repo, snapshotIDs[0]) require.NoError(t, err) - require.Equal(t, 0, status) + require.Empty(t, snap.Header.Tags) } func TestBackupPlakarTagsWhitespace(t *testing.T) { @@ -298,17 +296,20 @@ func TestBackupPlakarTagsWhitespace(t *testing.T) { ctx.MaxConcurrency = 1 ctx.Stdout = bufOut ctx.Stderr = bufErr - args := []string{tmpBackupDir} - subcommand := &Backup{} - err := subcommand.Parse(ctx, args) + err := Backup(ctx, repo, []string{tmpBackupDir}) require.NoError(t, err) - require.Equal(t, []string{"ci", "nightly", "prod"}, subcommand.Tags) + locateopts := locate.NewDefaultLocateOptions(locate.WithLatest(true)) + + require.NoError(t, repo.RebuildState()) + snapshotIDs, err := locate.LocateSnapshotIDs(repo, locateopts) + require.NoError(t, err, "LocateSnapshotIDs failed") + require.Len(t, snapshotIDs, 1) - status, err := subcommand.Execute(ctx, repo) + snap, err := snapshot.Load(repo, snapshotIDs[0]) require.NoError(t, err) - require.Equal(t, 0, status) + require.Equal(t, snap.Header.Tags, []string{"ci", "nightly", "prod"}) } func TestBackupPlakarTagsDoubleComma(t *testing.T) { @@ -329,17 +330,20 @@ func TestBackupPlakarTagsDoubleComma(t *testing.T) { ctx.MaxConcurrency = 1 ctx.Stdout = bufOut ctx.Stderr = bufErr - args := []string{tmpBackupDir} - subcommand := &Backup{} - err := subcommand.Parse(ctx, args) + err := Backup(ctx, repo, []string{tmpBackupDir}) require.NoError(t, err) - require.Equal(t, []string{"ci", "nightly"}, subcommand.Tags) + locateopts := locate.NewDefaultLocateOptions(locate.WithLatest(true)) + + require.NoError(t, repo.RebuildState()) + snapshotIDs, err := locate.LocateSnapshotIDs(repo, locateopts) + require.NoError(t, err, "LocateSnapshotIDs failed") + require.Len(t, snapshotIDs, 1) - status, err := subcommand.Execute(ctx, repo) + snap, err := snapshot.Load(repo, snapshotIDs[0]) require.NoError(t, err) - require.Equal(t, 0, status) + require.Equal(t, snap.Header.Tags, []string{"ci", "nightly"}) } func TestBackupPlakarTagsTrailingComma(t *testing.T) { @@ -360,17 +364,20 @@ func TestBackupPlakarTagsTrailingComma(t *testing.T) { ctx.MaxConcurrency = 1 ctx.Stdout = bufOut ctx.Stderr = bufErr - args := []string{tmpBackupDir} - subcommand := &Backup{} - err := subcommand.Parse(ctx, args) + err := Backup(ctx, repo, []string{tmpBackupDir}) require.NoError(t, err) - require.Equal(t, []string{"ci", "nightly"}, subcommand.Tags) + locateopts := locate.NewDefaultLocateOptions(locate.WithLatest(true)) + + require.NoError(t, repo.RebuildState()) + snapshotIDs, err := locate.LocateSnapshotIDs(repo, locateopts) + require.NoError(t, err, "LocateSnapshotIDs failed") + require.Len(t, snapshotIDs, 1) - status, err := subcommand.Execute(ctx, repo) + snap, err := snapshot.Load(repo, snapshotIDs[0]) require.NoError(t, err) - require.Equal(t, 0, status) + require.Equal(t, snap.Header.Tags, []string{"ci", "nightly"}) } func TestExecuteCmdCreateDefaultWithIgnores(t *testing.T) { @@ -380,16 +387,9 @@ func TestExecuteCmdCreateDefaultWithIgnores(t *testing.T) { repo, tmpBackupDir, ctx := generateFixtures(t, bufOut, bufErr) ctx.MaxConcurrency = 1 - args := []string{"-ignore", "**/subdir", tmpBackupDir} - - subcommand := &Backup{} - err := subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - status, err := subcommand.Execute(ctx, repo) + err := Backup(ctx, repo, []string{"-ignore", "**/subdir", tmpBackupDir}) require.NoError(t, err) - require.Equal(t, 0, status) output := bufOut.String() require.NotContains(t, output, "/subdir") diff --git a/subcommands/cached/cached.go b/subcommands/cached/cached.go index 5e277ae4d..5d8369380 100644 --- a/subcommands/cached/cached.go +++ b/subcommands/cached/cached.go @@ -42,13 +42,10 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &Cached{} }, - subcommands.BeforeRepositoryOpen, "cached") + subcommands.Register(Cached, subcommands.BeforeRepositoryOpen, "cached") } -type Cached struct { - subcommands.SubcommandBase - +type Cmd struct { socketPath string listener net.Listener @@ -70,9 +67,19 @@ const ( jobDone = -1 ) -func (cmd *Cached) Parse(ctx *appcontext.AppContext, args []string) error { +func NewCmd(socketpath string, teardown time.Duration) *Cmd { + return &Cmd{ + socketPath: socketpath, + teardown: teardown, + jobQueue: make(map[uuid.UUID]chan jobReq), + runningJobs: make(chan int), + } +} + +func Cached(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { var opt_foreground bool var opt_logfile string + var opt_teardown time.Duration flags := flag.NewFlagSet("cached", flag.ExitOnError) flags.StringVar(&opt_logfile, "log", "", "log file") @@ -83,7 +90,7 @@ func (cmd *Cached) Parse(ctx *appcontext.AppContext, args []string) error { flags.PrintDefaults() } - flags.DurationVar(&cmd.teardown, "teardown", 5*time.Second, "delay before tearing down cached") + flags.DurationVar(&opt_teardown, "teardown", 5*time.Second, "delay before tearing down cached") flags.Parse(args) if flags.NArg() != 0 { return fmt.Errorf("too many arguments") @@ -108,16 +115,10 @@ func (cmd *Cached) Parse(ctx *appcontext.AppContext, args []string) error { } } - cmd.socketPath = filepath.Join(ctx.CacheDir, "cached.sock") - - cmd.jobMtx = sync.Mutex{} - cmd.jobQueue = make(map[uuid.UUID]chan jobReq) - cmd.runningJobs = make(chan int) - - return nil + return NewCmd(filepath.Join(ctx.CacheDir, "cached.sock"), opt_teardown).Run(ctx) } -func (cmd *Cached) Close() error { +func (cmd *Cmd) Close() error { if cmd.listener != nil { cmd.listener.Close() } @@ -127,13 +128,13 @@ func (cmd *Cached) Close() error { return nil } -func (cmd *Cached) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { +func (cmd *Cmd) Run(ctx *appcontext.AppContext) error { // Since we are detaching, we lose all stack traces, with no possibility // to recover them, try to log them to a known location. crashLog := filepath.Join(ctx.GetInner().CacheDir, "crash-cached.log") f, err := os.OpenFile(crashLog, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600) if err != nil { - return 1, err + return err } debug.SetCrashOutput(f, debug.CrashOptions{}) @@ -142,11 +143,11 @@ func (cmd *Cached) Execute(ctx *appcontext.AppContext, repo *repository.Reposito f.Close() if err := cmd.ListenAndServe(ctx); err != nil { - return 1, err + return err } ctx.GetLogger().Info("Server gracefully stopped") - return 0, nil + return nil } // Background task dealing with the teardown, basically anything running sends a @@ -157,7 +158,7 @@ func (cmd *Cached) Execute(ctx *appcontext.AppContext, repo *repository.Reposito // and is simpler. This is conceptually a waitgroup, except we can't use a // waitgroup as it has one special property (you can't reincrement the semaphore // while a Wait() is in progress) that our use case would transgress. -func (cmd *Cached) Watcher(listener net.Listener) { +func (cmd *Cmd) Watcher(listener net.Listener) { var inflight int for { @@ -172,7 +173,7 @@ func (cmd *Cached) Watcher(listener net.Listener) { } } -func (cmd *Cached) ListenAndServe(ctx *appcontext.AppContext) error { +func (cmd *Cmd) ListenAndServe(ctx *appcontext.AppContext) error { lock, err := cached.LockedFile(cmd.socketPath + ".cached-lock") if err != nil { return fmt.Errorf("failed to obtain lock") @@ -230,7 +231,7 @@ func (cmd *Cached) ListenAndServe(ctx *appcontext.AppContext) error { } -func (cmd *Cached) handleCachedClient(ctx *appcontext.AppContext, conn net.Conn) { +func (cmd *Cmd) handleCachedClient(ctx *appcontext.AppContext, conn net.Conn) { defer conn.Close() encoder := msgpack.NewEncoder(conn) @@ -307,7 +308,7 @@ func (cmd *Cached) handleCachedClient(ctx *appcontext.AppContext, conn net.Conn) } } -func (cmd *Cached) rebuildJob(ctx *appcontext.AppContext, jobChan chan jobReq, repoID uuid.UUID, secret []byte, storeConfig map[string]string) error { +func (cmd *Cmd) rebuildJob(ctx *appcontext.AppContext, jobChan chan jobReq, repoID uuid.UUID, secret []byte, storeConfig map[string]string) error { var serializedConfig []byte store, serializedConfig, err := storage.Open(ctx.GetInner(), storeConfig) if err != nil { diff --git a/subcommands/cat/cat.go b/subcommands/cat/cat.go index 7040158d4..9410ea040 100644 --- a/subcommands/cat/cat.go +++ b/subcommands/cat/cat.go @@ -33,10 +33,15 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &Cat{} }, 0, "cat") + subcommands.Register(Cat, 0, "cat") } -func (cmd *Cat) Parse(ctx *appcontext.AppContext, args []string) error { +func Cat(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + decompress bool + highlight bool + ) + flags := flag.NewFlagSet("cat", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s [OPTIONS] [SNAPSHOT[:PATH]]...\n", flags.Name()) @@ -44,31 +49,16 @@ func (cmd *Cat) Parse(ctx *appcontext.AppContext, args []string) error { flags.PrintDefaults() } - flags.BoolVar(&cmd.Decompress, "decompress", false, "decompress output") - flags.BoolVar(&cmd.Highlight, "highlight", false, "highlight output") + flags.BoolVar(&decompress, "decompress", false, "decompress output") + flags.BoolVar(&highlight, "highlight", false, "highlight output") flags.Parse(args) if flags.NArg() == 0 { return fmt.Errorf("at least one parameter is required") } - cmd.RepositorySecret = ctx.GetSecret() - cmd.Paths = flags.Args() - - return nil -} - -type Cat struct { - subcommands.SubcommandBase - - Decompress bool - Highlight bool - Paths []string -} - -func (cmd *Cat) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { errors := 0 - for _, snapPath := range cmd.Paths { + for _, snapPath := range flags.Args() { snap, pathname, err := locate.OpenSnapshotByPath(repo, snapPath) if err != nil { ctx.GetLogger().Error("cat: %s: %s", snapPath, err) @@ -109,12 +99,12 @@ func (cmd *Cat) Execute(ctx *appcontext.AppContext, repo *repository.Repository) file, err := entry.Open(fs) if err != nil { - return 1, err + return err } var rd io.ReadCloser = file - if cmd.Decompress && entry.ResolvedObject.ContentType == "application/gzip" { + if decompress && entry.ResolvedObject.ContentType == "application/gzip" { gzRd, err := gzip.NewReader(rd) if err != nil { ctx.GetLogger().Error("cat: %s: %s", pathname, err) @@ -126,7 +116,7 @@ func (cmd *Cat) Execute(ctx *appcontext.AppContext, repo *repository.Repository) rd = gzRd } - if cmd.Highlight { + if highlight { lexer := lexers.Match(pathname) if lexer == nil { lexer = lexers.Get(entry.ResolvedObject.ContentType) @@ -186,7 +176,7 @@ func (cmd *Cat) Execute(ctx *appcontext.AppContext, repo *repository.Repository) } if errors != 0 { - return 1, fmt.Errorf("errors occurred") + return fmt.Errorf("errors occurred") } - return 0, nil + return nil } diff --git a/subcommands/check/check.go b/subcommands/check/check.go index 3a432f687..a1637ea87 100644 --- a/subcommands/check/check.go +++ b/subcommands/check/check.go @@ -30,21 +30,17 @@ import ( "github.com/google/uuid" ) -type Check struct { - subcommands.SubcommandBase - - LocateOptions *locate.LocateOptions - FastCheck bool - NoVerify bool - Snapshots []string -} - func init() { - subcommands.Register(func() subcommands.Subcommand { return &Check{} }, 0, "check") + subcommands.Register(Check, 0, "check") } -func (cmd *Check) Parse(ctx *appcontext.AppContext, args []string) error { - cmd.LocateOptions = locate.NewDefaultLocateOptions() +func Check(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + LocateOptions = locate.NewDefaultLocateOptions() + FastCheck bool + NoVerify bool + Snapshots []string + ) flags := flag.NewFlagSet("check", flag.ExitOnError) flags.Usage = func() { @@ -53,46 +49,38 @@ func (cmd *Check) Parse(ctx *appcontext.AppContext, args []string) error { flags.PrintDefaults() } - flags.BoolVar(&cmd.NoVerify, "no-verify", false, "disable signature verification") - flags.BoolVar(&cmd.FastCheck, "fast", false, "enable fast checking (no digest verification)") - cmd.LocateOptions.InstallLocateFlags(flags) + flags.BoolVar(&NoVerify, "no-verify", false, "disable signature verification") + flags.BoolVar(&FastCheck, "fast", false, "enable fast checking (no digest verification)") + LocateOptions.InstallLocateFlags(flags) flags.Parse(args) - if flags.NArg() != 0 && !cmd.LocateOptions.Empty() { + if flags.NArg() != 0 && !LocateOptions.Empty() { ctx.GetLogger().Warn("snapshot specified, filters will be ignored") } - cmd.RepositorySecret = ctx.GetSecret() - cmd.Snapshots = flags.Args() - - return nil -} - -func (cmd *Check) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { var snapshots []string - if len(cmd.Snapshots) == 0 { - snapshotIDs, err := locate.LocateSnapshotIDs(repo, cmd.LocateOptions) + if len(Snapshots) == 0 { + snapshotIDs, err := locate.LocateSnapshotIDs(repo, LocateOptions) if err != nil { - return 1, err + return err } for _, snapshotID := range snapshotIDs { snapshots = append(snapshots, fmt.Sprintf("%x:", snapshotID)) } } else { - for _, snapshotPath := range cmd.Snapshots { + for _, snapshotPath := range Snapshots { prefix, path := locate.ParseSnapshotPath(snapshotPath) if prefix != "" { if _, err := hex.DecodeString(prefix); err != nil { - return 1, fmt.Errorf("invalid snapshot prefix: %s", prefix) + return fmt.Errorf("invalid snapshot prefix: %s", prefix) } } - cmd.LocateOptions.Filters.IDs = []string{prefix} - snapshotIDs, err := locate.LocateSnapshotIDs(repo, cmd.LocateOptions) + LocateOptions.Filters.IDs = []string{prefix} + snapshotIDs, err := locate.LocateSnapshotIDs(repo, LocateOptions) if err != nil { - fmt.Fprintln(ctx.Stderr, err) - return 1, err + return err } for _, snapshotID := range snapshotIDs { @@ -102,12 +90,12 @@ func (cmd *Check) Execute(ctx *appcontext.AppContext, repo *repository.Repositor } opts := &snapshot.CheckOptions{ - FastCheck: cmd.FastCheck, + FastCheck: FastCheck, } checkCache, err := ctx.GetCache().Check() if err != nil { - return 1, err + return err } defer checkCache.Close() @@ -118,13 +106,13 @@ func (cmd *Check) Execute(ctx *appcontext.AppContext, repo *repository.Repositor for _, arg := range snapshots { snap, pathname, err := locate.OpenSnapshotByPath(repo, arg) if err != nil { - return 1, err + return err } snap.SetCheckCache(checkCache) var failed bool - if !cmd.NoVerify && snap.Header.Identity.Identifier != uuid.Nil { + if !NoVerify && snap.Header.Identity.Identifier != uuid.Nil { if ok, err := snap.Verify(); err != nil { ctx.GetLogger().Warn("%s", err) } else if !ok { @@ -151,9 +139,9 @@ func (cmd *Check) Execute(ctx *appcontext.AppContext, repo *repository.Repositor if failures == 1 { snapshots = "snapshot" } - return exitcodes.IntegrityFailure, fmt.Errorf("check failed for %d %s", + return subcommands.NewErrCode(exitcodes.IntegrityFailure, "check failed for %d %s", failures, snapshots) } - return 0, nil + return nil } diff --git a/subcommands/config/config.go b/subcommands/config/config.go index 0e1c1567b..0890b1c1c 100644 --- a/subcommands/config/config.go +++ b/subcommands/config/config.go @@ -37,14 +37,10 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &ConfigStoreCmd{} }, - subcommands.BeforeRepositoryOpen, "store") - subcommands.Register(func() subcommands.Subcommand { return &ConfigSourceCmd{} }, - subcommands.BeforeRepositoryOpen, "source") - subcommands.Register(func() subcommands.Subcommand { return &ConfigDestinationCmd{} }, - subcommands.BeforeRepositoryOpen, "destination") - subcommands.Register(func() subcommands.Subcommand { return &ConfigPolicyCmd{} }, - subcommands.BeforeRepositoryOpen, "policy") + subcommands.Register(ConfigStore, subcommands.BeforeRepositoryOpen, "store") + subcommands.Register(ConfigSource, subcommands.BeforeRepositoryOpen, "source") + subcommands.Register(ConfigDestination, subcommands.BeforeRepositoryOpen, "destination") + subcommands.Register(ConfigPolicy, subcommands.BeforeRepositoryOpen, "policy") } func normalizeName(name string) string { diff --git a/subcommands/config/config_extra_test.go b/subcommands/config/config_extra_test.go index 931bdf5b4..9bbc1dac5 100644 --- a/subcommands/config/config_extra_test.go +++ b/subcommands/config/config_extra_test.go @@ -6,7 +6,6 @@ import ( "path/filepath" "testing" - "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" "github.com/PlakarKorp/plakar/subcommands" "github.com/PlakarKorp/plakar/utils" @@ -16,19 +15,10 @@ import ( func TestConfigRegisteredFactories(t *testing.T) { // Look each command up through the registry to invoke the factory closures // registered in init(). - cases := []struct { - name string - typ interface{} - }{ - {"store", &ConfigStoreCmd{}}, - {"source", &ConfigSourceCmd{}}, - {"destination", &ConfigDestinationCmd{}}, - {"policy", &ConfigPolicyCmd{}}, - } - for _, c := range cases { - cmd, _, _ := subcommands.Lookup([]string{c.name}) - require.NotNil(t, cmd, "command %q not registered", c.name) - require.IsType(t, c.typ, cmd) + for _, name := range []string{"store", "source", "destination", "policy"} { + cmd, flags, _, _ := subcommands.Lookup([]string{name}) + require.NotNil(t, cmd, "command %q not registered", name) + require.Equal(t, subcommands.BeforeRepositoryOpen, flags) } } @@ -80,40 +70,26 @@ func TestDispatchUnknownCmd(t *testing.T) { func TestEntityParseNoAction(t *testing.T) { ctx, _, _ := newConfigCtx(t) - require.Error(t, (&ConfigStoreCmd{}).Parse(ctx, []string{})) - require.Error(t, (&ConfigSourceCmd{}).Parse(ctx, []string{})) - require.Error(t, (&ConfigDestinationCmd{}).Parse(ctx, []string{})) - require.Error(t, (&ConfigPolicyCmd{}).Parse(ctx, []string{})) + require.Error(t, ConfigStore(ctx, nil, []string{})) + require.Error(t, ConfigSource(ctx, nil, []string{})) + require.Error(t, ConfigDestination(ctx, nil, []string{})) + require.Error(t, ConfigPolicy(ctx, nil, []string{})) } func TestDestinationParseExecute(t *testing.T) { ctx, _, _ := newConfigCtx(t) - repo := &repository.Repository{} - cmd := &ConfigDestinationCmd{} - require.NoError(t, cmd.Parse(ctx, []string{"add", "mydest", "fs:/tmp/dst"})) - status, err := cmd.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + require.NoError(t, ConfigDestination(ctx, nil, []string{"add", "mydest", "fs:/tmp/dst"})) require.True(t, ctx.Config.HasDestination("mydest")) // A failing dispatch (rm of unknown) propagates status 1. - cmd = &ConfigDestinationCmd{} - require.NoError(t, cmd.Parse(ctx, []string{"rm", "ghost"})) - status, err = cmd.Execute(ctx, repo) - require.Error(t, err) - require.Equal(t, 1, status) + require.Error(t, ConfigDestination(ctx, nil, []string{"rm", "ghost"})) } func TestSourceParseExecute(t *testing.T) { ctx, _, _ := newConfigCtx(t) - repo := &repository.Repository{} - cmd := &ConfigSourceCmd{} - require.NoError(t, cmd.Parse(ctx, []string{"add", "mysrc", "fs:/tmp/src"})) - status, err := cmd.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + require.NoError(t, ConfigSource(ctx, nil, []string{"add", "mysrc", "fs:/tmp/src"})) require.True(t, ctx.Config.HasSource("mysrc")) } @@ -283,16 +259,11 @@ func TestDispatchPingUnknown(t *testing.T) { func TestPolicyParseExecute(t *testing.T) { ctx, _, _ := newConfigCtx(t) - repo := &repository.Repository{} - cmd := &ConfigPolicyCmd{} - require.NoError(t, cmd.Parse(ctx, []string{"add", "nightly"})) - status, err := cmd.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + require.NoError(t, ConfigPolicy(ctx, nil, []string{"add", "nightly"})) // policies.yml was written. - _, err = os.Stat(filepath.Join(ctx.ConfigDir, "policies.yml")) + _, err := os.Stat(filepath.Join(ctx.ConfigDir, "policies.yml")) require.NoError(t, err) } @@ -313,8 +284,8 @@ func TestDispatchPolicyLifecycle(t *testing.T) { // set require.Error(t, dispatchPolicy(ctx, "policy", "set", []string{"ghost", "k=v"})) - require.Error(t, dispatchPolicy(ctx, "policy", "set", []string{"daily"})) // too few - require.Error(t, dispatchPolicy(ctx, "policy", "set", []string{"daily", "bad"})) // malformed + require.Error(t, dispatchPolicy(ctx, "policy", "set", []string{"daily"})) // too few + require.Error(t, dispatchPolicy(ctx, "policy", "set", []string{"daily", "bad"})) // malformed // show (yaml + json), all + specific bufOut.Reset() @@ -323,7 +294,7 @@ func TestDispatchPolicyLifecycle(t *testing.T) { require.NoError(t, dispatchPolicy(ctx, "policy", "show", []string{"-json", "daily"})) // unset - require.Error(t, dispatchPolicy(ctx, "policy", "unset", []string{"daily"})) // too few + require.Error(t, dispatchPolicy(ctx, "policy", "unset", []string{"daily"})) // too few require.Error(t, dispatchPolicy(ctx, "policy", "unset", []string{"ghost", "tags"})) // rm diff --git a/subcommands/config/config_test.go b/subcommands/config/config_test.go index 7f6746704..e1914f83b 100644 --- a/subcommands/config/config_test.go +++ b/subcommands/config/config_test.go @@ -6,7 +6,6 @@ import ( "path/filepath" "testing" - "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" "github.com/PlakarKorp/plakar/utils" "github.com/stretchr/testify/require" @@ -45,22 +44,10 @@ func TestConfigEmpty(t *testing.T) { ctx.Config = cfg ctx.Stdout = bufOut ctx.Stderr = bufErr - repo := &repository.Repository{} - args := []string{} - subcommand := &ConfigStoreCmd{} - err = subcommand.Parse(ctx, args) - require.Error(t, err, "no action specified") + require.Error(t, ConfigStore(ctx, nil, []string{}), "no action specified") - subcommand = &ConfigStoreCmd{} - args = []string{"show"} - err = subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err := subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + require.NoError(t, ConfigStore(ctx, nil, []string{"show"})) output := bufOut.String() expectedOutput := "" @@ -69,25 +56,9 @@ func TestConfigEmpty(t *testing.T) { bufOut.Reset() bufErr.Reset() - args = []string{"add", "my-remote", "s3://foobar"} - subcommandr := &ConfigSourceCmd{} - err = subcommandr.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommandr) - - status, err = subcommandr.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + require.NoError(t, ConfigSource(ctx, nil, []string{"add", "my-remote", "s3://foobar"})) - args = []string{"add", "my-repo", "fs:/tmp/foobar"} - subcommandk := &ConfigStoreCmd{} - err = subcommandk.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommandk) - - status, err = subcommandk.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + require.NoError(t, ConfigStore(ctx, nil, []string{"add", "my-repo", "fs:/tmp/foobar"})) output = bufOut.String() expectedOutput = `` diff --git a/subcommands/config/destination.go b/subcommands/config/destination.go index 9fde8ee19..ee8f98a2e 100644 --- a/subcommands/config/destination.go +++ b/subcommands/config/destination.go @@ -6,16 +6,9 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type ConfigDestinationCmd struct { - subcommands.SubcommandBase - - args []string -} - -func (cmd *ConfigDestinationCmd) Parse(ctx *appcontext.AppContext, args []string) error { +func ConfigDestination(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("destination", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s\n", flags.Name()) @@ -34,14 +27,6 @@ func (cmd *ConfigDestinationCmd) Parse(ctx *appcontext.AppContext, args []string if flags.NArg() == 0 { return fmt.Errorf("no action specified") } - cmd.args = flags.Args() - return nil -} -func (cmd *ConfigDestinationCmd) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - err := dispatchSubcommand(ctx, "destination", cmd.args[0], cmd.args[1:]) - if err != nil { - return 1, err - } - return 0, nil + return dispatchSubcommand(ctx, "destination", flags.Args()[0], flags.Args()[1:]) } diff --git a/subcommands/config/policy.go b/subcommands/config/policy.go index 448270cbe..c49738055 100644 --- a/subcommands/config/policy.go +++ b/subcommands/config/policy.go @@ -8,17 +8,10 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" "github.com/PlakarKorp/plakar/utils" ) -type ConfigPolicyCmd struct { - subcommands.SubcommandBase - - args []string -} - -func (cmd *ConfigPolicyCmd) Parse(ctx *appcontext.AppContext, args []string) error { +func ConfigPolicy(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("policy", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s\n", flags.Name()) @@ -34,16 +27,8 @@ func (cmd *ConfigPolicyCmd) Parse(ctx *appcontext.AppContext, args []string) err if flags.NArg() == 0 { return fmt.Errorf("no action specified") } - cmd.args = flags.Args() - return nil -} -func (cmd *ConfigPolicyCmd) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - err := dispatchPolicy(ctx, "policy", cmd.args[0], cmd.args[1:]) - if err != nil { - return 1, err - } - return 0, nil + return dispatchPolicy(ctx, "policy", flags.Args()[0], flags.Args()[1:]) } func dispatchPolicy(ctx *appcontext.AppContext, cmd, subcmd string, args []string) error { diff --git a/subcommands/config/source.go b/subcommands/config/source.go index f100d9b0a..9e7ba3a91 100644 --- a/subcommands/config/source.go +++ b/subcommands/config/source.go @@ -6,16 +6,9 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type ConfigSourceCmd struct { - subcommands.SubcommandBase - - args []string -} - -func (cmd *ConfigSourceCmd) Parse(ctx *appcontext.AppContext, args []string) error { +func ConfigSource(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("source", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s\n", flags.Name()) @@ -34,14 +27,6 @@ func (cmd *ConfigSourceCmd) Parse(ctx *appcontext.AppContext, args []string) err if flags.NArg() == 0 { return fmt.Errorf("no action specified") } - cmd.args = flags.Args() - return nil -} -func (cmd *ConfigSourceCmd) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - err := dispatchSubcommand(ctx, "source", cmd.args[0], cmd.args[1:]) - if err != nil { - return 1, err - } - return 0, nil + return dispatchSubcommand(ctx, "source", flags.Args()[0], flags.Args()[1:]) } diff --git a/subcommands/config/store.go b/subcommands/config/store.go index 466acd41c..7d600195b 100644 --- a/subcommands/config/store.go +++ b/subcommands/config/store.go @@ -6,16 +6,9 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type ConfigStoreCmd struct { - subcommands.SubcommandBase - - args []string -} - -func (cmd *ConfigStoreCmd) Parse(ctx *appcontext.AppContext, args []string) error { +func ConfigStore(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("store", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s\n", flags.Name()) @@ -34,14 +27,6 @@ func (cmd *ConfigStoreCmd) Parse(ctx *appcontext.AppContext, args []string) erro if flags.NArg() == 0 { return fmt.Errorf("no action specified") } - cmd.args = flags.Args() - return nil -} -func (cmd *ConfigStoreCmd) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - err := dispatchSubcommand(ctx, "store", cmd.args[0], cmd.args[1:]) - if err != nil { - return 1, err - } - return 0, nil + return dispatchSubcommand(ctx, "store", flags.Args()[0], flags.Args()[1:]) } diff --git a/subcommands/create/create.go b/subcommands/create/create.go index 74323e580..9c19f44cc 100644 --- a/subcommands/create/create.go +++ b/subcommands/create/create.go @@ -25,11 +25,11 @@ import ( "strings" "github.com/PlakarKorp/kloset/compression" + "github.com/PlakarKorp/kloset/connectors/storage" "github.com/PlakarKorp/kloset/encryption" "github.com/PlakarKorp/kloset/hashing" "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/kloset/resources" - "github.com/PlakarKorp/kloset/connectors/storage" "github.com/PlakarKorp/kloset/versioning" "github.com/PlakarKorp/plakar/appcontext" "github.com/PlakarKorp/plakar/subcommands" @@ -37,11 +37,17 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &Create{} }, subcommands.BeforeRepositoryWithStorage, "create") + subcommands.Register(Create, subcommands.BeforeRepositoryWithStorage, "create") } -func (cmd *Create) Parse(ctx *appcontext.AppContext, args []string) error { - var allow_weak bool +func Create(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + allow_weak bool + hashalgo string + plaintext bool + nocompress bool + passphrase []byte + ) flags := flag.NewFlagSet("create", flag.ExitOnError) flags.Usage = func() { @@ -52,16 +58,16 @@ func (cmd *Create) Parse(ctx *appcontext.AppContext, args []string) error { } flags.BoolVar(&allow_weak, "weak-passphrase", false, "allow weak passphrase to protect the repository") - flags.StringVar(&cmd.Hashing, "hashing", hashing.DEFAULT_HASHING_ALGORITHM, "hashing algorithm to use for digests") - flags.BoolVar(&cmd.NoEncryption, "plaintext", false, "disable transparent encryption") - flags.BoolVar(&cmd.NoCompression, "no-compression", false, "disable transparent compression") + flags.StringVar(&hashalgo, "hashing", hashing.DEFAULT_HASHING_ALGORITHM, "hashing algorithm to use for digests") + flags.BoolVar(&plaintext, "plaintext", false, "disable transparent encryption") + flags.BoolVar(&nocompress, "no-compression", false, "disable transparent compression") flags.Parse(args) if flags.NArg() != 0 { return fmt.Errorf("%s: too many parameters", flag.CommandLine.Name()) } - if hashing.GetHasher(strings.ToUpper(cmd.Hashing)) == nil { + if hashing.GetHasher(strings.ToUpper(hashalgo)) == nil { return fmt.Errorf("%s: unknown hashing algorithm", flag.CommandLine.Name()) } @@ -70,62 +76,50 @@ func (cmd *Create) Parse(ctx *appcontext.AppContext, args []string) error { minEntropBits = 0. } - if !cmd.NoEncryption { - var passphrase []byte + if !plaintext { + var pass []byte if ctx.KeyFromFile == "" { tmp, err := utils.GetPassphraseConfirm("repository", minEntropBits, 3) if err != nil { return err } - passphrase = tmp + pass = tmp } else { - passphrase = []byte(ctx.KeyFromFile) + pass = []byte(ctx.KeyFromFile) } - if len(passphrase) == 0 { + if len(pass) == 0 { return fmt.Errorf("can't encrypt the repository with an empty passphrase") } - cmd.RepositorySecret = passphrase + passphrase = pass } - return nil -} - -type Create struct { - subcommands.SubcommandBase - - Hashing string - NoEncryption bool - NoCompression bool -} - -func (cmd *Create) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { storageConfiguration := storage.NewConfiguration() - if cmd.NoCompression { + if nocompress { storageConfiguration.Compression = nil } else { storageConfiguration.Compression = compression.NewDefaultConfiguration() } - hashingConfiguration, err := hashing.LookupDefaultConfiguration(strings.ToUpper(cmd.Hashing)) + hashingConfiguration, err := hashing.LookupDefaultConfiguration(strings.ToUpper(hashalgo)) if err != nil { - return 1, err + return err } storageConfiguration.Hashing = *hashingConfiguration var hasher hash.Hash - if !cmd.NoEncryption { + if !plaintext { key, err := encryption.DeriveKey(storageConfiguration.Encryption.KDFParams, - cmd.RepositorySecret) + passphrase) if err != nil { - return 1, err + return err } canary, err := encryption.DeriveCanary(storageConfiguration.Encryption, key) if err != nil { - return 1, err + return err } storageConfiguration.Encryption.Canary = canary hasher = hashing.GetMACHasher(storage.DEFAULT_HASHING_ALGORITHM, key) @@ -136,21 +130,21 @@ func (cmd *Create) Execute(ctx *appcontext.AppContext, repo *repository.Reposito serializedConfig, err := storageConfiguration.ToBytes() if err != nil { - return 1, err + return err } rd, err := storage.Serialize(hasher, resources.RT_CONFIG, versioning.GetCurrentVersion(resources.RT_CONFIG), bytes.NewReader(serializedConfig)) if err != nil { - return 1, err + return err } wrappedConfig, err := io.ReadAll(rd) if err != nil { - return 1, err + return err } if err := repo.Store().Create(ctx, wrappedConfig); err != nil { - return 1, err + return err } - return 0, nil + return nil } diff --git a/subcommands/diag/blob.go b/subcommands/diag/blob.go index 721066038..b3108b718 100644 --- a/subcommands/diag/blob.go +++ b/subcommands/diag/blob.go @@ -11,17 +11,9 @@ import ( "github.com/PlakarKorp/kloset/snapshot/header" "github.com/PlakarKorp/kloset/snapshot/vfs" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type DiagBlob struct { - subcommands.SubcommandBase - - blobtype string - mac string -} - -func (cmd *DiagBlob) Parse(ctx *appcontext.AppContext, args []string) error { +func Blob(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("diag blob", flag.ExitOnError) flags.Parse(args) @@ -29,40 +21,32 @@ func (cmd *DiagBlob) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("usage: %s blob type mac", flags.Name()) } - cmd.RepositorySecret = ctx.GetSecret() - cmd.blobtype = flags.Arg(0) - cmd.mac = flags.Arg(1) - - return nil -} - -func (cmd *DiagBlob) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - blobtype, err := resources.FromString(cmd.blobtype) + blobtype, err := resources.FromString(flags.Arg(0)) if err != nil { - return 1, fmt.Errorf("%w: %s", err, cmd.blobtype) + return fmt.Errorf("%w: %s", err, blobtype) } - macbytes, err := hex.DecodeString(cmd.mac) + macbytes, err := hex.DecodeString(flags.Arg(1)) if err != nil { - return 1, fmt.Errorf("%w: %s", err, cmd.mac) + return fmt.Errorf("%w: %s", err, flags.Arg(1)) } if len(macbytes) != 32 { - return 1, fmt.Errorf("invalid length for the mac: %s", cmd.mac) + return fmt.Errorf("invalid length for the mac: %s", flags.Arg(1)) } mac := objects.MAC(macbytes) buf, err := repo.GetBlobBytes(blobtype, mac) if err != nil { - return 1, fmt.Errorf("failed to open blob %s %x: %w", blobtype, mac, err) + return fmt.Errorf("failed to open blob %s %x: %w", blobtype, mac, err) } switch blobtype { case resources.RT_SNAPSHOT: hdr, err := header.NewFromBytes(buf) if err != nil { - return 1, fmt.Errorf("failed to deserialize %s %x: %w", + return fmt.Errorf("failed to deserialize %s %x: %w", blobtype, mac, err) } fmt.Fprintf(ctx.Stdout, "%+v\n", hdr) @@ -70,7 +54,7 @@ func (cmd *DiagBlob) Execute(ctx *appcontext.AppContext, repo *repository.Reposi case resources.RT_OBJECT: obj, err := objects.NewObjectFromBytes(buf) if err != nil { - return 1, fmt.Errorf("failed to deserialize %s %x: %w", + return fmt.Errorf("failed to deserialize %s %x: %w", blobtype, mac, err) } fmt.Fprintf(ctx.Stdout, "%+v\n", obj) @@ -78,7 +62,7 @@ func (cmd *DiagBlob) Execute(ctx *appcontext.AppContext, repo *repository.Reposi case resources.RT_CHUNK: chunk, err := objects.NewChunkFromBytes(buf) if err != nil { - return 1, fmt.Errorf("failed to deserialize %s %x: %w", + return fmt.Errorf("failed to deserialize %s %x: %w", blobtype, mac, err) } fmt.Fprintf(ctx.Stdout, "%+v\n", chunk) @@ -86,7 +70,7 @@ func (cmd *DiagBlob) Execute(ctx *appcontext.AppContext, repo *repository.Reposi case resources.RT_VFS_ENTRY: entry, err := vfs.EntryFromBytes(buf) if err != nil { - return 1, fmt.Errorf("failed to deserialize %s %x: %w", + return fmt.Errorf("failed to deserialize %s %x: %w", blobtype, mac, err) } fmt.Fprintf(ctx.Stdout, "%+v\n", entry) @@ -94,7 +78,7 @@ func (cmd *DiagBlob) Execute(ctx *appcontext.AppContext, repo *repository.Reposi case resources.RT_ERROR_ENTRY: error, err := vfs.ErrorItemFromBytes(buf) if err != nil { - return 1, fmt.Errorf("failed to deserialize %s %x: %w", + return fmt.Errorf("failed to deserialize %s %x: %w", blobtype, mac, err) } fmt.Fprintf(ctx.Stdout, "%+v\n", error) @@ -102,14 +86,14 @@ func (cmd *DiagBlob) Execute(ctx *appcontext.AppContext, repo *repository.Reposi case resources.RT_XATTR_ENTRY: xattr, err := vfs.XattrFromBytes(buf) if err != nil { - return 1, fmt.Errorf("failed to deserialize %s %x: %w", + return fmt.Errorf("failed to deserialize %s %x: %w", blobtype, mac, err) } fmt.Fprintf(ctx.Stdout, "%+v\n", xattr) default: - return 1, fmt.Errorf("don't know how to deserialize %s", blobtype) + return fmt.Errorf("don't know how to deserialize %s", blobtype) } - return 0, nil + return nil } diff --git a/subcommands/diag/blob_search.go b/subcommands/diag/blob_search.go index d69fbe5ae..51d814ac6 100644 --- a/subcommands/diag/blob_search.go +++ b/subcommands/diag/blob_search.go @@ -11,16 +11,9 @@ import ( "github.com/PlakarKorp/kloset/repository/state" "github.com/PlakarKorp/kloset/resources" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type DiagBlobSearch struct { - subcommands.SubcommandBase - - ObjectID string -} - -func (cmd *DiagBlobSearch) Parse(ctx *appcontext.AppContext, args []string) error { +func BlobSearch(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("diag packfile", flag.ExitOnError) flags.Parse(args) @@ -28,35 +21,30 @@ func (cmd *DiagBlobSearch) Parse(ctx *appcontext.AppContext, args []string) erro return fmt.Errorf("usage: %s blobsearch OBJECT", flags.Name()) } - cmd.RepositorySecret = ctx.GetSecret() - cmd.ObjectID = flags.Args()[0] - - return nil -} + objectID := flags.Args()[0] -func (cmd *DiagBlobSearch) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { fmt.Fprintf(ctx.Stdout, "Warning this command is slow and expensive. Use with caution.\n") - if len(cmd.ObjectID) != 64 { - return 1, fmt.Errorf("invalid object hash: %s", cmd.ObjectID) + if len(objectID) != 64 { + return fmt.Errorf("invalid object hash: %s", objectID) } - b, err := hex.DecodeString(cmd.ObjectID) + b, err := hex.DecodeString(objectID) if err != nil { - return 1, fmt.Errorf("invalid object hash: %s", cmd.ObjectID) + return fmt.Errorf("invalid object hash: %s", objectID) } needleMAC := objects.MAC(b) packfiles, err := repo.GetPackfiles() if err != nil { - return 1, err + return err } for _, packfileMac := range packfiles { p, err := repo.GetPackfile(packfileMac) if err != nil { - return 1, err + return err } for _, entry := range p.Index { @@ -65,26 +53,25 @@ func (cmd *DiagBlobSearch) Execute(ctx *appcontext.AppContext, repo *repository. if entry.Type == resources.RT_OBJECT { rd, err := repo.GetPackfileBlob(state.Location{Packfile: packfileMac, Offset: entry.Offset, Length: entry.Length}) if err != nil { - return 1, err + return err } blob, err := io.ReadAll(rd) if err != nil { - return 1, err + return err } object, err := objects.NewObjectFromBytes(blob) if err != nil { - return 1, err + return err } fmt.Fprintf(ctx.Stdout, "object: %x\n", object.ContentMAC) fmt.Fprintln(ctx.Stdout, " type:", object.ContentType) } } - } } - return 0, nil + return nil } diff --git a/subcommands/diag/chunks.go b/subcommands/diag/chunks.go index d2fb28604..965aec10a 100644 --- a/subcommands/diag/chunks.go +++ b/subcommands/diag/chunks.go @@ -7,16 +7,9 @@ import ( "github.com/PlakarKorp/kloset/locate" "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type DiagChunks struct { - subcommands.SubcommandBase - - SnapshotPath string -} - -func (cmd *DiagChunks) Parse(ctx *appcontext.AppContext, args []string) error { +func Chunks(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("diag chunks", flag.ExitOnError) flags.Parse(args) @@ -24,31 +17,24 @@ func (cmd *DiagChunks) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("usage: %s chunks SNAPSHOT:PATH", flags.Name()) } - cmd.RepositorySecret = ctx.GetSecret() - cmd.SnapshotPath = flags.Args()[0] - - return nil -} - -func (cmd *DiagChunks) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - snap, pathname, err := locate.OpenSnapshotByPath(repo, cmd.SnapshotPath) + snap, pathname, err := locate.OpenSnapshotByPath(repo, flags.Args()[0]) if err != nil { - return 1, err + return err } defer snap.Close() fs, err := snap.Filesystem() if err != nil { - return 1, err + return err } entry, err := fs.GetEntry(pathname) if err != nil { - return 1, err + return err } if entry.ResolvedObject == nil { - return 1, fmt.Errorf("no object for path: %s", pathname) + return fmt.Errorf("no object for path: %s", pathname) } var offset int64 @@ -58,5 +44,5 @@ func (cmd *DiagChunks) Execute(ctx *appcontext.AppContext, repo *repository.Repo offset += int64(chunk.Length) } - return 0, nil + return nil } diff --git a/subcommands/diag/contenttype.go b/subcommands/diag/contenttype.go index 8def9447b..188ad6fbb 100644 --- a/subcommands/diag/contenttype.go +++ b/subcommands/diag/contenttype.go @@ -8,16 +8,9 @@ import ( "github.com/PlakarKorp/kloset/locate" "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type DiagContentType struct { - subcommands.SubcommandBase - - SnapshotPath string -} - -func (cmd *DiagContentType) Parse(ctx *appcontext.AppContext, args []string) error { +func ContentType(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("diag contenttype", flag.ExitOnError) flags.Parse(args) @@ -25,16 +18,9 @@ func (cmd *DiagContentType) Parse(ctx *appcontext.AppContext, args []string) err return fmt.Errorf("usage: %s contenttype SNAPSHOT[:PATH]", flags.Name()) } - cmd.RepositorySecret = ctx.GetSecret() - cmd.SnapshotPath = flags.Args()[0] - - return nil -} - -func (cmd *DiagContentType) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - snap, pathname, err := locate.OpenSnapshotByPath(repo, cmd.SnapshotPath) + snap, pathname, err := locate.OpenSnapshotByPath(repo, flags.Args()[0]) if err != nil { - return 1, err + return err } defer snap.Close() @@ -47,15 +33,15 @@ func (cmd *DiagContentType) Execute(ctx *appcontext.AppContext, repo *repository tree, err := snap.ContentTypeIdx() if err != nil { - return 1, err + return err } if tree == nil { - return 1, fmt.Errorf("no content-type index available in the snapshot") + return fmt.Errorf("no content-type index available in the snapshot") } it, err := tree.ScanFrom(pathname) if err != nil { - return 1, err + return err } for it.Next() { @@ -67,8 +53,8 @@ func (cmd *DiagContentType) Execute(ctx *appcontext.AppContext, repo *repository fmt.Fprintln(ctx.Stdout, path) } if err := it.Err(); err != nil { - return 1, err + return err } - return 0, nil + return nil } diff --git a/subcommands/diag/diag.go b/subcommands/diag/diag.go index 8e435d8d1..0b5da6649 100644 --- a/subcommands/diag/diag.go +++ b/subcommands/diag/diag.go @@ -21,18 +21,18 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &DiagSnapshot{} }, 0, "diag", "snapshot") - subcommands.Register(func() subcommands.Subcommand { return &DiagBlobSearch{} }, 0, "diag", "blobsearch") - subcommands.Register(func() subcommands.Subcommand { return &DiagState{} }, 0, "diag", "state") - subcommands.Register(func() subcommands.Subcommand { return &DiagPackfile{} }, 0, "diag", "packfile") - subcommands.Register(func() subcommands.Subcommand { return &DiagObject{} }, 0, "diag", "object") - subcommands.Register(func() subcommands.Subcommand { return &DiagVFS{} }, 0, "diag", "vfs") - subcommands.Register(func() subcommands.Subcommand { return &DiagXattr{} }, 0, "diag", "xattr") - subcommands.Register(func() subcommands.Subcommand { return &DiagContentType{} }, 0, "diag", "contenttype") - subcommands.Register(func() subcommands.Subcommand { return &DiagLocks{} }, 0, "diag", "locks") - subcommands.Register(func() subcommands.Subcommand { return &DiagSearch{} }, 0, "diag", "search") - subcommands.Register(func() subcommands.Subcommand { return &DiagDirPack{} }, 0, "diag", "dirpack") - subcommands.Register(func() subcommands.Subcommand { return &DiagBlob{} }, 0, "diag", "blob") - subcommands.Register(func() subcommands.Subcommand { return &DiagChunks{} }, 0, "diag", "chunks") - subcommands.Register(func() subcommands.Subcommand { return &DiagRepository{} }, 0, "diag") + subcommands.Register(Snapshot, 0, "diag", "snapshot") + subcommands.Register(BlobSearch, 0, "diag", "blobsearch") + subcommands.Register(State, 0, "diag", "state") + subcommands.Register(Packfile, 0, "diag", "packfile") + subcommands.Register(Object, 0, "diag", "object") + subcommands.Register(VFS, 0, "diag", "vfs") + subcommands.Register(Xattr, 0, "diag", "xattr") + subcommands.Register(ContentType, 0, "diag", "contenttype") + subcommands.Register(Locks, 0, "diag", "locks") + subcommands.Register(Search, 0, "diag", "search") + subcommands.Register(DirPack, 0, "diag", "dirpack") + subcommands.Register(Blob, 0, "diag", "blob") + subcommands.Register(Chunks, 0, "diag", "chunks") + subcommands.Register(Repository, 0, "diag") } diff --git a/subcommands/diag/diag_test.go b/subcommands/diag/diag_test.go index e503093a1..270db1211 100644 --- a/subcommands/diag/diag_test.go +++ b/subcommands/diag/diag_test.go @@ -44,14 +44,8 @@ func TestExecuteCmdDiagSnapshot(t *testing.T) { indexId := snap.Header.GetIndexID() args := []string{"diag", "snapshot", fmt.Sprintf("%s", hex.EncodeToString(indexId[:]))} - subcommand, _, args := subcommands.Lookup(args) - err := subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err := subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + subcommand, _, _, args := subcommands.Lookup(args) + require.NoError(t, subcommand(ctx, repo, args)) // output should look like this // Version: 1.0.0 @@ -124,14 +118,8 @@ func TestExecuteCmdDiagState(t *testing.T) { indexId := snap.Header.GetIndexID() args := []string{"diag", "state"} - subcommand, _, args := subcommands.Lookup(args) - err := subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err := subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + subcommand, _, _, args := subcommands.Lookup(args) + require.NoError(t, subcommand(ctx, repo, args)) // output should look like this // aaf4c5b7b91ba00f5afde31c5a9f721bc78de202a491379339e011b9172db298 @@ -142,15 +130,8 @@ func TestExecuteCmdDiagState(t *testing.T) { bufOut.Reset() args = []string{"diag", "state", strings.Trim(output, "\n")} - subcommand, _, args = subcommands.Lookup(args) - err = subcommand.Parse(ctx, args) - - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err = subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + subcommand, _, _, args = subcommands.Lookup(args) + require.NoError(t, subcommand(ctx, repo, args)) // output should look like this // Version: 655.3.6 @@ -185,15 +166,9 @@ func TestExecuteCmdDiagPackfile(t *testing.T) { indexId := snap.Header.GetIndexID() args := []string{"diag", "state"} - subcommand, _, args := subcommands.Lookup(args) - err := subcommand.Parse(ctx, args) + subcommand, _, _, args := subcommands.Lookup(args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err := subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + require.NoError(t, subcommand(ctx, repo, args)) // output should look like this // aaf4c5b7b91ba00f5afde31c5a9f721bc78de202a491379339e011b9172db298 @@ -204,14 +179,8 @@ func TestExecuteCmdDiagPackfile(t *testing.T) { bufOut.Reset() args = []string{"diag", "state", strings.Trim(output, "\n")} - subcommand, _, args = subcommands.Lookup(args) - err = subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err = subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + subcommand, _, _, args = subcommands.Lookup(args) + require.NoError(t, subcommand(ctx, repo, args)) // output should look like this // Version: 655.3.6 @@ -253,14 +222,8 @@ func TestExecuteCmdDiagPackfile(t *testing.T) { bufOut.Reset() args = []string{"diag", "packfile", hex.EncodeToString(partPackfile)} - subcommand, _, args = subcommands.Lookup(args) - err = subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err = subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + subcommand, _, _, args = subcommands.Lookup(args) + require.NoError(t, subcommand(ctx, repo, args)) // output should look like this // Version: 1.0.0 @@ -285,14 +248,8 @@ func TestExecuteCmdDiagObject(t *testing.T) { indexId := snap.Header.GetIndexID() args := []string{"diag", "state"} - subcommand, _, args := subcommands.Lookup(args) - err := subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err := subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + subcommand, _, _, args := subcommands.Lookup(args) + require.NoError(t, subcommand(ctx, repo, args)) // output should look like this // aaf4c5b7b91ba00f5afde31c5a9f721bc78de202a491379339e011b9172db298 @@ -303,14 +260,8 @@ func TestExecuteCmdDiagObject(t *testing.T) { bufOut.Reset() args = []string{"diag", "state", strings.Trim(output, "\n")} - subcommand, _, args = subcommands.Lookup(args) - err = subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err = subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + subcommand, _, _, args = subcommands.Lookup(args) + require.NoError(t, subcommand(ctx, repo, args)) // output should look like this // Version: 655.3.6 @@ -350,14 +301,8 @@ func TestExecuteCmdDiagObject(t *testing.T) { bufOut.Reset() args = []string{"diag", "object", hex.EncodeToString(partObject)} - subcommand, _, args = subcommands.Lookup(args) - err = subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err = subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + subcommand, _, _, args = subcommands.Lookup(args) + require.NoError(t, subcommand(ctx, repo, args)) // output should look like this // object: 096d53564d0216066f0d2aa6b0f6fc159e5e78271e49f5bee0676ed5f229741e @@ -384,14 +329,8 @@ func TestExecuteCmdDiagVFS(t *testing.T) { indexId := snap.Header.GetIndexID() args := []string{"diag", "vfs", fmt.Sprintf("%s:subdir/dummy.txt", hex.EncodeToString(indexId[:]))} - subcommand, _, args := subcommands.Lookup(args) - err := subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err := subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + subcommand, _, _, args := subcommands.Lookup(args) + require.NoError(t, subcommand(ctx, repo, args)) // output should look like this // [FileEntry] @@ -423,14 +362,8 @@ func TestExecuteCmdDiagVFS(t *testing.T) { bufOut.Reset() args = []string{"diag", "vfs", fmt.Sprintf("%s:/subdir", hex.EncodeToString(indexId[:]))} - subcommand, _, args = subcommands.Lookup(args) - err = subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err = subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + subcommand, _, _, args = subcommands.Lookup(args) + require.NoError(t, subcommand(ctx, repo, args)) // output should look like this // [DirEntry] @@ -560,14 +493,8 @@ func TestExecuteCmdDiagXattr(t *testing.T) { indexId := snap.Header.GetIndexID() args := []string{"diag", "xattr", fmt.Sprintf("%s:subdir/dummy.txt", hex.EncodeToString(indexId[:]))} - subcommand, _, args := subcommands.Lookup(args) - err := subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err := subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + subcommand, _, _, args := subcommands.Lookup(args) + require.NoError(t, subcommand(ctx, repo, args)) // output should look like this @@ -585,14 +512,8 @@ func TestExecuteCmdDiagContentType(t *testing.T) { indexId := snap.Header.GetIndexID() args := []string{"diag", "contenttype", fmt.Sprintf("%s:subdir/dummy.txt", hex.EncodeToString(indexId[:]))} - subcommand, _, args := subcommands.Lookup(args) - err := subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err := subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + subcommand, _, _, args := subcommands.Lookup(args) + require.NoError(t, subcommand(ctx, repo, args)) // output should look like this @@ -609,14 +530,8 @@ func TestExecuteCmdDiagLocks(t *testing.T) { args := []string{"diag", "locks"} - subcommand, _, args := subcommands.Lookup(args) - err := subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err := subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + subcommand, _, _, args := subcommands.Lookup(args) + require.NoError(t, subcommand(ctx, repo, args)) // output should look like this @@ -634,14 +549,8 @@ func TestExecuteCmdDiagSearch(t *testing.T) { indexId := snap.Header.GetIndexID() args := []string{"diag", "search", fmt.Sprintf("%s:subdir/", hex.EncodeToString(indexId[:]))} - subcommand, _, args := subcommands.Lookup(args) - err := subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err := subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + subcommand, _, _, args := subcommands.Lookup(args) + require.NoError(t, subcommand(ctx, repo, args)) // output should look like this // f3b3c31e:/tmp/tmp_to_backup3206526426/another_subdir/bar diff --git a/subcommands/diag/dirpack.go b/subcommands/diag/dirpack.go index 04c56fcd4..c2a50ad18 100644 --- a/subcommands/diag/dirpack.go +++ b/subcommands/diag/dirpack.go @@ -13,17 +13,10 @@ import ( "github.com/PlakarKorp/kloset/snapshot" "github.com/PlakarKorp/kloset/snapshot/vfs" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" "github.com/vmihailenco/msgpack/v5" ) -type DiagDirPack struct { - subcommands.SubcommandBase - - SnapshotPath string -} - -func (cmd *DiagDirPack) Parse(ctx *appcontext.AppContext, args []string) error { +func DirPack(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("diag dirpack", flag.ExitOnError) flags.Parse(args) @@ -31,27 +24,9 @@ func (cmd *DiagDirPack) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("usage: %s dirpack SNAPSHOT[:PATH]", flags.Name()) } - cmd.RepositorySecret = ctx.GetSecret() - cmd.SnapshotPath = flags.Args()[0] - - return nil -} - -func readDirPackHdr(rd io.Reader) (typ snapshot.DirPackEntry, siz uint32, err error) { - endian := binary.LittleEndian - if err = binary.Read(rd, endian, &typ); err != nil { - return - } - if err = binary.Read(rd, endian, &siz); err != nil { - return - } - return -} - -func (cmd *DiagDirPack) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - snap, pathname, err := locate.OpenSnapshotByPath(repo, cmd.SnapshotPath) + snap, pathname, err := locate.OpenSnapshotByPath(repo, flags.Args()[0]) if err != nil { - return 1, err + return err } defer snap.Close() @@ -64,15 +39,15 @@ func (cmd *DiagDirPack) Execute(ctx *appcontext.AppContext, repo *repository.Rep tree, err := snap.DirPack() if err != nil { - return 1, err + return err } if tree == nil { - return 1, fmt.Errorf("no dirpack index available in the snapshot") + return fmt.Errorf("no dirpack index available in the snapshot") } it, err := tree.ScanFrom(pathname) if err != nil { - return 1, err + return err } for it.Next() { @@ -86,7 +61,7 @@ func (cmd *DiagDirPack) Execute(ctx *appcontext.AppContext, repo *repository.Rep obj, err := snap.LookupObject(dirpackmac) if err != nil { - return 1, fmt.Errorf("failed to get object %x: %w", dirpackmac, err) + return fmt.Errorf("failed to get object %x: %w", dirpackmac, err) } var size int64 @@ -102,18 +77,18 @@ func (cmd *DiagDirPack) Execute(ctx *appcontext.AppContext, repo *repository.Rep if errors.Is(err, io.EOF) { break } - return 1, fmt.Errorf("failed to read: %w", err) + return fmt.Errorf("failed to read: %w", err) } var entry vfs.Entry lrd := io.LimitReader(rd, int64(siz-uint32(len(entry.MAC[:])))) err = msgpack.NewDecoder(lrd).Decode(&entry) if err != nil { - return 1, fmt.Errorf("failed to read entry: %w", err) + return fmt.Errorf("failed to read entry: %w", err) } if _, err := io.ReadFull(rd, entry.MAC[:]); err != nil { - return 1, fmt.Errorf("failed to read entry mac: %w", err) + return fmt.Errorf("failed to read entry mac: %w", err) } fmt.Fprintf(ctx.Stdout, "vfs-entry %x %s %v %v %s\n", entry.MAC, path, @@ -121,8 +96,19 @@ func (cmd *DiagDirPack) Execute(ctx *appcontext.AppContext, repo *repository.Rep } } if err := it.Err(); err != nil { - return 1, err + return err } - return 0, nil + return nil +} + +func readDirPackHdr(rd io.Reader) (typ snapshot.DirPackEntry, siz uint32, err error) { + endian := binary.LittleEndian + if err = binary.Read(rd, endian, &typ); err != nil { + return + } + if err = binary.Read(rd, endian, &siz); err != nil { + return + } + return } diff --git a/subcommands/diag/locks.go b/subcommands/diag/locks.go index 287cf886e..ae296b138 100644 --- a/subcommands/diag/locks.go +++ b/subcommands/diag/locks.go @@ -7,26 +7,15 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type DiagLocks struct { - subcommands.SubcommandBase -} - -func (cmd *DiagLocks) Parse(ctx *appcontext.AppContext, args []string) error { +func Locks(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("diag locks", flag.ExitOnError) flags.Parse(args) - cmd.RepositorySecret = ctx.GetSecret() - - return nil -} - -func (cmd *DiagLocks) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { locksID, err := repo.GetLocks() if err != nil { - return 1, err + return err } for _, lockID := range locksID { @@ -51,5 +40,5 @@ func (cmd *DiagLocks) Execute(ctx *appcontext.AppContext, repo *repository.Repos fmt.Fprintf(ctx.Stdout, "[%x] Got %s access on %s owner %s\n", lockID, lockType, lock.Timestamp.UTC().Format(time.RFC3339), lock.Hostname) } - return 0, nil + return nil } diff --git a/subcommands/diag/object.go b/subcommands/diag/object.go index 24f934d8c..8fa712037 100644 --- a/subcommands/diag/object.go +++ b/subcommands/diag/object.go @@ -6,20 +6,13 @@ import ( "fmt" "io" - "github.com/PlakarKorp/plakar/appcontext" "github.com/PlakarKorp/kloset/objects" "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/kloset/resources" - "github.com/PlakarKorp/plakar/subcommands" + "github.com/PlakarKorp/plakar/appcontext" ) -type DiagObject struct { - subcommands.SubcommandBase - - ObjectID string -} - -func (cmd *DiagObject) Parse(ctx *appcontext.AppContext, args []string) error { +func Object(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("diag objects", flag.ExitOnError) flags.Parse(args) @@ -27,20 +20,15 @@ func (cmd *DiagObject) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("usage: %s object OBJECT", flags.Name()) } - cmd.RepositorySecret = ctx.GetSecret() - cmd.ObjectID = flags.Args()[0] + objectID := flags.Args()[0] - return nil -} - -func (cmd *DiagObject) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - if len(cmd.ObjectID) != 64 { - return 1, fmt.Errorf("invalid object hash: %s", cmd.ObjectID) + if len(objectID) != 64 { + return fmt.Errorf("invalid object hash: %s", objectID) } - b, err := hex.DecodeString(cmd.ObjectID) + b, err := hex.DecodeString(objectID) if err != nil { - return 1, fmt.Errorf("invalid object hash: %s", cmd.ObjectID) + return fmt.Errorf("invalid object hash: %s", objectID) } // Convert the byte slice to a [32]byte @@ -49,17 +37,17 @@ func (cmd *DiagObject) Execute(ctx *appcontext.AppContext, repo *repository.Repo rd, err := repo.GetBlob(resources.RT_OBJECT, byteArray) if err != nil { - return 1, err + return err } blob, err := io.ReadAll(rd) if err != nil { - return 1, err + return err } object, err := objects.NewObjectFromBytes(blob) if err != nil { - return 1, err + return err } fmt.Fprintf(ctx.Stdout, "object: %x\n", object.ContentMAC) @@ -68,5 +56,5 @@ func (cmd *DiagObject) Execute(ctx *appcontext.AppContext, repo *repository.Repo for _, chunk := range object.Chunks { fmt.Fprintf(ctx.Stdout, " MAC: %x\n", chunk.ContentMAC) } - return 0, nil + return nil } diff --git a/subcommands/diag/packfile.go b/subcommands/diag/packfile.go index e1cfae774..21cf2ae13 100644 --- a/subcommands/diag/packfile.go +++ b/subcommands/diag/packfile.go @@ -6,47 +6,33 @@ import ( "fmt" "time" - "github.com/PlakarKorp/plakar/appcontext" "github.com/PlakarKorp/kloset/repository" - "github.com/PlakarKorp/plakar/subcommands" + "github.com/PlakarKorp/plakar/appcontext" ) -type DiagPackfile struct { - subcommands.SubcommandBase - - Args []string -} - -func (cmd *DiagPackfile) Parse(ctx *appcontext.AppContext, args []string) error { +func Packfile(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("diag packfile", flag.ExitOnError) flags.Parse(args) - cmd.RepositorySecret = ctx.GetSecret() - cmd.Args = flags.Args()[0:] - - return nil -} - -func (cmd *DiagPackfile) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - if len(cmd.Args) == 0 { + if len(flags.Args()[0:]) == 0 { packfiles, err := repo.GetPackfiles() if err != nil { - return 1, err + return err } for _, packfile := range packfiles { fmt.Fprintf(ctx.Stdout, "%x\n", packfile) } } else { - for _, arg := range cmd.Args { + for _, arg := range flags.Args()[0:] { // convert arg to [32]byte if len(arg) != 64 { - return 1, fmt.Errorf("invalid packfile hash: %s", arg) + return fmt.Errorf("invalid packfile hash: %s", arg) } b, err := hex.DecodeString(arg) if err != nil { - return 1, fmt.Errorf("invalid packfile hash: %s", arg) + return fmt.Errorf("invalid packfile hash: %s", arg) } // Convert the byte slice to a [32]byte @@ -55,7 +41,7 @@ func (cmd *DiagPackfile) Execute(ctx *appcontext.AppContext, repo *repository.Re p, err := repo.GetPackfile(byteArray) if err != nil { - return 1, err + return err } fmt.Fprintf(ctx.Stdout, "Version: %s\n", p.Footer.Version) @@ -68,5 +54,6 @@ func (cmd *DiagPackfile) Execute(ctx *appcontext.AppContext, repo *repository.Re } } } - return 0, nil + + return nil } diff --git a/subcommands/diag/repository.go b/subcommands/diag/repository.go index e09807a77..550b7ebb8 100644 --- a/subcommands/diag/repository.go +++ b/subcommands/diag/repository.go @@ -8,17 +8,10 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/kloset/snapshot" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" "github.com/dustin/go-humanize" ) -type DiagRepository struct { - subcommands.SubcommandBase - - RepositoryLocation string -} - -func (cmd *DiagRepository) Parse(ctx *appcontext.AppContext, args []string) error { +func Repository(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { // Since this is the default action, we plug the general USAGE here. flags := flag.NewFlagSet("diag", flag.ExitOnError) flags.Usage = func() { @@ -35,13 +28,6 @@ func (cmd *DiagRepository) Parse(ctx *appcontext.AppContext, args []string) erro } flags.Parse(args) - cmd.RepositorySecret = ctx.GetSecret() - - return nil -} - -func (cmd *DiagRepository) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - fmt.Fprintln(ctx.Stdout, "Version:", repo.Configuration().Version) fmt.Fprintln(ctx.Stdout, "Timestamp:", repo.Configuration().Timestamp) fmt.Fprintln(ctx.Stdout, "RepositoryID:", repo.Configuration().RepositoryID) @@ -102,7 +88,7 @@ func (cmd *DiagRepository) Execute(ctx *appcontext.AppContext, repo *repository. snapshotIDs, err := locate.LocateSnapshotIDs(repo, nil) if err != nil { - return 1, err + return err } fmt.Fprintln(ctx.Stdout, "Snapshots:", len(snapshotIDs)) @@ -110,12 +96,12 @@ func (cmd *DiagRepository) Execute(ctx *appcontext.AppContext, repo *repository. for _, snapshotID := range snapshotIDs { snap, err := snapshot.Load(repo, snapshotID) if err != nil { - return 1, err + return err } totalSize += snap.Header.GetSource(0).Summary.Directory.Size + snap.Header.GetSource(0).Summary.Below.Size snap.Close() } fmt.Fprintf(ctx.Stdout, "Size: %s (%d bytes)\n", humanize.IBytes(totalSize), totalSize) - return 0, nil + return nil } diff --git a/subcommands/diag/search.go b/subcommands/diag/search.go index abfa615a3..86891a5a5 100644 --- a/subcommands/diag/search.go +++ b/subcommands/diag/search.go @@ -10,22 +10,16 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/kloset/snapshot" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type DiagSearch struct { - subcommands.SubcommandBase - - SnapshotPath string - Mimes []string -} - -func (cmd *DiagSearch) Parse(ctx *appcontext.AppContext, args []string) error { +func Search(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("diag search", flag.ExitOnError) flags.Parse(args) - var path string - var mimes []string + var ( + path string + mimes []string + ) switch flags.NArg() { case 1: @@ -37,36 +31,28 @@ func (cmd *DiagSearch) Parse(ctx *appcontext.AppContext, args []string) error { flags.Name()) } - cmd.RepositorySecret = ctx.GetSecret() - cmd.SnapshotPath = path - cmd.Mimes = mimes - - return nil -} - -func (cmd *DiagSearch) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - snap, pathname, err := locate.OpenSnapshotByPath(repo, cmd.SnapshotPath) + snap, pathname, err := locate.OpenSnapshotByPath(repo, path) if err != nil { - return 1, err + return err } defer snap.Close() opts := snapshot.SearchOpts{ Recursive: true, Prefix: pathname, - Mimes: cmd.Mimes, + Mimes: mimes, } it, err := snap.Search(context.Background(), &opts) if err != nil { - return 1, err + return err } for entry, err := range it { if err != nil { - return 1, err + return err } fmt.Fprintf(ctx.Stdout, "%x:%s\n", snap.Header.Identifier[0:4], entry.Path()) } - return 0, nil + return nil } diff --git a/subcommands/diag/snapshot.go b/subcommands/diag/snapshot.go index 3e07c18fe..05bb4f5f7 100644 --- a/subcommands/diag/snapshot.go +++ b/subcommands/diag/snapshot.go @@ -11,18 +11,11 @@ import ( "github.com/PlakarKorp/kloset/locate" "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" "github.com/dustin/go-humanize" "github.com/google/uuid" ) -type DiagSnapshot struct { - subcommands.SubcommandBase - - SnapshotID string -} - -func (cmd *DiagSnapshot) Parse(ctx *appcontext.AppContext, args []string) error { +func Snapshot(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("diag snapshot", flag.ExitOnError) flags.Parse(args) @@ -30,16 +23,9 @@ func (cmd *DiagSnapshot) Parse(ctx *appcontext.AppContext, args []string) error return fmt.Errorf("usage: %s snapshot SNAPSHOT", flags.Name()) } - cmd.RepositorySecret = ctx.GetSecret() - cmd.SnapshotID = flags.Args()[0] - - return nil -} - -func (cmd *DiagSnapshot) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - snap, _, err := locate.OpenSnapshotByPath(repo, cmd.SnapshotID) + snap, _, err := locate.OpenSnapshotByPath(repo, flags.Args()[0]) if err != nil { - return 1, err + return err } defer snap.Close() @@ -114,5 +100,5 @@ func (cmd *DiagSnapshot) Execute(ctx *appcontext.AppContext, repo *repository.Re fmt.Fprintf(ctx.Stdout, " - MIMEOther: %d\n", header.GetSource(0).Summary.Directory.MIMEOther+header.GetSource(0).Summary.Below.MIMEOther) fmt.Fprintf(ctx.Stdout, " - Errors: %d\n", header.GetSource(0).Summary.Directory.Errors+header.GetSource(0).Summary.Below.Errors) - return 0, nil + return nil } diff --git a/subcommands/diag/state.go b/subcommands/diag/state.go index d506a0d94..1aa9b2c03 100644 --- a/subcommands/diag/state.go +++ b/subcommands/diag/state.go @@ -10,45 +10,31 @@ import ( "github.com/PlakarKorp/kloset/repository/state" "github.com/PlakarKorp/kloset/resources" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type DiagState struct { - subcommands.SubcommandBase - - Args []string -} - -func (cmd *DiagState) Parse(ctx *appcontext.AppContext, args []string) error { +func State(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("diag state", flag.ExitOnError) flags.Parse(args) - cmd.RepositorySecret = ctx.GetSecret() - cmd.Args = flags.Args() - - return nil -} - -func (cmd *DiagState) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - if len(cmd.Args) == 0 { + if len(flags.Args()) == 0 { states, err := repo.GetStates() if err != nil { - return 1, err + return err } for _, state := range states { fmt.Fprintf(ctx.Stdout, "%x\n", state) } } else { - for _, arg := range cmd.Args { + for _, arg := range flags.Args() { // convert arg to [32]byte if len(arg) != 64 { - return 1, fmt.Errorf("invalid packfile hash: %s", arg) + return fmt.Errorf("invalid packfile hash: %s", arg) } b, err := hex.DecodeString(arg) if err != nil { - return 1, fmt.Errorf("invalid packfile hash: %s", arg) + return fmt.Errorf("invalid packfile hash: %s", arg) } // Convert the byte slice to a [32]byte @@ -57,7 +43,7 @@ func (cmd *DiagState) Execute(ctx *appcontext.AppContext, repo *repository.Repos rawStateRd, v, err := repo.GetState(byteArray) if err != nil { - return 1, err + return err } defer rawStateRd.Close() @@ -65,13 +51,13 @@ func (cmd *DiagState) Execute(ctx *appcontext.AppContext, repo *repository.Repos identifier := objects.RandomMAC() scanCache, err := repo.AppContext().GetCache().Scan(identifier) if err != nil { - return 1, err + return err } defer scanCache.Close() st, err := state.FromStream(rawStateRd, v, scanCache) if err != nil { - return 1, err + return err } fmt.Fprintf(ctx.Stdout, "Version: %s\n", st.Metadata.Version) @@ -111,5 +97,5 @@ func (cmd *DiagState) Execute(ctx *appcontext.AppContext, repo *repository.Repos } } } - return 0, nil + return nil } diff --git a/subcommands/diag/vfs.go b/subcommands/diag/vfs.go index 39da9c1d2..803126e85 100644 --- a/subcommands/diag/vfs.go +++ b/subcommands/diag/vfs.go @@ -11,17 +11,10 @@ import ( "github.com/PlakarKorp/kloset/resources" "github.com/PlakarKorp/kloset/snapshot/vfs" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" "github.com/dustin/go-humanize" ) -type DiagVFS struct { - subcommands.SubcommandBase - - SnapshotPath string -} - -func (cmd *DiagVFS) Parse(ctx *appcontext.AppContext, args []string) error { +func VFS(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("diag vfs", flag.ExitOnError) flags.Parse(args) @@ -29,28 +22,21 @@ func (cmd *DiagVFS) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("usage: %s vfs SNAPSHOT[:PATH]", flags.Name()) } - cmd.RepositorySecret = ctx.GetSecret() - cmd.SnapshotPath = flags.Args()[0] - - return nil -} - -func (cmd *DiagVFS) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - snap1, pathname, err := locate.OpenSnapshotByPath(repo, cmd.SnapshotPath) + snap1, pathname, err := locate.OpenSnapshotByPath(repo, flags.Args()[0]) if err != nil { - return 1, err + return err } defer snap1.Close() fs, err := snap1.Filesystem() if err != nil { - return 1, err + return err } pathname = path.Clean(pathname) entry, err := fs.GetEntry(pathname) if err != nil { - return 1, err + return err } if entry.Stat().Mode().IsDir() { @@ -92,25 +78,25 @@ func (cmd *DiagVFS) Execute(ctx *appcontext.AppContext, repo *repository.Reposit if summary == nil && entry.IsDir() { tree, err := snap1.SummaryIdx() if err != nil { - return 1, err + return err } key, found, err := tree.Find(pathname) if err != nil { - return 1, err + return err } if !found { - return 1, fmt.Errorf("could not resolve pathname: %s", pathname) + return fmt.Errorf("could not resolve pathname: %s", pathname) } serializedSummary, err := repo.GetBlobBytes(resources.RT_VFS_SUMMARY, key) if err != nil { - return 1, err + return err } summary, err = vfs.SummaryFromBytes(serializedSummary) if err != nil { - return 1, err + return err } } @@ -176,7 +162,7 @@ func (cmd *DiagVFS) Execute(ctx *appcontext.AppContext, repo *repository.Reposit if entry.IsDir() { iter, err := entry.Getdents(fs) if err != nil { - return 1, err + return err } offset := 0 for child := range iter { @@ -198,10 +184,10 @@ func (cmd *DiagVFS) Execute(ctx *appcontext.AppContext, repo *repository.Reposit offset := 0 for entry, err := range fs.Errors(pathname) { if err != nil { - return 1, fmt.Errorf("failure in scanning errors: %w", err) + return fmt.Errorf("failure in scanning errors: %w", err) } fmt.Fprintf(ctx.Stdout, "Error[%d]: %s: %s\n", offset, entry.Name, entry.Error) offset++ } - return 0, nil + return nil } diff --git a/subcommands/diag/xattrs.go b/subcommands/diag/xattrs.go index 9673849e5..439bfbace 100644 --- a/subcommands/diag/xattrs.go +++ b/subcommands/diag/xattrs.go @@ -13,16 +13,9 @@ import ( "github.com/PlakarKorp/kloset/resources" "github.com/PlakarKorp/kloset/snapshot/vfs" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type DiagXattr struct { - subcommands.SubcommandBase - - SnapshotPath string -} - -func (cmd *DiagXattr) Parse(ctx *appcontext.AppContext, args []string) error { +func Xattr(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("diag xattr", flag.ExitOnError) flags.Parse(args) @@ -30,15 +23,9 @@ func (cmd *DiagXattr) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("usage: %s xattr SNAPSHOT[:PATH]", flags.Name()) } - cmd.RepositorySecret = ctx.GetSecret() - cmd.SnapshotPath = flags.Args()[0] - return nil -} - -func (cmd *DiagXattr) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - snap, pathname, err := locate.OpenSnapshotByPath(repo, cmd.SnapshotPath) + snap, pathname, err := locate.OpenSnapshotByPath(repo, flags.Args()[0]) if err != nil { - return 1, err + return err } defer snap.Close() @@ -51,23 +38,23 @@ func (cmd *DiagXattr) Execute(ctx *appcontext.AppContext, repo *repository.Repos rd, err := repo.GetBlob(resources.RT_XATTR_BTREE, snap.Header.GetSource(0).VFS.Xattrs) if err != nil { - return 1, err + return err } store := repository.NewRepositoryStore[string, objects.MAC](repo, resources.RT_XATTR_NODE) tree, err := btree.Deserialize(rd, store, vfs.PathCmp) if err != nil { - return 1, err + return err } fs, err := snap.Filesystem() if err != nil { - return 1, err + return err } it, err := tree.ScanFrom(pathname) if err != nil { - return 1, err + return err } for it.Next() { @@ -78,20 +65,20 @@ func (cmd *DiagXattr) Execute(ctx *appcontext.AppContext, repo *repository.Repos xattr, err := fs.ResolveXattr(xattrmac) if err != nil { - return 1, err + return err } rd := vfs.NewObjectReader(repo, xattr.ResolvedObject, xattr.Size, -1) value, err := io.ReadAll(rd) if err != nil { - return 1, err + return err } fmt.Fprintln(ctx.Stdout, xattr.Path, xattr.Name, string(value)) } if err := it.Err(); err != nil { - return 1, err + return err } - return 0, nil + return nil } diff --git a/subcommands/diff/diff.go b/subcommands/diff/diff.go index f0881a578..c9cd85590 100644 --- a/subcommands/diff/diff.go +++ b/subcommands/diff/diff.go @@ -39,56 +39,44 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &Diff{} }, 0, "diff") + subcommands.Register(Diff, 0, "diff") } -func (cmd *Diff) Parse(ctx *appcontext.AppContext, args []string) error { +func Diff(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + highlight bool + recurse bool + ) + flags := flag.NewFlagSet("diff", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s [OPTIONS] SNAPSHOT:PATH SNAPSHOT[:PATH]\n", flags.Name()) fmt.Fprintf(flags.Output(), "\nOPTIONS:\n") flags.PrintDefaults() } - flags.BoolVar(&cmd.Highlight, "highlight", false, "highlight output") - flags.BoolVar(&cmd.Recursive, "recursive", false, "recursive diff of directories") + flags.BoolVar(&highlight, "highlight", false, "highlight output") + flags.BoolVar(&recurse, "recursive", false, "recursive diff of directories") flags.Parse(args) + var path1, path2 string if flags.NArg() == 1 { - cmd.Path1 = flags.Arg(0) - cmd.Path2 = "" + path1 = flags.Arg(0) + path2 = "" } else if flags.NArg() == 2 { - cmd.Path1 = flags.Arg(0) - cmd.Path2 = flags.Arg(1) + path1 = flags.Arg(0) + path2 = flags.Arg(1) } else { return fmt.Errorf("needs at least a snapshot ID and/or snapshot file to diff") } - cmd.RepositorySecret = ctx.GetSecret() - - return nil -} -type Diff struct { - subcommands.SubcommandBase - - Highlight bool - Recursive bool - Path1 string - Path2 string -} - -func (cmd *Diff) Name() string { - return "diff" -} - -func (cmd *Diff) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - snap1, pathname1, err := locate.OpenSnapshotByPath(repo, cmd.Path1) + snap1, pathname1, err := locate.OpenSnapshotByPath(repo, path1) if err != nil { - return 1, fmt.Errorf("diff: could not open snapshot: %s", cmd.Path1) + return fmt.Errorf("diff: could not open snapshot: %s", path1) } defer snap1.Close() vfs1, err := snap1.Filesystem() if err != nil { - return 1, fmt.Errorf("diff: could not get filesystem for snapshot: %s", cmd.Path1) + return fmt.Errorf("diff: could not get filesystem for snapshot: %s", path1) } id1 := fmt.Sprintf("%x", snap1.Header.GetIndexShortID()) @@ -96,19 +84,19 @@ func (cmd *Diff) Execute(ctx *appcontext.AppContext, repo *repository.Repository var id2 string var vfs2 fs.FS - if cmd.Path2 == "" { + if path2 == "" { vfs2 = os.DirFS("/") id2 = "local" } else { var snap2 *snapshot.Snapshot - snap2, pathname2, err = locate.OpenSnapshotByPath(repo, cmd.Path2) + snap2, pathname2, err = locate.OpenSnapshotByPath(repo, path2) if err != nil { - return 1, fmt.Errorf("diff: could not open snapshot: %s", cmd.Path2) + return fmt.Errorf("diff: could not open snapshot: %s", path2) } defer snap2.Close() vfs2, err = snap2.Filesystem() if err != nil { - return 1, fmt.Errorf("diff: could not get filesystem for snapshot: %s", cmd.Path2) + return fmt.Errorf("diff: could not get filesystem for snapshot: %s", path2) } id2 = fmt.Sprintf("%x", snap2.Header.GetIndexShortID()) } @@ -126,25 +114,25 @@ func (cmd *Diff) Execute(ctx *appcontext.AppContext, repo *repository.Repository out io.Writer = ctx.Stdout builder = strings.Builder{} ) - if cmd.Highlight { + if highlight { out = &builder } - err = cmd.diff_pathnames(out, id1, vfs1, pathname1, id2, vfs2, pathname2) + err = diff_pathnames(out, id1, vfs1, pathname1, id2, vfs2, pathname2, recurse) if err != nil { - return 1, fmt.Errorf("diff: could not diff pathnames: %w", err) + return fmt.Errorf("diff: could not diff pathnames: %w", err) } - if cmd.Highlight { + if highlight { err = quick.Highlight(ctx.Stdout, builder.String(), "diff", "terminal", "dracula") if err != nil { - return 1, fmt.Errorf("diff: could not highlight diff: %w", err) + return fmt.Errorf("diff: could not highlight diff: %w", err) } } - return 0, nil + return nil } -func (cmd *Diff) diff_pathnames(out io.Writer, id1 string, vfs1 fs.FS, pathname1 string, id2 string, vfs2 fs.FS, pathname2 string) error { +func diff_pathnames(out io.Writer, id1 string, vfs1 fs.FS, pathname1 string, id2 string, vfs2 fs.FS, pathname2 string, recurse bool) error { fsobj1, err := vfs1.Open(pathname1) if err != nil { return fmt.Errorf("could not open path %s in snapshot %s: %w", pathname1, id1, err) @@ -172,18 +160,18 @@ func (cmd *Diff) diff_pathnames(out io.Writer, id1 string, vfs1 fs.FS, pathname1 } if st1.IsDir() && st2.IsDir() { - if cmd.Recursive { - return cmd.diff_directories_recursive(out, id1, vfs1, pathname1, id2, vfs2, pathname1) + if recurse { + return diff_directories_recursive(out, id1, vfs1, pathname1, id2, vfs2, pathname1) } - return cmd.diff_directories_flat(out, pathname1, fsobj1, pathname2, fsobj2) + return diff_directories_flat(out, pathname1, fsobj1, pathname2, fsobj2) } else if st1.IsDir() || st2.IsDir() { return fmt.Errorf("can't diff different file types") } else { - return cmd.diff_readers(out, id1, pathname1, fsobj1, id2, pathname2, fsobj2) + return diff_readers(out, id1, pathname1, fsobj1, id2, pathname2, fsobj2) } } -func (cmd *Diff) diff_directories_flat(out io.Writer, pathname1 string, fsobj1 fs.File, pathname2 string, fsobj2 fs.File) error { +func diff_directories_flat(out io.Writer, pathname1 string, fsobj1 fs.File, pathname2 string, fsobj2 fs.File) error { // non VFS have their / stripped, reintroduce it if !strings.HasPrefix(pathname2, "/") { pathname2 = "/" + pathname2 // Ensure pathname starts with a slash @@ -236,7 +224,7 @@ func (cmd *Diff) diff_directories_flat(out io.Writer, pathname1 string, fsobj1 f return nil } -func (cmd *Diff) diff_directories_recursive(out io.Writer, id1 string, fs1 fs.FS, path1 string, id2 string, fs2 fs.FS, path2 string) error { +func diff_directories_recursive(out io.Writer, id1 string, fs1 fs.FS, path1 string, id2 string, fs2 fs.FS, path2 string) error { entries1, err1 := fs.ReadDir(fs1, path1) entries2, err2 := fs.ReadDir(fs2, path2) @@ -290,7 +278,7 @@ func (cmd *Diff) diff_directories_recursive(out io.Writer, id1 string, fs1 fs.FS case ok1 && ok2: if e1.IsDir() && e2.IsDir() { fmt.Fprintf(out, "Common subdirectories: %s and %s\n", full1, full2) - err := cmd.diff_directories_recursive(out, id1, fs1, full1, id2, fs2, full2) + err := diff_directories_recursive(out, id1, fs1, full1, id2, fs2, full2) if err != nil { return err } @@ -307,7 +295,7 @@ func (cmd *Diff) diff_directories_recursive(out io.Writer, id1 string, fs1 fs.FS return err } - err = cmd.diff_readers(out, id1, full1, rd1, id2, full2, rd2) + err = diff_readers(out, id1, full1, rd1, id2, full2, rd2) rd1.Close() rd2.Close() if err != nil { @@ -379,7 +367,7 @@ func binaryeq(rrd1 io.Reader, rrd2 io.Reader) (bool, error) { } } -func (cmd *Diff) diff_readers(out io.Writer, id1 string, pathname1 string, rd1 io.Reader, id2 string, pathname2 string, rd2 io.Reader) error { +func diff_readers(out io.Writer, id1 string, pathname1 string, rd1 io.Reader, id2 string, pathname2 string, rd2 io.Reader) error { if isbinary(rd1) || isbinary(rd2) { same, err := binaryeq(rd1, rd2) if err != nil { diff --git a/subcommands/digest/digest.go b/subcommands/digest/digest.go index 59aed1f40..c89e49c8f 100644 --- a/subcommands/digest/digest.go +++ b/subcommands/digest/digest.go @@ -19,6 +19,7 @@ package digest import ( "flag" "fmt" + "hash" "io" "path" "strings" @@ -34,11 +35,13 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &Digest{} }, 0, "digest") + subcommands.Register(Digest, 0, "digest") } -func (cmd *Digest) Parse(ctx *appcontext.AppContext, args []string) error { - var opt_hashing string +func Digest(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + algo string + ) flags := flag.NewFlagSet("digest", flag.ExitOnError) flags.Usage = func() { @@ -47,35 +50,22 @@ func (cmd *Digest) Parse(ctx *appcontext.AppContext, args []string) error { flags.PrintDefaults() } - flags.StringVar(&opt_hashing, "hashing", "SHA256", "hashing algorithm to use") + flags.StringVar(&algo, "hashing", "SHA256", "hashing algorithm to use") flags.Parse(args) if flags.NArg() == 0 { return fmt.Errorf("at least one parameter is required") } - hashingFunction := strings.ToUpper(opt_hashing) - if hashing.GetHasher(hashingFunction) == nil { - return fmt.Errorf("unsupported hashing algorithm: %s", hashingFunction) + hasher := hashing.GetHasher(strings.ToUpper(algo)) + if hasher == nil { + return fmt.Errorf("unsupported hashing algorithm: %s", algo) } - cmd.RepositorySecret = ctx.GetSecret() - cmd.HashingFunction = hashingFunction - cmd.Targets = flags.Args() + targets := flags.Args() - return nil -} - -type Digest struct { - subcommands.SubcommandBase - - HashingFunction string - Targets []string -} - -func (cmd *Digest) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { errors := 0 - for _, snapshotPath := range cmd.Targets { + for _, snapshotPath := range targets { snap, pathname, err := locate.OpenSnapshotByPath(repo, snapshotPath) if err != nil { ctx.GetLogger().Error("digest: %s: %s", pathname, err) @@ -89,14 +79,22 @@ func (cmd *Digest) Execute(ctx *appcontext.AppContext, repo *repository.Reposito continue } - cmd.displayDigests(ctx, fs, repo, snap, pathname) + displayDigests(ctx, fs, repo, snap, pathname, hasher, algo) snap.Close() } - return 0, nil + return nil } -func (cmd *Digest) displayDigests(ctx *appcontext.AppContext, fs *vfs.Filesystem, repo *repository.Repository, snap *snapshot.Snapshot, pathname string) error { +func displayDigests( + ctx *appcontext.AppContext, + fs *vfs.Filesystem, + repo *repository.Repository, + snap *snapshot.Snapshot, + pathname string, + hasher hash.Hash, + algo string, +) error { if err := ctx.Err(); err != nil { return err } @@ -112,7 +110,8 @@ func (cmd *Digest) displayDigests(ctx *appcontext.AppContext, fs *vfs.Filesystem return err } for child := range iter { - if err := cmd.displayDigests(ctx, fs, repo, snap, path.Join(pathname, child.Stat().Name())); err != nil { + pathname := path.Join(pathname, child.Stat().Name()) + if err := displayDigests(ctx, fs, repo, snap, pathname, hasher, algo); err != nil { return err } } @@ -128,12 +127,11 @@ func (cmd *Digest) displayDigests(ctx *appcontext.AppContext, fs *vfs.Filesystem } defer rd.Close() - algorithm := cmd.HashingFunction - hasher := hashing.GetHasher(algorithm) + hasher.Reset() if _, err := io.Copy(hasher, rd); err != nil { return err } digest := hasher.Sum(nil) - fmt.Fprintf(ctx.Stdout, "%s (%s) = %x\n", algorithm, utils.SanitizeText(pathname), digest) + fmt.Fprintf(ctx.Stdout, "%s (%s) = %x\n", algo, utils.SanitizeText(pathname), digest) return nil } diff --git a/subcommands/dup/dup.go b/subcommands/dup/dup.go index e20b67dd7..58581eada 100644 --- a/subcommands/dup/dup.go +++ b/subcommands/dup/dup.go @@ -27,17 +27,11 @@ import ( "github.com/PlakarKorp/plakar/subcommands" ) -type Dup struct { - subcommands.SubcommandBase - - SnapshotIDS []string -} - func init() { - subcommands.Register(func() subcommands.Subcommand { return &Dup{} }, 0, "dup") + subcommands.Register(Dup, 0, "dup") } -func (cmd *Dup) Parse(ctx *appcontext.AppContext, args []string) error { +func Dup(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("dup", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s [OPTIONS] [SNAPSHOT[:PATH]]...\n", flags.Name()) @@ -50,14 +44,8 @@ func (cmd *Dup) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("at least one parameter is required") } - cmd.SnapshotIDS = flags.Args() - - return nil -} - -func (cmd *Dup) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { errors := 0 - for _, snapshotPath := range cmd.SnapshotIDS { + for _, snapshotPath := range flags.Args() { snap, pathname, err := locate.OpenSnapshotByPath(repo, snapshotPath) if err != nil { ctx.GetLogger().Error("digest: %s: %s", pathname, err) @@ -76,5 +64,5 @@ func (cmd *Dup) Execute(ctx *appcontext.AppContext, repo *repository.Repository) snap.Close() } - return 0, nil + return nil } diff --git a/subcommands/help/help.go b/subcommands/help/help.go index 619cbae1c..3a53a097a 100644 --- a/subcommands/help/help.go +++ b/subcommands/help/help.go @@ -35,10 +35,12 @@ import ( var docs embed.FS func init() { - subcommands.Register(func() subcommands.Subcommand { return &Help{} }, subcommands.BeforeRepositoryOpen, "help") + subcommands.Register(Help, subcommands.BeforeRepositoryOpen, "help") } -func (cmd *Help) Parse(ctx *appcontext.AppContext, args []string) error { +func Help(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var style string + flags := flag.NewFlagSet("help", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s [OPTIONS]\n", flags.Name()) @@ -46,30 +48,20 @@ func (cmd *Help) Parse(ctx *appcontext.AppContext, args []string) error { flags.PrintDefaults() fmt.Fprint(flags.Output(), "\nTo view the man page for a specific command, run 'plakar help SUBCOMMAND'.\n") } - flags.StringVar(&cmd.Style, "style", "auto", "style to use") + flags.StringVar(&style, "style", "auto", "style to use") flags.Parse(args) - cmd.Command = strings.Join(flags.Args(), "-") - return nil -} - -type Help struct { - subcommands.SubcommandBase + command := strings.Join(flags.Args(), "-") - Style string - Command string -} - -func (cmd *Help) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { document := "docs/plakar.md" - if cmd.Command != "" { - document = fmt.Sprintf("docs/plakar-%s.md", cmd.Command) + if command != "" { + document = fmt.Sprintf("docs/plakar-%s.md", command) } content, err := docs.ReadFile(document) if err != nil { - fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd.Command) - return 1, err + fmt.Fprintf(os.Stderr, "unknown command: %s\n", command) + return err } disableColors := false @@ -86,7 +78,7 @@ func (cmd *Help) Execute(ctx *appcontext.AppContext, repo *repository.Repository } } else { options = []glamour.TermRendererOption{ - glamour.WithStandardStyle(cmd.Style), + glamour.WithStandardStyle(style), glamour.WithColorProfile(termenv.TrueColor), } } @@ -94,17 +86,15 @@ func (cmd *Help) Execute(ctx *appcontext.AppContext, repo *repository.Repository options..., ) if err != nil { - fmt.Fprintf(os.Stderr, "failed to create renderer: %s\n", err) - return 1, err + return fmt.Errorf("failed to create renderer: %w", err) } out, err := r.RenderBytes(content) if err != nil { - fmt.Fprintf(os.Stderr, "failed to render: %s\n", err) - return 1, err + return fmt.Errorf("renderer failed: %w", err) } fmt.Print(string(out)) - return 0, err + return nil } diff --git a/subcommands/info/errors.go b/subcommands/info/errors.go index 8b3da8eb4..855ecd93a 100644 --- a/subcommands/info/errors.go +++ b/subcommands/info/errors.go @@ -8,24 +8,24 @@ import ( "github.com/PlakarKorp/plakar/appcontext" ) -func (cmd *Info) executeErrors(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - snap, pathname, err := locate.OpenSnapshotByPath(repo, cmd.SnapshotID) +func infoErrors(ctx *appcontext.AppContext, repo *repository.Repository, snapshotID string) error { + snap, pathname, err := locate.OpenSnapshotByPath(repo, snapshotID) if err != nil { - return 1, err + return err } defer snap.Close() fs, err := snap.Filesystem() if err != nil { - return 1, err + return err } for item, err := range fs.Errors(pathname) { if err != nil { - return 1, fmt.Errorf("failed to scan errors: %w", err) + return fmt.Errorf("failed to scan errors: %w", err) } fmt.Fprintf(ctx.Stdout, "%s: %s\n", item.Name, item.Error) } - return 0, nil + return nil } diff --git a/subcommands/info/info.go b/subcommands/info/info.go index 94bf7a76d..42b67f7de 100644 --- a/subcommands/info/info.go +++ b/subcommands/info/info.go @@ -26,19 +26,17 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &Info{} }, 0, "info") + subcommands.Register(Info, 0, "info") } -type Info struct { - subcommands.SubcommandBase - SnapshotID string - Errors bool -} +func Info(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + listErrors bool + ) -func (cmd *Info) Parse(ctx *appcontext.AppContext, args []string) error { // Since this is the default action, we plug the general USAGE here. flags := flag.NewFlagSet("info", flag.ExitOnError) - flags.BoolVar(&cmd.Errors, "errors", false, "display errors in the repository or snapshot") + flags.BoolVar(&listErrors, "errors", false, "display errors in the repository or snapshot") flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s [-errors] [SNAPSHOT]\n", flags.Name()) } @@ -48,20 +46,13 @@ func (cmd *Info) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("too many arguments") } - cmd.RepositorySecret = ctx.GetSecret() if flags.NArg() == 1 { - cmd.SnapshotID = flags.Arg(0) - } - - return nil -} + if listErrors { + return infoErrors(ctx, repo, flags.Arg(0)) + } -func (cmd *Info) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - if cmd.SnapshotID == "" { - return cmd.executeRepository(ctx, repo) - } - if cmd.Errors { - return cmd.executeErrors(ctx, repo) + return infoSnapshot(ctx, repo, flags.Arg(0)) } - return cmd.executeSnapshot(ctx, repo) + + return infoRepo(ctx, repo) } diff --git a/subcommands/info/repository.go b/subcommands/info/repository.go index 3059203ab..f1951cd36 100644 --- a/subcommands/info/repository.go +++ b/subcommands/info/repository.go @@ -9,7 +9,7 @@ import ( "github.com/dustin/go-humanize" ) -func (cmd *Info) executeRepository(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { +func infoRepo(ctx *appcontext.AppContext, repo *repository.Repository) error { fmt.Fprintln(ctx.Stdout, "Version:", repo.Configuration().Version) fmt.Fprintln(ctx.Stdout, "Timestamp:", repo.Configuration().Timestamp) @@ -72,18 +72,18 @@ func (cmd *Info) executeRepository(ctx *appcontext.AppContext, repo *repository. nSnapshots, logicalSize, err := snapshot.LogicalSize(repo) if err != nil { - return 1, fmt.Errorf("unable to calculate logical size: %w", err) + return fmt.Errorf("unable to calculate logical size: %w", err) } fmt.Fprintln(ctx.Stdout, "Snapshots:", nSnapshots) storageSize, err := repo.Store().Size(ctx) if err != nil { - return 1, fmt.Errorf("unable to compute storage size: %w", err) + return fmt.Errorf("unable to compute storage size: %w", err) } fmt.Fprintf(ctx.Stdout, "Storage size: %s (%d bytes)\n", humanize.IBytes(uint64(storageSize)), uint64(storageSize)) fmt.Fprintf(ctx.Stdout, "Logical size: %s (%d bytes)\n", humanize.IBytes(uint64(logicalSize)), logicalSize) - return 0, nil + return nil } diff --git a/subcommands/info/snapshot.go b/subcommands/info/snapshot.go index 74154751d..c808dacd2 100644 --- a/subcommands/info/snapshot.go +++ b/subcommands/info/snapshot.go @@ -14,10 +14,10 @@ import ( "github.com/google/uuid" ) -func (cmd *Info) executeSnapshot(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - snap, _, err := locate.OpenSnapshotByPath(repo, cmd.SnapshotID) +func infoSnapshot(ctx *appcontext.AppContext, repo *repository.Repository, snapshotID string) error { + snap, _, err := locate.OpenSnapshotByPath(repo, snapshotID) if err != nil { - return 1, err + return err } defer snap.Close() @@ -95,5 +95,5 @@ func (cmd *Info) executeSnapshot(ctx *appcontext.AppContext, repo *repository.Re fmt.Fprintf(ctx.Stdout, " - MIMEOther: %d\n", header.GetSource(0).Summary.Directory.MIMEOther+header.GetSource(0).Summary.Below.MIMEOther) fmt.Fprintf(ctx.Stdout, " - Errors: %d\n", header.GetSource(0).Summary.Directory.Errors+header.GetSource(0).Summary.Below.Errors) - return 0, nil + return nil } diff --git a/subcommands/locate/locate.go b/subcommands/locate/locate.go index 3ac28c9f6..77cbae20c 100644 --- a/subcommands/locate/locate.go +++ b/subcommands/locate/locate.go @@ -31,11 +31,14 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &Locate{} }, 0, "locate") + subcommands.Register(Locate, 0, "locate") } -func (cmd *Locate) Parse(ctx *appcontext.AppContext, args []string) error { - cmd.LocateOptions = plocate.NewDefaultLocateOptions() +func Locate(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + locateOpts = plocate.NewDefaultLocateOptions() + snap string + ) flags := flag.NewFlagSet("locate", flag.ExitOnError) flags.Usage = func() { @@ -44,63 +47,50 @@ func (cmd *Locate) Parse(ctx *appcontext.AppContext, args []string) error { flags.PrintDefaults() } - flags.StringVar(&cmd.Snapshot, "snapshot", "", "snapshot to locate in") - cmd.LocateOptions.InstallLocateFlags(flags) + flags.StringVar(&snap, "snapshot", "", "snapshot to locate in") + locateOpts.InstallLocateFlags(flags) flags.Parse(args) - if cmd.Snapshot != "" && !cmd.LocateOptions.Empty() { + if snap != "" && !locateOpts.Empty() { ctx.GetLogger().Warn("snapshot specified, filters will be ignored") } - cmd.RepositorySecret = ctx.GetSecret() - cmd.Patterns = flags.Args() + patterns := flags.Args() - return nil -} - -type Locate struct { - subcommands.SubcommandBase - - LocateOptions *plocate.LocateOptions - Snapshot string - Patterns []string -} - -func (cmd *Locate) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { var snapshots []objects.MAC - if len(cmd.Snapshot) == 0 { - snapshotIDs, err := plocate.LocateSnapshotIDs(repo, cmd.LocateOptions) + if len(snap) == 0 { + snapshotIDs, err := plocate.LocateSnapshotIDs(repo, locateOpts) if err != nil { - return 1, fmt.Errorf("ls: could not fetch snapshots list: %w", err) + return fmt.Errorf("ls: could not fetch snapshots list: %w", err) } snapshots = append(snapshots, snapshotIDs...) } else { - snapshotIDs := plocate.LookupSnapshotByPrefix(repo, cmd.Snapshot) + snapshotIDs := plocate.LookupSnapshotByPrefix(repo, snap) snapshots = append(snapshots, snapshotIDs...) } for _, snapshotID := range snapshots { snap, err := snapshot.Load(repo, snapshotID) if err != nil { - return 1, fmt.Errorf("locate: could not get snapshot: %w", err) + return fmt.Errorf("locate: could not get snapshot: %w", err) } fs, err := snap.Filesystem() if err != nil { snap.Close() - return 1, fmt.Errorf("locate: could not get filesystem: %w", err) + return fmt.Errorf("locate: could not get filesystem: %w", err) } for pathname, err := range fs.Pathnames() { if err != nil { snap.Close() - return 1, fmt.Errorf("locate: could not get pathname: %w", err) + return fmt.Errorf("locate: could not get pathname: %w", err) } if err := ctx.Err(); err != nil { - return 1, err + return err } - for _, pattern := range cmd.Patterns { + for _, pattern := range patterns { matched := false if path.Base(pathname) == pattern { matched = true @@ -109,7 +99,7 @@ func (cmd *Locate) Execute(ctx *appcontext.AppContext, repo *repository.Reposito matched, err := path.Match(pattern, path.Base(pathname)) if err != nil { snap.Close() - return 1, fmt.Errorf("locate: could not match pattern: %w", err) + return fmt.Errorf("locate: could not match pattern: %w", err) } if !matched { continue @@ -120,5 +110,5 @@ func (cmd *Locate) Execute(ctx *appcontext.AppContext, repo *repository.Reposito } snap.Close() } - return 0, nil + return nil } diff --git a/subcommands/login/login.go b/subcommands/login/login.go index b700e9882..6fb416549 100644 --- a/subcommands/login/login.go +++ b/subcommands/login/login.go @@ -29,10 +29,18 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &Login{} }, subcommands.BeforeRepositoryOpen, "login") + subcommands.Register(Login, subcommands.BeforeRepositoryOpen, "login") } -func (cmd *Login) Parse(ctx *appcontext.AppContext, args []string) error { +func Login(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + status bool + github bool + email string + env bool + nospawn bool + ) + flags := flag.NewFlagSet("login", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s [OPTIONS]\n", flags.Name()) @@ -40,96 +48,82 @@ func (cmd *Login) Parse(ctx *appcontext.AppContext, args []string) error { flags.PrintDefaults() } - flags.BoolVar(&cmd.Status, "status", false, "do not login, just display the status") - flags.BoolVar(&cmd.NoSpawn, "no-spawn", false, "don't spawn browser") - flags.BoolVar(&cmd.Github, "github", false, "login with GitHub") - flags.StringVar(&cmd.Email, "email", "", "login with email") - flags.BoolVar(&cmd.Env, "env", false, "use token from environment variable PLAKAR_TOKEN") + flags.BoolVar(&status, "status", false, "do not login, just display the status") + flags.BoolVar(&nospawn, "no-spawn", false, "don't spawn browser") + flags.BoolVar(&github, "github", false, "login with GitHub") + flags.StringVar(&email, "email", "", "login with email") + flags.BoolVar(&env, "env", false, "use token from environment variable PLAKAR_TOKEN") flags.Parse(args) if flags.NArg() > 0 { return fmt.Errorf("too many arguments") } - if cmd.Status { - if cmd.Github || cmd.Email != "" || cmd.NoSpawn || cmd.Env { + if status { + if github || email != "" || nospawn || env { return fmt.Errorf("the -status option must be used alone") } } else { - if cmd.Github { - if cmd.Email != "" || cmd.Env { + if github { + if email != "" || env { return fmt.Errorf("the -github option cannot be used with -email or -env") } - } else if cmd.Email != "" { - if cmd.Env { + } else if email != "" { + if env { return fmt.Errorf("the -email option cannot be used with -env") } - addr, err := utils.ValidateEmail(cmd.Email) + addr, err := utils.ValidateEmail(email) if err != nil { return fmt.Errorf("invalid email address: %w", err) } - cmd.Email = addr - } else if !cmd.Env { + email = addr + } else if !env { fmt.Println("no provided login method, defaulting to GitHub") - cmd.Github = true + github = true } - if cmd.NoSpawn && !cmd.Github { + if nospawn && !github { return fmt.Errorf("the -no-spawn option is only valid with -github") } } - return nil -} - -type Login struct { - subcommands.SubcommandBase - - Status bool - Github bool - Email string - Env bool - NoSpawn bool -} - -func (cmd *Login) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - if cmd.Status { + if status { token, _ := ctx.GetCookies().GetAuthToken() status := "not logged in" if token != "" { status = "logged in" } fmt.Fprintf(ctx.Stdout, "%s\n", status) - return 0, nil + return nil } var token string - if cmd.Env { + if env { if token = ctx.GetCookies().GetAuthEnvToken(); token == "" { - return 1, fmt.Errorf("no auth token found in environment variable PLAKAR_TOKEN") + return fmt.Errorf("no auth token found in environment variable PLAKAR_TOKEN") } } else { - flow, err := plogin.NewLoginFlow(ctx, cmd.NoSpawn) + flow, err := plogin.NewLoginFlow(ctx, nospawn) if err != nil { - return 1, err + return err } defer flow.Close() - if cmd.Github { + if github { token, err = flow.Run("github", map[string]string{}) - } else if cmd.Email != "" { - token, err = flow.Run("email", map[string]string{"email": cmd.Email}) + } else if email != "" { + token, err = flow.Run("email", map[string]string{"email": email}) } else { - return 1, fmt.Errorf("invalid login method") + return fmt.Errorf("invalid login method") } if err != nil { - return 1, err + return err } } if err := ctx.GetCookies().PutAuthToken(token); err != nil { - return 1, fmt.Errorf("failed to store token in cache: %w", err) + return fmt.Errorf("failed to store token in cache: %w", err) } - return 0, nil + return nil } diff --git a/subcommands/login/logout.go b/subcommands/login/logout.go index 66f3b8e31..52962b8e3 100644 --- a/subcommands/login/logout.go +++ b/subcommands/login/logout.go @@ -27,10 +27,10 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &Logout{} }, subcommands.BeforeRepositoryOpen, "logout") + subcommands.Register(Logout, subcommands.BeforeRepositoryOpen, "logout") } -func (cmd *Logout) Parse(ctx *appcontext.AppContext, args []string) error { +func Logout(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("logout", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s\n", flags.Name()) @@ -41,16 +41,9 @@ func (cmd *Logout) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("too many arguments") } - return nil -} - -type Logout struct { - subcommands.SubcommandBase -} - -func (cmd *Logout) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { if err := ctx.GetCookies().DeleteAuthToken(); err != nil { - return 1, err + return err } - return 0, nil + + return nil } diff --git a/subcommands/login/token-create.go b/subcommands/login/token-create.go index 8fd2ee1b7..f45cffd8b 100644 --- a/subcommands/login/token-create.go +++ b/subcommands/login/token-create.go @@ -26,15 +26,11 @@ import ( "github.com/PlakarKorp/plakar/subcommands" ) -type TokenCreate struct { - subcommands.SubcommandBase -} - func init() { - subcommands.Register(func() subcommands.Subcommand { return &TokenCreate{} }, subcommands.BeforeRepositoryOpen, "token", "create") + subcommands.Register(TokenCreate, subcommands.BeforeRepositoryOpen, "token", "create") } -func (cmd *TokenCreate) Parse(ctx *appcontext.AppContext, args []string) error { +func TokenCreate(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("token create", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: token create\n") @@ -44,15 +40,12 @@ func (cmd *TokenCreate) Parse(ctx *appcontext.AppContext, args []string) error { if flags.NArg() > 0 { return fmt.Errorf("Too many arguments") } - return nil -} -func (cmd *TokenCreate) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { token, err := login.DeriveToken(ctx) if err != nil { - return 1, err + return err } fmt.Fprintln(ctx.Stdout, token) - return 0, nil + return nil } diff --git a/subcommands/ls/ls.go b/subcommands/ls/ls.go index e0c0c3c2a..ebbc49762 100644 --- a/subcommands/ls/ls.go +++ b/subcommands/ls/ls.go @@ -37,11 +37,16 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &Ls{} }, 0, "ls") + subcommands.Register(Ls, 0, "ls") } -func (cmd *Ls) Parse(ctx *appcontext.AppContext, args []string) error { - cmd.LocateOptions = locate.NewDefaultLocateOptions() +func Ls(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + locateopts = locate.NewDefaultLocateOptions() + recursive bool + displayUUID bool + showTags bool + ) flags := flag.NewFlagSet("ls", flag.ExitOnError) flags.Usage = func() { @@ -50,53 +55,32 @@ func (cmd *Ls) Parse(ctx *appcontext.AppContext, args []string) error { flags.PrintDefaults() } - flags.BoolVar(&cmd.DisplayUUID, "uuid", false, "display uuid instead of short ID") - flags.BoolVar(&cmd.Recursive, "recursive", false, "recursive listing") - flags.BoolVar(&cmd.ShowTags, "tags", false, "show tags") + flags.BoolVar(&displayUUID, "uuid", false, "display uuid instead of short ID") + flags.BoolVar(&recursive, "recursive", false, "recursive listing") + flags.BoolVar(&showTags, "tags", false, "show tags") - cmd.LocateOptions.InstallLocateFlags(flags) + locateopts.InstallLocateFlags(flags) flags.Parse(args) switch flags.NArg() { - case 0: // nothing + case 0: + return list_snapshots(ctx, repo, locateopts, showTags, displayUUID) case 1: - cmd.Path = []string{flags.Arg(0)} + return list_snapshot(ctx, repo, flags.Arg(0), recursive) default: return fmt.Errorf("too many arguments") } - - cmd.RepositorySecret = ctx.GetSecret() - return nil -} - -type Ls struct { - subcommands.SubcommandBase - - LocateOptions *locate.LocateOptions - Recursive bool - DisplayUUID bool - Path []string - - ShowTags bool -} - -func (cmd *Ls) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - if len(cmd.Path) == 0 { - if err := cmd.list_snapshots(ctx, repo); err != nil { - return 1, err - } - return 0, nil - } - - if err := cmd.list_snapshot(ctx, repo, cmd.Path[0], cmd.Recursive); err != nil { - return 1, err - } - return 0, nil } -func (cmd *Ls) list_snapshots(ctx *appcontext.AppContext, repo *repository.Repository) error { - snapshotIDs, err := locate.LocateSnapshotIDs(repo, cmd.LocateOptions) +func list_snapshots( + ctx *appcontext.AppContext, + repo *repository.Repository, + locateopts *locate.LocateOptions, + showTags bool, + displayUUID bool, +) error { + snapshotIDs, err := locate.LocateSnapshotIDs(repo, locateopts) if err != nil { return fmt.Errorf("ls: could not fetch snapshots list: %w", err) } @@ -108,14 +92,14 @@ func (cmd *Ls) list_snapshots(ctx *appcontext.AppContext, repo *repository.Repos } tags := "" - if cmd.ShowTags && len(snap.Header.Tags) > 0 { + if showTags && len(snap.Header.Tags) > 0 { tagList := strings.Join(snap.Header.Tags, ",") if tagList != "" { tags = " tags=" + strings.Join(snap.Header.Tags, ",") } } - if !cmd.DisplayUUID { + if !displayUUID { fmt.Fprintf(ctx.Stdout, "%s %10s%10s%10s %s%s\n", snap.Header.Timestamp.UTC().Format(time.RFC3339), hex.EncodeToString(snap.Header.GetIndexShortID()), @@ -139,7 +123,7 @@ func (cmd *Ls) list_snapshots(ctx *appcontext.AppContext, repo *repository.Repos return nil } -func (cmd *Ls) list_snapshot(ctx *appcontext.AppContext, repo *repository.Repository, snapshotPath string, recursive bool) error { +func list_snapshot(ctx *appcontext.AppContext, repo *repository.Repository, snapshotPath string, recursive bool) error { snap, pathname, err := locate.OpenSnapshotByPath(repo, snapshotPath) if err != nil { return err diff --git a/subcommands/maintenance/maintenance.go b/subcommands/maintenance/maintenance.go index 2a0ff30d7..344287c9d 100644 --- a/subcommands/maintenance/maintenance.go +++ b/subcommands/maintenance/maintenance.go @@ -37,31 +37,39 @@ import ( const defaultDuration = 7 * 24 * time.Hour func init() { - subcommands.Register(func() subcommands.Subcommand { return &Maintenance{} }, 0, "maintenance") + subcommands.Register(Maintenance, 0, "maintenance") } -func (cmd *Maintenance) Parse(ctx *appcontext.AppContext, args []string) error { +func Maintenance(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("maintenance", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s\n", flags.Name()) } flags.Parse(args) - cmd.RepositorySecret = ctx.GetSecret() + // This need to be configurable per repo, but we don't have a mechanism yet (comes in a PR soon!) + duration, err := time.ParseDuration(os.Getenv("PLAKAR_GRACEPERIOD")) + if err != nil { + duration = defaultDuration + } - return nil -} + cmd := &maintenance{ + repository: repo, + maintenanceID: objects.RandomMAC(), + cutoff: time.Now().Add(-duration), + } -type Maintenance struct { - subcommands.SubcommandBase + return cmd.Execute(ctx, repo) +} +type maintenance struct { repository *repository.Repository maintenanceID objects.MAC cutoff time.Time } // Builds the local cache of snapshot -> packfiles -func (cmd *Maintenance) updateCache(ctx *appcontext.AppContext, cache *caching.MaintenanceCache) error { +func (cmd *maintenance) updateCache(ctx *appcontext.AppContext, cache *caching.MaintenanceCache) error { wg := new(errgroup.Group) wg.SetLimit(ctx.MaxConcurrency) @@ -132,7 +140,7 @@ func (cmd *Maintenance) updateCache(ctx *appcontext.AppContext, cache *caching.M return nil } -func (cmd *Maintenance) colourPass(ctx *appcontext.AppContext, cache *caching.MaintenanceCache) error { +func (cmd *maintenance) colourPass(ctx *appcontext.AppContext, cache *caching.MaintenanceCache) error { var packfiles map[objects.MAC]struct{} = make(map[objects.MAC]struct{}) for packfileMAC := range cmd.repository.ListPackfiles() { packfiles[packfileMAC] = struct{}{} @@ -241,7 +249,7 @@ func (cmd *Maintenance) colourPass(ctx *appcontext.AppContext, cache *caching.Ma return nil } -func (cmd *Maintenance) sweepPass(ctx *appcontext.AppContext, cache *caching.MaintenanceCache) error { +func (cmd *maintenance) sweepPass(ctx *appcontext.AppContext, cache *caching.MaintenanceCache) error { noDeletion, _ := strconv.ParseBool(os.Getenv("PLAKAR_NODELETION")) stateID := objects.RandomMAC() @@ -318,7 +326,7 @@ func (cmd *Maintenance) sweepPass(ctx *appcontext.AppContext, cache *caching.Mai return nil } -func (cmd *Maintenance) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { +func (cmd *maintenance) Execute(ctx *appcontext.AppContext, repo *repository.Repository) error { // the maintenance algorithm is a bit tricky and needs to be done in the correct sequence, // here's what it has to do: // @@ -330,48 +338,37 @@ func (cmd *Maintenance) Execute(ctx *appcontext.AppContext, repo *repository.Rep // 6. remove the packfile in repository once it's flagged as deleted AND all snapshots have been `snapshot.Check`-ed // 7. rebuild a new aggregate state with a new serial without the deleted packfiles - cmd.repository = repo - - // This need to be configurable per repo, but we don't have a mechanism yet (comes in a PR soon!) - duration, err := time.ParseDuration(os.Getenv("PLAKAR_GRACEPERIOD")) - if err != nil { - duration = defaultDuration - } - - cmd.cutoff = time.Now().Add(-duration) - - cmd.maintenanceID = objects.RandomMAC() done, err := cmd.Lock() if err != nil { - return 1, err + return err } defer cmd.Unlock(done) cache, err := repo.AppContext().GetCache().Maintenance(repo.Configuration().RepositoryID) if err != nil { fmt.Fprintf(ctx.Stderr, "maintenance: Failed to open local cache %s\n", err) - return 1, err + return err } if err := cmd.updateCache(ctx, cache); err != nil { fmt.Fprintf(ctx.Stderr, "maintenance: Failed to update local cache %s\n", err) - return 1, err + return err } if err := cmd.colourPass(ctx, cache); err != nil { fmt.Fprintf(ctx.Stderr, "maintenance: Colouring pass failed %s\n", err) - return 1, err + return err } if err := cmd.sweepPass(ctx, cache); err != nil { fmt.Fprintf(ctx.Stderr, "maintenance: Sweep pass failed %s\n", err) - return 1, err + return err } - return 0, nil + return nil } -func (cmd *Maintenance) Lock() (chan bool, error) { +func (cmd *maintenance) Lock() (chan bool, error) { lockless, _ := strconv.ParseBool(os.Getenv("PLAKAR_LOCKLESS")) lockDone := make(chan bool) if lockless { @@ -463,6 +460,6 @@ func (cmd *Maintenance) Lock() (chan bool, error) { return lockDone, nil } -func (cmd *Maintenance) Unlock(ping chan bool) { +func (cmd *maintenance) Unlock(ping chan bool) { close(ping) } diff --git a/subcommands/mount/fuse/fuse.go b/subcommands/mount/fuse/fuse.go index b6c34516b..4ab5dfea1 100644 --- a/subcommands/mount/fuse/fuse.go +++ b/subcommands/mount/fuse/fuse.go @@ -36,20 +36,20 @@ import ( "github.com/google/uuid" ) -func ExecuteFUSE(ctx *appcontext.AppContext, repo *repository.Repository, mountpoint string, locateOptions *locate.LocateOptions, chrootfs fs.FS, allowOthers bool) (int, error) { +func ExecuteFUSE(ctx *appcontext.AppContext, repo *repository.Repository, mountpoint string, locateOptions *locate.LocateOptions, chrootfs fs.FS, allowOthers bool) error { if mountpoint == "" { mountpoint = filepath.Join(ctx.CWD, uuid.New().String()) if err := os.MkdirAll(mountpoint, 0700); err != nil { - return 1, err + return err } defer os.Remove(mountpoint) } else { mp, err := looksLikeMountpoint(mountpoint) if err != nil { - return 1, err + return err } if mp { - return 1, fmt.Errorf("%s already looks like a mountpoint; refusing to mount over it", mountpoint) + return fmt.Errorf("%s already looks like a mountpoint; refusing to mount over it", mountpoint) } } @@ -63,7 +63,7 @@ func ExecuteFUSE(ctx *appcontext.AppContext, repo *repository.Repository, mountp } c, err := fuse.Mount(mountpoint, mountOptions...) if err != nil { - return 1, fmt.Errorf("mount: %v", err) + return fmt.Errorf("mount: %v", err) } defer c.Close() @@ -80,13 +80,13 @@ func ExecuteFUSE(ctx *appcontext.AppContext, repo *repository.Repository, mountp err = fusefs.Serve(c, plakarfs.NewFS(ctx, repo, locateOptions, chrootfs)) if err != nil { - return 1, err + return err } <-c.Ready if err := c.MountError; err != nil { - return 1, err + return err } - return 0, nil + return nil } func looksLikeMountpoint(p string) (bool, error) { diff --git a/subcommands/mount/fuse/fuse_dragonfly.go b/subcommands/mount/fuse/fuse_dragonfly.go index a10aa027d..f05f49d41 100644 --- a/subcommands/mount/fuse/fuse_dragonfly.go +++ b/subcommands/mount/fuse/fuse_dragonfly.go @@ -25,6 +25,6 @@ import ( "github.com/PlakarKorp/plakar/appcontext" ) -func ExecuteFUSE(ctx *appcontext.AppContext, repo *repository.Repository, mountpoint string, locateOptions *locate.LocateOptions, chrootfs fs.FS, allowOthers bool) (int, error) { - return 1, fmt.Errorf("mount not supported on %s", ctx.OperatingSystem) +func ExecuteFUSE(ctx *appcontext.AppContext, repo *repository.Repository, mountpoint string, locateOptions *locate.LocateOptions, chrootfs fs.FS, allowOthers bool) error { + return fmt.Errorf("mount not supported on %s", ctx.OperatingSystem) } diff --git a/subcommands/mount/fuse/fuse_freebsd.go b/subcommands/mount/fuse/fuse_freebsd.go index a10aa027d..f05f49d41 100644 --- a/subcommands/mount/fuse/fuse_freebsd.go +++ b/subcommands/mount/fuse/fuse_freebsd.go @@ -25,6 +25,6 @@ import ( "github.com/PlakarKorp/plakar/appcontext" ) -func ExecuteFUSE(ctx *appcontext.AppContext, repo *repository.Repository, mountpoint string, locateOptions *locate.LocateOptions, chrootfs fs.FS, allowOthers bool) (int, error) { - return 1, fmt.Errorf("mount not supported on %s", ctx.OperatingSystem) +func ExecuteFUSE(ctx *appcontext.AppContext, repo *repository.Repository, mountpoint string, locateOptions *locate.LocateOptions, chrootfs fs.FS, allowOthers bool) error { + return fmt.Errorf("mount not supported on %s", ctx.OperatingSystem) } diff --git a/subcommands/mount/fuse/fuse_netbsd.go b/subcommands/mount/fuse/fuse_netbsd.go index a10aa027d..f05f49d41 100644 --- a/subcommands/mount/fuse/fuse_netbsd.go +++ b/subcommands/mount/fuse/fuse_netbsd.go @@ -25,6 +25,6 @@ import ( "github.com/PlakarKorp/plakar/appcontext" ) -func ExecuteFUSE(ctx *appcontext.AppContext, repo *repository.Repository, mountpoint string, locateOptions *locate.LocateOptions, chrootfs fs.FS, allowOthers bool) (int, error) { - return 1, fmt.Errorf("mount not supported on %s", ctx.OperatingSystem) +func ExecuteFUSE(ctx *appcontext.AppContext, repo *repository.Repository, mountpoint string, locateOptions *locate.LocateOptions, chrootfs fs.FS, allowOthers bool) error { + return fmt.Errorf("mount not supported on %s", ctx.OperatingSystem) } diff --git a/subcommands/mount/fuse/fuse_openbsd.go b/subcommands/mount/fuse/fuse_openbsd.go index a10aa027d..f05f49d41 100644 --- a/subcommands/mount/fuse/fuse_openbsd.go +++ b/subcommands/mount/fuse/fuse_openbsd.go @@ -25,6 +25,6 @@ import ( "github.com/PlakarKorp/plakar/appcontext" ) -func ExecuteFUSE(ctx *appcontext.AppContext, repo *repository.Repository, mountpoint string, locateOptions *locate.LocateOptions, chrootfs fs.FS, allowOthers bool) (int, error) { - return 1, fmt.Errorf("mount not supported on %s", ctx.OperatingSystem) +func ExecuteFUSE(ctx *appcontext.AppContext, repo *repository.Repository, mountpoint string, locateOptions *locate.LocateOptions, chrootfs fs.FS, allowOthers bool) error { + return fmt.Errorf("mount not supported on %s", ctx.OperatingSystem) } diff --git a/subcommands/mount/fuse/fuse_windows.go b/subcommands/mount/fuse/fuse_windows.go index a10aa027d..f05f49d41 100644 --- a/subcommands/mount/fuse/fuse_windows.go +++ b/subcommands/mount/fuse/fuse_windows.go @@ -25,6 +25,6 @@ import ( "github.com/PlakarKorp/plakar/appcontext" ) -func ExecuteFUSE(ctx *appcontext.AppContext, repo *repository.Repository, mountpoint string, locateOptions *locate.LocateOptions, chrootfs fs.FS, allowOthers bool) (int, error) { - return 1, fmt.Errorf("mount not supported on %s", ctx.OperatingSystem) +func ExecuteFUSE(ctx *appcontext.AppContext, repo *repository.Repository, mountpoint string, locateOptions *locate.LocateOptions, chrootfs fs.FS, allowOthers bool) error { + return fmt.Errorf("mount not supported on %s", ctx.OperatingSystem) } diff --git a/subcommands/mount/http/http.go b/subcommands/mount/http/http.go index 4fd60c96a..5f0c1dc6b 100644 --- a/subcommands/mount/http/http.go +++ b/subcommands/mount/http/http.go @@ -42,7 +42,7 @@ import ( type ListFn func(ctx context.Context, w http.ResponseWriter, r *http.Request) type OpenFn func(ctx context.Context, snapshot string) (fs.FS, error) -func ExecuteHTTP(ctx *appcontext.AppContext, repo *repository.Repository, mountpoint string, locateOptions *locate.LocateOptions, chrootfs fs.FS) (int, error) { +func ExecuteHTTP(ctx *appcontext.AppContext, repo *repository.Repository, mountpoint string, locateOptions *locate.LocateOptions, chrootfs fs.FS) error { addr := strings.TrimPrefix(mountpoint, "http://") var handler http.Handler @@ -126,12 +126,12 @@ func ExecuteHTTP(ctx *appcontext.AppContext, repo *repository.Repository, mountp defer cancel() _ = srv.Shutdown(shutdownCtx) <-errCh // wait for ListenAndServe to return - return 0, nil + return nil case err := <-errCh: if err != nil { - return 1, err + return err } - return 0, nil + return nil } } diff --git a/subcommands/mount/http/http_test.go b/subcommands/mount/http/http_test.go index 5f8e87191..1cce64c5e 100644 --- a/subcommands/mount/http/http_test.go +++ b/subcommands/mount/http/http_test.go @@ -123,16 +123,9 @@ func TestExecuteHTTPChrootStartServeShutdown(t *testing.T) { } addr := freePort(t) - errCh := make(chan struct { - status int - err error - }, 1) + errCh := make(chan error, 1) go func() { - status, err := ExecuteHTTP(ctx, repo, "http://"+addr, nil, chroot) - errCh <- struct { - status int - err error - }{status, err} + errCh <- ExecuteHTTP(ctx, repo, "http://"+addr, nil, chroot) }() base := "http://" + addr @@ -152,9 +145,8 @@ func TestExecuteHTTPChrootStartServeShutdown(t *testing.T) { ctx.GetInner().Cancel(nil) select { - case res := <-errCh: - require.NoError(t, res.err) - require.Equal(t, 0, res.status) + case err := <-errCh: + require.NoError(t, err) case <-time.After(8 * time.Second): t.Fatal("ExecuteHTTP did not return after context cancellation") } @@ -166,7 +158,6 @@ func TestExecuteHTTPListenError(t *testing.T) { repo, ctx := ptesting.GenerateRepository(t, nil, nil, nil) chroot := fstest.MapFS{} - status, err := ExecuteHTTP(ctx, repo, "http://256.256.256.256:99999", nil, chroot) + err := ExecuteHTTP(ctx, repo, "http://256.256.256.256:99999", nil, chroot) require.Error(t, err) - require.Equal(t, 1, status) } diff --git a/subcommands/mount/mount.go b/subcommands/mount/mount.go index 7bbab6880..e9765ee46 100644 --- a/subcommands/mount/mount.go +++ b/subcommands/mount/mount.go @@ -30,68 +30,58 @@ import ( "github.com/PlakarKorp/plakar/subcommands/mount/http" ) -type Mount struct { - subcommands.SubcommandBase - - Mountpoint string - LocateOptions *locate.LocateOptions - AllowOthers bool - - SnapshotPath string - - fs fs.FS -} - func init() { - subcommands.Register(func() subcommands.Subcommand { return &Mount{} }, 0, "mount") + subcommands.Register(Mount, 0, "mount") } -func (cmd *Mount) Parse(ctx *appcontext.AppContext, args []string) error { - cmd.LocateOptions = locate.NewDefaultLocateOptions() +func Mount(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + Mountpoint string + LocateOptions *locate.LocateOptions + AllowOthers bool + + SnapshotPath string + ) + + LocateOptions = locate.NewDefaultLocateOptions() flags := flag.NewFlagSet("mount", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s [-to PATH] [snapshotID]\n", flags.Name()) } - flags.StringVar(&cmd.Mountpoint, "to", "", "mount point") - flags.BoolVar(&cmd.AllowOthers, "allow-others", false, "allow other users to access the mount") - cmd.LocateOptions.InstallLocateFlags(flags) + flags.StringVar(&Mountpoint, "to", "", "mount point") + flags.BoolVar(&AllowOthers, "allow-others", false, "allow other users to access the mount") + LocateOptions.InstallLocateFlags(flags) flags.Parse(args) - cmd.RepositorySecret = ctx.GetSecret() - if flags.NArg() == 1 { // snapshot(s) level, reset LocateOptions - cmd.LocateOptions = locate.NewDefaultLocateOptions() - cmd.SnapshotPath = flags.Arg(0) + LocateOptions = locate.NewDefaultLocateOptions() + SnapshotPath = flags.Arg(0) } - return nil -} - -func (cmd *Mount) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { var chrootFS fs.FS - if cmd.SnapshotPath != "" { - snap, path, err := locate.OpenSnapshotByPath(repo, cmd.SnapshotPath) + if SnapshotPath != "" { + snap, path, err := locate.OpenSnapshotByPath(repo, SnapshotPath) if err != nil { - return 1, err + return err } pvfs, err := snap.Filesystem() if err != nil { - return 1, err + return err } subFS, err := fs.Sub(pvfs, path[1:]) if err != nil { - return 1, err + return err } chrootFS = subFS } - if strings.HasPrefix(cmd.Mountpoint, "http://") { - return http.ExecuteHTTP(ctx, repo, cmd.Mountpoint, cmd.LocateOptions, chrootFS) + if strings.HasPrefix(Mountpoint, "http://") { + return http.ExecuteHTTP(ctx, repo, Mountpoint, LocateOptions, chrootFS) } - return fuse.ExecuteFUSE(ctx, repo, cmd.Mountpoint, cmd.LocateOptions, chrootFS, cmd.AllowOthers) + return fuse.ExecuteFUSE(ctx, repo, Mountpoint, LocateOptions, chrootFS, AllowOthers) } diff --git a/subcommands/prune/prune.go b/subcommands/prune/prune.go index 4c4def051..99feff644 100644 --- a/subcommands/prune/prune.go +++ b/subcommands/prune/prune.go @@ -36,54 +36,8 @@ import ( "github.com/dustin/go-humanize" ) -type Prune struct { - subcommands.SubcommandBase - - LocateOptions *locate.LocateOptions - - Apply bool -} - func init() { - subcommands.Register(func() subcommands.Subcommand { return &Prune{} }, 0, "prune") -} - -func (cmd *Prune) Parse(ctx *appcontext.AppContext, args []string) error { - policyName := "" - cmd.LocateOptions = locate.NewDefaultLocateOptions() - policyOverride := locate.NewDefaultLocateOptions() - - flags := flag.NewFlagSet("prune", flag.ExitOnError) - flags.Usage = func() { - fmt.Fprintf(flags.Output(), "Usage: %s [OPTIONS] SNAPSHOT...\n", flags.Name()) - fmt.Fprintf(flags.Output(), "\nOPTIONS:\n") - flags.PrintDefaults() - } - flags.BoolVar(&cmd.Apply, "apply", false, "do the actual removal") - flags.StringVar(&policyName, "policy", "", "policy to use") - policyOverride.InstallLocateFlags(flags) - flags.Parse(args) - - if policyName != "" { - configFile := filepath.Join(ctx.ConfigDir, "policies.yml") - cfg, err := utils.LoadPolicyConfigFile(configFile) - if err != nil { - return fmt.Errorf("failed to load policies config: %w", err) - } - if !cfg.Has(policyName) { - return fmt.Errorf("policy %q not found", policyName) - } - cfg.ApplyConfig(policyName, cmd.LocateOptions) - } - mergePolicyOptions(cmd.LocateOptions, policyOverride) - - if flags.NArg() == 0 && cmd.LocateOptions.Empty() { - return fmt.Errorf("no filter specified, not going to prune everything") - } - - cmd.RepositorySecret = ctx.GetSecret() - - return nil + subcommands.Register(Prune, 0, "prune") } // mergePolicyOptions layers CLI overrides (from) onto policy-loaded options @@ -179,10 +133,45 @@ type planEntry struct { action string // "keep" or "delete" } -func (cmd *Prune) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - _, reasons, err := locate.Match(repo, cmd.LocateOptions) +func Prune(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + locopts = locate.NewDefaultLocateOptions() + policyOverride = locate.NewDefaultLocateOptions() + apply bool + policyName string + ) + + flags := flag.NewFlagSet("prune", flag.ExitOnError) + flags.Usage = func() { + fmt.Fprintf(flags.Output(), "Usage: %s [OPTIONS] SNAPSHOT...\n", flags.Name()) + fmt.Fprintf(flags.Output(), "\nOPTIONS:\n") + flags.PrintDefaults() + } + flags.BoolVar(&apply, "apply", false, "do the actual removal") + flags.StringVar(&policyName, "policy", "", "policy to use") + policyOverride.InstallLocateFlags(flags) + flags.Parse(args) + + if policyName != "" { + configFile := filepath.Join(ctx.ConfigDir, "policies.yml") + cfg, err := utils.LoadPolicyConfigFile(configFile) + if err != nil { + return fmt.Errorf("failed to load policies config: %w", err) + } + if !cfg.Has(policyName) { + return fmt.Errorf("policy %q not found", policyName) + } + cfg.ApplyConfig(policyName, locopts) + } + mergePolicyOptions(locopts, policyOverride) + + if flags.NArg() == 0 && locopts.Empty() { + return fmt.Errorf("no filter specified, not going to prune everything") + } + + _, reasons, err := locate.Match(repo, locopts) if err != nil { - return 1, err + return err } toDelete := make([]objects.MAC, 0, len(reasons)) @@ -224,7 +213,7 @@ func (cmd *Prune) Execute(ctx *appcontext.AppContext, repo *repository.Repositor entries = append(entries, entry) } - if !cmd.Apply { + if !apply { // Sort newest-first; unknown timestamps (IsZero) go last sort.SliceStable(entries, func(i, j int) bool { ti, tj := entries[i].ts, entries[j].ts @@ -256,11 +245,11 @@ func (cmd *Prune) Execute(ctx *appcontext.AppContext, repo *repository.Repositor e.action, e.prefix, r.Rule, r.Bucket, r.Rank, r.Cap) } } - return 0, nil + return nil } if len(toDelete) == 0 { - return 0, nil + return nil } errors := 0 @@ -280,8 +269,8 @@ func (cmd *Prune) Execute(ctx *appcontext.AppContext, repo *repository.Repositor wg.Wait() if errors != 0 { - return 1, fmt.Errorf("failed to remove %d snapshots", errors) + return fmt.Errorf("failed to remove %d snapshots", errors) } - return 0, nil + return nil } diff --git a/subcommands/ptar/factory_test.go b/subcommands/ptar/factory_test.go index d5978eede..46c9d1825 100644 --- a/subcommands/ptar/factory_test.go +++ b/subcommands/ptar/factory_test.go @@ -10,7 +10,6 @@ import ( // TestRegisteredFactory looks the command up through the registry, which // invokes the factory closure registered in init(). func TestRegisteredFactory(t *testing.T) { - cmd, _, _ := subcommands.Lookup([]string{"ptar"}) + cmd, _, _, _ := subcommands.Lookup([]string{"ptar"}) require.NotNil(t, cmd) - require.IsType(t, &Ptar{}, cmd) } diff --git a/subcommands/ptar/ptar.go b/subcommands/ptar/ptar.go index e90a5e326..d778d2704 100644 --- a/subcommands/ptar/ptar.go +++ b/subcommands/ptar/ptar.go @@ -44,25 +44,8 @@ import ( "github.com/google/uuid" ) -type Ptar struct { - subcommands.SubcommandBase - - KlosetPath string - KlosetUUID uuid.UUID - - AllowWeak bool - Hashing string - NoEncryption bool - NoCompression bool - Overwrite bool - - SyncTargets listFlag - SyncSecrets [][]byte - BackupTargets listFlag -} - func init() { - subcommands.Register(func() subcommands.Subcommand { return &Ptar{} }, subcommands.BeforeRepositoryWithStorage, "ptar") + subcommands.Register(Ptar, subcommands.BeforeRepositoryWithStorage, "ptar") } type listFlag []string @@ -79,8 +62,23 @@ func (l *listFlag) Set(value string) error { return nil } -func (cmd *Ptar) Parse(ctx *appcontext.AppContext, args []string) error { - cmd.KlosetUUID = uuid.Must(uuid.NewRandom()) +func Ptar(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + klosetPath string + klosetUUID = uuid.Must(uuid.NewRandom()) + + //AllowWeak bool + algo string + plaintext bool + nocompression bool + overwrite bool + + syncTargets listFlag + syncSecrets [][]byte + backupTargets listFlag + + repoSecret []byte + ) flags := flag.NewFlagSet("ptar", flag.ExitOnError) flags.Usage = func() { @@ -89,29 +87,29 @@ func (cmd *Ptar) Parse(ctx *appcontext.AppContext, args []string) error { flags.PrintDefaults() } - flags.StringVar(&cmd.Hashing, "hashing", hashing.DEFAULT_HASHING_ALGORITHM, "hashing algorithm to use for digests") - flags.BoolVar(&cmd.NoEncryption, "plaintext", false, "disable transparent encryption") - flags.BoolVar(&cmd.NoCompression, "no-compression", false, "disable transparent compression") - flags.BoolVar(&cmd.Overwrite, "overwrite", false, "overwrite the ptar archive if it already exists") - flags.Var(&cmd.SyncTargets, "k", "add a kloset location to include in the ptar archive (can be specified multiple times)") - flags.Var(&cmd.SyncTargets, "kloset", "add a kloset location to include in the ptar archive (can be specified multiple times)") - flags.StringVar(&cmd.KlosetPath, "o", "", "name of the ptar archive to create") + flags.StringVar(&algo, "hashing", hashing.DEFAULT_HASHING_ALGORITHM, "hashing algorithm to use for digests") + flags.BoolVar(&plaintext, "plaintext", false, "disable transparent encryption") + flags.BoolVar(&nocompression, "no-compression", false, "disable transparent compression") + flags.BoolVar(&overwrite, "overwrite", false, "overwrite the ptar archive if it already exists") + flags.Var(&syncTargets, "k", "add a kloset location to include in the ptar archive (can be specified multiple times)") + flags.Var(&syncTargets, "kloset", "add a kloset location to include in the ptar archive (can be specified multiple times)") + flags.StringVar(&klosetPath, "o", "", "name of the ptar archive to create") flags.Parse(args) - if cmd.KlosetPath == "" { + if klosetPath == "" { return fmt.Errorf("%s: -o option must be specified", flag.CommandLine.Name()) } - if len(cmd.SyncTargets) == 0 && flags.NArg() == 0 { - cmd.BackupTargets = []string{ctx.CWD} + if len(syncTargets) == 0 && flags.NArg() == 0 { + backupTargets = []string{ctx.CWD} } if len(flags.Args()) > 0 { - cmd.BackupTargets = make([]string, len(flags.Args())) - copy(cmd.BackupTargets, flags.Args()) + backupTargets = make([]string, len(flags.Args())) + copy(backupTargets, flags.Args()) } - for _, syncTarget := range cmd.SyncTargets { + for _, syncTarget := range syncTargets { var peerSecret []byte storeConfig, err := ctx.Config.GetRepository(syncTarget) @@ -165,14 +163,14 @@ func (cmd *Ptar) Parse(ctx *appcontext.AppContext, args []string) error { if err != nil { return err } - cmd.SyncSecrets = append(cmd.SyncSecrets, peerSecret) + syncSecrets = append(syncSecrets, peerSecret) } - if hashing.GetHasher(strings.ToUpper(cmd.Hashing)) == nil { + if hashing.GetHasher(strings.ToUpper(algo)) == nil { return fmt.Errorf("%s: unknown hashing algorithm", flag.CommandLine.Name()) } - if !cmd.NoEncryption { + if !plaintext { var passphrase []byte envPassphrase, ok := os.LookupEnv("PLAKAR_PASSPHRASE") @@ -194,42 +192,38 @@ func (cmd *Ptar) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("can't encrypt the repository with an empty passphrase") } - cmd.RepositorySecret = passphrase + repoSecret = passphrase } - return nil -} - -func (cmd *Ptar) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { storageConfiguration := storage.NewConfiguration() - storageConfiguration.RepositoryID = cmd.KlosetUUID + storageConfiguration.RepositoryID = klosetUUID - if cmd.NoCompression { + if nocompression { storageConfiguration.Compression = nil } else { storageConfiguration.Compression = compression.NewDefaultConfiguration() } - hashingConfiguration, err := hashing.LookupDefaultConfiguration(strings.ToUpper(cmd.Hashing)) + hashingConfiguration, err := hashing.LookupDefaultConfiguration(strings.ToUpper(algo)) if err != nil { - return 1, err + return err } storageConfiguration.Hashing = *hashingConfiguration var hasher hash.Hash var key []byte - if !cmd.NoEncryption { + if !plaintext { storageConfiguration.Encryption = encryption.NewDefaultConfiguration() key, err = encryption.DeriveKey(storageConfiguration.Encryption.KDFParams, - cmd.RepositorySecret) + repoSecret) if err != nil { - return 1, err + return err } canary, err := encryption.DeriveCanary(storageConfiguration.Encryption, key) if err != nil { - return 1, err + return err } storageConfiguration.Encryption.Canary = canary hasher = hashing.GetMACHasher(storage.DEFAULT_HASHING_ALGORITHM, key) @@ -243,94 +237,94 @@ func (cmd *Ptar) Execute(ctx *appcontext.AppContext, repo *repository.Repository serializedConfig, err := storageConfiguration.ToBytes() if err != nil { - return 1, err + return err } rd, err := storage.Serialize(hasher, resources.RT_CONFIG, versioning.GetCurrentVersion(resources.RT_CONFIG), bytes.NewReader(serializedConfig)) if err != nil { - return 1, err + return err } wrappedConfig, err := io.ReadAll(rd) if err != nil { - return 1, err + return err } - location := cmd.KlosetPath + location := klosetPath if !strings.HasPrefix(location, "ptar:") { location = "ptar://" + location } noSchemeLocation := strings.TrimPrefix(location, "ptar://") if _, err := os.Stat(noSchemeLocation); err == nil { - if !cmd.Overwrite { - return 1, fmt.Errorf("ptar archive %s already exists, use -overwrite to overwrite it", noSchemeLocation) + if !overwrite { + return fmt.Errorf("ptar archive %s already exists, use -overwrite to overwrite it", noSchemeLocation) } else { if err := os.Remove(noSchemeLocation); err != nil { - return 1, fmt.Errorf("could not remove existing ptar archive %s: %w", noSchemeLocation, err) + return fmt.Errorf("could not remove existing ptar archive %s: %w", noSchemeLocation, err) } } } st, err := storage.Create(ctx.GetInner(), map[string]string{"location": location}, wrappedConfig) if err != nil { - return 1, err + return err } repo, err = repository.New(ctx.GetInner(), key, st, wrappedConfig) if err != nil { - return 1, err + return err } identifier := objects.RandomMAC() scanCache, err := repo.AppContext().GetCache().Scan(identifier) if err != nil { - return 1, err + return err } defer scanCache.Close() repoWriter := repo.NewRepositoryWriter(scanCache, identifier, repository.PtarType, "") - for i, syncTarget := range cmd.SyncTargets { + for i, syncTarget := range syncTargets { storeConfig, err := ctx.Config.GetRepository(syncTarget) if err != nil { - return 1, fmt.Errorf("source repository: %w", err) + return fmt.Errorf("source repository: %w", err) } peerStore, peerStoreSerializedConfig, err := storage.Open(ctx.GetInner(), storeConfig) if err != nil { - return 1, fmt.Errorf("could not open source store %s: %s", syncTarget, err) + return fmt.Errorf("could not open source store %s: %s", syncTarget, err) } srcCtx := appcontext.NewAppContextFrom(ctx) - srcRepository, err := repository.New(srcCtx.GetInner(), cmd.SyncSecrets[i], peerStore, peerStoreSerializedConfig) + srcRepository, err := repository.New(srcCtx.GetInner(), syncSecrets[i], peerStore, peerStoreSerializedConfig) if err != nil { - return 1, fmt.Errorf("could not open source repository %s: %s", syncTarget, err) + return fmt.Errorf("could not open source repository %s: %s", syncTarget, err) } - if err := cmd.synchronize(ctx, srcRepository, repoWriter); err != nil { - return 1, err + if err := synchronize(ctx, srcRepository, repoWriter); err != nil { + return err } } - if err := cmd.backup(ctx, repoWriter); err != nil { - return 1, err + if err := backup(ctx, repoWriter, backupTargets); err != nil { + return err } // We are done with everything we can now stop the backup routines. repoWriter.PackerManager.Wait() err = repoWriter.CommitTransaction(identifier) if err != nil { - return 1, err + return err } if err := st.Close(ctx); err != nil { - return 1, err + return err } - return 0, nil + return nil } -func (cmd *Ptar) backup(ctx *appcontext.AppContext, repo *repository.RepositoryWriter) error { - for _, loc := range cmd.BackupTargets { +func backup(ctx *appcontext.AppContext, repo *repository.RepositoryWriter, targets []string) error { + for _, loc := range targets { opts := map[string]string{ "location": loc, } @@ -384,7 +378,7 @@ func (cmd *Ptar) backup(ctx *appcontext.AppContext, repo *repository.RepositoryW return nil } -func (cmd *Ptar) synchronize(ctx *appcontext.AppContext, srcRepository *repository.Repository, dstRepository *repository.RepositoryWriter) error { +func synchronize(ctx *appcontext.AppContext, srcRepository *repository.Repository, dstRepository *repository.RepositoryWriter) error { srcLocateOptions := locate.NewDefaultLocateOptions() srcSnapshotIDs, err := locate.LocateSnapshotIDs(srcRepository, srcLocateOptions) if err != nil { diff --git a/subcommands/ptar/ptar_test.go b/subcommands/ptar/ptar_test.go index dcdf04fc4..352f756c0 100644 --- a/subcommands/ptar/ptar_test.go +++ b/subcommands/ptar/ptar_test.go @@ -32,15 +32,7 @@ func TestExecuteCmdPtarDefault(t *testing.T) { }) args := []string{"-plaintext", "-o", filepath.Join(tmpDir, "test.ptar"), filepath.Join(tmpSourceDir, "subdir")} - - subcommand := &Ptar{} - err = subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err := subcommand.Execute(ctx, repo) - require.NoError(t, err) - require.Equal(t, 0, status) + require.NoError(t, Ptar(ctx, repo, args)) } func TestExecuteCmdPtarWithSync(t *testing.T) { @@ -66,13 +58,5 @@ func TestExecuteCmdPtarWithSync(t *testing.T) { }) args := []string{"-plaintext", "-o", filepath.Join(tmpDir, "test.ptar"), "-k", srcRepo.Root()} - - subcommand := &Ptar{} - err = subcommand.Parse(ctx, args) - require.NoError(t, err) - require.NotNil(t, subcommand) - - status, err := subcommand.Execute(ctx, dstRepo) - require.NoError(t, err) - require.Equal(t, 0, status) + require.NoError(t, Ptar(ctx, dstRepo, args)) } diff --git a/subcommands/repair/repair.go b/subcommands/repair/repair.go index 2fee34409..b1a8ed3dc 100644 --- a/subcommands/repair/repair.go +++ b/subcommands/repair/repair.go @@ -30,56 +30,44 @@ import ( "github.com/PlakarKorp/plakar/subcommands" ) -type Repair struct { - subcommands.SubcommandBase - - Apply bool - - repository *repository.Repository - repairID objects.MAC -} - func init() { - subcommands.Register(func() subcommands.Subcommand { return &Repair{} }, 0, "repair") + subcommands.Register(Repair, 0, "repair") } -func (cmd *Repair) Parse(ctx *appcontext.AppContext, args []string) error { +func Repair(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + Apply bool + ) + flags := flag.NewFlagSet("repair", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s\n", flags.Name()) fmt.Fprintf(flags.Output(), "\nOPTIONS:\n") flags.PrintDefaults() } - flags.BoolVar(&cmd.Apply, "apply", false, "do the actual repair") + flags.BoolVar(&Apply, "apply", false, "do the actual repair") flags.Parse(args) - cmd.RepositorySecret = ctx.GetSecret() - - return nil -} - -func (cmd *Repair) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - cmd.repository = repo - cmd.repairID = objects.RandomMAC() + repairID := objects.RandomMAC() - if cmd.Apply { - done, err := cmd.Lock() + if Apply { + done, err := lock(ctx, repo, repairID) if err != nil { - return 1, err + return err } - defer cmd.Unlock(done) + defer close(done) } oldCache, err := repo.AppContext().GetCache().Repository(repo.Configuration().RepositoryID) if err != nil { - return 1, err + return err } repo.RebuildStateWithCache(oldCache) remoteStates, err := repo.GetStates() if err != nil { - return 1, err + return err } remoteStatesMap := make(map[objects.MAC]struct{}, 0) @@ -90,7 +78,7 @@ func (cmd *Repair) Execute(ctx *appcontext.AppContext, repo *repository.Reposito packfilesPerState := make(map[objects.MAC][]objects.MAC, 0) for pe, err := range repo.ListPackfileEntries() { if err != nil { - return 1, err + return err } if _, ok := remoteStatesMap[pe.StateID]; ok { continue @@ -99,7 +87,7 @@ func (cmd *Repair) Execute(ctx *appcontext.AppContext, repo *repository.Reposito } for stateID, packfiles := range packfilesPerState { - if !cmd.Apply { + if !Apply { ctx.GetLogger().Info("found missing state %x\n", stateID) continue } else { @@ -108,18 +96,18 @@ func (cmd *Repair) Execute(ctx *appcontext.AppContext, repo *repository.Reposito scanCache, err := repo.AppContext().GetCache().Scan(stateID) if err != nil { - return 1, err + return err } deltaState, err := state.NewLocalState(scanCache) if err != nil { - return 1, err + return err } for _, pf := range packfiles { p, err := repo.GetPackfile(pf) if err != nil { - return 1, err + return err } if deltaState.Metadata.Timestamp.UnixNano() > p.Footer.Timestamp { @@ -138,12 +126,12 @@ func (cmd *Repair) Execute(ctx *appcontext.AppContext, repo *repository.Reposito }, } if err := deltaState.PutDelta(delta); err != nil { - return 1, err + return err } } if err := deltaState.PutPackfile(stateID, pf); err != nil { - return 1, err + return err } } @@ -157,13 +145,13 @@ func (cmd *Repair) Execute(ctx *appcontext.AppContext, repo *repository.Reposito }() err = repo.PutState(stateID, pr) if err != nil { - return 1, err + return err } scanCache.Close() } - if !cmd.Apply { + if !Apply { if len(packfilesPerState) == 0 { ctx.GetLogger().Info("no repairs needed\n") } else { @@ -171,12 +159,12 @@ func (cmd *Repair) Execute(ctx *appcontext.AppContext, repo *repository.Reposito } } - return 0, nil + return nil } -func (cmd *Repair) Lock() (chan bool, error) { +func lock(ctx *appcontext.AppContext, repo *repository.Repository, repairID objects.MAC) (chan bool, error) { lockDone := make(chan bool) - lock := repository.NewExclusiveLock(cmd.repository.AppContext().Hostname) + lock := repository.NewExclusiveLock(ctx.Hostname) buffer := &bytes.Buffer{} err := lock.SerializeToStream(buffer) @@ -184,42 +172,42 @@ func (cmd *Repair) Lock() (chan bool, error) { return nil, err } - _, err = cmd.repository.PutLock(cmd.repairID, buffer) + _, err = repo.PutLock(repairID, buffer) if err != nil { return nil, err } // We installed the lock, now let's see if there is a conflicting exclusive lock or not. - locksID, err := cmd.repository.GetLocks() + locksID, err := repo.GetLocks() if err != nil { // We still need to delete it, and we need to do so manually. - cmd.repository.DeleteLock(cmd.repairID) + repo.DeleteLock(repairID) return nil, err } for _, lockID := range locksID { - if lockID == cmd.repairID { + if lockID == repairID { continue } - rd, err := cmd.repository.GetLock(lockID) + rd, err := repo.GetLock(lockID) if err != nil { - cmd.repository.DeleteLock(cmd.repairID) + repo.DeleteLock(repairID) return nil, err } lock, err := repository.NewLockFromStream(rd) rd.Close() if err != nil { - cmd.repository.DeleteLock(cmd.repairID) + repo.DeleteLock(repairID) return nil, err } /* Kick out stale locks */ if lock.IsStale() { - err := cmd.repository.DeleteLock(lockID) + err := repo.DeleteLock(lockID) if err != nil { - cmd.repository.DeleteLock(cmd.repairID) + repo.DeleteLock(repairID) return nil, err } @@ -227,7 +215,7 @@ func (cmd *Repair) Lock() (chan bool, error) { } // There is a lock in place, we need to abort. - err = cmd.repository.DeleteLock(cmd.repairID) + err = repo.DeleteLock(repairID) if err != nil { return nil, err } @@ -241,10 +229,10 @@ func (cmd *Repair) Lock() (chan bool, error) { for { select { case <-lockDone: - cmd.repository.DeleteLock(cmd.repairID) + repo.DeleteLock(repairID) return case <-time.After(repository.LOCK_REFRESH_RATE): - lock := repository.NewExclusiveLock(cmd.repository.AppContext().Hostname) + lock := repository.NewExclusiveLock(ctx.Hostname) buffer := &bytes.Buffer{} @@ -252,14 +240,10 @@ func (cmd *Repair) Lock() (chan bool, error) { // correctly, and if they happen we will be ripped by the // watchdog anyway. lock.SerializeToStream(buffer) - cmd.repository.PutLock(cmd.repairID, buffer) + repo.PutLock(repairID, buffer) } } }() return lockDone, nil } - -func (cmd *Repair) Unlock(ping chan bool) { - close(ping) -} diff --git a/subcommands/restore/restore.go b/subcommands/restore/restore.go index 82e55f730..0c7801ffb 100644 --- a/subcommands/restore/restore.go +++ b/subcommands/restore/restore.go @@ -33,31 +33,22 @@ import ( "github.com/PlakarKorp/plakar/utils" ) -type Restore struct { - subcommands.SubcommandBase - - OptName string - OptCategory string - OptEnvironment string - OptPerimeter string - OptJob string - OptTag string - OptSkipPermissions bool - Opts map[string]string - - Target string - Strip string - Snapshots []string -} - func init() { - subcommands.Register(func() subcommands.Subcommand { return &Restore{} }, 0, "restore") + subcommands.Register(Restore, 0, "restore") } -func (cmd *Restore) Parse(ctx *appcontext.AppContext, args []string) error { - var pullPath string - - cmd.Opts = make(map[string]string) +func Restore(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + target string + name string + category string + environment string + perimeter string + job string + tag string + skipPerms bool + exporterOpts = make(map[string]string) + ) flags := flag.NewFlagSet("restore", flag.ExitOnError) flags.Usage = func() { @@ -66,74 +57,66 @@ func (cmd *Restore) Parse(ctx *appcontext.AppContext, args []string) error { flags.PrintDefaults() } - flags.StringVar(&cmd.OptName, "name", "", "filter by name") - flags.StringVar(&cmd.OptCategory, "category", "", "filter by category") - flags.StringVar(&cmd.OptEnvironment, "environment", "", "filter by environment") - flags.StringVar(&cmd.OptPerimeter, "perimeter", "", "filter by perimeter") - flags.StringVar(&cmd.OptJob, "job", "", "filter by job") - flags.StringVar(&cmd.OptTag, "tag", "", "filter by tag") - flags.Var(utils.NewOptsFlag(cmd.Opts), "o", "specify extra exporter options") + flags.StringVar(&name, "name", "", "filter by name") + flags.StringVar(&category, "category", "", "filter by category") + flags.StringVar(&environment, "environment", "", "filter by environment") + flags.StringVar(&perimeter, "perimeter", "", "filter by perimeter") + flags.StringVar(&job, "job", "", "filter by job") + flags.StringVar(&tag, "tag", "", "filter by tag") + flags.Var(utils.NewOptsFlag(exporterOpts), "o", "specify extra exporter options") - flags.StringVar(&pullPath, "to", "", "base directory where pull will restore") - flags.BoolVar(&cmd.OptSkipPermissions, "skip-permissions", false, "do not restore file permissions") + flags.StringVar(&target, "to", "", "base directory where pull will restore") + flags.BoolVar(&skipPerms, "skip-permissions", false, "do not restore file permissions") flags.Parse(args) if flags.NArg() != 0 { - if cmd.OptName != "" || cmd.OptCategory != "" || cmd.OptEnvironment != "" || cmd.OptPerimeter != "" || cmd.OptJob != "" || cmd.OptTag != "" { + if name != "" || category != "" || environment != "" || perimeter != "" || job != "" || tag != "" { ctx.GetLogger().Warn("snapshot specified, filters will be ignored") } } else if flags.NArg() > 1 { return fmt.Errorf("multiple restore paths specified, please specify only one") } - if pullPath == "" { - pullPath = fmt.Sprintf("%s/plakar-%s", ctx.CWD, time.Now().Format("20060102150405")) + if target == "" { + target = fmt.Sprintf("%s/plakar-%s", ctx.CWD, time.Now().Format("20060102150405")) } - cmd.RepositorySecret = ctx.GetSecret() - cmd.Target = pullPath - cmd.Snapshots = flags.Args() - - return nil -} - -func (cmd *Restore) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { var snapshots []string - if len(cmd.Snapshots) == 0 { + if len(flags.Args()) == 0 { locateOptions := locate.NewDefaultLocateOptions() locateOptions.Filters.Latest = true - locateOptions.Filters.Name = cmd.OptName - locateOptions.Filters.Category = cmd.OptCategory - locateOptions.Filters.Environment = cmd.OptEnvironment - locateOptions.Filters.Perimeter = cmd.OptPerimeter - locateOptions.Filters.Job = cmd.OptJob - locateOptions.Filters.Tags = []string{cmd.OptTag} + locateOptions.Filters.Name = name + locateOptions.Filters.Category = category + locateOptions.Filters.Environment = environment + locateOptions.Filters.Perimeter = perimeter + locateOptions.Filters.Job = job + locateOptions.Filters.Tags = []string{tag} snapshotIDs, err := locate.LocateSnapshotIDs(repo, locateOptions) if err != nil { - return 1, fmt.Errorf("ls: could not fetch snapshots list: %w", err) + return fmt.Errorf("ls: could not fetch snapshots list: %w", err) } for _, snapshotID := range snapshotIDs { snapshots = append(snapshots, fmt.Sprintf("%x:", snapshotID)) } } else { - for _, snapshotPath := range cmd.Snapshots { + for _, snapshotPath := range flags.Args() { prefix, path := locate.ParseSnapshotPath(snapshotPath) locateOptions := locate.NewDefaultLocateOptions() locateOptions.Filters.Latest = true - locateOptions.Filters.Name = cmd.OptName - locateOptions.Filters.Category = cmd.OptCategory - locateOptions.Filters.Environment = cmd.OptEnvironment - locateOptions.Filters.Perimeter = cmd.OptPerimeter - locateOptions.Filters.Job = cmd.OptJob - locateOptions.Filters.Tags = []string{cmd.OptTag} + locateOptions.Filters.Name = name + locateOptions.Filters.Category = category + locateOptions.Filters.Environment = environment + locateOptions.Filters.Perimeter = perimeter + locateOptions.Filters.Job = job + locateOptions.Filters.Tags = []string{tag} locateOptions.Filters.IDs = []string{prefix} snapshotIDs, err := locate.LocateSnapshotIDs(repo, locateOptions) if err != nil { - return 1, fmt.Errorf("ls: could not fetch snapshots list: %w", err) + return fmt.Errorf("ls: could not fetch snapshots list: %w", err) } for _, snapshotID := range snapshotIDs { snapshots = append(snapshots, fmt.Sprintf("%x:%s", snapshotID, path)) @@ -142,27 +125,27 @@ func (cmd *Restore) Execute(ctx *appcontext.AppContext, repo *repository.Reposit } if len(snapshots) == 0 { - return 1, fmt.Errorf("no snapshots found") + return fmt.Errorf("no snapshots found") } else if len(snapshots) > 1 { - return 1, fmt.Errorf("multiple snapshots found, please specify one") + return fmt.Errorf("multiple snapshots found, please specify one") } exporterConfig := map[string]string{ - "location": cmd.Target, + "location": target, } - if strings.HasPrefix(cmd.Target, "@") { - remote, ok := ctx.Config.GetDestination(cmd.Target[1:]) + if strings.HasPrefix(target, "@") { + remote, ok := ctx.Config.GetDestination(target[1:]) if !ok { - return 1, fmt.Errorf("could not resolve exporter: %s", cmd.Target) + return fmt.Errorf("could not resolve exporter: %s", target) } if _, ok := remote["location"]; !ok { - return 1, fmt.Errorf("could not resolve exporter location: %s", cmd.Target) + return fmt.Errorf("could not resolve exporter location: %s", target) } else { exporterConfig = remote } } - maps.Copy(exporterConfig, cmd.Opts) + maps.Copy(exporterConfig, exporterOpts) var exporterInstance exporter.Exporter var err error @@ -170,19 +153,19 @@ func (cmd *Restore) Execute(ctx *appcontext.AppContext, repo *repository.Reposit exporterInstance, err = exporter.NewExporter(ctx.GetInner(), options, exporterConfig) if err != nil { - return 1, err + return err } defer exporterInstance.Close(ctx) opts := &snapshot.ExportOptions{} - if cmd.OptSkipPermissions { + if skipPerms { opts.SkipPermissions = true } for _, snapPath := range snapshots { snap, pathname, relative, err := locate.OpenSnapshotByPathRelative(repo, snapPath) if err != nil { - return 1, err + return err } if relative != "" { @@ -195,10 +178,10 @@ func (cmd *Restore) Execute(ctx *appcontext.AppContext, repo *repository.Reposit err = snap.Export(exporterInstance, pathname, opts) if err != nil { - return 1, err + return err } snap.Close() } - return 0, nil + return nil } diff --git a/subcommands/rm/rm.go b/subcommands/rm/rm.go index af6b26e3a..7f77a0266 100644 --- a/subcommands/rm/rm.go +++ b/subcommands/rm/rm.go @@ -35,20 +35,15 @@ import ( "github.com/dustin/go-humanize" ) -type Rm struct { - subcommands.SubcommandBase - - LocateOptions *locate.LocateOptions - - Apply bool -} - func init() { - subcommands.Register(func() subcommands.Subcommand { return &Rm{} }, 0, "rm") + subcommands.Register(Rm, 0, "rm") } -func (cmd *Rm) Parse(ctx *appcontext.AppContext, args []string) error { - cmd.LocateOptions = locate.NewDefaultLocateOptions() +func Rm(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + locOpts = locate.NewDefaultLocateOptions() + apply bool + ) flags := flag.NewFlagSet("rm", flag.ExitOnError) flags.Usage = func() { @@ -56,34 +51,28 @@ func (cmd *Rm) Parse(ctx *appcontext.AppContext, args []string) error { fmt.Fprintf(flags.Output(), "\nOPTIONS:\n") flags.PrintDefaults() } - flags.BoolVar(&cmd.Apply, "apply", false, "do the actual removal") - cmd.LocateOptions.InstallDeletionFlags(flags) + flags.BoolVar(&apply, "apply", false, "do the actual removal") + locOpts.InstallDeletionFlags(flags) flags.Parse(args) - if flags.NArg() == 0 && cmd.LocateOptions.Empty() { + if flags.NArg() == 0 && locOpts.Empty() { return fmt.Errorf("no filter specified, not going to remove everything") } - cmd.LocateOptions.Filters.IDs = flags.Args() - - cmd.RepositorySecret = ctx.GetSecret() + locOpts.Filters.IDs = flags.Args() - return nil -} - -func (cmd *Rm) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - matches, err := locate.LocateSnapshotIDs(repo, cmd.LocateOptions) + matches, err := locate.LocateSnapshotIDs(repo, locOpts) if err != nil { - return 1, err + return err } if len(matches) == 0 { ctx.GetLogger().Info("rm: no snapshots matched the selection") - return 0, nil + return nil } // plan - if !cmd.Apply { + if !apply { type planEntry struct { prefix string id objects.MAC @@ -138,7 +127,7 @@ func (cmd *Rm) Execute(ctx *appcontext.AppContext, repo *repository.Repository) for _, e := range entries { fmt.Fprintf(ctx.Stdout, "%s\n", e.prefix) } - return 0, nil + return nil } // execution @@ -160,8 +149,8 @@ func (cmd *Rm) Execute(ctx *appcontext.AppContext, repo *repository.Repository) wg.Wait() if errors != 0 { - return 1, fmt.Errorf("failed to remove %d snapshots", errors) + return fmt.Errorf("failed to remove %d snapshots", errors) } - return 0, nil + return nil } diff --git a/subcommands/server/server.go b/subcommands/server/server.go index d5958c4db..dc427bd9f 100644 --- a/subcommands/server/server.go +++ b/subcommands/server/server.go @@ -27,11 +27,17 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &Server{} }, subcommands.BeforeRepositoryWithStorage, "server") + subcommands.Register(Server, subcommands.BeforeRepositoryWithStorage, "server") } -func (cmd *Server) Parse(ctx *appcontext.AppContext, args []string) error { - var opt_allowdelete bool +func Server(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + listenAddr string + allowDelete bool + cert string + key string + ) + flags := flag.NewFlagSet("server", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s [OPTIONS]\n", flags.Name()) @@ -39,44 +45,19 @@ func (cmd *Server) Parse(ctx *appcontext.AppContext, args []string) error { flags.PrintDefaults() } - flags.StringVar(&cmd.ListenAddr, "listen", "localhost:9876", "address to listen on") - flags.BoolVar(&opt_allowdelete, "allow-delete", false, "enable delete operations") - flags.StringVar(&cmd.Cert, "cert", "", "Full certificate chain") - flags.StringVar(&cmd.Key, "key", "", "Certificate private key") + flags.StringVar(&listenAddr, "listen", "localhost:9876", "address to listen on") + flags.BoolVar(&allowDelete, "allow-delete", false, "enable delete operations") + flags.StringVar(&cert, "cert", "", "Full certificate chain") + flags.StringVar(&key, "key", "", "Certificate private key") flags.Parse(args) - noDelete := true - if opt_allowdelete { - noDelete = false - } - - cmd.RepositorySecret = ctx.GetSecret() - cmd.NoDelete = noDelete - - return nil -} - -type Server struct { - subcommands.SubcommandBase - - ListenAddr string - NoDelete bool - Cert string - Key string -} - -func (cmd *Server) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { var protocol string - if cmd.Cert != "" && cmd.Key != "" { + if cert != "" && key != "" { protocol = "https" } else { protocol = "http" } - ctx.GetLogger().Info("listening on %s://%s", protocol, cmd.ListenAddr) - err := httpd.Server(ctx, repo, cmd.ListenAddr, cmd.NoDelete, cmd.Cert, cmd.Key) - if err != nil { - return 1, err - } - return 0, nil + ctx.GetLogger().Info("listening on %s://%s", protocol, listenAddr) + return httpd.Server(ctx, repo, listenAddr, !allowDelete, cert, key) } diff --git a/subcommands/service/add.go b/subcommands/service/add.go index f08bad97e..4d400cdb5 100644 --- a/subcommands/service/add.go +++ b/subcommands/service/add.go @@ -7,17 +7,9 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type ServiceAdd struct { - subcommands.SubcommandBase - - Service string - Keys map[string]string -} - -func (cmd *ServiceAdd) Parse(ctx *appcontext.AppContext, args []string) error { +func Add(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("service add", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s =...\n", flags.Name()) @@ -28,32 +20,30 @@ func (cmd *ServiceAdd) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("no service specified") } - cmd.Service = flags.Arg(0) - cmd.Keys = make(map[string]string) + var ( + service = flags.Arg(0) + keys = make(map[string]string, flags.NArg()-1) + ) for _, kv := range flags.Args()[1:] { key, val, found := strings.Cut(kv, "=") if !found || key == "" { return fmt.Errorf("invalid argument %q", kv) } - cmd.Keys[key] = val + keys[key] = val } - return nil -} - -func (cmd *ServiceAdd) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { sc, err := getClient(ctx) if err != nil { - return 1, err + return err } - if err := sc.SetServiceConfiguration(cmd.Service, cmd.Keys); err != nil { - return 1, err + if err := sc.SetServiceConfiguration(service, keys); err != nil { + return err } - if err := sc.SetServiceStatus(cmd.Service, true); err != nil { - return 1, err + if err := sc.SetServiceStatus(service, true); err != nil { + return err } - return 0, nil + return nil } diff --git a/subcommands/service/disable.go b/subcommands/service/disable.go index 42f7d0754..98596bc88 100644 --- a/subcommands/service/disable.go +++ b/subcommands/service/disable.go @@ -22,16 +22,9 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type ServiceDisable struct { - subcommands.SubcommandBase - - Service string -} - -func (cmd *ServiceDisable) Parse(ctx *appcontext.AppContext, args []string) error { +func Disable(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("service disable", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s \n", flags.Name()) @@ -42,22 +35,17 @@ func (cmd *ServiceDisable) Parse(ctx *appcontext.AppContext, args []string) erro return fmt.Errorf("invalid number of arguments, expected 1 but got %d", flags.NArg()) } - cmd.Service = flags.Arg(0) - cmd.RepositorySecret = ctx.GetSecret() + service := flags.Arg(0) - return nil -} - -func (cmd *ServiceDisable) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { sc, err := getClient(ctx) if err != nil { - return 1, err + return err } - if err := sc.SetServiceStatus(cmd.Service, false); err != nil { - return 1, err + if err := sc.SetServiceStatus(service, false); err != nil { + return err } fmt.Fprintf(ctx.Stdout, "disabled\n") - return 0, nil + return nil } diff --git a/subcommands/service/enable.go b/subcommands/service/enable.go index a502841b3..46f708c24 100644 --- a/subcommands/service/enable.go +++ b/subcommands/service/enable.go @@ -22,16 +22,9 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type ServiceEnable struct { - subcommands.SubcommandBase - - Service string -} - -func (cmd *ServiceEnable) Parse(ctx *appcontext.AppContext, args []string) error { +func Enable(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("service enable", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s \n", flags.Name()) @@ -42,22 +35,17 @@ func (cmd *ServiceEnable) Parse(ctx *appcontext.AppContext, args []string) error return fmt.Errorf("invalid number of arguments, expected 1 but got %d", flags.NArg()) } - cmd.Service = flags.Arg(0) - cmd.RepositorySecret = ctx.GetSecret() + service := flags.Arg(0) - return nil -} - -func (cmd *ServiceEnable) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { sc, err := getClient(ctx) if err != nil { - return 1, err + return err } - if err := sc.SetServiceStatus(cmd.Service, true); err != nil { - return 1, err + if err := sc.SetServiceStatus(service, true); err != nil { + return err } fmt.Fprintf(ctx.Stdout, "enabled\n") - return 0, nil + return nil } diff --git a/subcommands/service/list.go b/subcommands/service/list.go index 91ca9ef5e..f43036f25 100644 --- a/subcommands/service/list.go +++ b/subcommands/service/list.go @@ -22,14 +22,9 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type ServiceList struct { - subcommands.SubcommandBase -} - -func (cmd *ServiceList) Parse(ctx *appcontext.AppContext, args []string) error { +func List(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("service list", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s\n", flags.Name()) @@ -40,22 +35,19 @@ func (cmd *ServiceList) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("invalid argument: %s", flags.Arg(0)) } - return nil -} - -func (cmd *ServiceList) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { sc, err := getClient(ctx) if err != nil { - return 1, err + return err } list, err := sc.GetServiceList() if err != nil { - return 1, err + return err } + for _, svc := range list { fmt.Fprintf(ctx.Stdout, "%s\n", svc.Name) } - return 0, nil + return nil } diff --git a/subcommands/service/rm.go b/subcommands/service/rm.go index 78f9ac123..b7d5f3c2f 100644 --- a/subcommands/service/rm.go +++ b/subcommands/service/rm.go @@ -6,16 +6,9 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type ServiceRm struct { - subcommands.SubcommandBase - - Service string -} - -func (cmd *ServiceRm) Parse(ctx *appcontext.AppContext, args []string) error { +func Rm(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("service rm", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s \n", flags.Name()) @@ -30,22 +23,18 @@ func (cmd *ServiceRm) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("invalid argument %q", flags.Arg(1)) } - cmd.Service = flags.Arg(0) + service := flags.Arg(0) - return nil -} - -func (cmd *ServiceRm) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { sc, err := getClient(ctx) if err != nil { - return 1, err + return err } - if err := sc.SetServiceStatus(cmd.Service, false); err != nil { - return 1, err + if err := sc.SetServiceStatus(service, false); err != nil { + return err } - if err := sc.SetServiceConfiguration(cmd.Service, make(map[string]string)); err != nil { - return 1, err + if err := sc.SetServiceConfiguration(service, make(map[string]string)); err != nil { + return err } - return 0, nil + return nil } diff --git a/subcommands/service/service.go b/subcommands/service/service.go index 31eb44612..d4e71fb35 100644 --- a/subcommands/service/service.go +++ b/subcommands/service/service.go @@ -27,23 +27,19 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &ServiceList{} }, 0, "service", "list") - subcommands.Register(func() subcommands.Subcommand { return &ServiceStatus{} }, 0, "service", "status") - subcommands.Register(func() subcommands.Subcommand { return &ServiceEnable{} }, 0, "service", "enable") - subcommands.Register(func() subcommands.Subcommand { return &ServiceDisable{} }, 0, "service", "disable") - subcommands.Register(func() subcommands.Subcommand { return &ServiceSet{} }, 0, "service", "set") - subcommands.Register(func() subcommands.Subcommand { return &ServiceUnset{} }, 0, "service", "unset") - subcommands.Register(func() subcommands.Subcommand { return &ServiceAdd{} }, 0, "service", "add") - subcommands.Register(func() subcommands.Subcommand { return &ServiceRm{} }, 0, "service", "rm") - subcommands.Register(func() subcommands.Subcommand { return &ServiceShow{} }, 0, "service", "show") - subcommands.Register(func() subcommands.Subcommand { return &Service{} }, subcommands.BeforeRepositoryOpen, "service") + subcommands.Register(List, 0, "service", "list") + subcommands.Register(Status, 0, "service", "status") + subcommands.Register(Enable, 0, "service", "enable") + subcommands.Register(Disable, 0, "service", "disable") + subcommands.Register(Set, 0, "service", "set") + subcommands.Register(Unset, 0, "service", "unset") + subcommands.Register(Add, 0, "service", "add") + subcommands.Register(Rm, 0, "service", "rm") + subcommands.Register(Show, 0, "service", "show") + subcommands.Register(Service, subcommands.BeforeRepositoryOpen, "service") } -type Service struct { - subcommands.SubcommandBase -} - -func (_ *Service) Parse(ctx *appcontext.AppContext, args []string) error { +func Service(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("service", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s\n", flags.Name()) @@ -65,10 +61,6 @@ func (_ *Service) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("no action specified") } -func (cmd *Service) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - return 1, fmt.Errorf("no action specified") -} - func getClient(ctx *appcontext.AppContext) (*services.ServiceConnector, error) { authToken, err := ctx.GetCookies().GetAuthToken() if err != nil { diff --git a/subcommands/service/service_test.go b/subcommands/service/service_test.go index 04e91d1b3..59ac1f97a 100644 --- a/subcommands/service/service_test.go +++ b/subcommands/service/service_test.go @@ -30,15 +30,15 @@ func TestServiceRegisteredFactories(t *testing.T) { args []string typ interface{} }{ - {[]string{"service", "list"}, &ServiceList{}}, - {[]string{"service", "status"}, &ServiceStatus{}}, - {[]string{"service", "enable"}, &ServiceEnable{}}, - {[]string{"service", "disable"}, &ServiceDisable{}}, - {[]string{"service", "set"}, &ServiceSet{}}, - {[]string{"service", "unset"}, &ServiceUnset{}}, - {[]string{"service", "add"}, &ServiceAdd{}}, - {[]string{"service", "rm"}, &ServiceRm{}}, - {[]string{"service", "show"}, &ServiceShow{}}, + {[]string{"service", "list"}, &List{}}, + {[]string{"service", "status"}, &Status{}}, + {[]string{"service", "enable"}, &Enable{}}, + {[]string{"service", "disable"}, &Disable{}}, + {[]string{"service", "set"}, &Set{}}, + {[]string{"service", "unset"}, &Unset{}}, + {[]string{"service", "add"}, &Add{}}, + {[]string{"service", "rm"}, &Rm{}}, + {[]string{"service", "show"}, &Show{}}, } for _, c := range cases { cmd, _, _ := subcommands.Lookup(c.args) @@ -49,17 +49,17 @@ func TestServiceRegisteredFactories(t *testing.T) { func TestServiceListParse(t *testing.T) { ctx := newCtx(t) - cmd := &ServiceList{} + cmd := &List{} require.NoError(t, cmd.Parse(ctx, []string{})) // extra argument is rejected - require.Error(t, (&ServiceList{}).Parse(ctx, []string{"extra"})) + require.Error(t, (&List{}).Parse(ctx, []string{"extra"})) } func TestServiceListExecuteRequiresLogin(t *testing.T) { // With no auth token configured, getClient fails with a "requires login" // error and Execute returns status 1. ctx := newCtx(t) - cmd := &ServiceList{} + cmd := &List{} require.NoError(t, cmd.Parse(ctx, []string{})) status, err := cmd.Execute(ctx, &repository.Repository{}) require.Error(t, err) @@ -68,7 +68,7 @@ func TestServiceListExecuteRequiresLogin(t *testing.T) { func TestServiceStatusExecuteRequiresLogin(t *testing.T) { ctx := newCtx(t) - cmd := &ServiceStatus{} + cmd := &Status{} require.NoError(t, cmd.Parse(ctx, []string{"alerting"})) status, err := cmd.Execute(ctx, &repository.Repository{}) require.Error(t, err) diff --git a/subcommands/service/set.go b/subcommands/service/set.go index bc6bc522a..60384c210 100644 --- a/subcommands/service/set.go +++ b/subcommands/service/set.go @@ -8,17 +8,9 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type ServiceSet struct { - subcommands.SubcommandBase - - Service string - Keys map[string]string -} - -func (cmd *ServiceSet) Parse(ctx *appcontext.AppContext, args []string) error { +func Set(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("service set", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s =...\n", flags.Name()) @@ -29,39 +21,37 @@ func (cmd *ServiceSet) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("no service specified") } - cmd.Service = flags.Arg(0) - cmd.Keys = make(map[string]string) + var ( + service = flags.Arg(0) + keys = make(map[string]string, flags.NArg()-1) + ) for _, kv := range flags.Args()[1:] { key, val, found := strings.Cut(kv, "=") if !found || key == "" { return fmt.Errorf("invalid argument %q", kv) } - cmd.Keys[key] = val + keys[key] = val } - return nil -} - -func (cmd *ServiceSet) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { sc, err := getClient(ctx) if err != nil { - return 1, err + return err } - if len(cmd.Keys) == 0 { - return 0, nil + if len(keys) == 0 { + return nil } - config, err := sc.GetServiceConfiguration(cmd.Service) + config, err := sc.GetServiceConfiguration(service) if err != nil { - return 1, err + return err } - maps.Copy(config, cmd.Keys) - if err := sc.SetServiceConfiguration(cmd.Service, config); err != nil { - return 1, err + maps.Copy(config, keys) + if err := sc.SetServiceConfiguration(service, config); err != nil { + return err } - return 0, nil + return nil } diff --git a/subcommands/service/show.go b/subcommands/service/show.go index a2871029e..266b29caa 100644 --- a/subcommands/service/show.go +++ b/subcommands/service/show.go @@ -7,24 +7,20 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" "go.yaml.in/yaml/v3" ) -type ServiceShow struct { - subcommands.SubcommandBase +func Show(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + asJson bool + asYaml bool + showSecrets bool + ) - AsJson bool - AsYaml bool - ShowSecrets bool - Service string -} - -func (cmd *ServiceShow) Parse(ctx *appcontext.AppContext, args []string) error { flags := flag.NewFlagSet("service show", flag.ExitOnError) - flags.BoolVar(&cmd.AsJson, "json", false, "output in JSON format") - flags.BoolVar(&cmd.AsYaml, "yaml", false, "output in YAML format (default)") - flags.BoolVar(&cmd.ShowSecrets, "secrets", false, "show secret values instead of ********") + flags.BoolVar(&asJson, "json", false, "output in JSON format") + flags.BoolVar(&asYaml, "yaml", false, "output in YAML format (default)") + flags.BoolVar(&showSecrets, "secrets", false, "show secret values instead of ********") flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s [OPTIONS] \n", flags.Name()) fmt.Fprintf(flags.Output(), "\nOPTIONS:\n") @@ -36,32 +32,26 @@ func (cmd *ServiceShow) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("invalid number of arguments, expected 1 but got %d", flags.NArg()) } - cmd.Service = flags.Arg(0) - cmd.RepositorySecret = ctx.GetSecret() + Service := flags.Arg(0) - return nil -} - -func (cmd *ServiceShow) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { sc, err := getClient(ctx) if err != nil { - return 1, err + return err } - config, err := sc.GetServiceConfiguration(cmd.Service) + config, err := sc.GetServiceConfiguration(Service) if err != nil { - return 1, err + return err } - if cmd.AsJson { - err = json.NewEncoder(ctx.Stdout).Encode(map[string]any{cmd.Service: config}) + if asJson { + err = json.NewEncoder(ctx.Stdout).Encode(map[string]any{Service: config}) } else { - err = yaml.NewEncoder(ctx.Stdout).Encode(map[string]any{cmd.Service: config}) + err = yaml.NewEncoder(ctx.Stdout).Encode(map[string]any{Service: config}) } if err != nil { - return 1, fmt.Errorf("failed to encode config: %w", err) + return fmt.Errorf("failed to encode config: %w", err) } - return 0, nil - + return nil } diff --git a/subcommands/service/status.go b/subcommands/service/status.go index 8634b6961..2b021e78e 100644 --- a/subcommands/service/status.go +++ b/subcommands/service/status.go @@ -22,16 +22,9 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type ServiceStatus struct { - subcommands.SubcommandBase - - Service string -} - -func (cmd *ServiceStatus) Parse(ctx *appcontext.AppContext, args []string) error { +func Status(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("service status", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s \n", flags.Name()) @@ -42,26 +35,21 @@ func (cmd *ServiceStatus) Parse(ctx *appcontext.AppContext, args []string) error return fmt.Errorf("invalid number of arguments, expected 1 but got %d", flags.NArg()) } - cmd.Service = flags.Arg(0) - cmd.RepositorySecret = ctx.GetSecret() + service := flags.Arg(0) - return nil -} - -func (cmd *ServiceStatus) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { sc, err := getClient(ctx) if err != nil { - return 1, err + return err } - status, err := sc.GetServiceStatus(cmd.Service) + status, err := sc.GetServiceStatus(service) if err != nil { - return 1, err + return err } if status { fmt.Fprintf(ctx.Stdout, "status: enabled\n") } else { fmt.Fprintf(ctx.Stdout, "status: disabled\n") } - return 0, nil + return nil } diff --git a/subcommands/service/unset.go b/subcommands/service/unset.go index a9ee8c103..77e9a24c0 100644 --- a/subcommands/service/unset.go +++ b/subcommands/service/unset.go @@ -6,17 +6,9 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" - "github.com/PlakarKorp/plakar/subcommands" ) -type ServiceUnset struct { - subcommands.SubcommandBase - - Service string - Keys []string -} - -func (cmd *ServiceUnset) Parse(ctx *appcontext.AppContext, args []string) error { +func Unset(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("service unset", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s ...\n", flags.Name()) @@ -27,34 +19,32 @@ func (cmd *ServiceUnset) Parse(ctx *appcontext.AppContext, args []string) error return fmt.Errorf("no service specified") } - cmd.Service = flags.Arg(0) - cmd.Keys = flags.Args()[1:] + var ( + service = flags.Arg(0) + keys = flags.Args()[1:] + ) - return nil -} - -func (cmd *ServiceUnset) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { sc, err := getClient(ctx) if err != nil { - return 1, err + return err } - if len(cmd.Keys) == 0 { - return 0, nil + if len(keys) == 0 { + return nil } - config, err := sc.GetServiceConfiguration(cmd.Service) + config, err := sc.GetServiceConfiguration(service) if err != nil { - return 1, err + return err } - for _, key := range cmd.Keys { + for _, key := range keys { delete(config, key) } - if err := sc.SetServiceConfiguration(cmd.Service, config); err != nil { - return 1, err + if err := sc.SetServiceConfiguration(service, config); err != nil { + return err } - return 0, nil + return nil } diff --git a/subcommands/subcommands.go b/subcommands/subcommands.go index c65b0ff5c..5f70b66fd 100644 --- a/subcommands/subcommands.go +++ b/subcommands/subcommands.go @@ -1,6 +1,7 @@ package subcommands import ( + "fmt" "slices" "strings" @@ -16,55 +17,43 @@ const ( BeforeRepositoryOpen ) -type Subcommand interface { - Parse(ctx *appcontext.AppContext, args []string) error - Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) - GetRepositorySecret() []byte - GetFlags() CommandFlags - setFlags(CommandFlags) +type ErrCode struct { + Err error + Code int } -type SubcommandBase struct { - RepositorySecret []byte - Flags CommandFlags +func NewErrCode(code int, reason string, args ...any) *ErrCode { + return &ErrCode{Err: fmt.Errorf(reason, args...), Code: code} } -func (cmd *SubcommandBase) setFlags(flags CommandFlags) { - cmd.Flags = flags -} - -func (cmd *SubcommandBase) GetFlags() CommandFlags { - return cmd.Flags -} +func (e *ErrCode) Error() string { return e.Err.Error() } +func (e *ErrCode) String() string { return e.Error() } -func (cmd *SubcommandBase) GetRepositorySecret() []byte { - return cmd.RepositorySecret -} +type Subcommand func(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error -type CmdFactory func() Subcommand type subcmd struct { - args []string - nargs int - flags CommandFlags - factory CmdFactory + args []string + nargs int + flags CommandFlags + fn Subcommand } var subcommands []subcmd = make([]subcmd, 0) -func Register(factory CmdFactory, flags CommandFlags, args ...string) { +func Register(fn Subcommand, flags CommandFlags, args ...string) { if len(args) == 0 { panic("can't register commands with zero arguments") } subcommands = append(subcommands, subcmd{ - args: args, - nargs: len(args), - flags: flags, - factory: factory, + args: args, + nargs: len(args), + flags: flags, + fn: fn, }) } -func Lookup(arguments []string) (Subcommand, []string, []string) { +func Lookup(arguments []string) (Subcommand, CommandFlags, []string, []string) { nargs := len(arguments) for _, subcmd := range subcommands { if nargs < subcmd.nargs { @@ -75,12 +64,10 @@ func Lookup(arguments []string) (Subcommand, []string, []string) { continue } - cmd := subcmd.factory() - cmd.setFlags(subcmd.flags) - return cmd, arguments[:subcmd.nargs], arguments[subcmd.nargs:] + return subcmd.fn, subcmd.flags, arguments[:subcmd.nargs], arguments[subcmd.nargs:] } - return nil, nil, arguments + return nil, 0, nil, arguments } func List() [][]string { diff --git a/subcommands/sync/factory_test.go b/subcommands/sync/factory_test.go index 166ad56a1..33f9ffafe 100644 --- a/subcommands/sync/factory_test.go +++ b/subcommands/sync/factory_test.go @@ -10,7 +10,6 @@ import ( // TestRegisteredFactory looks the command up through the registry, which // invokes the factory closure registered in init(). func TestRegisteredFactory(t *testing.T) { - cmd, _, _ := subcommands.Lookup([]string{"sync"}) + cmd, _, _, _ := subcommands.Lookup([]string{"sync"}) require.NotNil(t, cmd) - require.IsType(t, &Sync{}, cmd) } diff --git a/subcommands/sync/sync.go b/subcommands/sync/sync.go index 1f09a604a..0a9bb5470 100644 --- a/subcommands/sync/sync.go +++ b/subcommands/sync/sync.go @@ -34,25 +34,16 @@ import ( "github.com/PlakarKorp/plakar/utils" ) -type Sync struct { - subcommands.SubcommandBase - - PeerRepositoryLocation string - PeerRepositorySecret []byte - - Direction string - PackfileTempStorage string - Cache string - - SrcLocateOptions *locate.LocateOptions -} - func init() { - subcommands.Register(func() subcommands.Subcommand { return &Sync{} }, 0, "sync") + subcommands.Register(Sync, 0, "sync") } -func (cmd *Sync) Parse(ctx *appcontext.AppContext, args []string) error { - cmd.SrcLocateOptions = locate.NewDefaultLocateOptions() +func Sync(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + packfileTempStorage string + cache string + srcLocOpts = locate.NewDefaultLocateOptions() + ) flags := flag.NewFlagSet("sync", flag.ExitOnError) flags.Usage = func() { @@ -62,9 +53,9 @@ func (cmd *Sync) Parse(ctx *appcontext.AppContext, args []string) error { flags.PrintDefaults() } - cmd.SrcLocateOptions.InstallLocateFlags(flags) - flags.StringVar(&cmd.PackfileTempStorage, "packfiles", "", "memory or a path to a directory to store temporary packfiles") - flags.StringVar(&cmd.Cache, "cache", "vfs", "path to store vfs cache, 'no' for uncached and 'vfs' for the default in memory cache") + srcLocOpts.InstallLocateFlags(flags) + flags.StringVar(&packfileTempStorage, "packfiles", "", "memory or a path to a directory to store temporary packfiles") + flags.StringVar(&cache, "cache", "vfs", "path to store vfs cache, 'no' for uncached and 'vfs' for the default in memory cache") flags.Parse(args) @@ -81,10 +72,10 @@ func (cmd *Sync) Parse(ctx *appcontext.AppContext, args []string) error { direction = args[0] peerRepositoryPath = args[1] case 3: - if !cmd.SrcLocateOptions.Empty() { + if !srcLocOpts.Empty() { ctx.GetLogger().Warn("snapshot specified, filters will be ignored") } - cmd.SrcLocateOptions.Filters.IDs = []string{args[0]} + srcLocOpts.Filters.IDs = []string{args[0]} direction = args[1] peerRepositoryPath = args[2] @@ -158,45 +149,24 @@ func (cmd *Sync) Parse(ctx *appcontext.AppContext, args []string) error { peerCtx := appcontext.NewAppContextFrom(ctx) peerCtx.SetSecret(peerSecret) + peerCtx.StoreConfig = storeConfig _, err = repository.NewNoRebuild(peerCtx.GetInner(), peerCtx.GetSecret(), peerStore, peerStoreSerializedConfig, true) if err != nil { return err } - cmd.RepositorySecret = ctx.GetSecret() - cmd.PeerRepositoryLocation = peerRepositoryPath - cmd.PeerRepositorySecret = peerSecret - cmd.Direction = direction - - return nil -} - -func (cmd *Sync) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - storeConfig, err := ctx.Config.GetRepository(cmd.PeerRepositoryLocation) - if err != nil { - return 1, fmt.Errorf("peer store: %w", err) - } - - peerStore, peerStoreSerializedConfig, err := storage.Open(ctx.GetInner(), storeConfig) - if err != nil { - return 1, fmt.Errorf("could not open peer store %s: %w", cmd.PeerRepositoryLocation, err) - } - - peerCtx := appcontext.NewAppContextFrom(ctx) - peerCtx.SetSecret(cmd.PeerRepositorySecret) - peerCtx.StoreConfig = storeConfig peerRepository, err := repository.NewNoRebuild(peerCtx.GetInner(), peerCtx.GetSecret(), peerStore, peerStoreSerializedConfig, true) if err != nil { - return 1, fmt.Errorf("could not open peer repository %s: %w", cmd.PeerRepositoryLocation, err) + return fmt.Errorf("could not open peer repository %s: %w", peerRepositoryPath, err) } if _, err = cached.RebuildStateFromStore(peerCtx, peerRepository.Configuration().RepositoryID, storeConfig, false); err != nil { - return 1, fmt.Errorf("failed to rebuild peer repository's state %s: %w", cmd.PeerRepositoryLocation, err) + return fmt.Errorf("failed to rebuild peer repository's state %s: %w", peerRepositoryPath, err) } if repo.Configuration().RepositoryID == peerRepository.Configuration().RepositoryID { if repo.Origin() == peerRepository.Origin() && repo.Root() == peerRepository.Root() { - return 1, fmt.Errorf("cannot synchronize snapshots to the same store") + return fmt.Errorf("cannot synchronize snapshots to the same store") } ctx.GetLogger().Error("ATTENTION") @@ -212,39 +182,40 @@ func (cmd *Sync) Execute(ctx *appcontext.AppContext, repo *repository.Repository ctx.GetLogger().Error("STEP 2: if no error, recreate your target store using `plakar create` and sync again") ctx.GetLogger().Error("STEP 3: if errors were found, contact support@plakar.io and we will take care of you") ctx.GetLogger().Error("") - return 1, fmt.Errorf("cannot synchronize snapshots from cloned stores") + return fmt.Errorf("cannot synchronize snapshots from cloned stores") } - if cmd.PackfileTempStorage != "memory" { - tmpDir, err := os.MkdirTemp(cmd.PackfileTempStorage, "plakar-sync-"+repo.Configuration().RepositoryID.String()+"-*") + if packfileTempStorage != "memory" { + tmpDir, err := os.MkdirTemp(packfileTempStorage, "plakar-sync-"+repo.Configuration().RepositoryID.String()+"-*") if err != nil { - return 1, err + return err } - cmd.PackfileTempStorage = tmpDir - defer os.RemoveAll(cmd.PackfileTempStorage) + packfileTempStorage = tmpDir + defer os.RemoveAll(packfileTempStorage) } else { - cmd.PackfileTempStorage = "" + packfileTempStorage = "" } var srcRepository *repository.Repository var dstRepository *repository.Repository srcStoreConfig := ctx.StoreConfig - if cmd.Direction == "to" { + if direction == "to" { srcRepository = repo dstRepository = peerRepository - } else if cmd.Direction == "from" { + } else if direction == "from" { srcRepository = peerRepository dstRepository = repo srcStoreConfig = storeConfig tmp := ctx ctx = peerCtx peerCtx = tmp - } else if cmd.Direction == "with" { + } else if direction == "with" { srcRepository = repo dstRepository = peerRepository } else { - return 1, fmt.Errorf("could not synchronize %s: invalid direction, must be to, from or with", cmd.PeerRepositoryLocation) + return fmt.Errorf("could not synchronize %s: invalid direction, must be to, from or with", + peerRepositoryPath) } srcLocation := srcRepository.Origin() @@ -255,28 +226,28 @@ func (cmd *Sync) Execute(ctx *appcontext.AppContext, repo *repository.Repository for objMAC, err := range srcRepository.ListSnapshots() { if err != nil { - return 1, err + return err } srcSnapshotsMap[objMAC] = struct{}{} } for objMAC, err := range dstRepository.ListSnapshots() { if err != nil { - return 1, err + return err } dstSnapshotsMap[objMAC] = struct{}{} } srcSyncList := make([]objects.MAC, 0) - srcSnapshotIDs, err := locate.LocateSnapshotIDs(srcRepository, cmd.SrcLocateOptions) + srcSnapshotIDs, err := locate.LocateSnapshotIDs(srcRepository, srcLocOpts) if err != nil { - return 1, fmt.Errorf("could not locate snapshots in store %s: %s", dstLocation, err) + return fmt.Errorf("could not locate snapshots in store %s: %s", dstLocation, err) } - if cmd.Direction != "with" { + if direction != "with" { if len(srcSnapshotIDs) == 0 { ctx.GetLogger().Info("No matching snapshot found in store %s", srcLocation) - return 0, nil + return nil } } @@ -289,10 +260,10 @@ func (cmd *Sync) Execute(ctx *appcontext.AppContext, repo *repository.Repository srcSynced := 0 for _, snapshotID := range srcSyncList { if err := ctx.Err(); err != nil { - return 1, err + return err } - err := cmd.synchronize(ctx, peerCtx, srcRepository, dstRepository, srcStoreConfig, snapshotID) + err := synchronize(ctx, peerCtx, srcRepository, dstRepository, srcStoreConfig, snapshotID, packfileTempStorage, cache) if err != nil { ctx.GetLogger().Error("failed to synchronize snapshot %x from store %s: %s", snapshotID[:4], srcLocation, err) @@ -301,11 +272,11 @@ func (cmd *Sync) Execute(ctx *appcontext.AppContext, repo *repository.Repository } } - if cmd.Direction == "with" { + if direction == "with" { - dstSnapshotIDs, err := locate.LocateSnapshotIDs(dstRepository, cmd.SrcLocateOptions) + dstSnapshotIDs, err := locate.LocateSnapshotIDs(dstRepository, srcLocOpts) if err != nil { - return 1, fmt.Errorf("could not locate snapshots in store %s: %s", dstLocation, err) + return fmt.Errorf("could not locate snapshots in store %s: %s", dstLocation, err) } srcRepository = peerRepository @@ -325,10 +296,11 @@ func (cmd *Sync) Execute(ctx *appcontext.AppContext, repo *repository.Repository dstSynced := 0 for _, snapshotID := range dstSyncList { if err := ctx.Err(); err != nil { - return 1, err + return err } - err := cmd.synchronize(ctx, peerCtx, srcRepository, dstRepository, srcStoreConfig, snapshotID) + err := synchronize(ctx, peerCtx, srcRepository, dstRepository, srcStoreConfig, snapshotID, + packfileTempStorage, cache) if err != nil { ctx.GetLogger().Error("failed to synchronize snapshot %x from peer store %s: %s", snapshotID[:4], dstLocation, err) @@ -340,7 +312,7 @@ func (cmd *Sync) Execute(ctx *appcontext.AppContext, repo *repository.Repository srcLocation, dstLocation, srcSynced+dstSynced) - } else if cmd.Direction == "to" { + } else if direction == "to" { ctx.GetLogger().Info("sync: synchronization from %s to %s completed: %d snapshots synchronized", srcLocation, dstLocation, @@ -352,10 +324,17 @@ func (cmd *Sync) Execute(ctx *appcontext.AppContext, repo *repository.Repository srcSynced) } - return 0, nil + return nil } -func (cmd *Sync) synchronize(ctx, peerCtx *appcontext.AppContext, srcRepository, dstRepository *repository.Repository, srcStoreConfig map[string]string, snapshotID objects.MAC) error { +func synchronize( + ctx, peerCtx *appcontext.AppContext, + srcRepository, dstRepository *repository.Repository, + srcStoreConfig map[string]string, + snapshotID objects.MAC, + PackfileTempStorage string, + Cache string, +) error { srcLocation := srcRepository.Origin() dstLocation := dstRepository.Origin() @@ -366,7 +345,7 @@ func (cmd *Sync) synchronize(ctx, peerCtx *appcontext.AppContext, srcRepository, } defer srcSnapshot.Close() - dstSnapshot, err := snapshot.Create(dstRepository, repository.DefaultType, cmd.PackfileTempStorage, srcSnapshot.Header.Identifier, &snapshot.BuilderOptions{ + dstSnapshot, err := snapshot.Create(dstRepository, repository.DefaultType, PackfileTempStorage, srcSnapshot.Header.Identifier, &snapshot.BuilderOptions{ NoCommit: false, NoCheckpoint: false, StateRefresher: stateRefresher(peerCtx, dstRepository), @@ -380,7 +359,7 @@ func (cmd *Sync) synchronize(ctx, peerCtx *appcontext.AppContext, srcRepository, dstSnapshot.Header = srcSnapshot.Header var parentVFS *vfs.Filesystem - if cmd.Cache == "vfs" { + if Cache == "vfs" { parentID, _, err := locate.Match(dstRepository, &locate.LocateOptions{ Filters: locate.LocateFilters{ Latest: true, diff --git a/subcommands/sync/sync_test.go b/subcommands/sync/sync_test.go index 2de4c3e49..60abf5a38 100644 --- a/subcommands/sync/sync_test.go +++ b/subcommands/sync/sync_test.go @@ -98,16 +98,6 @@ func snapshotIDs(t *testing.T, repo *repository.Repository) map[objects.MAC]stru return ids } -func runSync(t *testing.T, fixture *syncFixture, args []string) { - subcommand := &Sync{} - err := subcommand.Parse(fixture.localCtx, args) - require.NoError(t, err) - - status, err := subcommand.Execute(fixture.localCtx, fixture.localRepo) - require.NoError(t, err) - require.Equal(t, 0, status) -} - func testSyncDirection(t *testing.T, direction string, localPassphrase, peerPassphrase []byte) { fixture := setupSync(t, localPassphrase, peerPassphrase) @@ -132,7 +122,7 @@ func testSyncDirection(t *testing.T, direction string, localPassphrase, peerPass wantSynchronized = 2 } - runSync(t, fixture, []string{direction, fixture.peerArg}) + require.NoError(t, Sync(fixture.localCtx, fixture.localRepo, []string{direction, fixture.peerArg})) // whatever the direction, both ends must now hold every snapshot localIDs := snapshotIDs(t, fixture.localRepo) @@ -186,7 +176,9 @@ func TestExecuteCmdSyncSnapshotID(t *testing.T) { defer snap.Close() indexId := snap.Header.GetIndexID() - runSync(t, fixture, []string{hex.EncodeToString(indexId[:]), "to", fixture.peerArg}) + + err := Sync(fixture.localCtx, fixture.localRepo, []string{hex.EncodeToString(indexId[:]), "to", fixture.peerArg}) + require.NoError(t, err) peerIDs := snapshotIDs(t, fixture.peerRepo) require.Contains(t, peerIDs, snap.Header.Identifier) diff --git a/subcommands/version/version.go b/subcommands/version/version.go index 7846b86b4..0b0ca89c9 100644 --- a/subcommands/version/version.go +++ b/subcommands/version/version.go @@ -27,10 +27,10 @@ import ( ) func init() { - subcommands.Register(func() subcommands.Subcommand { return &Version{} }, subcommands.BeforeRepositoryOpen, "version") + subcommands.Register(Version, subcommands.BeforeRepositoryOpen, "version") } -func (_ *Version) Parse(ctx *appcontext.AppContext, args []string) error { +func Version(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { flags := flag.NewFlagSet("version", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s\n", flags.Name()) @@ -43,14 +43,6 @@ func (_ *Version) Parse(ctx *appcontext.AppContext, args []string) error { return fmt.Errorf("Too many arguments") } - return nil -} - -type Version struct { - subcommands.SubcommandBase -} - -func (cmd *Version) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { fmt.Printf("plakar/%s\n", utils.GetVersion()) - return 0, nil + return nil } diff --git a/task/task.go b/task/task.go index 238cec445..73cc02958 100644 --- a/task/task.go +++ b/task/task.go @@ -5,7 +5,6 @@ import ( "github.com/PlakarKorp/kloset/repository" "github.com/PlakarKorp/plakar/appcontext" "github.com/PlakarKorp/plakar/reporting" - "github.com/PlakarKorp/plakar/subcommands" "github.com/PlakarKorp/plakar/subcommands/backup" "github.com/PlakarKorp/plakar/subcommands/check" "github.com/PlakarKorp/plakar/subcommands/maintenance" @@ -14,7 +13,7 @@ import ( "github.com/PlakarKorp/plakar/subcommands/sync" ) -func RunCommand(ctx *appcontext.AppContext, cmd subcommands.Subcommand, repo *repository.Repository, taskName string) (int, error) { +func Report(ctx *appcontext.AppContext, repo *repository.Repository, taskKind, taskName string) error { location := "" var err error @@ -25,31 +24,12 @@ func RunCommand(ctx *appcontext.AppContext, cmd subcommands.Subcommand, repo *re reporter := reporting.NewReporter(ctx) report := reporter.NewReport() - var taskKind string - switch cmd.(type) { - case *backup.Backup: - taskKind = "backup" - case *check.Check: - taskKind = "check" - case *restore.Restore: - taskKind = "restore" - case *sync.Sync: - taskKind = "sync" - case *rm.Rm: - taskKind = "rm" - case *maintenance.Maintenance: - taskKind = "maintenance" - default: - report.SetIgnore() - } - report.TaskStart(taskKind, taskName) if repo != nil { report.WithRepositoryName(location) report.WithRepository(repo) } - var status int var snapshotID objects.MAC var warning error if _, ok := cmd.(*backup.Backup); ok { @@ -59,7 +39,7 @@ func RunCommand(ctx *appcontext.AppContext, cmd subcommands.Subcommand, repo *re report.WithSnapshotID(snapshotID) } } else { - status, err = cmd.Execute(ctx, repo) + status, err = cmd(ctx, repo) } if status == 0 { @@ -74,5 +54,5 @@ func RunCommand(ctx *appcontext.AppContext, cmd subcommands.Subcommand, repo *re reporter.StopAndWait() - return status, err + return err } diff --git a/testing/cached.go b/testing/cached.go index 1f6286064..25f1af0c8 100644 --- a/testing/cached.go +++ b/testing/cached.go @@ -19,17 +19,15 @@ import ( func StartCached(t *testing.T, ctx *appcontext.AppContext) { srvCtx := appcontext.NewAppContextFrom(ctx) - srv := &cachedcmd.Cached{} - err := srv.Parse(srvCtx, []string{"-foreground", "-teardown", "5m"}) - require.NoError(t, err) + socketPath := filepath.Join(ctx.CacheDir, "cached.sock") + srv := cachedcmd.NewCmd(socketPath, 5*time.Minute) done := make(chan struct{}) go func() { defer close(done) - srv.Execute(srvCtx, nil) + srv.Run(srvCtx) }() - socketPath := filepath.Join(ctx.CacheDir, "cached.sock") require.Eventually(t, func() bool { conn, err := net.Dial("unix", socketPath) if err != nil { From f723491f2e971e1a017e55cd5a4caefb481a14b4 Mon Sep 17 00:00:00 2001 From: Omar Polo Date: Sun, 21 Jun 2026 13:25:35 +0000 Subject: [PATCH 2/2] more wip --- subcommands/backup/backup.go | 234 +++++++++++++++-------------------- 1 file changed, 102 insertions(+), 132 deletions(-) diff --git a/subcommands/backup/backup.go b/subcommands/backup/backup.go index 95dfd97b1..da01754e1 100644 --- a/subcommands/backup/backup.go +++ b/subcommands/backup/backup.go @@ -43,32 +43,8 @@ import ( "github.com/PlakarKorp/plakar/utils" ) -type Backup struct { - subcommands.SubcommandBase - - Job string - Tags []string - Excludes []string - Sources []string - OptCheck bool - Opts map[string]string - DryRun bool - PackfileTempStorage string - ForcedTimestamp time.Time - PreHook string - PostHook string - FailHook string - NoXattr bool - Cache string - NoProgress bool - Name string - Category string - Environment string - Perimeter string -} - func init() { - subcommands.Register(func() subcommands.Subcommand { return &Backup{} }, 0, "backup") + subcommands.Register(Backup, 0, "backup") } type ignoreFlags []string @@ -106,15 +82,32 @@ func (e *tagFlags) asList() []string { return strings.Split(tags, ",") } -func (cmd *Backup) Parse(ctx *appcontext.AppContext, args []string) error { +func Backup(ctx *appcontext.AppContext, repo *repository.Repository, args []string) error { + var ( + job string + check bool + opts = make(map[string]string) + dryRun bool + packfiles string + forceTimestamp time.Time + prehook string + posthook string + failhook string + noXattr bool + cache string + noProgress bool + name string + category string + environment string + perimeter string + ) + var opt_ignore_files ignoreFlags var opt_ignore ignoreFlags var opt_tags tagFlags excludes := []string{} - cmd.Opts = make(map[string]string) - flags := flag.NewFlagSet("backup", flag.ExitOnError) flags.Usage = func() { fmt.Fprintf(flags.Output(), "Usage: %s [OPTIONS] path\n", flags.Name()) @@ -124,26 +117,28 @@ func (cmd *Backup) Parse(ctx *appcontext.AppContext, args []string) error { } flags.Var(&opt_tags, "tag", "comma-separated list of tags to apply to the snapshot") - flags.StringVar(&cmd.Name, "name", "default", "backup name") - flags.StringVar(&cmd.Category, "category", "", "backup category") - flags.StringVar(&cmd.Environment, "environment", "", "backup environment") - flags.StringVar(&cmd.Perimeter, "perimeter", "", "backup perimeter") - flags.StringVar(&cmd.Job, "job", "", "backup job") + flags.StringVar(&name, "name", "default", "backup name") + flags.StringVar(&category, "category", "", "backup category") + flags.StringVar(&environment, "environment", "", "backup environment") + flags.StringVar(&perimeter, "perimeter", "", "backup perimeter") + flags.StringVar(&job, "job", "", "backup job") flags.Var(&opt_ignore_files, "ignore-file", "path to a file containing newline-separated gitignore patterns, treated as -ignore; can be specified multiple times") flags.Var(&opt_ignore, "ignore", "gitignore pattern to exclude files, can be specified multiple times to add several exclusion patterns") - flags.StringVar(&cmd.PackfileTempStorage, "packfiles", "", "memory or a path to a directory to store temporary packfiles") - flags.BoolVar(&cmd.OptCheck, "check", false, "check the snapshot after creating it") - flags.Var(utils.NewOptsFlag(cmd.Opts), "o", "specify extra importer options") - flags.BoolVar(&cmd.DryRun, "dry-run", false, "do not actually perform a backup") - flags.BoolVar(&cmd.NoXattr, "no-xattr", false, "do not back up extended attributes") - flags.StringVar(&cmd.Cache, "cache", "vfs", "path to store vfs cache, 'no' for uncached and 'vfs' for the default in memory cache") - flags.BoolVar(&cmd.NoProgress, "no-progress", false, "do not display progress") - - flags.Var(locate.NewTimeFlag(&cmd.ForcedTimestamp), "force-timestamp", "force a timestamp") + flags.StringVar(&packfiles, "packfiles", "", "memory or a path to a directory to store temporary packfiles") + flags.BoolVar(&check, "check", false, "check the snapshot after creating it") + flags.Var(utils.NewOptsFlag(opts), "o", "specify extra importer options") + flags.BoolVar(&dryRun, "dry-run", false, "do not actually perform a backup") + flags.BoolVar(&noXattr, "no-xattr", false, "do not back up extended attributes") + flags.StringVar(&cache, "cache", "vfs", "path to store vfs cache, 'no' for uncached and 'vfs' for the default in memory cache") + flags.BoolVar(&noProgress, "no-progress", false, "do not display progress") + flags.StringVar(&prehook, "pre-hook", "", "pre hook command") + flags.StringVar(&posthook, "post-hook", "", "post hook command") + + flags.Var(locate.NewTimeFlag(&forceTimestamp), "force-timestamp", "force a timestamp") flags.Parse(args) - if !cmd.ForcedTimestamp.IsZero() { - if cmd.ForcedTimestamp.After(time.Now()) { + if !forceTimestamp.IsZero() { + if forceTimestamp.After(time.Now()) { return fmt.Errorf("forced timestamp cannot be in the future") } } @@ -162,56 +157,40 @@ func (cmd *Backup) Parse(ctx *appcontext.AppContext, args []string) error { excludes = append(excludes, item) } - cmd.RepositorySecret = ctx.GetSecret() - cmd.Excludes = excludes - cmd.Tags = opt_tags.asList() - - // If no tags were provided via CLI flag, check PLAKAR_TAGS env var - if len(cmd.Tags) == 0 { + tags := opt_tags.asList() + if len(tags) == 0 { if envTags, ok := os.LookupEnv("PLAKAR_TAGS"); ok && envTags != "" { parts := strings.Split(envTags, ",") - var tags []string for _, t := range parts { t = strings.TrimSpace(t) if t != "" { tags = append(tags, t) } } - cmd.Tags = tags } } - cmd.Sources = flags.Args() - - if len(cmd.Sources) == 0 { - cmd.Sources = append(cmd.Sources, "fs:"+ctx.CWD) + Sources := flags.Args() + if len(Sources) == 0 { + Sources = append(Sources, "fs:"+ctx.CWD) } - return nil -} - -func (cmd *Backup) Execute(ctx *appcontext.AppContext, repo *repository.Repository) (int, error) { - ret, err, _, _ := cmd.DoBackup(ctx, repo) - return ret, err -} - -func (cmd *Backup) DoBackup(ctx *appcontext.AppContext, repo *repository.Repository) (int, error, objects.MAC, error) { emitter := repo.Emitter("import") defer emitter.Close() - opts := &snapshot.BuilderOptions{ - Name: cmd.Name, - Tags: cmd.Tags, - Job: cmd.Job, - Category: cmd.Category, - Environment: cmd.Environment, - Perimeter: cmd.Perimeter, - NoXattr: cmd.NoXattr, + builderOpts := &snapshot.BuilderOptions{ + Name: name, + Tags: tags, + Job: job, + Category: category, + Environment: environment, + Perimeter: perimeter, + NoXattr: noXattr, StateRefresher: stateRefresher(ctx, repo), } - if !cmd.ForcedTimestamp.IsZero() { - opts.ForcedTimestamp = cmd.ForcedTimestamp + if !forceTimestamp.IsZero() { + builderOpts.ForcedTimestamp = forceTimestamp } sourcesPerOrig := make(map[string][]importer.Importer) @@ -219,7 +198,7 @@ func (cmd *Backup) DoBackup(ctx *appcontext.AppContext, repo *repository.Reposit // otherwise it makes plugin development harder than needed. sourcesPerOrigForStats := make(map[string][]importer.Importer) - for _, source := range cmd.Sources { + for _, source := range Sources { scanDir := "fs:" + ctx.CWD if source != "" { scanDir = source @@ -227,15 +206,15 @@ func (cmd *Backup) DoBackup(ctx *appcontext.AppContext, repo *repository.Reposit // We are going to mutate this, so do a copy cmdOptsCopy := make(map[string]string) - maps.Copy(cmdOptsCopy, cmd.Opts) + maps.Copy(cmdOptsCopy, opts) if strings.HasPrefix(scanDir, "@") { remote, ok := ctx.Config.GetSource(scanDir[1:]) if !ok { - return 1, fmt.Errorf("could not resolve importer: %s", scanDir), objects.MAC{}, nil + return fmt.Errorf("could not resolve importer: %s", scanDir) } if _, ok := remote["location"]; !ok { - return 1, fmt.Errorf("could not resolve importer location: %s", scanDir), objects.MAC{}, nil + return fmt.Errorf("could not resolve importer location: %s", scanDir) } else { // inherit all the options -- but the ones // specified in the command line takes the @@ -253,17 +232,17 @@ func (cmd *Backup) DoBackup(ctx *appcontext.AppContext, repo *repository.Reposit cmdOptsCopy["location"] = scanDir } - excludes := exclude.NewRuleSet() - if err := excludes.AddRulesFromArray(cmd.Excludes); err != nil { - return 1, fmt.Errorf("failed to setup exclude rules: %w", err), objects.MAC{}, nil + e := exclude.NewRuleSet() + if err := e.AddRulesFromArray(excludes); err != nil { + return fmt.Errorf("failed to setup exclude rules: %w", err) } importerOpts := ctx.ImporterOpts() - importerOpts.Excludes = cmd.Excludes + importerOpts.Excludes = excludes imp, err := importer.NewImporter(ctx.GetInner(), importerOpts, cmdOptsCopy) if err != nil { - return 1, fmt.Errorf("failed to create an importer for %s: %s", scanDir, err), objects.MAC{}, nil + return fmt.Errorf("failed to create an importer for %s: %s", scanDir, err) } defer imp.Close(ctx) @@ -275,10 +254,10 @@ func (cmd *Backup) DoBackup(ctx *appcontext.AppContext, repo *repository.Reposit importerKey := typ + ":" + orig sourcesPerOrig[importerKey] = append(sourcesPerOrig[importerKey], imp) - if !cmd.NoProgress && (imp.Flags()&location.FLAG_STREAM) == 0 { + if !noProgress && (imp.Flags()&location.FLAG_STREAM) == 0 { imp, err := importer.NewImporter(ctx.GetInner(), importerOpts, cmdOptsCopy) if err != nil { - return 1, fmt.Errorf("failed to create an importer for %s: %s", scanDir, err), objects.MAC{}, nil + return fmt.Errorf("failed to create an importer for %s: %s", scanDir, err) } defer imp.Close(ctx) sourcesPerOrigForStats[importerKey] = append(sourcesPerOrigForStats[importerKey], imp) @@ -287,57 +266,57 @@ func (cmd *Backup) DoBackup(ctx *appcontext.AppContext, repo *repository.Reposit // XXX - until we unlock multi-source if len(sourcesPerOrig) != 1 { - return 1, fmt.Errorf("multi-source backup not supported yet"), objects.MAC{}, nil + return fmt.Errorf("multi-source backup not supported yet") } - if cmd.PackfileTempStorage == "memory" { - cmd.PackfileTempStorage = "" + if packfiles == "memory" { + packfiles = "" } else { - tmpDir, err := os.MkdirTemp(cmd.PackfileTempStorage, "plakar-backup-"+repo.Configuration().RepositoryID.String()+"-*") + tmpDir, err := os.MkdirTemp(packfiles, "plakar-backup-"+repo.Configuration().RepositoryID.String()+"-*") if err != nil { - return 1, err, objects.NilMac, nil + return err } - cmd.PackfileTempStorage = tmpDir - defer os.RemoveAll(cmd.PackfileTempStorage) + packfiles = tmpDir + defer os.RemoveAll(packfiles) } // Execute pre-backup hook - if err := executeHook(ctx, cmd.PreHook); err != nil { - return 1, fmt.Errorf("pre-backup hook failed: %w", err), objects.MAC{}, nil + if err := executeHook(ctx, prehook); err != nil { + return fmt.Errorf("pre-backup hook failed: %w", err) } - snap, err := snapshot.Create(repo, repository.DefaultType, cmd.PackfileTempStorage, objects.NilMac, opts) + snap, err := snapshot.Create(repo, repository.DefaultType, packfiles, objects.NilMac, builderOpts) if err != nil { ctx.GetLogger().Error("%s", err) - return 1, err, objects.MAC{}, nil + return err } defer snap.Close() - if cmd.Job != "" { - snap.Header.Job = cmd.Job + if job != "" { + snap.Header.Job = job } // Actual import of sources. for key, sourceImporters := range sourcesPerOrig { source, err := snapshot.NewSource(repo.AppContext(), sourceImporters...) if err != nil { - return 1, err, objects.NilMac, nil + return err } - if err := source.SetExcludes(cmd.Excludes); err != nil { - return 1, err, objects.MAC{}, nil + if err := source.SetExcludes(excludes); err != nil { + return err } - if cmd.DryRun { + if dryRun { if err := dryrun(ctx, source, emitter); err != nil { - return 1, err, objects.MAC{}, nil + return err } - return 0, nil, objects.MAC{}, nil + return nil } var parentVFS *vfs.Filesystem - if cmd.Cache == "vfs" { + if cache == "vfs" { parentID, _, err := locate.Match(repo, &locate.LocateOptions{ Filters: locate.LocateFilters{ Latest: true, @@ -353,7 +332,7 @@ func (cmd *Backup) DoBackup(ctx *appcontext.AppContext, repo *repository.Reposit }, }) if err != nil { - return 1, nil, objects.MAC{}, err + return nil } if len(parentID) != 0 { @@ -372,14 +351,14 @@ func (cmd *Backup) DoBackup(ctx *appcontext.AppContext, repo *repository.Reposit } snap.WithVFSCache(parentVFS) - if !cmd.NoProgress && (source.Flags()&location.FLAG_STREAM) == 0 { + if !noProgress && (source.Flags()&location.FLAG_STREAM) == 0 { source, err := snapshot.NewSource(repo.AppContext(), sourcesPerOrigForStats[key]...) if err != nil { - return 1, err, objects.NilMac, nil + return err } - if err := source.SetExcludes(cmd.Excludes); err != nil { - return 1, err, objects.MAC{}, nil + if err := source.SetExcludes(excludes); err != nil { + return err } go func() { @@ -395,24 +374,24 @@ func (cmd *Backup) DoBackup(ctx *appcontext.AppContext, repo *repository.Reposit } if err := snap.Backup(source); err != nil { - if err := executeHook(ctx, cmd.FailHook); err != nil { + if err := executeHook(ctx, failhook); err != nil { ctx.GetLogger().Warn("post-backup fail hook failed: %s", err) } - return 1, fmt.Errorf("failed to backup source: %w", err), objects.MAC{}, nil + return fmt.Errorf("failed to backup source: %w", err) } } if err := snap.Commit(); err != nil { - if err := executeHook(ctx, cmd.FailHook); err != nil { + if err := executeHook(ctx, failhook); err != nil { ctx.GetLogger().Warn("post-backup fail hook failed: %s", err) } - return 1, fmt.Errorf("failed to commit snapshot: %w", err), objects.MAC{}, nil + return fmt.Errorf("failed to commit snapshot: %w", err) } - if cmd.OptCheck { + if check { _, err := cached.RebuildStateFromStore(ctx, repo.Configuration().RepositoryID, ctx.StoreConfig, false) if err != nil { - return 1, fmt.Errorf("failed to rebuild state %w", err), objects.MAC{}, nil + return fmt.Errorf("failed to rebuild state %w", err) } checkOptions := &snapshot.CheckOptions{ @@ -421,41 +400,32 @@ func (cmd *Backup) DoBackup(ctx *appcontext.AppContext, repo *repository.Reposit checkSnap, err := snapshot.Load(repo, snap.Header.Identifier) if err != nil { - return 1, fmt.Errorf("failed to load snapshot: %w", err), objects.MAC{}, nil + return fmt.Errorf("failed to load snapshot: %w", err) } defer checkSnap.Close() checkCache, err := ctx.GetCache().Check() if err != nil { - return 1, err, objects.MAC{}, nil + return err } defer checkCache.Close() checkSnap.SetCheckCache(checkCache) if err := checkSnap.Check("/", checkOptions); err != nil { - if err := executeHook(ctx, cmd.FailHook); err != nil { + if err := executeHook(ctx, failhook); err != nil { ctx.GetLogger().Warn("post-backup fail hook failed: %s", err) } - return 1, fmt.Errorf("failed to check snapshot: %w", err), objects.MAC{}, nil + return fmt.Errorf("failed to check snapshot: %w", err) } } // Execute post-backup hook - if err := executeHook(ctx, cmd.PostHook); err != nil { + if err := executeHook(ctx, posthook); err != nil { ctx.GetLogger().Warn("post-backup hook failed: %s", err) } - totalErrors := uint64(0) - for i := 0; i < len(snap.Header.Sources); i++ { - s := snap.Header.GetSource(i) - totalErrors += s.Summary.Directory.Errors + s.Summary.Below.Errors - } - var warning error - if totalErrors > 0 { - warning = fmt.Errorf("%d errors during backup", totalErrors) - } - return 0, nil, snap.Header.Identifier, warning + return nil } func LoadIgnoreFile(filename string) ([]string, error) {