diff --git a/composer.py b/composer.py new file mode 100644 index 0000000..3645a92 --- /dev/null +++ b/composer.py @@ -0,0 +1,128 @@ +import os +from pathlib import Path +from typing import List, Dict +import logging +from docker_wrapper import Docker + + +class ConfigError(Exception): + """Custom exception for configuration related errors.""" + pass + + +class DockerComposer: + def __init__(self, config_file: str = 'docker-composer.conf'): + """Initialize DockerComposer with configuration file.""" + self.logger = self._setup_logger() + self.working_dir = Path.cwd() + self.config = self._load_config(config_file) + + def _setup_logger(self) -> logging.Logger: + """Set up logging configuration.""" + logger = logging.getLogger('DockerComposer') + logger.setLevel(logging.INFO) + + formatter = logging.Formatter('%(message)s') + handler = logging.StreamHandler() + handler.setFormatter(formatter) + logger.addHandler(handler) + + return logger + + def _load_config(self, config_file: str) -> Dict[str, any]: + """Load and parse configuration file.""" + if not os.path.exists(config_file): + raise ConfigError(f"Configuration file '{config_file}' not found") + + try: + with open(config_file, 'rt') as f: + lines = f.readlines() + + config = {} + + # Parse compose path + compose_path = self._parse_config_line(lines[0], 'compose-path') + compose_path = Path(compose_path if compose_path.startswith('/') + else self.working_dir / compose_path) + config['compose_path'] = compose_path.resolve() + + # Parse exclude containers + exclude_str = self._parse_config_line(lines[1], 'exclude-containers') + config['exclude_containers'] = [ + container.strip() + for container in exclude_str.split(',') + if container.strip() + ] + + return config + + except IndexError: + raise ConfigError("Configuration file is incomplete") + except Exception as e: + raise ConfigError(f"Failed to load configuration: {str(e)}") + + def _parse_config_line(self, line: str, key: str) -> str: + """Parse a configuration line to extract its value.""" + if '=' not in line: + raise ConfigError(f"Invalid configuration format for {key}") + return line.split('=', 1)[1].strip() + + def _get_compose_directories(self) -> Dict[str, Path]: + """Get valid Docker compose directories.""" + compose_dirs = {} + compose_path = self.config['compose_path'] + + if not compose_path.exists(): + raise ConfigError(f"Compose path '{compose_path}' does not exist") + + for item in compose_path.iterdir(): + # Skip if not a directory or hidden + if not item.is_dir() or item.name.startswith('.'): + continue + + # Skip excluded containers + if item.name in self.config['exclude_containers']: + self.logger.info(f"Skipping excluded container: {item.name}") + continue + + # Check for docker-compose.yml + if not (item / 'docker-compose.yml').exists(): + self.logger.warning(f"Skipping {item.name}: no docker-compose.yml found") + continue + + compose_dirs[item.name] = item + + return compose_dirs + + def compose(self) -> None: + """Execute Docker compose for all valid directories.""" + compose_dirs = self._get_compose_directories() + + if not compose_dirs: + self.logger.warning("No valid compose directories found") + return + + for container_name, directory in compose_dirs.items(): + try: + self.logger.info(f"COMPOSING {container_name}...") + Docker.compose(str(directory)) + self.logger.info("DONE!") + except Exception as e: + self.logger.error(f"Failed to compose {container_name}: {str(e)}") + + +def main(): + """Main entry point for the Docker composer.""" + try: + composer = DockerComposer() + composer.compose() + except ConfigError as e: + print(f"Configuration error: {str(e)}") + exit(1) + except Exception as e: + print(f"Unexpected error: {str(e)}") + exit(1) + + +if __name__ == "__main__": + main() \ No newline at end of file