Fixed some issues

This commit is contained in:
GeorgianaElena
2019-02-11 09:24:16 +02:00
parent eee29a0957
commit 3ee387cd3b
10 changed files with 89 additions and 47 deletions

View File

@@ -1,3 +1,4 @@
pytest
pytest-asyncio
passlib
git+https://github.com/yuvipanda/hubtraf.git

View File

@@ -53,8 +53,13 @@ def test_manual_https(preserve_config):
# verify that our certificate was loaded by traefik
assert server_cert == file_cert
# verify that we can still connect to the hub
r = requests.get("https://127.0.0.1/hub/api", verify=False)
for i in range(5):
time.sleep(i)
# verify that we can still connect to the hub
r = requests.get("https://127.0.0.1/hub/api", verify=False)
if r.status_code == 200:
break;
r.raise_for_status()
# cleanup

View File

@@ -119,16 +119,19 @@ def test_remove_from_config_error():
def test_reload_hub():
with mock.patch('tljh.systemd.restart_service') as restart_service, mock.patch(
'tljh.systemd.check_service_active'
) as check_active, mock.patch('tljh.systemd.check_hub_ready') as check_ready:
) as check_active, mock.patch('tljh.config.check_hub_ready') as check_ready:
config.reload_component('hub')
assert restart_service.called_with('jupyterhub')
assert check_active.called_with('jupyterhub')
def test_reload_proxy(tljh_dir):
with mock.patch('tljh.systemd.restart_service') as restart_service:
with mock.patch('tljh.systemd.restart_service') as restart_service, mock.patch(
'tljh.systemd.check_service_active'
) as check_active:
config.reload_component('proxy')
assert restart_service.called_with('traefik')
assert check_active.called_with('traefik')
assert os.path.exists(os.path.join(config.STATE_DIR, 'traefik.toml'))

View File

@@ -159,3 +159,27 @@ def test_auth_github():
assert c.JupyterHub.authenticator_class == 'oauthenticator.github.GitHubOAuthenticator'
assert c.GitHubOAuthenticator.client_id == 'something'
assert c.GitHubOAuthenticator.client_secret == 'something-else'
def test_auth_api_default():
"""
Test default traefik api authentication settings with no overrides
"""
c = apply_mock_config({})
assert c.TraefikTomlProxy.traefik_api_username == 'api_admin'
assert c.TraefikTomlProxy.traefik_api_password == 'admin'
def test_set_auth_api():
"""
Test setting per traefik api credentials
"""
c = apply_mock_config({
'auth_api': {
'username': 'some_user',
'password': '1234'
}
})
assert c.TraefikTomlProxy.traefik_api_username == 'some_user'
assert c.TraefikTomlProxy.traefik_api_password == '1234'

View File

@@ -173,6 +173,14 @@ def remove_config_value(config_path, key_path, value):
with open(config_path, 'w') as f:
yaml.dump(config, f)
def check_hub_ready():
import requests
try:
r = requests.get('http://127.0.0.1:80')
return r.status_code == 200
except:
return False
def reload_component(component):
"""
@@ -182,17 +190,21 @@ def reload_component(component):
"""
# import here to avoid circular imports
from tljh import systemd, traefik
import time
if component == 'hub':
systemd.restart_service('jupyterhub')
# Ensure hub is back up
while not systemd.check_service_active('jupyterhub'):
asyncio.sleep(1)
while not systemd.check_hub_ready():
asyncio.sleep(1)
time.sleep(1)
while not check_hub_ready():
time.sleep(1)
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'):
time.sleep(1)
print('Proxy reload with new configuration complete')

View File

@@ -46,12 +46,18 @@ default = {
'domains': [],
},
},
'auth_api': {
'ip': "127.0.0.1",
'port': 8099,
'username': 'api_admin',
'password': 'admin',
'basic_auth': ''
},
'user_environment': {
'default_app': 'classic',
},
}
def load_config(config_file=CONFIG_FILE):
"""Load the current config as a dictionary
@@ -62,6 +68,8 @@ def load_config(config_file=CONFIG_FILE):
config_overrides = yaml.load(f)
else:
config_overrides = {}
generate_traefik_api_credentials()
return _merge_dictionaries(dict(default), config_overrides)
@@ -76,6 +84,7 @@ def apply_config(config_overrides, c):
update_limits(c, tljh_config)
update_user_environment(c, tljh_config)
update_user_account_config(c, tljh_config)
update_auth_api(c, tljh_config)
def set_if_not_none(parent, key, value):
@@ -85,6 +94,14 @@ def set_if_not_none(parent, key, value):
if value is not None:
setattr(parent, key, value)
def generate_traefik_api_credentials():
from passlib.apache import HtpasswdFile
ht = HtpasswdFile()
ht.set_password(default['auth_api']['username'], default['auth_api']['password'])
traefik_api_hashed_password = str(ht.to_string()).split(":")[1][:-3]
default['auth_api']['basic_auth'] = default['auth_api']['username'] + ":" + traefik_api_hashed_password
def update_auth(c, config):
"""
@@ -149,6 +166,14 @@ def update_user_account_config(c, config):
c.SystemdSpawner.username_template = 'jupyter-{USERNAME}'
def update_auth_api(c, config):
"""
Set traefik api endpoint credentials
"""
c.TraefikTomlProxy.traefik_api_username = config['auth_api']['username']
c.TraefikTomlProxy.traefik_api_password = config['auth_api']['password']
def _merge_dictionaries(a, b, path=None, update=True):
"""
Merge two dictionaries recursively.

View File

@@ -44,9 +44,9 @@ c.JupyterHub.cleanup_servers = False
c.JupyterHub.hub_port = 15001
c.TraefikTomlProxy.should_start = False
c.TraefikTomlProxy.traefik_api_password = "admin"
c.TraefikTomlProxy.traefik_api_username = "api_admin"
c.TraefikTomlProxy.toml_dynamic_config_file = "/opt/tljh/state/rules.toml"
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')]

View File

@@ -85,33 +85,3 @@ def check_service_active(name):
return True
except subprocess.CalledProcessError:
return False
def check_hub_ready():
"""
Check if the hub is ready
"""
try:
last_restart = subprocess.check_output([
'systemctl',
'show',
'jupyterhub',
'-p',
'ActiveEnterTimestamp'
]).decode().strip()
last_restart = " ".join(last_restart.split(" ")[-3:-1])
out = subprocess.check_output([
'journalctl',
'-u',
'jupyterhub',
'--since',
last_restart
])
if "JupyterHub is now running at" in out.decode():
return True
except subprocess.CalledProcessError:
return False

View File

@@ -75,11 +75,11 @@ def ensure_traefik_config(state_dir):
):
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(), 0o744)
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(), 0o744)
os.fchmod(f.fileno(), 0o600)
# ensure acme.json exists and is private
with open(os.path.join(state_dir, "acme.json"), "a") as f:

View File

@@ -41,9 +41,11 @@ idleTimeout = "10m0s"
{% endif %}
{% endif %}
[entryPoints.auth_api]
address = ":8099"
address = ":{{auth_api['port']}}"
[entryPoints.auth_api.whiteList]
sourceRange = ['{{auth_api['ip']}}']
[entryPoints.auth_api.auth.basic]
users = ["api_admin:$apr1$eS/j3kum$q/X2khsIEG/bBGsteP.x./"]
users = ['{{auth_api['basic_auth']}}']
[wss]
protocol = "http"
@@ -67,5 +69,5 @@ entryPoint = "https"
{% endif %}
[file]
filename = "/opt/tljh/state/rules.toml"
filename = "rules.toml"
watch = true