package cmd import ( "context" "crypto/sha256" "encoding/hex" "fmt" "io" "net/http" "os" "strings" "github.com/spf13/cobra" ) var ( flagDownloadURL string flagDownloadSHA256 string flagForce bool ) var downloadCmd = &cobra.Command{ Use: "download ", Short: "Download a cached file; fall back to --url on cache miss", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { cli, cfg, err := client() if err != nil { return err } key, err := resolveKey(args[0], cfg) if err != nil { return err } dest := args[1] ctx := context.Background() exists, err := cli.Exists(ctx, key) if err != nil { return err } if exists { fmt.Fprintf(cmd.ErrOrStderr(), "Cache HIT — %s\n", key) rc, err := cli.Get(ctx, key) if err != nil { return err } defer rc.Close() return writeWithSHA(dest, rc, flagDownloadSHA256) } fmt.Fprintf(cmd.ErrOrStderr(), "Cache MISS — %s\n", key) if flagDownloadURL == "" { return fmt.Errorf("%w: %s", ErrMissNoFallback, key) } fmt.Fprintf(cmd.ErrOrStderr(), "Fetching %s\n", flagDownloadURL) body, err := httpGet(flagDownloadURL) if err != nil { return err } defer body.Close() // Tee to a tempfile so we can verify the checksum before re-reading // for the upload step. Pure-streaming up to s3 + sha256 verify is // possible but complicates rewind on mismatch — tempfile keeps it // honest at the cost of one extra disk pass. tmp, err := os.CreateTemp("", "cacher-dl-*") if err != nil { return err } defer os.Remove(tmp.Name()) defer tmp.Close() if _, err := io.Copy(tmp, body); err != nil { return err } if _, err := tmp.Seek(0, io.SeekStart); err != nil { return err } if err := writeWithSHA(dest, tmp, flagDownloadSHA256); err != nil { return err } // Re-open the verified destination file for upload to S3 so callers // see the cached file on next run. f, err := os.Open(dest) if err != nil { return err } defer f.Close() if err := cli.Put(ctx, key, f); err != nil { fmt.Fprintf(cmd.ErrOrStderr(), "warn: upload to S3 failed: %v\n", err) // Local download succeeded — don't fail the build just because // of an upload glitch. return nil } fmt.Fprintf(cmd.ErrOrStderr(), "Cached → %s\n", key) return nil }, } var uploadCmd = &cobra.Command{ Use: "upload ", Short: "Upload a local file to the cache", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { cli, cfg, err := client() if err != nil { return err } key, err := resolveKey(args[0], cfg) if err != nil { return err } ctx := context.Background() if !flagForce { exists, err := cli.Exists(ctx, key) if err != nil { return err } if exists { fmt.Fprintf(cmd.ErrOrStderr(), "Skipped — %s already present (use --force to overwrite)\n", key) return nil } } f, err := os.Open(args[1]) if err != nil { return err } defer f.Close() if err := cli.Put(ctx, key, f); err != nil { return err } fmt.Fprintf(cmd.ErrOrStderr(), "Uploaded → %s\n", key) return nil }, } var existsCmd = &cobra.Command{ Use: "exists ", Short: "Exit 0 if key exists, 1 if missing, 2 on error", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cli, cfg, err := client() if err != nil { return err } key, err := resolveKey(args[0], cfg) if err != nil { return err } ok, err := cli.Exists(context.Background(), key) if err != nil { return err } fmt.Fprintln(cmd.OutOrStdout(), key) if !ok { return ErrNotFound } return nil }, } var listCmd = &cobra.Command{ Use: "list [sub-prefix]", Short: "List cache keys under prefix (optionally narrowed by sub-prefix)", Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cli, _, err := client() if err != nil { return err } sub := "" if len(args) == 1 { sub = args[0] } keys, err := cli.List(context.Background(), sub) if err != nil { return err } out := cmd.OutOrStdout() for _, k := range keys { fmt.Fprintln(out, k) } return nil }, } var deleteCmd = &cobra.Command{ Use: "delete ", Short: "Delete a cache key (no error if missing)", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cli, cfg, err := client() if err != nil { return err } key, err := resolveKey(args[0], cfg) if err != nil { return err } if err := cli.Delete(context.Background(), key); err != nil { return err } fmt.Fprintf(cmd.ErrOrStderr(), "Deleted — %s\n", key) return nil }, } var keyCmd = &cobra.Command{ Use: "key