Skip to content

Commit 6fc6786

Browse files
author
builder
committed
Add Database wrapper for first set of tests
Signed-off-by: builder <build@localhost>
1 parent 571cd2e commit 6fc6786

File tree

6 files changed

+1489
-60
lines changed

6 files changed

+1489
-60
lines changed

container_ci_suite/container_lib.py

Lines changed: 228 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,13 @@
3838
import logging
3939
import urllib.request
4040
from pathlib import Path
41-
from typing import List, Optional
41+
from typing import List, Optional, Literal
4242
from datetime import datetime
4343

4444
from container_ci_suite.engines.podman_wrapper import PodmanCLIWrapper
4545
from container_ci_suite import utils
4646
from container_ci_suite.engines.container import ContainerImage
47+
from container_ci_suite.engines.database import DatabaseWrapper
4748
from container_ci_suite.utils import ContainerTestLibUtils
4849
from 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

Comments
 (0)