pre-commit: run black without string normalization

This commit is contained in:
Erik Sundell
2021-11-01 09:42:45 +01:00
parent e75de14839
commit 771ae59636
32 changed files with 691 additions and 587 deletions

View File

@@ -25,7 +25,14 @@ def add_source(name, source_url, section):
distro is determined from /etc/os-release
"""
# 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}'], stderr=subprocess.STDOUT).decode().strip()
distro = (
subprocess.check_output(
['/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:
# Write out deb line only if it already doesn't exist
@@ -46,8 +53,4 @@ def install_packages(packages):
env = os.environ.copy()
# Stop apt from asking questions!
env['DEBIAN_FRONTEND'] = 'noninteractive'
utils.run_subprocess([
'apt-get',
'install',
'--yes'
] + packages, env=env)
utils.run_subprocess(['apt-get', 'install', '--yes'] + packages, env=env)

View File

@@ -30,10 +30,14 @@ def check_miniconda_version(prefix, version):
Return true if a miniconda install with version exists at prefix
"""
try:
installed_version = subprocess.check_output([
os.path.join(prefix, 'bin', 'conda'),
'-V'
], stderr=subprocess.STDOUT).decode().strip().split()[1]
installed_version = (
subprocess.check_output(
[os.path.join(prefix, 'bin', 'conda'), '-V'], stderr=subprocess.STDOUT
)
.decode()
.strip()
.split()[1]
)
return V(installed_version) >= V(version)
except (subprocess.CalledProcessError, FileNotFoundError):
# Conda doesn't exist
@@ -71,9 +75,7 @@ def fix_permissions(prefix):
Run after each install command.
"""
utils.run_subprocess(
["chown", "-R", f"{os.getuid()}:{os.getgid()}", prefix]
)
utils.run_subprocess(["chown", "-R", f"{os.getuid()}:{os.getgid()}", prefix])
utils.run_subprocess(["chmod", "-R", "o-w", prefix])
@@ -81,12 +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
@@ -106,21 +103,30 @@ def ensure_conda_packages(prefix, packages):
# Explicitly do *not* capture stderr, since that's not always JSON!
# Scripting conda is a PITA!
# FIXME: raise different exception when using
raw_output = subprocess.check_output(conda_executable + [
'install',
'-c', 'conda-forge', # Make customizable if we ever need to
'--json',
'--prefix', abspath
] + packages).decode()
raw_output = subprocess.check_output(
conda_executable
+ [
'install',
'-c',
'conda-forge', # Make customizable if we ever need to
'--json',
'--prefix',
abspath,
]
+ packages
).decode()
# `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([
l 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"')
])
filtered_output = '\n'.join(
[
l
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"')
]
)
output = json.loads(filtered_output.lstrip('\x00'))
if 'success' in output and output['success'] == True:
return

View File

@@ -247,7 +247,9 @@ def check_hub_ready():
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)
r = requests.get(
'http://127.0.0.1:%d%s/hub/api' % (http_port, base_url), verify=False
)
return r.status_code == 200
except:
return False
@@ -306,13 +308,17 @@ def _is_list(item):
def main(argv=None):
if os.geteuid() != 0:
print("tljh-config needs root privileges to run", file=sys.stderr)
print("Try using sudo before the tljh-config command you wanted to run", file=sys.stderr)
print(
"Try using sudo before the tljh-config command you wanted to run",
file=sys.stderr,
)
sys.exit(1)
if argv is None:
argv = sys.argv[1:]
from .log import init_logging
try:
init_logging()
except Exception as e:
@@ -321,75 +327,48 @@ 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')
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'
)
set_parser.add_argument(
'value',
help='Value to set the configuration key to'
'key_path', help='Dot separated path to configuration key to set'
)
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'
)
add_item_parser.add_argument(
'value',
help='Value to add to the configuration key'
'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')
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'
)
remove_item_parser.add_argument(
'value',
help='Value to remove from key_path'
'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')
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='?'
nargs='?',
)
args = argparser.parse_args(argv)
@@ -397,16 +376,13 @@ def main(argv=None):
if args.action == 'show':
show_config(args.config_path)
elif args.action == 'set':
set_config_value(args.config_path, args.key_path,
parse_value(args.value))
set_config_value(args.config_path, args.key_path, parse_value(args.value))
elif args.action == 'unset':
unset_config_value(args.config_path, args.key_path)
elif args.action == 'add-item':
add_config_value(args.config_path, args.key_path,
parse_value(args.value))
add_config_value(args.config_path, args.key_path, parse_value(args.value))
elif args.action == 'remove-item':
remove_config_value(args.config_path, args.key_path,
parse_value(args.value))
remove_config_value(args.config_path, args.key_path, parse_value(args.value))
elif args.action == 'reload':
reload_component(args.component)
else:

View File

@@ -20,16 +20,9 @@ default = {
'base_url': '/',
'auth': {
'type': 'firstuseauthenticator.FirstUseAuthenticator',
'FirstUseAuthenticator': {
'create_users': False
}
},
'users': {
'allowed': [],
'banned': [],
'admin': [],
'extra_user_groups': {}
'FirstUseAuthenticator': {'create_users': False},
},
'users': {'allowed': [], 'banned': [], 'admin': [], 'extra_user_groups': {}},
'limits': {
'memory': None,
'cpu': None,
@@ -65,12 +58,10 @@ default = {
'every': 60,
'concurrency': 5,
'users': False,
'max_age': 0
'max_age': 0,
},
'configurator': {
'enabled': False
}
}
'configurator': {'enabled': False},
},
}
@@ -189,7 +180,9 @@ def update_auth(c, config):
if not (auth_key[0] == auth_key[0].upper() and isinstance(auth_value, dict)):
if auth_key == 'type':
continue
raise ValueError(f"Error: auth.{auth_key} was ignored, it didn't look like a valid configuration")
raise ValueError(
f"Error: auth.{auth_key} was ignored, it didn't look like a valid configuration"
)
class_name = auth_key
class_config_to_set = auth_value
class_config = c[class_name]
@@ -255,9 +248,7 @@ def set_cull_idle_service(config):
"""
Set Idle Culler service
"""
cull_cmd = [
sys.executable, '-m', 'jupyterhub_idle_culler'
]
cull_cmd = [sys.executable, '-m', 'jupyterhub_idle_culler']
cull_config = config['services']['cull']
print()
@@ -283,8 +274,10 @@ def set_configurator(config):
"""
HERE = os.path.abspath(os.path.dirname(__file__))
configurator_cmd = [
sys.executable, "-m", "jupyterhub_configurator.app",
f"--Configurator.config_file={HERE}/jupyterhub_configurator_config.py"
sys.executable,
"-m",
"jupyterhub_configurator.app",
f"--Configurator.config_file={HERE}/jupyterhub_configurator_config.py",
]
configurator_service = {
'name': 'configurator',

View File

@@ -22,6 +22,7 @@ def tljh_extra_user_pip_packages():
"""
pass
@hookspec
def tljh_extra_hub_pip_packages():
"""
@@ -29,6 +30,7 @@ def tljh_extra_hub_pip_packages():
"""
pass
@hookspec
def tljh_extra_apt_packages():
"""
@@ -38,6 +40,7 @@ def tljh_extra_apt_packages():
"""
pass
@hookspec
def tljh_custom_jupyterhub_config(c):
"""
@@ -48,6 +51,7 @@ def tljh_custom_jupyterhub_config(c):
"""
pass
@hookspec
def tljh_config_post_install(config):
"""
@@ -60,6 +64,7 @@ def tljh_config_post_install(config):
"""
pass
@hookspec
def tljh_post_install():
"""
@@ -70,10 +75,11 @@ def tljh_post_install():
"""
pass
@hookspec
def tljh_new_user_create(username):
"""
Script to be executed after a new user has been added.
This can be arbitrary Python code.
"""
pass
pass

View File

@@ -73,11 +73,10 @@ def ensure_jupyterhub_service(prefix):
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:
traefik_unit_template = f.read()
#Set up proxy / hub secret token if it is not already setup
# Set up proxy / hub secret token if it is not already setup
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:
@@ -103,7 +102,6 @@ def ensure_jupyterhub_service(prefix):
systemd.enable_service('traefik')
def ensure_jupyterhub_package(prefix):
"""
Install JupyterHub into our conda environment if needed.
@@ -117,11 +115,7 @@ 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'
])
apt.install_packages(['libssl-dev', 'libcurl4-openssl-dev', 'build-essential'])
conda.ensure_pip_packages(prefix, ['pycurl==7.*'], upgrade=True)
conda.ensure_pip_packages(
@@ -135,7 +129,7 @@ def ensure_jupyterhub_package(prefix):
"jupyterhub-tmpauthenticator==0.6.*",
"oauthenticator==14.*",
"jupyterhub-idle-culler==1.*",
"git+https://github.com/yuvipanda/jupyterhub-configurator@317759e17c8e48de1b1352b836dac2a230536dba"
"git+https://github.com/yuvipanda/jupyterhub-configurator@317759e17c8e48de1b1352b836dac2a230536dba",
],
upgrade=True,
)
@@ -283,9 +277,9 @@ def ensure_jupyterhub_running(times=20):
# Everything else should immediately abort
raise
except requests.ConnectionError:
# Hub isn't up yet, sleep & loop
time.sleep(1)
continue
# Hub isn't up yet, sleep & loop
time.sleep(1)
continue
except Exception:
# Everything else should immediately abort
raise
@@ -312,7 +306,9 @@ def ensure_symlinks(prefix):
# 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}')
raise FileExistsError(
f'/usr/bin/tljh-config exists but is not a symlink to {tljh_config_src}'
)
else:
# We have a working symlink, so do nothing
return
@@ -343,17 +339,21 @@ def run_plugin_actions(plugin_manager):
# Install apt packages
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)
))
logger.info(
'Installing {} apt packages collected from plugins: {}'.format(
len(apt_packages), ' '.join(apt_packages)
)
)
apt.install_packages(apt_packages)
# Install hub pip packages
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)
))
logger.info(
'Installing {} hub pip packages collected from plugins: {}'.format(
len(hub_pip_packages), ' '.join(hub_pip_packages)
)
)
conda.ensure_pip_packages(
HUB_ENV_PREFIX,
hub_pip_packages,
@@ -363,17 +363,21 @@ def run_plugin_actions(plugin_manager):
# Install conda packages
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)
))
logger.info(
'Installing {} user conda packages collected from plugins: {}'.format(
len(conda_packages), ' '.join(conda_packages)
)
)
conda.ensure_conda_packages(USER_ENV_PREFIX, conda_packages)
# Install pip packages
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)
))
logger.info(
'Installing {} user pip packages collected from plugins: {}'.format(
len(user_pip_packages), ' '.join(user_pip_packages)
)
)
conda.ensure_pip_packages(
USER_ENV_PREFIX,
user_pip_packages,
@@ -409,28 +413,22 @@ def ensure_config_yaml(plugin_manager):
def main():
from .log import init_logging
init_logging()
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'
)
argparser.add_argument(
'--plugin',
nargs='*',
help='Plugin pip-specs to install'
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(
'--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()

View File

@@ -10,7 +10,7 @@ def generate_system_username(username):
If username < 26 char, we just return it.
Else, we hash the username, truncate username at
26 char, append a '-' and first add 5char of hash.
26 char, append a '-' and first add 5char of hash.
This makes sure our usernames are always under 32char.
"""
@@ -19,6 +19,5 @@ def generate_system_username(username):
userhash = hashlib.sha256(username.encode('utf-8')).hexdigest()
return '{username_trunc}-{hash}'.format(
username_trunc=username[:26],
hash=userhash[:5]
)
username_trunc=username[:26], hash=userhash[:5]
)

View File

@@ -13,10 +13,7 @@ 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'):
@@ -31,43 +28,28 @@ 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):
@@ -76,11 +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):
@@ -89,11 +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):
@@ -101,25 +75,18 @@ 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
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

@@ -29,6 +29,7 @@ checksums = {
"linux-arm64": "0640fa665125efa6b598fc08c100178e24de66c5c6035ce5d75668d3dc3706e1"
}
def checksum_file(path):
"""Compute the sha256 checksum of a path"""
hasher = hashlib.sha256()
@@ -37,16 +38,13 @@ def checksum_file(path):
hasher.update(chunk)
return hasher.hexdigest()
def fatal_error(e):
# Retry only when connection is reset or we think we didn't download entire file
return str(e) != "ContentTooShort" and not isinstance(e, ConnectionResetError)
@backoff.on_exception(
backoff.expo,
Exception,
max_tries=2,
giveup=fatal_error
)
@backoff.on_exception(backoff.expo, Exception, max_tries=2, giveup=fatal_error)
def ensure_traefik_binary(prefix):
"""Download and install the traefik binary to a location identified by a prefix path such as '/opt/tljh/hub/'"""
traefik_bin = os.path.join(prefix, "bin", "traefik")
@@ -150,4 +148,3 @@ def ensure_traefik_config(state_dir):
# ensure acme.json exists and is private
with open(os.path.join(state_dir, "acme.json"), "a") as f:
os.fchmod(f.fileno(), 0o600)

View File

@@ -25,17 +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)
@@ -51,22 +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):
@@ -79,11 +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):
@@ -96,12 +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):
@@ -112,9 +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

@@ -5,12 +5,14 @@ from systemdspawner import SystemdSpawner
from traitlets import Dict, Unicode, List
from jupyterhub_configurator.mixins import ConfiguratorSpawnerMixin
class CustomSpawner(SystemdSpawner):
"""
SystemdSpawner with user creation on spawn.
FIXME: Remove this somehow?
"""
user_groups = Dict(key_trait=Unicode(), value_trait=List(Unicode()), config=True)
def start(self):
@@ -34,12 +36,15 @@ class CustomSpawner(SystemdSpawner):
user.ensure_user_group(system_username, group)
return super().start()
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']:
# 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 = type(
'UserCreatingSpawner', (ConfiguratorSpawnerMixin, CustomSpawner), {}
)
else:
UserCreatingSpawner = type('UserCreatingSpawner', (CustomSpawner,), {})

View File

@@ -24,20 +24,26 @@ def run_subprocess(cmd, *args, **kwargs):
and failed output directly to the user's screen
"""
logger = logging.getLogger('tljh')
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, *args, **kwargs)
proc = subprocess.run(
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, *args, **kwargs
)
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(
command=printable_command, code=proc.returncode
))
logger.error(
'Ran {command} with exit code {code}'.format(
command=printable_command, code=proc.returncode
)
)
logger.error(proc.stdout.decode())
raise subprocess.CalledProcessError(cmd=cmd, returncode=proc.returncode)
else:
# This goes into installer.log
logger.debug('Ran {command} with exit code {code}'.format(
command=printable_command, code=proc.returncode
))
logger.debug(
'Ran {command} with exit code {code}'.format(
command=printable_command, code=proc.returncode
)
)
# This produces multi line log output, unfortunately. Not sure how to fix.
# For now, prioritizing human readability over machine readability.
logger.debug(proc.stdout.decode())