Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/).

## 1.5.1

### Security

- Bumped manager dependencies to resolve High-severity advisories: `cryptography` (46.0.7 → 50.0.0), `pyjwt` (2.12.1 → 2.13.0), and `python-multipart` (0.0.26 → 0.0.27).

### Bugfixes

- The logs viewer no longer goes blank over time. The manager reused a single Docker log client whose connections leaked whenever a live-follow log stream was closed, so eventually every log request would hang and the page rendered empty. Each log request now uses its own client, released when the stream ends.

## 1.5.0

### New Features
Expand Down
92 changes: 49 additions & 43 deletions manager/runtime_connectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,6 @@ async def service_logs(self, service, tail, follow, since):

class LocalDockerConnector(ConnectorBase):
CLIENT = docker.from_env()
_AIO_CLIENT = None

@classmethod
def _get_aio_client(cls):
if cls._AIO_CLIENT is None:
cls._AIO_CLIENT = aiodocker.Docker()
return cls._AIO_CLIENT

def __init__(self):
# Create our own docker network
Expand Down Expand Up @@ -401,27 +394,35 @@ async def service_logs(
AsyncGenerator[str, None]: Async infinite generator if following,
else async finite generator.
"""
container = await self._get_aio_client().containers.get(
create_container_name(service.name, service.version)
)

kwargs = {
"tail": tail if tail else "all",
"stdout": True,
"stderr": True,
"follow": follow,
}
# Use a fresh aiodocker client per request and close it in `finally`, so
# the underlying connection is always released -- including when the
# browser abandons a follow=True stream (closing the logs page). A
# long-lived cached client leaked those connections until its pool
# exhausted and every log request hung (empty logs page).
client = aiodocker.Docker()
try:
container = await client.containers.get(
create_container_name(service.name, service.version)
)

if since:
kwargs["since"] = datetime_to_timestamp(since)
logs = container.log(**kwargs)
kwargs = {
"tail": tail if tail else "all",
"stdout": True,
"stderr": True,
"follow": follow,
}
if since:
kwargs["since"] = datetime_to_timestamp(since)
logs = container.log(**kwargs)

if follow:
async for log in logs:
yield log
else:
for log in await logs:
yield log
if follow:
async for log in logs:
yield log
else:
for log in await logs:
yield log
finally:
await client.close()

def manager_logs(self, since: datetime) -> str:
"""Get the manager logs from a certain time
Expand Down Expand Up @@ -453,24 +454,29 @@ async def manager_logs_stream(
else async finite generator.
"""
manager = self._get_manager_container()
container = await self._get_aio_client().containers.get(manager.id)
# Fresh client per request, closed in `finally` (see service_logs for why).
client = aiodocker.Docker()
try:
container = await client.containers.get(manager.id)

kwargs = {
"tail": tail if tail else "all",
"stdout": True,
"stderr": True,
"follow": follow,
}
if since:
kwargs["since"] = datetime_to_timestamp(since)
logs = container.log(**kwargs)

if follow:
async for log in logs:
yield log
else:
for log in await logs:
yield log
kwargs = {
"tail": tail if tail else "all",
"stdout": True,
"stderr": True,
"follow": follow,
}
if since:
kwargs["since"] = datetime_to_timestamp(since)
logs = container.log(**kwargs)

if follow:
async for log in logs:
yield log
else:
for log in await logs:
yield log
finally:
await client.close()


RTE_CONN = LocalDockerConnector()
6 changes: 3 additions & 3 deletions requirements_manager.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ uvicorn==0.44.0
docker==7.1.0
aiodocker==0.26.0
semver==3.0.4
python-multipart==0.0.26
python-multipart==0.0.27
toml==0.10.2
sqlalchemy==2.0.49
dash==4.1.0
pyjwt==2.12.1
pyjwt==2.13.0
bcrypt==5.0.0
jinja2==3.1.6
cookiecutter==2.7.1
cryptography==46.0.7
cryptography==50.0.0
Loading