provisioning/scripts/start-local.nu

317 lines
9.3 KiB
Text
Raw Normal View History

#!/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