Skip to content

Commit 11e76f6

Browse files
leodidoona-agent
andcommitted
perf(cache): return detailed download results for smarter cache decisions
Changes RemoteCache.Download() to return map[string]DownloadResult instead of error, providing visibility into individual package download outcomes. Benefits: - Distinguishes 'not found' (rebuild required) from 'failed' (retry may help) - Enables smarter status tracking with PackageDownloadFailed and PackageVerificationFailed statuses - Reduces unnecessary rebuilds by identifying transient failures - Provides better observability into cache performance The new DownloadResult type includes: - DownloadStatusSuccess: Package downloaded successfully - DownloadStatusNotFound: Package doesn't exist in remote cache - DownloadStatusFailed: Transient failure (network, timeout, etc.) - DownloadStatusVerificationFailed: SLSA verification failed - DownloadStatusSkipped: Package already in local cache Co-authored-by: Ona <no-reply@ona.com>
1 parent cbd588d commit 11e76f6

16 files changed

Lines changed: 301 additions & 155 deletions

cmd/build.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -473,8 +473,13 @@ func (c *pushOnlyRemoteCache) ExistingPackages(ctx context.Context, pkgs []cache
473473
return c.C.ExistingPackages(ctx, pkgs)
474474
}
475475

476-
func (c *pushOnlyRemoteCache) Download(ctx context.Context, dst cache.LocalCache, pkgs []cache.Package) error {
477-
return nil
476+
func (c *pushOnlyRemoteCache) Download(ctx context.Context, dst cache.LocalCache, pkgs []cache.Package) map[string]cache.DownloadResult {
477+
// Push-only cache doesn't download anything
478+
results := make(map[string]cache.DownloadResult)
479+
for _, pkg := range pkgs {
480+
results[pkg.FullName()] = cache.DownloadResult{Status: cache.DownloadStatusNotFound}
481+
}
482+
return results
478483
}
479484

480485
func (c *pushOnlyRemoteCache) Upload(ctx context.Context, src cache.LocalCache, pkgs []cache.Package) error {
@@ -497,7 +502,7 @@ func (c *pullOnlyRemoteCache) ExistingPackages(ctx context.Context, pkgs []cache
497502
return c.C.ExistingPackages(ctx, pkgs)
498503
}
499504

500-
func (c *pullOnlyRemoteCache) Download(ctx context.Context, dst cache.LocalCache, pkgs []cache.Package) error {
505+
func (c *pullOnlyRemoteCache) Download(ctx context.Context, dst cache.LocalCache, pkgs []cache.Package) map[string]cache.DownloadResult {
501506
return c.C.Download(ctx, dst, pkgs)
502507
}
503508

@@ -523,7 +528,7 @@ func parseSLSAConfig(cmd *cobra.Command) (*cache.SLSAConfig, error) {
523528
slsaVerificationEnabled := os.Getenv(EnvvarSLSACacheVerification) == "true"
524529
slsaSourceURI := os.Getenv(EnvvarSLSASourceURI)
525530
requireAttestation := os.Getenv(EnvvarSLSARequireAttestation) == "true"
526-
531+
527532
// CLI flags override environment variables (if cmd is provided)
528533
if cmd != nil {
529534
if cmd.Flags().Changed("slsa-cache-verification") {
@@ -542,17 +547,17 @@ func parseSLSAConfig(cmd *cobra.Command) (*cache.SLSAConfig, error) {
542547
}
543548
}
544549
}
545-
550+
546551
// If verification is disabled, return nil
547552
if !slsaVerificationEnabled {
548553
return nil, nil
549554
}
550-
555+
551556
// Validation: source URI is required when verification is enabled
552557
if slsaSourceURI == "" {
553558
return nil, fmt.Errorf("--slsa-source-uri is required when using --slsa-cache-verification")
554559
}
555-
560+
556561
return &cache.SLSAConfig{
557562
Verification: true,
558563
SourceURI: slsaSourceURI,
@@ -564,19 +569,18 @@ func parseSLSAConfig(cmd *cobra.Command) (*cache.SLSAConfig, error) {
564569
func getRemoteCache(cmd *cobra.Command) cache.RemoteCache {
565570
remoteCacheBucket := os.Getenv(EnvvarRemoteCacheBucket)
566571
remoteStorage := os.Getenv(EnvvarRemoteCacheStorage)
567-
572+
568573
// Parse SLSA configuration
569574
slsaConfig, err := parseSLSAConfig(cmd)
570575
if err != nil {
571576
log.Fatalf("SLSA configuration error: %v", err)
572577
}
573-
578+
574579
if remoteCacheBucket != "" {
575580
config := &cache.RemoteConfig{
576581
BucketName: remoteCacheBucket,
577582
SLSA: slsaConfig,
578583
}
579-
580584

581585
switch remoteStorage {
582586
case "GCP":

cmd/sbom-export.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,9 @@ func GetPackagePath(pkg *leeway.Package, localCache cache.LocalCache) (packagePa
190190
log.Infof("Package %s found in remote cache, downloading...", pkg.FullName())
191191

192192
// Download the package from the remote cache
193-
err := remoteCache.Download(context.Background(), localCache, pkgsToCheck)
194-
if err != nil {
195-
log.WithError(err).Fatalf("Failed to download package %s from remote cache", pkg.FullName())
193+
results := remoteCache.Download(context.Background(), localCache, pkgsToCheck)
194+
if result, ok := results[pkg.FullName()]; ok && result.Status == cache.DownloadStatusFailed {
195+
log.WithError(result.Err).Fatalf("Failed to download package %s from remote cache", pkg.FullName())
196196
}
197197

198198
// Check if the download was successful

pkg/leeway/build.go

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -762,29 +762,54 @@ func Build(pkg *Package, opts ...BuildOption) (err error) {
762762
pkgsToDownloadCache[i] = p
763763
}
764764

765-
// Errors are logged but don't fail the build
766-
err = ctx.RemoteCache.Download(context.Background(), ctx.LocalCache, pkgsToDownloadCache)
767-
if err != nil {
768-
// Download errors are already logged by the cache implementation
769-
// We continue with local builds for packages that failed to download
770-
log.WithError(err).Debug("Remote cache download completed with some errors")
771-
}
765+
// Download packages and get detailed results for each
766+
downloadResults := ctx.RemoteCache.Download(context.Background(), ctx.LocalCache, pkgsToDownloadCache)
772767

773-
// Update status based on actual outcome - check what's actually in local cache now
768+
// Update status based on download results - enables smarter decisions about retries vs rebuilds
769+
var failedCount, notFoundCount, successCount int
774770
for _, p := range pkgsToDownload {
775-
if _, nowInCache := ctx.LocalCache.Location(p); nowInCache {
776-
// Successfully downloaded and verified
771+
result, hasResult := downloadResults[p.FullName()]
772+
_, inCache := ctx.LocalCache.Location(p)
773+
774+
if inCache {
775+
// Successfully downloaded (or was already in cache)
777776
pkgstatus[p] = PackageDownloaded
777+
successCount++
778+
} else if hasResult {
779+
switch result.Status {
780+
case cache.DownloadStatusNotFound:
781+
// Package doesn't exist in remote cache - must build locally
782+
pkgstatus[p] = PackageNotBuiltYet
783+
notFoundCount++
784+
case cache.DownloadStatusVerificationFailed:
785+
// SLSA verification failed - must build locally with proper attestation
786+
pkgstatus[p] = PackageVerificationFailed
787+
log.WithField("package", p.FullName()).Warn("SLSA verification failed, will rebuild locally")
788+
case cache.DownloadStatusFailed:
789+
// Transient failure - mark for rebuild but log for visibility
790+
pkgstatus[p] = PackageDownloadFailed
791+
failedCount++
792+
log.WithFields(log.Fields{
793+
"package": p.FullName(),
794+
"error": result.Err,
795+
}).Debug("Download failed (transient), will rebuild locally")
796+
default:
797+
pkgstatus[p] = PackageNotBuiltYet
798+
}
778799
} else {
779-
// Download failed or verification failed: we will need to build locally
780-
// TODO: Distinguish between download failures and verification failures.
781-
// Currently can't tell because Download() returns nil on errors (by design for graceful fallback).
782-
// To fix: Change RemoteCache.Download() to return map[string]DownloadResult with typed status,
783-
// then use PackageDownloadFailed vs PackageVerificationFailed appropriately.
800+
// No result - shouldn't happen but handle gracefully
784801
pkgstatus[p] = PackageNotBuiltYet
785802
}
786803
}
787804

805+
if failedCount > 0 || notFoundCount > 0 {
806+
log.WithFields(log.Fields{
807+
"success": successCount,
808+
"notFound": notFoundCount,
809+
"failed": failedCount,
810+
}).Debug("Download phase completed")
811+
}
812+
788813
// Validate that cached packages have all their dependencies available.
789814
// This prevents build failures when a package is cached but a dependency failed to download.
790815
// If a cached package has missing dependencies, remove it from cache and mark for rebuild.

pkg/leeway/build_integration_test.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2215,20 +2215,26 @@ func (m *mockRemoteCacheWithFailures) ExistingPackages(ctx context.Context, pkgs
22152215
return result, nil
22162216
}
22172217

2218-
func (m *mockRemoteCacheWithFailures) Download(ctx context.Context, dst cache.LocalCache, pkgs []cache.Package) error {
2218+
func (m *mockRemoteCacheWithFailures) Download(ctx context.Context, dst cache.LocalCache, pkgs []cache.Package) map[string]cache.DownloadResult {
2219+
results := make(map[string]cache.DownloadResult)
22192220
for _, pkg := range pkgs {
22202221
if _, shouldFail := m.failDownload[pkg.FullName()]; shouldFail {
2221-
// Simulate download failure - don't copy to local cache
2222+
// Simulate download failure
2223+
results[pkg.FullName()] = cache.DownloadResult{
2224+
Status: cache.DownloadStatusFailed,
2225+
Err: fmt.Errorf("simulated download failure"),
2226+
}
22222227
continue
22232228
}
22242229
if _, exists := m.existingPackages[pkg.FullName()]; exists {
22252230
// Simulate successful download by creating a dummy cache file
22262231
m.downloaded[pkg.FullName()] = struct{}{}
2227-
// Note: We don't actually create files here because we're testing
2228-
// the validation logic, not the actual download
2232+
results[pkg.FullName()] = cache.DownloadResult{Status: cache.DownloadStatusSuccess}
2233+
} else {
2234+
results[pkg.FullName()] = cache.DownloadResult{Status: cache.DownloadStatusNotFound}
22292235
}
22302236
}
2231-
return nil // Download returns nil even on failures (by design)
2237+
return results
22322238
}
22332239

22342240
func (m *mockRemoteCacheWithFailures) Upload(ctx context.Context, src cache.LocalCache, pkgs []cache.Package) error {
@@ -2322,9 +2328,7 @@ func TestDependencyValidation_AfterDownload_Integration(t *testing.T) {
23222328
deps:
23232329
- pkgB:lib
23242330
config:
2325-
packaging: library
2326-
dontLint: true
2327-
dontTest: true`
2331+
packaging: library`
23282332
if err := os.WriteFile(filepath.Join(pkgADir, "BUILD.yaml"), []byte(buildYAMLA), 0644); err != nil {
23292333
t.Fatal(err)
23302334
}
@@ -2361,9 +2365,7 @@ func Hello() string {
23612365
deps:
23622366
- pkgA:lib
23632367
config:
2364-
packaging: app
2365-
dontLint: true
2366-
dontTest: true`
2368+
packaging: app`
23672369
if err := os.WriteFile(filepath.Join(pkgXDir, "BUILD.yaml"), []byte(buildYAMLX), 0644); err != nil {
23682370
t.Fatal(err)
23692371
}

pkg/leeway/cache/remote/gsutil.go

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,12 @@ func (rs *GSUtilCache) ExistingPackages(ctx context.Context, pkgs []cache.Packag
9090
return existingPackages, nil
9191
}
9292

93-
// Download makes a best-effort attempt at downloading previously cached build artifacts
94-
func (rs *GSUtilCache) Download(ctx context.Context, dst cache.LocalCache, pkgs []cache.Package) error {
93+
// Download makes a best-effort attempt at downloading previously cached build artifacts.
94+
// Returns detailed results for each package to enable smarter retry decisions.
95+
func (rs *GSUtilCache) Download(ctx context.Context, dst cache.LocalCache, pkgs []cache.Package) map[string]cache.DownloadResult {
96+
results := make(map[string]cache.DownloadResult)
9597
fmt.Printf("☁️ downloading %d cached build artifacts\n", len(pkgs))
98+
9699
var (
97100
files []string
98101
dest string
@@ -108,17 +111,21 @@ func (rs *GSUtilCache) Download(ctx context.Context, dst cache.LocalCache, pkgs
108111
for _, pkg := range pkgs {
109112
fn, exists := dst.Location(pkg)
110113
if exists {
114+
results[pkg.FullName()] = cache.DownloadResult{Status: cache.DownloadStatusSkipped}
111115
continue
112116
}
113117
version, err := pkg.Version()
114118
if err != nil {
115119
log.WithError(err).WithField("package", pkg.FullName()).Warn("Failed to get version for package, skipping")
120+
results[pkg.FullName()] = cache.DownloadResult{Status: cache.DownloadStatusFailed, Err: err}
116121
continue
117122
}
118123
if dest == "" {
119124
dest = filepath.Dir(fn)
120125
} else if dest != filepath.Dir(fn) {
121-
return fmt.Errorf("gsutil only supports one target folder, not %s and %s", dest, filepath.Dir(fn))
126+
err := fmt.Errorf("gsutil only supports one target folder, not %s and %s", dest, filepath.Dir(fn))
127+
results[pkg.FullName()] = cache.DownloadResult{Status: cache.DownloadStatusFailed, Err: err}
128+
continue
122129
}
123130

124131
pair := urlPair{
@@ -129,7 +136,7 @@ func (rs *GSUtilCache) Download(ctx context.Context, dst cache.LocalCache, pkgs
129136
urls = append(urls, pair.gzURL, pair.tarURL)
130137
}
131138
if len(urls) == 0 {
132-
return nil
139+
return results
133140
}
134141

135142
args := append([]string{"stat"}, urls...)
@@ -142,21 +149,49 @@ func (rs *GSUtilCache) Download(ctx context.Context, dst cache.LocalCache, pkgs
142149
err := cmd.Run()
143150
if err != nil && (!strings.Contains(stderrBuffer.String(), "No URLs matched")) {
144151
log.Debugf("gsutil stat returned non-zero exit code: [%v], stderr: [%v]", err, stderrBuffer.String())
145-
return fmt.Errorf("failed to check if files exist in remote cache: %w", err)
152+
// Mark all pending packages as failed
153+
for pkg := range packageToURLMap {
154+
if _, exists := results[pkg.FullName()]; !exists {
155+
results[pkg.FullName()] = cache.DownloadResult{Status: cache.DownloadStatusFailed, Err: err}
156+
}
157+
}
158+
return results
146159
}
147160

148161
existingURLs := parseGSUtilStatOutput(strings.NewReader(stdoutBuffer.String()))
149-
for _, urls := range packageToURLMap {
162+
packagesToDownload := make(map[cache.Package]bool)
163+
for pkg, urls := range packageToURLMap {
150164
if _, exists := existingURLs[urls.gzURL]; exists {
151165
files = append(files, urls.gzURL)
166+
packagesToDownload[pkg] = true
152167
continue
153168
}
154169
if _, exists := existingURLs[urls.tarURL]; exists {
155170
files = append(files, urls.tarURL)
171+
packagesToDownload[pkg] = true
172+
continue
173+
}
174+
// Package not found in remote cache
175+
results[pkg.FullName()] = cache.DownloadResult{Status: cache.DownloadStatusNotFound}
176+
}
177+
178+
if len(files) > 0 {
179+
transferErr := gsutilTransfer(dest, files)
180+
for pkg := range packagesToDownload {
181+
if transferErr != nil {
182+
results[pkg.FullName()] = cache.DownloadResult{Status: cache.DownloadStatusFailed, Err: transferErr}
183+
} else {
184+
// Verify the file was actually downloaded
185+
if _, exists := dst.Location(pkg); exists {
186+
results[pkg.FullName()] = cache.DownloadResult{Status: cache.DownloadStatusSuccess}
187+
} else {
188+
results[pkg.FullName()] = cache.DownloadResult{Status: cache.DownloadStatusFailed, Err: fmt.Errorf("file not found after transfer")}
189+
}
190+
}
156191
}
157192
}
158193

159-
return gsutilTransfer(dest, files)
194+
return results
160195
}
161196

162197
// Upload makes a best effort to upload the build artifacts to a remote cache
@@ -195,7 +230,7 @@ func (rs *GSUtilCache) UploadFile(ctx context.Context, filePath string, key stri
195230
// HasFile checks if a file exists in the remote cache with the given key
196231
func (rs *GSUtilCache) HasFile(ctx context.Context, key string) (bool, error) {
197232
target := fmt.Sprintf("gs://%s/%s", rs.BucketName, key)
198-
233+
199234
cmd := exec.CommandContext(ctx, "gsutil", "stat", target)
200235
if err := cmd.Run(); err != nil {
201236
// gsutil stat returns non-zero exit code if file doesn't exist
@@ -209,7 +244,7 @@ func (rs *GSUtilCache) HasFile(ctx context.Context, key string) (bool, error) {
209244
}
210245
return false, fmt.Errorf("failed to check if file exists at %s: %w", target, err)
211246
}
212-
247+
213248
return true, nil
214249
}
215250

pkg/leeway/cache/remote/no_cache.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,14 @@ func (NoRemoteCache) ExistingPackages(ctx context.Context, pkgs []cache.Package)
1919
return map[cache.Package]struct{}{}, nil
2020
}
2121

22-
// Download makes a best-effort attempt at downloading previously cached build artifacts
23-
func (NoRemoteCache) Download(ctx context.Context, dst cache.LocalCache, pkgs []cache.Package) error {
24-
return nil
22+
// Download makes a best-effort attempt at downloading previously cached build artifacts.
23+
// NoRemoteCache always returns NotFound for all packages since there is no remote cache.
24+
func (NoRemoteCache) Download(ctx context.Context, dst cache.LocalCache, pkgs []cache.Package) map[string]cache.DownloadResult {
25+
results := make(map[string]cache.DownloadResult)
26+
for _, pkg := range pkgs {
27+
results[pkg.FullName()] = cache.DownloadResult{Status: cache.DownloadStatusNotFound}
28+
}
29+
return results
2530
}
2631

2732
// Upload makes a best effort to upload the build artifacts to a remote cache

pkg/leeway/cache/remote/no_cache_test.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,17 @@ func TestDownload(t *testing.T) {
8686
pkgs = append(pkgs, packages[i])
8787
}
8888

89-
err := noCache.Download(context.Background(), localCache, pkgs)
90-
if err != nil {
91-
t.Errorf("Download() error = %v", err)
89+
results := noCache.Download(context.Background(), localCache, pkgs)
90+
// NoRemoteCache should return NotFound for all packages
91+
for _, pkg := range pkgs {
92+
result, ok := results[pkg.FullName()]
93+
if !ok {
94+
t.Errorf("Download() missing result for package %s", pkg.FullName())
95+
continue
96+
}
97+
if result.Status != cache.DownloadStatusNotFound {
98+
t.Errorf("Download() status = %v, want %v", result.Status, cache.DownloadStatusNotFound)
99+
}
92100
}
93101
}
94102

0 commit comments

Comments
 (0)