Skip to content

Commit faaf938

Browse files
author
Andrei Neagu
committed
using new docker client
1 parent e71c113 commit faaf938

File tree

7 files changed

+184
-56
lines changed

7 files changed

+184
-56
lines changed

packages/service-library/src/servicelib/docker_utils.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
2-
from collections.abc import Awaitable, Callable
3-
from contextlib import AsyncExitStack
2+
from collections.abc import AsyncIterator, Awaitable, Callable
3+
from contextlib import AsyncExitStack, asynccontextmanager
44
from dataclasses import dataclass
55
from datetime import datetime
66
from functools import cached_property
@@ -10,6 +10,7 @@
1010
import aiohttp
1111
import arrow
1212
from aiohttp import ClientSession
13+
from fastapi import FastAPI
1314
from models_library.docker import DockerGenericTag
1415
from models_library.generated_models.docker_rest_api import ProgressDetail
1516
from models_library.utils.change_case import snake_to_camel
@@ -286,14 +287,15 @@ async def pull_image(
286287
)
287288

288289

289-
async def create_remote_docker_client(
290-
settings: DockerApiProxysettings,
291-
) -> aiodocker.Docker:
292-
"""Allows to attach to a remote docker client istnace and use it in place
293-
of the docker socket.
290+
@asynccontextmanager
291+
async def lifespan_remote_docker_client(app: FastAPI) -> AsyncIterator[None]:
292+
"""Ensures `setup` and `teardown` for the docker client.
294293
295-
NOTE: Usually this is required to use a manager node's docker client for swarm commands.
294+
NOTE: the `DockerApiProxysettings` must be placed as `DOCKER_API_PROXY`
295+
on the Application's Settings object
296296
"""
297+
settings: DockerApiProxysettings = app.state.settings.DOCKER_API_PROXY
298+
297299
session: ClientSession | None = None
298300
if settings.DOCKER_API_PROXY_USER and settings.DOCKER_API_PROXY_PASSWORD:
299301
session = ClientSession(
@@ -303,4 +305,26 @@ async def create_remote_docker_client(
303305
)
304306
)
305307

306-
return aiodocker.Docker(url=settings.base_url, session=session)
308+
async with AsyncExitStack() as exit_stack:
309+
if settings.DOCKER_API_PROXY_USER and settings.DOCKER_API_PROXY_PASSWORD:
310+
await exit_stack.enter_async_context(
311+
ClientSession(
312+
auth=aiohttp.BasicAuth(
313+
login=settings.DOCKER_API_PROXY_USER,
314+
password=settings.DOCKER_API_PROXY_PASSWORD.get_secret_value(),
315+
)
316+
)
317+
)
318+
319+
client = await exit_stack.enter_async_context(
320+
aiodocker.Docker(url=settings.base_url, session=session)
321+
)
322+
323+
app.state.remote_docker_client = client
324+
325+
yield
326+
327+
328+
def get_remote_docker_client(app: FastAPI) -> aiodocker.Docker:
329+
client: aiodocker.Docker = app.state.remote_docker_client
330+
return client

services/docker-api-proxy/requirements/_test.in

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
--requirement ../../../packages/service-library/requirements/_base.in
77

88
aiodocker
9+
arrow
10+
asgi_lifespan
911
docker
1012
faker
13+
fastapi
1114
flaky
1215
pytest
1316
pytest-asyncio
@@ -16,4 +19,3 @@ pytest-mock
1619
python-dotenv
1720
PyYAML
1821
tenacity
19-
arrow

services/docker-api-proxy/requirements/_test.txt

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,16 @@ anyio==4.8.0
3737
# via
3838
# fast-depends
3939
# faststream
40+
# starlette
4041
arrow==1.3.0
4142
# via
4243
# -r requirements/../../../packages/models-library/requirements/_base.in
4344
# -r requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in
4445
# -r requirements/../../../packages/service-library/requirements/_base.in
4546
# -r requirements/_test.in
46-
attrs==24.3.0
47+
asgi-lifespan==2.1.0
48+
# via -r requirements/_test.in
49+
attrs==25.1.0
4750
# via
4851
# aiohttp
4952
# jsonschema
@@ -69,7 +72,7 @@ click==8.1.8
6972
# via typer
7073
coverage==7.6.10
7174
# via pytest-cov
72-
deprecated==1.2.15
75+
deprecated==1.2.18
7376
# via
7477
# opentelemetry-api
7578
# opentelemetry-exporter-otlp-proto-grpc
@@ -87,6 +90,8 @@ faker==35.0.0
8790
# via -r requirements/_test.in
8891
fast-depends==2.4.12
8992
# via faststream
93+
fastapi==0.115.7
94+
# via -r requirements/_test.in
9095
faststream==0.5.34
9196
# via -r requirements/../../../packages/service-library/requirements/_base.in
9297
flaky==3.8.1
@@ -99,7 +104,7 @@ googleapis-common-protos==1.66.0
99104
# via
100105
# opentelemetry-exporter-otlp-proto-grpc
101106
# opentelemetry-exporter-otlp-proto-http
102-
grpcio==1.69.0
107+
grpcio==1.70.0
103108
# via opentelemetry-exporter-otlp-proto-grpc
104109
idna==3.10
105110
# via
@@ -215,7 +220,7 @@ protobuf==5.29.3
215220
# opentelemetry-proto
216221
psutil==6.1.1
217222
# via -r requirements/../../../packages/service-library/requirements/_base.in
218-
pydantic==2.10.5
223+
pydantic==2.10.6
219224
# via
220225
# -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt
221226
# -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt
@@ -241,6 +246,7 @@ pydantic==2.10.5
241246
# -r requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/_base.in
242247
# -r requirements/../../../packages/settings-library/requirements/_base.in
243248
# fast-depends
249+
# fastapi
244250
# pydantic-extra-types
245251
# pydantic-settings
246252
pydantic-core==2.27.2
@@ -329,8 +335,20 @@ redis==5.2.1
329335
# -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt
330336
# -c requirements/../../../requirements/constraints.txt
331337
# -r requirements/../../../packages/service-library/requirements/_base.in
332-
referencing==0.36.1
338+
referencing==0.35.1
333339
# via
340+
# -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt
341+
# -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt
342+
# -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt
343+
# -c requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt
344+
# -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt
345+
# -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt
346+
# -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt
347+
# -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt
348+
# -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt
349+
# -c requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt
350+
# -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt
351+
# -c requirements/../../../requirements/constraints.txt
334352
# jsonschema
335353
# jsonschema-specifications
336354
requests==2.32.3
@@ -351,7 +369,24 @@ shellingham==1.5.4
351369
six==1.17.0
352370
# via python-dateutil
353371
sniffio==1.3.1
354-
# via anyio
372+
# via
373+
# anyio
374+
# asgi-lifespan
375+
starlette==0.45.3
376+
# via
377+
# -c requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt
378+
# -c requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt
379+
# -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt
380+
# -c requirements/../../../packages/service-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt
381+
# -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt
382+
# -c requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt
383+
# -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt
384+
# -c requirements/../../../packages/service-library/requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt
385+
# -c requirements/../../../packages/service-library/requirements/../../../requirements/constraints.txt
386+
# -c requirements/../../../packages/settings-library/requirements/../../../packages/common-library/requirements/../../../requirements/constraints.txt
387+
# -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt
388+
# -c requirements/../../../requirements/constraints.txt
389+
# fastapi
355390
tenacity==9.0.0
356391
# via
357392
# -r requirements/../../../packages/service-library/requirements/_base.in
@@ -371,12 +406,12 @@ typing-extensions==4.12.2
371406
# aiodebug
372407
# anyio
373408
# faker
409+
# fastapi
374410
# faststream
375411
# opentelemetry-sdk
376412
# pydantic
377413
# pydantic-core
378414
# pydantic-extra-types
379-
# referencing
380415
# typer
381416
urllib3==2.3.0
382417
# via

services/docker-api-proxy/requirements/_tools.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
astroid==3.3.8
22
# via pylint
3-
black==24.10.0
3+
black==25.1.0
44
# via -r requirements/../../../requirements/devenv.txt
55
build==1.2.2.post1
66
# via pip-tools
@@ -21,7 +21,7 @@ filelock==3.17.0
2121
# via virtualenv
2222
identify==2.6.6
2323
# via pre-commit
24-
isort==5.13.2
24+
isort==6.0.0
2525
# via
2626
# -r requirements/../../../requirements/devenv.txt
2727
# pylint
@@ -42,7 +42,7 @@ packaging==24.2
4242
# build
4343
pathspec==0.12.1
4444
# via black
45-
pip==24.3.1
45+
pip==25.0
4646
# via pip-tools
4747
pip-tools==7.4.1
4848
# via -r requirements/../../../requirements/devenv.txt
@@ -53,7 +53,7 @@ platformdirs==4.3.6
5353
# virtualenv
5454
pre-commit==4.1.0
5555
# via -r requirements/../../../requirements/devenv.txt
56-
pylint==3.3.3
56+
pylint==3.3.4
5757
# via -r requirements/../../../requirements/devenv.txt
5858
pyproject-hooks==1.2.0
5959
# via
@@ -64,7 +64,7 @@ pyyaml==6.0.2
6464
# -c requirements/../../../requirements/constraints.txt
6565
# -c requirements/_test.txt
6666
# pre-commit
67-
ruff==0.9.2
67+
ruff==0.9.3
6868
# via -r requirements/../../../requirements/devenv.txt
6969
setuptools==75.8.0
7070
# via pip-tools

services/docker-api-proxy/tests/integration/conftest.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
# pylint:disable=unrecognized-options
22

3+
from collections.abc import AsyncIterator, Callable
4+
from contextlib import AbstractAsyncContextManager, asynccontextmanager
5+
from typing import Annotated
6+
7+
import aiodocker
8+
import pytest
9+
from asgi_lifespan import LifespanManager
10+
from fastapi import FastAPI
11+
from pydantic import Field
12+
from pytest_simcore.helpers.monkeypatch_envs import EnvVarsDict, setenvs_from_dict
13+
from servicelib.docker_utils import (
14+
get_remote_docker_client,
15+
lifespan_remote_docker_client,
16+
)
17+
from servicelib.fastapi.lifespan_utils import combine_lfiespan_context_managers
18+
from settings_library.application import BaseApplicationSettings
19+
from settings_library.docker_api_proxy import DockerApiProxysettings
20+
321
pytest_plugins = [
422
"pytest_simcore.docker_api_proxy",
523
"pytest_simcore.docker_compose",
@@ -12,3 +30,34 @@
1230
def pytest_configure(config):
1331
# Set asyncio_mode to "auto"
1432
config.option.asyncio_mode = "auto"
33+
34+
35+
def _get_test_app() -> FastAPI:
36+
class ApplicationSetting(BaseApplicationSettings):
37+
DOCKER_API_PROXY: Annotated[
38+
DockerApiProxysettings,
39+
Field(json_schema_extra={"auto_default_from_env": True}),
40+
]
41+
42+
app = FastAPI(
43+
lifespan=combine_lfiespan_context_managers(lifespan_remote_docker_client)
44+
)
45+
app.state.settings = ApplicationSetting.create_from_envs()
46+
47+
return app
48+
49+
50+
@pytest.fixture
51+
async def setup_docker_client(
52+
monkeypatch: pytest.MonkeyPatch,
53+
) -> Callable[[EnvVarsDict], AbstractAsyncContextManager[aiodocker.Docker]]:
54+
@asynccontextmanager
55+
async def _(env_vars: EnvVarsDict) -> AsyncIterator[aiodocker.Docker]:
56+
setenvs_from_dict(monkeypatch, env_vars)
57+
58+
app = _get_test_app()
59+
60+
async with LifespanManager(app):
61+
yield get_remote_docker_client(app)
62+
63+
return _
Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
# pylint: disable=unused-argument
2+
13
import json
24
import sys
5+
from collections.abc import Callable
6+
from contextlib import AbstractAsyncContextManager
37
from pathlib import Path
48

5-
from servicelib.docker_utils import create_remote_docker_client
9+
import aiodocker
10+
from pytest_simcore.helpers.monkeypatch_envs import EnvVarsDict
611
from settings_library.docker_api_proxy import DockerApiProxysettings
712

813
HERE = Path(sys.argv[0] if __name__ == "__main__" else __file__).resolve().parent
@@ -13,9 +18,16 @@
1318

1419

1520
async def test_unauthenticated_docker_client(
21+
docker_swarm: None,
1622
docker_api_proxy_settings: DockerApiProxysettings,
23+
setup_docker_client: Callable[
24+
[EnvVarsDict], AbstractAsyncContextManager[aiodocker.Docker]
25+
],
1726
):
18-
docker = await create_remote_docker_client(docker_api_proxy_settings)
19-
async with docker:
20-
info = await docker.system.info()
27+
envs = {
28+
"DOCKER_API_PROXY_HOST": "127.0.0.1",
29+
"DOCKER_API_PROXY_PORT": "8014",
30+
}
31+
async with setup_docker_client(envs) as working_docker:
32+
info = await working_docker.system.info()
2133
print(json.dumps(info, indent=2))

0 commit comments

Comments
 (0)