2025-10-07 10:59:52 +01:00

435 lines
13 KiB
Plaintext

#!/usr/bin/env nu
# Platform-Specific Deployment Functions
#
# Implements deployment logic for each supported platform:
# - Docker (docker-compose)
# - Podman (podman-compose)
# - Kubernetes (kubectl)
# - OrbStack (orb CLI)
use helpers.nu *
# Deploy to Docker using docker-compose
#
# @param config: Deployment configuration record
# @returns: Deployment result record
export def deploy-docker [config: record]: nothing -> record {
print $"🐳 Deploying to Docker..."
# Determine compose file based on mode
let compose_base = get-platform-path "docker-compose"
let compose_overlay = match $config.mode {
"solo" => "docker-compose.solo.yaml"
"multi-user" => "docker-compose.multi-user.yaml"
"cicd" => "docker-compose.cicd.yaml"
"enterprise" => "docker-compose.enterprise.yaml"
_ => "docker-compose.solo.yaml"
}
let base_file = $compose_base | path join "docker-compose.yaml"
let overlay_file = $compose_base | path join $compose_overlay
# Validate compose files exist
if not ($base_file | path exists) {
error make {msg: $"Compose file not found: ($base_file)"}
}
if not ($overlay_file | path exists) {
error make {msg: $"Overlay file not found: ($overlay_file)"}
}
# Generate .env file with configuration
let env_file = generate-docker-env $config
print $"📝 Generated environment file: ($env_file)"
# Pull images first
print "📦 Pulling Docker images..."
try {
^docker-compose -f $base_file -f $overlay_file pull
} catch {|err|
return {
success: false
platform: "docker"
error: $"Failed to pull images: ($err.msg)"
timestamp: (date now)
}
}
# Start services
print "🚀 Starting services..."
try {
^docker-compose -f $base_file -f $overlay_file up -d
{
success: true
platform: "docker"
compose_files: [$base_file, $overlay_file]
env_file: $env_file
message: "Docker deployment successful"
timestamp: (date now)
}
} catch {|err|
{
success: false
platform: "docker"
error: $"Failed to start services: ($err.msg)"
timestamp: (date now)
}
}
}
# Deploy to Podman using podman-compose
#
# @param config: Deployment configuration record
# @returns: Deployment result record
export def deploy-podman [config: record]: nothing -> record {
print $"🦭 Deploying to Podman..."
# Check if podman-compose is available
let has_podman_compose = (which podman-compose | is-not-empty)
if not $has_podman_compose {
error make {
msg: "podman-compose not found"
help: "Install with: pip install podman-compose"
}
}
# Determine compose file based on mode
let compose_base = get-platform-path "docker-compose"
let compose_overlay = match $config.mode {
"solo" => "docker-compose.solo.yaml"
"multi-user" => "docker-compose.multi-user.yaml"
"cicd" => "docker-compose.cicd.yaml"
"enterprise" => "docker-compose.enterprise.yaml"
_ => "docker-compose.solo.yaml"
}
let base_file = $compose_base | path join "docker-compose.yaml"
let overlay_file = $compose_base | path join $compose_overlay
# Generate .env file
let env_file = generate-docker-env $config
print $"📝 Generated environment file: ($env_file)"
# Pull images
print "📦 Pulling Podman images..."
try {
^podman-compose -f $base_file -f $overlay_file pull
} catch {|err|
return {
success: false
platform: "podman"
error: $"Failed to pull images: ($err.msg)"
timestamp: (date now)
}
}
# Start services
print "🚀 Starting services..."
try {
^podman-compose -f $base_file -f $overlay_file up -d
{
success: true
platform: "podman"
compose_files: [$base_file, $overlay_file]
env_file: $env_file
message: "Podman deployment successful"
timestamp: (date now)
}
} catch {|err|
{
success: false
platform: "podman"
error: $"Failed to start services: ($err.msg)"
timestamp: (date now)
}
}
}
# Deploy to Kubernetes using kubectl
#
# @param config: Deployment configuration record
# @returns: Deployment result record
export def deploy-kubernetes [config: record]: nothing -> record {
print $"☸️ Deploying to Kubernetes..."
# Create namespace if it doesn't exist
let namespace = "provisioning-platform"
print $"📦 Creating namespace: ($namespace)"
try {
^kubectl create namespace $namespace --dry-run=client -o yaml | ^kubectl apply -f -
} catch {|err|
return {
success: false
platform: "kubernetes"
error: $"Failed to create namespace: ($err.msg)"
timestamp: (date now)
}
}
# Generate Kubernetes manifests
print "📝 Generating Kubernetes manifests..."
let manifests_dir = generate-k8s-manifests $config $namespace
# Apply manifests
print $"🚀 Applying manifests from: ($manifests_dir)"
try {
^kubectl apply -f $manifests_dir -n $namespace
{
success: true
platform: "kubernetes"
namespace: $namespace
manifests_dir: $manifests_dir
message: "Kubernetes deployment successful"
timestamp: (date now)
}
} catch {|err|
{
success: false
platform: "kubernetes"
error: $"Failed to apply manifests: ($err.msg)"
timestamp: (date now)
}
}
}
# Deploy to OrbStack using orb CLI
#
# @param config: Deployment configuration record
# @returns: Deployment result record
export def deploy-orbstack [config: record]: nothing -> record {
print $"🌐 Deploying to OrbStack..."
# Check if orb CLI is available
let has_orb = (which orb | is-not-empty)
if not $has_orb {
error make {
msg: "orb CLI not found"
help: "OrbStack must be installed with CLI tools enabled"
}
}
# OrbStack uses Docker Compose under the hood
# but with orb-specific optimizations
let compose_base = get-platform-path "docker-compose"
let compose_overlay = match $config.mode {
"solo" => "docker-compose.solo.yaml"
"multi-user" => "docker-compose.multi-user.yaml"
"cicd" => "docker-compose.cicd.yaml"
"enterprise" => "docker-compose.enterprise.yaml"
_ => "docker-compose.solo.yaml"
}
let base_file = $compose_base | path join "docker-compose.yaml"
let overlay_file = $compose_base | path join $compose_overlay
# Generate .env file
let env_file = generate-docker-env $config
print $"📝 Generated environment file: ($env_file)"
# Use docker-compose via OrbStack's Docker
print "📦 Pulling images via OrbStack..."
try {
^docker-compose -f $base_file -f $overlay_file pull
} catch {|err|
return {
success: false
platform: "orbstack"
error: $"Failed to pull images: ($err.msg)"
timestamp: (date now)
}
}
# Start services
print "🚀 Starting services..."
try {
^docker-compose -f $base_file -f $overlay_file up -d
{
success: true
platform: "orbstack"
compose_files: [$base_file, $overlay_file]
env_file: $env_file
message: "OrbStack deployment successful"
timestamp: (date now)
}
} catch {|err|
{
success: false
platform: "orbstack"
error: $"Failed to start services: ($err.msg)"
timestamp: (date now)
}
}
}
# Generate Docker/Podman environment file
#
# @param config: Deployment configuration record
# @returns: Path to generated .env file
def generate-docker-env [config: record]: nothing -> path {
let env_content = [
$"# Generated by provisioning-installer"
$"# Deployment mode: ($config.mode)"
$"# Platform: ($config.platform)"
$"# Generated at: (date now)"
""
$"PROVISIONING_MODE=($config.mode)"
$"PROVISIONING_DOMAIN=($config.domain)"
$"PROVISIONING_PLATFORM=($config.platform)"
""
"# Service Configuration"
]
# Add service-specific environment variables
let service_vars = $config.services | each {|svc|
let port_var = $"($svc.name | str upcase | str replace '-' '_')_PORT=($svc.port)"
let enabled_var = $"($svc.name | str upcase | str replace '-' '_')_ENABLED=($svc.enabled)"
[$port_var, $enabled_var]
} | flatten
let full_content = ($env_content | append $service_vars | str join "\n")
# Save to .env file
let env_file = get-platform-path "docker-compose" | path join ".env"
$full_content | save -f $env_file
$env_file
}
# Generate Kubernetes manifests from templates
#
# @param config: Deployment configuration record
# @param namespace: Target namespace
# @returns: Path to manifests directory
def generate-k8s-manifests [config: record, namespace: string]: nothing -> path {
let manifests_dir = $env.PWD | path join "k8s-manifests"
# Create directory
mkdir $manifests_dir
# Generate namespace manifest
let ns_manifest = {
apiVersion: "v1"
kind: "Namespace"
metadata: {
name: $namespace
labels: {
provisioning-mode: $config.mode
managed-by: "provisioning-installer"
}
}
}
$ns_manifest | to yaml | save -f ($manifests_dir | path join "namespace.yaml")
# Generate service manifests for each enabled service
$config.services | each {|svc|
if $svc.enabled {
let deployment = generate-k8s-deployment $svc $config $namespace
let service = generate-k8s-service $svc $namespace
$deployment | to yaml | save -f ($manifests_dir | path join $"deployment-($svc.name).yaml")
$service | to yaml | save -f ($manifests_dir | path join $"service-($svc.name).yaml")
}
}
$manifests_dir
}
# Generate Kubernetes Deployment manifest
#
# @param service: Service configuration
# @param config: Deployment configuration
# @param namespace: Target namespace
# @returns: Deployment manifest record
def generate-k8s-deployment [service: record, config: record, namespace: string]: nothing -> record {
{
apiVersion: "apps/v1"
kind: "Deployment"
metadata: {
name: $service.name
namespace: $namespace
labels: {
app: $service.name
mode: $config.mode
}
}
spec: {
replicas: 1
selector: {
matchLabels: {
app: $service.name
}
}
template: {
metadata: {
labels: {
app: $service.name
}
}
spec: {
containers: [{
name: $service.name
image: $"ghcr.io/provisioning-platform/($service.name):latest"
ports: [{
containerPort: $service.port
}]
env: [
{name: "PROVISIONING_MODE", value: $config.mode}
{name: "PROVISIONING_DOMAIN", value: $config.domain}
]
}]
}
}
}
}
}
# Generate Kubernetes Service manifest
#
# @param service: Service configuration
# @param namespace: Target namespace
# @returns: Service manifest record
def generate-k8s-service [service: record, namespace: string]: nothing -> record {
{
apiVersion: "v1"
kind: "Service"
metadata: {
name: $service.name
namespace: $namespace
}
spec: {
selector: {
app: $service.name
}
ports: [{
port: $service.port
targetPort: $service.port
protocol: "TCP"
}]
type: "ClusterIP"
}
}
}
# Get platform-specific base path
#
# @param subpath: Subpath within platform directory
# @returns: Full path to platform directory
def get-platform-path [subpath: string = ""]: nothing -> path {
let base_path = $env.PWD | path dirname | path dirname
if $subpath == "" {
$base_path
} else {
$base_path | path join $subpath
}
}