"""CMS deployment orchestrator."""
import os
import sys
import json
import shutil
CUR_DIR = os.path.dirname(os.path.abspath(__file__))
if CUR_DIR not in sys.path:
sys.path.insert(0, CUR_DIR)
ACTION_ROOT = os.path.dirname(CUR_DIR)
if ACTION_ROOT not in sys.path:
sys.path.append(ACTION_ROOT)
from config import cfg, get_config
from log_diagnostics import emit_failure_diagnostics
from log_config import get_logger
from utils import (
exec_popen, run_cmd, run_as_user, run_python_as_user, CommandError,
check_backup_files, check_rollback_files, FileCheckError,
CGroupManager, IPTablesManager, ProcessManager,
ensure_dir, ensure_file, copy_tree, safe_remove,
read_version, get_version_major,
)
from config_validation_runner import validate_config_params_or_raise
LOGGER = get_logger()
def validate_install_config():
"""Validate config_params_lun.json before component pre_install writes config."""
validate_config_params_or_raise(ACTION_ROOT, logger=LOGGER)
class CmsDeploy:
"""CMS deployment orchestrator (cgroup, iptables, user permissions, upgrade/rollback/backup)."""
def __init__(self):
self._cfg = get_config()
self.paths = self._cfg.paths
self.deploy = self._cfg.deploy
self.timeout = self._cfg.timeout
self.ograc_user = self.deploy.ograc_user
self.ograc_group = self.deploy.ograc_group
self.user_and_group = f"{self.ograc_user}:{self.ograc_group}"
self.deploy_mode = self.deploy.deploy_mode
self.ograc_in_container = self.deploy.ograc_in_container
self.storage_share_fs = self.deploy.storage_share_fs
self.storage_archive_fs = self.deploy.storage_archive_fs
self.cluster_name = self.deploy.cluster_name
self.cgroup = CGroupManager(
self.paths.cgroup_memory_path,
self.paths.cgroup_default_mem_size_gb,
)
self.log_file = self.paths.cms_deploy_log
def _run_cms_ctl(self, action, *extra_args):
"""Invoke cms_ctl.py as ograc user (structured args, per-action timeout)."""
script = os.path.join(CUR_DIR, "cms_ctl.py")
args = [action] + [str(a) for a in extra_args]
op_timeout = self.timeout.get(action)
ret, stdout, stderr = run_python_as_user(
script, args, self.ograc_user,
log_file=self.log_file, timeout=op_timeout,
)
if ret != 0:
LOGGER.error(f"cms_ctl.py {action} failed (rc={ret}): {stdout} {stderr}")
raise CommandError(f"python3 cms_ctl.py {' '.join(args)}", ret, stdout, stderr)
return stdout
def _ensure_cms_home(self):
"""Ensure CMS home dir structure exists."""
ensure_dir(self.paths.cms_home, 0o750, self.user_and_group)
ensure_dir(self.paths.cms_cfg_dir, 0o750, self.user_and_group)
ensure_dir(self.paths.cms_log_dir, 0o750, self.user_and_group)
ensure_file(self.log_file, 0o640, self.user_and_group)
def _check_old_install(self):
"""Check for existing RPM install."""
if os.path.isfile(self.paths.rpm_flag):
return
if os.path.isdir(self.paths.cms_service_dir):
raise RuntimeError(f"CMS already installed in {self.paths.cms_home}")
if os.path.exists(self.paths.cms_home):
run_cmd(f"chmod 750 -R {self.paths.cms_home}")
if os.path.exists(self.paths.cms_log_dir):
run_cmd(f"chmod 750 -R {self.paths.cms_log_dir}")
run_cmd(f"find {self.paths.cms_log_dir} -type f | xargs chmod 640")
def _chown_mod_scripts(self):
"""Set script permissions."""
LOGGER.info(f"Setting script permissions for user: {self.ograc_user}")
run_cmd(
f"chown -h {self.user_and_group} {CUR_DIR}/*.py {CUR_DIR}/appctl.sh 2>/dev/null; "
f"chmod 400 {CUR_DIR}/*.py {CUR_DIR}/appctl.sh 2>/dev/null; "
f"chmod 500 {CUR_DIR}/cms_daemon.py {CUR_DIR}/appctl.sh 2>/dev/null",
"failed to set script permissions",
)
def _ensure_user_profile_writable(self):
"""Ensure ograc user .bashrc exists and is writable (fix ownership as root)."""
import pwd as _pwd
try:
home = _pwd.getpwnam(self.ograc_user).pw_dir
except KeyError:
LOGGER.warning("User %s not found, skip profile fix", self.ograc_user)
return
bashrc = os.path.join(home, ".bashrc")
if not os.path.exists(bashrc):
with open(bashrc, "w") as f:
f.write("")
run_cmd(f"chown {self.user_and_group} {bashrc}", "failed to chown .bashrc")
os.chmod(bashrc, 0o644)
def _copy_cms_scripts(self):
"""Copy CMS scripts to install dir (skip if src == dst)."""
dst = self.paths.cms_scripts
if os.path.realpath(CUR_DIR) == os.path.realpath(dst):
LOGGER.info("CMS scripts already in install path, skip copy")
return
LOGGER.info("Copying CMS scripts from %s to %s", CUR_DIR, dst)
copy_tree(CUR_DIR, dst, replace=True, skip_names={"__pycache__"})
def _read_link_type(self):
"""Read link_type from deploy_param.json."""
return self.deploy.get("link_type", "")
def _read_deploy_mode_from_backup(self, backup_path):
"""Read deploy_mode from backup deploy_param.json."""
bak_config = os.path.join(backup_path, "config", "deploy_param.json")
if os.path.exists(bak_config):
try:
with open(bak_config, encoding="utf-8") as f:
return json.load(f).get("deploy_mode", "")
except (json.JSONDecodeError, OSError):
pass
return ""
def _update_cms_service(self, link_type):
"""Update CMS service files (bin/lib)."""
LOGGER.info(f"Updating CMS service files in {self.paths.cms_service_dir}")
pkg = self.paths.cms_pkg_dir
for target in (self.paths.cms_service_dir, os.path.join(self.paths.ograc_app_home, "server")):
run_cmd(f"rm -rf {target}/*")
run_cmd(
f"cp -arf {pkg}/add-ons {pkg}/admin {pkg}/bin "
f"{pkg}/cfg {pkg}/lib {pkg}/package.xml {target}"
)
if self.deploy_mode in ("file", "dss"):
return
addons = os.path.join(self.paths.cms_service_dir, "add-ons")
if link_type == "1":
run_cmd(f"cp -arf {addons}/mlnx/lib* {addons}/")
elif link_type == "0":
run_cmd(f"cp -arf {addons}/nomlnx/lib* {addons}/")
else:
run_cmd(f"cp -arf {addons}/1823/lib* {addons}/")
run_cmd(f"cp -arf {addons}/kmc_shared/lib* {addons}/")
def _chown_mod_cms_service(self):
"""Set CMS service file permissions."""
LOGGER.info("Setting CMS service file permissions")
for base_dir, sub_dir in [
(self.paths.cms_home, "service"),
(self.paths.ograc_app_home, "server"),
]:
target = os.path.join(base_dir, sub_dir) if sub_dir != "service" else self.paths.cms_service_dir
run_cmd(f"chown -hR {self.user_and_group} {base_dir}")
run_cmd(f"chmod -R 700 {target}")
run_cmd(f"find {target}/add-ons -type f | xargs chmod 500")
run_cmd(f"find {target}/admin -type f | xargs chmod 400")
run_cmd(f"find {target}/bin -type f | xargs chmod 500")
run_cmd(f"find {target}/lib -type f | xargs chmod 500")
run_cmd(f"find {target}/cfg -type f | xargs chmod 400")
pkg_xml = os.path.join(target, "package.xml")
if os.path.exists(pkg_xml):
run_cmd(f"chmod 400 {pkg_xml}")
if self.deploy_mode == "dss":
run_cmd(f'setcap CAP_SYS_RAWIO+ep "{self.paths.cms_service_dir}/bin/cms"')
def _update_cms_config_upgrade(self, deploy_mode_backup):
"""Update CMS config on upgrade."""
self._run_cms_ctl("upgrade")
def _update_cms_gcc_file(self, deploy_mode_backup):
"""Update GCC files on upgrade (only when backup mode changes)."""
LOGGER.info("No GCC file update needed")
def _record_cms_info(self, backup_dir):
"""Record pre-upgrade CMS file info."""
LOGGER.info("Recording CMS module info before upgrade")
cms_bak = os.path.join(backup_dir, "cms")
os.makedirs(cms_bak, mode=0o750, exist_ok=True)
run_cmd(f"tree -afis {self.paths.cms_home} >> {cms_bak}/cms_home_files_list.txt")
run_cmd(f"tree -afis {self.paths.cms_scripts} >> {cms_bak}/cms_scripts_files_list.txt")
with open(os.path.join(cms_bak, "backup.bak"), "a") as f:
f.write("cms backup information for upgrade\n")
ret, time_str, _ = exec_popen("date")
f.write(f"time: {time_str}\n")
f.write(f"ograc_user: {self.user_and_group}\n")
ret, size_str, _ = exec_popen(f"du -sh {self.paths.cms_home}")
f.write(f"cms_home: total_size={size_str}\n")
ret, size_str, _ = exec_popen(f"du -sh {self.paths.cms_scripts}")
f.write(f"cms_scripts: total_size={size_str}\n")
def _check_cms_node_and_res_list(self):
"""Check CMS node and resource list."""
LOGGER.info("Checking CMS node and resource list")
base_cmd = "source ~/.bashrc && cms"
ret, stdout, _ = run_as_user(f"{base_cmd} node -list", self.ograc_user)
if ret != 0:
raise RuntimeError("Failed to query cms node -list")
if "node0" not in stdout:
raise RuntimeError("node0 not found in CMS node list")
if "node1" not in stdout:
raise RuntimeError("node1 not found in CMS node list")
ret, stdout, _ = run_as_user(f"{base_cmd} res -list", self.ograc_user)
if ret != 0:
raise RuntimeError("Failed to query cms res -list")
if "db" not in stdout:
raise RuntimeError("resource not found in CMS res list")
def action_start(self):
"""Start CMS."""
LOGGER.info("========== START CMS ==========")
self.cgroup.setup()
IPTablesManager.accept(self.paths.cms_ini)
ProcessManager.ensure_shm_dir(self.paths.shm_home, self.user_and_group)
ProcessManager.clear_shm(self.paths.shm_home, self.paths.instance.shm_pattern)
self._run_cms_ctl("start")
LOGGER.info("========== START CMS DONE ==========")
def action_stop(self):
"""Stop CMS."""
LOGGER.info("========== STOP CMS ==========")
self._run_cms_ctl("stop")
IPTablesManager.delete(self.paths.cms_ini)
LOGGER.info("========== STOP CMS DONE ==========")
def action_pre_install(self, install_type=""):
"""Pre-install."""
LOGGER.info("========== PRE_INSTALL CMS ==========")
validate_install_config()
if install_type == "reserve":
update_cfg = os.path.join(CUR_DIR, "..", "compat", "update_config.py")
run_python_as_user(
update_cfg,
["-c", "cms", "-a", "add", "-k", "cms_reserve", "-v", "cms"],
self.ograc_user,
)
self._check_old_install()
self._chown_mod_scripts()
self._run_cms_ctl("pre_install", install_type)
LOGGER.info("========== PRE_INSTALL CMS DONE ==========")
def _prepare_gcc_device(self):
"""Prepare GCC device permissions as root (DSS mode, non-container only)."""
if self.ograc_in_container != "0":
return
gcc_home = self._cfg.deploy.get("gcc_home", "")
if not gcc_home:
return
if self.deploy_mode == "dss":
node_id = str(self.deploy.node_id)
LOGGER.info("Preparing GCC device %s as root on node %s", gcc_home, node_id)
if node_id == "0":
run_cmd(f"dd if=/dev/zero of={gcc_home} bs=1M count=1025",
f"failed to zero gcc device {gcc_home}")
run_cmd(f"chmod 600 {gcc_home}")
run_cmd(f"chown {self.user_and_group} {gcc_home}")
elif self.deploy_mode == "file":
run_cmd(f"chown {self.user_and_group} {gcc_home} 2>/dev/null || true")
def _patch_cms_rpath(self):
"""Embed instance lib paths into cms binary RUNPATH (setcap makes ld.so ignore LD_LIBRARY_PATH).
Must run before setcap since patchelf clears capabilities."""
cms_bin = os.path.join(self.paths.cms_service_dir, "bin", "cms")
lib_dir = os.path.join(self.paths.cms_service_dir, "lib")
addons_dir = os.path.join(self.paths.cms_service_dir, "add-ons")
rpath = f"{lib_dir}:{addons_dir}"
ret, current_rpath, _ = exec_popen(f'patchelf --print-rpath "{cms_bin}" 2>/dev/null')
if ret != 0:
LOGGER.warning("patchelf not available, trying chrpath")
ret2, _, _ = exec_popen(f'chrpath -r "{rpath}" "{cms_bin}"')
if ret2 != 0:
raise RuntimeError(
"patchelf/chrpath not found. DSS mode requires one of them to embed "
"library paths into the cms binary (setcap makes LD_LIBRARY_PATH ignored). "
"Install patchelf: yum install -y patchelf"
)
LOGGER.info("Set cms RPATH via chrpath: %s", rpath)
return
current_rpath = current_rpath.strip()
if lib_dir in current_rpath and addons_dir in current_rpath:
LOGGER.info("cms RPATH already contains required paths, skip patchelf")
return
run_cmd(
f'patchelf --set-rpath "{rpath}" "{cms_bin}"',
"failed to set RPATH on cms binary",
)
LOGGER.info("Set cms RPATH: %s", rpath)
def _get_cms_install_step(self):
try:
return int(self._cfg.deploy.get("install_step", 0) or 0)
except (TypeError, ValueError):
return 0
def action_install(self, install_type=""):
"""Install CMS."""
LOGGER.info("========== INSTALL CMS ==========")
if (self.ograc_in_container == "0"
and self.deploy_mode != "dss"):
share_path = self.paths.share_path(self.storage_share_fs)
run_cmd(f"chown {self.user_and_group} {share_path}")
if not os.path.isfile(self.paths.rpm_flag):
self._copy_cms_scripts()
self._ensure_user_profile_writable()
cms_install_step = self._get_cms_install_step()
if cms_install_step >= 2:
LOGGER.info(
"CMS install_step=%s, skip root-side GCC prepare to keep install reentrant",
cms_install_step,
)
else:
self._prepare_gcc_device()
if self.deploy_mode == "dss":
self._run_cms_ctl("setup_files")
self._patch_cms_rpath()
run_cmd(f'setcap CAP_SYS_RAWIO+ep "{self.paths.cms_service_dir}/bin/cms"')
self._run_cms_ctl("setup_gcc")
else:
self._run_cms_ctl("install")
if install_type == "reserve":
update_cfg = os.path.join(CUR_DIR, "..", "compat", "update_config.py")
run_python_as_user(
update_cfg,
["--component=cms_ini", "--action=update",
"--key=_DISK_DETECT_FILE", "--value=gcc_file_detect_disk,"],
self.ograc_user,
)
LOGGER.info("========== INSTALL CMS DONE ==========")
def action_uninstall(self, uninstall_type="", force_uninstall=""):
"""Uninstall CMS."""
LOGGER.info("========== UNINSTALL CMS ==========")
self._run_cms_ctl("uninstall", uninstall_type, force_uninstall)
self.cgroup.clean()
LOGGER.info("========== UNINSTALL CMS DONE ==========")
def action_check_status(self):
"""Check status."""
self._run_cms_ctl("check_status")
def action_backup(self):
"""Backup."""
self._run_cms_ctl("backup")
def action_restore(self):
"""Restore CMS config from backup."""
LOGGER.info("========== RESTORE CMS ==========")
backup_cfg_dir = os.path.join(self.paths.backup_dir, "files")
cms_cfg_dir = os.path.join(self.paths.cms_home, "cfg")
if not os.path.isdir(backup_cfg_dir):
LOGGER.error("Backup directory not found: %s", backup_cfg_dir)
raise RuntimeError(f"CMS backup not found: {backup_cfg_dir}")
cms_json_bak = os.path.join(backup_cfg_dir, "cms.json")
if not os.path.isfile(cms_json_bak):
LOGGER.warning("cms.json not found in backup, skipping restore")
return
if os.path.isdir(cms_cfg_dir):
shutil.rmtree(cms_cfg_dir)
os.makedirs(cms_cfg_dir, mode=0o750, exist_ok=True)
for name in os.listdir(backup_cfg_dir):
src = os.path.join(backup_cfg_dir, name)
if os.path.isfile(src) and name.endswith((".json", ".ini")):
shutil.copy2(src, os.path.join(cms_cfg_dir, name))
LOGGER.info("Restored CMS config: %s", name)
exec_popen(
f"chown -hR {self.user_and_group} {cms_cfg_dir}")
LOGGER.info("========== RESTORE CMS DONE ==========")
def action_init_container(self):
"""Container init (config changes + cms_ctl)."""
LOGGER.info("========== INIT CONTAINER CMS ==========")
from cms_container_init import CmsContainerInit
init = CmsContainerInit()
init.run()
LOGGER.info("========== INIT CONTAINER CMS DONE ==========")
def action_pre_upgrade(self):
"""Pre-upgrade check."""
LOGGER.info("========== PRE_UPGRADE CMS ==========")
version_first = get_version_major(self.paths.versions_yml)
ograc_user = self.ograc_user
self._chown_mod_scripts()
if not os.path.isdir(os.path.join(self.paths.cms_home, "service")):
raise RuntimeError("CMS service directory not found, CMS may not be installed")
if self.deploy_mode == "file":
gcc_dir = os.path.join(
self.paths.share_path(self.storage_share_fs), "gcc_home"
)
if not os.path.isdir(gcc_dir):
raise RuntimeError(f"GCC home not found: {gcc_dir}")
ret, stdout, _ = run_as_user("source ~/.bashrc && cms stat -server", ograc_user)
ret2, stdout2, _ = exec_popen(
f"ps -fu {ograc_user} | grep 'cms server -start' | grep -vE '(grep|defunct)' | wc -l"
)
if ret2 == 0 and stdout2.strip() != "1":
raise RuntimeError("CMS process not running, start it before pre_upgrade")
self._check_cms_node_and_res_list()
LOGGER.info("========== PRE_UPGRADE CMS DONE ==========")
def action_upgrade_backup(self, backup_path):
"""Upgrade backup."""
LOGGER.info("========== UPGRADE_BACKUP CMS ==========")
version_first = get_version_major(self.paths.versions_yml)
ograc_user = self.ograc_user
ret, owner, _ = exec_popen(f"stat -c %U {self.paths.cms_home}")
if ret == 0 and owner.strip() != ograc_user:
raise RuntimeError("Upgrade user differs from installed user")
cms_bak = os.path.join(backup_path, "cms")
if os.path.isdir(cms_bak):
raise RuntimeError(f"Backup dir already exists: {cms_bak}")
os.makedirs(cms_bak, mode=0o750)
home_bak = os.path.join(cms_bak, "cms_home")
os.makedirs(home_bak, mode=0o750)
for item in os.listdir(self.paths.cms_home):
if item == "log":
continue
src = os.path.join(self.paths.cms_home, item)
run_cmd(f"cp -arf {src} {home_bak}/")
scripts_bak = os.path.join(cms_bak, "cms_scripts")
os.makedirs(scripts_bak, mode=0o750)
run_cmd(f"cp -arf {self.paths.cms_scripts}/* {scripts_bak}/")
self._record_cms_info(backup_path)
check_backup_files(
f"{cms_bak}/cms_home_files_list.txt", home_bak, self.paths.cms_home
)
check_backup_files(
f"{cms_bak}/cms_scripts_files_list.txt", scripts_bak, self.paths.cms_scripts
)
LOGGER.info("========== UPGRADE_BACKUP CMS DONE ==========")
def action_upgrade(self, upgrade_type="", backup_path=""):
"""Upgrade CMS."""
LOGGER.info("========== UPGRADE CMS ==========")
link_type = self._read_link_type()
deploy_mode_backup = self._read_deploy_mode_from_backup(backup_path)
self._update_cms_service(link_type)
self._chown_mod_cms_service()
self._update_cms_config_upgrade(deploy_mode_backup)
if os.path.realpath(CUR_DIR) != os.path.realpath(self.paths.cms_scripts):
run_cmd(f"rm -rf {self.paths.cms_scripts}/*")
run_cmd(f"cp -arf {CUR_DIR}/* {self.paths.cms_scripts}/")
self._update_cms_gcc_file(deploy_mode_backup)
for tmp_file in self.paths.cms_tmp_files:
safe_remove(tmp_file)
import glob as g
for f in g.glob(os.path.join(self.paths.cms_home, "ograc.ogd.cms*")):
safe_remove(f)
update_cfg = os.path.join(CUR_DIR, "..", "compat", "update_config.py")
run_python_as_user(
update_cfg,
["--component=cms_ini", "--action=update",
"--key=_DISK_DETECT_FILE", "--value=gcc_file_detect_disk,"],
self.ograc_user,
)
LOGGER.info("========== UPGRADE CMS DONE ==========")
def action_rollback(self, rollback_type="", backup_path=""):
"""Rollback CMS."""
LOGGER.info("========== ROLLBACK CMS ==========")
versions_file = os.path.join(CUR_DIR, "../../versions.yml")
version = read_version(versions_file)
cms_bak = os.path.join(backup_path, "cms")
if not os.path.isdir(cms_bak):
raise RuntimeError(f"Backup dir not found: {cms_bak}")
home_bak = os.path.join(cms_bak, "cms_home")
if not os.path.isdir(home_bak):
raise RuntimeError(f"CMS home backup not found: {home_bak}")
LOGGER.info(f"Rolling back CMS from {backup_path}, version={version}")
for item in os.listdir(self.paths.cms_home):
if item == "log":
continue
safe_remove(os.path.join(self.paths.cms_home, item))
run_cmd(f"cp -arf {home_bak}/* {self.paths.cms_home}/")
scripts_bak = os.path.join(cms_bak, "cms_scripts")
if not os.path.isdir(scripts_bak):
raise RuntimeError(f"CMS scripts backup not found: {scripts_bak}")
run_cmd(f"rm -rf {self.paths.cms_scripts}/*")
run_cmd(f"cp -arf {scripts_bak}/* {self.paths.cms_scripts}/")
check_rollback_files(
f"{cms_bak}/cms_home_files_list.txt", home_bak, self.paths.cms_home
)
check_rollback_files(
f"{cms_bak}/cms_scripts_files_list.txt", scripts_bak, self.paths.cms_scripts
)
LOGGER.info("========== ROLLBACK CMS DONE ==========")
def action_post_upgrade(self):
"""Post-upgrade check."""
LOGGER.info("========== POST_UPGRADE CMS ==========")
if not os.listdir(self.paths.cms_service_dir):
raise RuntimeError("CMS service directory is empty after upgrade")
if not os.listdir(self.paths.cms_scripts):
raise RuntimeError("CMS scripts directory is empty after upgrade")
if self.deploy_mode == "file":
gcc_dir = os.path.join(
self.paths.share_path(self.storage_share_fs), "gcc_home"
)
if not os.path.isdir(gcc_dir):
raise RuntimeError(f"GCC home not found after upgrade: {gcc_dir}")
ret, stdout, _ = run_as_user(
"source ~/.bashrc && cms stat -server", self.ograc_user
)
ret2, count_str, _ = exec_popen(
f"ps -fu {self.ograc_user} | grep 'cms server -start' | grep -vE '(grep|defunct)' | wc -l"
)
if ret2 == 0 and count_str.strip() != "1":
raise RuntimeError("CMS process is not running after upgrade")
self._check_cms_node_and_res_list()
LOGGER.info("========== POST_UPGRADE CMS DONE ==========")
def main():
if len(sys.argv) < 2:
print(
"Usage: python3 cms_deploy.py <action> [args...]\n"
"Actions: start, stop, pre_install, install, uninstall, check_status,\n"
" backup, restore, init_container, pre_upgrade, upgrade_backup,\n"
" upgrade, rollback, post_upgrade",
file=sys.stderr,
)
sys.exit(1)
action = sys.argv[1]
args = sys.argv[2:]
deployer = CmsDeploy()
deployer._ensure_cms_home()
try:
if action == "start":
deployer.action_start()
elif action == "stop":
deployer.action_stop()
elif action == "pre_install":
install_type = args[0] if args else ""
deployer.action_pre_install(install_type)
elif action == "install":
install_type = args[0] if args else ""
deployer.action_install(install_type)
elif action == "uninstall":
uninstall_type = args[0] if args else ""
force_uninstall = args[1] if len(args) > 1 else ""
deployer.action_uninstall(uninstall_type, force_uninstall)
elif action == "check_status":
deployer.action_check_status()
elif action == "backup":
deployer.action_backup()
elif action == "restore":
deployer.action_restore()
elif action == "init_container":
deployer.action_init_container()
elif action == "pre_upgrade":
deployer.action_pre_upgrade()
elif action == "upgrade_backup":
backup_path = args[0] if args else ""
deployer.action_upgrade_backup(backup_path)
elif action == "upgrade":
upgrade_type = args[0] if args else ""
backup_path = args[1] if len(args) > 1 else ""
deployer.action_upgrade(upgrade_type, backup_path)
elif action == "rollback":
rollback_type = args[0] if args else ""
backup_path = args[1] if len(args) > 1 else ""
deployer.action_rollback(rollback_type, backup_path)
elif action == "post_upgrade":
deployer.action_post_upgrade()
else:
print(f"Unknown action: {action}", file=sys.stderr)
sys.exit(1)
except (CommandError, FileCheckError, RuntimeError) as e:
LOGGER.error(f"Action '{action}' failed: {e}")
emit_failure_diagnostics("CMS", action, deployer.paths.diagnostic_log_specs(), error=e)
print(f"ERROR: {e}", file=sys.stderr)
sys.exit(1)
except Exception as e:
LOGGER.error(f"Unexpected error in action '{action}': {e}", exc_info=True)
emit_failure_diagnostics("CMS", action, deployer.paths.diagnostic_log_specs(), error=e)
print(f"UNEXPECTED ERROR: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()