This commit is contained in:
2025-12-31 21:54:26 -08:00
parent eac6faf073
commit b2cae6f897
2 changed files with 91 additions and 19 deletions

1
README.md Normal file
View File

@@ -0,0 +1 @@
# Composer

View File

@@ -16,8 +16,13 @@ logger.setLevel(logging.DEBUG) #TODO: add something to change log levles
class Setup:
"""Setup class ensuring appropriate files and symlinks are created for a Composer instance"""
@staticmethod
def create_symlink():
"""Creates a symlink between the path stored in HOST_DATA_PATH and store/data"""
host_data_env = getenv("HOST_DATA_PATH", None)
if host_data_env is None:
logger.info(f"HOST_DATA_PATH is not set, symlink not created")
@@ -35,42 +40,65 @@ class Setup:
@staticmethod
def populate_store():
"""Creates empty store/stack/.env file if it doesn't exist"""
Path("store/stack").mkdir(parents=True, exist_ok=True)
Path("store/stack/.env").touch(exist_ok=True)
class Composer:
"""Composer instance to manage a single docker compose stack
Attributes:
docker: DockerClient instance used to interact with the compose stack
services: list of Service objects defined by the stack
"""
services = []
docker = DockerClient(
compose_files=["store/stack/compose.yml"],
compose_env_files=["store/stack/.env"]
)
def __init__(self, remote_url):
"""Initializes the Composer based on a remote_url
Args:
remote_url: string containing the https location of the repo
"""
if not Path("store/stack/.git").exists():
self.repo = Repo.init("store/stack")
self._add_remote(remote_url)
self.repo.create_remote('origin', url)
else:
self.repo = Repo("store/stack")
self.repo.remotes.origin.pull('master')
self.docker = DockerClient(
compose_files=["store/stack/compose.yml"],
compose_env_files=["store/stack/.env"]
)
if self.docker.compose.config(return_json=True)["name"] != "stack":
logger.warn(f"Composer stack name is wrong, either make sure it is set manually or keep it store/stack")
self.start_update_job()
self._create_services()
self.set_status("up")
logger.info("Composer started and services created")
def _add_remote(self, url):
self.repo.create_remote('origin', url)
def _create_services(self):
"""Recreates service list after the Composer stack is updated"""
for service in self.services:
self.services[service].close()
services_dict = self.docker.compose.config().services
self.services = {service: Service(service, services_dict[service].labels, self) for service in services_dict}
def start_update_job(self):
"""Schedules a recurring update based on the UPDATE environment variable
Each update occurs after an interval of UPDATE minutes. If UPDATE does not contain a valid integer in minutes, defaults to 1 minute.
"""
update_time = getenv("UPDATE", 1)
try:
self.update_time = int(update_time)
@@ -81,6 +109,13 @@ class Composer:
schedule.every(self.update_time).minutes.do(self.update)
def set_status(self, status):
"""Sets the running status of the compose stack
Args:
status: string containing "up", "down", or "restart" corresponding to the respective running state of the composer
"""
try:
if status == "up":
self.docker.compose.up(
@@ -103,6 +138,9 @@ class Composer:
logger.critical(f"Setting status of composer failed: {error}")
def update(self):
"""Updates the Composer stack based on the remote repo"""
current = self.repo.head.commit
self.repo.remotes.origin.pull('master')
if current != self.repo.head.commit:
@@ -112,23 +150,54 @@ class Composer:
class Service:
update_task = None
"""Service instance meant to represent a container in a Composer stack
Attributes:
labels: labels given to the service
name: name given to the service
parent: Composer instance containing the service
"""
def __init__(self, name, labels, parent):
"""Initializes the Service instance
Args:
name: name given to the service
labels: labels given to the service
parent: Composer instance containing the service
"""
self.name = name
self.labels = labels
self.parent = parent
self.update_task = self.start_update_job()
self._update_task = self.start_update_job()
def close(self):
"""Safely removes the service instance"""
if self.update_time is None:
return None
schedule.cancel_job(self.update_task)
schedule.cancel_job(self._update_task)
def get_status(self):
"""Gets the running status of the service
Returns:
"up" or "down" depending on the running status of the service
"""
return "up" if bool(self.parent.docker.compose.ps(services=[self.name])) else "down"
def start_update_job(self):
"""Schedules a recurring update based on the composer.update label
Each update occurs after an interval of composer.update minutes. If composer.update does not contain a valid integer in minutes, defaults to the parent Composer's update_time.
"""
self.update_time = self.parent.update_time
if 'composer.update' in self.labels:
try:
@@ -142,6 +211,13 @@ class Service:
return None
def set_status(self, status):
"""Sets the running status of the service
Args:
status: string containing "up", "down", or "restart" corresponding to the respective running state of the service
"""
try:
if status == "up":
self.parent.docker.compose.up(
@@ -166,14 +242,9 @@ class Service:
logger.critical(error)
def update(self, restart=True):
try:
self.parent.docker.compose.build(
quiet=True,
services=[self.name]
)
except DockerException:
logger.warn(f"Failed to build docker image for service {self.name} - cancelling container update")
return None
"""Updates the Composer stack based on container images"""
self.parent.docker.compose.pull(
ignore_pull_failures=True,
quiet=True,