From e8dbc6a6fc9fae190c650c196824fbb94caa4f18 Mon Sep 17 00:00:00 2001 From: Rodrigo Tobar Date: Mon, 19 Apr 2021 14:28:33 +0800 Subject: [PATCH] Add workingDir parameter to DockerApp Docker containers run by default under /. Additionally, when we ensure and create a new user in the container, we run under /home/r ("r" is the new user). Both of these options make it impossible to retrieve anything from the working directory after execution finishes though, which is why we need a new parameter that lets users specify under which directory the container command will run (and thus they can also map it from the host system). This commit adds support for a new workingDir parameter in DockerApps. Its value defaults to "/", and is only taken into account when the image from which the container is started doesn't already contain a value for its WorkingDir specification. We take care that this preference is obeyed both when we internally create a new user, as well as when we don't A new unit test checks that this is working as expected. This work is part of LIU-53. Signed-off-by: Rodrigo Tobar --- daliuge-engine/dlg/apps/dockerapp.py | 16 ++++++++++++++-- daliuge-engine/test/apps/test_docker.py | 12 ++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/daliuge-engine/dlg/apps/dockerapp.py b/daliuge-engine/dlg/apps/dockerapp.py index 04955b76d..791862e5a 100644 --- a/daliuge-engine/dlg/apps/dockerapp.py +++ b/daliuge-engine/dlg/apps/dockerapp.py @@ -82,7 +82,9 @@ class DockerApp(BarrierAppDROP): `initialize` time, meaning that the docker images will become available at the time the physical graph (which this application is part of) is deployed. Docker containers also need a command to be run in them, which should be - an available program inside the image. + an available program inside the image. Optionally, users can provide a + working directory (in the container) under which the command will run + via the `workingDir` parameter. **Input and output** @@ -247,6 +249,15 @@ def initialize(self, **kwargs): logger.debug("Took %.2f [s] to pull image '%s'", (end-start), self._image) else: logger.debug("Image '%s' found, no need to pull it", self._image) + + # Check if the image specifies a working directory + # If it doesn't use the one provided by the user + inspection = c.api.inspect_image(self._image) + logger.debug("Docker Image inspection: %r", inspection) + self.workdir = inspection.get('ContainerConfig', {}).get('WorkingDir', None) + if not self.workdir: + self.workdir = self._getArg(kwargs, 'workingDir', '/') + c.api.close() self._containerIp = None @@ -327,7 +338,7 @@ def run(self): 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.values()]): 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)) + createUserAndGo += "su -l $(getent passwd {0} | cut -f1 -d:) -c /bin/bash -c 'cd {1}; {2}'".format(uid, self.workdir, utils.escapeQuotes(cmd, doubleQuotes=False)) cmd = createUserAndGo @@ -351,6 +362,7 @@ def rm(container): volumes=binds, user=user, environment=env, + working_dir=self.workdir ) self._containerId = cId = container.id logger.info("Created container %s for %r", cId, self) diff --git a/daliuge-engine/test/apps/test_docker.py b/daliuge-engine/test/apps/test_docker.py index 514195a56..5786da44c 100644 --- a/daliuge-engine/test/apps/test_docker.py +++ b/daliuge-engine/test/apps/test_docker.py @@ -201,3 +201,15 @@ def test_additional_bindings(self): # Cleanup os.unlink(tempFile) shutil.rmtree(tempDir) + + def _test_working_dir(self, ensureUserAndSwitch): + a = DockerApp('a', 'a', workingDir='/mydir', image='ubuntu:14.04', command='pwd > %o0', ensureUserAndSwitch=ensureUserAndSwitch) + b = FileDROP('b', 'b') + a.addOutput(b) + with DROPWaiterCtx(self, b, 100): + a.execute() + self.assertEqual(b'/mydir', droputils.allDropContents(b).strip()) + + def test_working_dir(self): + self._test_working_dir(True) + self._test_working_dir(False) \ No newline at end of file