mirror of
https://github.com/tcsenpai/deu.git
synced 2025-06-03 10:10:10 +00:00
Initial commit
This commit is contained in:
commit
f216f37449
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
testbed
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
*.pyw
|
||||||
|
*.pyz
|
||||||
|
*.pywz
|
||||||
|
*.pyzw
|
||||||
|
docker-compose.yml
|
||||||
|
deu.toml
|
250
deu.py
Executable file
250
deu.py
Executable file
@ -0,0 +1,250 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, Any, Optional
|
||||||
|
import yaml
|
||||||
|
import toml
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||||
|
)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def validate_path(path: Path) -> None:
|
||||||
|
"""Validate the provided path."""
|
||||||
|
if not path:
|
||||||
|
raise ValueError("Path cannot be empty")
|
||||||
|
|
||||||
|
if not path.is_absolute():
|
||||||
|
path = path.absolute()
|
||||||
|
|
||||||
|
if not path.parent.exists():
|
||||||
|
raise FileNotFoundError(f"Parent directory {path.parent} does not exist")
|
||||||
|
|
||||||
|
def validate_image(image: str) -> None:
|
||||||
|
"""Validate the Docker image name."""
|
||||||
|
if not image:
|
||||||
|
raise ValueError("Docker image cannot be empty")
|
||||||
|
|
||||||
|
# Basic validation for image format
|
||||||
|
if not any(char in image for char in [':', '@']):
|
||||||
|
logger.warning("No tag or digest specified for the image. Using 'latest' tag")
|
||||||
|
|
||||||
|
def create_docker_compose_config(workspace_path: Path, image: str, service_name: str) -> Dict[str, Any]:
|
||||||
|
"""Create the Docker Compose configuration."""
|
||||||
|
return {
|
||||||
|
"services": {
|
||||||
|
service_name: {
|
||||||
|
"image": image,
|
||||||
|
"command": "tail -f /dev/null", # Keep container running with a shell
|
||||||
|
"volumes": [f"{workspace_path.absolute()}:/workspace"],
|
||||||
|
"working_dir": "/workspace",
|
||||||
|
"stdin_open": True,
|
||||||
|
"tty": True,
|
||||||
|
"environment": {
|
||||||
|
"TERM": "xterm-256color",
|
||||||
|
},
|
||||||
|
"security_opt": ["seccomp=unconfined"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def create_toml_config(container_path: Path, service_name: str) -> Dict[str, Any]:
|
||||||
|
"""Create the TOML configuration."""
|
||||||
|
return {
|
||||||
|
"container": {
|
||||||
|
"path": str(container_path.absolute()),
|
||||||
|
"service": service_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def write_config(path: Path, config: Dict[str, Any], config_type: str) -> None:
|
||||||
|
"""Write configuration to a file."""
|
||||||
|
try:
|
||||||
|
if config_type == "yaml":
|
||||||
|
path.write_text(yaml.dump(config, sort_keys=False))
|
||||||
|
elif config_type == "toml":
|
||||||
|
path.write_text(toml.dumps(config))
|
||||||
|
logger.info(f"Created {config_type.upper()} file at: {path}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to write {config_type.upper()} file: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def run_docker_compose_command(container_path: Path, command: str, detach: bool = False) -> None:
|
||||||
|
"""Run a Docker Compose command."""
|
||||||
|
try:
|
||||||
|
cmd = ["docker", "compose", "-f", str(container_path / "docker-compose.yml")]
|
||||||
|
cmd.extend(command.split())
|
||||||
|
if detach:
|
||||||
|
cmd.append("-d")
|
||||||
|
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logger.error(f"Failed to execute Docker Compose command: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def get_service_name(container_path: Path) -> str:
|
||||||
|
"""Get the service name from deu.toml."""
|
||||||
|
try:
|
||||||
|
config = toml.loads((Path.cwd() / "deu.toml").read_text())
|
||||||
|
return config["container"]["service"]
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to read service name from deu.toml: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def create_dev_container(path: str, image: str, service_name: str) -> None:
|
||||||
|
"""
|
||||||
|
Create a development container configuration.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path: Path to the .container directory
|
||||||
|
image: Docker image to use
|
||||||
|
service_name: Name of the container service
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
container_path = Path(path)
|
||||||
|
validate_path(container_path)
|
||||||
|
validate_image(image)
|
||||||
|
|
||||||
|
# Create the container directory if it doesn't exist
|
||||||
|
container_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Create configurations
|
||||||
|
workspace_path = container_path.parent
|
||||||
|
compose_config = create_docker_compose_config(workspace_path, image, service_name)
|
||||||
|
toml_config = create_toml_config(container_path, service_name)
|
||||||
|
|
||||||
|
# Write configurations
|
||||||
|
write_config(container_path / "docker-compose.yml", compose_config, "yaml")
|
||||||
|
write_config(Path.cwd() / "deu.toml", toml_config, "toml")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to create development container: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def find_container_config() -> Optional[Path]:
|
||||||
|
"""Find the container configuration directory."""
|
||||||
|
current = Path.cwd()
|
||||||
|
toml_path = current / "deu.toml"
|
||||||
|
|
||||||
|
if toml_path.exists():
|
||||||
|
try:
|
||||||
|
config = toml.loads(toml_path.read_text())
|
||||||
|
container_path = Path(config["container"]["path"])
|
||||||
|
if container_path.exists() and (container_path / "docker-compose.yml").exists():
|
||||||
|
return container_path
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to read deu.toml: {e}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def handle_activate() -> None:
|
||||||
|
"""Activate the container shell."""
|
||||||
|
container_path = find_container_config()
|
||||||
|
if not container_path:
|
||||||
|
logger.error("No container configuration found. Run 'deu init' first.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
service_name = get_service_name(container_path)
|
||||||
|
print(f"Trying to activate container shell for service '{service_name}'...")
|
||||||
|
|
||||||
|
# Check if container is running, start it if not
|
||||||
|
try:
|
||||||
|
ps_output = subprocess.run(
|
||||||
|
["docker", "compose", "-f", str(container_path / "docker-compose.yml"), "ps"],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
if service_name not in ps_output.stdout:
|
||||||
|
logger.info(f"Container '{service_name}' is not running. Starting it...")
|
||||||
|
run_docker_compose_command(container_path, "up -d")
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
logger.info(f"Container '{service_name}' is not running. Starting it...")
|
||||||
|
run_docker_compose_command(container_path, "up -d")
|
||||||
|
|
||||||
|
# Now exec into the container
|
||||||
|
run_docker_compose_command(container_path, f"exec {service_name} bash")
|
||||||
|
|
||||||
|
def handle_background() -> None:
|
||||||
|
"""Start the container in background mode."""
|
||||||
|
container_path = find_container_config()
|
||||||
|
if not container_path:
|
||||||
|
logger.error("No container configuration found. Run 'deu init' first.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
run_docker_compose_command(container_path, "up", detach=True)
|
||||||
|
|
||||||
|
def handle_logs() -> None:
|
||||||
|
"""Show container logs."""
|
||||||
|
container_path = find_container_config()
|
||||||
|
if not container_path:
|
||||||
|
logger.error("No container configuration found. Run 'deu init' first.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
run_docker_compose_command(container_path, "logs -f")
|
||||||
|
|
||||||
|
def handle_stop() -> None:
|
||||||
|
"""Stop the container."""
|
||||||
|
container_path = find_container_config()
|
||||||
|
if not container_path:
|
||||||
|
logger.error("No container configuration found. Run 'deu init' first.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
run_docker_compose_command(container_path, "stop")
|
||||||
|
|
||||||
|
def handle_rm() -> None:
|
||||||
|
"""Remove the container."""
|
||||||
|
container_path = find_container_config()
|
||||||
|
if not container_path:
|
||||||
|
logger.error("No container configuration found. Run 'deu init' first.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
run_docker_compose_command(container_path, "down")
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Main entry point for the script."""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Development container utility",
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||||
|
)
|
||||||
|
subparsers = parser.add_subparsers(dest="command", help="Command to execute")
|
||||||
|
|
||||||
|
# Init command
|
||||||
|
init_parser = subparsers.add_parser("init", help="Initialize a new container")
|
||||||
|
init_parser.add_argument("path", help="Path to the .container directory")
|
||||||
|
init_parser.add_argument("--image", required=True, help="Docker image to use")
|
||||||
|
init_parser.add_argument("--service", default="dev", help="Name of the container service")
|
||||||
|
|
||||||
|
# Other commands
|
||||||
|
subparsers.add_parser("activate", help="Activate container shell")
|
||||||
|
subparsers.add_parser("background", help="Start container in background")
|
||||||
|
subparsers.add_parser("logs", help="Show container logs")
|
||||||
|
subparsers.add_parser("stop", help="Stop container")
|
||||||
|
subparsers.add_parser("rm", help="Remove container")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.command == "init":
|
||||||
|
create_dev_container(args.path, args.image, args.service)
|
||||||
|
elif args.command == "activate":
|
||||||
|
handle_activate()
|
||||||
|
elif args.command == "background":
|
||||||
|
handle_background()
|
||||||
|
elif args.command == "logs":
|
||||||
|
handle_logs()
|
||||||
|
elif args.command == "stop":
|
||||||
|
handle_stop()
|
||||||
|
elif args.command == "rm":
|
||||||
|
handle_rm()
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
Loading…
x
Reference in New Issue
Block a user