Update core components including CLI, Nushell libraries, plugins system, and utility scripts for the provisioning system. CLI Updates: - Command implementations - CLI utilities and dispatching - Help system improvements - Command validation Library Updates: - Configuration management system - Infrastructure validation - Extension system improvements - Secrets management - Workspace operations - Cache management system Plugin System: - Interactive form plugin (inquire) - KCL integration plugin - Performance optimization plugins - Plugin registration system Utilities: - Build and distribution scripts - Installation procedures - Testing utilities - Development tools Documentation: - Library module documentation - Extension API guides - Plugin usage guides - Service management documentation All changes are backward compatible. No breaking changes.
535 lines
15 KiB
Plaintext
535 lines
15 KiB
Plaintext
#!/usr/bin/env nu
|
|
|
|
# Service Lifecycle Management
|
|
# Handles starting and stopping services based on deployment mode
|
|
|
|
def get-service-pid-dir []: nothing -> string {
|
|
$"($env.HOME)/.provisioning/services/pids"
|
|
}
|
|
|
|
def get-service-log-dir []: nothing -> string {
|
|
$"($env.HOME)/.provisioning/services/logs"
|
|
}
|
|
|
|
# Start service based on deployment mode
|
|
export def start-service-by-mode [
|
|
service_def: record
|
|
service_name: string
|
|
]: nothing -> bool {
|
|
match $service_def.deployment.mode {
|
|
"binary" => {
|
|
start-binary-service $service_def $service_name
|
|
}
|
|
"docker" => {
|
|
start-docker-service $service_def $service_name
|
|
}
|
|
"docker-compose" => {
|
|
start-docker-compose-service $service_def $service_name
|
|
}
|
|
"kubernetes" => {
|
|
start-kubernetes-service $service_def $service_name
|
|
}
|
|
"remote" => {
|
|
# Remote services are not started locally
|
|
print $"Service '($service_name)' is remote - checking availability..."
|
|
true
|
|
}
|
|
_ => {
|
|
print $"Unknown deployment mode: ($service_def.deployment.mode)"
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
# Start binary service
|
|
def start-binary-service [
|
|
service_def: record
|
|
service_name: string
|
|
]: nothing -> bool {
|
|
let binary_config = $service_def.deployment.binary
|
|
let binary_path = ($binary_config.binary_path | str replace -a '${HOME}' $env.HOME)
|
|
|
|
# Expand binary path
|
|
let binary_path = if ($binary_path | str starts-with '~') {
|
|
$binary_path | str replace '~' $env.HOME
|
|
} else {
|
|
$binary_path
|
|
}
|
|
|
|
if not ($binary_path | path exists) {
|
|
print $"Binary not found: ($binary_path)"
|
|
return false
|
|
}
|
|
|
|
let working_dir = if "working_dir" in $binary_config {
|
|
$binary_config.working_dir | str replace -a '${HOME}' $env.HOME
|
|
} else {
|
|
$env.PWD
|
|
}
|
|
|
|
let log_dir = (get-service-log-dir)
|
|
let log_file = $"($log_dir)/($service_name).log"
|
|
let pid_dir = (get-service-pid-dir)
|
|
let pid_file = $"($pid_dir)/($service_name).pid"
|
|
|
|
# Build command with args
|
|
let args = $binary_config.args | str join ' '
|
|
let env_vars = if "env" in $binary_config {
|
|
$binary_config.env | transpose key value | each { |row|
|
|
$"($row.key)=($row.value)"
|
|
} | str join ' '
|
|
} else {
|
|
""
|
|
}
|
|
|
|
# Start in background
|
|
let cmd = if ($env_vars | is-empty) {
|
|
$"($binary_path) ($args) >> ($log_file) 2>&1 & echo $!"
|
|
} else {
|
|
$"($env_vars) ($binary_path) ($args) >> ($log_file) 2>&1 & echo $!"
|
|
}
|
|
|
|
let result = (do {
|
|
bash -c $cmd | str trim
|
|
} | complete)
|
|
|
|
if $result.exit_code == 0 {
|
|
let pid = $result.stdout
|
|
$pid | save -f $pid_file
|
|
|
|
sleep 1sec
|
|
|
|
# Verify process started
|
|
let pid_int = ($pid | into int)
|
|
if (ps | where pid == $pid_int | length) > 0 {
|
|
print $"✅ Started [$service_name] with PID [$pid]"
|
|
true
|
|
} else {
|
|
print $"❌ Failed to start [$service_name]"
|
|
false
|
|
}
|
|
} else {
|
|
print $"❌ Error starting [$service_name]: ($result.stderr)"
|
|
false
|
|
}
|
|
}
|
|
|
|
# Start Docker service
|
|
def start-docker-service [
|
|
service_def: record
|
|
service_name: string
|
|
]: nothing -> bool {
|
|
let docker_config = $service_def.deployment.docker
|
|
|
|
# Check if container already exists
|
|
let existing = (docker ps -a --filter $"name=($docker_config.container_name)" --format "{{.Names}}" | lines)
|
|
|
|
if ($docker_config.container_name in $existing) {
|
|
# Container exists, try to start it
|
|
let result = (do {
|
|
docker start $docker_config.container_name
|
|
} | complete)
|
|
|
|
if $result.exit_code == 0 {
|
|
print $"✅ Started existing container: ($docker_config.container_name)"
|
|
return true
|
|
} else {
|
|
# Remove old container and create new one
|
|
print $"Removing old container: ($docker_config.container_name)"
|
|
docker rm -f $docker_config.container_name
|
|
}
|
|
}
|
|
|
|
# Build docker run command by collecting all components
|
|
let restart_policy = $docker_config.restart_policy? | default "unless-stopped"
|
|
|
|
let port_args = (
|
|
$docker_config.ports
|
|
| flat-map {|port| ["-p", $port]}
|
|
)
|
|
|
|
let volume_args = (
|
|
$docker_config.volumes
|
|
| each {|volume|
|
|
let expanded = ($volume | str replace -a '${HOME}' $env.HOME)
|
|
["-v", $expanded]
|
|
}
|
|
| flatten
|
|
)
|
|
|
|
let env_args = if "environment" in $docker_config {
|
|
($docker_config.environment | columns)
|
|
| each {|key|
|
|
let value = ($docker_config.environment | get $key)
|
|
["-e", $"($key)=($value)"]
|
|
}
|
|
| flatten
|
|
} else {
|
|
[]
|
|
}
|
|
|
|
let network_args = if "networks" in $docker_config {
|
|
$docker_config.networks
|
|
| flat-map {|network| ["--network", $network]}
|
|
} else {
|
|
[]
|
|
}
|
|
|
|
let command_args = if "command" in $docker_config {
|
|
$docker_config.command
|
|
} else {
|
|
[]
|
|
}
|
|
|
|
let cmd = [
|
|
"docker", "run", "-d",
|
|
"--name", $docker_config.container_name,
|
|
"--restart", $restart_policy
|
|
]
|
|
| append $port_args
|
|
| append $volume_args
|
|
| append $env_args
|
|
| append $network_args
|
|
| append $docker_config.image
|
|
| if ($command_args | is-not-empty) {
|
|
append $command_args
|
|
} else {
|
|
.
|
|
}
|
|
|
|
let result = (do {
|
|
run-external ...$cmd
|
|
} | complete)
|
|
|
|
if $result.exit_code == 0 {
|
|
print $"✅ Started Docker container: ($docker_config.container_name)"
|
|
true
|
|
} else {
|
|
print $"❌ Failed to start Docker container: ($docker_config.container_name) - ($result.stderr)"
|
|
false
|
|
}
|
|
}
|
|
|
|
# Start Docker Compose service
|
|
def start-docker-compose-service [
|
|
service_def: record
|
|
service_name: string
|
|
]: nothing -> bool {
|
|
let compose_config = $service_def.deployment.docker_compose
|
|
let compose_file = ($compose_config.compose_file | str replace -a '${HOME}' $env.HOME)
|
|
|
|
if not ($compose_file | path exists) {
|
|
print $"Compose file not found: ($compose_file)"
|
|
return false
|
|
}
|
|
|
|
let project_name = $compose_config.project_name? | default "provisioning"
|
|
|
|
let result = if "env_file" in $compose_config {
|
|
let env_file = ($compose_config.env_file | str replace -a '${HOME}' $env.HOME)
|
|
(do {
|
|
docker compose -f $compose_file -p $project_name --env-file $env_file up -d $compose_config.service_name
|
|
} | complete)
|
|
} else {
|
|
(do {
|
|
docker compose -f $compose_file -p $project_name up -d $compose_config.service_name
|
|
} | complete)
|
|
}
|
|
|
|
if $result.exit_code == 0 {
|
|
print $"✅ Started Docker Compose service: ($compose_config.service_name)"
|
|
true
|
|
} else {
|
|
print $"❌ Failed to start Docker Compose service: ($compose_config.service_name) - ($result.stderr)"
|
|
false
|
|
}
|
|
}
|
|
|
|
# Start Kubernetes service
|
|
def start-kubernetes-service [
|
|
service_def: record
|
|
service_name: string
|
|
]: nothing -> bool {
|
|
let k8s_config = $service_def.deployment.kubernetes
|
|
|
|
let kubeconfig = if "kubeconfig" in $k8s_config {
|
|
["--kubeconfig", $k8s_config.kubeconfig]
|
|
} else {
|
|
[]
|
|
}
|
|
|
|
# Check if namespace exists
|
|
let ns_check = (do {
|
|
kubectl ...$kubeconfig get namespace $k8s_config.namespace
|
|
} | complete)
|
|
|
|
if $ns_check.exit_code != 0 {
|
|
print $"Creating namespace: ($k8s_config.namespace)"
|
|
kubectl ...$kubeconfig create namespace $k8s_config.namespace
|
|
}
|
|
|
|
# Apply manifests if provided
|
|
if "manifests_path" in $k8s_config {
|
|
let manifests_path = ($k8s_config.manifests_path | str replace -a '${HOME}' $env.HOME)
|
|
|
|
if ($manifests_path | path exists) {
|
|
let result = (do {
|
|
kubectl ...$kubeconfig apply -f $manifests_path -n $k8s_config.namespace
|
|
} | complete)
|
|
|
|
if $result.exit_code == 0 {
|
|
print $"✅ Applied manifests for: [$service_name]"
|
|
} else {
|
|
print $"❌ Failed to apply manifests for: [$service_name] - ($result.stderr)"
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
# Install Helm chart if provided
|
|
if "helm_chart" in $k8s_config {
|
|
let helm_config = $k8s_config.helm_chart
|
|
|
|
let base_helm_cmd = ["helm", "install", $helm_config.release_name, $helm_config.chart, "-n", $k8s_config.namespace]
|
|
|
|
if "repo_url" in $helm_config {
|
|
# Add repo first
|
|
let repo_result = (do {
|
|
helm repo add $service_name $helm_config.repo_url
|
|
helm repo update
|
|
} | complete)
|
|
|
|
if $repo_result.exit_code != 0 {
|
|
print $"Warning: Could not add Helm repo - ($repo_result.stderr)"
|
|
}
|
|
}
|
|
|
|
let with_version = if "version" in $helm_config {
|
|
$base_helm_cmd | append ["--version", $helm_config.version]
|
|
} else {
|
|
$base_helm_cmd
|
|
}
|
|
|
|
let helm_cmd = if "values_file" in $helm_config {
|
|
let values_file = ($helm_config.values_file | str replace -a '${HOME}' $env.HOME)
|
|
$with_version | append ["-f", $values_file]
|
|
} else {
|
|
$with_version
|
|
}
|
|
|
|
let result = (do {
|
|
run-external ...$helm_cmd
|
|
} | complete)
|
|
|
|
if $result.exit_code == 0 {
|
|
print $"✅ Installed Helm chart: ($helm_config.chart)"
|
|
true
|
|
} else {
|
|
print $"❌ Failed to install Helm chart: ($helm_config.chart) - ($result.stderr)"
|
|
false
|
|
}
|
|
} else {
|
|
true
|
|
}
|
|
}
|
|
|
|
# Stop service by mode
|
|
export def stop-service-by-mode [
|
|
service_name: string
|
|
service_def: record
|
|
force: bool = false
|
|
]: nothing -> bool {
|
|
match $service_def.deployment.mode {
|
|
"binary" => {
|
|
stop-binary-service $service_name $force
|
|
}
|
|
"docker" => {
|
|
stop-docker-service $service_def $force
|
|
}
|
|
"docker-compose" => {
|
|
stop-docker-compose-service $service_def
|
|
}
|
|
"kubernetes" => {
|
|
stop-kubernetes-service $service_def $force
|
|
}
|
|
"remote" => {
|
|
print $"Service '($service_name)' is remote - cannot stop"
|
|
true
|
|
}
|
|
_ => {
|
|
print $"Unknown deployment mode: ($service_def.deployment.mode)"
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
# Stop binary service
|
|
def stop-binary-service [
|
|
service_name: string
|
|
force: bool
|
|
]: nothing -> bool {
|
|
let pid_dir = (get-service-pid-dir)
|
|
let pid_file = $"($pid_dir)/($service_name).pid"
|
|
|
|
if not ($pid_file | path exists) {
|
|
print $"No PID file found for ($service_name)"
|
|
return true
|
|
}
|
|
|
|
let pid = (open $pid_file | str trim | into int)
|
|
|
|
if (ps | where pid == $pid | length) == 0 {
|
|
print $"Process [$pid] not found"
|
|
rm $pid_file
|
|
return true
|
|
}
|
|
|
|
let result = (do {
|
|
if $force {
|
|
kill -9 $pid
|
|
} else {
|
|
kill $pid
|
|
}
|
|
} | complete)
|
|
|
|
if $result.exit_code == 0 {
|
|
sleep 2sec
|
|
|
|
# Check if still running
|
|
if (ps | where pid == $pid | length) > 0 {
|
|
print $"Process still running, force killing..."
|
|
kill -9 $pid
|
|
sleep 1sec
|
|
}
|
|
|
|
rm $pid_file
|
|
print $"✅ Stopped [$service_name]"
|
|
true
|
|
} else {
|
|
print $"❌ Failed to stop [$service_name]: ($result.stderr)"
|
|
false
|
|
}
|
|
}
|
|
|
|
# Stop Docker service
|
|
def stop-docker-service [
|
|
service_def: record
|
|
force: bool
|
|
]: nothing -> bool {
|
|
let container_name = $service_def.deployment.docker.container_name
|
|
|
|
let result = (do {
|
|
if $force {
|
|
docker kill $container_name
|
|
} else {
|
|
docker stop $container_name
|
|
}
|
|
} | complete)
|
|
|
|
if $result.exit_code == 0 {
|
|
print $"✅ Stopped Docker container: [$container_name]"
|
|
true
|
|
} else {
|
|
print $"❌ Failed to stop Docker container: [$container_name] - ($result.stderr)"
|
|
false
|
|
}
|
|
}
|
|
|
|
# Stop Docker Compose service
|
|
def stop-docker-compose-service [
|
|
service_def: record
|
|
]: nothing -> bool {
|
|
let compose_config = $service_def.deployment.docker_compose
|
|
let compose_file = ($compose_config.compose_file | str replace -a '${HOME}' $env.HOME)
|
|
let project_name = $compose_config.project_name? | default "provisioning"
|
|
|
|
let result = (do {
|
|
docker compose -f $compose_file -p $project_name stop $compose_config.service_name
|
|
} | complete)
|
|
|
|
if $result.exit_code == 0 {
|
|
print $"✅ Stopped Docker Compose service: ($compose_config.service_name)"
|
|
true
|
|
} else {
|
|
print $"❌ Failed to stop Docker Compose service: ($compose_config.service_name) - ($result.stderr)"
|
|
false
|
|
}
|
|
}
|
|
|
|
# Stop Kubernetes service
|
|
def stop-kubernetes-service [
|
|
service_def: record
|
|
force: bool
|
|
]: nothing -> bool {
|
|
let k8s_config = $service_def.deployment.kubernetes
|
|
|
|
let kubeconfig = if "kubeconfig" in $k8s_config {
|
|
["--kubeconfig", $k8s_config.kubeconfig]
|
|
} else {
|
|
[]
|
|
}
|
|
|
|
# Delete deployment
|
|
let result = (do {
|
|
if $force {
|
|
kubectl ...$kubeconfig delete deployment $k8s_config.deployment_name -n $k8s_config.namespace --force --grace-period=0
|
|
} else {
|
|
kubectl ...$kubeconfig delete deployment $k8s_config.deployment_name -n $k8s_config.namespace
|
|
}
|
|
} | complete)
|
|
|
|
if $result.exit_code == 0 {
|
|
print $"✅ Deleted Kubernetes deployment: ($k8s_config.deployment_name)"
|
|
true
|
|
} else {
|
|
print $"❌ Failed to delete Kubernetes deployment: ($k8s_config.deployment_name) - ($result.stderr)"
|
|
false
|
|
}
|
|
}
|
|
|
|
# Get service PID (for binary services)
|
|
export def get-service-pid [
|
|
service_name: string
|
|
]: nothing -> int {
|
|
let pid_dir = (get-service-pid-dir)
|
|
let pid_file = $"($pid_dir)/[$service_name].pid"
|
|
|
|
if not ($pid_file | path exists) {
|
|
return 0
|
|
}
|
|
|
|
let result = (do {
|
|
open $pid_file | str trim | into int
|
|
} | complete)
|
|
|
|
if $result.exit_code == 0 {
|
|
$result.stdout | into int
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
# Kill service process
|
|
export def kill-service-process [
|
|
service_name: string
|
|
signal: string = "TERM"
|
|
]: nothing -> bool {
|
|
let pid = (get-service-pid $service_name)
|
|
|
|
if $pid == 0 {
|
|
print $"No PID found for [$service_name]"
|
|
return false
|
|
}
|
|
|
|
let result = (do {
|
|
bash -c $"kill -($signal) ($pid)"
|
|
} | complete)
|
|
|
|
if $result.exit_code == 0 {
|
|
true
|
|
} else {
|
|
print $"Failed to send [$signal] to process [$pid]: ($result.stderr)"
|
|
false
|
|
}
|
|
}
|