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
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
```bash
# Make the script executable
chmod +x deu.py
# Optionally, create a symlink
ln -s $(pwd)/deu.py /usr/local/bin/deu
# Or copy it
cp $(pwd)/deu.py /usr/local/bin/deu
sudo ln -s $(pwd)/deu.py /usr/local/bin/deu
```
## Usage
### Initialize a container
### Initialize a Container
Create a new development container:
```bash
# Initialize with a random service name
deu init .container --image python:3.11
# Local container (in current directory)
deu init --image ubuntu:24.04
# Initialize with a specific service name
deu init .container --image python:3.11 --service my_service
# Global container (stored in ~/.config/deu/)
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
# Start container in background
deu background
# 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
deu logs
deu logs # Use local container
deu logs my_container
# Stop container
deu stop
deu stop # Use local container
deu stop my_container
# Remove container
deu rm
deu rm # Use local container
deu rm my_container
```
## Configuration
deu creates two configuration files:
`deu` creates two configuration files:
1. `.container/docker-compose.yml`: Docker Compose configuration
2. `deu.toml`: Container metadata and settings
1. `deu.toml` - Container configuration (local or in ~/.config/deu/)
2. `.container/docker-compose.yml` - Docker Compose configuration (local or in ~/.config/deu/.container_name/)
## Requirements

256
deu.py
View File

@ -199,60 +199,66 @@ def get_container_config() -> ContainerConfig:
sys.exit(1)
def handle_activate() -> None:
"""Activate the container shell."""
config = get_container_config()
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 get_global_config_dir() -> Path:
"""Get the global configuration directory."""
return Path.home() / ".config" / "deu"
def handle_background() -> None:
"""Start the container in background mode."""
container_path = get_container_config().path
run_docker_compose_command(container_path, "up", detach=True)
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:
config = toml.loads(local_toml.read_text())
if config["container"]["service"] == container_name:
return get_container_config()
except Exception:
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"]],
)
except Exception:
continue
return None
# If no container name, just check local deu.toml
local_toml = Path.cwd() / "deu.toml"
if local_toml.exists():
return get_container_config()
return None
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:
def create_dev_container(
path: str, image: str, service_name: str, is_global: bool = False
) -> None:
"""Create a development container configuration."""
try:
container_path = Path(path)
validate_path(container_path)
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)
toml_path = Path.cwd() / "deu.toml"
validate_image(image)
# 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)
# Create the container directory if it doesn't exist
# Create the container directory
container_path.mkdir(parents=True, exist_ok=True)
# Create configurations
@ -278,7 +284,7 @@ def create_dev_container(path: str, image: str, service_name: str) -> None:
# Write configurations
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:
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}"
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:
"""Main entry point for the script."""
parser = argparse.ArgumentParser(
@ -312,35 +408,69 @@ def main() -> None:
# 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("path", nargs="?", help="Path to the .container directory")
init_parser.add_argument("--image", required=True, help="Docker image to use")
init_parser.add_argument(
"--service",
default=generate_service_name(),
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
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")
# Activate command
activate_parser = subparsers.add_parser("activate", help="Activate container shell")
activate_parser.add_argument(
"container_name", nargs="?", help="Name of the container to activate"
)
# 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()
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":
handle_activate()
handle_activate(args.container_name)
elif args.command == "background":
handle_background()
handle_background(args.container_name)
elif args.command == "logs":
handle_logs()
handle_logs(args.container_name)
elif args.command == "stop":
handle_stop()
handle_stop(args.container_name)
elif args.command == "rm":
handle_rm()
handle_rm(args.container_name)
else:
parser.print_help()
sys.exit(1)