From 1d027bde570ef6e4aeda280c36db3f4e0c7b3253 Mon Sep 17 00:00:00 2001 From: sneak Date: Sat, 26 Jul 2025 02:41:00 +0200 Subject: [PATCH] Fix prune command to use config file for bucket and prefix - Remove --bucket and --prefix command line flags - Use bucket and prefix from S3 configuration in config file - Update command to follow same pattern as other commands - Maintain consistency that all configuration comes from config file --- internal/cli/prune.go | 145 +++++++++++++++++++++++++++++++----------- 1 file changed, 107 insertions(+), 38 deletions(-) diff --git a/internal/cli/prune.go b/internal/cli/prune.go index 2490ccf..467eb26 100644 --- a/internal/cli/prune.go +++ b/internal/cli/prune.go @@ -5,18 +5,31 @@ import ( "fmt" "os" + "git.eeqj.de/sneak/vaultik/internal/backup" + "git.eeqj.de/sneak/vaultik/internal/config" + "git.eeqj.de/sneak/vaultik/internal/database" "git.eeqj.de/sneak/vaultik/internal/globals" + "git.eeqj.de/sneak/vaultik/internal/log" + "git.eeqj.de/sneak/vaultik/internal/s3" "github.com/spf13/cobra" "go.uber.org/fx" ) // PruneOptions contains options for the prune command type PruneOptions struct { - Bucket string - Prefix string DryRun bool } +// PruneApp contains all dependencies needed for pruning +type PruneApp struct { + Globals *globals.Globals + Config *config.Config + Repositories *database.Repositories + S3Client *s3.Client + DB *database.DB + Shutdowner fx.Shutdowner +} + // NewPruneCommand creates the prune command func NewPruneCommand() *cobra.Command { opts := &PruneOptions{} @@ -24,55 +37,111 @@ func NewPruneCommand() *cobra.Command { cmd := &cobra.Command{ Use: "prune", Short: "Remove unreferenced blobs", - Long: `Delete blobs that are no longer referenced by any snapshot`, - Args: cobra.NoArgs, + Long: `Delete blobs that are no longer referenced by any snapshot. + +This command will: +1. Download all snapshot metadata from S3 +2. Build a list of all referenced blobs +3. List all blobs in S3 +4. Delete any blobs not referenced by any snapshot + +Config is located at /etc/vaultik/config.yml by default, but can be overridden by +specifying a path using --config or by setting VAULTIK_CONFIG to a path.`, + Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { - // Validate required flags - if opts.Bucket == "" { - return fmt.Errorf("--bucket is required") + // Check for private key + if os.Getenv("VAULTIK_PRIVATE_KEY") == "" { + return fmt.Errorf("VAULTIK_PRIVATE_KEY environment variable must be set") } - if opts.Prefix == "" { - return fmt.Errorf("--prefix is required") + + // Use unified config resolution + configPath, err := ResolveConfigPath() + if err != nil { + return err } - return runPrune(cmd.Context(), opts) + + // Use the app framework like other commands + rootFlags := GetRootFlags() + return RunWithApp(cmd.Context(), AppOptions{ + ConfigPath: configPath, + LogOptions: log.LogOptions{ + Verbose: rootFlags.Verbose, + Debug: rootFlags.Debug, + }, + Modules: []fx.Option{ + backup.Module, + s3.Module, + fx.Provide(fx.Annotate( + func(g *globals.Globals, cfg *config.Config, repos *database.Repositories, + s3Client *s3.Client, db *database.DB, shutdowner fx.Shutdowner) *PruneApp { + return &PruneApp{ + Globals: g, + Config: cfg, + Repositories: repos, + S3Client: s3Client, + DB: db, + Shutdowner: shutdowner, + } + }, + )), + }, + Invokes: []fx.Option{ + fx.Invoke(func(app *PruneApp, lc fx.Lifecycle) { + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + // Start the prune operation in a goroutine + go func() { + // Run the prune operation + if err := app.runPrune(ctx, opts); err != nil { + if err != context.Canceled { + log.Error("Prune operation failed", "error", err) + } + } + + // Shutdown the app when prune completes + if err := app.Shutdowner.Shutdown(); err != nil { + log.Error("Failed to shutdown", "error", err) + } + }() + return nil + }, + OnStop: func(ctx context.Context) error { + log.Debug("Stopping prune operation") + return nil + }, + }) + }), + }, + }) }, } - cmd.Flags().StringVar(&opts.Bucket, "bucket", "", "S3 bucket name") - cmd.Flags().StringVar(&opts.Prefix, "prefix", "", "S3 prefix") cmd.Flags().BoolVar(&opts.DryRun, "dry-run", false, "Show what would be deleted without actually deleting") return cmd } -func runPrune(ctx context.Context, opts *PruneOptions) error { - if os.Getenv("VAULTIK_PRIVATE_KEY") == "" { - return fmt.Errorf("VAULTIK_PRIVATE_KEY environment variable must be set") - } - - app := fx.New( - fx.Supply(opts), - fx.Provide(globals.New), - // Additional modules will be added here - fx.Invoke(func(g *globals.Globals) error { - // TODO: Implement prune logic - fmt.Printf("Pruning bucket %s with prefix %s\n", opts.Bucket, opts.Prefix) - if opts.DryRun { - fmt.Println("Running in dry-run mode") - } - return nil - }), - fx.NopLogger, +// runPrune executes the prune operation +func (app *PruneApp) runPrune(ctx context.Context, opts *PruneOptions) error { + log.Info("Starting prune operation", + "bucket", app.Config.S3.Bucket, + "prefix", app.Config.S3.Prefix, + "dry_run", opts.DryRun, ) - if err := app.Start(ctx); err != nil { - return fmt.Errorf("failed to start prune: %w", err) + // TODO: Implement the actual prune logic + // 1. Download all snapshot metadata + // 2. Build set of referenced blobs + // 3. List all blobs in S3 + // 4. Delete unreferenced blobs + + fmt.Printf("Pruning bucket %s with prefix %s\n", app.Config.S3.Bucket, app.Config.S3.Prefix) + if opts.DryRun { + fmt.Println("Running in dry-run mode") } - defer func() { - if err := app.Stop(ctx); err != nil { - fmt.Printf("error stopping app: %v\n", err) - } - }() + + // For now, just show we're using the config properly + log.Info("Prune operation completed successfully") return nil -} +} \ No newline at end of file