Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions infra/helm/cert-manager/letsencrypt-staging.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# ClusterIssuer for Let's Encrypt TLS certificates
# Generated by: uv run api-forge-cli k8s setup-tls --email pieware@gmail.com
# This is a cluster-scoped resource (not namespaced).
# Apply with: kubectl apply -f infra/helm/cert-manager/letsencrypt-staging.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
labels:
app.kubernetes.io/managed-by: api-forge-cli
spec:
acme:
# Let's Encrypt ACME server
server: https://acme-staging-v02.api.letsencrypt.org/directory
# Email for certificate expiration notifications
email: pieware@gmail.com
# Secret to store the ACME account private key
privateKeySecretRef:
name: letsencrypt-staging-account-key
# HTTP-01 challenge solver using NGINX ingress
solvers:
- http01:
ingress:
class: nginx
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies = [
"temporalio>=1.18.1",
"requests>=2.32.5",
"ruamel.yaml>=0.18.6",
"kr8s>=0.20.14",
]

[build-system]
Expand Down
38 changes: 32 additions & 6 deletions src/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
"""Main CLI application module."""
"""Main CLI application module.

This module provides the main entry point for the API Forge CLI.
Commands are organized by deployment target (dev, prod, k8s, fly)
rather than by operation type (up, down, status).

Command Groups:
- dev: Development Docker Compose environment
- prod: Production Docker Compose deployment
- k8s: Kubernetes Helm deployment
- fly: Fly.io Kubernetes (coming soon)
- entity: Entity/model scaffolding
- secrets: Secret management
- users: Keycloak user management (dev)
"""

import typer

from .deploy_commands import deploy_app
from .entity_commands import entity_app
from .secrets_commands import secrets_app
from .commands import (
dev_app,
entity_app,
fly_app,
k8s_app,
prod_app,
secrets_app,
users_app,
)

# Create the main CLI application
app = typer.Typer(
Expand All @@ -13,10 +33,16 @@
rich_markup_mode="rich",
)

# Register command groups
app.add_typer(deploy_app, name="deploy")
# Register deployment target command groups
app.add_typer(dev_app, name="dev", help="Development environment commands")
app.add_typer(prod_app, name="prod", help="Production Docker Compose commands")
app.add_typer(k8s_app, name="k8s", help="Kubernetes Helm deployment commands")
app.add_typer(fly_app, name="fly", help="Fly.io Kubernetes commands (coming soon)")

# Register utility command groups
app.add_typer(entity_app, name="entity")
app.add_typer(secrets_app, name="secrets")
app.add_typer(users_app, name="users")


def main() -> None:
Expand Down
32 changes: 32 additions & 0 deletions src/cli/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""CLI command modules organized by deployment target.

This package provides the restructured CLI with separate command groups
for each deployment target (dev, prod, k8s, fly) and utilities (entity, secrets, users).

Command Groups:
- dev: Development environment using Docker Compose
- prod: Production Docker Compose deployment
- k8s: Kubernetes deployment using Helm
- fly: Fly.io Kubernetes (FKS) deployment (future)
- entity: Entity/model scaffolding
- secrets: Secret management utilities
- users: Keycloak user management (dev environment)
"""

from .dev import app as dev_app
from .entity import entity_app
from .fly import fly_app
from .k8s import k8s_app
from .prod import prod_app
from .secrets import secrets_app
from .users import users_app

__all__ = [
"dev_app",
"prod_app",
"k8s_app",
"fly_app",
"entity_app",
"secrets_app",
"users_app",
]
264 changes: 264 additions & 0 deletions src/cli/commands/dev.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
"""Development environment CLI commands.

This module provides commands for managing the Docker Compose
development environment including Keycloak, PostgreSQL, Redis, and Temporal.

Commands:
up - Start the development environment
down - Stop the development environment
status - Show status of development services
logs - View logs from a service
restart - Restart a specific service
"""

from pathlib import Path

import typer

from src.cli.deployment import DevDeployer
from src.cli.deployment.helm_deployer.image_builder import DeploymentError

from .shared import (
confirm_action,
console,
get_project_root,
handle_error,
print_header,
)

# Create the dev command group
app = typer.Typer(
name="dev",
help="🔧 Development environment commands (Docker Compose)",
no_args_is_help=True,
)


def _get_deployer() -> DevDeployer:
"""Create a DevDeployer instance with current project context."""
return DevDeployer(console, Path(get_project_root()))


# =============================================================================
# Commands
# =============================================================================


@app.command()
def up(
force: bool = typer.Option(
False,
"--force",
"-f",
help="Force restart even if services are already running",
),
no_wait: bool = typer.Option(
False,
"--no-wait",
help="Don't wait for services to be healthy",
),
start_server: bool = typer.Option(
True,
"--start-server/--no-start-server",
help="Start FastAPI dev server after services are ready",
),
) -> None:
"""🚀 Start the development environment.

Starts all development services (Keycloak, PostgreSQL, Redis, Temporal)
using Docker Compose, then optionally starts the FastAPI development server.

Examples:
# Start everything including dev server
api-forge-cli dev up

# Start services only, no dev server
api-forge-cli dev up --no-start-server

# Force restart all services
api-forge-cli dev up --force
"""
print_header("Starting Development Environment")

try:
deployer = _get_deployer()
deployer.deploy(force=force, no_wait=no_wait, start_server=start_server)
except DeploymentError as e:
handle_error(f"Deployment failed: {e.message}", e.details)


@app.command()
def down(
volumes: bool = typer.Option(
False,
"--volumes",
"-v",
help="Also remove data volumes (DESTROYS ALL DATA)",
),
yes: bool = typer.Option(
False,
"--yes",
"-y",
help="Skip confirmation prompt",
),
) -> None:
"""⏹️ Stop the development environment.

Stops all Docker Compose services. Use --volumes to also remove
persistent data (databases, caches).

Examples:
# Stop services (preserves data)
api-forge-cli dev down

# Stop and remove all data
api-forge-cli dev down --volumes
"""
details = "This will stop all development Docker Compose services."
extra_warning = None

if volumes:
extra_warning = (
"⚠️ --volumes flag is set: ALL DATA WILL BE PERMANENTLY DELETED!\n"
" This includes databases, caches, and any persistent storage."
)

if not confirm_action(
action="Stop development environment",
details=details,
extra_warning=extra_warning,
force=yes,
):
console.print("[dim]Operation cancelled.[/dim]")
raise typer.Exit(0)

print_header("Stopping Development Environment", style="red")

try:
deployer = _get_deployer()
deployer.teardown(volumes=volumes)
except DeploymentError as e:
handle_error(f"Teardown failed: {e.message}", e.details)


@app.command()
def status() -> None:
"""📊 Show status of development services.

Displays the current status of all development services including
health check results and connection information.

Examples:
api-forge-cli dev status
"""
deployer = _get_deployer()
deployer.show_status()


@app.command()
def logs(
service: str = typer.Argument(
None,
help="Service name (keycloak, postgres, redis, temporal). Shows all if omitted.",
),
follow: bool = typer.Option(
False,
"--follow",
"-f",
help="Follow log output",
),
tail: int = typer.Option(
100,
"--tail",
"-n",
help="Number of lines to show from the end",
),
) -> None:
"""📜 View logs from development services.

Shows logs from Docker Compose services. Specify a service name
to view logs from a single service.

Examples:
# View all logs
api-forge-cli dev logs

# View PostgreSQL logs
api-forge-cli dev logs postgres

# Follow Keycloak logs
api-forge-cli dev logs keycloak --follow
"""
import subprocess

compose_file = "docker-compose.dev.yml"
cmd = ["docker", "compose", "-f", compose_file, "logs"]

if tail:
cmd.extend(["--tail", str(tail)])

if follow:
cmd.append("--follow")

if service:
# Map friendly names to Docker Compose service names
service_map = {
"keycloak": "keycloak",
"postgres": "postgres",
"redis": "redis",
"temporal": "temporal",
"temporal-ui": "temporal-web",
}
compose_service = service_map.get(service.lower(), service)
cmd.append(compose_service)

try:
subprocess.run(cmd, cwd=get_project_root(), check=True)
except subprocess.CalledProcessError as e:
handle_error(f"Failed to get logs: {e}")
except KeyboardInterrupt:
pass # User cancelled with Ctrl+C


@app.command()
def restart(
service: str = typer.Argument(
...,
help="Service to restart (keycloak, postgres, redis, temporal)",
),
) -> None:
"""🔄 Restart a specific development service.

Restarts a single service without affecting other services.

Examples:
# Restart PostgreSQL
api-forge-cli dev restart postgres

# Restart Keycloak
api-forge-cli dev restart keycloak
"""
import subprocess

compose_file = "docker-compose.dev.yml"

# Map friendly names to Docker Compose service names
service_map = {
"keycloak": "keycloak",
"postgres": "postgres",
"redis": "redis",
"temporal": "temporal",
"temporal-ui": "temporal-web",
}

compose_service = service_map.get(service.lower(), service)

console.print(f"[bold]Restarting {service}...[/bold]")

cmd = ["docker", "compose", "-f", compose_file, "restart", compose_service]

try:
subprocess.run(cmd, cwd=get_project_root(), check=True)
console.print(f"[green]✅ {service} restarted successfully[/green]")
except subprocess.CalledProcessError as e:
handle_error(f"Failed to restart {service}: {e}")
2 changes: 1 addition & 1 deletion src/cli/entity_commands.py → src/cli/commands/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from rich.prompt import Prompt
from rich.table import Table

from .utils import console, get_project_root
from .shared import console, get_project_root

# Create the entity command group
entity_app = typer.Typer(help="🎭 Entity management commands")
Expand Down
Loading
Loading