diff --git a/tests/test_installer.py b/tests/test_installer.py index f845209..d97f01b 100644 --- a/tests/test_installer.py +++ b/tests/test_installer.py @@ -2,12 +2,10 @@ Unit test functions in installer.py """ import os -from datetime import date from tljh import installer - def test_ensure_node(): installer.ensure_node() assert os.path.exists('/usr/bin/node') @@ -19,39 +17,5 @@ def test_ensure_config_yaml(tljh_dir): assert os.path.exists(installer.CONFIG_FILE) assert os.path.isdir(installer.CONFIG_DIR) assert os.path.isdir(os.path.join(installer.CONFIG_DIR, 'jupyterhub_config.d')) - assert not os.path.exists(installer.OLD_CONFIG_FILE) - - # run again, with old config in the way and no new config - upgraded_config = 'old: config\n' - with open(installer.OLD_CONFIG_FILE, 'w') as f: - f.write(upgraded_config) - os.remove(installer.CONFIG_FILE) - installer.ensure_config_yaml(pm) - assert os.path.exists(installer.CONFIG_FILE) - assert not os.path.exists(installer.OLD_CONFIG_FILE) - with open(installer.CONFIG_FILE) as f: - assert f.read() == upgraded_config - - # run again, this time with both old and new config - duplicate_config = 'dupe: config\n' - with open(installer.OLD_CONFIG_FILE, 'w') as f: - f.write(duplicate_config) - installer.ensure_config_yaml(pm) - assert os.path.exists(installer.CONFIG_FILE) - assert not os.path.exists(installer.OLD_CONFIG_FILE) - # didn't clobber config: - with open(installer.CONFIG_FILE) as f: - assert f.read() == upgraded_config - - # preserved old config - backup_config = installer.CONFIG_FILE + f".old.{date.today().isoformat()}" - assert os.path.exists(backup_config) - with open(backup_config) as f: - assert f.read() == duplicate_config - - - - - - - + # verify that old config doesn't exist + assert not os.path.exists(os.path.join(tljh_dir, 'config.yaml')) diff --git a/tests/test_migrator.py b/tests/test_migrator.py new file mode 100644 index 0000000..24e67a8 --- /dev/null +++ b/tests/test_migrator.py @@ -0,0 +1,57 @@ +""" +Unit test functions in installer.py +""" +import os +from datetime import date + +from tljh import migrator, config + + +def test_migrate_config(tljh_dir): + CONFIG_FILE = config.CONFIG_FILE + CONFIG_DIR = config.CONFIG_DIR + OLD_CONFIG_FILE = os.path.join(tljh_dir, "config.yaml") + OLD_CONFIG_D = os.path.join(tljh_dir, "jupyterhub_config.d") + CONFIG_D = os.path.join(config.CONFIG_DIR, "jupyterhub_config.d") + old_config_py = os.path.join(OLD_CONFIG_D, "upgrade.py") + new_config_py = os.path.join(CONFIG_D, "upgrade.py") + + # initial condition: nothing exists + assert not os.path.exists(CONFIG_FILE) + assert not os.path.exists(OLD_CONFIG_FILE) + assert os.path.isdir(CONFIG_DIR) + + # run migration with old config and no new config + upgraded_config = "old: config\n" + with open(OLD_CONFIG_FILE, "w") as f: + f.write(upgraded_config) + os.makedirs(OLD_CONFIG_D, exist_ok=True) + with open(old_config_py, "w") as f: + f.write("c.JupyterHub.log_level = 10") + + migrator.migrate_config_files() + assert os.path.exists(CONFIG_FILE) + assert not os.path.exists(OLD_CONFIG_FILE) + with open(CONFIG_FILE) as f: + assert f.read() == upgraded_config + assert os.path.exists(new_config_py) + assert not os.path.exists(OLD_CONFIG_D) + + # run again, this time with both old and new config + duplicate_config = "dupe: config\n" + with open(OLD_CONFIG_FILE, "w") as f: + f.write(duplicate_config) + migrator.migrate_config_files() + assert os.path.exists(CONFIG_FILE) + assert not os.path.exists(OLD_CONFIG_FILE) + # didn't clobber config: + with open(CONFIG_FILE) as f: + assert f.read() == upgraded_config + + # preserved old config + backup_config = CONFIG_FILE + f".old.{date.today().isoformat()}" + assert os.path.exists(backup_config) + with open(backup_config) as f: + assert f.read() == duplicate_config + + # migrate jupyterhub_con diff --git a/tljh/config.py b/tljh/config.py index 8c3c050..a09bda1 100644 --- a/tljh/config.py +++ b/tljh/config.py @@ -30,9 +30,6 @@ 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') -# deprecated config file location -OLD_CONFIG_FILE = os.path.join(INSTALL_PREFIX, 'config.yaml') - def set_item_in_config(config, property_path, value): """ diff --git a/tljh/installer.py b/tljh/installer.py index d45b51e..2fa8c0c 100644 --- a/tljh/installer.py +++ b/tljh/installer.py @@ -1,12 +1,10 @@ """Installation logic for TLJH""" import argparse -from datetime import date import itertools import logging import os import secrets -import shutil import subprocess import sys import time @@ -16,13 +14,21 @@ from urllib.request import urlopen, URLError import pluggy from ruamel.yaml import YAML -from tljh import conda, systemd, traefik, user, apt, hooks +from tljh import ( + apt, + conda, + hooks, + migrator, + systemd, + traefik, + user, +) + from tljh.config import ( CONFIG_DIR, CONFIG_FILE, HUB_ENV_PREFIX, INSTALL_PREFIX, - OLD_CONFIG_FILE, STATE_DIR, USER_ENV_PREFIX, ) @@ -378,24 +384,7 @@ def ensure_config_yaml(plugin_manager): for path in [CONFIG_DIR, os.path.join(CONFIG_DIR, 'jupyterhub_config.d')]: os.makedirs(path, mode=0o700, exist_ok=True) - # handle old TLJH_DIR/config.yaml location - if os.path.exists(OLD_CONFIG_FILE): - if os.path.exists(CONFIG_FILE): - # new config file already created! still move the config, - # but avoid collision - timestamp = date.today().isoformat() - dest = dest_base = f"{CONFIG_FILE}.old.{timestamp}" - i = 0 - while os.path.exists(dest): - # avoid collisions - dest = dest_base + f".{i}" - i += 1 - logger.warning(f"Found config in both old ({OLD_CONFIG_FILE}) and new ({CONFIG_FILE}).") - logger.warning(f"Moving {OLD_CONFIG_FILE} to {dest} to avoid clobbering. Its contents will be ignored.") - else: - logger.warning(f"Moving old config file to new location {OLD_CONFIG_FILE} -> {CONFIG_FILE}") - dest = CONFIG_FILE - shutil.move(OLD_CONFIG_FILE, dest) + migrator.migrate_config_files() if os.path.exists(CONFIG_FILE): with open(CONFIG_FILE, 'r') as f: diff --git a/tljh/migrator.py b/tljh/migrator.py new file mode 100644 index 0000000..a301c93 --- /dev/null +++ b/tljh/migrator.py @@ -0,0 +1,69 @@ +"""Migration utilities for upgrading tljh""" + +import os +from datetime import date +import logging +import shutil + +from tljh.config import ( + CONFIG_DIR, + CONFIG_FILE, + INSTALL_PREFIX, +) + + +logger = logging.getLogger(__name__) + + +def migrate_file(old_path, new_path): + """Migrate one file from an old location to a new one + + avoids collisions if the new file exists + """ + if not os.path.exists(old_path): + return + if os.path.exists(new_path): + # new config file already created! still move the config, + # but avoid collision + timestamp = date.today().isoformat() + dest = dest_base = f"{new_path}.old.{timestamp}" + i = 0 + while os.path.exists(dest): + # avoid collisions + dest = dest_base + f".{i}" + i += 1 + logger.warning(f"Found file in both old ({old_path}) and new ({new_path}).") + logger.warning( + f"Moving {old_path} to {dest} to avoid clobbering. Its contents will be ignored." + ) + else: + dest = new_path + shutil.move(old_path, dest) + + +def migrate_directory(old_dir, new_dir): + """Migrate a directory to a new location""" + if not os.path.exists(old_dir): + return + if os.path.exists(new_dir): + # both dirs exist + for f in os.listdir(old_dir): + src = os.path.join(old_dir, f) + dest = os.path.join(new_dir, f) + if os.path.isdir(src): + migrate_directory(src, dest) + else: + migrate_file(src, dest) + else: + logger.warning(f"Moving directory to new location {old_dir} -> {new_dir}") + shutil.move(old_dir, new_dir) + + +def migrate_config_files(): + """Migrate config files to their new locations""" + # handle old TLJH_DIR/config.yaml location + migrate_file(os.path.join(INSTALL_PREFIX, "config.yaml"), CONFIG_FILE) + migrate_directory( + os.path.join(INSTALL_PREFIX, "jupyterhub_config.d"), + os.path.join(CONFIG_DIR, "jupyterhub_config.d"), + )