Add re_compose.py
This commit is contained in:
parent
aeb4fce709
commit
c3bb13622f
1 changed files with 207 additions and 0 deletions
207
re_compose.py
Normal file
207
re_compose.py
Normal file
|
@ -0,0 +1,207 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Optional
|
||||
import logging
|
||||
from docker_wrapper import Docker, NoContainersError
|
||||
|
||||
|
||||
class ConfigError(Exception):
|
||||
"""Custom exception for configuration related errors."""
|
||||
pass
|
||||
|
||||
|
||||
class DockerComposeManager:
|
||||
def __init__(self, config_file: str = 'docker-composer.conf', debug: bool = False):
|
||||
"""
|
||||
Initialize Docker Compose Manager.
|
||||
|
||||
Args:
|
||||
config_file: Path to configuration file
|
||||
debug: Enable debug logging
|
||||
"""
|
||||
self.debug = debug
|
||||
self.logger = self._setup_logger()
|
||||
self.config = self._load_config(config_file)
|
||||
self.compose_dirs = self._get_compose_dirs()
|
||||
self.containers = self._get_containers()
|
||||
|
||||
def _setup_logger(self) -> logging.Logger:
|
||||
"""Configure logging."""
|
||||
logger = logging.getLogger('DockerComposeManager')
|
||||
logger.setLevel(logging.DEBUG if self.debug else logging.INFO)
|
||||
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
||||
return logger
|
||||
|
||||
def _load_config(self, config_file: str) -> Dict[str, any]:
|
||||
"""
|
||||
Load configuration from file.
|
||||
|
||||
Args:
|
||||
config_file: Path to configuration file
|
||||
|
||||
Returns:
|
||||
Dictionary containing configuration
|
||||
|
||||
Raises:
|
||||
ConfigError: If configuration file is invalid or missing
|
||||
"""
|
||||
try:
|
||||
if not os.path.exists(config_file):
|
||||
raise ConfigError(f"Configuration file '{config_file}' not found")
|
||||
|
||||
with open(config_file, 'rt') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
if len(lines) < 2:
|
||||
raise ConfigError("Incomplete configuration file")
|
||||
|
||||
config = {}
|
||||
working_dir = Path.cwd()
|
||||
|
||||
# Parse compose path
|
||||
compose_path = lines[0][lines[0].find('=')+1:].strip()
|
||||
compose_path = Path(compose_path if compose_path.startswith('/')
|
||||
else working_dir / compose_path)
|
||||
config['compose_path'] = compose_path.resolve()
|
||||
|
||||
# Parse exclude containers
|
||||
exclude_str = lines[1][lines[1].find('=')+1:].strip()
|
||||
config['exclude_containers'] = [
|
||||
container.strip()
|
||||
for container in exclude_str.split(',')
|
||||
if container.strip()
|
||||
]
|
||||
|
||||
if self.debug:
|
||||
self.logger.debug(f"Working directory: {working_dir}")
|
||||
self.logger.debug(f"Compose path: {config['compose_path']}")
|
||||
self.logger.debug(f"Exclude containers: {config['exclude_containers']}")
|
||||
|
||||
return config
|
||||
|
||||
except Exception as e:
|
||||
raise ConfigError(f"Failed to load configuration: {str(e)}")
|
||||
|
||||
def _get_compose_dirs(self) -> List[Path]:
|
||||
"""Get list of valid compose directories."""
|
||||
compose_dirs = []
|
||||
|
||||
try:
|
||||
for item in self.config['compose_path'].iterdir():
|
||||
if (item.is_dir() and
|
||||
not item.name.startswith('.') and
|
||||
item.name not in self.config['exclude_containers']):
|
||||
compose_dirs.append(item)
|
||||
|
||||
if self.debug:
|
||||
self.logger.debug(f"Compose directories: {compose_dirs}")
|
||||
|
||||
return sorted(compose_dirs)
|
||||
|
||||
except Exception as e:
|
||||
raise ConfigError(f"Failed to get compose directories: {str(e)}")
|
||||
|
||||
def _get_containers(self) -> List[str]:
|
||||
"""Get list of container names from compose directories."""
|
||||
return [dir.name for dir in self.compose_dirs]
|
||||
|
||||
def display_menu(self) -> None:
|
||||
"""Display container selection menu."""
|
||||
print("\nWhat Docker container would you like to (re-)compose?")
|
||||
for idx, container in enumerate(self.containers):
|
||||
print(f" {idx} - {container}")
|
||||
print(" q - quit")
|
||||
|
||||
def recompose_container(self, container_index: int) -> bool:
|
||||
"""
|
||||
Recompose a specific container.
|
||||
|
||||
Args:
|
||||
container_index: Index of container to recompose
|
||||
|
||||
Returns:
|
||||
bool: True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
container = self.containers[container_index]
|
||||
container_dir = self.compose_dirs[container_index]
|
||||
|
||||
self.logger.info(f"\nRecomposing {container}...")
|
||||
|
||||
# Stop and remove existing container if it exists
|
||||
if Docker.containers_exist():
|
||||
self.logger.info("Stopping container...")
|
||||
Docker.stop(container)
|
||||
self.logger.info("Removing container...")
|
||||
Docker.rm(container)
|
||||
|
||||
# Compose new container
|
||||
self.logger.info("Composing container...")
|
||||
status, output = Docker.compose(str(container_dir))
|
||||
|
||||
if status == 0:
|
||||
self.logger.info("Container recomposed successfully!")
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"Failed to recompose container: {output}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error recomposing container: {str(e)}")
|
||||
return False
|
||||
|
||||
def run(self) -> None:
|
||||
"""Main application loop."""
|
||||
try:
|
||||
while True:
|
||||
self.display_menu()
|
||||
selection = input().strip()
|
||||
|
||||
if selection.lower() == 'q':
|
||||
self.logger.info("\nExiting...")
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
container_index = int(selection)
|
||||
if 0 <= container_index < len(self.containers):
|
||||
if self.recompose_container(container_index):
|
||||
response = input('\nWould you like to (re-)compose another container? (y/N) ').strip().lower()
|
||||
if response not in ['y', 'yes']:
|
||||
self.logger.info("\nExiting...")
|
||||
break
|
||||
else:
|
||||
self.logger.error("Invalid container index")
|
||||
except ValueError:
|
||||
self.logger.error("Invalid selection. Please enter a number or 'q'")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
self.logger.info("\nOperation cancelled by user")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Unexpected error: {str(e)}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
try:
|
||||
manager = DockerComposeManager(debug=False)
|
||||
manager.run()
|
||||
except ConfigError as e:
|
||||
print(f"Configuration error: {str(e)}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Unexpected error: {str(e)}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in a new issue