55
66from django .utils .encoding import force_bytes , force_text
77
8- from sentry .models .releasefile import ReleaseArchive
9- from sentry .tasks . assemble import RELEASE_ARCHIVE_FILENAME
8+ from sentry .models .releasefile import ARTIFACT_INDEX_FILENAME , ReleaseArchive , read_artifact_index
9+ from sentry .utils import json
1010
1111__all__ = ["JavaScriptStacktraceProcessor" ]
1212
@@ -392,17 +392,58 @@ def get_from_archive(url: str, archive: ReleaseArchive) -> Tuple[bytes, dict]:
392392 raise KeyError (f"Not found in archive: '{ url } '" )
393393
394394
395+ @metrics .wraps ("sourcemaps.load_artifact_index" )
396+ def get_artifact_index (release , dist ):
397+ dist_name = dist and dist .name or None
398+
399+ ident = ReleaseFile .get_ident (ARTIFACT_INDEX_FILENAME , dist_name )
400+ cache_key = f"artifact-index:v1:{ release .id } :{ ident } "
401+ result = cache .get (cache_key )
402+ if result == - 1 :
403+ index = None
404+ elif result :
405+ index = json .loads (result )
406+ else :
407+ index = read_artifact_index (release , dist )
408+ cache_value = - 1 if index is None else json .dumps (index )
409+ # Only cache for a short time to keep the manifest up-to-date
410+ cache .set (cache_key , cache_value , timeout = 60 )
411+
412+ return index
413+
414+
415+ def get_index_entry (release , dist , url ) -> Optional [dict ]:
416+ index = get_artifact_index (release , dist )
417+ if index :
418+ for candidate in ReleaseFile .normalize (url ):
419+ entry = index .get ("files" , {}).get (candidate )
420+ if entry :
421+ return entry
422+
423+ return None
424+
425+
395426@metrics .wraps ("sourcemaps.fetch_release_archive" )
396- def fetch_release_archive (release , dist ) -> Optional [IO ]:
427+ def fetch_release_archive_for_url (release , dist , url ) -> Optional [IO ]:
397428 """Fetch release archive and cache if possible.
398429
430+ Multiple archives might have been uploaded, so we need the URL
431+ to get the correct archive from the artifact index.
432+
399433 If return value is not empty, the caller is responsible for closing the stream.
400434 """
401- dist_name = dist and dist .name or None
402- releasefile_ident = ReleaseFile .get_ident (RELEASE_ARCHIVE_FILENAME , dist_name )
403- cache_key = get_release_file_cache_key (
404- release_id = release .id , releasefile_ident = releasefile_ident
405- )
435+ info = get_index_entry (release , dist , url )
436+ if info is None :
437+ # Cannot write negative cache entry here because ID of release archive
438+ # is not yet known
439+ return None
440+
441+ archive_ident = info ["archive_ident" ]
442+
443+ # TODO(jjbayer): Could already extract filename from info and return
444+ # it later
445+
446+ cache_key = get_release_file_cache_key (release_id = release .id , releasefile_ident = archive_ident )
406447
407448 result = cache .get (cache_key )
408449
@@ -412,11 +453,13 @@ def fetch_release_archive(release, dist) -> Optional[IO]:
412453 return BytesIO (result )
413454 else :
414455 qs = ReleaseFile .objects .filter (
415- release = release , dist = dist , ident = releasefile_ident
456+ release = release , dist = dist , ident = archive_ident
416457 ).select_related ("file" )
417458 try :
418459 releasefile = qs [0 ]
419460 except IndexError :
461+ # This should not happen when there is an archive_ident in the manifest
462+ logger .error ("sourcemaps.missing_archive" , exc_info = sys .exc_info ())
420463 # Cache as nonexistent:
421464 cache .set (cache_key , - 1 , 60 )
422465 return None
@@ -460,11 +503,10 @@ def fetch_release_artifact(url, release, dist):
460503 return result_from_cache (url , result )
461504
462505 start = time .monotonic ()
463-
464- release_file = fetch_release_archive (release , dist )
465- if release_file is not None :
506+ archive_file = fetch_release_archive_for_url (release , dist , url )
507+ if archive_file is not None :
466508 try :
467- archive = ReleaseArchive (release_file )
509+ archive = ReleaseArchive (archive_file )
468510 except BaseException as exc :
469511 logger .error ("Failed to initialize archive for release %s" , release .id , exc_info = exc )
470512 # TODO(jjbayer): cache error and return here
@@ -473,8 +515,10 @@ def fetch_release_artifact(url, release, dist):
473515 try :
474516 fp , headers = get_from_archive (url , archive )
475517 except KeyError :
476- logger .debug (
477- "Release artifact %r not found in archive (release_id=%s)" , url , release .id
518+ # The manifest mapped the url to an archive, but the file
519+ # is not there.
520+ logger .error (
521+ "Release artifact %r not found in archive %s" , url , archive_file .id
478522 )
479523 cache .set (cache_key , - 1 , 60 )
480524 metrics .timing (
0 commit comments