Skip to content

Commit 4711874

Browse files
authored
Merge pull request #1504 from mokibit/resolve-required-variable-syntax
Fix variable interpolation in unquoted values (command and entrypoint)
2 parents 7ce6233 + ea78af1 commit 4711874

8 files changed

Lines changed: 127 additions & 16 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix variable interpolation in unquoted ``command`` and ``entrypoint`` values.

podman_compose.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2097,10 +2097,6 @@ def normalize_service(service: dict[str, Any], sub_dir: str = "") -> dict[str, A
20972097
if "build" in service and "args" in service["build"]:
20982098
if isinstance(build["args"], dict):
20992099
build["args"] = norm_as_list(build["args"])
2100-
for key in ("command", "entrypoint"):
2101-
if key in service:
2102-
if isinstance(service[key], str):
2103-
service[key] = shlex.split(service[key])
21042100
for key in ("env_file", "security_opt", "volumes"):
21052101
if key not in service:
21062102
continue
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
version: "3.7"
2+
services:
3+
command_test:
4+
image: nopush/podman-compose-test
5+
command: echo ${CMD_VAR:?CMD_VAR variable missing}

tests/integration/interpolation/test_podman_compose_interpolation.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ def compose_yaml_path() -> str:
1313
return os.path.join(os.path.join(test_path(), "interpolation"), "docker-compose.yml")
1414

1515

16+
def compose_command_yaml_path() -> str:
17+
return os.path.join(
18+
os.path.join(test_path(), "interpolation"), "docker-compose-command-interpolation.yml"
19+
)
20+
21+
1622
class TestComposeInterpolation(unittest.TestCase, RunSubprocessMixin):
1723
def test_interpolation(self) -> None:
1824
try:
@@ -86,3 +92,52 @@ def test_required_set_variable_missing(self) -> None:
8692
1,
8793
)
8894
self.assertIn(b"required variable NOT_A_VARIABLE is missing a value: Missing variable", err)
95+
96+
def test_command_interpolation_unquoted(self) -> None:
97+
try:
98+
self.run_subprocess_assert_returncode(
99+
[
100+
podman_compose_path(),
101+
"-f",
102+
compose_command_yaml_path(),
103+
"up",
104+
],
105+
0,
106+
{"CMD_VAR": "hello_world"},
107+
)
108+
output, _ = self.run_subprocess_assert_returncode(
109+
[
110+
podman_compose_path(),
111+
"-f",
112+
compose_command_yaml_path(),
113+
"logs",
114+
],
115+
0,
116+
{"CMD_VAR": "hello_world"},
117+
)
118+
self.assertIn(b"hello_world", output)
119+
finally:
120+
self.run_subprocess_assert_returncode(
121+
[
122+
podman_compose_path(),
123+
"-f",
124+
compose_command_yaml_path(),
125+
"down",
126+
],
127+
0,
128+
{"CMD_VAR": "hello_world"},
129+
)
130+
131+
def test_command_interpolation_unquoted_missing_variable(self) -> None:
132+
out, err = self.run_subprocess_assert_returncode(
133+
[
134+
podman_compose_path(),
135+
"-f",
136+
compose_command_yaml_path(),
137+
"up",
138+
],
139+
1,
140+
)
141+
self.assertIn(
142+
b"required variable CMD_VAR is missing a value: CMD_VAR variable missing", err
143+
)

tests/unit/test_can_merge_build.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,23 +122,23 @@ def test_parse_with_map_merge_into_none(self):
122122
@parameterized.expand([
123123
({}, {"$$$": []}, {"$$$": []}),
124124
({"$$$": []}, {}, {"$$$": []}),
125-
({"$$$": []}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}),
125+
({"$$$": []}, {"$$$": "sh-2"}, {"$$$": "sh-2"}),
126126
({"$$$": "sh-2"}, {"$$$": []}, {"$$$": []}),
127-
({}, {"$$$": "sh"}, {"$$$": ["sh"]}),
128-
({"$$$": "sh"}, {}, {"$$$": ["sh"]}),
129-
({"$$$": "sh-1"}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}),
130-
({"$$$": ["sh-1"]}, {"$$$": "sh-2"}, {"$$$": ["sh-2"]}),
127+
({}, {"$$$": "sh"}, {"$$$": "sh"}),
128+
({"$$$": "sh"}, {}, {"$$$": "sh"}),
129+
({"$$$": "sh-1"}, {"$$$": "sh-2"}, {"$$$": "sh-2"}),
130+
({"$$$": ["sh-1"]}, {"$$$": "sh-2"}, {"$$$": "sh-2"}),
131131
({"$$$": "sh-1"}, {"$$$": ["sh-2"]}, {"$$$": ["sh-2"]}),
132132
({"$$$": "sh-1"}, {"$$$": ["sh-2", "sh-3"]}, {"$$$": ["sh-2", "sh-3"]}),
133133
({"$$$": ["sh-1"]}, {"$$$": ["sh-2", "sh-3"]}, {"$$$": ["sh-2", "sh-3"]}),
134134
({"$$$": ["sh-1", "sh-2"]}, {"$$$": ["sh-3", "sh-4"]}, {"$$$": ["sh-3", "sh-4"]}),
135135
({}, {"$$$": ["sh-3", "sh 4"]}, {"$$$": ["sh-3", "sh 4"]}),
136-
({"$$$": "sleep infinity"}, {"$$$": "sh"}, {"$$$": ["sh"]}),
137-
({"$$$": "sh"}, {"$$$": "sleep infinity"}, {"$$$": ["sleep", "infinity"]}),
136+
({"$$$": "sleep infinity"}, {"$$$": "sh"}, {"$$$": "sh"}),
137+
({"$$$": "sh"}, {"$$$": "sleep infinity"}, {"$$$": "sleep infinity"}),
138138
(
139139
{},
140140
{"$$$": "bash -c 'sleep infinity'"},
141-
{"$$$": ["bash", "-c", "sleep infinity"]},
141+
{"$$$": "bash -c 'sleep infinity'"},
142142
),
143143
])
144144
def test_parse_compose_file_when_multiple_composes_keys_command_entrypoint(

tests/unit/test_container_to_args.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,3 +1317,41 @@ async def test_stop_grace_period_minutes(self) -> None:
13171317
"busybox",
13181318
],
13191319
)
1320+
1321+
async def test_command_string_is_shlex_split(self) -> None:
1322+
c = create_compose_mock()
1323+
1324+
cnt = get_minimal_container()
1325+
cnt["command"] = "sleep infinity"
1326+
1327+
args = await container_to_args(c, cnt)
1328+
self.assertEqual(
1329+
args,
1330+
[
1331+
"--name=project_name_service_name1",
1332+
"-d",
1333+
"--network=bridge:alias=service_name",
1334+
"busybox",
1335+
"sleep",
1336+
"infinity",
1337+
],
1338+
)
1339+
1340+
async def test_entrypoint_string_is_shlex_split(self) -> None:
1341+
c = create_compose_mock()
1342+
1343+
cnt = get_minimal_container()
1344+
cnt["entrypoint"] = "bash -c 'echo hello'"
1345+
1346+
args = await container_to_args(c, cnt)
1347+
self.assertEqual(
1348+
args,
1349+
[
1350+
"--name=project_name_service_name1",
1351+
"-d",
1352+
"--network=bridge:alias=service_name",
1353+
"--entrypoint",
1354+
'["bash", "-c", "echo hello"]',
1355+
"busybox",
1356+
],
1357+
)

tests/unit/test_normalize_service.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,16 @@ def test_normalize_service_with_sub_dir(
100100
([], []),
101101
(["sh"], ["sh"]),
102102
(["sh", "-c", "date"], ["sh", "-c", "date"]),
103-
("sh", ["sh"]),
104-
("sleep infinity", ["sleep", "infinity"]),
103+
("sh", "sh"),
104+
("sleep infinity", "sleep infinity"),
105105
(
106106
"bash -c 'sleep infinity'",
107-
["bash", "-c", "sleep infinity"],
107+
"bash -c 'sleep infinity'",
108108
),
109109
])
110-
def test_command_like(self, input: Union[list[str], str], expected: list[str]) -> None:
110+
def test_command_like(
111+
self, input: Union[list[str], str], expected: Union[list[str], str]
112+
) -> None:
111113
for key in ['command', 'entrypoint']:
112114
input_service = {}
113115
input_service[key] = input

tests/unit/test_var_interpolate.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,20 @@ class TestVarInterpolate(unittest.TestCase):
177177
# 45. Dollar sign not followed by valid variable name (should be treated as literal)
178178
# Disable shell comparison since POSIX shells would treat $5 as fifth argument
179179
("Price is $5", {}, "Price is $5", False),
180+
# 46. Required variable with spaces in error message (unquoted command syntax)
181+
(
182+
"cmd ${BAR:?BAR variable missing}",
183+
{"BAR": "myvalue"},
184+
"cmd myvalue",
185+
True,
186+
),
187+
# 47. Required variable with spaces in error message fails when missing
188+
(
189+
"cmd ${BAR:?BAR variable missing}",
190+
{},
191+
ValueError("required variable BAR is missing a value: BAR variable missing"),
192+
True,
193+
),
180194
]
181195

182196
@parameterized.expand(test_cases)

0 commit comments

Comments
 (0)