def run(self): # Replace any placeholder in the commandline with the proper path or # dataURL, depending on the type of input/output it is # In the case of fs-based i/o we replace the command-line with the path # that the Drop will receive *inside* the docker container (see below) def isFSBased(x): return isinstance(x, (FileDROP, DirectoryContainer)) fsInputs = [i for i in self.inputs if isFSBased(i)] fsOutputs = [o for o in self.outputs if isFSBased(o)] dockerInputs = [DockerPath(i.uid, DFMS_ROOT + i.path) for i in fsInputs] dockerOutputs = [DockerPath(o.uid, DFMS_ROOT + o.path) for o in fsOutputs] dataURLInputs = [i for i in self.inputs if not isFSBased(i)] dataURLOutputs = [o for o in self.outputs if not isFSBased(o)] cmd = droputils.replace_path_placeholders(self._command, dockerInputs, dockerOutputs) cmd = droputils.replace_dataurl_placeholders(cmd, dataURLInputs, dataURLOutputs) # We bind the inputs and outputs inside the docker under the DFMS_ROOT # directory, maintaining the rest of their original paths. # Outputs are bound only up to their dirname (see class doc for details) # Volume bindings are setup for FileDROPs and DirectoryContainers only vols = [x.path for x in dockerInputs] + [os.path.dirname(x.path) for x in dockerOutputs] binds = [i.path + ":" + dockerInputs[x].path for x, i in enumerate(fsInputs)] binds += [ os.path.dirname(o.path) + ":" + os.path.dirname(dockerOutputs[x].path) for x, o in enumerate(fsOutputs) ] binds += [host_path + ":" + container_path for host_path, container_path in self._additionalBindings.items()] if logger.isEnabledFor(logging.DEBUG): logger.debug("Volume bindings: %r" % (binds)) # Wait until the DockerApps this application runtime depends on have # started, and replace their IP placeholders by the real IPs for waiter in self._waiters: uid, ip = waiter.waitForIp() cmd = cmd.replace("%containerIp[{0}]%".format(uid), ip) if logger.isEnabledFor(logging.DEBUG): logger.debug("Command after IP replacement is: %s" % (cmd)) # If a user has been given, we run the container as that user. It is # useful to make sure that the USER environment variable is set in those # cases (e.g., casapy requires this to correctly operate) user = self._user env = {} if user is not None: env = {"USER": user} if self._ensureUserAndSwitch is True: # Append commands that will make sure a user is present with the # same UID of the current user, and that the command that was # supplied for this container runs as that user. # Also make sure that the output will belong to that user uid = os.getuid() createUserAndGo = "id -u {0} &> /dev/null || adduser --uid {0} r; ".format(uid) for dirname in set([os.path.dirname(x.path) for x in dockerOutputs]): createUserAndGo += 'chown -R {0}.{0} "{1}"; '.format(uid, dirname) createUserAndGo += "cd; su -l $(getent passwd {0} | cut -f1 -d:) -c /bin/bash -c '{1}'".format( uid, utils.escapeQuotes(cmd, doubleQuotes=False) ) cmd = createUserAndGo # Wrap everything inside bash cmd = '/bin/bash -c "%s"' % (utils.escapeQuotes(cmd, singleQuotes=False)) if logger.isEnabledFor(logging.DEBUG): logger.debug("Command after user creation and wrapping is: %s" % (cmd)) extra_kwargs = self._kwargs_from_env() c = AutoVersionClient(**extra_kwargs) # Remove the container unless it's specified that we should keep it # (used below) def rm(container): if self._removeContainer: c.remove_container(container) # Create container host_config = c.create_host_config(binds=binds) container = c.create_container( self._image, cmd, volumes=vols, host_config=host_config, user=user, environment=env ) self._containerId = cId = container["Id"] if logger.isEnabledFor(logging.INFO): logger.info("Created container %s for %r" % (cId, self)) # Start it start = time.time() c.start(container) if logger.isEnabledFor(logging.INFO): logger.info("Started container %s" % (cId)) # Figure out the container's IP and save it # Setting self.containerIp will trigger an event being sent to the # registered listeners inspection = c.inspect_container(container) self.containerIp = inspection["NetworkSettings"]["IPAddress"] # Wait until it finishes self._exitCode = c.wait(container) end = time.time() if logger.isEnabledFor(logging.INFO): logger.info("Container %s finished in %.2f [s] with exit code %d" % (cId, (end - start), self._exitCode)) if self._exitCode == 0 and logger.isEnabledFor(logging.DEBUG): msg = "Container %s finished successfully" % (cId,) stdout = c.logs(container, stdout=True, stderr=False) stderr = c.logs(container, stdout=False, stderr=True) logger.debug(msg + ", output follows.\n==STDOUT==\n%s==STDERR==\n%s" % (stdout, stderr)) elif self._exitCode != 0: stdout = c.logs(container, stdout=True, stderr=False) stderr = c.logs(container, stdout=False, stderr=True) msg = "Container %s didn't finish successfully (exit code %d)" % (cId, self._exitCode) logger.error(msg + ", output follows.\n==STDOUT==\n%s==STDERR==\n%s" % (stdout, stderr)) rm(container) raise Exception(msg) rm(container)