From efc9eeb536393fdca0dd8f9c85ceee121fa82536 Mon Sep 17 00:00:00 2001 From: Justin Harms Date: Sun, 31 May 2026 17:10:31 -0500 Subject: [PATCH] Updated code structure using new Cobra CLI conventions --- cmd/pastebin/main.go | 73 ++++------------------------------- cmd/pastebin/main_test.go | 8 ++-- cmd/pastebin/server.go | 80 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 69 deletions(-) create mode 100644 cmd/pastebin/server.go diff --git a/cmd/pastebin/main.go b/cmd/pastebin/main.go index 6ff4b7a..3f0ff5a 100644 --- a/cmd/pastebin/main.go +++ b/cmd/pastebin/main.go @@ -1,87 +1,30 @@ package main import ( - "context" "fmt" - "log" - "net/http" "os" - "os/signal" - "syscall" - "time" "github.com/spf13/cobra" - - "scratchbox/internal/cleanup" - "scratchbox/internal/config" - httpapi "scratchbox/internal/http" - "scratchbox/internal/storage" ) func main() { cmd := newRootCmd() if err := cmd.Execute(); err != nil { - log.Fatalf("command failed: %v", err) + fmt.Fprintln(os.Stderr, err) + os.Exit(1) } } func newRootCmd() *cobra.Command { - var configPath string - cmd := &cobra.Command{ - Use: "pastebin", - Short: "Run the scratchbox service", + Use: "scratchbox", + Short: "Scratchbox CLI", SilenceUsage: true, - RunE: func(_ *cobra.Command, _ []string) error { - return run(configPath) + SilenceErrors: true, + RunE: func(cmd *cobra.Command, _ []string) error { + return cmd.Help() }, } - cmd.Flags().StringVarP(&configPath, "config", "c", "", "path to config file") + cmd.AddCommand(newServerCmd()) return cmd } - -func run(configPath string) error { - cfg, err := config.Load(configPath) - if err != nil { - return fmt.Errorf("config load failed: %w", err) - } - - logger := log.New(os.Stdout, "[pastebin] ", log.LstdFlags|log.LUTC) - - store, err := storage.NewFilesystemStore(cfg.Storage.DataDir) - if err != nil { - return fmt.Errorf("storage init failed: %w", err) - } - - server, err := httpapi.NewServer(cfg, store, logger) - if err != nil { - return fmt.Errorf("http init failed: %w", err) - } - - cleanupWorker := cleanup.NewWorker(store, cfg.Storage.CleanupIntervalParsed, logger) - - ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) - defer cancel() - - go cleanupWorker.Start(ctx) - - httpServer := &http.Server{ - Addr: cfg.Server.ListenAddr, - Handler: server.Routes(), - } - - go func() { - <-ctx.Done() - shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) - defer shutdownCancel() - if err := httpServer.Shutdown(shutdownCtx); err != nil { - logger.Printf("http shutdown failed: %v", err) - } - }() - - logger.Printf("listening on %s", cfg.Server.ListenAddr) - if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { - return fmt.Errorf("http server exited with error: %w", err) - } - return nil -} diff --git a/cmd/pastebin/main_test.go b/cmd/pastebin/main_test.go index c5aecef..24aea9f 100644 --- a/cmd/pastebin/main_test.go +++ b/cmd/pastebin/main_test.go @@ -180,16 +180,16 @@ func TestMainHelperProcess(t *testing.T) { _ = proc.Signal(os.Interrupt) } }() - os.Args = []string{"pastebin", "--config", cfgPath} + os.Args = []string{"scratchbox", "server", "--config", cfgPath} main() os.Exit(0) case "bad-config": - os.Args = []string{"pastebin", "--config", "/no/such/config.yaml"} + os.Args = []string{"scratchbox", "server", "--config", "/no/such/config.yaml"} main() os.Exit(0) case "storage-fail": cfgPath := os.Getenv("MAIN_HELPER_CONFIG") - os.Args = []string{"pastebin", "--config", cfgPath} + os.Args = []string{"scratchbox", "server", "--config", cfgPath} main() os.Exit(0) case "http-fail": @@ -197,7 +197,7 @@ func TestMainHelperProcess(t *testing.T) { if err := os.Chdir(t.TempDir()); err != nil { os.Exit(2) } - os.Args = []string{"pastebin", "--config", cfgPath} + os.Args = []string{"scratchbox", "server", "--config", cfgPath} main() os.Exit(0) default: diff --git a/cmd/pastebin/server.go b/cmd/pastebin/server.go new file mode 100644 index 0000000..8f16851 --- /dev/null +++ b/cmd/pastebin/server.go @@ -0,0 +1,80 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/spf13/cobra" + + "scratchbox/internal/cleanup" + "scratchbox/internal/config" + httpapi "scratchbox/internal/http" + "scratchbox/internal/storage" +) + +func newServerCmd() *cobra.Command { + var configPath string + + cmd := &cobra.Command{ + Use: "server", + Short: "Run the scratchbox service", + Args: cobra.NoArgs, + RunE: func(_ *cobra.Command, _ []string) error { + return run(configPath) + }, + } + cmd.Flags().StringVarP(&configPath, "config", "c", "", "path to config file") + return cmd +} + +func run(configPath string) error { + cfg, err := config.Load(configPath) + if err != nil { + return fmt.Errorf("config load failed: %w", err) + } + + logger := log.New(os.Stdout, "[pastebin] ", log.LstdFlags|log.LUTC) + + store, err := storage.NewFilesystemStore(cfg.Storage.DataDir) + if err != nil { + return fmt.Errorf("storage init failed: %w", err) + } + + server, err := httpapi.NewServer(cfg, store, logger) + if err != nil { + return fmt.Errorf("http init failed: %w", err) + } + + cleanupWorker := cleanup.NewWorker(store, cfg.Storage.CleanupIntervalParsed, logger) + + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer cancel() + + go cleanupWorker.Start(ctx) + + httpServer := &http.Server{ + Addr: cfg.Server.ListenAddr, + Handler: server.Routes(), + } + + go func() { + <-ctx.Done() + shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) + defer shutdownCancel() + if err := httpServer.Shutdown(shutdownCtx); err != nil { + logger.Printf("http shutdown failed: %v", err) + } + }() + + logger.Printf("listening on %s", cfg.Server.ListenAddr) + if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + return fmt.Errorf("http server exited with error: %w", err) + } + return nil +}