@@ -97,8 +97,8 @@ func WithDecompress(decompress bool) Opt {
97
97
// - The digest was not specified.
98
98
// - The file already exists in the local target path.
99
99
//
100
- // When the `data` file exists in the cache dir with `digest. <ALGO>` file,
101
- // the digest is verified by comparing the content of `digest. <ALGO>` with the expected
100
+ // When the `data` file exists in the cache dir with `<ALGO>.digest ` file,
101
+ // the digest is verified by comparing the content of `<ALGO>.digest ` with the expected
102
102
// digest string. So, the actual digest of the `data` file is not computed.
103
103
func WithExpectedDigest (expectedDigest digest.Digest ) Opt {
104
104
return func (o * options ) error {
@@ -183,24 +183,19 @@ func Download(local, remote string, opts ...Opt) (*Result, error) {
183
183
return res , nil
184
184
}
185
185
186
- shad := filepath . Join (o .cacheDir , "download" , "by-url-sha256" , fmt . Sprintf ( "%x" , sha256 . Sum256 ([] byte ( remote ))) )
186
+ shad := cacheDirectoryPath (o .cacheDir , remote )
187
187
shadData := filepath .Join (shad , "data" )
188
- shadDigest := ""
189
- if o .expectedDigest != "" {
190
- algo := o .expectedDigest .Algorithm ().String ()
191
- if strings .Contains (algo , "/" ) || strings .Contains (algo , "\\ " ) {
192
- return nil , fmt .Errorf ("invalid digest algorithm %q" , algo )
193
- }
194
- shadDigest = filepath .Join (shad , algo + ".digest" )
188
+ shadDigest , err := cacheDigestPath (shad , o .expectedDigest )
189
+ if err != nil {
190
+ return nil , err
195
191
}
196
192
if _ , err := os .Stat (shadData ); err == nil {
197
193
logrus .Debugf ("file %q is cached as %q" , localPath , shadData )
198
- if shadDigestB , err := os .ReadFile (shadDigest ); err == nil {
194
+ if _ , err := os .Stat (shadDigest ); err == nil {
199
195
logrus .Debugf ("Comparing digest %q with the cached digest file %q, not computing the actual digest of %q" ,
200
196
o .expectedDigest , shadDigest , shadData )
201
- shadDigestS := strings .TrimSpace (string (shadDigestB ))
202
- if o .expectedDigest .String () != shadDigestS {
203
- return nil , fmt .Errorf ("expected digest %q does not match the cached digest %q" , o .expectedDigest .String (), shadDigestS )
197
+ if err := validateCachedDigest (shadDigest , o .expectedDigest ); err != nil {
198
+ return nil , err
204
199
}
205
200
if err := copyLocal (localPath , shadData , ext , o .decompress , "" , "" ); err != nil {
206
201
return nil , err
@@ -247,6 +242,73 @@ func Download(local, remote string, opts ...Opt) (*Result, error) {
247
242
return res , nil
248
243
}
249
244
245
+ // Cached checks if the remote resource is in the cache.
246
+ //
247
+ // Download caches the remote resource if WithCache or WithCacheDir option is specified.
248
+ // Local files are not cached.
249
+ //
250
+ // When the cache path already exists, Cached returns Result with StatusUsedCache.
251
+ func Cached (remote string , opts ... Opt ) (* Result , error ) {
252
+ var o options
253
+ for _ , f := range opts {
254
+ if err := f (& o ); err != nil {
255
+ return nil , err
256
+ }
257
+ }
258
+ if o .cacheDir == "" {
259
+ return nil , fmt .Errorf ("caching-only mode requires the cache directory to be specified" )
260
+ }
261
+ if IsLocal (remote ) {
262
+ return nil , fmt .Errorf ("local files are not cached" )
263
+ }
264
+
265
+ shad := cacheDirectoryPath (o .cacheDir , remote )
266
+ shadData := filepath .Join (shad , "data" )
267
+ shadDigest , err := cacheDigestPath (shad , o .expectedDigest )
268
+ if err != nil {
269
+ return nil , err
270
+ }
271
+ if _ , err := os .Stat (shadData ); err != nil {
272
+ return nil , err
273
+ }
274
+ if _ , err := os .Stat (shadDigest ); err != nil {
275
+ if err := validateCachedDigest (shadDigest , o .expectedDigest ); err != nil {
276
+ return nil , err
277
+ }
278
+ } else {
279
+ if err := validateLocalFileDigest (shadData , o .expectedDigest ); err != nil {
280
+ return nil , err
281
+ }
282
+ }
283
+ res := & Result {
284
+ Status : StatusUsedCache ,
285
+ CachePath : shadData ,
286
+ ValidatedDigest : o .expectedDigest != "" ,
287
+ }
288
+ return res , nil
289
+ }
290
+
291
+ // cacheDirectoryPath returns the cache subdirectory path.
292
+ // - "url" file contains the url
293
+ // - "data" file contains the data
294
+ func cacheDirectoryPath (cacheDir string , remote string ) string {
295
+ return filepath .Join (cacheDir , "download" , "by-url-sha256" , fmt .Sprintf ("%x" , sha256 .Sum256 ([]byte (remote ))))
296
+ }
297
+
298
+ // cacheDigestPath returns the cache digest file path.
299
+ // - "<ALGO>.digest" contains the digest
300
+ func cacheDigestPath (shad string , expectedDigest digest.Digest ) (string , error ) {
301
+ shadDigest := ""
302
+ if expectedDigest != "" {
303
+ algo := expectedDigest .Algorithm ().String ()
304
+ if strings .Contains (algo , "/" ) || strings .Contains (algo , "\\ " ) {
305
+ return "" , fmt .Errorf ("invalid digest algorithm %q" , algo )
306
+ }
307
+ shadDigest = filepath .Join (shad , algo + ".digest" )
308
+ }
309
+ return shadDigest , nil
310
+ }
311
+
250
312
func IsLocal (s string ) bool {
251
313
return ! strings .Contains (s , "://" ) || strings .HasPrefix (s , "file://" )
252
314
}
@@ -278,6 +340,9 @@ func copyLocal(dst, src, ext string, decompress bool, description string, expect
278
340
return err
279
341
}
280
342
343
+ if expectedDigest != "" {
344
+ logrus .Debugf ("verifying digest of local file %q (%s)" , srcPath , expectedDigest )
345
+ }
281
346
if err := validateLocalFileDigest (srcPath , expectedDigest ); err != nil {
282
347
return err
283
348
}
@@ -366,14 +431,28 @@ func decompressLocal(dst, src, ext string, description string) error {
366
431
return err
367
432
}
368
433
434
+ func validateCachedDigest (shadDigest string , expectedDigest digest.Digest ) error {
435
+ if expectedDigest == "" {
436
+ return nil
437
+ }
438
+ shadDigestB , err := os .ReadFile (shadDigest )
439
+ if err != nil {
440
+ return err
441
+ }
442
+ shadDigestS := strings .TrimSpace (string (shadDigestB ))
443
+ if shadDigestS != expectedDigest .String () {
444
+ return fmt .Errorf ("expected digest %q, got %q" , expectedDigest , shadDigestS )
445
+ }
446
+ return nil
447
+ }
448
+
369
449
func validateLocalFileDigest (localPath string , expectedDigest digest.Digest ) error {
370
450
if localPath == "" {
371
451
return fmt .Errorf ("validateLocalFileDigest: got empty localPath" )
372
452
}
373
453
if expectedDigest == "" {
374
454
return nil
375
455
}
376
- logrus .Debugf ("verifying digest of local file %q (%s)" , localPath , expectedDigest )
377
456
algo := expectedDigest .Algorithm ()
378
457
if ! algo .Available () {
379
458
return fmt .Errorf ("expected digest algorithm %q is not available" , algo )
0 commit comments