From 92d0c12d1f28e31686fcf367e6139567b78decb4 Mon Sep 17 00:00:00 2001 From: Sujan Aryal Date: Thu, 27 Jan 2022 10:48:40 -0600 Subject: [PATCH 1/4] Update changes to support named servers --- jupyterhub_ssh/__init__.py | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/jupyterhub_ssh/__init__.py b/jupyterhub_ssh/__init__.py index 6415113..3dbd79d 100644 --- a/jupyterhub_ssh/__init__.py +++ b/jupyterhub_ssh/__init__.py @@ -40,15 +40,29 @@ async def get_user_server_url(self, session, username): Else return None """ + dashChar = "-" + serverName = "" + if dashChar in username: + usernameWithServer = username.split(dashChar, 1) + username = usernameWithServer[0] + serverName = usernameWithServer[1] async with session.get(self.app.hub_url / "hub/api/users" / username) as resp: if resp.status != 200: return None user = await resp.json() - print(user) + # # Checking if username has any - which would be the name of the particular server the user wants to connect to + # # For example username would look like admin-mytestserver. Any characters can be used here but chose - just for the sake of it + # # If the server name itself has - then we would need to split only once. Splitting with split(char, #Occurrence) + # if serverName: + # server = user.get("servers/" + serverName, {}).get("", {}) + # if server.get("ready", False): + # return self.app.hub_url / user["servers"][""]["url"][1:] + # else: + # return None # URLs will have preceding slash, but yarl forbids those server = user.get("servers", {}).get("", {}) if server.get("ready", False): - return self.app.hub_url / user["servers"][""]["url"][1:] + return self.app.hub_url / user["servers"][serverName if serverName!="" else ""]["url"][1:] else: return None @@ -56,7 +70,17 @@ async def start_user_server(self, session, username): """ """ # REST API reference: https://jupyterhub.readthedocs.io/en/stable/_static/rest-api/index.html#operation--users--name--server-post # REST API implementation: https://github.com/jupyterhub/jupyterhub/blob/187fe911edce06eb067f736eaf4cc9ea52e69e08/jupyterhub/apihandlers/users.py#L451-L497 - create_url = self.app.hub_url / "hub/api/users" / username / "server" + dashChar = "-" + serverName = "" + if dashChar in username: + usernameWithServer = username.split(dashChar, 1) + username = usernameWithServer[0] + serverName = usernameWithServer[1] + + if serverName: + create_url = self.app.hub_url / "hub/api/users" / username / "servers" / serverName + else: + create_url = self.app.hub_url / "hub/api/users" / username / "server" async with session.post(create_url) as resp: if resp.status == 201 or resp.status == 400: @@ -68,6 +92,8 @@ async def start_user_server(self, session, username): # We manually generate this, even though it's *bad* # Mostly because when the server is already running, JupyterHub # doesn't respond with the whole model! + if serverName: + return self.app.hub_url / "user" / username / serverName return self.app.hub_url / "user" / username elif resp.status == 202: # Server start has been requested, now and potentially earlier, @@ -81,6 +107,10 @@ async def start_user_server(self, session, username): while notebook_url is None: # FIXME: Exponential backoff + make this configurable await asyncio.sleep(0.5) + if serverName: + notebook_url = await self.get_user_server_url( + session, username + "/" + serverName + ) notebook_url = await self.get_user_server_url( session, username ) @@ -152,6 +182,7 @@ async def _handle_client(self, stdin, stdout, stderr): """ Handle data transfer once session has been fully established. """ + print ("Notebook URL looks like: ", self.notebook_url) async with ClientSession() as client, Terminado( self.notebook_url, self.token, client ) as terminado: From 20b5dc58e8b0d26597219106e8790bef56492b41 Mon Sep 17 00:00:00 2001 From: Sujan Aryal Date: Thu, 27 Jan 2022 10:59:03 -0600 Subject: [PATCH 2/4] Readme updates and variable name changes --- README.md | 11 ++++++++++ jupyterhub_ssh/__init__.py | 42 +++++++++++++++++++------------------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index ab6ed1a..9041175 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,17 @@ With a JupyterHub [SSH](https://www.ssh.com/ssh) server deployed, you can start [SFTP](https://www.ssh.com/ssh/sftp) server deployed alongside the JupyterHub's user storage, you can use SFTP to work against your JupyterHub user's home directory. These services are authenticated using an access token acquired from your JupyterHub's user interface under `/hub/token`. +## What does this fork have? +This project a forked project from yuvipanda/jupyterhub_ssh and I was able to directly contribute to create a fork to support one small feature. + +Current jupyterhub-ssh by yuvipanda only supports ssh connection to unnamed server. I added few lines to support named server as well. + +The way it'd work is if you have a named server, you'd simply do `ssh user-namedserver@hub_url` + +It does create the named server automactically if it doesn't exist, which you'll be able to view in the JupyterHub UI as well. + +All the other features remain the same and you can still connect to unnamed server by just `user@hub_url` + ## Development Status This project is under active develpoment :tada:, so expect a few changes along the way. diff --git a/jupyterhub_ssh/__init__.py b/jupyterhub_ssh/__init__.py index 3dbd79d..c56a0d3 100644 --- a/jupyterhub_ssh/__init__.py +++ b/jupyterhub_ssh/__init__.py @@ -40,12 +40,12 @@ async def get_user_server_url(self, session, username): Else return None """ - dashChar = "-" - serverName = "" - if dashChar in username: - usernameWithServer = username.split(dashChar, 1) - username = usernameWithServer[0] - serverName = usernameWithServer[1] + dash_char = "-" + server_name = "" + if dash_char in username: + username_with_server = username.split(dash_char, 1) + username = username_with_server[0] + server_name = username_with_server[1] async with session.get(self.app.hub_url / "hub/api/users" / username) as resp: if resp.status != 200: return None @@ -53,8 +53,8 @@ async def get_user_server_url(self, session, username): # # Checking if username has any - which would be the name of the particular server the user wants to connect to # # For example username would look like admin-mytestserver. Any characters can be used here but chose - just for the sake of it # # If the server name itself has - then we would need to split only once. Splitting with split(char, #Occurrence) - # if serverName: - # server = user.get("servers/" + serverName, {}).get("", {}) + # if server_name: + # server = user.get("servers/" + server_name, {}).get("", {}) # if server.get("ready", False): # return self.app.hub_url / user["servers"][""]["url"][1:] # else: @@ -62,7 +62,7 @@ async def get_user_server_url(self, session, username): # URLs will have preceding slash, but yarl forbids those server = user.get("servers", {}).get("", {}) if server.get("ready", False): - return self.app.hub_url / user["servers"][serverName if serverName!="" else ""]["url"][1:] + return self.app.hub_url / user["servers"][server_name if server_name!="" else ""]["url"][1:] else: return None @@ -70,15 +70,15 @@ async def start_user_server(self, session, username): """ """ # REST API reference: https://jupyterhub.readthedocs.io/en/stable/_static/rest-api/index.html#operation--users--name--server-post # REST API implementation: https://github.com/jupyterhub/jupyterhub/blob/187fe911edce06eb067f736eaf4cc9ea52e69e08/jupyterhub/apihandlers/users.py#L451-L497 - dashChar = "-" - serverName = "" - if dashChar in username: - usernameWithServer = username.split(dashChar, 1) - username = usernameWithServer[0] - serverName = usernameWithServer[1] + dash_char = "-" + server_name = "" + if dash_char in username: + username_with_server = username.split(dash_char, 1) + username = username_with_server[0] + server_name = username_with_server[1] - if serverName: - create_url = self.app.hub_url / "hub/api/users" / username / "servers" / serverName + if server_name: + create_url = self.app.hub_url / "hub/api/users" / username / "servers" / server_name else: create_url = self.app.hub_url / "hub/api/users" / username / "server" @@ -92,8 +92,8 @@ async def start_user_server(self, session, username): # We manually generate this, even though it's *bad* # Mostly because when the server is already running, JupyterHub # doesn't respond with the whole model! - if serverName: - return self.app.hub_url / "user" / username / serverName + if server_name: + return self.app.hub_url / "user" / username / server_name return self.app.hub_url / "user" / username elif resp.status == 202: # Server start has been requested, now and potentially earlier, @@ -107,9 +107,9 @@ async def start_user_server(self, session, username): while notebook_url is None: # FIXME: Exponential backoff + make this configurable await asyncio.sleep(0.5) - if serverName: + if server_name: notebook_url = await self.get_user_server_url( - session, username + "/" + serverName + session, username + "/" + server_name ) notebook_url = await self.get_user_server_url( session, username From 6ad7c5b83d168eee19736477ce2f8740b40e3a27 Mon Sep 17 00:00:00 2001 From: Sujan Aryal Date: Thu, 27 Jan 2022 11:00:37 -0600 Subject: [PATCH 3/4] readme update --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 9041175..2257123 100644 --- a/README.md +++ b/README.md @@ -215,3 +215,8 @@ proxy: 4. Enter the token received from JupyterHub as a password. 5. TADA :tada: Now you can transfer files to and from your home directory on the hubs. + + +# Disclaimer +I do not own this project. Please look at official project at https://github.com/yuvipanda/jupyterhub-ssh +I just added a few lines of code to support named servers. \ No newline at end of file From 548cbf3214a4f75115221972f0fdc04f20f85291 Mon Sep 17 00:00:00 2001 From: Sujan Aryal Date: Wed, 2 Feb 2022 14:54:15 -0600 Subject: [PATCH 4/4] Updated code comment --- jupyterhub_ssh/__init__.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/jupyterhub_ssh/__init__.py b/jupyterhub_ssh/__init__.py index c56a0d3..fe09835 100644 --- a/jupyterhub_ssh/__init__.py +++ b/jupyterhub_ssh/__init__.py @@ -40,6 +40,9 @@ async def get_user_server_url(self, session, username): Else return None """ + # Checking if username has any dash char "-" which would be then have the name of the named server the user wants to connect to + # For example username would look like admin-mytestserver. Any characters can be used here but chose - just for the sake of it + # If the server name itself has "-" then we would need to split only once. Splitting with split(char, #Occurrence) dash_char = "-" server_name = "" if dash_char in username: @@ -50,16 +53,6 @@ async def get_user_server_url(self, session, username): if resp.status != 200: return None user = await resp.json() - # # Checking if username has any - which would be the name of the particular server the user wants to connect to - # # For example username would look like admin-mytestserver. Any characters can be used here but chose - just for the sake of it - # # If the server name itself has - then we would need to split only once. Splitting with split(char, #Occurrence) - # if server_name: - # server = user.get("servers/" + server_name, {}).get("", {}) - # if server.get("ready", False): - # return self.app.hub_url / user["servers"][""]["url"][1:] - # else: - # return None - # URLs will have preceding slash, but yarl forbids those server = user.get("servers", {}).get("", {}) if server.get("ready", False): return self.app.hub_url / user["servers"][server_name if server_name!="" else ""]["url"][1:] @@ -70,7 +63,10 @@ async def start_user_server(self, session, username): """ """ # REST API reference: https://jupyterhub.readthedocs.io/en/stable/_static/rest-api/index.html#operation--users--name--server-post # REST API implementation: https://github.com/jupyterhub/jupyterhub/blob/187fe911edce06eb067f736eaf4cc9ea52e69e08/jupyterhub/apihandlers/users.py#L451-L497 - dash_char = "-" + + # Checking if username has any dash char "-" which would be then have the name of the named server the user wants to connect to + # For example username would look like admin-mytestserver. Any characters can be used here but chose - just for the sake of it + # If the server name itself has "-" then we would need to split only once. Splitting with split(char, #Occurrence)dash_char = "-" server_name = "" if dash_char in username: username_with_server = username.split(dash_char, 1)