3838import logging
3939import urllib .request
4040from pathlib import Path
41- from typing import List , Optional
41+ from typing import List , Optional , Literal
4242from datetime import datetime
4343
4444from container_ci_suite .engines .podman_wrapper import PodmanCLIWrapper
4545from container_ci_suite import utils
4646from container_ci_suite .engines .container import ContainerImage
47+ from container_ci_suite .engines .database import DatabaseWrapper
4748from container_ci_suite .utils import ContainerTestLibUtils
4849from container_ci_suite .utils import (
4950 get_full_ca_file_path ,
@@ -73,6 +74,7 @@ def __init__(
7374 podman_build_log : Path = None ,
7475 app_id_file_dir : Path = None ,
7576 cid_file_dir : Path = None ,
77+ db_type : str = "mysql" ,
7678 ):
7779 """Initialize the container test library."""
7880 self .app_id_file_dir = (
@@ -86,10 +88,12 @@ def __init__(
8688 else Path (tempfile .mkdtemp (prefix = "cid_files_" ))
8789 )
8890 self .image_name = image_name
91+ self .db_type = db_type
8992 self .s2i_image : bool = s2i_image
9093 self ._lib = None
9194 self .app_name = app_name
9295 self .podman_build_log : Path = podman_build_log
96+ self ._db_lib = None
9397
9498 @property
9599 def lib (self ):
@@ -101,8 +105,23 @@ def lib(self):
101105 )
102106 return self ._lib
103107
108+ @property
109+ def db_lib (self ):
110+ if not self ._db_lib :
111+ self ._db_lib = DatabaseWrapper (
112+ image_name = self .image_name , db_type = self .db_type
113+ )
114+ return self ._db_lib
115+
104116 def set_new_image (self , image_name ):
105117 self .image_name = image_name
118+ self .db_lib .image_name = image_name
119+
120+ def set_new_db_type (
121+ self , db_type : Literal ["mysql" , "mariadb" , "postgresql" , "postgres" ] = "mysql"
122+ ):
123+ self .db_lib .db_type = db_type
124+ self .db_type = db_type
106125
107126 def cleanup (self ) -> None :
108127 """
@@ -335,60 +354,227 @@ def get_cid(self, cid_file_name: str) -> str:
335354 def get_cip (self , cid_file_name : str = "app_dockerfile" ) -> str :
336355 return self .lib .get_cip (cid_file_name = cid_file_name )
337356
338- def assert_container_creation_fails (self , container_args : str ) -> bool :
357+ def test_db_connection (
358+ self ,
359+ container_ip : str = "" ,
360+ username : str = "user" ,
361+ password : str = "pass" ,
362+ database : str = "db" ,
363+ max_attempts : int = 60 ,
364+ sleep_time : int = 3 ,
365+ ) -> bool :
366+ return self .db_lib .test_connection (
367+ container_ip = container_ip ,
368+ username = username ,
369+ password = password ,
370+ database = database ,
371+ max_attempts = max_attempts ,
372+ sleep_time = sleep_time ,
373+ )
374+
375+ def assert_container_creation_fails (
376+ self , cid_file_name : str , container_args : List [str ] | str , command : str
377+ ) -> bool :
339378 """
340379 Assert that container creation should fail.
341380
342381 Args:
382+ cid_file_name: Name for the cid_file_name
343383 container_args: Container arguments
344384
345385 Returns:
346386 True if container creation failed as expected, False otherwise
347387 """
348- cid_file = "assert"
349388 max_attempts = 10
389+ if isinstance (container_args , list ):
390+ container_args = " " .join (container_args )
391+
392+ if self .create_container (
393+ cid_file_name = cid_file_name , container_args = container_args , command = command
394+ ):
395+ container_id = self .get_cid (cid_file_name )
396+ attempt = 1
397+ while attempt <= max_attempts :
398+ if not ContainerImage .is_container_running (container_id ):
399+ break
400+ time .sleep (2 )
401+ attempt += 1
402+ if attempt > max_attempts :
403+ PodmanCLIWrapper .call_podman_command (
404+ cmd = f"stop { container_id } " , ignore_error = True
405+ )
406+ return False
407+ # Check exit status
408+ try :
409+ exit_status = PodmanCLIWrapper .call_podman_command (
410+ cmd = f"inspect -f '{{{{.State.ExitCode}}}}' { container_id } " ,
411+ return_output = True ,
412+ ).strip ()
413+ if exit_status == "0" :
414+ return False
415+ except subprocess .CalledProcessError :
416+ pass
417+
418+ # Clean up
419+ PodmanCLIWrapper .call_podman_command (
420+ cmd = f"rm -v { container_id } " , ignore_error = True
421+ )
422+ cid_path = self .cid_file_dir / cid_file_name
423+ if cid_path .exists ():
424+ cid_path .unlink ()
425+ return True
426+ return False
427+
428+ def assert_container_creation_succeeds (
429+ self ,
430+ container_args : List [str ] | str ,
431+ command : str = "" ,
432+ test_connection_func = None ,
433+ connection_params : dict = None ,
434+ ) -> bool :
435+ """
436+ Assert that container creation succeeds and optionally test connection.
437+
438+ This is a Python/PyTest conversion of the bash function from
439+ postgresql-container/test/run_test (lines 247-283):
440+
441+ ```bash
442+ assert_container_creation_succeeds ()
443+ {
444+ local check_env=false
445+ local name=pg-success-"$(ct_random_string)"
446+ local PGUSER='' PGPASS='' DB='' ADMIN_PASS=
447+ local docker_args=
448+ local ret=0
449+
450+ for arg; do
451+ docker_args+=" $(printf "%q" "$arg")"
452+ if $check_env; then
453+ local env=${arg//=*/}
454+ local val=${arg//$env=/}
455+ case $env in
456+ POSTGRESQL_ADMIN_PASSWORD) ADMIN_PASS=$val ;;
457+ POSTGRESQL_USER) PGUSER=$val ;;
458+ POSTGRESQL_PASSWORD) PGPASS=$val ;;
459+ POSTGRESQL_DATABASE) DB=$val ;;
460+ esac
461+ check_env=false
462+ elif test "$arg" = -e; then
463+ check_env=:
464+ fi
465+ done
466+
467+ DOCKER_ARGS=$docker_args create_container "$name" || ret=1
468+
469+ if test -n "$PGUSER" && test -n "$PGPASS"; then
470+ PGUSER=$PGUSER PASS=$PGPASS DB=$DB test_connection "$name" || ret=2
471+ fi
472+
473+ if test -n "$ADMIN_PASS"; then
474+ PGUSER=postgres PASS=$ADMIN_PASS DB=$DB test_connection "$name" || ret=3
475+ fi
476+
477+ return $ret
478+ }
479+ ```
480+
481+ Args:
482+ container_args: Container arguments (can be list or string)
483+ command: Command to run in container (optional)
484+ test_connection_func: Optional function to test connection after creation
485+ connection_params: Optional parameters for connection test function
486+
487+ Returns:
488+ True if container creation succeeded, False otherwise
489+
490+ Example:
491+ >>> ct = ContainerTestLib(image_name="postgres:13")
492+ >>> # Basic usage - just test creation
493+ >>> assert ct.assert_container_creation_succeeds(
494+ ... "-e POSTGRESQL_USER=user -e POSTGRESQL_PASSWORD=pass -e POSTGRESQL_DATABASE=db"
495+ ... )
496+
497+ >>> # With connection testing
498+ >>> from container_ci_suite.engines.database import DatabaseWrapper
499+ >>> db = DatabaseWrapper(image_name="postgres:13", db_type="postgresql")
500+ >>> def test_conn(cid_file, params):
501+ ... ct = params['ct']
502+ ... db = params['db']
503+ ... container_ip = ct.get_cip(cid_file)
504+ ... return db.test_connection(container_ip, params['user'], params['pass'])
505+ >>>
506+ >>> assert ct.assert_container_creation_succeeds(
507+ ... "-e POSTGRESQL_USER=user -e POSTGRESQL_PASSWORD=pass",
508+ ... test_connection_func=test_conn,
509+ ... connection_params={'ct': ct, 'db': db, 'user': 'user', 'pass': 'pass'}
510+ ... )
511+ """
512+ # Generate unique container name
513+ cid_file = f"success-{ self .random_string (length = 10 )} "
350514
351- old_container_args = getattr (self , "container_args" , "" )
352- self .container_args = container_args
515+ # Convert list to string if needed
516+ if isinstance (container_args , list ):
517+ container_args = " " .join (container_args )
518+
519+ if not container_args :
520+ logging .error ("Container arguments cannot be empty" )
521+ return False
353522
354523 try :
355- if self .create_container (cid_file ):
356- container_id = self .get_cid (cid_file )
357-
358- attempt = 1
359- while attempt <= max_attempts :
360- if not ContainerImage .is_container_running (container_id ):
361- break
362- time .sleep (2 )
363- attempt += 1
364- if attempt > max_attempts :
365- PodmanCLIWrapper .call_podman_command (
366- cmd = f"stop { container_id } " , ignore_error = True
367- )
368- return False
524+ # Create the container
525+ logging .info (f"Creating container with args: { container_args } " )
526+ if not self .create_container (
527+ cid_file_name = cid_file , container_args = container_args , command = command
528+ ):
529+ logging .error ("Failed to create container" )
530+ return False
531+
532+ # Get container ID
533+ container_id = self .get_cid (cid_file )
534+ logging .info (f"Container created successfully: { container_id } " )
369535
370- # Check exit status
536+ # Wait a bit for container to start
537+ time .sleep (2 )
538+
539+ # Check if container is running
540+ if not ContainerImage .is_container_running (container_id ):
541+ logging .error ("Container is not running" )
542+ # Check exit status for debugging
371543 try :
372544 exit_status = PodmanCLIWrapper .call_podman_command (
373545 cmd = f"inspect -f '{{{{.State.ExitCode}}}}' { container_id } " ,
374546 return_output = True ,
375547 ).strip ()
376- if exit_status == "0" :
377- return False
548+ logging .error (f"Container exited with status: { exit_status } " )
549+ # Dump logs for debugging
550+ logs = PodmanCLIWrapper .call_podman_command (
551+ cmd = f"logs { container_id } " ,
552+ return_output = True ,
553+ ignore_error = True ,
554+ )
555+ logging .error (f"Container logs:\n { logs } " )
378556 except subprocess .CalledProcessError :
379557 pass
558+ return False
380559
381- # Clean up
382- PodmanCLIWrapper .call_podman_command (
383- cmd = f"rm -v { container_id } " , ignore_error = True
384- )
385- cid_path = self .cid_file_dir / cid_file
386- if cid_path .exists ():
387- cid_path .unlink ()
388- finally :
389- if old_container_args :
390- self .container_args = old_container_args
391- return True
560+ # If connection test function is provided, test the connection
561+ if test_connection_func and connection_params :
562+ logging .info ("Testing connection..." )
563+ try :
564+ if not test_connection_func (cid_file , connection_params ):
565+ logging .error ("Connection test failed" )
566+ return False
567+ logging .info ("Connection test passed" )
568+ except Exception as e :
569+ logging .error (f"Connection test raised exception: { e } " )
570+ return False
571+
572+ logging .info ("Container creation succeeded" )
573+ return True
574+
575+ except Exception as e :
576+ logging .error (f"Error during container creation test: { e } " )
577+ return False
392578
393579 def run_command (
394580 self , cmd : str , return_output : bool = True , ignore_errors : bool = False
@@ -417,24 +603,29 @@ def test_app_dockerfile(self):
417603 return self .lib .test_app_dockerfile ()
418604
419605 def create_container (
420- self , cid_file_name : str = "" , container_args : str = "" , command : str = ""
606+ self ,
607+ cid_file_name : str = "" ,
608+ container_args : str = "" ,
609+ docker_args : str = "" ,
610+ command : str = "" ,
421611 ) -> bool :
422612 """
423613 Create a container.
424614 Args:
425615 cid_file_name: Name for the cid_file_name
616+ docker_args: Docker arguments to run in container
426617 command: Command to run in container
427618 container_args: Additional container arguments
428619 Returns:
429620 True if container created successfully, False otherwise
430621 """
431- if not container_args :
432- container_args = getattr ( self , "container_args" , "" )
622+ if isinstance ( container_args , list ) :
623+ container_args = " " . join ( container_args )
433624 if not self .cid_file_dir .exists ():
434625 self .cid_file_dir = Path (tempfile .mkdtemp (prefix = "cid_files_" ))
435626 full_cid_file_name : Path = self .cid_file_dir / cid_file_name
436627 try :
437- cmd = f"run --cidfile={ full_cid_file_name } -d { container_args } { self .image_name } { command } "
628+ cmd = f"run { docker_args } --cidfile={ full_cid_file_name } -d { container_args } { self .image_name } { command } "
438629 logging .info (f"Command to create container is '{ cmd } '." )
439630 PodmanCLIWrapper .call_podman_command (cmd = cmd , return_output = True )
440631 if not ContainerImage .wait_for_cid (cid_file_name = full_cid_file_name ):
0 commit comments