From 8f36572770660ad17c616c5ccd641c2f68ffd1bd Mon Sep 17 00:00:00 2001 From: yuvipanda Date: Tue, 19 Dec 2017 12:46:22 -0800 Subject: [PATCH] Allow passing in uid and username of user built into image This allows volumes to properly respect the uid of the user from the host. Much easier than using user namespaces --- repo2docker/app.py | 47 ++++++++++++++++++++++++++++++-- repo2docker/buildpacks/base.py | 8 +++--- repo2docker/buildpacks/docker.py | 4 +-- tests/volumes.py | 7 ++++- 4 files changed, 57 insertions(+), 9 deletions(-) diff --git a/repo2docker/app.py b/repo2docker/app.py index a47a9120..0476d997 100644 --- a/repo2docker/app.py +++ b/repo2docker/app.py @@ -19,7 +19,7 @@ import escapism from traitlets.config import Application -from traitlets import Unicode, List, default, Tuple, Dict +from traitlets import Unicode, List, default, Tuple, Dict, Int import docker from docker.utils import kwargs_from_env @@ -117,6 +117,29 @@ class Repo2Docker(Application): config=True ) + user_id = Int( + 1000, + help=""" + UID of the user to create inside the built image. + + Should be a uid that is not currently used by anything in the image. + + Might not affect Dockerfile builds. + """, + config=True + ) + + user_name = Unicode( + 'jovyan', + help=""" + Username of the user to create inside the built image. + + Should be a uid that is not currently used by anything in the image, + and should conform to the restrictions on user names for Linux. + """, + config=True + ) + def fetch(self, url, ref, checkout_path): try: for line in execute_cmd(['git', 'clone', url, checkout_path], @@ -224,6 +247,19 @@ class Repo2Docker(Application): default=[] ) + argparser.add_argument( + '--user-id', + help='User id the primary user in the image', + default=1000, + type=int + ) + + argparser.add_argument( + '--user-name', + help='User name of primary user in the image', + default='jovyan' + ) + return argparser def json_excepthook(self, etype, evalue, traceback): @@ -307,6 +343,9 @@ class Repo2Docker(Application): self.run_cmd = args.cmd + self.user_id = int(args.user_id) + self.user_name = args.user_name + if args.build_memory_limit: self.build_memory_limit = args.build_memory_limit @@ -424,9 +463,13 @@ class Repo2Docker(Application): extra=dict(phase='building')) if self.build: + build_args = { + 'NB_USER': self.user_name, + 'NB_UID': str(self.user_id) + } self.log.info('Using %s builder\n', bp.name, extra=dict(phase='building')) - for l in picked_buildpack.build(self.output_image_spec, self.build_memory_limit): + for l in picked_buildpack.build(self.output_image_spec, self.build_memory_limit, build_args): if 'stream' in l: self.log.info(l['stream'], extra=dict(phase='building')) diff --git a/repo2docker/buildpacks/base.py b/repo2docker/buildpacks/base.py index dd0a53f2..c3a914cd 100644 --- a/repo2docker/buildpacks/base.py +++ b/repo2docker/buildpacks/base.py @@ -30,8 +30,8 @@ ENV LANGUAGE en_US.UTF-8 ENV SHELL /bin/bash # Set up user -ENV NB_USER jovyan -ENV NB_UID 1000 +ARG NB_USER +ARG NB_UID ENV HOME /home/${NB_USER} RUN adduser --disabled-password \ @@ -360,7 +360,7 @@ class BuildPack(LoggingConfigurable): post_build_scripts=self.post_build_scripts, ) - def build(self, image_spec, memory_limit): + def build(self, image_spec, memory_limit, build_args): tarf = io.BytesIO() tar = tarfile.open(fileobj=tarf, mode='w') dockerfile_tarinfo = tarfile.TarInfo("Dockerfile") @@ -406,7 +406,7 @@ class BuildPack(LoggingConfigurable): fileobj=tarf, tag=image_spec, custom_context=True, - buildargs={}, + buildargs=build_args, decode=True, forcerm=True, rm=True, diff --git a/repo2docker/buildpacks/docker.py b/repo2docker/buildpacks/docker.py index 9c4994e2..c5da9c14 100644 --- a/repo2docker/buildpacks/docker.py +++ b/repo2docker/buildpacks/docker.py @@ -18,7 +18,7 @@ class DockerBuildPack(BuildPack): with open(Dockerfile) as f: return f.read() - def build(self, image_spec, memory_limit): + def build(self, image_spec, memory_limit, build_args): limits = { # Always disable memory swap for building, since mostly # nothing good can come of that. @@ -31,7 +31,7 @@ class DockerBuildPack(BuildPack): path=os.getcwd(), dockerfile=self.binder_path(self.dockerfile), tag=image_spec, - buildargs={}, + buildargs=build_args, decode=True, forcerm=True, rm=True, diff --git a/tests/volumes.py b/tests/volumes.py index 2d021a9c..5d283562 100644 --- a/tests/volumes.py +++ b/tests/volumes.py @@ -12,9 +12,12 @@ def test_volume_abspath(): """ ts = str(time.time()) with tempfile.TemporaryDirectory() as tmpdir: + username = os.getlogin() subprocess.check_call([ 'repo2docker', - '-v', '{}:/home/jovyan'.format(tmpdir), + '-v', '{}:/home/{}'.format(tmpdir, username), + '--user-id', str(os.geteuid()), + '--user-name', username, tmpdir, '--', '/bin/bash', @@ -37,6 +40,8 @@ def test_volume_relpath(): subprocess.check_call([ 'repo2docker', '-v', '.:.', + '--user-id', str(os.geteuid()), + '--user-name', os.getlogin(), tmpdir, '--', '/bin/bash',