11import os
22import re
3+ import tempfile
34import yaml
45import aiodocker
56import logging
67from datetime import datetime , timezone
7- from sqlalchemy import select
8+ from sqlalchemy import or_ , select
89from sqlalchemy .ext .asyncio import AsyncSession
910from redis .asyncio import Redis
1011from arq .connections import ArqRedis
@@ -247,17 +248,28 @@ async def update_traefik_config(
247248 project : Project ,
248249 db : AsyncSession ,
249250 settings : Settings ,
251+ * ,
252+ include_deployment_ids : set [str ] | None = None ,
250253 ) -> None :
251254 """Update Traefik config for a project including domains."""
252255 path = os .path .join (settings .traefik_dir , f"project_{ project .id } .yml" )
253256
254257 # Get aliases
258+ include_ids = include_deployment_ids or set ()
259+ if include_ids :
260+ where_clause = or_ (
261+ Deployment .conclusion == "succeeded" ,
262+ Deployment .id .in_ (list (include_ids )),
263+ )
264+ else :
265+ where_clause = Deployment .conclusion == "succeeded"
266+
255267 result = await db .execute (
256268 select (Alias )
257269 .join (Deployment , Alias .deployment_id == Deployment .id )
258270 .filter (
259271 Deployment .project_id == project .id ,
260- Deployment . conclusion == "succeeded" ,
272+ where_clause ,
261273 )
262274 )
263275 aliases = result .scalars ().all ()
@@ -339,6 +351,12 @@ async def update_traefik_config(
339351 }
340352 }
341353
354+ # If there is nothing to configure, remove any stale config file.
355+ if not routers and not services and not middlewares :
356+ if os .path .exists (path ):
357+ os .remove (path )
358+ return
359+
342360 # Write config
343361 os .makedirs (settings .traefik_dir , exist_ok = True )
344362 config = {"http" : {"routers" : routers }}
@@ -347,8 +365,20 @@ async def update_traefik_config(
347365 if middlewares :
348366 config ["http" ]["middlewares" ] = middlewares
349367
350- with open (path , "w" ) as f :
351- yaml .dump (config , f , sort_keys = False , indent = 2 )
368+ # Write atomically so Traefik's file watcher never reads a partially-written YAML.
369+ fd , tmp_path = tempfile .mkstemp (
370+ prefix = f".{ os .path .basename (path )} ." , dir = settings .traefik_dir
371+ )
372+ try :
373+ with os .fdopen (fd , "w" ) as f :
374+ yaml .safe_dump (config , f , sort_keys = False , indent = 2 )
375+ os .replace (tmp_path , path )
376+ finally :
377+ try :
378+ if os .path .exists (tmp_path ):
379+ os .remove (tmp_path )
380+ except OSError :
381+ pass
352382
353383 async def create (
354384 self ,
0 commit comments