162 lines
5.9 KiB
Python
162 lines
5.9 KiB
Python
import importlib.util
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import fcntl
|
|
import select
|
|
from ..services import config as Config
|
|
from ..lib import color_log
|
|
|
|
def set_nonblocking(fd):
|
|
"""Make a file descriptor non-blocking."""
|
|
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
|
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
|
|
|
def run_command(cmd, description):
|
|
"""Run a command and display output in real-time."""
|
|
try:
|
|
color_log.Show("INFO", f"Executing: {description}")
|
|
|
|
# Create process without output buffering
|
|
process = subprocess.Popen(
|
|
cmd,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
bufsize=0, # No buffering
|
|
universal_newlines=True
|
|
)
|
|
|
|
# Set both pipes to non-blocking mode
|
|
set_nonblocking(process.stdout.fileno())
|
|
set_nonblocking(process.stderr.fileno())
|
|
|
|
# Use select to read from pipes as data becomes available
|
|
while True:
|
|
reads = [process.stdout.fileno(), process.stderr.fileno()]
|
|
ret = select.select(reads, [], [])
|
|
|
|
for fd in ret[0]:
|
|
if fd == process.stdout.fileno():
|
|
line = process.stdout.readline()
|
|
if line:
|
|
print(line.rstrip())
|
|
if fd == process.stderr.fileno():
|
|
line = process.stderr.readline()
|
|
if line:
|
|
print(line.rstrip())
|
|
|
|
# Check if process has finished
|
|
if process.poll() is not None:
|
|
break
|
|
|
|
# Read any remaining output
|
|
stdout, stderr = process.communicate()
|
|
if stdout:
|
|
print(stdout.rstrip())
|
|
if stderr:
|
|
print(stderr.rstrip())
|
|
|
|
return process.returncode == 0
|
|
|
|
except Exception as e:
|
|
color_log.Show("FAILED", f"Error executing {description}: {str(e)}")
|
|
return False
|
|
|
|
def update_instance(instance_name, action, force_pull=False):
|
|
"""Update a single instance with git pull, module update, and service restart."""
|
|
color_log.Show("INFO", f"\n=== Starting update process for {instance_name} ===")
|
|
|
|
# 1. Pull latest code
|
|
color_log.Show("INFO", "Step 1/4: Pulling latest code...")
|
|
pull_cmd = ["python", "utility/main.py", "git", "pull", instance_name]
|
|
if force_pull:
|
|
pull_cmd.append("--force")
|
|
if not run_command(pull_cmd, f"Pulling latest code for {instance_name}"):
|
|
color_log.Show("WARNING", f"Skipping module update for {instance_name} due to pull failure")
|
|
return False
|
|
|
|
# 2. Update module list
|
|
color_log.Show("INFO", "Step 2/4: Updating module list...")
|
|
module_list_cmd = ["python", "utility/main.py", "module", "update_list", instance_name]
|
|
if not run_command(module_list_cmd, f"Updating module list for {instance_name}"):
|
|
color_log.Show("WARNING", f"Module list update failed for {instance_name}")
|
|
return False
|
|
|
|
# 3. Update modules
|
|
color_log.Show("INFO", "Step 3/4: Updating modules...")
|
|
module_cmd = ["python", "utility/main.py", "module", action, instance_name]
|
|
if not run_command(module_cmd, f"Updating modules for {instance_name}"):
|
|
color_log.Show("WARNING", f"Module update failed for {instance_name}")
|
|
return False
|
|
|
|
# 4. Restart service
|
|
color_log.Show("INFO", "Step 4/4: Restarting service...")
|
|
restart_cmd = ["python", "utility/main.py", "service", "restart", instance_name]
|
|
if not run_command(restart_cmd, f"Restarting service for {instance_name}"):
|
|
color_log.Show("WARNING", f"Service restart failed for {instance_name}")
|
|
return False
|
|
|
|
color_log.Show("OK", f"=== Update process completed for {instance_name} ===\n")
|
|
return True
|
|
|
|
def main():
|
|
# Parse arguments
|
|
parser = argparse.ArgumentParser(description="Update modules for all instances")
|
|
parser.add_argument("instance", type=str, help="Instance Name")
|
|
parser.add_argument(
|
|
"action",
|
|
help="Action to perform",
|
|
choices=["uninstall", "install", "upgrade"]
|
|
)
|
|
parser.add_argument(
|
|
"config_path",
|
|
help="Path to the config file"
|
|
)
|
|
parser.add_argument(
|
|
"--force-pull",
|
|
action="store_true",
|
|
help="Force pull with hard reset (discards local changes)"
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
# Load the configuration
|
|
color_log.Show("INFO", f"Loading configuration from {args.config_path}")
|
|
config = Config.Config(config_path=args.config_path)
|
|
if args.instance == "all":
|
|
# Get instances
|
|
instances = config.get_instances()
|
|
if not isinstance(instances, list):
|
|
color_log.Show("FAILED", "Error: instances is not a valid list.")
|
|
sys.exit(1)
|
|
|
|
# Process each instance
|
|
success_count = 0
|
|
for instance in instances:
|
|
if "name" not in instance:
|
|
color_log.Show("WARNING", f"Instance missing 'name' field. Skipping.")
|
|
continue
|
|
|
|
instance_name = instance["name"]
|
|
color_log.Show("INFO", f"\nProcessing instance: {instance_name}")
|
|
|
|
if update_instance(instance_name, args.action, args.force_pull):
|
|
success_count += 1
|
|
color_log.Show("OK", f"Successfully updated {instance_name}")
|
|
else:
|
|
color_log.Show("FAILED", f"Failed to update {instance_name}")
|
|
|
|
# Summary
|
|
color_log.Show("INFO", f"\nUpdate Summary:")
|
|
color_log.Show("INFO", f"Total instances: {len(instances)}")
|
|
color_log.Show("INFO", f"Successful updates: {success_count}")
|
|
color_log.Show("INFO", f"Failed updates: {len(instances) - success_count}")
|
|
else:
|
|
if update_instance(args.instance, args.action, args.force_pull):
|
|
color_log.Show("OK", f"Successfully updated {args.instance}")
|
|
else:
|
|
color_log.Show("FAILED", f"Failed to update {args.instance}")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|