diff --git a/trustgraph-flow/trustgraph/config/service/config.py b/trustgraph-flow/trustgraph/config/service/config.py index e16245fe..ced4cbe7 100644 --- a/trustgraph-flow/trustgraph/config/service/config.py +++ b/trustgraph-flow/trustgraph/config/service/config.py @@ -11,6 +11,7 @@ logger = logging.getLogger(__name__) WORKSPACES_NAMESPACE = "__workspaces__" WORKSPACE_TYPE = "workspace" +TEMPLATE_WORKSPACE = "__template__" class Configuration: @@ -142,6 +143,35 @@ class Configuration: return ConfigResponse( ) + async def provision_from_template(self, workspace): + """Copy all config from __template__ into a new workspace, + skipping keys that already exist (upsert-missing).""" + + template = await self.get_config(TEMPLATE_WORKSPACE) + + if not template: + logger.info( + f"No template config to provision for {workspace}" + ) + return 0 + + existing_types = await self.get_config(workspace) + + written = 0 + for type_name, entries in template.items(): + existing_keys = set(existing_types.get(type_name, {}).keys()) + for key, value in entries.items(): + if key not in existing_keys: + await self.table_store.put_config( + workspace, type_name, key, value + ) + written += 1 + + if written > 0: + await self.inc_version() + + return written + async def get_config(self, workspace): table = await self.table_store.get_all_for_workspace(workspace) diff --git a/trustgraph-flow/trustgraph/config/service/service.py b/trustgraph-flow/trustgraph/config/service/service.py index 1133a78b..36b368eb 100644 --- a/trustgraph-flow/trustgraph/config/service/service.py +++ b/trustgraph-flow/trustgraph/config/service/service.py @@ -178,12 +178,35 @@ class Processor(AsyncProcessor): if workspace_id not in self.workspace_consumers: logger.info(f"Workspace created: {workspace_id}") await self._add_workspace_consumer(workspace_id) + await self._provision_workspace(workspace_id) for workspace_id in workspace_changes.deleted: if workspace_id in self.workspace_consumers: logger.info(f"Workspace deleted: {workspace_id}") await self._remove_workspace_consumer(workspace_id) + async def _provision_workspace(self, workspace_id): + try: + written = await self.config.provision_from_template( + workspace_id + ) + if written > 0: + logger.info( + f"Provisioned workspace {workspace_id} with " + f"{written} entries from template" + ) + # Notify other services about the new config + types = {} + template = await self.config.get_config(workspace_id) + for t in template: + types[t] = [workspace_id] + await self.push(changes=types) + except Exception as e: + logger.error( + f"Failed to provision workspace {workspace_id}: {e}", + exc_info=True, + ) + async def _add_workspace_consumer(self, workspace_id): queue = workspace_queue( self.config_request_queue_base, workspace_id,