435 lines
13 KiB
Plaintext
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
|
|
}
|
|
}
|