1616import os
1717import platform
1818import re
19+ import time
1920
2021
21- class Instance (object ):
22- """A section of a singularity-compose.yml, typically includes an image
22+ class Instance :
23+ """
24+ A section of a singularity-compose.yml, typically includes an image
2325 name, volumes, build directory, and any ports or environment variables
2426 relevant to the instance.
2527
@@ -85,6 +87,16 @@ def get_replica_name(self):
8587 def uri (self ):
8688 return "instance://%s" % self .get_replica_name ()
8789
90+ @property
91+ def run_background (self ):
92+ """
93+ Determine if the process should be run in the background.
94+ """
95+ run = self .params .get ("run" , {}) or {}
96+ if isinstance (run , list ):
97+ return False
98+ return run .get ("background" ) or False
99+
88100 def set_context (self , params ):
89101 """set and validate parameters from the singularity-compose.yml,
90102 including build (context and recipe). We don't pull or create
@@ -127,12 +139,15 @@ def set_context(self, params):
127139 # Volumes and Ports
128140
129141 def set_volumes (self , params ):
130- """set volumes from the recipe"""
142+ """
143+ Set volumes from the recipe
144+ """
131145 self .volumes = params .get ("volumes" , [])
132146 self ._volumes_from = params .get ("volumes_from" , [])
133147
134148 def set_volumes_from (self , instances ):
135- """volumes from is called after all instances are read in, and
149+ """
150+ Volumes from is called after all instances are read in, and
136151 then volumes can be mapped (and shared) with both containers.
137152 with Docker, this is done with isolation, but for Singularity
138153 we will try sharing a bind on the host.
@@ -149,7 +164,9 @@ def set_volumes_from(self, instances):
149164 self .volumes .append (volume )
150165
151166 def set_network (self , params ):
152- """set network from the recipe to be used"""
167+ """
168+ Set network from the recipe to be used
169+ """
153170 self .network = params .get ("network" , {})
154171
155172 # if not specified, set the default value for the property
@@ -188,13 +205,15 @@ def set_run(self, params):
188205 self .run_opts = self ._get_command_opts (run_group .get ("options" , []))
189206
190207 def _get_command_opts (self , group ):
191- """Given a string of arguments or options, parse into a list with
208+ """
209+ Given a string of arguments or options, parse into a list with
192210 proper flags added.
193211 """
194212 return ["--%s" % opt if len (opt ) > 1 else "-%s" % opt for opt in group ]
195213
196214 def _get_network_commands (self , ip_address = None ):
197- """take a list of ports, return the list of --network-args to
215+ """
216+ Take a list of ports, return the list of --network-args to
198217 ensure they are bound correctly.
199218 """
200219 ports = ["--net" ]
@@ -216,7 +235,9 @@ def _get_network_commands(self, ip_address=None):
216235 return ports
217236
218237 def _get_bind_commands (self ):
219- """take a list of volumes, and return the bind commands for Singularity"""
238+ """
239+ Take a list of volumes, and return the bind commands for Singularity
240+ """
220241 binds = []
221242 for volume in self .volumes :
222243 src , dest = volume .split (":" )
@@ -237,7 +258,8 @@ def _get_bind_commands(self):
237258 return binds
238259
239260 def run_post (self ):
240- """run post create commands. Can be added to an instance definition
261+ """
262+ Run post create commands. Can be added to an instance definition
241263 either to run a command directly, or execute a script. The path
242264 is assumed to be on the host.
243265
@@ -272,7 +294,8 @@ def run_post(self):
272294 # Image
273295
274296 def get_image (self ):
275- """get the associated instance image name, to be built if it doesn't
297+ """
298+ Get the associated instance image name, to be built if it doesn't
276299 exit. It can either be defined at the config from self.image, or
277300 ultimately generated via a pull from a uri.
278301 """
@@ -294,7 +317,8 @@ def get_image(self):
294317 # Build
295318
296319 def build (self , working_dir ):
297- """build an image if called for based on having a recipe and context.
320+ """
321+ Build an image if called for based on having a recipe and context.
298322 Otherwise, pull a container uri to the instance workspace.
299323 """
300324 sif_binary = self .get_image ()
@@ -363,7 +387,8 @@ def build(self, working_dir):
363387 bot .exit ("neither image and build defined for %s" % self .name )
364388
365389 def get_build_options (self ):
366- """'get build options will parse through params, and return build
390+ """
391+ Get build options will parse through params, and return build
367392 options (if they exist)
368393 """
369394 options = []
@@ -390,7 +415,8 @@ def get_build_options(self):
390415
391416 # State
392417 def exists (self ):
393- """return boolean if an instance exists. We do this by way of listing
418+ """
419+ Return boolean if an instance exists. We do this by way of listing
394420 instances, and so the calling user is important.
395421 """
396422 instances = [x .name for x in self .client .instances (quiet = True , sudo = self .sudo )]
@@ -404,7 +430,8 @@ def get(self):
404430 break
405431
406432 def stop (self , timeout = None ):
407- """delete the instance, if it exists. Singularity doesn't have delete
433+ """
434+ Delete the instance, if it exists. Singularity doesn't have delete
408435 or remove commands, everything is a stop.
409436 """
410437 if self .instance :
@@ -415,7 +442,8 @@ def stop(self, timeout=None):
415442 # Networking
416443
417444 def get_address (self ):
418- """get the bridge address of an image. If it's busybox, we can't use
445+ """
446+ Get the bridge address of an image. If it's busybox, we can't use
419447 hostname -I.
420448 """
421449 ip_address = None
@@ -453,7 +481,9 @@ def get_address(self):
453481 # Logs
454482
455483 def clear_logs (self ):
456- """delete logs for an instance, if they exist."""
484+ """
485+ Delete logs for an instance, if they exist.
486+ """
457487 log_folder = self ._get_log_folder ()
458488
459489 for ext in ["out" , "err" ]:
@@ -473,7 +503,9 @@ def clear_logs(self):
473503 pass
474504
475505 def _get_log_folder (self ):
476- """get a log folder that includes a user, home, and host"""
506+ """
507+ Get a log folder that includes a user, home, and host
508+ """
477509 home = get_userhome ()
478510 user = os .path .basename (home )
479511
@@ -486,7 +518,9 @@ def _get_log_folder(self):
486518 return os .path .join (home , ".singularity" , "instances" , "logs" , hostname , user )
487519
488520 def logs (self , tail = 0 ):
489- """show logs for an instance"""
521+ """
522+ Show logs for an instance
523+ """
490524
491525 log_folder = self ._get_log_folder ()
492526
@@ -516,7 +550,8 @@ def logs(self, tail=0):
516550 # Create and Delete
517551
518552 def up (self , working_dir , ip_address = None , writable_tmpfs = False ):
519- """up is the same as create, but like Docker, we build / pull instances
553+ """
554+ Up is the same as create, but like Docker, we build / pull instances
520555 first.
521556 """
522557 image = self .get_image () or ""
@@ -527,7 +562,9 @@ def up(self, working_dir, ip_address=None, writable_tmpfs=False):
527562 self .create (writable_tmpfs = writable_tmpfs , ip_address = ip_address )
528563
529564 def create (self , ip_address = None , sudo = False , writable_tmpfs = False ):
530- """create an instance, if it doesn't exist."""
565+ """
566+ Create an instance, if it doesn't exist.
567+ """
531568 image = self .get_image ()
532569
533570 # Case 1: No build context or image defined
@@ -605,19 +642,25 @@ def create(self, ip_address=None, sudo=False, writable_tmpfs=False):
605642 # If the user has run defined, finish with the run
606643 if "run" in self .params :
607644
645+ run_args = self .run_args or ""
646+
608647 # Show the command to the user
609648 commands = "%s %s %s" % (
610649 " " .join (self .run_opts ),
611650 self .uri ,
612- self . run_args or "" ,
651+ run_args ,
613652 )
614- bot .debug ("singularity run %s" % commands )
615653
616- for line in self .client .run (
617- image = self .instance ,
618- args = self .run_args ,
619- sudo = self .sudo ,
620- stream = True ,
621- options = self .run_opts ,
654+ bot .debug ("singularity run %s" % commands )
655+ for line in (
656+ self .client .run (
657+ image = self .instance ,
658+ args = run_args ,
659+ sudo = self .sudo ,
660+ stream = True ,
661+ options = self .run_opts ,
662+ background = self .run_background ,
663+ )
664+ or []
622665 ):
623- print (line )
666+ print (line . strip ( " \n " ) )
0 commit comments