global configs

This commit is contained in:
tcsenpai 2025-05-01 12:32:19 +02:00
parent 546ffb5f10
commit 6a460d16cf
2 changed files with 225 additions and 90 deletions

View File

@ -1,62 +1,67 @@
# deu (Docker Environment Utility) # DEU - Docker Environment Utility
A simple utility to create and manage development containers. A simple utility to create and manage development containers using Docker Compose.
## Overview ## Overview
deu helps you quickly set up development containers with a consistent configuration. It creates a Docker Compose setup and manages container lifecycle. `deu` helps you quickly set up development environments in Docker containers. It creates a Docker Compose configuration and manages container states (start, stop, logs, etc.).
## Installation ## Installation
```bash ```bash
# Make the script executable
chmod +x deu.py chmod +x deu.py
sudo ln -s $(pwd)/deu.py /usr/local/bin/deu
# Optionally, create a symlink
ln -s $(pwd)/deu.py /usr/local/bin/deu
# Or copy it
cp $(pwd)/deu.py /usr/local/bin/deu
``` ```
## Usage ## Usage
### Initialize a container ### Initialize a Container
Create a new development container:
```bash ```bash
# Initialize with a random service name # Local container (in current directory)
deu init .container --image python:3.11 deu init --image ubuntu:24.04
# Initialize with a specific service name # Global container (stored in ~/.config/deu/)
deu init .container --image python:3.11 --service my_service deu init -g --image ubuntu:24.04
# With custom service name
deu init --image ubuntu:24.04 --service my_container
``` ```
### Container Management ### Manage Containers
All commands support both local and global containers:
```bash ```bash
# Start container in background
deu background
# Activate container shell # Activate container shell
deu activate deu activate # Use local container
deu activate my_container # Use specific container (local or global)
# Start container in background
deu background # Use local container
deu background my_container
# View container logs # View container logs
deu logs deu logs # Use local container
deu logs my_container
# Stop container # Stop container
deu stop deu stop # Use local container
deu stop my_container
# Remove container # Remove container
deu rm deu rm # Use local container
deu rm my_container
``` ```
## Configuration ## Configuration
deu creates two configuration files: `deu` creates two configuration files:
1. `.container/docker-compose.yml`: Docker Compose configuration 1. `deu.toml` - Container configuration (local or in ~/.config/deu/)
2. `deu.toml`: Container metadata and settings 2. `.container/docker-compose.yml` - Docker Compose configuration (local or in ~/.config/deu/.container_name/)
## Requirements ## Requirements

250
deu.py
View File

@ -199,60 +199,66 @@ def get_container_config() -> ContainerConfig:
sys.exit(1) sys.exit(1)
def handle_activate() -> None: def get_global_config_dir() -> Path:
"""Activate the container shell.""" """Get the global configuration directory."""
config = get_container_config() return Path.home() / ".config" / "deu"
print(f"Trying to activate container shell for service '{config.service}'...")
# Check if container is running, start it if not
def find_container_config(
container_name: Optional[str] = None,
) -> Optional[ContainerConfig]:
"""Find container configuration, checking local then global."""
# If container name is provided, search for it
if container_name:
# First check local deu.toml
local_toml = Path.cwd() / "deu.toml"
if local_toml.exists():
try: try:
ps_output = subprocess.run( config = toml.loads(local_toml.read_text())
["docker", "compose", "-f", str(config.path / "docker-compose.yml"), "ps"], if config["container"]["service"] == container_name:
check=True, return get_container_config()
capture_output=True, except Exception:
text=True, pass
# Then check global configs
global_dir = get_global_config_dir()
if global_dir.exists():
for toml_file in global_dir.glob("*.toml"):
try:
config = toml.loads(toml_file.read_text())
if config["container"]["service"] == container_name:
return ContainerConfig(
path=Path(config["container"]["path"]),
service=config["container"]["service"],
image=config["container"]["image"],
shell=ShellType[config["container"]["shell"]],
) )
if config.service not in ps_output.stdout: except Exception:
logger.info(f"Container '{config.service}' is not running. Starting it...") continue
run_docker_compose_command(config.path, "up -d") return None
except subprocess.CalledProcessError:
logger.info(f"Container '{config.service}' is not running. Starting it...")
run_docker_compose_command(config.path, "up -d")
# Use appropriate shell based on configuration # If no container name, just check local deu.toml
shell = "bash" if config.shell == ShellType.BASH else "sh" local_toml = Path.cwd() / "deu.toml"
run_docker_compose_command(config.path, f"exec {config.service} {shell}") if local_toml.exists():
return get_container_config()
return None
def handle_background() -> None: def create_dev_container(
"""Start the container in background mode.""" path: str, image: str, service_name: str, is_global: bool = False
container_path = get_container_config().path ) -> None:
run_docker_compose_command(container_path, "up", detach=True)
def handle_logs() -> None:
"""Show container logs."""
container_path = get_container_config().path
run_docker_compose_command(container_path, "logs -f")
def handle_stop() -> None:
"""Stop the container."""
container_path = get_container_config().path
run_docker_compose_command(container_path, "stop")
def handle_rm() -> None:
"""Remove the container."""
container_path = get_container_config().path
run_docker_compose_command(container_path, "down")
def create_dev_container(path: str, image: str, service_name: str) -> None:
"""Create a development container configuration.""" """Create a development container configuration."""
try: try:
if is_global:
# Create global config directory first
global_dir = get_global_config_dir()
global_dir.mkdir(parents=True, exist_ok=True)
container_path = global_dir / f".{service_name}"
toml_path = global_dir / f"{service_name}.toml"
else:
container_path = Path(path) container_path = Path(path)
validate_path(container_path) toml_path = Path.cwd() / "deu.toml"
validate_image(image) validate_image(image)
# Ensure image exists and detect shell # Ensure image exists and detect shell
@ -264,7 +270,7 @@ def create_dev_container(path: str, image: str, service_name: str) -> None:
) )
sys.exit(1) sys.exit(1)
# Create the container directory if it doesn't exist # Create the container directory
container_path.mkdir(parents=True, exist_ok=True) container_path.mkdir(parents=True, exist_ok=True)
# Create configurations # Create configurations
@ -278,7 +284,7 @@ def create_dev_container(path: str, image: str, service_name: str) -> None:
# Write configurations # Write configurations
write_config(container_path / "docker-compose.yml", compose_config, "yaml") write_config(container_path / "docker-compose.yml", compose_config, "yaml")
write_config(Path.cwd() / "deu.toml", toml_config, "toml") write_config(toml_path, toml_config, "toml")
except Exception as e: except Exception as e:
logger.error(f"Failed to create development container: {e}") logger.error(f"Failed to create development container: {e}")
@ -302,6 +308,96 @@ def generate_service_name() -> str:
return f"{random.choice(adjectives)}_{random.choice(nouns)}_{random_number}" return f"{random.choice(adjectives)}_{random.choice(nouns)}_{random_number}"
def handle_activate(container_name: Optional[str] = None) -> None:
"""Activate the container shell."""
config = find_container_config(container_name)
if not config:
if container_name:
logger.error(f"Container '{container_name}' not found locally or globally.")
else:
logger.error(
"No local container configuration found. Run 'deu init' first or specify a container name."
)
sys.exit(1)
print(f"Trying to activate container shell for service '{config.service}'...")
# Check if container is running, start it if not
try:
ps_output = subprocess.run(
["docker", "compose", "-f", str(config.path / "docker-compose.yml"), "ps"],
check=True,
capture_output=True,
text=True,
)
if config.service not in ps_output.stdout:
logger.info(f"Container '{config.service}' is not running. Starting it...")
run_docker_compose_command(config.path, "up -d")
except subprocess.CalledProcessError:
logger.info(f"Container '{config.service}' is not running. Starting it...")
run_docker_compose_command(config.path, "up -d")
# Use appropriate shell based on configuration
shell = "bash" if config.shell == ShellType.BASH else "sh"
run_docker_compose_command(config.path, f"exec {config.service} {shell}")
def handle_background(container_name: Optional[str] = None) -> None:
"""Start the container in background mode."""
config = find_container_config(container_name)
if not config:
if container_name:
logger.error(f"Container '{container_name}' not found locally or globally.")
else:
logger.error(
"No local container configuration found. Run 'deu init' first or specify a container name."
)
sys.exit(1)
run_docker_compose_command(config.path, "up", detach=True)
def handle_logs(container_name: Optional[str] = None) -> None:
"""Show container logs."""
config = find_container_config(container_name)
if not config:
if container_name:
logger.error(f"Container '{container_name}' not found locally or globally.")
else:
logger.error(
"No local container configuration found. Run 'deu init' first or specify a container name."
)
sys.exit(1)
run_docker_compose_command(config.path, "logs -f")
def handle_stop(container_name: Optional[str] = None) -> None:
"""Stop the container."""
config = find_container_config(container_name)
if not config:
if container_name:
logger.error(f"Container '{container_name}' not found locally or globally.")
else:
logger.error(
"No local container configuration found. Run 'deu init' first or specify a container name."
)
sys.exit(1)
run_docker_compose_command(config.path, "stop")
def handle_rm(container_name: Optional[str] = None) -> None:
"""Remove the container."""
config = find_container_config(container_name)
if not config:
if container_name:
logger.error(f"Container '{container_name}' not found locally or globally.")
else:
logger.error(
"No local container configuration found. Run 'deu init' first or specify a container name."
)
sys.exit(1)
run_docker_compose_command(config.path, "down")
def main() -> None: def main() -> None:
"""Main entry point for the script.""" """Main entry point for the script."""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
@ -312,35 +408,69 @@ def main() -> None:
# Init command # Init command
init_parser = subparsers.add_parser("init", help="Initialize a new container") 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("path", nargs="?", help="Path to the .container directory")
init_parser.add_argument("--image", required=True, help="Docker image to use") init_parser.add_argument("--image", required=True, help="Docker image to use")
init_parser.add_argument( init_parser.add_argument(
"--service", "--service",
default=generate_service_name(), default=generate_service_name(),
help="Name of the container service", help="Name of the container service",
) )
init_parser.add_argument(
"-g",
"--global",
dest="is_global",
action="store_true",
help="Create container in global config",
)
# Other commands # Activate command
subparsers.add_parser("activate", help="Activate container shell") activate_parser = subparsers.add_parser("activate", help="Activate container shell")
subparsers.add_parser("background", help="Start container in background") activate_parser.add_argument(
subparsers.add_parser("logs", help="Show container logs") "container_name", nargs="?", help="Name of the container to activate"
subparsers.add_parser("stop", help="Stop container") )
subparsers.add_parser("rm", help="Remove container")
# Background command
background_parser = subparsers.add_parser(
"background", help="Start container in background"
)
background_parser.add_argument(
"container_name", nargs="?", help="Name of the container to start"
)
# Logs command
logs_parser = subparsers.add_parser("logs", help="Show container logs")
logs_parser.add_argument(
"container_name", nargs="?", help="Name of the container to show logs for"
)
# Stop command
stop_parser = subparsers.add_parser("stop", help="Stop container")
stop_parser.add_argument(
"container_name", nargs="?", help="Name of the container to stop"
)
# Remove command
rm_parser = subparsers.add_parser("rm", help="Remove container")
rm_parser.add_argument(
"container_name", nargs="?", help="Name of the container to remove"
)
args = parser.parse_args() args = parser.parse_args()
if args.command == "init": if args.command == "init":
create_dev_container(args.path, args.image, args.service) create_dev_container(
args.path or ".container", args.image, args.service, args.is_global
)
elif args.command == "activate": elif args.command == "activate":
handle_activate() handle_activate(args.container_name)
elif args.command == "background": elif args.command == "background":
handle_background() handle_background(args.container_name)
elif args.command == "logs": elif args.command == "logs":
handle_logs() handle_logs(args.container_name)
elif args.command == "stop": elif args.command == "stop":
handle_stop() handle_stop(args.container_name)
elif args.command == "rm": elif args.command == "rm":
handle_rm() handle_rm(args.container_name)
else: else:
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)