mirror of
https://github.com/containers/podman-compose.git
synced 2025-08-11 22:49:16 +02:00
Implement environment variable interpolation to YAML dictionary keys
`podman-compose` currently does not support interpolating environment variables in dictionary keys, despite the compose file specification indicating this capability. See the relevant compose-spec documentation: https://github.com/compose-spec/compose-spec/blob/main/12-interpolation.md This feature is useful in `labels` or `environment` sections, where keys can be user-defined strings. To enable interpolation, an alternate equal sign syntax must be used, e.g.: services: foo: labels: - "$VAR_NAME=label_value" After this PR `podman-compose` will align more closely with the compose file specification, allowing for the interpolation of environment variables in dictionary keys. Signed-off-by: Monika Kairaityte <monika@kibit.lt>
This commit is contained in:
1
newsfragments/env-var-interpolation-for-keys.feature
Normal file
1
newsfragments/env-var-interpolation-for-keys.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Add support for environment variable interpolation for YAML keys.
|
@ -290,7 +290,7 @@ def rec_subs(value: dict | str | Iterable, subs_dict: dict[str, Any]) -> dict |
|
|||||||
svc_envs = rec_subs(svc_envs, subs_dict)
|
svc_envs = rec_subs(svc_envs, subs_dict)
|
||||||
subs_dict.update(svc_envs)
|
subs_dict.update(svc_envs)
|
||||||
|
|
||||||
value = {k: rec_subs(v, subs_dict) for k, v in value.items()}
|
value = {rec_subs(k, subs_dict): rec_subs(v, subs_dict) for k, v in value.items()}
|
||||||
elif isinstance(value, str):
|
elif isinstance(value, str):
|
||||||
|
|
||||||
def convert(m: re.Match) -> str:
|
def convert(m: re.Match) -> str:
|
||||||
|
@ -1 +1,2 @@
|
|||||||
DOT_ENV_VARIABLE=This value is from the .env file
|
DOT_ENV_VARIABLE=This value is from the .env file
|
||||||
|
TEST_LABELS=TEST
|
||||||
|
@ -11,4 +11,10 @@ services:
|
|||||||
EXAMPLE_DOT_ENV: $DOT_ENV_VARIABLE
|
EXAMPLE_DOT_ENV: $DOT_ENV_VARIABLE
|
||||||
EXAMPLE_LITERAL: This is a $$literal
|
EXAMPLE_LITERAL: This is a $$literal
|
||||||
EXAMPLE_EMPTY: $NOT_A_VARIABLE
|
EXAMPLE_EMPTY: $NOT_A_VARIABLE
|
||||||
|
labels_test:
|
||||||
|
image: busybox
|
||||||
|
labels:
|
||||||
|
- "$TEST_LABELS=test_labels"
|
||||||
|
- test.${TEST_LABELS}=${TEST_LABELS}
|
||||||
|
- "${TEST_LABELS}.test2=test2(`${TEST_LABELS}`)"
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
@ -36,6 +37,18 @@ class TestComposeInterpolation(unittest.TestCase, RunSubprocessMixin):
|
|||||||
self.assertIn("EXAMPLE_DOT_ENV='This value is from the .env file'", str(output))
|
self.assertIn("EXAMPLE_DOT_ENV='This value is from the .env file'", str(output))
|
||||||
self.assertIn("EXAMPLE_EMPTY=''", str(output))
|
self.assertIn("EXAMPLE_EMPTY=''", str(output))
|
||||||
self.assertIn("EXAMPLE_LITERAL='This is a $literal'", str(output))
|
self.assertIn("EXAMPLE_LITERAL='This is a $literal'", str(output))
|
||||||
|
|
||||||
|
output, _ = self.run_subprocess_assert_returncode([
|
||||||
|
"podman",
|
||||||
|
"inspect",
|
||||||
|
"interpolation_labels_test_1",
|
||||||
|
])
|
||||||
|
inspect_output = json.loads(output)
|
||||||
|
labels_dict = inspect_output[0].get("Config", {}).get("Labels", {})
|
||||||
|
self.assertIn(('TEST', 'test_labels'), labels_dict.items())
|
||||||
|
self.assertIn(('TEST.test2', 'test2(`TEST`)'), labels_dict.items())
|
||||||
|
self.assertIn(('test.TEST', 'TEST'), labels_dict.items())
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self.run_subprocess_assert_returncode([
|
self.run_subprocess_assert_returncode([
|
||||||
podman_compose_path(),
|
podman_compose_path(),
|
||||||
|
@ -71,3 +71,34 @@ class TestRecSubs(unittest.TestCase):
|
|||||||
sub_dict = {"v1": "high priority", "empty": ""}
|
sub_dict = {"v1": "high priority", "empty": ""}
|
||||||
result = rec_subs(input, sub_dict)
|
result = rec_subs(input, sub_dict)
|
||||||
self.assertEqual(result, expected, msg=desc)
|
self.assertEqual(result, expected, msg=desc)
|
||||||
|
|
||||||
|
def test_env_var_substitution_in_dictionary_keys(self) -> None:
|
||||||
|
sub_dict = {"NAME": "TEST1", "NAME2": "TEST2"}
|
||||||
|
input = {
|
||||||
|
'services': {
|
||||||
|
'test': {
|
||||||
|
'image': 'busybox',
|
||||||
|
'labels': {
|
||||||
|
'$NAME and ${NAME2}': '${NAME2} and $NAME',
|
||||||
|
'test1.${NAME}': 'test1',
|
||||||
|
'$NAME': '${NAME2}',
|
||||||
|
'${NAME}.test2': 'Host(`${NAME2}`)',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = rec_subs(input, sub_dict)
|
||||||
|
expected = {
|
||||||
|
'services': {
|
||||||
|
'test': {
|
||||||
|
'image': 'busybox',
|
||||||
|
'labels': {
|
||||||
|
'TEST1 and TEST2': 'TEST2 and TEST1',
|
||||||
|
'test1.TEST1': 'test1',
|
||||||
|
'TEST1': 'TEST2',
|
||||||
|
'TEST1.test2': 'Host(`TEST2`)',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
Reference in New Issue
Block a user