pre-commit: run black with string normalization

This commit is contained in:
Erik Sundell
2021-11-03 23:55:34 +01:00
parent 2ba942ba76
commit e0aaa4f995
31 changed files with 700 additions and 692 deletions

View File

@@ -13,9 +13,9 @@ def trust_gpg_key(key):
key is a GPG public key (bytes) that can be passed to apt-key add via stdin.
"""
# If gpg2 doesn't exist, install it.
if not os.path.exists('/usr/bin/gpg2'):
install_packages(['gnupg2'])
utils.run_subprocess(['apt-key', 'add', '-'], input=key)
if not os.path.exists("/usr/bin/gpg2"):
install_packages(["gnupg2"])
utils.run_subprocess(["apt-key", "add", "-"], input=key)
def add_source(name, source_url, section):
@@ -27,20 +27,20 @@ def add_source(name, source_url, section):
# lsb_release is not installed in most docker images by default
distro = (
subprocess.check_output(
['/bin/bash', '-c', 'source /etc/os-release && echo ${VERSION_CODENAME}'],
["/bin/bash", "-c", "source /etc/os-release && echo ${VERSION_CODENAME}"],
stderr=subprocess.STDOUT,
)
.decode()
.strip()
)
line = f'deb {source_url} {distro} {section}\n'
with open(os.path.join('/etc/apt/sources.list.d/', name + '.list'), 'a+') as f:
line = f"deb {source_url} {distro} {section}\n"
with open(os.path.join("/etc/apt/sources.list.d/", name + ".list"), "a+") as f:
# Write out deb line only if it already doesn't exist
f.seek(0)
if line not in f.read():
f.write(line)
f.truncate()
utils.run_subprocess(['apt-get', 'update', '--yes'])
utils.run_subprocess(["apt-get", "update", "--yes"])
def install_packages(packages):
@@ -48,9 +48,9 @@ def install_packages(packages):
Install debian packages
"""
# Check if an apt-get update is required
if len(os.listdir('/var/lib/apt/lists')) == 0:
utils.run_subprocess(['apt-get', 'update', '--yes'])
if len(os.listdir("/var/lib/apt/lists")) == 0:
utils.run_subprocess(["apt-get", "update", "--yes"])
env = os.environ.copy()
# Stop apt from asking questions!
env['DEBIAN_FRONTEND'] = 'noninteractive'
utils.run_subprocess(['apt-get', 'install', '--yes'] + packages, env=env)
env["DEBIAN_FRONTEND"] = "noninteractive"
utils.run_subprocess(["apt-get", "install", "--yes"] + packages, env=env)

View File

@@ -32,7 +32,7 @@ def check_miniconda_version(prefix, version):
try:
installed_version = (
subprocess.check_output(
[os.path.join(prefix, 'bin', 'conda'), '-V'], stderr=subprocess.STDOUT
[os.path.join(prefix, "bin", "conda"), "-V"], stderr=subprocess.STDOUT
)
.decode()
.strip()
@@ -53,7 +53,7 @@ def download_miniconda_installer(installer_url, sha256sum):
of given version, verifies the sha256sum & provides path to it to the `with`
block to run.
"""
with tempfile.NamedTemporaryFile('wb') as f:
with tempfile.NamedTemporaryFile("wb") as f:
f.write(requests.get(installer_url).content)
# Remain in the NamedTemporaryFile context, but flush changes, see:
# https://docs.python.org/3/library/os.html#os.fsync
@@ -61,7 +61,7 @@ def download_miniconda_installer(installer_url, sha256sum):
os.fsync(f.fileno())
if sha256_file(f.name) != sha256sum:
raise Exception('sha256sum hash mismatch! Downloaded file corrupted')
raise Exception("sha256sum hash mismatch! Downloaded file corrupted")
yield f.name
@@ -83,7 +83,7 @@ def install_miniconda(installer_path, prefix):
"""
Install miniconda with installer at installer_path under prefix
"""
utils.run_subprocess(['/bin/bash', installer_path, '-u', '-b', '-p', prefix])
utils.run_subprocess(["/bin/bash", installer_path, "-u", "-b", "-p", prefix])
# fix permissions on initial install
# a few files have the wrong ownership and permissions initially
# when the installer is run as root
@@ -97,7 +97,7 @@ def ensure_conda_packages(prefix, packages):
Note that conda seem to update dependencies by default, so there is probably
no need to have a update parameter exposed for this function.
"""
conda_executable = [os.path.join(prefix, 'bin', 'mamba')]
conda_executable = [os.path.join(prefix, "bin", "mamba")]
abspath = os.path.abspath(prefix)
# Let subprocess errors propagate
# Explicitly do *not* capture stderr, since that's not always JSON!
@@ -106,11 +106,11 @@ def ensure_conda_packages(prefix, packages):
raw_output = subprocess.check_output(
conda_executable
+ [
'install',
'-c',
'conda-forge', # Make customizable if we ever need to
'--json',
'--prefix',
"install",
"-c",
"conda-forge", # Make customizable if we ever need to
"--json",
"--prefix",
abspath,
]
+ packages
@@ -118,17 +118,17 @@ def ensure_conda_packages(prefix, packages):
# `conda install` outputs JSON lines for fetch updates,
# and a undelimited output at the end. There is no reasonable way to
# parse this outside of this kludge.
filtered_output = '\n'.join(
filtered_output = "\n".join(
[
l
for l in raw_output.split('\n')
for l in raw_output.split("\n")
# Sometimes the JSON messages start with a \x00. The lstrip removes these.
# conda messages seem to randomly throw \x00 in places for no reason
if not l.lstrip('\x00').startswith('{"fetch"')
if not l.lstrip("\x00").startswith('{"fetch"')
]
)
output = json.loads(filtered_output.lstrip('\x00'))
if 'success' in output and output['success'] == True:
output = json.loads(filtered_output.lstrip("\x00"))
if "success" in output and output["success"] == True:
return
fix_permissions(prefix)
@@ -138,10 +138,10 @@ def ensure_pip_packages(prefix, packages, upgrade=False):
Ensure pip packages are installed in the given conda prefix.
"""
abspath = os.path.abspath(prefix)
pip_executable = [os.path.join(abspath, 'bin', 'python'), '-m', 'pip']
pip_cmd = pip_executable + ['install']
pip_executable = [os.path.join(abspath, "bin", "python"), "-m", "pip"]
pip_cmd = pip_executable + ["install"]
if upgrade:
pip_cmd.append('--upgrade')
pip_cmd.append("--upgrade")
utils.run_subprocess(pip_cmd + packages)
fix_permissions(prefix)
@@ -153,9 +153,9 @@ def ensure_pip_requirements(prefix, requirements_path, upgrade=False):
requirements_path can be a file or a URL.
"""
abspath = os.path.abspath(prefix)
pip_executable = [os.path.join(abspath, 'bin', 'python'), '-m', 'pip']
pip_cmd = pip_executable + ['install']
pip_executable = [os.path.join(abspath, "bin", "python"), "-m", "pip"]
pip_cmd = pip_executable + ["install"]
if upgrade:
pip_cmd.append('--upgrade')
utils.run_subprocess(pip_cmd + ['--requirement', requirements_path])
pip_cmd.append("--upgrade")
utils.run_subprocess(pip_cmd + ["--requirement", requirements_path])
fix_permissions(prefix)

View File

@@ -25,12 +25,12 @@ import requests
from .yaml import yaml
INSTALL_PREFIX = os.environ.get('TLJH_INSTALL_PREFIX', '/opt/tljh')
HUB_ENV_PREFIX = os.path.join(INSTALL_PREFIX, 'hub')
USER_ENV_PREFIX = os.path.join(INSTALL_PREFIX, 'user')
STATE_DIR = os.path.join(INSTALL_PREFIX, 'state')
CONFIG_DIR = os.path.join(INSTALL_PREFIX, 'config')
CONFIG_FILE = os.path.join(CONFIG_DIR, 'config.yaml')
INSTALL_PREFIX = os.environ.get("TLJH_INSTALL_PREFIX", "/opt/tljh")
HUB_ENV_PREFIX = os.path.join(INSTALL_PREFIX, "hub")
USER_ENV_PREFIX = os.path.join(INSTALL_PREFIX, "user")
STATE_DIR = os.path.join(INSTALL_PREFIX, "state")
CONFIG_DIR = os.path.join(INSTALL_PREFIX, "config")
CONFIG_FILE = os.path.join(CONFIG_DIR, "config.yaml")
def set_item_in_config(config, property_path, value):
@@ -42,7 +42,7 @@ def set_item_in_config(config, property_path, value):
property_path is a series of dot separated values. Any part of the path
that does not exist is created.
"""
path_components = property_path.split('.')
path_components = property_path.split(".")
# Mutate a copy of the config, not config itself
cur_part = config_copy = deepcopy(config)
@@ -69,7 +69,7 @@ def unset_item_from_config(config, property_path):
property_path is a series of dot separated values.
"""
path_components = property_path.split('.')
path_components = property_path.split(".")
# Mutate a copy of the config, not config itself
cur_part = config_copy = deepcopy(config)
@@ -94,13 +94,13 @@ def unset_item_from_config(config, property_path):
for i, cur_path in enumerate(path_components):
if i == len(path_components) - 1:
if cur_path not in cur_part:
raise ValueError(f'{property_path} does not exist in config!')
raise ValueError(f"{property_path} does not exist in config!")
del cur_part[cur_path]
remove_empty_configs(config_copy, path_components[:-1])
break
else:
if cur_path not in cur_part:
raise ValueError(f'{property_path} does not exist in config!')
raise ValueError(f"{property_path} does not exist in config!")
cur_part = cur_part[cur_path]
return config_copy
@@ -110,7 +110,7 @@ def add_item_to_config(config, property_path, value):
"""
Add an item to a list in config.
"""
path_components = property_path.split('.')
path_components = property_path.split(".")
# Mutate a copy of the config, not config itself
cur_part = config_copy = deepcopy(config)
@@ -136,7 +136,7 @@ def remove_item_from_config(config, property_path, value):
"""
Remove an item from a list in config.
"""
path_components = property_path.split('.')
path_components = property_path.split(".")
# Mutate a copy of the config, not config itself
cur_part = config_copy = deepcopy(config)
@@ -144,12 +144,12 @@ def remove_item_from_config(config, property_path, value):
if i == len(path_components) - 1:
# Final component, it must be a list and we delete from it
if cur_path not in cur_part or not _is_list(cur_part[cur_path]):
raise ValueError(f'{property_path} is not a list')
raise ValueError(f"{property_path} is not a list")
cur_part = cur_part[cur_path]
cur_part.remove(value)
else:
if cur_path not in cur_part or not _is_dict(cur_part[cur_path]):
raise ValueError(f'{property_path} does not exist in config!')
raise ValueError(f"{property_path} does not exist in config!")
cur_part = cur_part[cur_path]
return config_copy
@@ -182,7 +182,7 @@ def set_config_value(config_path, key_path, value):
config = set_item_in_config(config, key_path, value)
with open(config_path, 'w') as f:
with open(config_path, "w") as f:
yaml.dump(config, f)
@@ -200,7 +200,7 @@ def unset_config_value(config_path, key_path):
config = unset_item_from_config(config, key_path)
with open(config_path, 'w') as f:
with open(config_path, "w") as f:
yaml.dump(config, f)
@@ -218,7 +218,7 @@ def add_config_value(config_path, key_path, value):
config = add_item_to_config(config, key_path, value)
with open(config_path, 'w') as f:
with open(config_path, "w") as f:
yaml.dump(config, f)
@@ -236,19 +236,19 @@ def remove_config_value(config_path, key_path, value):
config = remove_item_from_config(config, key_path, value)
with open(config_path, 'w') as f:
with open(config_path, "w") as f:
yaml.dump(config, f)
def check_hub_ready():
from .configurer import load_config
base_url = load_config()['base_url']
base_url = base_url[:-1] if base_url[-1] == '/' else base_url
http_port = load_config()['http']['port']
base_url = load_config()["base_url"]
base_url = base_url[:-1] if base_url[-1] == "/" else base_url
http_port = load_config()["http"]["port"]
try:
r = requests.get(
'http://127.0.0.1:%d%s/hub/api' % (http_port, base_url), verify=False
"http://127.0.0.1:%d%s/hub/api" % (http_port, base_url), verify=False
)
return r.status_code == 200
except:
@@ -264,33 +264,33 @@ def reload_component(component):
# import here to avoid circular imports
from tljh import systemd, traefik
if component == 'hub':
systemd.restart_service('jupyterhub')
if component == "hub":
systemd.restart_service("jupyterhub")
# Ensure hub is back up
while not systemd.check_service_active('jupyterhub'):
while not systemd.check_service_active("jupyterhub"):
time.sleep(1)
while not check_hub_ready():
time.sleep(1)
print('Hub reload with new configuration complete')
elif component == 'proxy':
print("Hub reload with new configuration complete")
elif component == "proxy":
traefik.ensure_traefik_config(STATE_DIR)
systemd.restart_service('traefik')
while not systemd.check_service_active('traefik'):
systemd.restart_service("traefik")
while not systemd.check_service_active("traefik"):
time.sleep(1)
print('Proxy reload with new configuration complete')
print("Proxy reload with new configuration complete")
def parse_value(value_str):
"""Parse a value string"""
if value_str is None:
return value_str
if re.match(r'^\d+$', value_str):
if re.match(r"^\d+$", value_str):
return int(value_str)
elif re.match(r'^\d+\.\d*$', value_str):
elif re.match(r"^\d+\.\d*$", value_str):
return float(value_str)
elif value_str.lower() == 'true':
elif value_str.lower() == "true":
return True
elif value_str.lower() == 'false':
elif value_str.lower() == "false":
return False
else:
# it's a string
@@ -327,67 +327,67 @@ def main(argv=None):
argparser = argparse.ArgumentParser()
argparser.add_argument(
'--config-path', default=CONFIG_FILE, help='Path to TLJH config.yaml file'
"--config-path", default=CONFIG_FILE, help="Path to TLJH config.yaml file"
)
subparsers = argparser.add_subparsers(dest='action')
subparsers = argparser.add_subparsers(dest="action")
show_parser = subparsers.add_parser('show', help='Show current configuration')
show_parser = subparsers.add_parser("show", help="Show current configuration")
unset_parser = subparsers.add_parser('unset', help='Unset a configuration property')
unset_parser = subparsers.add_parser("unset", help="Unset a configuration property")
unset_parser.add_argument(
'key_path', help='Dot separated path to configuration key to unset'
"key_path", help="Dot separated path to configuration key to unset"
)
set_parser = subparsers.add_parser('set', help='Set a configuration property')
set_parser = subparsers.add_parser("set", help="Set a configuration property")
set_parser.add_argument(
'key_path', help='Dot separated path to configuration key to set'
"key_path", help="Dot separated path to configuration key to set"
)
set_parser.add_argument('value', help='Value to set the configuration key to')
set_parser.add_argument("value", help="Value to set the configuration key to")
add_item_parser = subparsers.add_parser(
'add-item', help='Add a value to a list for a configuration property'
"add-item", help="Add a value to a list for a configuration property"
)
add_item_parser.add_argument(
'key_path', help='Dot separated path to configuration key to add value to'
"key_path", help="Dot separated path to configuration key to add value to"
)
add_item_parser.add_argument('value', help='Value to add to the configuration key')
add_item_parser.add_argument("value", help="Value to add to the configuration key")
remove_item_parser = subparsers.add_parser(
'remove-item', help='Remove a value from a list for a configuration property'
"remove-item", help="Remove a value from a list for a configuration property"
)
remove_item_parser.add_argument(
'key_path', help='Dot separated path to configuration key to remove value from'
"key_path", help="Dot separated path to configuration key to remove value from"
)
remove_item_parser.add_argument('value', help='Value to remove from key_path')
remove_item_parser.add_argument("value", help="Value to remove from key_path")
reload_parser = subparsers.add_parser(
'reload', help='Reload a component to apply configuration change'
"reload", help="Reload a component to apply configuration change"
)
reload_parser.add_argument(
'component',
choices=('hub', 'proxy'),
help='Which component to reload',
default='hub',
nargs='?',
"component",
choices=("hub", "proxy"),
help="Which component to reload",
default="hub",
nargs="?",
)
args = argparser.parse_args(argv)
if args.action == 'show':
if args.action == "show":
show_config(args.config_path)
elif args.action == 'set':
elif args.action == "set":
set_config_value(args.config_path, args.key_path, parse_value(args.value))
elif args.action == 'unset':
elif args.action == "unset":
unset_config_value(args.config_path, args.key_path)
elif args.action == 'add-item':
elif args.action == "add-item":
add_config_value(args.config_path, args.key_path, parse_value(args.value))
elif args.action == 'remove-item':
elif args.action == "remove-item":
remove_config_value(args.config_path, args.key_path, parse_value(args.value))
elif args.action == 'reload':
elif args.action == "reload":
reload_component(args.component)
else:
argparser.print_help()
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@@ -17,50 +17,50 @@ from .yaml import yaml
# Default configuration for tljh
# User provided config is merged into this
default = {
'base_url': '/',
'auth': {
'type': 'firstuseauthenticator.FirstUseAuthenticator',
'FirstUseAuthenticator': {'create_users': False},
"base_url": "/",
"auth": {
"type": "firstuseauthenticator.FirstUseAuthenticator",
"FirstUseAuthenticator": {"create_users": False},
},
'users': {'allowed': [], 'banned': [], 'admin': [], 'extra_user_groups': {}},
'limits': {
'memory': None,
'cpu': None,
"users": {"allowed": [], "banned": [], "admin": [], "extra_user_groups": {}},
"limits": {
"memory": None,
"cpu": None,
},
'http': {
'port': 80,
"http": {
"port": 80,
},
'https': {
'enabled': False,
'port': 443,
'tls': {
'cert': '',
'key': '',
"https": {
"enabled": False,
"port": 443,
"tls": {
"cert": "",
"key": "",
},
'letsencrypt': {
'email': '',
'domains': [],
"letsencrypt": {
"email": "",
"domains": [],
},
},
'traefik_api': {
'ip': "127.0.0.1",
'port': 8099,
'username': 'api_admin',
'password': '',
"traefik_api": {
"ip": "127.0.0.1",
"port": 8099,
"username": "api_admin",
"password": "",
},
'user_environment': {
'default_app': 'classic',
"user_environment": {
"default_app": "classic",
},
'services': {
'cull': {
'enabled': True,
'timeout': 600,
'every': 60,
'concurrency': 5,
'users': False,
'max_age': 0,
"services": {
"cull": {
"enabled": True,
"timeout": 600,
"every": 60,
"concurrency": 5,
"users": False,
"max_age": 0,
},
'configurator': {'enabled': False},
"configurator": {"enabled": False},
},
}
@@ -109,14 +109,14 @@ def set_if_not_none(parent, key, value):
def load_traefik_api_credentials():
"""Load traefik api secret from a file"""
proxy_secret_path = os.path.join(STATE_DIR, 'traefik-api.secret')
proxy_secret_path = os.path.join(STATE_DIR, "traefik-api.secret")
if not os.path.exists(proxy_secret_path):
return {}
with open(proxy_secret_path) as f:
password = f.read()
return {
'traefik_api': {
'password': password,
"traefik_api": {
"password": password,
}
}
@@ -135,7 +135,7 @@ def update_base_url(c, config):
"""
Update base_url of JupyterHub through tljh config
"""
c.JupyterHub.base_url = config['base_url']
c.JupyterHub.base_url = config["base_url"]
def update_auth(c, config):
@@ -172,13 +172,13 @@ def update_auth(c, config):
c.JupyterHub.authenticator_class and any configured value being None won't
be set.
"""
tljh_auth_config = config['auth']
tljh_auth_config = config["auth"]
c.JupyterHub.authenticator_class = tljh_auth_config['type']
c.JupyterHub.authenticator_class = tljh_auth_config["type"]
for auth_key, auth_value in tljh_auth_config.items():
if not (auth_key[0] == auth_key[0].upper() and isinstance(auth_value, dict)):
if auth_key == 'type':
if auth_key == "type":
continue
raise ValueError(
f"Error: auth.{auth_key} was ignored, it didn't look like a valid configuration"
@@ -194,75 +194,75 @@ def update_userlists(c, config):
"""
Set user whitelists & admin lists
"""
users = config['users']
users = config["users"]
c.Authenticator.allowed_users = set(users['allowed'])
c.Authenticator.blocked_users = set(users['banned'])
c.Authenticator.admin_users = set(users['admin'])
c.Authenticator.allowed_users = set(users["allowed"])
c.Authenticator.blocked_users = set(users["banned"])
c.Authenticator.admin_users = set(users["admin"])
def update_usergroups(c, config):
"""
Set user groups
"""
users = config['users']
c.UserCreatingSpawner.user_groups = users['extra_user_groups']
users = config["users"]
c.UserCreatingSpawner.user_groups = users["extra_user_groups"]
def update_limits(c, config):
"""
Set user server limits
"""
limits = config['limits']
limits = config["limits"]
c.Spawner.mem_limit = limits['memory']
c.Spawner.cpu_limit = limits['cpu']
c.Spawner.mem_limit = limits["memory"]
c.Spawner.cpu_limit = limits["cpu"]
def update_user_environment(c, config):
"""
Set user environment configuration
"""
user_env = config['user_environment']
user_env = config["user_environment"]
# Set default application users are launched into
if user_env['default_app'] == 'jupyterlab':
c.Spawner.default_url = '/lab'
elif user_env['default_app'] == 'nteract':
c.Spawner.default_url = '/nteract'
if user_env["default_app"] == "jupyterlab":
c.Spawner.default_url = "/lab"
elif user_env["default_app"] == "nteract":
c.Spawner.default_url = "/nteract"
def update_user_account_config(c, config):
c.SystemdSpawner.username_template = 'jupyter-{USERNAME}'
c.SystemdSpawner.username_template = "jupyter-{USERNAME}"
def update_traefik_api(c, config):
"""
Set traefik api endpoint credentials
"""
c.TraefikTomlProxy.traefik_api_username = config['traefik_api']['username']
c.TraefikTomlProxy.traefik_api_password = config['traefik_api']['password']
c.TraefikTomlProxy.traefik_api_username = config["traefik_api"]["username"]
c.TraefikTomlProxy.traefik_api_password = config["traefik_api"]["password"]
def set_cull_idle_service(config):
"""
Set Idle Culler service
"""
cull_cmd = [sys.executable, '-m', 'jupyterhub_idle_culler']
cull_config = config['services']['cull']
cull_cmd = [sys.executable, "-m", "jupyterhub_idle_culler"]
cull_config = config["services"]["cull"]
print()
cull_cmd += ['--timeout=%d' % cull_config['timeout']]
cull_cmd += ['--cull-every=%d' % cull_config['every']]
cull_cmd += ['--concurrency=%d' % cull_config['concurrency']]
cull_cmd += ['--max-age=%d' % cull_config['max_age']]
if cull_config['users']:
cull_cmd += ['--cull-users']
cull_cmd += ["--timeout=%d" % cull_config["timeout"]]
cull_cmd += ["--cull-every=%d" % cull_config["every"]]
cull_cmd += ["--concurrency=%d" % cull_config["concurrency"]]
cull_cmd += ["--max-age=%d" % cull_config["max_age"]]
if cull_config["users"]:
cull_cmd += ["--cull-users"]
cull_service = {
'name': 'cull-idle',
'admin': True,
'command': cull_cmd,
"name": "cull-idle",
"admin": True,
"command": cull_cmd,
}
return cull_service
@@ -280,9 +280,9 @@ def set_configurator(config):
f"--Configurator.config_file={HERE}/jupyterhub_configurator_config.py",
]
configurator_service = {
'name': 'configurator',
'url': 'http://127.0.0.1:10101',
'command': configurator_cmd,
"name": "configurator",
"url": "http://127.0.0.1:10101",
"command": configurator_cmd,
}
return configurator_service
@@ -291,9 +291,9 @@ def set_configurator(config):
def update_services(c, config):
c.JupyterHub.services = []
if config['services']['cull']['enabled']:
if config["services"]["cull"]["enabled"]:
c.JupyterHub.services.append(set_cull_idle_service(config))
if config['services']['configurator']['enabled']:
if config["services"]["configurator"]["enabled"]:
c.JupyterHub.services.append(set_configurator(config))
@@ -314,7 +314,7 @@ def _merge_dictionaries(a, b, path=None, update=True):
elif update:
a[key] = b[key]
else:
raise Exception('Conflict at %s' % '.'.join(path + [str(key)]))
raise Exception("Conflict at %s" % ".".join(path + [str(key)]))
else:
a[key] = b[key]
return a

View File

@@ -3,8 +3,8 @@ Hook specifications that pluggy plugins can override
"""
import pluggy
hookspec = pluggy.HookspecMarker('tljh')
hookimpl = pluggy.HookimplMarker('tljh')
hookspec = pluggy.HookspecMarker("tljh")
hookimpl = pluggy.HookimplMarker("tljh")
@hookspec

View File

@@ -46,18 +46,18 @@ def remove_chp():
Ensure CHP is not running
"""
if os.path.exists("/etc/systemd/system/configurable-http-proxy.service"):
if systemd.check_service_active('configurable-http-proxy.service'):
if systemd.check_service_active("configurable-http-proxy.service"):
try:
systemd.stop_service('configurable-http-proxy.service')
systemd.stop_service("configurable-http-proxy.service")
except subprocess.CalledProcessError:
logger.info("Cannot stop configurable-http-proxy...")
if systemd.check_service_enabled('configurable-http-proxy.service'):
if systemd.check_service_enabled("configurable-http-proxy.service"):
try:
systemd.disable_service('configurable-http-proxy.service')
systemd.disable_service("configurable-http-proxy.service")
except subprocess.CalledProcessError:
logger.info("Cannot disable configurable-http-proxy...")
try:
systemd.uninstall_unit('configurable-http-proxy.service')
systemd.uninstall_unit("configurable-http-proxy.service")
except subprocess.CalledProcessError:
logger.info("Cannot uninstall configurable-http-proxy...")
@@ -70,36 +70,36 @@ def ensure_jupyterhub_service(prefix):
remove_chp()
systemd.reload_daemon()
with open(os.path.join(HERE, 'systemd-units', 'jupyterhub.service')) as f:
with open(os.path.join(HERE, "systemd-units", "jupyterhub.service")) as f:
hub_unit_template = f.read()
with open(os.path.join(HERE, 'systemd-units', 'traefik.service')) as f:
with open(os.path.join(HERE, "systemd-units", "traefik.service")) as f:
traefik_unit_template = f.read()
# Set up proxy / hub secret token if it is not already setup
proxy_secret_path = os.path.join(STATE_DIR, 'traefik-api.secret')
proxy_secret_path = os.path.join(STATE_DIR, "traefik-api.secret")
if not os.path.exists(proxy_secret_path):
with open(proxy_secret_path, 'w') as f:
with open(proxy_secret_path, "w") as f:
f.write(secrets.token_hex(32))
traefik.ensure_traefik_config(STATE_DIR)
unit_params = dict(
python_interpreter_path=sys.executable,
jupyterhub_config_path=os.path.join(HERE, 'jupyterhub_config.py'),
jupyterhub_config_path=os.path.join(HERE, "jupyterhub_config.py"),
install_prefix=INSTALL_PREFIX,
)
systemd.install_unit('jupyterhub.service', hub_unit_template.format(**unit_params))
systemd.install_unit('traefik.service', traefik_unit_template.format(**unit_params))
systemd.install_unit("jupyterhub.service", hub_unit_template.format(**unit_params))
systemd.install_unit("traefik.service", traefik_unit_template.format(**unit_params))
systemd.reload_daemon()
# If JupyterHub is running, we want to restart it.
systemd.restart_service('jupyterhub')
systemd.restart_service('traefik')
systemd.restart_service("jupyterhub")
systemd.restart_service("traefik")
# Mark JupyterHub & traefik to start at boot time
systemd.enable_service('jupyterhub')
systemd.enable_service('traefik')
systemd.enable_service("jupyterhub")
systemd.enable_service("traefik")
def ensure_jupyterhub_package(prefix):
@@ -115,8 +115,8 @@ def ensure_jupyterhub_package(prefix):
# Install pycurl. JupyterHub prefers pycurl over SimpleHTTPClient automatically
# pycurl is generally more bugfree - see https://github.com/jupyterhub/the-littlest-jupyterhub/issues/289
# build-essential is also generally useful to everyone involved, and required for pycurl
apt.install_packages(['libssl-dev', 'libcurl4-openssl-dev', 'build-essential'])
conda.ensure_pip_packages(prefix, ['pycurl==7.*'], upgrade=True)
apt.install_packages(["libssl-dev", "libcurl4-openssl-dev", "build-essential"])
conda.ensure_pip_packages(prefix, ["pycurl==7.*"], upgrade=True)
conda.ensure_pip_packages(
prefix,
@@ -140,17 +140,17 @@ def ensure_usergroups():
"""
Sets up user groups & sudo rules
"""
user.ensure_group('jupyterhub-admins')
user.ensure_group('jupyterhub-users')
user.ensure_group("jupyterhub-admins")
user.ensure_group("jupyterhub-users")
logger.info("Granting passwordless sudo to JupyterHub admins...")
with open('/etc/sudoers.d/jupyterhub-admins', 'w') as f:
with open("/etc/sudoers.d/jupyterhub-admins", "w") as f:
# JupyterHub admins should have full passwordless sudo access
f.write('%jupyterhub-admins ALL = (ALL) NOPASSWD: ALL\n')
f.write("%jupyterhub-admins ALL = (ALL) NOPASSWD: ALL\n")
# `sudo -E` should preserve the $PATH we set. This allows
# admins in jupyter terminals to do `sudo -E pip install <package>`,
# `pip` is in the $PATH we set in jupyterhub_config.py to include the user conda env.
f.write('Defaults exempt_group = jupyterhub-admins\n')
f.write("Defaults exempt_group = jupyterhub-admins\n")
def ensure_user_environment(user_requirements_txt_file):
@@ -159,50 +159,58 @@ def ensure_user_environment(user_requirements_txt_file):
"""
logger.info("Setting up user environment...")
miniconda_old_version = '4.5.4'
miniconda_new_version = '4.7.10'
miniconda_old_version = "4.5.4"
miniconda_new_version = "4.7.10"
# Install mambaforge using an installer from
# https://github.com/conda-forge/miniforge/releases
mambaforge_new_version = '4.10.3-7'
mambaforge_new_version = "4.10.3-7"
# Check system architecture, set appropriate installer checksum
if os.uname().machine == 'aarch64':
installer_sha256 = "ac95f137b287b3408e4f67f07a284357b1119ee157373b788b34e770ef2392b2"
elif os.uname().machine == 'x86_64':
installer_sha256 = "fc872522ec427fcab10167a93e802efaf251024b58cc27b084b915a9a73c4474"
if os.uname().machine == "aarch64":
installer_sha256 = (
"ac95f137b287b3408e4f67f07a284357b1119ee157373b788b34e770ef2392b2"
)
elif os.uname().machine == "x86_64":
installer_sha256 = (
"fc872522ec427fcab10167a93e802efaf251024b58cc27b084b915a9a73c4474"
)
# Check OS, set appropriate string for conda installer path
if os.uname().sysname != 'Linux':
if os.uname().sysname != "Linux":
raise OSError("TLJH is only supported on Linux platforms.")
# Then run `mamba --version` to get the conda and mamba versions
# Keep these in sync with tests/test_conda.py::prefix
mambaforge_conda_new_version = '4.10.3'
mambaforge_mamba_version = '0.16.0'
mambaforge_conda_new_version = "4.10.3"
mambaforge_mamba_version = "0.16.0"
if conda.check_miniconda_version(USER_ENV_PREFIX, mambaforge_conda_new_version):
conda_version = '4.10.3'
conda_version = "4.10.3"
elif conda.check_miniconda_version(USER_ENV_PREFIX, miniconda_new_version):
conda_version = '4.8.1'
conda_version = "4.8.1"
elif conda.check_miniconda_version(USER_ENV_PREFIX, miniconda_old_version):
conda_version = '4.5.8'
conda_version = "4.5.8"
# If no prior miniconda installation is found, we can install a newer version
else:
logger.info('Downloading & setting up user environment...')
installer_url = "https://github.com/conda-forge/miniforge/releases/download/{v}/Mambaforge-{v}-Linux-{arch}.sh".format(v=mambaforge_new_version, arch=os.uname().machine)
with conda.download_miniconda_installer(installer_url, installer_sha256) as installer_path:
logger.info("Downloading & setting up user environment...")
installer_url = "https://github.com/conda-forge/miniforge/releases/download/{v}/Mambaforge-{v}-Linux-{arch}.sh".format(
v=mambaforge_new_version, arch=os.uname().machine
)
with conda.download_miniconda_installer(
installer_url, installer_sha256
) as installer_path:
conda.install_miniconda(installer_path, USER_ENV_PREFIX)
conda_version = '4.10.3'
conda_version = "4.10.3"
conda.ensure_conda_packages(
USER_ENV_PREFIX,
[
# Conda's latest version is on conda much more so than on PyPI.
'conda==' + conda_version,
'mamba==' + mambaforge_mamba_version,
"conda==" + conda_version,
"mamba==" + mambaforge_mamba_version,
],
)
conda.ensure_pip_requirements(
USER_ENV_PREFIX,
os.path.join(HERE, 'requirements-base.txt'),
os.path.join(HERE, "requirements-base.txt"),
upgrade=True,
)
@@ -231,25 +239,25 @@ def ensure_admins(admin_password_list):
else:
config = {}
config['users'] = config.get('users', {})
config["users"] = config.get("users", {})
db_passw = os.path.join(STATE_DIR, 'passwords.dbm')
db_passw = os.path.join(STATE_DIR, "passwords.dbm")
admins = []
for admin_password_entry in admin_password_list:
for admin_password_pair in admin_password_entry:
if ":" in admin_password_pair:
admin, password = admin_password_pair.split(':')
admin, password = admin_password_pair.split(":")
admins.append(admin)
# Add admin:password to the db
password = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
with dbm.open(db_passw, 'c', 0o600) as db:
with dbm.open(db_passw, "c", 0o600) as db:
db[admin] = password
else:
admins.append(admin_password_pair)
config['users']['admin'] = admins
config["users"]["admin"] = admins
with open(config_path, 'w+') as f:
with open(config_path, "w+") as f:
yaml.dump(config, f)
@@ -262,12 +270,12 @@ def ensure_jupyterhub_running(times=20):
for i in range(times):
try:
logger.info(f'Waiting for JupyterHub to come up ({i + 1}/{times} tries)')
logger.info(f"Waiting for JupyterHub to come up ({i + 1}/{times} tries)")
# Because we don't care at this level that SSL is valid, we can suppress
# InsecureRequestWarning for this request.
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
requests.get('http://127.0.0.1', verify=False)
requests.get("http://127.0.0.1", verify=False)
return
except requests.HTTPError as h:
if h.response.status_code in [404, 502, 503]:
@@ -299,15 +307,15 @@ def ensure_symlinks(prefix):
around this with sudo -E and extra entries in the sudoers file, but this
is far more secure at the cost of upsetting some FHS purists.
"""
tljh_config_src = os.path.join(prefix, 'bin', 'tljh-config')
tljh_config_dest = '/usr/bin/tljh-config'
tljh_config_src = os.path.join(prefix, "bin", "tljh-config")
tljh_config_dest = "/usr/bin/tljh-config"
if os.path.exists(tljh_config_dest):
if os.path.realpath(tljh_config_dest) != tljh_config_src:
# tljh-config exists that isn't ours. We should *not* delete this file,
# instead we throw an error and abort. Deleting files owned by other people
# while running as root is dangerous, especially with symlinks involved.
raise FileExistsError(
f'/usr/bin/tljh-config exists but is not a symlink to {tljh_config_src}'
f"/usr/bin/tljh-config exists but is not a symlink to {tljh_config_src}"
)
else:
# We have a working symlink, so do nothing
@@ -324,9 +332,9 @@ def setup_plugins(plugins=None):
conda.ensure_pip_packages(HUB_ENV_PREFIX, plugins, upgrade=True)
# Set up plugin infrastructure
pm = pluggy.PluginManager('tljh')
pm = pluggy.PluginManager("tljh")
pm.add_hookspecs(hooks)
pm.load_setuptools_entrypoints('tljh')
pm.load_setuptools_entrypoints("tljh")
return pm
@@ -340,8 +348,8 @@ def run_plugin_actions(plugin_manager):
apt_packages = list(set(itertools.chain(*hook.tljh_extra_apt_packages())))
if apt_packages:
logger.info(
'Installing {} apt packages collected from plugins: {}'.format(
len(apt_packages), ' '.join(apt_packages)
"Installing {} apt packages collected from plugins: {}".format(
len(apt_packages), " ".join(apt_packages)
)
)
apt.install_packages(apt_packages)
@@ -350,8 +358,8 @@ def run_plugin_actions(plugin_manager):
hub_pip_packages = list(set(itertools.chain(*hook.tljh_extra_hub_pip_packages())))
if hub_pip_packages:
logger.info(
'Installing {} hub pip packages collected from plugins: {}'.format(
len(hub_pip_packages), ' '.join(hub_pip_packages)
"Installing {} hub pip packages collected from plugins: {}".format(
len(hub_pip_packages), " ".join(hub_pip_packages)
)
)
conda.ensure_pip_packages(
@@ -364,8 +372,8 @@ def run_plugin_actions(plugin_manager):
conda_packages = list(set(itertools.chain(*hook.tljh_extra_user_conda_packages())))
if conda_packages:
logger.info(
'Installing {} user conda packages collected from plugins: {}'.format(
len(conda_packages), ' '.join(conda_packages)
"Installing {} user conda packages collected from plugins: {}".format(
len(conda_packages), " ".join(conda_packages)
)
)
conda.ensure_conda_packages(USER_ENV_PREFIX, conda_packages)
@@ -374,8 +382,8 @@ def run_plugin_actions(plugin_manager):
user_pip_packages = list(set(itertools.chain(*hook.tljh_extra_user_pip_packages())))
if user_pip_packages:
logger.info(
'Installing {} user pip packages collected from plugins: {}'.format(
len(user_pip_packages), ' '.join(user_pip_packages)
"Installing {} user pip packages collected from plugins: {}".format(
len(user_pip_packages), " ".join(user_pip_packages)
)
)
conda.ensure_pip_packages(
@@ -393,7 +401,7 @@ def ensure_config_yaml(plugin_manager):
Ensure we have a config.yaml present
"""
# ensure config dir exists and is private
for path in [CONFIG_DIR, os.path.join(CONFIG_DIR, 'jupyterhub_config.d')]:
for path in [CONFIG_DIR, os.path.join(CONFIG_DIR, "jupyterhub_config.d")]:
os.makedirs(path, mode=0o700, exist_ok=True)
migrator.migrate_config_files()
@@ -407,7 +415,7 @@ def ensure_config_yaml(plugin_manager):
hook = plugin_manager.hook
hook.tljh_config_post_install(config=config)
with open(CONFIG_FILE, 'w+') as f:
with open(CONFIG_FILE, "w+") as f:
yaml.dump(config, f)
@@ -418,17 +426,17 @@ def main():
argparser = argparse.ArgumentParser()
argparser.add_argument(
'--admin', nargs='*', action='append', help='List of usernames set to be admin'
"--admin", nargs="*", action="append", help="List of usernames set to be admin"
)
argparser.add_argument(
'--user-requirements-txt-url',
help='URL to a requirements.txt file that should be installed in the user environment',
"--user-requirements-txt-url",
help="URL to a requirements.txt file that should be installed in the user environment",
)
argparser.add_argument('--plugin', nargs='*', help='Plugin pip-specs to install')
argparser.add_argument("--plugin", nargs="*", help="Plugin pip-specs to install")
argparser.add_argument(
'--progress-page-server-pid',
"--progress-page-server-pid",
type=int,
help='The pid of the progress page server',
help="The pid of the progress page server",
)
args = argparser.parse_args()
@@ -463,5 +471,5 @@ def main():
logger.info("Done!")
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@@ -21,14 +21,14 @@ c.JupyterHub.hub_port = 15001
c.TraefikTomlProxy.should_start = False
dynamic_conf_file_path = os.path.join(INSTALL_PREFIX, 'state', 'rules', 'rules.toml')
dynamic_conf_file_path = os.path.join(INSTALL_PREFIX, "state", "rules", "rules.toml")
c.TraefikTomlProxy.toml_dynamic_config_file = dynamic_conf_file_path
c.JupyterHub.proxy_class = TraefikTomlProxy
c.SystemdSpawner.extra_paths = [os.path.join(USER_ENV_PREFIX, 'bin')]
c.SystemdSpawner.default_shell = '/bin/bash'
c.SystemdSpawner.extra_paths = [os.path.join(USER_ENV_PREFIX, "bin")]
c.SystemdSpawner.default_shell = "/bin/bash"
# Drop the '-singleuser' suffix present in the default template
c.SystemdSpawner.unit_name_template = 'jupyter-{USERNAME}'
c.SystemdSpawner.unit_name_template = "jupyter-{USERNAME}"
tljh_config = configurer.load_config()
configurer.apply_config(tljh_config, c)
@@ -41,6 +41,6 @@ pm.hook.tljh_custom_jupyterhub_config(c=c)
# Load arbitrary .py config files if they exist.
# This is our escape hatch
extra_configs = sorted(glob(os.path.join(CONFIG_DIR, 'jupyterhub_config.d', '*.py')))
extra_configs = sorted(glob(os.path.join(CONFIG_DIR, "jupyterhub_config.d", "*.py")))
for ec in extra_configs:
load_subconfig(ec)

View File

@@ -17,7 +17,7 @@ def generate_system_username(username):
if len(username) < 26:
return username
userhash = hashlib.sha256(username.encode('utf-8')).hexdigest()
return '{username_trunc}-{hash}'.format(
userhash = hashlib.sha256(username.encode("utf-8")).hexdigest()
return "{username_trunc}-{hash}".format(
username_trunc=username[:26], hash=userhash[:5]
)

View File

@@ -13,43 +13,43 @@ def reload_daemon():
Makes systemd discover new units.
"""
subprocess.run(['systemctl', 'daemon-reload'], check=True)
subprocess.run(["systemctl", "daemon-reload"], check=True)
def install_unit(name, unit, path='/etc/systemd/system'):
def install_unit(name, unit, path="/etc/systemd/system"):
"""
Install unit with given name
"""
with open(os.path.join(path, name), 'w') as f:
with open(os.path.join(path, name), "w") as f:
f.write(unit)
def uninstall_unit(name, path='/etc/systemd/system'):
def uninstall_unit(name, path="/etc/systemd/system"):
"""
Uninstall unit with given name
"""
subprocess.run(['rm', os.path.join(path, name)], check=True)
subprocess.run(["rm", os.path.join(path, name)], check=True)
def start_service(name):
"""
Start service with given name.
"""
subprocess.run(['systemctl', 'start', name], check=True)
subprocess.run(["systemctl", "start", name], check=True)
def stop_service(name):
"""
Start service with given name.
"""
subprocess.run(['systemctl', 'stop', name], check=True)
subprocess.run(["systemctl", "stop", name], check=True)
def restart_service(name):
"""
Restart service with given name.
"""
subprocess.run(['systemctl', 'restart', name], check=True)
subprocess.run(["systemctl", "restart", name], check=True)
def enable_service(name):
@@ -58,7 +58,7 @@ def enable_service(name):
This most likely makes the service start on bootup
"""
subprocess.run(['systemctl', 'enable', name], check=True)
subprocess.run(["systemctl", "enable", name], check=True)
def disable_service(name):
@@ -67,7 +67,7 @@ def disable_service(name):
This most likely makes the service start on bootup
"""
subprocess.run(['systemctl', 'disable', name], check=True)
subprocess.run(["systemctl", "disable", name], check=True)
def check_service_active(name):
@@ -75,7 +75,7 @@ def check_service_active(name):
Check if a service is currently active (running)
"""
try:
subprocess.run(['systemctl', 'is-active', name], check=True)
subprocess.run(["systemctl", "is-active", name], check=True)
return True
except subprocess.CalledProcessError:
return False
@@ -86,7 +86,7 @@ def check_service_enabled(name):
Check if a service is enabled
"""
try:
subprocess.run(['systemctl', 'is-enabled', name], check=True)
subprocess.run(["systemctl", "is-enabled", name], check=True)
return True
except subprocess.CalledProcessError:
return False

View File

@@ -15,9 +15,9 @@ from tljh.configurer import load_config, _merge_dictionaries
# traefik 2.7.x is not supported yet, use v1.7.x for now
# see: https://github.com/jupyterhub/traefik-proxy/issues/97
machine = os.uname().machine
if machine == 'aarch64':
if machine == "aarch64":
plat = "linux-arm64"
elif machine == 'x86_64':
elif machine == "x86_64":
plat = "linux-amd64"
else:
raise OSError(f"Error. Platform: {os.uname().sysname} / {machine} Not supported.")
@@ -26,7 +26,7 @@ traefik_version = "1.7.33"
# record sha256 hashes for supported platforms here
checksums = {
"linux-amd64": "314ffeaa4cd8ed6ab7b779e9b6773987819f79b23c28d7ab60ace4d3683c5935",
"linux-arm64": "0640fa665125efa6b598fc08c100178e24de66c5c6035ce5d75668d3dc3706e1"
"linux-arm64": "0640fa665125efa6b598fc08c100178e24de66c5c6035ce5d75668d3dc3706e1",
}
@@ -69,7 +69,7 @@ def ensure_traefik_binary(prefix):
response = requests.get(traefik_url)
if response.status_code == 206:
raise Exception("ContentTooShort")
with open(traefik_bin, 'wb') as f:
with open(traefik_bin, "wb") as f:
f.write(response.content)
os.chmod(traefik_bin, 0o755)
@@ -89,7 +89,7 @@ def compute_basic_auth(username, password):
def load_extra_config(extra_config_dir):
extra_configs = sorted(glob(os.path.join(extra_config_dir, '*.toml')))
extra_configs = sorted(glob(os.path.join(extra_config_dir, "*.toml")))
# Load the toml list of files into dicts and merge them
config = toml.load(extra_configs)
return config
@@ -102,9 +102,9 @@ def ensure_traefik_config(state_dir):
traefik_dynamic_config_dir = os.path.join(state_dir, "rules")
config = load_config()
config['traefik_api']['basic_auth'] = compute_basic_auth(
config['traefik_api']['username'],
config['traefik_api']['password'],
config["traefik_api"]["basic_auth"] = compute_basic_auth(
config["traefik_api"]["username"],
config["traefik_api"]["password"],
)
with open(os.path.join(os.path.dirname(__file__), "traefik.toml.tpl")) as f:

View File

@@ -25,9 +25,9 @@ def ensure_user(username):
# User doesn't exist, time to create!
pass
subprocess.check_call(['useradd', '--create-home', username])
subprocess.check_call(["useradd", "--create-home", username])
subprocess.check_call(['chmod', 'o-rwx', expanduser(f'~{username}')])
subprocess.check_call(["chmod", "o-rwx", expanduser(f"~{username}")])
pm = get_plugin_manager()
pm.hook.tljh_new_user_create(username=username)
@@ -43,14 +43,14 @@ def remove_user(username):
# User doesn't exist, nothing to do
return
subprocess.check_call(['deluser', '--quiet', username])
subprocess.check_call(["deluser", "--quiet", username])
def ensure_group(groupname):
"""
Ensure given group exists
"""
subprocess.check_call(['groupadd', '--force', groupname])
subprocess.check_call(["groupadd", "--force", groupname])
def remove_group(groupname):
@@ -63,7 +63,7 @@ def remove_group(groupname):
# Group doesn't exist, nothing to do
return
subprocess.check_call(['delgroup', '--quiet', groupname])
subprocess.check_call(["delgroup", "--quiet", groupname])
def ensure_user_group(username, groupname):
@@ -76,7 +76,7 @@ def ensure_user_group(username, groupname):
if username in group.gr_mem:
return
subprocess.check_call(['gpasswd', '--add', username, groupname])
subprocess.check_call(["gpasswd", "--add", username, groupname])
def remove_user_group(username, groupname):
@@ -87,4 +87,4 @@ def remove_user_group(username, groupname):
if username not in group.gr_mem:
return
subprocess.check_call(['gpasswd', '--delete', username, groupname])
subprocess.check_call(["gpasswd", "--delete", username, groupname])

View File

@@ -20,16 +20,16 @@ class CustomSpawner(SystemdSpawner):
Perform system user activities before starting server
"""
# FIXME: Move this elsewhere? Into the Authenticator?
system_username = generate_system_username('jupyter-' + self.user.name)
system_username = generate_system_username("jupyter-" + self.user.name)
# FIXME: This is a hack. Allow setting username directly instead
self.username_template = system_username
user.ensure_user(system_username)
user.ensure_user_group(system_username, 'jupyterhub-users')
user.ensure_user_group(system_username, "jupyterhub-users")
if self.user.admin:
user.ensure_user_group(system_username, 'jupyterhub-admins')
user.ensure_user_group(system_username, "jupyterhub-admins")
else:
user.remove_user_group(system_username, 'jupyterhub-admins')
user.remove_user_group(system_username, "jupyterhub-admins")
if self.user_groups:
for group, users in self.user_groups.items():
if self.user.name in users:
@@ -40,11 +40,11 @@ class CustomSpawner(SystemdSpawner):
cfg = configurer.load_config()
# Use the jupyterhub-configurator mixin only if configurator is enabled
# otherwise, any bugs in the configurator backend will stop new user spawns!
if cfg['services']['configurator']['enabled']:
if cfg["services"]["configurator"]["enabled"]:
# Dynamically create the Spawner class using `type`(https://docs.python.org/3/library/functions.html?#type),
# based on whether or not it should inherit from ConfiguratorSpawnerMixin
UserCreatingSpawner = type(
'UserCreatingSpawner', (ConfiguratorSpawnerMixin, CustomSpawner), {}
"UserCreatingSpawner", (ConfiguratorSpawnerMixin, CustomSpawner), {}
)
else:
UserCreatingSpawner = type('UserCreatingSpawner', (CustomSpawner,), {})
UserCreatingSpawner = type("UserCreatingSpawner", (CustomSpawner,), {})

View File

@@ -23,15 +23,15 @@ def run_subprocess(cmd, *args, **kwargs):
In TLJH, this sends successful output to the installer log,
and failed output directly to the user's screen
"""
logger = logging.getLogger('tljh')
logger = logging.getLogger("tljh")
proc = subprocess.run(
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, *args, **kwargs
)
printable_command = ' '.join(cmd)
printable_command = " ".join(cmd)
if proc.returncode != 0:
# Our process failed! Show output to the user
logger.error(
'Ran {command} with exit code {code}'.format(
"Ran {command} with exit code {code}".format(
command=printable_command, code=proc.returncode
)
)
@@ -40,7 +40,7 @@ def run_subprocess(cmd, *args, **kwargs):
else:
# This goes into installer.log
logger.debug(
'Ran {command} with exit code {code}'.format(
"Ran {command} with exit code {code}".format(
command=printable_command, code=proc.returncode
)
)
@@ -54,8 +54,8 @@ def get_plugin_manager():
Return plugin manager instance
"""
# Set up plugin infrastructure
pm = pluggy.PluginManager('tljh')
pm = pluggy.PluginManager("tljh")
pm.add_hookspecs(hooks)
pm.load_setuptools_entrypoints('tljh')
pm.load_setuptools_entrypoints("tljh")
return pm