@@ -19,22 +19,18 @@ class TrackedContainer:
1919 Docker client instance
2020 image_name: str
2121 Name of the docker image to launch
22- **kwargs: dict, optional
23- Default keyword arguments to pass to docker.DockerClient.containers.run
2422 """
2523
2624 def __init__ (
2725 self ,
2826 docker_client : docker .DockerClient ,
2927 image_name : str ,
30- ** kwargs : Any ,
3128 ):
3229 self .container : Container | None = None
3330 self .docker_client : docker .DockerClient = docker_client
3431 self .image_name : str = image_name
35- self .kwargs : Any = kwargs
3632
37- def run_detached (self , ** kwargs : Any ) -> Container :
33+ def run_detached (self , ** kwargs : Any ) -> None :
3834 """Runs a docker container using the pre-configured image name
3935 and a mix of the pre-configured container options and those passed
4036 to this method.
@@ -47,18 +43,41 @@ def run_detached(self, **kwargs: Any) -> Container:
4743 **kwargs: dict, optional
4844 Keyword arguments to pass to docker.DockerClient.containers.run
4945 extending and/or overriding key/value pairs passed to the constructor
50-
51- Returns
52- -------
53- docker.Container
5446 """
55- all_kwargs = self .kwargs | kwargs
56- LOGGER .info (f"Running { self .image_name } with args { all_kwargs } ..." )
47+ LOGGER .info (
48+ f"Creating a container for the image: { self .image_name } with args: { kwargs } ..."
49+ )
50+ default_kwargs = {"detach" : True , "tty" : True }
51+ final_kwargs = default_kwargs | kwargs
5752 self .container = self .docker_client .containers .run (
58- self .image_name ,
59- ** all_kwargs ,
53+ self .image_name , ** final_kwargs
6054 )
61- return self .container
55+ LOGGER .info (f"Container { self .container .name } created" )
56+
57+ def get_logs (self ) -> str :
58+ assert self .container is not None
59+ logs = self .container .logs ().decode ()
60+ assert isinstance (logs , str )
61+ return logs
62+
63+ def get_health (self ) -> str :
64+ assert self .container is not None
65+ self .container .reload ()
66+ return self .container .health # type: ignore
67+
68+ def exec_cmd (self , cmd : str , ** kwargs : Any ) -> str :
69+ assert self .container is not None
70+ container = self .container
71+
72+ LOGGER .info (f"Running cmd: `{ cmd } ` on container: { container .name } " )
73+ default_kwargs = {"tty" : True }
74+ final_kwargs = default_kwargs | kwargs
75+ exec_result = container .exec_run (cmd , ** final_kwargs )
76+ output = exec_result .output .decode ().rstrip ()
77+ assert isinstance (output , str )
78+ LOGGER .debug (f"Command output: { output } " )
79+ assert exec_result .exit_code == 0 , f"Command: `{ cmd } ` failed"
80+ return output
6281
6382 def run_and_wait (
6483 self ,
@@ -68,14 +87,15 @@ def run_and_wait(
6887 no_failure : bool = True ,
6988 ** kwargs : Any ,
7089 ) -> str :
71- running_container = self .run_detached (** kwargs )
72- rv = running_container . wait ( timeout = timeout )
73- logs = running_container . logs (). decode ( "utf-8" )
74- assert isinstance ( logs , str )
90+ self .run_detached (** kwargs )
91+ assert self . container is not None
92+ rv = self . container . wait ( timeout = timeout )
93+ logs = self . get_logs ( )
7594 LOGGER .debug (logs )
7695 assert no_warnings == (not self .get_warnings (logs ))
7796 assert no_errors == (not self .get_errors (logs ))
7897 assert no_failure == (rv ["StatusCode" ] == 0 )
98+ self .remove ()
7999 return logs
80100
81101 @staticmethod
@@ -93,8 +113,9 @@ def _lines_starting_with(logs: str, pattern: str) -> list[str]:
93113 def remove (self ) -> None :
94114 """Kills and removes the tracked docker container."""
95115 if self .container is None :
96- LOGGER .info ("No container to remove" )
116+ LOGGER .debug ("No container to remove" )
97117 else :
98118 LOGGER .info (f"Removing container { self .container .name } ..." )
99119 self .container .remove (force = True )
100120 LOGGER .info (f"Container { self .container .name } removed" )
121+ self .container = None
0 commit comments