Update service table schema

This commit is contained in:
Duc Trung Le
2023-03-09 13:15:53 +01:00
committed by Duc Trung LE
parent 95781880c5
commit bf565ece3b
5 changed files with 85 additions and 64 deletions

View File

@@ -35,7 +35,7 @@ def upgrade():
if 'services' in tables:
op.add_column(
'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')
for item in COL_DATA:

View File

@@ -32,10 +32,12 @@ def access_scopes(oauth_client: orm.OAuthClient, db: Session):
if spawner:
scopes.add(f"access:servers!server={spawner.user.name}/{spawner.name}")
else:
statement = f"SELECT * FROM services WHERE oauth_client_id = '{oauth_client.identifier}'"
service = db.execute(text(statement)).fetchall()
statement = "SELECT * FROM services WHERE oauth_client_id = :identifier"
service = db.execute(
text(statement), {"identifier": oauth_client.identifier}
).fetchall()
if len(service) > 0:
scopes.add(f"access:services!service={service.name}")
scopes.add(f"access:services!service={service[0].name}")
return frozenset(scopes)

View File

@@ -63,9 +63,21 @@ class ServiceAPIHandler(APIHandler):
note='generated at runtime'
)
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()
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
@needs_scope('admin:services')
@@ -136,6 +148,10 @@ class ServiceAPIHandler(APIHandler):
if orm_server is not None:
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.commit()

View File

@@ -2375,7 +2375,7 @@ class JupyterHub(Application):
)
traits = service.traits(input=True)
for key in traits:
value = getattr(orm_service, key, None)
value = orm_service.get_column(key)
if value is not None:
setattr(service, key, value)
@@ -2448,6 +2448,7 @@ class JupyterHub(Application):
if key not in traits:
raise AttributeError("No such service field: %s" % key)
setattr(service, key, value)
orm_service.update_column(key, value)
if service.api_token:
self.service_tokens[service.api_token] = service.name
@@ -3238,48 +3239,6 @@ class JupyterHub(Application):
if self._check_services_health_callback is not None:
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):
"""Start the whole thing"""
self.io_loop = loop = IOLoop.current()
@@ -3365,12 +3324,21 @@ class JupyterHub(Application):
# start the service(s)
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
)
# if not service_status:
# # Stop the application if a service failed to start.
# self.exit(1)
if not service_status:
if service.from_config:
# 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)

View File

@@ -396,23 +396,23 @@ class Service(Base):
url = Column(Unicode(2047), nullable=True)
oauth_client_allowed_scopes = Column(
JSONList, nullable=True, default=[]
) # List of string
oauth_client_allowed_scopes = Column(JSONList, nullable=True)
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(
"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(
'OAuthClient',
back_populates="service",
@@ -460,6 +458,43 @@ class Service(Base):
"""
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:
"""Mixin for expiring entries