17
17
18
18
logger = logging .getLogger ("podman.containers" )
19
19
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_.-]*" )
21
21
22
22
23
23
class CreateMixin : # pylint: disable=too-few-public-methods
@@ -375,14 +375,58 @@ def create(
375
375
payload = api .prepare_body (payload )
376
376
377
377
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 ,
379
381
)
380
382
response .raise_for_status (not_found = ImageNotFound )
381
383
382
384
container_id = response .json ()["Id" ]
383
385
384
386
return self .get (container_id )
385
387
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
+
386
430
# pylint: disable=too-many-locals,too-many-statements,too-many-branches
387
431
@staticmethod
388
432
def _render_payload (kwargs : MutableMapping [str , Any ]) -> dict [str , Any ]:
@@ -410,6 +454,23 @@ def _render_payload(kwargs: MutableMapping[str, Any]) -> dict[str, Any]:
410
454
with suppress (KeyError ):
411
455
del args [key ]
412
456
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
+
413
474
# These keywords are not supported for various reasons.
414
475
unsupported_keys = set (args .keys ()).intersection (
415
476
(
@@ -466,9 +527,9 @@ def to_bytes(size: Union[int, str, None]) -> Union[int, None]:
466
527
try :
467
528
return int (size )
468
529
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 ())
472
533
if search :
473
534
return int (search .group (1 )) * (1024 ** mapping [search .group (2 )])
474
535
raise TypeError (
@@ -497,7 +558,7 @@ def to_bytes(size: Union[int, str, None]) -> Union[int, None]:
497
558
"dns_search" : pop ("dns_search" ),
498
559
"dns_server" : pop ("dns" ),
499
560
"entrypoint" : pop ("entrypoint" ),
500
- "env" : pop ( " environment" ) ,
561
+ "env" : environment ,
501
562
"env_host" : pop ("env_host" ), # TODO document, podman only
502
563
"expose" : {},
503
564
"groups" : pop ("group_add" ),
@@ -607,7 +668,7 @@ def to_bytes(size: Union[int, str, None]) -> Union[int, None]:
607
668
if _k in bool_options and v is True :
608
669
options .append (option_name )
609
670
elif _k in regular_options :
610
- options .append (f' { option_name } ={ v } ' )
671
+ options .append (f" { option_name } ={ v } " )
611
672
elif _k in simple_options :
612
673
options .append (v )
613
674
@@ -709,12 +770,12 @@ def parse_host_port(_container_port, _protocol, _host):
709
770
710
771
for item in args .pop ("volumes" , {}).items ():
711
772
key , value = item
712
- extended_mode = value .get (' extended_mode' , [])
773
+ extended_mode = value .get (" extended_mode" , [])
713
774
if not isinstance (extended_mode , list ):
714
775
raise ValueError ("'extended_mode' value should be a list" )
715
776
716
777
options = extended_mode
717
- mode = value .get (' mode' )
778
+ mode = value .get (" mode" )
718
779
if mode is not None :
719
780
if not isinstance (mode , str ):
720
781
raise ValueError ("'mode' value should be a str" )
@@ -729,10 +790,10 @@ def parse_host_port(_container_port, _protocol, _host):
729
790
params ["volumes" ].append (volume )
730
791
else :
731
792
mount_point = {
732
- "destination" : value [' bind' ],
793
+ "destination" : value [" bind" ],
733
794
"options" : options ,
734
795
"source" : key ,
735
- "type" : ' bind' ,
796
+ "type" : " bind" ,
736
797
}
737
798
params ["mounts" ].append (mount_point )
738
799
0 commit comments