Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ cachew namespaces

# Directory snapshots
cachew save <namespace> <directory> [paths...] (--key <key> | -H <glob>) [--ttl 1h] [--exclude pattern]
cachew restore <namespace> <directory> (--key <key> | -H <glob>)
cachew restore <namespace> <directory> (--key <key> | -H <glob>) # exit 0 hit, 2 miss, 1 error

# Git
cachew git restore <repo-url> <directory> [--no-bundle]
Expand Down
19 changes: 16 additions & 3 deletions cmd/cachew/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ type CLI struct {
Git GitCmd `cmd:"" help:"Git-aware operations." group:"Git:"`
}

func main() {
func main() { os.Exit(run()) }

func run() int {
cli := CLI{}
kctx := kong.Parse(&cli, kong.UsageOnError(), kong.HelpOptions{Compact: true}, kong.DefaultEnvars("CACHEW"), kong.Bind(&cli))
ctx := context.Background()
Expand All @@ -56,9 +58,19 @@ func main() {
kctx.BindTo(ctx, (*context.Context)(nil))
kctx.Bind(c)
kctx.Bind(c.HTTP())
kctx.FatalIfErrorf(kctx.Run(ctx))
err := kctx.Run(ctx)
if errors.Is(err, errCacheMiss) {
return 2
}
kctx.FatalIfErrorf(err)
return 0
}

// errCacheMiss signals that a restore found no object for the requested key.
// Sentinel so main can exit with code 2 (distinct from generic errors at
// code 1), matching conventions used by grep/diff.
var errCacheMiss = errors.New("cache miss")

type GetCmd struct {
Namespace client.Namespace `arg:"" help:"Namespace for organizing cache objects."`
Key PlatformKey `arg:"" help:"Object key (hex or string)."`
Expand Down Expand Up @@ -222,7 +234,8 @@ func (c *RestoreCmd) Run(ctx context.Context, api *client.Client, cli *CLI) erro
return errors.Wrap(err, "failed to restore")
}
if !hit {
return errors.Errorf("cache miss: %s", display)
fmt.Fprintf(os.Stderr, "Cache miss: %s\n", display) //nolint:forbidigo
return errCacheMiss
}

fmt.Fprintf(os.Stderr, "Restored: %s\n", display) //nolint:forbidigo
Expand Down
22 changes: 22 additions & 0 deletions cmd/cachew/save_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package main

import (
"context"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"runtime"
"testing"
"time"

"github.com/alecthomas/assert/v2"
"github.com/alecthomas/errors"

"github.com/block/cachew/client"
)
Expand Down Expand Up @@ -53,3 +57,21 @@ func TestResolveKeyHashFilesNoMatch(t *testing.T) {
_, _, err := resolveKey(&CLI{}, "", []string{filepath.Join(t.TempDir(), "missing-*")})
assert.Error(t, err)
}

func TestRestoreCacheMiss(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
http.NotFound(w, nil) //nolint:staticcheck
}))
defer srv.Close()

api := client.New(srv.URL, nil)
defer api.Close()

cmd := &RestoreCmd{
Namespace: "test",
Directory: t.TempDir(),
Key: "absent",
}
err := cmd.Run(context.Background(), api, &CLI{})
assert.True(t, errors.Is(err, errCacheMiss), "expected errCacheMiss sentinel, got %v", err)
}