637 lines
No EOL
22 KiB
Text
Executable file
637 lines
No EOL
22 KiB
Text
Executable file
#!/usr/bin/env nu
|
|
|
|
# Rustelo Application Deployment Script
|
|
# Nushell version of deploy.sh
|
|
# Handles deployment of the Rustelo application in various environments
|
|
|
|
def main [...args] {
|
|
# Default configuration
|
|
let mut config = {
|
|
environment: "production",
|
|
compose_file: "docker-compose.yml",
|
|
build_args: "",
|
|
migrate_db: false,
|
|
backup_db: false,
|
|
health_check: true,
|
|
timeout: 300,
|
|
project_name: "rustelo",
|
|
docker_registry: "",
|
|
image_tag: "latest",
|
|
force_recreate: false,
|
|
scale_replicas: 1,
|
|
features: "production",
|
|
use_default_features: false,
|
|
debug: false
|
|
}
|
|
|
|
# Parse command line arguments
|
|
let parsed = parse_deployment_args $args
|
|
$config = ($config | merge $parsed.config)
|
|
let command = $parsed.command
|
|
|
|
print $"(ansi blue)🚀 Rustelo Deployment Manager(ansi reset)"
|
|
|
|
# Validate command
|
|
if ($command | is-empty) {
|
|
print $"(ansi red)❌ No command specified(ansi reset)"
|
|
show_deployment_usage
|
|
exit 1
|
|
}
|
|
|
|
# Validate and set environment
|
|
$config = (validate_deployment_environment $config)
|
|
|
|
# Check prerequisites
|
|
check_deployment_prerequisites $config
|
|
|
|
# Set environment variables
|
|
set_deployment_environment_vars $config
|
|
|
|
# Execute command
|
|
execute_deployment_command $command $config
|
|
}
|
|
|
|
# Parse deployment command line arguments
|
|
def parse_deployment_args [args] {
|
|
let mut config = {}
|
|
let mut command = ""
|
|
let mut i = 0
|
|
|
|
while $i < ($args | length) {
|
|
let arg = ($args | get $i)
|
|
|
|
match $arg {
|
|
"-h" | "--help" => {
|
|
show_deployment_usage
|
|
exit 0
|
|
}
|
|
"-e" | "--env" => {
|
|
$i = $i + 1
|
|
if $i < ($args | length) {
|
|
$config = ($config | upsert environment ($args | get $i))
|
|
}
|
|
}
|
|
"-f" | "--file" => {
|
|
$i = $i + 1
|
|
if $i < ($args | length) {
|
|
$config = ($config | upsert compose_file ($args | get $i))
|
|
}
|
|
}
|
|
"-p" | "--project" => {
|
|
$i = $i + 1
|
|
if $i < ($args | length) {
|
|
$config = ($config | upsert project_name ($args | get $i))
|
|
}
|
|
}
|
|
"-t" | "--tag" => {
|
|
$i = $i + 1
|
|
if $i < ($args | length) {
|
|
$config = ($config | upsert image_tag ($args | get $i))
|
|
}
|
|
}
|
|
"-r" | "--registry" => {
|
|
$i = $i + 1
|
|
if $i < ($args | length) {
|
|
$config = ($config | upsert docker_registry ($args | get $i))
|
|
}
|
|
}
|
|
"-s" | "--scale" => {
|
|
$i = $i + 1
|
|
if $i < ($args | length) {
|
|
$config = ($config | upsert scale_replicas ($args | get $i | into int))
|
|
}
|
|
}
|
|
"--migrate" => {
|
|
$config = ($config | upsert migrate_db true)
|
|
}
|
|
"--backup" => {
|
|
$config = ($config | upsert backup_db true)
|
|
}
|
|
"--no-health-check" => {
|
|
$config = ($config | upsert health_check false)
|
|
}
|
|
"--force-recreate" => {
|
|
$config = ($config | upsert force_recreate true)
|
|
}
|
|
"--timeout" => {
|
|
$i = $i + 1
|
|
if $i < ($args | length) {
|
|
$config = ($config | upsert timeout ($args | get $i | into int))
|
|
}
|
|
}
|
|
"--build-arg" => {
|
|
$i = $i + 1
|
|
if $i < ($args | length) {
|
|
let current_args = ($config | get -i build_args | default "")
|
|
$config = ($config | upsert build_args $"($current_args) --build-arg ($args | get $i)")
|
|
}
|
|
}
|
|
"--features" => {
|
|
$i = $i + 1
|
|
if $i < ($args | length) {
|
|
$config = ($config | upsert features ($args | get $i))
|
|
}
|
|
}
|
|
"--default-features" => {
|
|
$config = ($config | upsert use_default_features true)
|
|
}
|
|
"--debug" => {
|
|
$config = ($config | upsert debug true)
|
|
}
|
|
$cmd if not ($cmd | str starts-with "-") => {
|
|
if ($command | is-empty) {
|
|
$command = $cmd
|
|
}
|
|
}
|
|
_ => {
|
|
print $"(ansi red)❌ Unknown option: ($arg)(ansi reset)"
|
|
show_deployment_usage
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
$i = $i + 1
|
|
}
|
|
|
|
{config: $config, command: $command}
|
|
}
|
|
|
|
# Show deployment usage information
|
|
def show_deployment_usage [] {
|
|
print "Usage: nu deploy.nu [OPTIONS] COMMAND"
|
|
print ""
|
|
print "Commands:"
|
|
print " deploy Deploy the application"
|
|
print " stop Stop the application"
|
|
print " restart Restart the application"
|
|
print " status Show deployment status"
|
|
print " logs Show application logs"
|
|
print " scale Scale application replicas"
|
|
print " backup Create database backup"
|
|
print " migrate Run database migrations"
|
|
print " rollback Rollback to previous version"
|
|
print " health Check application health"
|
|
print " update Update application to latest version"
|
|
print " clean Clean up unused containers and images"
|
|
print ""
|
|
print "Options:"
|
|
print " -e, --env ENV Environment (dev|staging|production) [default: production]"
|
|
print " -f, --file FILE Docker compose file [default: docker-compose.yml]"
|
|
print " -p, --project PROJECT Project name [default: rustelo]"
|
|
print " -t, --tag TAG Docker image tag [default: latest]"
|
|
print " -r, --registry REGISTRY Docker registry URL"
|
|
print " -s, --scale REPLICAS Number of replicas [default: 1]"
|
|
print " --migrate Run database migrations before deployment"
|
|
print " --backup Create database backup before deployment"
|
|
print " --no-health-check Skip health check after deployment"
|
|
print " --force-recreate Force recreation of containers"
|
|
print " --timeout SECONDS Deployment timeout [default: 300]"
|
|
print " --build-arg ARG Docker build arguments"
|
|
print " --features FEATURES Cargo features to enable [default: production]"
|
|
print " --default-features Use default features instead of custom"
|
|
print " --debug Enable debug output"
|
|
print " -h, --help Show this help message"
|
|
print ""
|
|
print "Examples:"
|
|
print " nu deploy.nu deploy # Deploy production"
|
|
print " nu deploy.nu deploy -e staging # Deploy staging"
|
|
print " nu deploy.nu deploy --migrate --backup # Deploy with migration and backup"
|
|
print " nu deploy.nu scale -s 3 # Scale to 3 replicas"
|
|
print " nu deploy.nu logs # Show logs"
|
|
print " nu deploy.nu health # Check health status"
|
|
print " nu deploy.nu deploy --features \"auth,metrics\" # Deploy with specific features"
|
|
print " nu deploy.nu deploy --default-features # Deploy with all default features"
|
|
}
|
|
|
|
# Validate deployment environment
|
|
def validate_deployment_environment [config] {
|
|
let environment = ($config | get environment)
|
|
let mut updated_config = $config
|
|
|
|
match $environment {
|
|
"dev" | "development" => {
|
|
$updated_config = ($updated_config | upsert environment "development")
|
|
$updated_config = ($updated_config | upsert compose_file "docker-compose.yml")
|
|
}
|
|
"staging" => {
|
|
$updated_config = ($updated_config | upsert environment "staging")
|
|
$updated_config = ($updated_config | upsert compose_file "docker-compose.staging.yml")
|
|
}
|
|
"prod" | "production" => {
|
|
$updated_config = ($updated_config | upsert environment "production")
|
|
$updated_config = ($updated_config | upsert compose_file "docker-compose.yml")
|
|
}
|
|
_ => {
|
|
print $"(ansi red)❌ Invalid environment: ($environment)(ansi reset)"
|
|
print $"(ansi red)Valid environments: dev, staging, production(ansi reset)"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
$updated_config
|
|
}
|
|
|
|
# Check deployment prerequisites
|
|
def check_deployment_prerequisites [config] {
|
|
print $"(ansi blue)🔍 Checking prerequisites...(ansi reset)"
|
|
|
|
# Check if Docker is installed and running
|
|
try {
|
|
docker --version | ignore
|
|
} catch {
|
|
print $"(ansi red)❌ Docker is not installed or not in PATH(ansi reset)"
|
|
exit 1
|
|
}
|
|
|
|
try {
|
|
docker info | ignore
|
|
} catch {
|
|
print $"(ansi red)❌ Docker daemon is not running(ansi reset)"
|
|
exit 1
|
|
}
|
|
|
|
# Check if Docker Compose is installed
|
|
try {
|
|
docker-compose --version | ignore
|
|
} catch {
|
|
print $"(ansi red)❌ Docker Compose is not installed or not in PATH(ansi reset)"
|
|
exit 1
|
|
}
|
|
|
|
# Check if compose file exists
|
|
let compose_file = ($config | get compose_file)
|
|
if not ($compose_file | path exists) {
|
|
print $"(ansi red)❌ Compose file not found: ($compose_file)(ansi reset)"
|
|
exit 1
|
|
}
|
|
|
|
print $"(ansi green)✅ Prerequisites check passed(ansi reset)"
|
|
}
|
|
|
|
# Set deployment environment variables
|
|
def set_deployment_environment_vars [config] {
|
|
$env.COMPOSE_PROJECT_NAME = ($config | get project_name)
|
|
$env.DOCKER_REGISTRY = ($config | get docker_registry)
|
|
$env.IMAGE_TAG = ($config | get image_tag)
|
|
$env.ENVIRONMENT = ($config | get environment)
|
|
|
|
# Source environment-specific variables
|
|
let env_file = $".env.($config | get environment)"
|
|
let default_env_file = ".env"
|
|
|
|
if ($env_file | path exists) {
|
|
print $"(ansi blue)📄 Loading environment variables from ($env_file)(ansi reset)"
|
|
# Note: Nushell doesn't have direct source equivalent, would need custom implementation
|
|
} else if ($default_env_file | path exists) {
|
|
print $"(ansi blue)📄 Loading environment variables from ($default_env_file)(ansi reset)"
|
|
# Note: Nushell doesn't have direct source equivalent, would need custom implementation
|
|
}
|
|
|
|
if ($config | get debug) {
|
|
print $"(ansi blue)🐛 Environment variables set:(ansi reset)"
|
|
print $"(ansi blue) COMPOSE_PROJECT_NAME=($env.COMPOSE_PROJECT_NAME)(ansi reset)"
|
|
print $"(ansi blue) DOCKER_REGISTRY=($env.DOCKER_REGISTRY)(ansi reset)"
|
|
print $"(ansi blue) IMAGE_TAG=($env.IMAGE_TAG)(ansi reset)"
|
|
print $"(ansi blue) ENVIRONMENT=($env.ENVIRONMENT)(ansi reset)"
|
|
print $"(ansi blue) FEATURES=($config | get features)(ansi reset)"
|
|
print $"(ansi blue) USE_DEFAULT_FEATURES=($config | get use_default_features)(ansi reset)"
|
|
}
|
|
}
|
|
|
|
# Execute deployment command
|
|
def execute_deployment_command [command, config] {
|
|
match $command {
|
|
"deploy" => {
|
|
deployment_build_images $config
|
|
deployment_create_backup $config
|
|
deployment_run_migrations $config
|
|
deployment_deploy_application $config
|
|
deployment_wait_for_health $config
|
|
deployment_show_status $config
|
|
}
|
|
"stop" => {
|
|
deployment_stop_application $config
|
|
}
|
|
"restart" => {
|
|
deployment_restart_application $config
|
|
deployment_wait_for_health $config
|
|
}
|
|
"status" => {
|
|
deployment_show_status $config
|
|
}
|
|
"logs" => {
|
|
deployment_show_logs $config
|
|
}
|
|
"scale" => {
|
|
deployment_scale_application $config
|
|
}
|
|
"backup" => {
|
|
deployment_create_backup $config
|
|
}
|
|
"migrate" => {
|
|
deployment_run_migrations $config
|
|
}
|
|
"rollback" => {
|
|
deployment_rollback_application $config
|
|
}
|
|
"health" => {
|
|
deployment_check_health $config
|
|
}
|
|
"update" => {
|
|
deployment_update_application $config
|
|
deployment_wait_for_health $config
|
|
}
|
|
"clean" => {
|
|
deployment_cleanup $config
|
|
}
|
|
_ => {
|
|
print $"(ansi red)❌ Unknown command: ($command)(ansi reset)"
|
|
show_deployment_usage
|
|
exit 1
|
|
}
|
|
}
|
|
}
|
|
|
|
# Build Docker images
|
|
def deployment_build_images [config] {
|
|
print $"(ansi blue)🏗️ Building Docker images...(ansi reset)"
|
|
|
|
let compose_file = ($config | get compose_file)
|
|
let mut build_cmd = ["docker-compose", "-f", $compose_file, "build"]
|
|
|
|
# Add build arguments
|
|
let build_args = ($config | get build_args)
|
|
if not ($build_args | is-empty) {
|
|
$build_cmd = ($build_cmd | append ($build_args | split row " "))
|
|
}
|
|
|
|
# Add feature arguments
|
|
if not ($config | get use_default_features) {
|
|
$build_cmd = ($build_cmd | append ["--build-arg", $"CARGO_FEATURES=($config | get features)", "--build-arg", "NO_DEFAULT_FEATURES=true"])
|
|
} else {
|
|
$build_cmd = ($build_cmd | append ["--build-arg", "CARGO_FEATURES=", "--build-arg", "NO_DEFAULT_FEATURES=false"])
|
|
}
|
|
|
|
if ($config | get debug) {
|
|
print $"(ansi blue)🐛 Build command: ($build_cmd | str join ' ')(ansi reset)"
|
|
}
|
|
|
|
try {
|
|
run-external ($build_cmd | first) ..($build_cmd | skip 1)
|
|
print $"(ansi green)✅ Docker images built successfully(ansi reset)"
|
|
} catch {
|
|
print $"(ansi red)❌ Failed to build Docker images(ansi reset)"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Create database backup
|
|
def deployment_create_backup [config] {
|
|
if ($config | get backup_db) {
|
|
print $"(ansi blue)💾 Creating database backup...(ansi reset)"
|
|
|
|
let compose_file = ($config | get compose_file)
|
|
let backup_file = $"backup_(date now | format date %Y%m%d_%H%M%S).sql"
|
|
|
|
try {
|
|
docker-compose -f $compose_file exec -T db pg_dump -U postgres rustelo_prod | save $backup_file
|
|
print $"(ansi green)✅ Database backup created: ($backup_file)(ansi reset)"
|
|
} catch {
|
|
print $"(ansi red)❌ Failed to create database backup(ansi reset)"
|
|
exit 1
|
|
}
|
|
}
|
|
}
|
|
|
|
# Run database migrations
|
|
def deployment_run_migrations [config] {
|
|
if ($config | get migrate_db) {
|
|
print $"(ansi blue)🚀 Running database migrations...(ansi reset)"
|
|
|
|
let compose_file = ($config | get compose_file)
|
|
|
|
try {
|
|
docker-compose -f $compose_file run --rm migrate
|
|
print $"(ansi green)✅ Database migrations completed successfully(ansi reset)"
|
|
} catch {
|
|
print $"(ansi red)❌ Database migrations failed(ansi reset)"
|
|
exit 1
|
|
}
|
|
}
|
|
}
|
|
|
|
# Deploy application
|
|
def deployment_deploy_application [config] {
|
|
print $"(ansi blue)🚀 Deploying application...(ansi reset)"
|
|
|
|
let compose_file = ($config | get compose_file)
|
|
let mut compose_cmd = ["docker-compose", "-f", $compose_file, "up", "-d"]
|
|
|
|
if ($config | get force_recreate) {
|
|
$compose_cmd = ($compose_cmd | append "--force-recreate")
|
|
}
|
|
|
|
let scale_replicas = ($config | get scale_replicas)
|
|
if $scale_replicas > 1 {
|
|
$compose_cmd = ($compose_cmd | append ["--scale", $"app=($scale_replicas)"])
|
|
}
|
|
|
|
if ($config | get debug) {
|
|
print $"(ansi blue)🐛 Deploy command: ($compose_cmd | str join ' ')(ansi reset)"
|
|
}
|
|
|
|
try {
|
|
run-external ($compose_cmd | first) ..($compose_cmd | skip 1)
|
|
print $"(ansi green)✅ Application deployed successfully(ansi reset)"
|
|
} catch {
|
|
print $"(ansi red)❌ Failed to deploy application(ansi reset)"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Wait for application health
|
|
def deployment_wait_for_health [config] {
|
|
if ($config | get health_check) {
|
|
print $"(ansi blue)🏥 Waiting for application to be healthy...(ansi reset)"
|
|
|
|
let start_time = (date now | into int)
|
|
let health_url = "http://localhost:3030/health"
|
|
let timeout = ($config | get timeout)
|
|
|
|
loop {
|
|
let current_time = (date now | into int)
|
|
let elapsed = ($current_time - $start_time)
|
|
|
|
if $elapsed > $timeout {
|
|
print $"(ansi red)❌ Health check timeout after ($timeout) seconds(ansi reset)"
|
|
exit 1
|
|
}
|
|
|
|
try {
|
|
let response = (http get $health_url)
|
|
if ($response | get -i status | default "" | str contains "healthy") {
|
|
print $"(ansi green)✅ Application is healthy(ansi reset)"
|
|
break
|
|
}
|
|
} catch {
|
|
# Health check failed, continue retrying
|
|
}
|
|
|
|
if ($config | get debug) {
|
|
print $"(ansi blue)🐛 Health check failed, retrying in 5 seconds... (($elapsed)s elapsed)(ansi reset)"
|
|
}
|
|
sleep 5sec
|
|
}
|
|
}
|
|
}
|
|
|
|
# Show deployment status
|
|
def deployment_show_status [config] {
|
|
let compose_file = ($config | get compose_file)
|
|
|
|
print $"(ansi blue)📊 Deployment status:(ansi reset)"
|
|
try {
|
|
docker-compose -f $compose_file ps
|
|
} catch {
|
|
print $"(ansi yellow)⚠️ Failed to get container status(ansi reset)"
|
|
}
|
|
|
|
print $"(ansi blue)📈 Container resource usage:(ansi reset)"
|
|
try {
|
|
docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"
|
|
} catch {
|
|
print $"(ansi yellow)⚠️ Failed to get resource usage(ansi reset)"
|
|
}
|
|
}
|
|
|
|
# Show application logs
|
|
def deployment_show_logs [config] {
|
|
let compose_file = ($config | get compose_file)
|
|
|
|
try {
|
|
docker-compose -f $compose_file logs
|
|
} catch {
|
|
print $"(ansi red)❌ Failed to retrieve logs(ansi reset)"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Scale application
|
|
def deployment_scale_application [config] {
|
|
let scale_replicas = ($config | get scale_replicas)
|
|
let compose_file = ($config | get compose_file)
|
|
|
|
print $"(ansi blue)📏 Scaling application to ($scale_replicas) replicas...(ansi reset)"
|
|
|
|
try {
|
|
docker-compose -f $compose_file up -d --scale $"app=($scale_replicas)"
|
|
print $"(ansi green)✅ Application scaled successfully(ansi reset)"
|
|
} catch {
|
|
print $"(ansi red)❌ Failed to scale application(ansi reset)"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Stop application
|
|
def deployment_stop_application [config] {
|
|
let compose_file = ($config | get compose_file)
|
|
|
|
print $"(ansi blue)🛑 Stopping application...(ansi reset)"
|
|
|
|
try {
|
|
docker-compose -f $compose_file down
|
|
print $"(ansi green)✅ Application stopped successfully(ansi reset)"
|
|
} catch {
|
|
print $"(ansi red)❌ Failed to stop application(ansi reset)"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Restart application
|
|
def deployment_restart_application [config] {
|
|
let compose_file = ($config | get compose_file)
|
|
|
|
print $"(ansi blue)🔄 Restarting application...(ansi reset)"
|
|
|
|
try {
|
|
docker-compose -f $compose_file restart
|
|
print $"(ansi green)✅ Application restarted successfully(ansi reset)"
|
|
} catch {
|
|
print $"(ansi red)❌ Failed to restart application(ansi reset)"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Check application health
|
|
def deployment_check_health [config] {
|
|
print $"(ansi blue)🏥 Checking application health...(ansi reset)"
|
|
|
|
let health_url = "http://localhost:3030/health"
|
|
|
|
try {
|
|
let health_response = (http get $health_url)
|
|
|
|
if ($health_response | get -i status | default "" | str contains "healthy") {
|
|
print $"(ansi green)✅ Application is healthy(ansi reset)"
|
|
print $"(ansi blue)📋 Health details:(ansi reset)"
|
|
$health_response | table
|
|
} else {
|
|
print $"(ansi red)❌ Application is not healthy(ansi reset)"
|
|
$health_response | table
|
|
exit 1
|
|
}
|
|
} catch {
|
|
print $"(ansi red)❌ Failed to check application health(ansi reset)"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Update application
|
|
def deployment_update_application [config] {
|
|
let compose_file = ($config | get compose_file)
|
|
|
|
print $"(ansi blue)🔄 Updating application...(ansi reset)"
|
|
|
|
try {
|
|
# Pull latest images
|
|
docker-compose -f $compose_file pull
|
|
|
|
# Restart with new images
|
|
docker-compose -f $compose_file up -d --force-recreate
|
|
|
|
print $"(ansi green)✅ Application updated successfully(ansi reset)"
|
|
} catch {
|
|
print $"(ansi red)❌ Failed to update application(ansi reset)"
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Rollback application
|
|
def deployment_rollback_application [config] {
|
|
print $"(ansi yellow)⚠️ Rollback functionality not implemented yet(ansi reset)"
|
|
print $"(ansi yellow)💡 Please manually specify the desired image tag and redeploy(ansi reset)"
|
|
}
|
|
|
|
# Cleanup unused containers and images
|
|
def deployment_cleanup [config] {
|
|
print $"(ansi blue)🧹 Cleaning up unused containers and images...(ansi reset)"
|
|
|
|
try {
|
|
# Remove stopped containers
|
|
docker container prune -f
|
|
|
|
# Remove unused images
|
|
docker image prune -f
|
|
|
|
# Remove unused volumes
|
|
docker volume prune -f
|
|
|
|
# Remove unused networks
|
|
docker network prune -f
|
|
|
|
print $"(ansi green)✅ Cleanup completed(ansi reset)"
|
|
} catch {
|
|
print $"(ansi red)❌ Cleanup failed(ansi reset)"
|
|
exit 1
|
|
}
|
|
} |