def test_cli_remove_int(tljh_dir): config.main(["add-item", "foo.bar", "1"]) config.main(["add-item", "foo.bar", "2"]) cfg = configurer.load_config() assert cfg['foo']['bar'] == [1, 2] config.main(["remove-item", "foo.bar", "1"]) cfg = configurer.load_config() assert cfg['foo']['bar'] == [2]
def test_cli_unset(tljh_dir): config.main(["set", "foo.bar", "1"]) config.main(["set", "foo.bar2", "2"]) cfg = configurer.load_config() assert cfg['foo'] == {'bar': 1, 'bar2': 2} config.main(["unset", "foo.bar"]) cfg = configurer.load_config() assert cfg['foo'] == {'bar2': 2}
def test_cli_unset(tljh_dir): config.main(["set", "foo.bar", "1"]) config.main(["set", "foo.bar2", "2"]) cfg = configurer.load_config() assert cfg["foo"] == {"bar": 1, "bar2": 2} config.main(["unset", "foo.bar"]) cfg = configurer.load_config() assert cfg["foo"] == {"bar2": 2}
def ensure_traefik_config(state_dir): """Render the traefik.toml config file""" config = load_config() 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: template = Template(f.read()) new_toml = template.render(config) https = config["https"] letsencrypt = https["letsencrypt"] tls = https["tls"] # validate https config if https["enabled"]: if not tls["cert"] and not letsencrypt["email"]: raise ValueError( "To enable https, you must set tls.cert+key or letsencrypt.email+domains" ) if (letsencrypt["email"] and not letsencrypt["domains"]) or ( letsencrypt["domains"] and not letsencrypt["email"]): raise ValueError( "Both email and domains must be set for letsencrypt") with open(os.path.join(state_dir, "traefik.toml"), "w") as f: os.fchmod(f.fileno(), 0o600) f.write(new_toml) with open(os.path.join(state_dir, "rules.toml"), "w") as f: os.fchmod(f.fileno(), 0o600) # ensure acme.json exists and is private with open(os.path.join(state_dir, "acme.json"), "a") as f: os.fchmod(f.fileno(), 0o600)
def ensure_traefik_config(state_dir): """Render the traefik.toml config file""" traefik_std_config_file = os.path.join(state_dir, "traefik.toml") traefik_extra_config_dir = os.path.join(CONFIG_DIR, "traefik_config.d") 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'], ) with open(os.path.join(os.path.dirname(__file__), "traefik.toml.tpl")) as f: template = Template(f.read()) std_config = template.render(config) https = config["https"] letsencrypt = https["letsencrypt"] tls = https["tls"] # validate https config if https["enabled"]: if not tls["cert"] and not letsencrypt["email"]: raise ValueError( "To enable https, you must set tls.cert+key or letsencrypt.email+domains" ) if (letsencrypt["email"] and not letsencrypt["domains"]) or ( letsencrypt["domains"] and not letsencrypt["email"]): raise ValueError( "Both email and domains must be set for letsencrypt") # Ensure traefik extra static config dir exists and is private os.makedirs(traefik_extra_config_dir, mode=0o700, exist_ok=True) # Ensure traefik dynamic config dir exists and is private os.makedirs(traefik_dynamic_config_dir, mode=0o700, exist_ok=True) try: # Load standard config file merge it with the extra config files into a dict extra_config = load_extra_config(traefik_extra_config_dir) new_toml = _merge_dictionaries(toml.loads(std_config), extra_config) except FileNotFoundError: new_toml = toml.loads(std_config) # Dump the dict into a toml-formatted string and write it to file with open(traefik_std_config_file, "w") as f: os.fchmod(f.fileno(), 0o600) toml.dump(new_toml, f) with open(os.path.join(traefik_dynamic_config_dir, "rules.toml"), "w") as f: os.fchmod(f.fileno(), 0o600) # ensure acme.json exists and is private with open(os.path.join(state_dir, "acme.json"), "a") as f: os.fchmod(f.fileno(), 0o600)
def test_load_secrets(tljh_dir): """ Test loading secret files """ with open(os.path.join(tljh_dir, 'state', 'traefik-api.secret'), 'w') as f: f.write("traefik-password") tljh_config = configurer.load_config() assert tljh_config['traefik_api']['password'] == "traefik-password" c = apply_mock_config(tljh_config) assert c.TraefikTomlProxy.traefik_api_password == "traefik-password"
def tljh_custom_jupyterhub_config(c, tljh_config_file=CONFIG_FILE): # hub c.JupyterHub.cleanup_servers = False c.JupyterHub.authenticator_class = PAMAuthenticator c.JupyterHub.spawner_class = PlasmaSpawner c.JupyterHub.template_paths.insert( 0, os.path.join(os.path.dirname(__file__), "templates") ) # let the spawner infer the user home directory c.PlasmaSpawner.base_volume_path = "" # fetch the list of allowed UNIX groups from the TLJH config tljh_config = load_config(tljh_config_file) include_list = tljh_config.get("plasma", {}).get("groups", []) include_groups = set(include_list) c.JupyterHub.tornado_settings.update({"include_groups": include_groups}) # add an extra handler to handle user group permissions c.JupyterHub.extra_handlers.extend( [ (r"permissions", PermissionsHandler), (r"api/permissions", PermissionsAPIHandler), ( r"permissions-static/(.*)", CacheControlStaticFilesHandler, {"path": os.path.join(os.path.dirname(__file__), "static")}, ), ] ) # spawner # update name template for named servers c.PlasmaSpawner.name_template = "{prefix}-{username}-{servername}" # increase the timeout to be able to pull larger Docker images c.PlasmaSpawner.start_timeout = 120 c.PlasmaSpawner.pull_policy = "Never" c.PlasmaSpawner.remove = True c.PlasmaSpawner.default_url = "/lab" # TODO: change back to jupyterhub-singleuser c.PlasmaSpawner.cmd = ["/srv/conda/envs/notebook/bin/jupyterhub-singleuser"] # set the default cpu and memory limits c.PlasmaSpawner.args = ["--ResourceUseDisplay.track_cpu_percent=True"] # prevent PID 1 running in the Docker container to stop when child processes are killed # see https://github.com/plasmabio/plasma/issues/191 for more info c.PlasmaSpawner.extra_host_config = {'init': True} # register Cockpit as a service if active if check_service_active("cockpit"): c.JupyterHub.services.append( {"name": "cockpit", "url": "http://0.0.0.0:9090",}, )
def test_load_secrets(tljh_dir): """ Test loading secret files """ with open(os.path.join(tljh_dir, "state", "traefik-api.secret"), "w") as f: f.write("traefik-password") tljh_config = configurer.load_config() assert tljh_config["traefik_api"]["password"] == "traefik-password" c = apply_mock_config(tljh_config) assert c.TraefikTomlProxy.traefik_api_password == "traefik-password"
def tljh_custom_jupyterhub_config(c): # hub c.JupyterHub.hub_ip = public_ips()[0] c.JupyterHub.cleanup_servers = False c.JupyterHub.spawner_class = Repo2DockerSpawner # add extra templates for the service UI c.JupyterHub.template_paths.insert( 0, os.path.join(os.path.dirname(__file__), "templates") ) # spawner c.DockerSpawner.cmd = ["jupyterhub-singleuser"] c.DockerSpawner.pull_policy = "Never" c.DockerSpawner.remove = True # fetch limits from the TLJH config tljh_config = load_config() limits = tljh_config["limits"] cpu_limit = limits["cpu"] mem_limit = limits["memory"] c.JupyterHub.tornado_settings.update( {"default_cpu_limit": cpu_limit, "default_mem_limit": mem_limit} ) # register the handlers to manage the user images c.JupyterHub.extra_handlers.extend( [ (r"environments", ImagesHandler), (r"api/environments", BuildHandler), (r"api/environments/([^/]+)/logs", LogsHandler), ( r"environments-static/(.*)", CacheControlStaticFilesHandler, {"path": os.path.join(os.path.dirname(__file__), "static")}, ), ] )
# Use a high port so users can try this on machines with a JupyterHub already present c.JupyterHub.hub_port = 15001 c.TraefikTomlProxy.should_start = False dynamic_conf_file_path = os.path.join(INSTALL_PREFIX, 'state', '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' # Drop the '-singleuser' suffix present in the default template c.SystemdSpawner.unit_name_template = 'jupyter-{USERNAME}' tljh_config = configurer.load_config() configurer.apply_config(tljh_config, c) # Let TLJH hooks modify `c` if they want # Set up plugin infrastructure pm = pluggy.PluginManager('tljh') pm.add_hookspecs(hooks) pm.load_setuptools_entrypoints('tljh') # Call our custom configuration plugin 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')))
""" # FIXME: Move this elsewhere? Into the Authenticator? 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") if self.user.admin: user.ensure_user_group(system_username, "jupyterhub-admins") else: 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: 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), {} ) else: UserCreatingSpawner = type("UserCreatingSpawner", (CustomSpawner,), {})
def test_cli_add_float(tljh_dir): config.main(["add-item", "foo.bar", "1.25"]) cfg = configurer.load_config() assert cfg['foo']['bar'] == [1.25]
def test_cli_set_int(tljh_dir): config.main(["set", "https.port", "123"]) cfg = configurer.load_config() assert cfg['https']['port'] == 123
def test_cli_set_bool(tljh_dir, arg, value): config.main(["set", "https.enabled", arg]) cfg = configurer.load_config() assert cfg['https']['enabled'] == value
""" This file is only used for local development and overrides some of the default values from the plugin. """ import getpass from jupyterhub.auth import DummyAuthenticator from tljh.configurer import apply_config, load_config from tljh_repo2docker import tljh_custom_jupyterhub_config c.JupyterHub.services = [] # set default limits in the TLJH config in memory tljh_config = load_config() tljh_config["limits"]["memory"] = "2G" tljh_config["limits"]["cpu"] = 2 apply_config(tljh_config, c) tljh_custom_jupyterhub_config(c) c.JupyterHub.authenticator_class = DummyAuthenticator user = getpass.getuser() c.Authenticator.admin_users = {user, "alice"}