Skip to content

Commit d5341de

Browse files
authored
Merge pull request #52 from manics/fix-arg-env-handling
Fix arg env handling
2 parents b716ab4 + 1b2d91f commit d5341de

File tree

6 files changed

+41
-40
lines changed

6 files changed

+41
-40
lines changed

repo2shellscript/shellscript.py

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@
2525

2626

2727
def _expand_env(s, *args):
28-
# repo2docker uses PATH when expanding PATH
29-
env = {"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}
28+
env = {}
3029
for e in reversed(args):
3130
env.update(e)
3231
return Template(s).substitute(env)
@@ -78,7 +77,7 @@ def _docker_copy(copy, chown):
7877
return statement
7978

8079

81-
def dockerfile_to_bash(dockerfile, buildargs):
80+
def dockerfile_to_bash(dockerfile, buildargs, parentenv):
8281
"""
8382
Convert a Dockerfile to a bash script
8483
@@ -94,16 +93,24 @@ def dockerfile_to_bash(dockerfile, buildargs):
9493
bash = []
9594
cmd = ""
9695
entrypoint = ""
96+
9797
# Needed so we know which directory to run the start command from
9898
currentdir = ""
99-
# Runtime environment
100-
runtimeenv = {}
101-
# Build and runtime environment
102-
currentenv = {}
103-
parser = DockerfileParser(dockerfile)
99+
100+
# The final runtime environment (expanded ENV only)
101+
runtimeenv = parentenv.copy()
102+
103+
# Combined build and runtime environments (since during the build we need
104+
# to take ARG and ENV into account)
105+
currentenv = parentenv.copy()
106+
107+
parser = DockerfileParser(
108+
dockerfile, env_replace=True, build_args=buildargs, parent_env=parentenv
109+
)
104110
user = "root"
105111

106-
for d in parser.structure:
112+
assert len(parser.structure) == len(parser.context_structure)
113+
for (d, ctx) in zip(parser.structure, parser.context_structure):
107114
statement = ""
108115
instruction = d["instruction"]
109116
for line in d["content"].splitlines():
@@ -120,18 +127,12 @@ def dockerfile_to_bash(dockerfile, buildargs):
120127
raise NotImplementedError(f"Base image {d['value']} not supported")
121128
statement += base_setup
122129
elif instruction == "ARG":
123-
argname = d["value"].split("=", 1)[0]
124-
try:
125-
argvalue = shlex.quote(buildargs[d["value"]])
126-
except KeyError:
127-
if "=" in d["value"]:
128-
argvalue = d["value"].split("=", 1)[1]
129-
else:
130-
raise
131-
# Expand because this may eventually end up as a runtime env
132-
argvalue = _expand_env(argvalue, currentenv)
133-
currentenv[argname] = argvalue
134-
statement += f"export {argname}={argvalue}\n"
130+
for argname, argvalue in ctx.line_args.items():
131+
# Expand because this may eventually end up as a runtime env
132+
argvalue = _expand_env(argvalue, currentenv)
133+
currentenv[argname] = argvalue
134+
escaped_argvalue = shlex.quote(argvalue)
135+
statement += f"export {argname}={escaped_argvalue}\n"
135136
elif instruction == "CMD":
136137
cmd = " ".join(shlex.quote(p) for p in json.loads(d["value"]))
137138
elif instruction == "COPY":
@@ -143,20 +144,17 @@ def dockerfile_to_bash(dockerfile, buildargs):
143144
elif instruction == "ENTRYPOINT":
144145
entrypoint = " ".join(shlex.quote(p) for p in json.loads(d["value"]))
145146
elif instruction == "ENV":
146-
# repodocker is inconsistent in how it uses ENV
147-
try:
148-
k, v = d["value"].split("=", 1)
149-
except ValueError:
150-
k, v = d["value"].split(" ", 1)
151-
argvalue = _expand_env(v, currentenv)
152-
currentenv[k] = argvalue
153-
statement += f"export {k}={argvalue}\n"
154-
runtimeenv[k] = argvalue
147+
for envname, envvalue in ctx.line_envs.items():
148+
envvalue = _expand_env(envvalue, currentenv)
149+
currentenv[envname] = envvalue
150+
runtimeenv[envname] = envvalue
151+
escaped_envvalue = shlex.quote(envvalue)
152+
statement += f"export {envname}={escaped_envvalue}\n"
155153
elif instruction == "RUN":
156154
run = _sudo_user(user, d["value"], currentenv)
157155
statement += f"{run}\n"
158156
elif instruction == "USER":
159-
user = d["value"]
157+
user = _expand_env(d["value"], currentenv)
160158
elif instruction == "WORKDIR":
161159
statement += f"cd {d['value']}\n"
162160
currentdir = _expand_env(d["value"], currentenv)
@@ -175,8 +173,7 @@ def dockerfile_to_bash(dockerfile, buildargs):
175173
"dir": currentdir,
176174
"env": runtimeenv,
177175
"start": f"{entrypoint} {cmd}",
178-
# Expand ${NB_USER}
179-
"user": _expand_env(user, currentenv),
176+
"user": user,
180177
}
181178
return r
182179

@@ -233,6 +230,10 @@ def build(
233230
):
234231

235232
buildargs = buildargs or {}
233+
# repo2docker uses PATH when expanding PATH
234+
parentenv = {
235+
"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
236+
}
236237

237238
if not tag:
238239
tag = str(uuid4())
@@ -260,7 +261,7 @@ def build(
260261
else:
261262
dockerfile = os.path.join(builddir)
262263

263-
r = dockerfile_to_bash(dockerfile, buildargs)
264+
r = dockerfile_to_bash(dockerfile, buildargs, parentenv)
264265
build_file = os.path.join(builddir, "repo2shellscript-build.bash")
265266
start_file = os.path.join(builddir, "repo2shellscript-start.bash")
266267
systemd_file = os.path.join(builddir, "repo2shellscript.service")

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
name="repo2shellscript",
55
# https://github.com/jupyter/repo2docker/pull/848 was merged!
66
install_requires=[
7-
"dockerfile-parse",
7+
"dockerfile-parse>=2,<3",
88
"jupyter-repo2docker>=2022.02.0",
99
"importlib_resources;python_version<'3.7'",
1010
],

tests/reference-outputs/test/repo2shellscript-build.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ fi
307307
# time ${MAMBA_EXE} clean --all -f -y && \
308308
# ${MAMBA_EXE} list -p ${NB_PYTHON_PREFIX} \
309309
# '
310-
sudo -u ${NB_USER} --preserve-env=DEBIAN_FRONTEND,LC_ALL,LANG,LANGUAGE,SHELL,NB_USER,NB_UID,USER,HOME,APP_BASE,CONDA_DIR,NB_PYTHON_PREFIX,NPM_DIR,NPM_CONFIG_GLOBALCONFIG,NB_ENVIRONMENT_FILE,MAMBA_ROOT_PREFIX,MAMBA_EXE,KERNEL_PYTHON_PREFIX,PATH,REPO_DIR,CONDA_DEFAULT_ENV bash -c 'TIMEFORMAT='"'"'time: %3R'"'"' bash -c '"'"'time ${MAMBA_EXE} env update -p ${NB_PYTHON_PREFIX} --file "environment.yml" && time ${MAMBA_EXE} clean --all -f -y && ${MAMBA_EXE} list -p ${NB_PYTHON_PREFIX} '"'"''
310+
sudo -u test --preserve-env=PATH,DEBIAN_FRONTEND,LC_ALL,LANG,LANGUAGE,SHELL,NB_USER,NB_UID,USER,HOME,APP_BASE,CONDA_DIR,NB_PYTHON_PREFIX,NPM_DIR,NPM_CONFIG_GLOBALCONFIG,NB_ENVIRONMENT_FILE,MAMBA_ROOT_PREFIX,MAMBA_EXE,KERNEL_PYTHON_PREFIX,REPO_DIR,CONDA_DEFAULT_ENV bash -c 'TIMEFORMAT='"'"'time: %3R'"'"' bash -c '"'"'time ${MAMBA_EXE} env update -p ${NB_PYTHON_PREFIX} --file "environment.yml" && time ${MAMBA_EXE} clean --all -f -y && ${MAMBA_EXE} list -p ${NB_PYTHON_PREFIX} '"'"''
311311

312312
# ensure root user after preassemble scripts
313313

tests/reference-outputs/test/repo2shellscript-start.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ if [ $(id -un) != test ]; then
44
echo ERROR: Must be run as user test
55
exit 1
66
fi
7+
export PATH=/home/test/.local/bin:/home/test/.local/bin:/srv/conda/envs/notebook/bin:/srv/conda/bin:/srv/npm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
78
export DEBIAN_FRONTEND=noninteractive
89
export LC_ALL=en_US.UTF-8
910
export LANG=en_US.UTF-8
@@ -20,7 +21,6 @@ export NB_ENVIRONMENT_FILE=/tmp/env/environment.lock
2021
export MAMBA_ROOT_PREFIX=/srv/conda
2122
export MAMBA_EXE=/srv/conda/bin/mamba
2223
export KERNEL_PYTHON_PREFIX=/srv/conda/envs/notebook
23-
export PATH=/home/test/.local/bin:/home/test/.local/bin:/srv/conda/envs/notebook/bin:/srv/conda/bin:/srv/npm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
2424
export REPO_DIR=/home/test
2525
export CONDA_DEFAULT_ENV=/srv/conda/envs/notebook
2626
export PYTHONUNBUFFERED=1

tests/reference-outputs/test/repo2shellscript.service

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Description=repo2shellscript
55
User=test
66
Restart=always
77
RestartSec=10
8+
Environment='PATH=/home/test/.local/bin:/home/test/.local/bin:/srv/conda/envs/notebook/bin:/srv/conda/bin:/srv/npm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
89
Environment='DEBIAN_FRONTEND=noninteractive'
910
Environment='LC_ALL=en_US.UTF-8'
1011
Environment='LANG=en_US.UTF-8'
@@ -21,7 +22,6 @@ Environment='NB_ENVIRONMENT_FILE=/tmp/env/environment.lock'
2122
Environment='MAMBA_ROOT_PREFIX=/srv/conda'
2223
Environment='MAMBA_EXE=/srv/conda/bin/mamba'
2324
Environment='KERNEL_PYTHON_PREFIX=/srv/conda/envs/notebook'
24-
Environment='PATH=/home/test/.local/bin:/home/test/.local/bin:/srv/conda/envs/notebook/bin:/srv/conda/bin:/srv/npm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
2525
Environment='REPO_DIR=/home/test'
2626
Environment='CONDA_DEFAULT_ENV=/srv/conda/envs/notebook'
2727
Environment='PYTHONUNBUFFERED=1'

tests/test_repo2shellscript.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def _recursive_filelist(d):
2323
def _normalise_build_script(lines):
2424
for line in lines:
2525
line = re.sub(
26-
r"build_script_files/\S+-2fsite-2dpackages-2f(\S+)-\w+(\s+|/)",
26+
r"build_script_files/\S+-2f(repo2docker-2fbuildpacks-2f\S+)-\w+(\s+|/)",
2727
r"<normalised>\1 ",
2828
line,
2929
)
@@ -51,7 +51,7 @@ def test_compare(tmp_path):
5151
# Normalise filenames under build_script_files
5252
for build_script in (tmp_path / "test" / "build_script_files").iterdir():
5353
normalised_name = re.match(
54-
r"^.+-2fsite-2dpackages-2f(.+)-\w+$", build_script.name
54+
r"^.+-2f(repo2docker-2fbuildpacks-2f.+)-\w+$", build_script.name
5555
).group(1)
5656
build_script.rename(build_script.parent / normalised_name)
5757

0 commit comments

Comments
 (0)