-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FAST_BUILD] Migrate runhooks
to python
#2007
Closed
Closed
Changes from 34 commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
d1ff010
Migrate start-notebook.sh to bash
yuvipanda ed70463
Rename start-notebook.sh to start-notebook
yuvipanda 00e751e
Cleanup start-notebook a little
yuvipanda ea06768
Fix typo
yuvipanda 14a1e8c
Migrate start-singleuser as well
yuvipanda 40f37d7
Remove unused import
yuvipanda 3364294
Run symlink commands as root
yuvipanda bb98d5e
Combine repetitive RUN commands
yuvipanda 9e71839
Remove multiple args to env
yuvipanda 67302e3
Fix conditional inversion
yuvipanda 1804a8c
Fix how start-singleuser is exec'd
yuvipanda 527b756
Actually call jupyterhub-singleuser in start-singleuser
yuvipanda 9f7bdb3
Pass through any additional args we get
yuvipanda 37d5b15
Put .py suffix on the start-* scripts
yuvipanda 81c67ef
Add .sh shims for the start-* scripts
yuvipanda 219cf36
Document start-notebook.sh and start-singleuser.sh
yuvipanda d6519ac
Partially test start-notebook.sh
yuvipanda 6b96076
Convert run-hooks to python
yuvipanda 73727f3
Make more hook tests pass
yuvipanda b011204
Rename some run-hooks.sh to run-hooks.py
yuvipanda c002adc
Run pre-commit
yuvipanda 39a301c
Don't rely on path to find python
yuvipanda c5e5d42
Merge remote-tracking branch 'upstream/main' into runhooks-py
yuvipanda 79f3ff9
Sort list of files before executing them
yuvipanda 98e4ec6
If any env vars are *unset* by a script, remove them as well
yuvipanda f91ee8f
Clarify how files are executed
yuvipanda 88b23d0
Fix missing uid: in printed message
yuvipanda 1a22aa3
Add warning to run-hooks.sh
yuvipanda 448041e
Merge branch 'main' into runhooks-py
mathbunnyru c5bbe10
Update run-hooks.sh
mathbunnyru 0dc09e2
Update run-hooks.py
mathbunnyru 13901cf
Merge branch 'main' into runhooks-py
mathbunnyru 80800f7
Merge branch 'main' into runhooks-py
mathbunnyru c139f70
Merge branch 'main' into runhooks-py
mathbunnyru 127f3bc
Merge branch 'main' into runhooks-py
mathbunnyru 0b03d93
Merge branch 'main' into runhooks-py
mathbunnyru 0541f72
Merge branch 'main' into runhooks-py
mathbunnyru 01c931c
Merge branch 'main' into runhooks-py
mathbunnyru baab7d9
Merge branch 'main' into runhooks-py
mathbunnyru c6c2696
Merge branch 'main' into runhooks-py
mathbunnyru File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
#!/usr/bin/env python | ||
# Copyright (c) Jupyter Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||
|
||
import json | ||
|
||
# The run-hooks.py script looks for *.sh scripts to source | ||
# and executable files to run within a passed directory | ||
import os | ||
import subprocess | ||
import sys | ||
import tempfile | ||
from pathlib import PosixPath | ||
from textwrap import dedent | ||
|
||
|
||
def source(path: PosixPath) -> None: | ||
""" | ||
Emulate the bash `source` command accurately | ||
|
||
When used in bash, `source` executes the passed file in the current 'context' | ||
of the script from where it is called. This primarily deals with how | ||
bash (and thus environment variables) are modified. | ||
|
||
1. Any bash variables (particularly any set via `export`) are passed on to the | ||
sourced script as their values are at the point source is called | ||
2. The sourced script can itself use `export` to affect the bash variables of the | ||
parent script that called it. | ||
|
||
(2) is the primary difference between `source` and just calling a shell script, | ||
and makes it possible for a set of scripts running in sequence to share data by | ||
passing bash variables across with `export`. | ||
|
||
Given bash variables are environment variables, we will simply look for all modified | ||
environment variables in the script we have sourced, and update the calling python | ||
script's environment variables to match. | ||
|
||
Args: | ||
path (PosixPath): Valid bash script to source | ||
""" | ||
# We start a bash process and have it `source` the script we are given. Then, we | ||
# use python (for convenience) to dump the environment variables from the bash process into | ||
# json (we could use `env` but then handling multiline variable values becomes a nightmare). | ||
# The json is written to a temporary file we create. We read this json, and update our python | ||
# process' environment variable with whatever we get back from bash. | ||
with tempfile.NamedTemporaryFile() as bash_file, tempfile.NamedTemporaryFile() as py_file, tempfile.NamedTemporaryFile() as env_vars_file: | ||
py_file.write( | ||
dedent( | ||
f""" | ||
import os | ||
import json | ||
with(open("{env_vars_file.name}", "w")) as f: | ||
json.dump(dict(os.environ), f) | ||
""" | ||
).encode() | ||
) | ||
py_file.flush() | ||
|
||
bash_file.write( | ||
dedent( | ||
f""" | ||
#!/bin/bash | ||
source {path} | ||
{sys.executable} {py_file.name} | ||
""" | ||
).encode() | ||
) | ||
bash_file.flush() | ||
|
||
run = subprocess.run(["/bin/bash", bash_file.name]) | ||
|
||
if run.returncode != 0: | ||
print( | ||
f"{path} has failed with return code {run.returncode}, continuing execution" | ||
) | ||
return | ||
|
||
# Get env vars of the sourced process after it exits | ||
# This may contain *additional* env vars, or some may be *removed* | ||
child_env_vars = json.load(env_vars_file) | ||
|
||
# Remove any env vars from our environment that were explicitly removed from the child | ||
removed_env_vars = set(os.environ.keys()) - set(child_env_vars.keys()) | ||
for name in removed_env_vars: | ||
del os.environ[name] | ||
|
||
# Update our environment with any *new* or *modified* env vars from the child process | ||
os.environ.update(child_env_vars) | ||
|
||
|
||
if len(sys.argv) != 2: | ||
print("Should pass exactly one directory") | ||
sys.exit(1) | ||
|
||
hooks_directory = PosixPath(sys.argv[1]) | ||
|
||
if not hooks_directory.exists(): | ||
print(f"Directory {hooks_directory} does not exist") | ||
sys.exit(1) | ||
mathbunnyru marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if not hooks_directory.is_dir(): | ||
print(f"{hooks_directory} is not a directory") | ||
sys.exit(1) | ||
mathbunnyru marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
print(f"Running hooks in: {hooks_directory} as uid: {os.getuid()} gid: {os.getgid()}") | ||
|
||
for f in sorted(hooks_directory.iterdir()): | ||
if f.suffix == ".sh": | ||
print(f"Sourcing shell script: {f}") | ||
source(f) | ||
elif os.access(f, os.X_OK): | ||
print(f"Running executable: {f}") | ||
run = subprocess.run([str(f)]) | ||
if run.returncode != 0: | ||
print( | ||
f"{f} has failed with return code {run.returncode}, continuing execution" | ||
) | ||
else: | ||
print(f"Ignoring non-executable: {f}") | ||
|
||
|
||
print(f"Done running hooks in: {hooks_directory}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,6 @@ | ||
#!/bin/bash | ||
# Copyright (c) Jupyter Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||
# echo "WARNING: Use run-hooks.py instead" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, we have to comment this line, otherwise, tests fail, because they do not expect warnings. |
||
|
||
mathbunnyru marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# The run-hooks.sh script looks for *.sh scripts to source | ||
# and executable files to run within a passed directory | ||
|
||
if [ "$#" -ne 1 ]; then | ||
echo "Should pass exactly one directory" | ||
return 1 | ||
fi | ||
|
||
if [[ ! -d "${1}" ]] ; then | ||
echo "Directory ${1} doesn't exist or is not a directory" | ||
return 1 | ||
fi | ||
|
||
echo "Running hooks in: ${1} as uid: $(id -u) gid: $(id -g)" | ||
for f in "${1}/"*; do | ||
# Hadling a case when the directory is empty | ||
[ -e "${f}" ] || continue | ||
case "${f}" in | ||
*.sh) | ||
echo "Sourcing shell script: ${f}" | ||
# shellcheck disable=SC1090 | ||
source "${f}" | ||
# shellcheck disable=SC2181 | ||
if [ $? -ne 0 ] ; then | ||
echo "${f} has failed, continuing execution" | ||
fi | ||
;; | ||
*) | ||
if [ -x "${f}" ] ; then | ||
echo "Running executable: ${f}" | ||
"${f}" | ||
# shellcheck disable=SC2181 | ||
if [ $? -ne 0 ] ; then | ||
echo "${f} has failed, continuing execution" | ||
fi | ||
else | ||
echo "Ignoring non-executable: ${f}" | ||
fi | ||
;; | ||
esac | ||
done | ||
echo "Done running hooks in: ${1}" | ||
exec /usr/local/bin/run-hooks.py "$@" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer having this as actual files, added to the repo and to the images.
Having them inlined here, in my opinion, doesn't make anything easier.
Of course, we will have to pass some params to these tiny scripts, but that's ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mathbunnyru yeah that makes sense. I'll iterate with them inline but move them out by the end.