Skip to content

Commit 13288d3

Browse files
committed
Implement short syntax for env variables in compose.yml "environment:"
This commit allows compose file to directly use environment variable values in "environment:" section when variables were set in `.env` file. This functionality was missing, as docker-compose supports both: short and variable interpolation syntax forms: environment: - FOO and environment: - FOO=${FOO} Relevant docker-compose documentation: https://docs.docker.com/compose/how-tos/environment-variables/set-environment-variables/ podman-compose is more compatible with docker-compose after this change. Signed-off-by: Monika Kairaityte <[email protected]>
1 parent 7105198 commit 13288d3

File tree

6 files changed

+117
-29
lines changed

6 files changed

+117
-29
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Implemented short syntax for environment variables set in `.env` for compose.yml "environment:" section.

podman_compose.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1159,7 +1159,14 @@ async def container_to_args(
11591159
podman_args.extend(["-e", e])
11601160
env = norm_as_list(cnt.get("environment", {}))
11611161
for e in env:
1162-
podman_args.extend(["-e", e])
1162+
# new environment variable is set
1163+
if "=" in e:
1164+
podman_args.extend(["-e", e])
1165+
else:
1166+
# environment variable already exists in environment so pass its value
1167+
if e in compose.environ.keys():
1168+
podman_args.extend(["-e", f"{e}={compose.environ[e]}"])
1169+
11631170
tmpfs_ls = cnt.get("tmpfs", [])
11641171
if isinstance(tmpfs_ls, str):
11651172
tmpfs_ls = [tmpfs_ls]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
ZZVAR1='This value is loaded but should be overwritten'
22
ZZVAR2='This value is loaded from .env in project/ directory'
3+
ZZVAR3=TEST
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
services:
2+
app:
3+
image: nopush/podman-compose-test
4+
command: ["/bin/busybox", "sh", "-c", "env | grep ZZVAR3"]
5+
# 'env_file:' section is not used, so .env file is searched in the same directory as compose.yml
6+
# file
7+
environment:
8+
# this is short syntax: podman-compose takes only this variable value from '.env' file and
9+
# sends it to container environment
10+
- ZZVAR3

tests/integration/env_file_tests/test_podman_compose_env_file.py

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,20 @@ class TestComposeEnvFile(unittest.TestCase, RunSubprocessMixin):
1616
def test_path_env_file_inline(self) -> None:
1717
# Test taking env variable value directly from env-file when its path is inline path
1818
base_path = compose_base_path()
19-
path_compose_file = os.path.join(base_path, "project/container-compose.yaml")
19+
compose_file_path = os.path.join(base_path, "project/container-compose.yaml")
2020
try:
2121
self.run_subprocess_assert_returncode([
2222
podman_compose_path(),
2323
"-f",
24-
path_compose_file,
24+
compose_file_path,
2525
"--env-file",
2626
os.path.join(base_path, "env-files/project-1.env"),
2727
"up",
2828
])
2929
output, _ = self.run_subprocess_assert_returncode([
3030
podman_compose_path(),
3131
"-f",
32-
path_compose_file,
32+
compose_file_path,
3333
"logs",
3434
])
3535
# takes only value ZZVAR1 as container-compose.yaml file requires
@@ -38,26 +38,26 @@ def test_path_env_file_inline(self) -> None:
3838
self.run_subprocess_assert_returncode([
3939
podman_compose_path(),
4040
"-f",
41-
path_compose_file,
41+
compose_file_path,
4242
"down",
4343
])
4444

4545
def test_path_env_file_flat_in_compose_file(self) -> None:
4646
# Test taking env variable value from env-file/project-1.env which was declared in
4747
# compose file's env_file
4848
base_path = compose_base_path()
49-
path_compose_file = os.path.join(base_path, "project/container-compose.env-file-flat.yaml")
49+
compose_file_path = os.path.join(base_path, "project/container-compose.env-file-flat.yaml")
5050
try:
5151
self.run_subprocess_assert_returncode([
5252
podman_compose_path(),
5353
"-f",
54-
path_compose_file,
54+
compose_file_path,
5555
"up",
5656
])
5757
output, _ = self.run_subprocess_assert_returncode([
5858
podman_compose_path(),
5959
"-f",
60-
path_compose_file,
60+
compose_file_path,
6161
"logs",
6262
])
6363
# takes all values with a substring ZZ as container-compose.env-file-flat.yaml
@@ -70,26 +70,26 @@ def test_path_env_file_flat_in_compose_file(self) -> None:
7070
self.run_subprocess_assert_returncode([
7171
podman_compose_path(),
7272
"-f",
73-
path_compose_file,
73+
compose_file_path,
7474
"down",
7575
])
7676

7777
def test_path_env_file_obj_in_compose_file(self) -> None:
7878
# take variable value from env-file project-1.env which was declared in compose
7979
# file's env_file by -path: ...
8080
base_path = compose_base_path()
81-
path_compose_file = os.path.join(base_path, "project/container-compose.env-file-obj.yaml")
81+
compose_file_path = os.path.join(base_path, "project/container-compose.env-file-obj.yaml")
8282
try:
8383
self.run_subprocess_assert_returncode([
8484
podman_compose_path(),
8585
"-f",
86-
path_compose_file,
86+
compose_file_path,
8787
"up",
8888
])
8989
output, _ = self.run_subprocess_assert_returncode([
9090
podman_compose_path(),
9191
"-f",
92-
path_compose_file,
92+
compose_file_path,
9393
"logs",
9494
])
9595
# takes all values with a substring ZZ as container-compose.env-file-obj.yaml
@@ -102,28 +102,28 @@ def test_path_env_file_obj_in_compose_file(self) -> None:
102102
self.run_subprocess_assert_returncode([
103103
podman_compose_path(),
104104
"-f",
105-
path_compose_file,
105+
compose_file_path,
106106
"down",
107107
])
108108

109109
def test_exists_optional_env_file_path_in_compose_file(self) -> None:
110110
# test taking env variable values from several env-files when one of them is optional
111111
# and exists
112112
base_path = compose_base_path()
113-
path_compose_file = os.path.join(
113+
compose_file_path = os.path.join(
114114
base_path, "project/container-compose.env-file-obj-optional-exists.yaml"
115115
)
116116
try:
117117
self.run_subprocess_assert_returncode([
118118
podman_compose_path(),
119119
"-f",
120-
path_compose_file,
120+
compose_file_path,
121121
"up",
122122
])
123123
output, _ = self.run_subprocess_assert_returncode([
124124
podman_compose_path(),
125125
"-f",
126-
path_compose_file,
126+
compose_file_path,
127127
"logs",
128128
])
129129
# FIXME: gives a weird output, needs to be double checked
@@ -135,28 +135,28 @@ def test_exists_optional_env_file_path_in_compose_file(self) -> None:
135135
self.run_subprocess_assert_returncode([
136136
podman_compose_path(),
137137
"-f",
138-
path_compose_file,
138+
compose_file_path,
139139
"down",
140140
])
141141

142142
def test_missing_optional_env_file_path_in_compose_file(self) -> None:
143143
# test taking env variable values from several env-files when one of them is optional and
144144
# is missing (silently skip it)
145145
base_path = compose_base_path()
146-
path_compose_file = os.path.join(
146+
compose_file_path = os.path.join(
147147
base_path, "project/container-compose.env-file-obj-optional-missing.yaml"
148148
)
149149
try:
150150
self.run_subprocess_assert_returncode([
151151
podman_compose_path(),
152152
"-f",
153-
path_compose_file,
153+
compose_file_path,
154154
"up",
155155
])
156156
output, _ = self.run_subprocess_assert_returncode([
157157
podman_compose_path(),
158158
"-f",
159-
path_compose_file,
159+
compose_file_path,
160160
"logs",
161161
])
162162
# takes all values with a substring ZZ as container-compose.env-file-obj-optional.yaml
@@ -169,29 +169,29 @@ def test_missing_optional_env_file_path_in_compose_file(self) -> None:
169169
self.run_subprocess_assert_returncode([
170170
podman_compose_path(),
171171
"-f",
172-
path_compose_file,
172+
compose_file_path,
173173
"down",
174174
])
175175

176176
def test_var_value_inline_overrides_env_file_path_inline(self) -> None:
177177
# Test overriding env value when value is declared in inline command
178178
base_path = compose_base_path()
179-
path_compose_file = os.path.join(base_path, "project/container-compose.yaml")
179+
compose_file_path = os.path.join(base_path, "project/container-compose.yaml")
180180
try:
181181
self.run_subprocess_assert_returncode([
182182
"env",
183183
"ZZVAR1=podman-rocks-321",
184184
podman_compose_path(),
185185
"-f",
186-
path_compose_file,
186+
compose_file_path,
187187
"--env-file",
188188
os.path.join(base_path, "env-files/project-1.env"),
189189
"up",
190190
])
191191
output, _ = self.run_subprocess_assert_returncode([
192192
podman_compose_path(),
193193
"-f",
194-
path_compose_file,
194+
compose_file_path,
195195
"logs",
196196
])
197197
# takes only value ZZVAR1 as container-compose.yaml file requires
@@ -200,7 +200,7 @@ def test_var_value_inline_overrides_env_file_path_inline(self) -> None:
200200
self.run_subprocess_assert_returncode([
201201
podman_compose_path(),
202202
"-f",
203-
path_compose_file,
203+
compose_file_path,
204204
"down",
205205
])
206206

@@ -209,7 +209,7 @@ def test_taking_env_variables_from_env_files_from_different_directories(self) ->
209209
# Test overriding env values by directory env-files-tests/.env file values
210210
# and only take value from project/.env, when it does not exist in env-files-tests/.env
211211
base_path = compose_base_path()
212-
path_compose_file = os.path.join(
212+
compose_file_path = os.path.join(
213213
base_path, "project/container-compose.load-.env-in-project.yaml"
214214
)
215215
try:
@@ -218,7 +218,7 @@ def test_taking_env_variables_from_env_files_from_different_directories(self) ->
218218
output, _ = self.run_subprocess_assert_returncode([
219219
podman_compose_path(),
220220
"-f",
221-
path_compose_file,
221+
compose_file_path,
222222
"run",
223223
"--rm",
224224
"app",
@@ -233,14 +233,47 @@ def test_taking_env_variables_from_env_files_from_different_directories(self) ->
233233
[
234234
'ZZVAR1=This value is loaded but should be overwritten\r',
235235
'ZZVAR2=This value is loaded from .env in project/ directory\r',
236-
'ZZVAR3=\r',
236+
'ZZVAR3=TEST\r',
237237
'',
238238
],
239239
)
240240
finally:
241241
self.run_subprocess_assert_returncode([
242242
podman_compose_path(),
243243
"-f",
244-
path_compose_file,
244+
compose_file_path,
245+
"down",
246+
])
247+
248+
def test_env_var_value_accessed_in_compose_file_short_syntax(self) -> None:
249+
# Test that compose file can access the environment variable set in .env file using
250+
# short syntax, that is: only the name of environment variable is used in "environment:" in
251+
# compose.yml file and its value is picked up directly from .env file
252+
# long syntax of environment variables interpolation is tested in
253+
# tests/integration/interpolation
254+
255+
base_path = compose_base_path()
256+
compose_file_path = os.path.join(base_path, "project/container-compose.short_syntax.yaml")
257+
try:
258+
self.run_subprocess_assert_returncode([
259+
podman_compose_path(),
260+
"-f",
261+
compose_file_path,
262+
"up",
263+
"-d",
264+
])
265+
output, _ = self.run_subprocess_assert_returncode([
266+
podman_compose_path(),
267+
"-f",
268+
compose_file_path,
269+
"logs",
270+
])
271+
# ZZVAR3 was set in .env file
272+
self.assertEqual(output, b"ZZVAR3=TEST\n")
273+
finally:
274+
self.run_subprocess_assert_returncode([
275+
podman_compose_path(),
276+
"-f",
277+
compose_file_path,
245278
"down",
246279
])

tests/unit/test_container_to_args.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,42 @@ async def test_no_hosts_extension(self) -> None:
262262
],
263263
)
264264

265+
@parameterized.expand([
266+
# short syntax: only take this specific environment variable value from .env file
267+
("use_env_var_from_default_env_file_short_syntax", ["ZZVAR1"], "ZZVAR1=TEST1"),
268+
# long syntax: environment variable value from .env file is taken through variable
269+
# interpolation
270+
# only the value required in 'environment:' compose file is sent to containers
271+
# environment
272+
("use_env_var_from_default_env_file_long_syntax", ["ZZVAR1=TEST1"], "ZZVAR1=TEST1"),
273+
# "environment:" section in compose file overrides environment variable value from .env file
274+
(
275+
"use_env_var_from_default_env_file_override_value",
276+
["ZZVAR1=NEW_TEST1"],
277+
"ZZVAR1=NEW_TEST1",
278+
),
279+
])
280+
async def test_env_file(self, test_name: str, cnt_env: list, expected_var: str) -> None:
281+
c = create_compose_mock()
282+
# environment variables were set in .env file
283+
c.environ = {"ZZVAR1": "TEST1", "ZZVAR2": "TEST2"}
284+
285+
cnt = get_minimal_container()
286+
cnt["environment"] = cnt_env
287+
288+
args = await container_to_args(c, cnt)
289+
self.assertEqual(
290+
args,
291+
[
292+
"--name=project_name_service_name1",
293+
"-d",
294+
"-e",
295+
f"{expected_var}",
296+
"--network=bridge:alias=service_name",
297+
"busybox",
298+
],
299+
)
300+
265301
async def test_env_file_str(self) -> None:
266302
c = create_compose_mock()
267303

0 commit comments

Comments
 (0)