#!/usr/bin/python3 from __future__ import annotations from functools import lru_cache from pathlib import Path from subprocess import call, check_output from datetime import datetime import logging KEEP_DAYS = 3 def main(): setup_logger() build_images() update_containers() prune() @lru_cache(maxsize=1) def get_today() -> str: return datetime.now().strftime('%Y%m%d') class Image: def __init__(self, base_name: str): self._base_name = base_name @property def qual_name(self) -> str: return f'{self._base_name}-panthera' @property def latest(self) -> str: return f'{self.qual_name}:latest' @property def today(self) -> str: return f'{self.qual_name}:{get_today()}' @classmethod def from_file(cls, path: Path) -> list[Image]: return [cls(name) for name in read_file(path)] @property def _work_dir(self) -> Path: return Path(f'../images/{self._base_name}') def build(self): if self._todays_image_exists: logging.info(f'don\'t need to build {self._base_name}') else: logging.info(f'building {self._base_name}') self._build_image() self._tag_image() @property def _todays_image_exists(self) -> bool: for image in images_on_host(): if self.qual_name in image and get_today() in image: return True else: return False def _build_image(self): call(['docker', 'build', '--no-cache', f'--tag={self.today}', '.'], cwd=self._work_dir) def _tag_image(self): call(['docker', 'tag', f'{self.today}', f'{self.latest}']) class Container: def __init__(self, name: str): self._name = name @property def name(self) -> str: return self._name @property def _work_dir(self) -> Path: return Path(f'../container/{self.name}') def _pull_images(self) -> None: logging.info(' pulling images') if not self._work_dir.is_dir(): print(f"Skipping {self._name}, {self._work_dir} does not exist") return call(['docker-compose', 'pull', '--ignore-pull-failures', '--quiet'], cwd=self._work_dir) def _up(self) -> None: logging.info(' bringing up') call(['docker-compose', 'up', '--detach'], cwd=self._work_dir) def update(self) -> None: logging.info(f'container: {self.name}') self._pull_images() self._up() @classmethod def from_file(cls, path: Path) -> list[Container]: return [cls(name) for name in read_file(path)] def build_images() -> None: logging.info('building images') images = Image.from_file(Path('images.conf')) for image in images: image.build() def update_containers() -> None: logging.info('updating containers') containers = Container.from_file(Path('container.conf')) for container in containers: container.update() @lru_cache(maxsize=1) def images_on_host() -> list[str]: result = check_output(['docker', 'images']).decode('utf-8').strip() images = result.split('\n') for image in images: logging.debug(image) return images def read_file(path: Path) -> list[str]: content = path.read_text().strip() elements = content.split('\n') elements = [item for item in elements if not item.startswith('#')] return elements def prune() -> None: logging.info('pruning') hours = int(KEEP_DAYS * 24) call(['docker', 'system', 'prune', '--all', '--force', '--filter', f'until={hours}h']) def setup_logger() -> None: logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO) logging.info('set up logger') if __name__ == "__main__": main()