@@ -39,6 +39,7 @@ import urllib.request, urllib.error, urllib.parse
3939import hashlib
4040import traceback
4141import subprocess
42+ import shutil
4243import dockerhub
4344import cleanup
4445import sqlitedict
@@ -109,6 +110,14 @@ def main():
109110 help = "Indicate that this is a dry-run" ,
110111 default = False )
111112
113+ # Alternative build root for faster singularity build
114+ parser .add_argument ("--build-dir" ,
115+ dest = 'build_dir' ,
116+ help = "Alternative directory for singularity build (e.g. on a faster filesystem). "
117+ "If set, images are built here first, then moved to the final image directory." ,
118+ type = str ,
119+ default = None )
120+
112121 try :
113122 args = parser .parse_args ()
114123 except :
@@ -141,7 +150,7 @@ def main():
141150 if args .docker :
142151 image = args .docker
143152 if not args .dryrun :
144- return publish_image (image , singularity_rootfs , args .registry , doauth , manifest_cache )
153+ return publish_image (image , singularity_rootfs , args .registry , doauth , manifest_cache , build_dir = args . build_dir )
145154 else :
146155 return verify_image (image , args .registry , doauth , manifest_cache )
147156 else :
@@ -190,7 +199,7 @@ def main():
190199 for i in range (tries ):
191200 if not args .dryrun :
192201 try :
193- retval = publish_image (image , singularity_rootfs , registry , doauth , manifest_cache )
202+ retval = publish_image (image , singularity_rootfs , registry , doauth , manifest_cache , build_dir = args . build_dir )
194203 except Exception as ex :
195204 if i < tries - 1 :
196205 print ("Failed to publish image: {}" .format (image ))
@@ -367,7 +376,7 @@ def get_manifest(hub, namespace, repo_name, repo_tag, manifest_cache):
367376 manifest_cache [digest ] = manifest
368377 return manifest , digest
369378
370- def publish_image (image , singularity_rootfs , registry , doauth , manifest_cache ):
379+ def publish_image (image , singularity_rootfs , registry , doauth , manifest_cache , build_dir = None ):
371380
372381 # Tell the user the namespace, repo name and tag
373382 registry , namespace , repo_name , repo_tag = parse_image (image )
@@ -442,24 +451,54 @@ def publish_image(image, singularity_rootfs, registry, doauth, manifest_cache):
442451 if 'password' in auth :
443452 singularity_env ['SINGULARITY_DOCKER_PASSWORD' ] = auth ['password' ]
444453
454+ # Determine the actual build target directory.
455+ # If --build-dir is specified, build the sandbox on the (potentially faster)
456+ # alternative filesystem, then move the result into the final image_dir.
457+ if build_dir :
458+ build_dir = os .path .abspath (build_dir )
459+ # Mirror the image_parentdir/image_dir structure under build_dir so
460+ # concurrent builds for different images don't collide.
461+ build_parentdir = os .path .join (build_dir , image_hash [0 :sep + 3 ])
462+ build_target = os .path .join (build_parentdir , image_hash [sep + 3 :])
463+ try :
464+ os .makedirs (build_parentdir )
465+ except OSError as oe :
466+ if oe .errno != errno .EEXIST :
467+ raise
468+ else :
469+ build_target = image_dir
470+
445471 print ("Calling singularity to build sandbox from image" )
446- subprocess .check_call (
447- ['singularity' , '--silent' , 'build' ,
448- '--disable-cache=true' , # Images are only downloaded once
449- '--force' , # Don't get stuck at a prompt if the target somehow exists
450- '--fix-perms' ,
451- '--sandbox' ,
452- image_dir , 'docker://' + image ],
453- env = singularity_env )
472+ try :
473+ subprocess .check_call (
474+ ['singularity' , '--silent' , 'build' ,
475+ '--disable-cache=true' , # Images are only downloaded once
476+ '--force' , # Don't get stuck at a prompt if the target somehow exists
477+ '--fix-perms' ,
478+ '--sandbox' ,
479+ build_target , 'docker://' + image ],
480+ env = singularity_env )
481+ except BaseException :
482+ # Clean up partial build artifacts in the alternative build root
483+ if build_dir and os .path .exists (build_target ):
484+ print ("Cleaning up partial build at %s" % build_target )
485+ shutil .rmtree (build_target , ignore_errors = True )
486+ raise
454487
455488 # Various fixups to make the image compatible with CVMFS and singularity.
456- srv = os .path .join (image_dir , "srv" )
457- cvmfs = os .path .join (image_dir , "cvmfs" )
489+ srv = os .path .join (build_target , "srv" )
490+ cvmfs = os .path .join (build_target , "cvmfs" )
458491 if not os .path .exists (srv ):
459492 os .makedirs (srv )
460493 if not os .path .exists (cvmfs ):
461494 os .makedirs (cvmfs )
462495
496+ # If we built in an alternative build root, move the result to the final
497+ # image directory inside CVMFS.
498+ if build_dir :
499+ print ("Moving image from build dir %s to %s" % (build_target , image_dir ))
500+ shutil .move (build_target , image_dir )
501+
463502 make_final_symlink (image_dir , singularity_rootfs , namespace , repo_name , repo_tag )
464503 # Publish CVMFS as necessary.
465504 return publish_txn ()
0 commit comments