import asyncio
import json
import os
from typing import Dict, List, Optional
from ceph_devstack import config, logger
from ceph_devstack.resources import PodmanResource
class Container(PodmanResource):
network: str
secret: List[str]
cmd_vars: List[str] = ["name", "image", "image_name", "image_tag"]
build_cmd: List[str] = [
"podman",
"build",
"-t",
"{image_name}:{image_tag}",
".",
]
create_cmd: List[str] = ["podman", "container", "create", "{name}"]
remove_cmd: List[str] = ["podman", "container", "rm", "-f", "{name}"]
start_cmd: List[str] = ["podman", "container", "start", "{name}"]
stop_cmd: List[str] = ["podman", "container", "stop", "{name}"]
exists_cmd: List[str] = ["podman", "container", "inspect", "{name}"]
pull_cmd: List[str] = ["podman", "pull", "{image}"]
wait_cmd: List[str] = ["podman", "wait", "{name}"]
env_vars: Dict[str, Optional[str]] = {}
_image_name: str | None = None
def __init__(self, name: str = ""):
super().__init__(name)
self.env_vars = {**self.__class__.env_vars}
for key in self.env_vars:
if os.environ.get(key):
self.env_vars[key] = os.environ[key]
def add_env_to_args(self, args: List):
args = super().format_cmd(args)
for key, value in self.env_vars.items():
if not value:
continue
args.insert(-1, "-e")
args.insert(-1, f"{key}={value}")
return args
@property
def config(self):
return config["containers"].get(self.__class__.__name__.lower(), {})
@property
def image_name(self) -> str:
if self._image_name is not None:
return self._image_name
return self.__class__.__name__.lower()
@property
def image(self):
if self.repo:
return f"localhost/{self.image_name}"
return self.config["image"]
@property
def image_tag(self):
if ":" not in self.image:
return "latest"
return self.image.split(":")[-1]
@property
def repo(self):
repo = self.config.get("repo", "")
try:
return repo.expanduser()
except AttributeError:
return os.path.expanduser(repo)
@property
def cwd(self):
return self.repo or "."
async def pull(self):
if not getattr(self, "pull_cmd", None):
return
if self.image.startswith("localhost/"):
return
logger.debug(f"{self.name}: pulling from: {self.image}")
await self.cmd(
self.format_cmd(self.pull_cmd),
check=True,
stream_output=True,
)
async def build(self):
if not getattr(self, "repo", None):
return
logger.debug(f"{self.name}: building from repo: {self.repo}")
await self.cmd(
self.format_cmd(self.build_cmd),
check=True,
stream_output=True,
)
logger.debug(f"{self.name}: built")
async def create(self):
if not getattr(self, "create_cmd", None):
return
if await self.exists():
return
args = self.add_env_to_args(self.format_cmd(self.create_cmd))
logger.debug(f"{self.name}: creating")
await self.cmd(
args,
check=True,
stream_output=True,
)
logger.debug(f"{self.name}: created")
async def start(self):
if not getattr(self, "start_cmd", None):
return
logger.debug(f"{self.name}: starting")
await self.cmd(
self.format_cmd(self.start_cmd),
check=True,
stream_output=True,
)
if "--health-cmd" in self.create_cmd or "--healthcheck-cmd" in self.create_cmd:
rc = None
while rc != 0:
proc = await self.cmd(
self.format_cmd(["podman", "healthcheck", "run", "{name}"]),
)
rc = await proc.wait()
await asyncio.sleep(1)
logger.debug(f"{self.name}: started")
async def stop(self):
if not getattr(self, "stop_cmd", None):
return
logger.debug(f"{self.name}: stopping")
await self.cmd(
self.format_cmd(self.stop_cmd),
stream_output=True,
)
logger.debug(f"{self.name}: stopping")
async def remove(self):
if not getattr(self, "remove_cmd", None):
return
logger.debug(f"{self.name}: removing")
await super().remove()
logger.debug(f"{self.name}: removed")
async def is_running(self):
proc = await self.cmd(self.format_cmd(self.exists_cmd))
assert proc.stdout is not None
if not await self.exists():
return False
result = json.loads(await proc.stdout.read())
if not result:
return False
return result[0]["State"]["Status"].lower() == "running"
async def wait(self) -> Optional[int]:
proc = await self.cmd(self.format_cmd(self.wait_cmd))
out, err = await proc.communicate()
if proc.returncode:
logger.error(f"Could not wait for {self.name}: {err.decode().strip()}")
return proc.returncode
return int(out.decode().strip())