Prevent creating managed servicesat runtime

This commit is contained in:
Duc Trung LE
2023-05-16 12:02:55 +02:00
parent 9ef5978515
commit 2823c12552
3 changed files with 34 additions and 9 deletions

View File

@@ -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 JupyterHubs REST API. Only externally-managed services can be added at runtime by using JupyterHubs 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**

View File

@@ -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 = [

View File

@@ -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