Skip to content

Commit cdede4a

Browse files
authored
Merge pull request #3 from piewared/feature/INGRESS_AUTO_TLS
Feature/ingress auto tls
2 parents b9648d9 + e50f862 commit cdede4a

29 files changed

Lines changed: 4151 additions & 1326 deletions
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# ClusterIssuer for Let's Encrypt TLS certificates
2+
# Generated by: uv run api-forge-cli k8s setup-tls --email pieware@gmail.com
3+
# This is a cluster-scoped resource (not namespaced).
4+
# Apply with: kubectl apply -f infra/helm/cert-manager/letsencrypt-staging.yaml
5+
apiVersion: cert-manager.io/v1
6+
kind: ClusterIssuer
7+
metadata:
8+
name: letsencrypt-staging
9+
labels:
10+
app.kubernetes.io/managed-by: api-forge-cli
11+
spec:
12+
acme:
13+
# Let's Encrypt ACME server
14+
server: https://acme-staging-v02.api.letsencrypt.org/directory
15+
# Email for certificate expiration notifications
16+
email: pieware@gmail.com
17+
# Secret to store the ACME account private key
18+
privateKeySecretRef:
19+
name: letsencrypt-staging-account-key
20+
# HTTP-01 challenge solver using NGINX ingress
21+
solvers:
22+
- http01:
23+
ingress:
24+
class: nginx

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies = [
2929
"temporalio>=1.18.1",
3030
"requests>=2.32.5",
3131
"ruamel.yaml>=0.18.6",
32+
"kr8s>=0.20.14",
3233
]
3334

3435
[build-system]

src/cli/__init__.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
1-
"""Main CLI application module."""
1+
"""Main CLI application module.
2+
3+
This module provides the main entry point for the API Forge CLI.
4+
Commands are organized by deployment target (dev, prod, k8s, fly)
5+
rather than by operation type (up, down, status).
6+
7+
Command Groups:
8+
- dev: Development Docker Compose environment
9+
- prod: Production Docker Compose deployment
10+
- k8s: Kubernetes Helm deployment
11+
- fly: Fly.io Kubernetes (coming soon)
12+
- entity: Entity/model scaffolding
13+
- secrets: Secret management
14+
- users: Keycloak user management (dev)
15+
"""
216

317
import typer
418

5-
from .deploy_commands import deploy_app
6-
from .entity_commands import entity_app
7-
from .secrets_commands import secrets_app
19+
from .commands import (
20+
dev_app,
21+
entity_app,
22+
fly_app,
23+
k8s_app,
24+
prod_app,
25+
secrets_app,
26+
users_app,
27+
)
828

929
# Create the main CLI application
1030
app = typer.Typer(
@@ -13,10 +33,16 @@
1333
rich_markup_mode="rich",
1434
)
1535

16-
# Register command groups
17-
app.add_typer(deploy_app, name="deploy")
36+
# Register deployment target command groups
37+
app.add_typer(dev_app, name="dev", help="Development environment commands")
38+
app.add_typer(prod_app, name="prod", help="Production Docker Compose commands")
39+
app.add_typer(k8s_app, name="k8s", help="Kubernetes Helm deployment commands")
40+
app.add_typer(fly_app, name="fly", help="Fly.io Kubernetes commands (coming soon)")
41+
42+
# Register utility command groups
1843
app.add_typer(entity_app, name="entity")
1944
app.add_typer(secrets_app, name="secrets")
45+
app.add_typer(users_app, name="users")
2046

2147

2248
def main() -> None:

src/cli/commands/__init__.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""CLI command modules organized by deployment target.
2+
3+
This package provides the restructured CLI with separate command groups
4+
for each deployment target (dev, prod, k8s, fly) and utilities (entity, secrets, users).
5+
6+
Command Groups:
7+
- dev: Development environment using Docker Compose
8+
- prod: Production Docker Compose deployment
9+
- k8s: Kubernetes deployment using Helm
10+
- fly: Fly.io Kubernetes (FKS) deployment (future)
11+
- entity: Entity/model scaffolding
12+
- secrets: Secret management utilities
13+
- users: Keycloak user management (dev environment)
14+
"""
15+
16+
from .dev import app as dev_app
17+
from .entity import entity_app
18+
from .fly import fly_app
19+
from .k8s import k8s_app
20+
from .prod import prod_app
21+
from .secrets import secrets_app
22+
from .users import users_app
23+
24+
__all__ = [
25+
"dev_app",
26+
"prod_app",
27+
"k8s_app",
28+
"fly_app",
29+
"entity_app",
30+
"secrets_app",
31+
"users_app",
32+
]

src/cli/commands/dev.py

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
"""Development environment CLI commands.
2+
3+
This module provides commands for managing the Docker Compose
4+
development environment including Keycloak, PostgreSQL, Redis, and Temporal.
5+
6+
Commands:
7+
up - Start the development environment
8+
down - Stop the development environment
9+
status - Show status of development services
10+
logs - View logs from a service
11+
restart - Restart a specific service
12+
"""
13+
14+
from pathlib import Path
15+
16+
import typer
17+
18+
from src.cli.deployment import DevDeployer
19+
from src.cli.deployment.helm_deployer.image_builder import DeploymentError
20+
21+
from .shared import (
22+
confirm_action,
23+
console,
24+
get_project_root,
25+
handle_error,
26+
print_header,
27+
)
28+
29+
# Create the dev command group
30+
app = typer.Typer(
31+
name="dev",
32+
help="🔧 Development environment commands (Docker Compose)",
33+
no_args_is_help=True,
34+
)
35+
36+
37+
def _get_deployer() -> DevDeployer:
38+
"""Create a DevDeployer instance with current project context."""
39+
return DevDeployer(console, Path(get_project_root()))
40+
41+
42+
# =============================================================================
43+
# Commands
44+
# =============================================================================
45+
46+
47+
@app.command()
48+
def up(
49+
force: bool = typer.Option(
50+
False,
51+
"--force",
52+
"-f",
53+
help="Force restart even if services are already running",
54+
),
55+
no_wait: bool = typer.Option(
56+
False,
57+
"--no-wait",
58+
help="Don't wait for services to be healthy",
59+
),
60+
start_server: bool = typer.Option(
61+
True,
62+
"--start-server/--no-start-server",
63+
help="Start FastAPI dev server after services are ready",
64+
),
65+
) -> None:
66+
"""🚀 Start the development environment.
67+
68+
Starts all development services (Keycloak, PostgreSQL, Redis, Temporal)
69+
using Docker Compose, then optionally starts the FastAPI development server.
70+
71+
Examples:
72+
# Start everything including dev server
73+
api-forge-cli dev up
74+
75+
# Start services only, no dev server
76+
api-forge-cli dev up --no-start-server
77+
78+
# Force restart all services
79+
api-forge-cli dev up --force
80+
"""
81+
print_header("Starting Development Environment")
82+
83+
try:
84+
deployer = _get_deployer()
85+
deployer.deploy(force=force, no_wait=no_wait, start_server=start_server)
86+
except DeploymentError as e:
87+
handle_error(f"Deployment failed: {e.message}", e.details)
88+
89+
90+
@app.command()
91+
def down(
92+
volumes: bool = typer.Option(
93+
False,
94+
"--volumes",
95+
"-v",
96+
help="Also remove data volumes (DESTROYS ALL DATA)",
97+
),
98+
yes: bool = typer.Option(
99+
False,
100+
"--yes",
101+
"-y",
102+
help="Skip confirmation prompt",
103+
),
104+
) -> None:
105+
"""⏹️ Stop the development environment.
106+
107+
Stops all Docker Compose services. Use --volumes to also remove
108+
persistent data (databases, caches).
109+
110+
Examples:
111+
# Stop services (preserves data)
112+
api-forge-cli dev down
113+
114+
# Stop and remove all data
115+
api-forge-cli dev down --volumes
116+
"""
117+
details = "This will stop all development Docker Compose services."
118+
extra_warning = None
119+
120+
if volumes:
121+
extra_warning = (
122+
"⚠️ --volumes flag is set: ALL DATA WILL BE PERMANENTLY DELETED!\n"
123+
" This includes databases, caches, and any persistent storage."
124+
)
125+
126+
if not confirm_action(
127+
action="Stop development environment",
128+
details=details,
129+
extra_warning=extra_warning,
130+
force=yes,
131+
):
132+
console.print("[dim]Operation cancelled.[/dim]")
133+
raise typer.Exit(0)
134+
135+
print_header("Stopping Development Environment", style="red")
136+
137+
try:
138+
deployer = _get_deployer()
139+
deployer.teardown(volumes=volumes)
140+
except DeploymentError as e:
141+
handle_error(f"Teardown failed: {e.message}", e.details)
142+
143+
144+
@app.command()
145+
def status() -> None:
146+
"""📊 Show status of development services.
147+
148+
Displays the current status of all development services including
149+
health check results and connection information.
150+
151+
Examples:
152+
api-forge-cli dev status
153+
"""
154+
deployer = _get_deployer()
155+
deployer.show_status()
156+
157+
158+
@app.command()
159+
def logs(
160+
service: str = typer.Argument(
161+
None,
162+
help="Service name (keycloak, postgres, redis, temporal). Shows all if omitted.",
163+
),
164+
follow: bool = typer.Option(
165+
False,
166+
"--follow",
167+
"-f",
168+
help="Follow log output",
169+
),
170+
tail: int = typer.Option(
171+
100,
172+
"--tail",
173+
"-n",
174+
help="Number of lines to show from the end",
175+
),
176+
) -> None:
177+
"""📜 View logs from development services.
178+
179+
Shows logs from Docker Compose services. Specify a service name
180+
to view logs from a single service.
181+
182+
Examples:
183+
# View all logs
184+
api-forge-cli dev logs
185+
186+
# View PostgreSQL logs
187+
api-forge-cli dev logs postgres
188+
189+
# Follow Keycloak logs
190+
api-forge-cli dev logs keycloak --follow
191+
"""
192+
import subprocess
193+
194+
compose_file = "docker-compose.dev.yml"
195+
cmd = ["docker", "compose", "-f", compose_file, "logs"]
196+
197+
if tail:
198+
cmd.extend(["--tail", str(tail)])
199+
200+
if follow:
201+
cmd.append("--follow")
202+
203+
if service:
204+
# Map friendly names to Docker Compose service names
205+
service_map = {
206+
"keycloak": "keycloak",
207+
"postgres": "postgres",
208+
"redis": "redis",
209+
"temporal": "temporal",
210+
"temporal-ui": "temporal-web",
211+
}
212+
compose_service = service_map.get(service.lower(), service)
213+
cmd.append(compose_service)
214+
215+
try:
216+
subprocess.run(cmd, cwd=get_project_root(), check=True)
217+
except subprocess.CalledProcessError as e:
218+
handle_error(f"Failed to get logs: {e}")
219+
except KeyboardInterrupt:
220+
pass # User cancelled with Ctrl+C
221+
222+
223+
@app.command()
224+
def restart(
225+
service: str = typer.Argument(
226+
...,
227+
help="Service to restart (keycloak, postgres, redis, temporal)",
228+
),
229+
) -> None:
230+
"""🔄 Restart a specific development service.
231+
232+
Restarts a single service without affecting other services.
233+
234+
Examples:
235+
# Restart PostgreSQL
236+
api-forge-cli dev restart postgres
237+
238+
# Restart Keycloak
239+
api-forge-cli dev restart keycloak
240+
"""
241+
import subprocess
242+
243+
compose_file = "docker-compose.dev.yml"
244+
245+
# Map friendly names to Docker Compose service names
246+
service_map = {
247+
"keycloak": "keycloak",
248+
"postgres": "postgres",
249+
"redis": "redis",
250+
"temporal": "temporal",
251+
"temporal-ui": "temporal-web",
252+
}
253+
254+
compose_service = service_map.get(service.lower(), service)
255+
256+
console.print(f"[bold]Restarting {service}...[/bold]")
257+
258+
cmd = ["docker", "compose", "-f", compose_file, "restart", compose_service]
259+
260+
try:
261+
subprocess.run(cmd, cwd=get_project_root(), check=True)
262+
console.print(f"[green]✅ {service} restarted successfully[/green]")
263+
except subprocess.CalledProcessError as e:
264+
handle_error(f"Failed to restart {service}: {e}")
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from rich.prompt import Prompt
1111
from rich.table import Table
1212

13-
from .utils import console, get_project_root
13+
from .shared import console, get_project_root
1414

1515
# Create the entity command group
1616
entity_app = typer.Typer(help="🎭 Entity management commands")

0 commit comments

Comments
 (0)