317 lines
9.3 KiB
Text
317 lines
9.3 KiB
Text
|
|
#!/usr/bin/env nu
|
||
|
|
# Start local provisioning services via Docker Compose
|
||
|
|
# Usage: nu provisioning/scripts/start-local.nu [--stop|--restart|--logs|--status]
|
||
|
|
#
|
||
|
|
# Options:
|
||
|
|
# --stop Stop all services
|
||
|
|
# --restart Restart all services
|
||
|
|
# --logs Follow logs in real-time
|
||
|
|
# --status Show service status
|
||
|
|
# (default) Start services and show info
|
||
|
|
|
||
|
|
use std log
|
||
|
|
|
||
|
|
def result_ok [value: any] { {ok: $value, err: null} }
|
||
|
|
def result_err [message: string] { {ok: null, err: $message} }
|
||
|
|
|
||
|
|
def is_ok [result: record] { $result.err == null }
|
||
|
|
|
||
|
|
# Phase 1: Validate environment and preconditions
|
||
|
|
def validate_environment [] {
|
||
|
|
# Guard 1: Docker is installed
|
||
|
|
let docker_check = (
|
||
|
|
try { which docker | length > 0 } catch { false }
|
||
|
|
)
|
||
|
|
if not $docker_check {
|
||
|
|
return (result_err "Docker not found. Please install Docker Desktop or Docker Engine.")
|
||
|
|
}
|
||
|
|
|
||
|
|
# Guard 2: Docker daemon is running
|
||
|
|
let docker_running = (
|
||
|
|
try { docker ps out> /dev/null 2>&1; true } catch { false }
|
||
|
|
)
|
||
|
|
if not $docker_running {
|
||
|
|
return (result_err "Docker daemon is not running. Please start Docker and try again.")
|
||
|
|
}
|
||
|
|
|
||
|
|
# Guard 3: docker-compose.local.yml exists
|
||
|
|
let compose_file = "provisioning/docker-compose.local.yml"
|
||
|
|
if not ($compose_file | path exists) {
|
||
|
|
return (result_err $"Docker Compose file not found: ($compose_file)")
|
||
|
|
}
|
||
|
|
|
||
|
|
# Guard 4: .env.local exists (or create from template)
|
||
|
|
let env_file = ".env.local"
|
||
|
|
let env_template = "provisioning/.env.local.template"
|
||
|
|
if not ($env_file | path exists) {
|
||
|
|
if ($env_template | path exists) {
|
||
|
|
log info $"Creating ($env_file) from template"
|
||
|
|
cp $env_template $env_file
|
||
|
|
} else {
|
||
|
|
log warning $"(.env.local) not found, using defaults"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
result_ok null
|
||
|
|
}
|
||
|
|
|
||
|
|
# Phase 2: Check port availability
|
||
|
|
def check_ports [] {
|
||
|
|
let ports = [
|
||
|
|
{port: 8080, service: "API Gateway"}
|
||
|
|
{port: 8082, service: "Extension Registry"}
|
||
|
|
{port: 9090, service: "Orchestrator"}
|
||
|
|
{port: 2379, service: "ETCD Client"}
|
||
|
|
{port: 2380, service: "ETCD Peer"}
|
||
|
|
{port: 9091, service: "Prometheus"}
|
||
|
|
]
|
||
|
|
|
||
|
|
let unavailable = $ports | where {|p|
|
||
|
|
try {
|
||
|
|
let result = (ss -tuln 2>/dev/null | grep $"(:($p.port) )")
|
||
|
|
$result | length > 0
|
||
|
|
} catch {
|
||
|
|
false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($unavailable | length) > 0 {
|
||
|
|
let port_list = ($unavailable | map { |p| $" - Port ($p.port): ($p.service)" } | str join "\n")
|
||
|
|
return (result_err $"The following ports are already in use:\n($port_list)\n\nEither stop the conflicting services or wait for cleanup.")
|
||
|
|
}
|
||
|
|
|
||
|
|
result_ok null
|
||
|
|
}
|
||
|
|
|
||
|
|
# Phase 3: Start Docker Compose services
|
||
|
|
def start_services [] {
|
||
|
|
log info "Starting Docker Compose services..."
|
||
|
|
|
||
|
|
try {
|
||
|
|
docker compose -f provisioning/docker-compose.local.yml --env-file .env.local up -d
|
||
|
|
result_ok null
|
||
|
|
} catch {|err|
|
||
|
|
result_err $"Failed to start services: ($err.msg)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Phase 4: Wait for health checks
|
||
|
|
def wait_for_services [timeout_secs: int] {
|
||
|
|
log info $"Waiting for services to be healthy (timeout: ($timeout_secs)s)..."
|
||
|
|
|
||
|
|
let services = [
|
||
|
|
{name: "etcd", port: 2379}
|
||
|
|
{name: "extension-registry", port: 8082}
|
||
|
|
{name: "orchestrator", port: 9090}
|
||
|
|
{name: "api-gateway", port: 8080}
|
||
|
|
]
|
||
|
|
|
||
|
|
let start_time = (date now | into int)
|
||
|
|
mut all_healthy = false
|
||
|
|
|
||
|
|
loop {
|
||
|
|
let elapsed = ((date now | into int) - $start_time)
|
||
|
|
|
||
|
|
if $elapsed > $timeout_secs {
|
||
|
|
return (result_err "Services did not become healthy within timeout period. Check docker logs.")
|
||
|
|
}
|
||
|
|
|
||
|
|
let health_results = $services | map {|s|
|
||
|
|
let healthy = (
|
||
|
|
try {
|
||
|
|
curl -s -f -m 2 $"http://localhost:($s.port)/api/v1/health" out> /dev/null 2>&1
|
||
|
|
true
|
||
|
|
} catch {
|
||
|
|
false
|
||
|
|
}
|
||
|
|
)
|
||
|
|
{service: $s.name, healthy: $healthy}
|
||
|
|
}
|
||
|
|
|
||
|
|
let healthy_count = ($health_results | where healthy | length)
|
||
|
|
|
||
|
|
if $healthy_count == ($services | length) {
|
||
|
|
log info "All services are healthy"
|
||
|
|
$all_healthy = true
|
||
|
|
break
|
||
|
|
}
|
||
|
|
|
||
|
|
let unhealthy = $health_results | where {|h| not $h.healthy} | map {|h| $h.service}
|
||
|
|
log debug $"Waiting for services: ($unhealthy | str join ', ')"
|
||
|
|
|
||
|
|
sleep 2sec
|
||
|
|
}
|
||
|
|
|
||
|
|
result_ok $all_healthy
|
||
|
|
}
|
||
|
|
|
||
|
|
# Phase 5: Verify service endpoints
|
||
|
|
def verify_endpoints [] {
|
||
|
|
log info "Verifying service endpoints..."
|
||
|
|
|
||
|
|
let endpoints = [
|
||
|
|
{name: "API Gateway", url: "http://localhost:8080/api/v1/health"}
|
||
|
|
{name: "Extension Registry", url: "http://localhost:8082/api/v1/health"}
|
||
|
|
{name: "Orchestrator", url: "http://localhost:9090/api/v1/health"}
|
||
|
|
{name: "ETCD", url: "http://localhost:2379/version"}
|
||
|
|
]
|
||
|
|
|
||
|
|
let results = $endpoints | map {|ep|
|
||
|
|
let response = (
|
||
|
|
try {
|
||
|
|
curl -s $ep.url | from json
|
||
|
|
} catch {
|
||
|
|
null
|
||
|
|
}
|
||
|
|
)
|
||
|
|
{service: $ep.name, accessible: ($response != null)}
|
||
|
|
}
|
||
|
|
|
||
|
|
let all_accessible = ($results | where accessible | length) == ($results | length)
|
||
|
|
|
||
|
|
if not $all_accessible {
|
||
|
|
let inaccessible = ($results | where {|r| not $r.accessible} | map {|r| $r.service})
|
||
|
|
log warning $"Some endpoints are not accessible: ($inaccessible | str join ', ')"
|
||
|
|
} else {
|
||
|
|
log info "All endpoints are accessible"
|
||
|
|
}
|
||
|
|
|
||
|
|
result_ok $results
|
||
|
|
}
|
||
|
|
|
||
|
|
# Phase 6: Display access information
|
||
|
|
def show_info [] {
|
||
|
|
log info ""
|
||
|
|
log info "=========================================="
|
||
|
|
log info "Provisioning Local Services Started"
|
||
|
|
log info "=========================================="
|
||
|
|
log info ""
|
||
|
|
log info "Service URLs:"
|
||
|
|
log info " API Gateway: http://localhost:8080"
|
||
|
|
log info " Extension Registry: http://localhost:8082"
|
||
|
|
log info " Orchestrator: http://localhost:9090"
|
||
|
|
log info " ETCD Client: http://localhost:2379"
|
||
|
|
log info " Prometheus: http://localhost:9091"
|
||
|
|
log info ""
|
||
|
|
log info "Useful commands:"
|
||
|
|
log info " View logs: docker compose -f provisioning/docker-compose.local.yml logs -f"
|
||
|
|
log info " Stop services: docker compose -f provisioning/docker-compose.local.yml down"
|
||
|
|
log info " Container status: docker ps --filter label=com.docker.compose.project=provisioning"
|
||
|
|
log info ""
|
||
|
|
log info "Test connectivity:"
|
||
|
|
log info " curl http://localhost:8082/api/v1/health"
|
||
|
|
log info " curl http://localhost:9090/api/v1/health"
|
||
|
|
log info ""
|
||
|
|
log info "=========================================="
|
||
|
|
}
|
||
|
|
|
||
|
|
# Command: Stop services
|
||
|
|
def stop_services [] {
|
||
|
|
log info "Stopping Docker Compose services..."
|
||
|
|
|
||
|
|
try {
|
||
|
|
docker compose -f provisioning/docker-compose.local.yml --env-file .env.local down
|
||
|
|
log info "Services stopped successfully"
|
||
|
|
result_ok null
|
||
|
|
} catch {|err|
|
||
|
|
result_err $"Failed to stop services: ($err.msg)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Command: Restart services
|
||
|
|
def restart_services [] {
|
||
|
|
let stop_result = stop_services
|
||
|
|
if not (is_ok $stop_result) {
|
||
|
|
return $stop_result
|
||
|
|
}
|
||
|
|
|
||
|
|
sleep 2sec
|
||
|
|
|
||
|
|
let start_result = start_services
|
||
|
|
if not (is_ok $start_result) {
|
||
|
|
return $start_result
|
||
|
|
}
|
||
|
|
|
||
|
|
wait_for_services 60
|
||
|
|
}
|
||
|
|
|
||
|
|
# Command: Show logs
|
||
|
|
def show_logs [] {
|
||
|
|
log info "Following Docker Compose logs (Ctrl+C to stop)..."
|
||
|
|
docker compose -f provisioning/docker-compose.local.yml --env-file .env.local logs -f
|
||
|
|
}
|
||
|
|
|
||
|
|
# Command: Show status
|
||
|
|
def show_status [] {
|
||
|
|
log info "Checking service status..."
|
||
|
|
docker compose -f provisioning/docker-compose.local.yml ps
|
||
|
|
}
|
||
|
|
|
||
|
|
# Main execution
|
||
|
|
def main [--stop: bool, --restart: bool, --logs: bool, --status: bool] {
|
||
|
|
# Load environment
|
||
|
|
if (".env.local" | path exists) {
|
||
|
|
load-env (.env.local | open --raw | lines | where {|l| $l !~ '^#' and $l | str trim | length > 0} | each {|l|
|
||
|
|
let parts = ($l | split column '=' -c 2)
|
||
|
|
{key: ($parts.0.0 | str trim), value: ($parts.0.1 | str trim)}
|
||
|
|
} | each {|kv|
|
||
|
|
{($kv.key): $kv.value}
|
||
|
|
} | reduce item=null as $item ({}; . + $item))
|
||
|
|
}
|
||
|
|
|
||
|
|
# Route to appropriate command
|
||
|
|
if $stop {
|
||
|
|
let result = (stop_services)
|
||
|
|
if not (is_ok $result) {
|
||
|
|
log error $result.err
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
} else if $restart {
|
||
|
|
let result = (restart_services)
|
||
|
|
if not (is_ok $result) {
|
||
|
|
log error $result.err
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
show_info
|
||
|
|
} else if $logs {
|
||
|
|
show_logs
|
||
|
|
} else if $status {
|
||
|
|
show_status
|
||
|
|
} else {
|
||
|
|
# Default: Start services
|
||
|
|
let validate = (validate_environment)
|
||
|
|
if not (is_ok $validate) {
|
||
|
|
log error $validate.err
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
let ports = (check_ports)
|
||
|
|
if not (is_ok $ports) {
|
||
|
|
log error $ports.err
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
let start = (start_services)
|
||
|
|
if not (is_ok $start) {
|
||
|
|
log error $start.err
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
let wait = (wait_for_services 60)
|
||
|
|
if not (is_ok $wait) {
|
||
|
|
log error $wait.err
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
let verify = (verify_endpoints)
|
||
|
|
if not (is_ok $verify) {
|
||
|
|
log error $verify.err
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
show_info
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
main ...$nu.args
|