Skip to content

Commit f71c924

Browse files
committed
Add envs parameter to compose up/config
1 parent 7dc3102 commit f71c924

File tree

5 files changed

+144
-6
lines changed

5 files changed

+144
-6
lines changed

python_on_whales/client_config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ class ClientConfig:
6464
compose_project_name: Optional[str] = None
6565
compose_project_directory: Optional[ValidPath] = None
6666
compose_compatibility: Optional[bool] = None
67+
compose_envs: Optional[Dict[str, str]] = None
6768
client_call: List[str] = field(default_factory=lambda: ["docker"])
6869
_client_call_with_path: Optional[List[Union[Path, str]]] = None
6970

@@ -165,6 +166,10 @@ def docker_cmd(self) -> Command:
165166
def docker_compose_cmd(self) -> Command:
166167
return self.client_config.docker_compose_cmd
167168

169+
@property
170+
def docker_compose_envs(self) -> Dict[str, str]:
171+
return self.client_config.compose_envs or {}
172+
168173

169174
class ReloadableObject(DockerCLICaller):
170175
def __init__(

python_on_whales/components/compose/cli_wrapper.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ def build(
5858
full_cmd += services
5959
run(full_cmd, capture_stdout=False)
6060

61-
def config(self, return_json: bool = False) -> Union[ComposeConfig, Dict[str, Any]]:
61+
def config(
62+
self, return_json: bool = False, envs: Dict[str, str] = {}
63+
) -> Union[ComposeConfig, Dict[str, Any]]:
6264
"""Returns the configuration of the compose stack for further inspection.
6365
6466
For example
@@ -76,12 +78,15 @@ def config(self, return_json: bool = False) -> Union[ComposeConfig, Dict[str, An
7678
lists and dicts corresponding to the json response, unmodified.
7779
It may be useful if you just want to print the config or want to access
7880
a field that was not in the `ComposeConfig` class.
81+
envs: A dictionary of environment variables to set for the compose process.
7982
8083
# Returns
8184
A `ComposeConfig` object if `return_json` is `False`, and a `dict` otherwise.
8285
"""
8386
full_cmd = self.docker_compose_cmd + ["config", "--format", "json"]
84-
result = run(full_cmd, capture_stdout=True)
87+
run_envs = {**self.docker_compose_envs, **envs}
88+
89+
result = run(full_cmd, capture_stdout=True, env=run_envs)
8590
if return_json:
8691
return json.loads(result)
8792
else:
@@ -485,7 +490,7 @@ def run(
485490
command: List[str] = [],
486491
detach: bool = False,
487492
# entrypoint: Optional[List[str]] = None,
488-
# envs: Dict[str, str] = {},
493+
envs: Dict[str, str] = {},
489494
# labels: Dict[str, str] = {},
490495
name: Optional[str] = None,
491496
tty: bool = True,
@@ -567,10 +572,12 @@ def run(
567572
full_cmd.append(service)
568573
full_cmd += command
569574

575+
run_envs = {**self.docker_compose_envs, **envs}
576+
570577
if stream:
571578
return stream_stdout_and_stderr(full_cmd)
572579
else:
573-
result = run(full_cmd, tty=tty)
580+
result = run(full_cmd, tty=tty, env=run_envs)
574581
if detach:
575582
Container = python_on_whales.components.container.cli_wrapper.Container
576583
return Container(self.client_config, result, is_immutable_id=True)
@@ -651,6 +658,7 @@ def up(
651658
log_prefix: bool = True,
652659
start: bool = True,
653660
quiet: bool = False,
661+
envs: Dict[str, str] = {},
654662
):
655663
"""Start the containers.
656664
@@ -681,6 +689,7 @@ def up(
681689
start: Start the service after creating them.
682690
quiet: By default, some progress bars and logs are sent to stderr and stdout.
683691
Set `quiet=True` to avoid having any output.
692+
envs: A dictionary of environment variables to set for the compose process.
684693
685694
# Returns
686695
`None` at the moment. The plan is to be able to capture and stream the logs later.
@@ -700,13 +709,15 @@ def up(
700709
full_cmd.add_flag("--no-log-prefix", not log_prefix)
701710
full_cmd.add_flag("--no-start", not start)
702711

712+
run_envs = {**self.docker_compose_envs, **envs}
713+
703714
if services == []:
704715
return
705716
elif services is not None:
706717
services = to_list(services)
707718
full_cmd += services
708719
# important information is written to both stdout AND stderr.
709-
run(full_cmd, capture_stdout=quiet, capture_stderr=quiet)
720+
run(full_cmd, capture_stdout=quiet, capture_stderr=quiet, env=run_envs)
710721

711722
def version(self) -> str:
712723
"""Returns the version of docker compose as a `str`."""

python_on_whales/docker_client.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import base64
22
import warnings
3-
from typing import List, Optional
3+
from typing import Dict, List, Optional
44

55
from python_on_whales.client_config import ClientConfig, DockerCLICaller
66
from python_on_whales.components.buildx.cli_wrapper import BuildxCLI
@@ -56,6 +56,7 @@ class DockerClient(DockerCLICaller):
5656
the [documentation for profiles](https://docs.docker.com/compose/profiles/).
5757
compose_env_file: .env file containing the environments variables to inject
5858
into the compose project. By default, it uses `./.env`.
59+
compose_envs: Environment variables to inject into the docker compose process.
5960
compose_project_name: The name of the compose project. It will be prefixed to
6061
networks, volumes and containers created by compose.
6162
compose_project_directory: Use an alternate working directory. By default, it
@@ -100,6 +101,7 @@ def __init__(
100101
compose_project_name: Optional[str] = None,
101102
compose_project_directory: Optional[ValidPath] = None,
102103
compose_compatibility: Optional[bool] = None,
104+
compose_envs: Optional[Dict[str, str]] = None,
103105
client_binary: str = "docker",
104106
client_call: List[str] = ["docker"],
105107
):
@@ -128,6 +130,7 @@ def __init__(
128130
compose_project_name=compose_project_name,
129131
compose_project_directory=compose_project_directory,
130132
compose_compatibility=compose_compatibility,
133+
compose_envs=compose_envs,
131134
client_call=client_call,
132135
)
133136
super().__init__(client_config)

tests/python_on_whales/components/dummy_compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ services:
1515
busybox:
1616
image: busybox:latest
1717
command: sleep infinity
18+
environment:
19+
- SOME_VARIABLE=${SOME_VARIABLE_TO_INSERT:-nothing}
1820
busybox-2-electric-boogaloo:
1921
image: busybox:latest
2022
depends_on:

tests/python_on_whales/components/test_compose.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,123 @@ def test_docker_compose_up_down_some_services():
218218
docker.compose.down(timeout=1)
219219

220220

221+
def test_docker_compose_config_process_envs(tmp_path: Path):
222+
docker = DockerClient(
223+
compose_files=[
224+
PROJECT_ROOT
225+
/ "tests/python_on_whales/components/dummy_compose_ends_quickly.yml"
226+
],
227+
compose_compatibility=True,
228+
)
229+
envs = {"SOME_VARIABLE_TO_INSERT": "test-value"}
230+
231+
output = docker.compose.config(envs=envs)
232+
233+
assert output.services["alpine"].environment["SOME_VARIABLE"] == "test-value"
234+
235+
236+
def test_docker_compose_config_process_envs_override(tmp_path: Path):
237+
docker = DockerClient(
238+
compose_files=[
239+
PROJECT_ROOT
240+
/ "tests/python_on_whales/components/dummy_compose_ends_quickly.yml"
241+
],
242+
compose_compatibility=True,
243+
compose_envs={"SOME_VARIABLE_TO_INSERT": "test-client"},
244+
)
245+
246+
output = docker.compose.config(envs={"SOME_VARIABLE_TO_INSERT": "test-call"})
247+
assert output.services["alpine"].environment["SOME_VARIABLE"] == "test-call"
248+
249+
250+
def test_docker_compose_up_process_envs(tmp_path: Path):
251+
docker = DockerClient(
252+
compose_files=[
253+
PROJECT_ROOT
254+
/ "tests/python_on_whales/components/dummy_compose_ends_quickly.yml"
255+
],
256+
compose_compatibility=True,
257+
)
258+
259+
docker.compose.up(envs={"SOME_VARIABLE_TO_INSERT": "test-value"})
260+
261+
assert not docker.container.inspect("alpine").state.running
262+
263+
result = docker.compose.execute(
264+
"alpine", ["bash", "-c", "echo $SOME_VARIABLE"], tty=False
265+
)
266+
267+
assert result == "test-value"
268+
269+
270+
def test_docker_compose_up_process_envs_override(tmp_path: Path):
271+
docker = DockerClient(
272+
compose_files=[
273+
PROJECT_ROOT
274+
/ "tests/python_on_whales/components/dummy_compose_ends_quickly.yml"
275+
],
276+
compose_compatibility=True,
277+
compose_envs={"SOME_VARIABLE_TO_INSERT": "test-client"},
278+
)
279+
280+
docker.compose.up(envs={"SOME_VARIABLE_TO_INSERT": "test-call"})
281+
282+
assert not docker.container.inspect("alpine").state.running
283+
284+
result = docker.compose.execute(
285+
"alpine", ["bash", "-c", "echo $SOME_VARIABLE"], tty=False
286+
)
287+
288+
assert result == "test-call"
289+
290+
291+
def test_docker_compose_run_process_envs(tmp_path: Path):
292+
docker = DockerClient(
293+
compose_files=[
294+
PROJECT_ROOT
295+
/ "tests/python_on_whales/components/dummy_compose_ends_quickly.yml"
296+
],
297+
compose_compatibility=True,
298+
)
299+
300+
docker.compose.up(envs={"SOME_VARIABLE_TO_INSERT": "test-up"})
301+
302+
assert not docker.container.inspect("alpine").state.running
303+
304+
result = docker.compose.run(
305+
"busybox",
306+
["echo", "${SOME_VARIABLE}"],
307+
remove=True,
308+
tty=False,
309+
envs={"SOME_VARIABLE_TO_INSERT": "test-exec"},
310+
)
311+
assert result == "test-exec"
312+
313+
314+
def test_docker_compose_run_process_envs_override(tmp_path: Path):
315+
docker = DockerClient(
316+
compose_files=[
317+
PROJECT_ROOT
318+
/ "tests/python_on_whales/components/dummy_compose_ends_quickly.yml"
319+
],
320+
compose_compatibility=True,
321+
compose_envs={"SOME_VARIABLE_TO_INSERT": "test-client"},
322+
)
323+
324+
docker.compose.up()
325+
326+
assert not docker.container.inspect("alpine").state.running
327+
328+
result = docker.compose.run(
329+
"busybox",
330+
["echo", "${SOME_VARIABLE}"],
331+
remove=True,
332+
tty=False,
333+
envs={"SOME_VARIABLE_TO_INSERT": "test-exec"},
334+
)
335+
assert result == "test-exec"
336+
337+
221338
def test_docker_compose_ps():
222339
docker.compose.up(["my_service", "busybox"], detach=True)
223340
containers = docker.compose.ps()

0 commit comments

Comments
 (0)