Skip to content

Commit 371ecb8

Browse files
Merge pull request #513 from Mr-Sunglasses/fix/#489
Fix/#489
2 parents bbbe758 + fd8bfcd commit 371ecb8

File tree

3 files changed

+262
-34
lines changed

3 files changed

+262
-34
lines changed

podman/domain/containers_create.py

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
logger = logging.getLogger("podman.containers")
1919

20-
NAMED_VOLUME_PATTERN = re.compile(r'[a-zA-Z0-9][a-zA-Z0-9_.-]*')
20+
NAMED_VOLUME_PATTERN = re.compile(r"[a-zA-Z0-9][a-zA-Z0-9_.-]*")
2121

2222

2323
class CreateMixin: # pylint: disable=too-few-public-methods
@@ -375,14 +375,58 @@ def create(
375375
payload = api.prepare_body(payload)
376376

377377
response = self.client.post(
378-
"/containers/create", headers={"content-type": "application/json"}, data=payload
378+
"/containers/create",
379+
headers={"content-type": "application/json"},
380+
data=payload,
379381
)
380382
response.raise_for_status(not_found=ImageNotFound)
381383

382384
container_id = response.json()["Id"]
383385

384386
return self.get(container_id)
385387

388+
@staticmethod
389+
def _convert_env_list_to_dict(env_list):
390+
"""Convert a list of environment variables to a dictionary.
391+
392+
Args:
393+
env_list (List[str]): List of environment variables in the format ["KEY=value"]
394+
395+
Returns:
396+
Dict[str, str]: Dictionary of environment variables
397+
398+
Raises:
399+
ValueError: If any environment variable is not in the correct format
400+
"""
401+
if not isinstance(env_list, list):
402+
raise TypeError(f"Expected list, got {type(env_list).__name__}")
403+
404+
env_dict = {}
405+
406+
for env_var in env_list:
407+
if not isinstance(env_var, str):
408+
raise TypeError(
409+
f"Environment variable must be a string, "
410+
f"got {type(env_var).__name__}: {repr(env_var)}"
411+
)
412+
413+
# Handle empty strings
414+
if not env_var.strip():
415+
raise ValueError(f"Environment variable cannot be empty")
416+
if "=" not in env_var:
417+
raise ValueError(
418+
f"Environment variable '{env_var}' is not in the correct format. "
419+
"Expected format: 'KEY=value'"
420+
)
421+
key, value = env_var.split("=", 1) # Split on first '=' only
422+
423+
# Validate key is not empty
424+
if not key.strip():
425+
raise ValueError(f"Environment variable has empty key: '{env_var}'")
426+
427+
env_dict[key] = value
428+
return env_dict
429+
386430
# pylint: disable=too-many-locals,too-many-statements,too-many-branches
387431
@staticmethod
388432
def _render_payload(kwargs: MutableMapping[str, Any]) -> dict[str, Any]:
@@ -410,6 +454,23 @@ def _render_payload(kwargs: MutableMapping[str, Any]) -> dict[str, Any]:
410454
with suppress(KeyError):
411455
del args[key]
412456

457+
# Handle environment variables
458+
environment = args.pop("environment", None)
459+
if environment is not None:
460+
if isinstance(environment, list):
461+
try:
462+
environment = CreateMixin._convert_env_list_to_dict(environment)
463+
except ValueError as e:
464+
raise ValueError(
465+
"Failed to convert environment variables list to dictionary. "
466+
f"Error: {str(e)}"
467+
) from e
468+
elif not isinstance(environment, dict):
469+
raise TypeError(
470+
"Environment variables must be provided as either a dictionary "
471+
"or a list of strings in the format ['KEY=value']"
472+
)
473+
413474
# These keywords are not supported for various reasons.
414475
unsupported_keys = set(args.keys()).intersection(
415476
(
@@ -466,9 +527,9 @@ def to_bytes(size: Union[int, str, None]) -> Union[int, None]:
466527
try:
467528
return int(size)
468529
except ValueError as bad_size:
469-
mapping = {'b': 0, 'k': 1, 'm': 2, 'g': 3}
470-
mapping_regex = ''.join(mapping.keys())
471-
search = re.search(rf'^(\d+)([{mapping_regex}])$', size.lower())
530+
mapping = {"b": 0, "k": 1, "m": 2, "g": 3}
531+
mapping_regex = "".join(mapping.keys())
532+
search = re.search(rf"^(\d+)([{mapping_regex}])$", size.lower())
472533
if search:
473534
return int(search.group(1)) * (1024 ** mapping[search.group(2)])
474535
raise TypeError(
@@ -497,7 +558,7 @@ def to_bytes(size: Union[int, str, None]) -> Union[int, None]:
497558
"dns_search": pop("dns_search"),
498559
"dns_server": pop("dns"),
499560
"entrypoint": pop("entrypoint"),
500-
"env": pop("environment"),
561+
"env": environment,
501562
"env_host": pop("env_host"), # TODO document, podman only
502563
"expose": {},
503564
"groups": pop("group_add"),
@@ -607,7 +668,7 @@ def to_bytes(size: Union[int, str, None]) -> Union[int, None]:
607668
if _k in bool_options and v is True:
608669
options.append(option_name)
609670
elif _k in regular_options:
610-
options.append(f'{option_name}={v}')
671+
options.append(f"{option_name}={v}")
611672
elif _k in simple_options:
612673
options.append(v)
613674

@@ -709,12 +770,12 @@ def parse_host_port(_container_port, _protocol, _host):
709770

710771
for item in args.pop("volumes", {}).items():
711772
key, value = item
712-
extended_mode = value.get('extended_mode', [])
773+
extended_mode = value.get("extended_mode", [])
713774
if not isinstance(extended_mode, list):
714775
raise ValueError("'extended_mode' value should be a list")
715776

716777
options = extended_mode
717-
mode = value.get('mode')
778+
mode = value.get("mode")
718779
if mode is not None:
719780
if not isinstance(mode, str):
720781
raise ValueError("'mode' value should be a str")
@@ -729,10 +790,10 @@ def parse_host_port(_container_port, _protocol, _host):
729790
params["volumes"].append(volume)
730791
else:
731792
mount_point = {
732-
"destination": value['bind'],
793+
"destination": value["bind"],
733794
"options": options,
734795
"source": key,
735-
"type": 'bind',
796+
"type": "bind",
736797
}
737798
params["mounts"].append(mount_point)
738799

podman/tests/integration/test_container_create.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,44 @@ def test_container_extra_hosts(self):
103103
for hosts_entry in formatted_hosts:
104104
self.assertIn(hosts_entry, logs)
105105

106+
def test_container_environment_variables(self):
107+
"""Test environment variables passed to the container."""
108+
with self.subTest("Check environment variables as dictionary"):
109+
env_dict = {"MY_VAR": "123", "ANOTHER_VAR": "456"}
110+
container = self.client.containers.create(
111+
self.alpine_image, command=["env"], environment=env_dict
112+
)
113+
self.containers.append(container)
114+
115+
container_env = container.attrs.get('Config', {}).get('Env', [])
116+
for key, value in env_dict.items():
117+
self.assertIn(f"{key}={value}", container_env)
118+
119+
container.start()
120+
container.wait()
121+
logs = b"\n".join(container.logs()).decode()
122+
123+
for key, value in env_dict.items():
124+
self.assertIn(f"{key}={value}", logs)
125+
126+
with self.subTest("Check environment variables as list"):
127+
env_list = ["MY_VAR=123", "ANOTHER_VAR=456"]
128+
container = self.client.containers.create(
129+
self.alpine_image, command=["env"], environment=env_list
130+
)
131+
self.containers.append(container)
132+
133+
container_env = container.attrs.get('Config', {}).get('Env', [])
134+
for env in env_list:
135+
self.assertIn(env, container_env)
136+
137+
container.start()
138+
container.wait()
139+
logs = b"\n".join(container.logs()).decode()
140+
141+
for env in env_list:
142+
self.assertIn(env, logs)
143+
106144
def _test_memory_limit(self, parameter_name, host_config_name, set_mem_limit=False):
107145
"""Base for tests which checks memory limits"""
108146
memory_limit_tests = [

0 commit comments

Comments
 (0)