mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-14 21:43:01 +00:00
Update service table schema
This commit is contained in:

committed by
Duc Trung LE

parent
95781880c5
commit
bf565ece3b
@@ -35,7 +35,7 @@ def upgrade():
|
|||||||
if 'services' in tables:
|
if 'services' in tables:
|
||||||
op.add_column(
|
op.add_column(
|
||||||
'services',
|
'services',
|
||||||
sa.Column('from_config', sa.Boolean, nullable=True, default=True),
|
sa.Column('from_config', sa.Boolean, default=True),
|
||||||
)
|
)
|
||||||
op.execute('UPDATE services SET from_config = true')
|
op.execute('UPDATE services SET from_config = true')
|
||||||
for item in COL_DATA:
|
for item in COL_DATA:
|
||||||
|
@@ -32,10 +32,12 @@ def access_scopes(oauth_client: orm.OAuthClient, db: Session):
|
|||||||
if spawner:
|
if spawner:
|
||||||
scopes.add(f"access:servers!server={spawner.user.name}/{spawner.name}")
|
scopes.add(f"access:servers!server={spawner.user.name}/{spawner.name}")
|
||||||
else:
|
else:
|
||||||
statement = f"SELECT * FROM services WHERE oauth_client_id = '{oauth_client.identifier}'"
|
statement = "SELECT * FROM services WHERE oauth_client_id = :identifier"
|
||||||
service = db.execute(text(statement)).fetchall()
|
service = db.execute(
|
||||||
|
text(statement), {"identifier": oauth_client.identifier}
|
||||||
|
).fetchall()
|
||||||
if len(service) > 0:
|
if len(service) > 0:
|
||||||
scopes.add(f"access:services!service={service.name}")
|
scopes.add(f"access:services!service={service[0].name}")
|
||||||
|
|
||||||
return frozenset(scopes)
|
return frozenset(scopes)
|
||||||
|
|
||||||
|
@@ -63,9 +63,21 @@ class ServiceAPIHandler(APIHandler):
|
|||||||
note='generated at runtime'
|
note='generated at runtime'
|
||||||
)
|
)
|
||||||
if new_service.managed or new_service.url:
|
if new_service.managed or new_service.url:
|
||||||
self.app.start_service(service_name, new_service)
|
service_status = self.app.start_service(service_name, new_service)
|
||||||
|
if not service_status:
|
||||||
|
self.log.error(
|
||||||
|
'Failed to start service %s',
|
||||||
|
service_name,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
self.app.toggle_service_health_check()
|
self.app.toggle_service_health_check()
|
||||||
|
|
||||||
|
if new_service.oauth_no_confirm:
|
||||||
|
oauth_no_confirm_list = self.settings.get('oauth_no_confirm_list')
|
||||||
|
msg = f"Allowing service {new_service.name} to complete OAuth without confirmation on an authorization web page"
|
||||||
|
self.log.warning(msg)
|
||||||
|
oauth_no_confirm_list.add(new_service.oauth_client_id)
|
||||||
|
|
||||||
return new_service
|
return new_service
|
||||||
|
|
||||||
@needs_scope('admin:services')
|
@needs_scope('admin:services')
|
||||||
@@ -136,6 +148,10 @@ class ServiceAPIHandler(APIHandler):
|
|||||||
if orm_server is not None:
|
if orm_server is not None:
|
||||||
self.db.delete(orm_server)
|
self.db.delete(orm_server)
|
||||||
|
|
||||||
|
if service.oauth_no_confirm:
|
||||||
|
oauth_no_confirm_list = self.settings.get('oauth_no_confirm_list')
|
||||||
|
oauth_no_confirm_list.remove(service.oauth_client_id)
|
||||||
|
|
||||||
self.db.delete(orm_service)
|
self.db.delete(orm_service)
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
|
||||||
|
@@ -2375,7 +2375,7 @@ class JupyterHub(Application):
|
|||||||
)
|
)
|
||||||
traits = service.traits(input=True)
|
traits = service.traits(input=True)
|
||||||
for key in traits:
|
for key in traits:
|
||||||
value = getattr(orm_service, key, None)
|
value = orm_service.get_column(key)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
setattr(service, key, value)
|
setattr(service, key, value)
|
||||||
|
|
||||||
@@ -2448,6 +2448,7 @@ class JupyterHub(Application):
|
|||||||
if key not in traits:
|
if key not in traits:
|
||||||
raise AttributeError("No such service field: %s" % key)
|
raise AttributeError("No such service field: %s" % key)
|
||||||
setattr(service, key, value)
|
setattr(service, key, value)
|
||||||
|
orm_service.update_column(key, value)
|
||||||
|
|
||||||
if service.api_token:
|
if service.api_token:
|
||||||
self.service_tokens[service.api_token] = service.name
|
self.service_tokens[service.api_token] = service.name
|
||||||
@@ -3238,48 +3239,6 @@ class JupyterHub(Application):
|
|||||||
if self._check_services_health_callback is not None:
|
if self._check_services_health_callback is not None:
|
||||||
self._check_services_health_callback.stop()
|
self._check_services_health_callback.stop()
|
||||||
|
|
||||||
async def _start_service(self, service_name, service, ssl_context):
|
|
||||||
for service_name, service in self._service_map.items():
|
|
||||||
msg = f'{service_name} at {service.url}' if service.url else service_name
|
|
||||||
if service.managed:
|
|
||||||
self.log.info("Starting managed service %s", msg)
|
|
||||||
try:
|
|
||||||
await service.start()
|
|
||||||
except Exception as e:
|
|
||||||
self.log.critical(
|
|
||||||
"Failed to start service %s", service_name, exc_info=True
|
|
||||||
)
|
|
||||||
self.exit(1)
|
|
||||||
else:
|
|
||||||
self.log.info("Adding external service %s", msg)
|
|
||||||
|
|
||||||
if service.url:
|
|
||||||
tries = 10 if service.managed else 1
|
|
||||||
for i in range(tries):
|
|
||||||
try:
|
|
||||||
await Server.from_orm(service.orm.server).wait_up(
|
|
||||||
http=True, timeout=1, ssl_context=ssl_context
|
|
||||||
)
|
|
||||||
except AnyTimeoutError:
|
|
||||||
if service.managed:
|
|
||||||
status = await service.spawner.poll()
|
|
||||||
if status is not None:
|
|
||||||
self.log.error(
|
|
||||||
"Service %s exited with status %s",
|
|
||||||
service_name,
|
|
||||||
status,
|
|
||||||
)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
self.log.error(
|
|
||||||
"Cannot connect to %s service %s at %s. Is it running?",
|
|
||||||
service.kind,
|
|
||||||
service_name,
|
|
||||||
service.url,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
"""Start the whole thing"""
|
"""Start the whole thing"""
|
||||||
self.io_loop = loop = IOLoop.current()
|
self.io_loop = loop = IOLoop.current()
|
||||||
@@ -3365,12 +3324,21 @@ class JupyterHub(Application):
|
|||||||
|
|
||||||
# start the service(s)
|
# start the service(s)
|
||||||
for service_name, service in self._service_map.items():
|
for service_name, service in self._service_map.items():
|
||||||
service_status = await self._start_service(
|
service_status = await self.start_service(
|
||||||
service_name, service, ssl_context
|
service_name, service, ssl_context
|
||||||
)
|
)
|
||||||
# if not service_status:
|
if not service_status:
|
||||||
# # Stop the application if a service failed to start.
|
if service.from_config:
|
||||||
# self.exit(1)
|
# Stop the application if a config-based service failed to start.
|
||||||
|
self.exit(1)
|
||||||
|
else:
|
||||||
|
# Only warn for database-based service, so that admin can connect
|
||||||
|
# to hub to remove the service.
|
||||||
|
self.log.error(
|
||||||
|
"Failed to start database-based service %s",
|
||||||
|
service_name,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
|
||||||
await self.proxy.check_routes(self.users, self._service_map)
|
await self.proxy.check_routes(self.users, self._service_map)
|
||||||
|
|
||||||
|
@@ -396,23 +396,23 @@ class Service(Base):
|
|||||||
|
|
||||||
url = Column(Unicode(2047), nullable=True)
|
url = Column(Unicode(2047), nullable=True)
|
||||||
|
|
||||||
oauth_client_allowed_scopes = Column(
|
oauth_client_allowed_scopes = Column(JSONList, nullable=True)
|
||||||
JSONList, nullable=True, default=[]
|
|
||||||
) # List of string
|
|
||||||
|
|
||||||
info = Column(JSONDict, nullable=True, default={}) # Dict
|
info = Column(JSONDict, nullable=True)
|
||||||
|
|
||||||
display = Column(Boolean, default=True, nullable=True)
|
display = Column(Boolean, nullable=True)
|
||||||
|
|
||||||
oauth_no_confirm = Column(Boolean, default=False, nullable=True)
|
oauth_no_confirm = Column(Boolean, nullable=True)
|
||||||
|
|
||||||
command = Column(JSONList, nullable=True, default=[]) # List of string
|
command = Column(JSONList, nullable=True)
|
||||||
|
|
||||||
cwd = Column(Unicode, nullable=True)
|
cwd = Column(Unicode(4095), nullable=True)
|
||||||
|
|
||||||
environment = Column(JSONDict, nullable=True, default={}) # Dict
|
environment = Column(JSONDict, nullable=True)
|
||||||
|
|
||||||
user = Column(Unicode, nullable=True)
|
user = Column(Unicode(255), nullable=True)
|
||||||
|
|
||||||
|
from_config = Column(Boolean, default=True)
|
||||||
|
|
||||||
api_tokens = relationship(
|
api_tokens = relationship(
|
||||||
"APIToken", back_populates="service", cascade="all, delete-orphan"
|
"APIToken", back_populates="service", cascade="all, delete-orphan"
|
||||||
@@ -437,8 +437,6 @@ class Service(Base):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
from_config = Column(Boolean, default=True, nullable=True)
|
|
||||||
|
|
||||||
oauth_client = relationship(
|
oauth_client = relationship(
|
||||||
'OAuthClient',
|
'OAuthClient',
|
||||||
back_populates="service",
|
back_populates="service",
|
||||||
@@ -460,6 +458,43 @@ class Service(Base):
|
|||||||
"""
|
"""
|
||||||
return db.query(cls).filter(cls.name == name).first()
|
return db.query(cls).filter(cls.name == name).first()
|
||||||
|
|
||||||
|
def update_column(self, column_name: str, value: any) -> bool:
|
||||||
|
"""_summary_
|
||||||
|
|
||||||
|
Args:
|
||||||
|
column_name (str): _description_
|
||||||
|
value (any): _description_
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: _description_
|
||||||
|
"""
|
||||||
|
if (
|
||||||
|
hasattr(self, column_name)
|
||||||
|
and not getattr(Service, column_name).foreign_keys
|
||||||
|
):
|
||||||
|
setattr(self, column_name, value)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_column(self, column_name: str):
|
||||||
|
"""_summary_
|
||||||
|
|
||||||
|
Args:
|
||||||
|
column_name (str): _description_
|
||||||
|
value (any): _description_
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: _description_
|
||||||
|
"""
|
||||||
|
if (
|
||||||
|
hasattr(self, column_name)
|
||||||
|
and not getattr(Service, column_name).foreign_keys
|
||||||
|
):
|
||||||
|
return getattr(self, column_name)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Expiring:
|
class Expiring:
|
||||||
"""Mixin for expiring entries
|
"""Mixin for expiring entries
|
||||||
|
Reference in New Issue
Block a user