From ac7d82b55fb2d2e22e8f76b06312d68815ffa109 Mon Sep 17 00:00:00 2001 From: Zack Brady Date: Mon, 12 Jan 2026 16:36:37 -0500 Subject: [PATCH] added/updated logging for `serve` and `remove` (#487) * added error logging for hauler store serve * updated logging for hauler store remove to match others * added more error logging for user responses --- cmd/hauler/cli/store/remove.go | 30 +++++++++++++------- cmd/hauler/cli/store/serve.go | 51 ++++++++++++++++++++++++++++------ 2 files changed, 62 insertions(+), 19 deletions(-) diff --git a/cmd/hauler/cli/store/remove.go b/cmd/hauler/cli/store/remove.go index f8bc095..8eab88e 100644 --- a/cmd/hauler/cli/store/remove.go +++ b/cmd/hauler/cli/store/remove.go @@ -1,8 +1,12 @@ package store import ( + "bufio" "context" + "errors" "fmt" + "io" + "os" "strings" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -67,43 +71,49 @@ func RemoveCmd(ctx context.Context, o *flags.RemoveOpts, s *store.Layout, ref st if len(matches) >= 1 { l.Infof("found %d matching references:", len(matches)) for _, m := range matches { - l.Infof(" - %s", formatReference(m.reference)) + l.Infof(" - [%s]", formatReference(m.reference)) } } if !o.Force { - fmt.Printf("are you sure you want to remove [%d] artifact(s) from the store? (yes/no) ", len(matches)) + fmt.Printf(" ↳ are you sure you want to remove [%d] artifact(s) from the store? (yes/no) ", len(matches)) - var response string - _, err := fmt.Scanln(&response) - if err != nil { - return fmt.Errorf("failed to read response: %w", err) + reader := bufio.NewReader(os.Stdin) + + line, err := reader.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + return fmt.Errorf("failed to read response: [%w]... please answer 'yes' or 'no'", err) } + + response := strings.ToLower(strings.TrimSpace(line)) + switch response { case "yes", "y": l.Infof("starting to remove artifacts from store...") case "no", "n": l.Infof("successfully cancelled removal of artifacts from store") return nil + case "": + return fmt.Errorf("failed to read response... please answer 'yes' or 'no'") default: - return fmt.Errorf("invalid response '%s' - please answer 'yes' or 'no'", response) + return fmt.Errorf("invalid response [%s]... please answer 'yes' or 'no'", response) } } // remove artifact(s) for _, m := range matches { if err := s.RemoveArtifact(ctx, m.reference, m.desc); err != nil { - return fmt.Errorf("failed to remove artifact %s: %w", formatReference(m.reference), err) + return fmt.Errorf("failed to remove artifact [%s]: %w", formatReference(m.reference), err) } l.Infof("successfully removed [%s] of type [%s] with digest [%s]", formatReference(m.reference), m.desc.MediaType, m.desc.Digest.String()) } // clean up unreferenced blobs - l.Infof("cleaning up unreferenced blobs...") + l.Infof("cleaning up all unreferenced blobs...") removedCount, removedSize, err := s.CleanUp(ctx) if err != nil { - l.Warnf("garbage collection failed: %v", err) + l.Warnf("garbage collection failed: [%v]", err) } else if removedCount > 0 { l.Infof("successfully removed [%d] unreferenced blobs [freed %d bytes]", removedCount, removedSize) } diff --git a/cmd/hauler/cli/store/serve.go b/cmd/hauler/cli/store/serve.go index 4cecd80..806632b 100644 --- a/cmd/hauler/cli/store/serve.go +++ b/cmd/hauler/cli/store/serve.go @@ -2,9 +2,12 @@ package store import ( "context" + "errors" "fmt" + "io/fs" "net/http" "os" + "path/filepath" "strings" "github.com/distribution/distribution/v3/configuration" @@ -21,6 +24,37 @@ import ( "hauler.dev/go/hauler/pkg/store" ) +func validateStoreExists(s *store.Layout) error { + indexPath := filepath.Join(s.Root, "index.json") + + _, err := os.Stat(indexPath) + if err == nil { + return nil + } + + if errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf( + "no store found at [%s]\n ↳ does the hauler store exist? (verify with `hauler store info`)", + s.Root, + ) + } + + return fmt.Errorf( + "unable to access store at [%s]: %w", + s.Root, + err, + ) +} + +func loadConfig(filename string) (*configuration.Configuration, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + + return configuration.Parse(f) +} + func DefaultRegistryConfig(o *flags.ServeRegistryOpts, rso *flags.StoreRootOpts, ro *flags.CliRootOpts) *configuration.Configuration { cfg := &configuration.Configuration{ Version: "0.1", @@ -53,6 +87,10 @@ func ServeRegistryCmd(ctx context.Context, o *flags.ServeRegistryOpts, s *store. l := log.FromContext(ctx) ctx = dcontext.WithVersion(ctx, version.Version) + if err := validateStoreExists(s); err != nil { + return err + } + tr := server.NewTempRegistry(ctx, o.RootDir) if err := tr.Start(); err != nil { return err @@ -101,6 +139,10 @@ func ServeFilesCmd(ctx context.Context, o *flags.ServeFilesOpts, s *store.Layout l := log.FromContext(ctx) ctx = dcontext.WithVersion(ctx, version.Version) + if err := validateStoreExists(s); err != nil { + return err + } + opts := &flags.CopyOpts{} if err := CopyCmd(ctx, opts, s, "dir://"+o.RootDir, ro); err != nil { return err @@ -125,12 +167,3 @@ func ServeFilesCmd(ctx context.Context, o *flags.ServeFilesOpts, s *store.Layout return nil } - -func loadConfig(filename string) (*configuration.Configuration, error) { - f, err := os.Open(filename) - if err != nil { - return nil, err - } - - return configuration.Parse(f) -}