mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-18 15:33:02 +00:00
Prevent creating managed servicesat runtime
This commit is contained in:
@@ -176,7 +176,7 @@ In this case, the `url` field will be passed along to the Service as
|
|||||||
|
|
||||||
## Adding or removing services at runtime
|
## Adding or removing services at runtime
|
||||||
|
|
||||||
Both Hub-Managed services and Externally-Managed services can be added at runtime by using JupyterHub’s REST API.
|
Only externally-managed services can be added at runtime by using JupyterHub’s REST API.
|
||||||
|
|
||||||
### Add a new service
|
### Add a new service
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ POST /hub/api/services/:servicename
|
|||||||
|
|
||||||
**Required scope: `admin:services`**
|
**Required scope: `admin:services`**
|
||||||
|
|
||||||
**Payload**: The payload should contain the definition of the service to be created. The endpoint supports the same properties as services defined in the config file.
|
**Payload**: The payload should contain the definition of the service to be created. The endpoint supports the same properties as externally-managed services defined in the config file.
|
||||||
|
|
||||||
**Possible responses**
|
**Possible responses**
|
||||||
|
|
||||||
|
@@ -49,6 +49,11 @@ class ServiceAPIHandler(APIHandler):
|
|||||||
|
|
||||||
self._check_service_model(spec)
|
self._check_service_model(spec)
|
||||||
service_name = spec["name"]
|
service_name = spec["name"]
|
||||||
|
managed = bool(spec.get('command'))
|
||||||
|
if managed:
|
||||||
|
msg = f"Can not create managed service {service_name} at runtime"
|
||||||
|
self.log.error(msg, exc_info=True)
|
||||||
|
raise web.HTTPError(400, msg)
|
||||||
try:
|
try:
|
||||||
new_service = self.service_from_spec(spec)
|
new_service = self.service_from_spec(spec)
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -64,12 +69,9 @@ class ServiceAPIHandler(APIHandler):
|
|||||||
await self.app._add_tokens(
|
await self.app._add_tokens(
|
||||||
{new_service.api_token: new_service.name}, kind='service'
|
{new_service.api_token: new_service.name}, kind='service'
|
||||||
)
|
)
|
||||||
elif new_service.managed:
|
if new_service.url:
|
||||||
new_service.api_token = new_service.orm.new_api_token(
|
# Start polling for external service
|
||||||
note='generated at runtime'
|
service_status = await self.app.start_service(service_name, new_service)
|
||||||
)
|
|
||||||
if new_service.managed or new_service.url:
|
|
||||||
service_status = self.app.start_service(service_name, new_service)
|
|
||||||
if not service_status:
|
if not service_status:
|
||||||
self.log.error(
|
self.log.error(
|
||||||
'Failed to start service %s',
|
'Failed to start service %s',
|
||||||
@@ -116,6 +118,8 @@ class ServiceAPIHandler(APIHandler):
|
|||||||
try:
|
try:
|
||||||
await self.remove_service(service, orm_service)
|
await self.remove_service(service, orm_service)
|
||||||
self.services.pop(service_name)
|
self.services.pop(service_name)
|
||||||
|
if service.url:
|
||||||
|
self.app.toggle_service_health_check()
|
||||||
except Exception:
|
except Exception:
|
||||||
msg = f"Failed to remove service {service_name}"
|
msg = f"Failed to remove service {service_name}"
|
||||||
self.log.error(msg, exc_info=True)
|
self.log.error(msg, exc_info=True)
|
||||||
@@ -178,7 +182,7 @@ class ServiceAPIHandler(APIHandler):
|
|||||||
service = self.services.get(name)
|
service = self.services.get(name)
|
||||||
return service, orm_service
|
return service, orm_service
|
||||||
|
|
||||||
return None, None
|
return (None, None)
|
||||||
|
|
||||||
|
|
||||||
default_handlers = [
|
default_handlers = [
|
||||||
|
@@ -4,6 +4,7 @@ import json
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
|
from copy import deepcopy
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
from urllib.parse import quote, urlparse
|
from urllib.parse import quote, urlparse
|
||||||
@@ -2177,6 +2178,26 @@ async def test_create_service_duplication(app, service_admin_user, service_data)
|
|||||||
assert r.status_code == 409
|
assert r.status_code == 409
|
||||||
|
|
||||||
|
|
||||||
|
@mark.services
|
||||||
|
async def test_create_managed_service(app, service_admin_user, service_data):
|
||||||
|
db = app.db
|
||||||
|
service_name = 'managed-service-from-api'
|
||||||
|
managed_service_data = deepcopy(service_data)
|
||||||
|
managed_service_data['command'] = ['foo']
|
||||||
|
r = await api_request(
|
||||||
|
app,
|
||||||
|
f'services/{service_name}',
|
||||||
|
headers=auth_header(db, service_admin_user.name),
|
||||||
|
data=json.dumps(managed_service_data),
|
||||||
|
method='post',
|
||||||
|
)
|
||||||
|
|
||||||
|
assert r.status_code == 400
|
||||||
|
assert 'Can not create managed service' in r.json()['message']
|
||||||
|
orm_service = orm.Service.find(db, service_name)
|
||||||
|
assert orm_service is None
|
||||||
|
|
||||||
|
|
||||||
@mark.services
|
@mark.services
|
||||||
async def test_remove_service(app, service_admin_user, service_data):
|
async def test_remove_service(app, service_admin_user, service_data):
|
||||||
db = app.db
|
db = app.db
|
||||||
|
Reference in New Issue
Block a user