@@ -326,3 +326,181 @@ async def fix_style_task(event):
326326 await gh .post (
327327 event .data ["issue" ]["comments_url" ], {}, data = {"body" : message }
328328 )
329+
330+
331+ async def copy_pr_mirror (pr_mirror_url , shared_pr_mirror_url ):
332+ """Create an s3 client to copy between the
333+ per-pr mirror and the shared pr mirror.
334+ Also accumulated s3 objects to be deleted from
335+ the PR mirror
336+ """
337+ pr_url = helpers .s3_parse_url (pr_mirror_url )
338+ shared_pr_url = helpers .s3_parse_url (shared_pr_mirror_url )
339+
340+ s3 = boto3 .resource ("s3" )
341+ pr_bucket_name = pr_url .get ("bucket" )
342+ pr_bucket = s3 .Bucket (pr_bucket_name )
343+ pr_mirror_prefix = pr_url .get ("prefix" )
344+
345+ shared_pr_bucket = s3 .Bucket (shared_pr_url .get ("bucket" ))
346+ shared_pr_mirror_prefix = shared_pr_url .get ("prefix" )
347+
348+ # Files extensions to copy
349+ extensions = (".spack" , ".spec.json" , ".spec.yaml" , ".spec.json.sig" )
350+
351+ for obj in pr_bucket .filter (Prefix = pr_mirror_prefix ):
352+ if obj .key .endswith (extensions ):
353+ # Create a new opject replacing the first instance of the pr_mirror_prefix
354+ # with the shared_pr_mirror_prefix.
355+ new_obj = shared_pr_bucket .Object (
356+ obj .key .replace (pr_mirror_prefix , shared_pr_mirror_prefix , 1 )
357+ )
358+ # Copy the PR mirror object to the new object in the shared PR mirror
359+ new_obj .copy (
360+ {
361+ "Bucket" : pr_bucket_name ,
362+ "Key" : obj .key ,
363+ }
364+ )
365+
366+
367+ async def delete_pr_mirror (pr_mirror_url ):
368+ pr_url = helpers .s3_parse_url (pr_mirror_url )
369+
370+ s3 = boto3 .resource ("s3" )
371+ pr_bucket = s3 .Bucket (pr_url .get ("bucket" ))
372+ pr_mirror_prefix = pr_url .get ("prefix" )
373+ pr_bucket .filter (Prefix = pr_mirror_prefix ).delete ()
374+
375+
376+ # Upate index per stack mirror
377+ async def update_mirror_index (mirror_url ):
378+ """Use spack buildcache command to update index on remote mirror"""
379+
380+ # Current job stack
381+ job = get_current_job ()
382+ stack = job .meta ["info" ]["stack" ]
383+
384+ # Check if another reindex for this stack is queued
385+ do_reindex = True
386+ ltask_q = get_queue (job .origin )
387+
388+ for job in ltask_q .jobs :
389+ info = job .meta ["info" ]
390+ if info ["type" ] == "reindex" and info ["stack" ] == stack :
391+ do_reindex = False
392+ break
393+
394+ # Check the queue for more reindex jobs, if there are none,
395+ # run reindex on the graduated PR mirror.
396+ if do_reindex :
397+ print (f"Updating binary index at { mirror_url } " )
398+ await helpers .run_in_subprocess (
399+ [
400+ "spack" ,
401+ "-d" ,
402+ "buildcache" ,
403+ "update-index" ,
404+ "--mirror-url" ,
405+ f"'{ mirror_url } '" ,
406+ ]
407+ )
408+
409+
410+ # This works because we guarentee the hash is in the filename.
411+ # If this assumption is ever broken, this code will break.
412+ def hash_from_key (key ):
413+ h = None
414+ # hash is 32 chars long between a "-" and a "."
415+ # examples include:
416+ # linux-ubuntu18.04-x86_64-gcc-8.4.0-armadillo-10.5.0-gq3ijjrtnzgpm4bvuamjr6wa7hzxkypz.spack
417+ # linux-ubuntu18.04-x86_64-gcc-8.4.0-armadillo-10.5.0-gq3ijjrtnzgpm4bvuamjr6wa7hzxkypz.spec.json
418+ h = re .findall ("-([a-zA-Z0-9]{32,32})\." , key .lower ())
419+ if len (h ) > 1 :
420+ # Error, multiple matches are ambigious
421+ h = None
422+ elif h :
423+ h = h [0 ]
424+ return h
425+
426+
427+ # Prune per stack mirror
428+ async def prune_mirror_duplicates (pr_mirror_url , publish_mirror_url ):
429+ s3 = boto3 .resource ("s3" )
430+
431+ pr_url = helpers .s3_parse_url (pr_mirror_url )
432+ pr_bucket_name = pr_url .get ("bucket" )
433+ pr_bucket = s3 .Bucket (pr_bucket_name )
434+ pr_mirror_prefix = pr_url .get ("prefix" )
435+
436+ publish_url = helpers .s3_parse_url (publish_mirror_url )
437+ publish_bucket = s3 .Bucket (publish_url .get ("bucket" ))
438+ publish_mirror_prefix = publish_url .get ("prefix" )
439+
440+ # All of the expected possible spec file extensions
441+ extensions = (".spec.json" , ".spec.yaml" , ".spec.json.sig" )
442+
443+ # Get the current time for age based pruning
444+ now = datetime .now ()
445+ pr_specs = {}
446+ for obj in pr_bucket .objects .filter (
447+ Prefix = pr_mirror_prefix ,
448+ ):
449+ # Need to convert from aware to naive time to get delta
450+ last_modified = obj .last_modified .replace (tzinfo = None )
451+ # Prune obj.last_modified > 7 days to avoid storing cached objects
452+ # that only existed during development.
453+ if (now - last_modified ).days >= helpers .pr_mirror_retire_after_days :
454+ logger .debug (
455+ f"pr mirror pruning { obj .key } from s3://{ pr_bucket_name } : "
456+ "reason(age)"
457+ )
458+ # Anything older than the retirement age should just be indesciminately
459+ # pruned
460+ obj .delete ()
461+
462+ # Grab the hash from the object, to ensure all of the files associated with
463+ # it are also removed.
464+ spec_hash = hash_from_key (obj .key )
465+ if spec_hash :
466+ pr_specs .add (hash_from_key (obj .key ))
467+ continue
468+
469+ if not obj .key .endswith (extensions ):
470+ continue
471+
472+ # Get the hashes in the shared PR bucket.
473+ spec_hash = hash_from_key (obj .key )
474+ if spec_hash :
475+ pr_specs .add (hash_from_key (obj .key ))
476+ else :
477+ logger .error (f"Encountered spec file without hash in name: { obj .key } " )
478+
479+ # Check in the published base branch bucket for duplicates to delete
480+ delete_specs = {}
481+ for obj in publish_bucket .objects .filter (
482+ Prefix = publish_mirror_prefix ,
483+ ):
484+ if not obj .key .endswith (extensions ):
485+ continue
486+
487+ spec_hash = hash_from_key (obj .key .lower ())
488+ if spec_hash in pr_specs :
489+ delete_specs .add (spec_hash )
490+
491+ # Also look at the .spack files for deletion
492+ extensions = (".spack" , * extensions )
493+
494+ # Delete all of the objects with marked hashes
495+ for obj in pr_bucket .objects .filter (
496+ Prefix = pr_mirror_prefix ,
497+ ):
498+ if not obj .key .endswith (extensions ):
499+ continue
500+
501+ if hash_from_key (obj .key ) in delete_specs :
502+ logger .debug (
503+ f"pr mirror pruning { obj .key } from s3://{ pr_bucket_name } : "
504+ "reason(published)"
505+ )
506+ obj .delete ()
0 commit comments