diff --git a/doc/zh/Command.md b/doc/zh/Command.md index f423569c8bf285e498ef31898ad783284577f084..a17481947561c69d75cdfa807f14f46bc0fad703 100644 --- a/doc/zh/Command.md +++ b/doc/zh/Command.md @@ -16,12 +16,14 @@ ## `oedp run [action]` -执行项目某个方法,默认为当前路径;`[action]`为插件可使用的方法,可通过 `oedp info`命令查询。 - -| 选项 | 简写 | 是否必需 | 功能说明 | -| -------------------- | ------ | -------- | ------------------------ | -| `--project [path]` | `-p` | N | 项目路径,默认为当前路径 | -| `--debug` | `-d` | N | 以debug模式运行 | +执行项目某个方法,默认为当前路径;`[action]`为插件支持的操作,可通过 `oedp info`命令查询 + +| 选项 | 简写 | 是否必需 | 功能说明 | +| ------------------ | ---- | -------- | ------------------------------------ | +| `--project [path]` | `-p` | N | 项目路径,默认为当前路径 | +| `--debug` | `-d` | N | 以debug模式运行 | +| `--time` | `-t` | N | 记录脚本中每个步骤的时间戳与执行用时 | +| `--local` | `-l` | N | 部署到本地(并非所有插件都支持) | ## `oedp list` diff --git a/oedp/build/constants.sh b/oedp/build/constants.sh index 33c924ea8457c974838cf4ca0e72eb2382cfaaed..1ccd9286cce017faadfa7a42c3c9f84a87a59aa5 100644 --- a/oedp/build/constants.sh +++ b/oedp/build/constants.sh @@ -26,5 +26,5 @@ BUILD_SCRIPT_DIR="${PROJECT_DIR}"/build # 存放构建中间产物的暂存目录 TEMP_DIR="${PROJECT_DIR}"/temp # 打包目录 -PACKING_DIR_NAME=oedp-1.1.1 +PACKING_DIR_NAME=oedp-1.1.2 PACKING_DIR="${TEMP_DIR}/${PACKING_DIR_NAME}" \ No newline at end of file diff --git a/oedp/build/oedp.spec b/oedp/build/oedp.spec index 6742bf3501fbddd88d81e07baa5826d7b28ba089..66169b1a7c308bdb75b31d52eae19bf3ee1c5aef 100644 --- a/oedp/build/oedp.spec +++ b/oedp/build/oedp.spec @@ -1,9 +1,10 @@ %define _python_bytecompile_errors_terminate_build 0 Name: oedp -Version: 1.1.1 +Version: 1.1.2 Release: release_number Summary: openEuler deploy tool License: MulanPSL-2.0 +URL: https://gitee.com/openeuler/oeDeploy Source0: %{name}-%{version}.tar.gz BuildArch: noarch @@ -70,6 +71,9 @@ fi %changelog +* Tue Jul 5 2025 Ding Jiahui - 1.1.2-0 +- Add local deployment mode and runtime printing. + * Tue Jun 3 2025 Ding Jiahui - 1.1.1-0 - Add the command 'oedp list' to support querying the plugin list. diff --git a/oedp/build/static/oeDeploy.desktop b/oedp/build/static/oeDeploy.desktop index 02fd683fad55d90aafd9c40d9ff39571f98c16ae..5b5cca739f7e9921f6da72ab126b83a189f78e63 100644 --- a/oedp/build/static/oeDeploy.desktop +++ b/oedp/build/static/oeDeploy.desktop @@ -1,5 +1,5 @@ [Desktop Entry] -Version=1.1.1 +Version=1.1.2 Type=Application Name=oeDeploy Exec=xdg-open /usr/share/applications/oeDeploy.html diff --git a/oedp/src/commands/run/run_action.py b/oedp/src/commands/run/run_action.py index 356aa5d2ee802054a9e379e693be89e7ebe4fb02..9637222db35f498062199b04137a08ce6d6ef12b 100644 --- a/oedp/src/commands/run/run_action.py +++ b/oedp/src/commands/run/run_action.py @@ -18,19 +18,25 @@ from src.utils.log.logger_generator import LoggerGenerator class RunAction: - def __init__(self, action: str, tasks: list, project: str, debug: bool): + def __init__(self, action: str, tasks: list, project: str, **kwargs): """ 执行指定项目的指定方法。 - :param project: 项目目录路径 :param action: 方法名称 - :param tasks: 方法代码路径 - :param debug: 是否启用调试模式 + :param tasks: 任务列表 + :param project: 项目目录路径 + :param kwargs: 可选参数,包括: + - debug: bool, 是否启用调试模式 (默认False) + - time: bool, 是否记录任务时间 (默认False) + - local: bool, 是否部署到本地 (默认False) + 以及其他自定义参数 """ self.action = action self.tasks = tasks self.project = project - self.debug = debug + self.debug = kwargs.get('debug', False) + self.time = kwargs.get('time', False) + self.local = kwargs.get('local', False) self.log = LoggerGenerator().get_logger('run_action') def run(self) -> bool: @@ -60,6 +66,7 @@ class RunAction: if self.debug and task.get('disabled_in_debug', False): self.log.info(f'Skipping task "{task.get("name", "unamed task")}"') return True + self.log.info(f'Running task {task.get("name", "")}') workspace = os.path.join(project, 'workspace') playbook = os.path.join(workspace, task['playbook']) @@ -67,6 +74,7 @@ class RunAction: self.log.error(f'Playbook does not exist: {os.path.abspath(playbook)}') return False inventory = os.path.join(project, 'config.yaml') + cmd = ['ansible-playbook', playbook, '-i', inventory] if 'vars' in task: variables = os.path.join(workspace, task['vars']) @@ -76,9 +84,17 @@ class RunAction: cmd.extend(['-e', f'@{variables}']) if 'scope' in task and task['scope'] != 'all': cmd.extend(['--limit', task['scope']]) + if self.local: + cmd.extend(['--connection=local']) + + env = os.environ.copy() + if self.time: + env['ANSIBLE_CALLBACK_WHITELIST'] = 'profile_tasks' + self.log.debug(f'Executing cmd: {cmd}') try: - out, err, ret = CommandExecutor.run_single_cmd(cmd, print_on_console=True) + out, err, ret = CommandExecutor.run_single_cmd(cmd, print_on_console=True, env=env) + self.log.debug('Execution log:\n' + out) if ret != 0: self.log.error(f'Execute cmd failed [code:{ret}]:\nSTDOUT: {out}\nSTDERR: {err}') return False diff --git a/oedp/src/commands/run/run_cmd.py b/oedp/src/commands/run/run_cmd.py index 46b6640f905f7bebe3c018e2dfdc428b5b718215..4adcc6e851c044e7bfb0d5e83738823caca2d63a 100644 --- a/oedp/src/commands/run/run_cmd.py +++ b/oedp/src/commands/run/run_cmd.py @@ -21,17 +21,21 @@ from decimal import Decimal class RunCmd: - def __init__(self, action: str, project: str, debug: bool): + def __init__(self, action: str, project: str, debug: bool = False, time: bool = False, local: bool = False): """ 执行一个项目中的方法。 :param action: 方法名称 :param project: 项目目录路径 :param debug: 是否启用调试模式 + :param time: 是否记录任务时间 + :param local: 是否部署到本地 """ self.action = action self.project = project self.debug = debug + self.time = time + self.local = local self.log = LoggerGenerator().get_logger('run_cmd') def run(self): @@ -54,7 +58,12 @@ class RunCmd: self.log.error(f'Failed to get tasks info: {action}') return False tasks = action['tasks'] - return RunAction(self.action, tasks, self.project, self.debug).run() + return RunAction( + self.action, tasks, self.project, + debug=self.debug, + time=self.time, + local=self.local + ).run() finally: end_time = time.time() seconds = Decimal(f"{format(end_time - start_time, '.1f')}") diff --git a/oedp/src/constants/const.py b/oedp/src/constants/const.py index 717222bb0121a83cf827eef080d9297b44a62a63..48da440963551fde71a18de2eaef170703c15cde 100644 --- a/oedp/src/constants/const.py +++ b/oedp/src/constants/const.py @@ -15,7 +15,7 @@ import stat # oedp 版本信息 -VERSION = "1.1.1-0" +VERSION = "1.1.2" OK = 0 FAILED = 1 diff --git a/oedp/src/parsers/oedp_parser.py b/oedp/src/parsers/oedp_parser.py index 36fbf7e330bc0d11982295076da4333a794a9c32..667a8e300ff10dae5748a2766faf6ac523a4455c 100644 --- a/oedp/src/parsers/oedp_parser.py +++ b/oedp/src/parsers/oedp_parser.py @@ -147,7 +147,7 @@ class OeDeployParser: 'run', prog='oedp run', help='run an action on a project', - usage='%(prog)s [-p|--project ]' + usage='%(prog)s [-p|--project ] [-d|--debug] [-t|--time] [-l|--local]' ) deploy_command.add_argument( 'action', @@ -165,6 +165,16 @@ class OeDeployParser: action='store_true', help='Enable debug mode' ) + deploy_command.add_argument( + '-t', '--time', + action='store_true', + help='Enable timing for each task' + ) + deploy_command.add_argument( + '-l', '--local', + action='store_true', + help='Force deploy to localhost' + ) deploy_command.set_defaults(func=self._run_run_command) def _add_check_command(self): @@ -307,7 +317,9 @@ class OeDeployParser: action = args.action project = args.project debug = args.debug - return RunCmd(action, project, debug).run() + time = args.time + local = args.local + return RunCmd(action, project, debug, time, local).run() @staticmethod def _run_check_command(args): diff --git a/oedp/src/utils/command/command_executor.py b/oedp/src/utils/command/command_executor.py index 421ffbd2e1121ce2a0e7a447af538a6e214bd35f..e85bc24fd168bb550137bac21b9736241a80a933 100644 --- a/oedp/src/utils/command/command_executor.py +++ b/oedp/src/utils/command/command_executor.py @@ -23,19 +23,29 @@ class CommandExecutor: """ @staticmethod - def run_single_cmd(cmd, timeout=600, raise_exception=False, show_error=True, encoding=None, print_on_console=False): - if encoding is None: - encoding = sys.getdefaultencoding() + def run_single_cmd(cmd, **kwargs): + timeout = kwargs.get('timeout', 600) + raise_exception = kwargs.get('raise_exception', False) + show_error = kwargs.get('show_error', True) + encoding = kwargs.get('encoding', None) + print_on_console = kwargs.get('print_on_console', False) + env = kwargs.get('env', None) + + if env is not None and not isinstance(env, dict): + raise ValueError("env parameter must be a dictionary or None") + process_env = os.environ.copy() + if env: + process_env.update(env) try: pipe = subprocess.Popen( cmd, universal_newlines=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, start_new_session=True, - encoding=encoding + encoding=encoding, env=process_env ) except (ValueError, OSError) as error_except: if raise_exception: raise error_except - return "", "", pipe.returncode + return "", str(error_except), 1 # 返回默认错误码,避免访问未赋值的pipe变量 try: stdout, stderr = CommandExecutor._get_stdout_stderr(pipe, timeout, print_on_console) @@ -58,9 +68,14 @@ class CommandExecutor: @staticmethod def run_mult_cmd(cmds, timeout=600, raise_exception=False, show_error=True, encoding=None, print_on_console=False, - file_descriptor=None): + file_descriptor=None, env=None): if encoding is None: encoding = sys.getdefaultencoding() + if env is not None and not isinstance(env, dict): + raise ValueError("env parameter must be a dictionary or None") + process_env = os.environ.copy() + if env: + process_env.update(env) pipes = [] for cmd in cmds: @@ -69,7 +84,7 @@ class CommandExecutor: try: pipe = subprocess.Popen( cmd, stdin=last_pipe_stdout, universal_newlines=True, stderr=subprocess.PIPE, - stdout=stdout_target, start_new_session=True, encoding=encoding + stdout=stdout_target, start_new_session=True, encoding=encoding, env=process_env ) except (ValueError, OSError) as error_except: if raise_exception: diff --git a/unittest/test_run_action.py b/unittest/test_run_action.py index 8bd2b78ed88f85eafb9dcdf5b31129316a95f4d0..d30d0622572d30791ce96eae794af6a172c1a3f4 100644 --- a/unittest/test_run_action.py +++ b/unittest/test_run_action.py @@ -12,10 +12,9 @@ # ====================================================================================================================== import unittest -from unittest.mock import patch, MagicMock +from unittest.mock import patch from src.commands.run.run_action import RunAction -from src.utils.command.command_executor import CommandExecutor -import os + class TestRunAction(unittest.TestCase): def setUp(self): @@ -34,7 +33,7 @@ class TestRunAction(unittest.TestCase): mock_exists.return_value = False tasks = [self.valid_task] - runner = RunAction(self.action_name, tasks, self.project_path, False) + runner = RunAction(self.action_name, tasks, self.project_path, debug=False) result = runner.run() self.assertFalse(result) @@ -45,7 +44,7 @@ class TestRunAction(unittest.TestCase): mock_cmd.return_value = ("", "Permission denied", 1) tasks = [self.valid_task] - runner = RunAction(self.action_name, tasks, self.project_path, False) + runner = RunAction(self.action_name, tasks, self.project_path, debug=False) result = runner.run() self.assertFalse(result) @@ -57,7 +56,7 @@ class TestRunAction(unittest.TestCase): mock_exists.return_value = True mock_cmd.return_value = ("", "", 0) # 模拟命令执行成功 disabled_task = {**self.valid_task, "disabled": True} - runner = RunAction(self.action_name, [disabled_task], self.project_path, False) + runner = RunAction(self.action_name, [disabled_task], self.project_path, debug=False) with self.assertLogs(level='INFO') as log: result = runner.run() @@ -74,7 +73,7 @@ class TestRunAction(unittest.TestCase): mock_cmd.return_value = ("", "", 0) disabled_task = {**self.valid_task, "disabled_in_debug": True} - runner = RunAction(self.action_name, [disabled_task], self.project_path, True) + runner = RunAction(self.action_name, [disabled_task], self.project_path, debug=True) with self.assertLogs(level='INFO') as log: result = runner.run() @@ -93,7 +92,7 @@ class TestRunAction(unittest.TestCase): self.valid_task, {**self.valid_task, "name": "task2", "playbook": "config.yml"} ] - runner = RunAction(self.action_name, tasks, self.project_path, False) + runner = RunAction(self.action_name, tasks, self.project_path, debug=False) result = runner.run() self.assertTrue(result) @@ -112,7 +111,7 @@ class TestRunAction(unittest.TestCase): "vars": "custom_vars.yml", "scope": "web_servers" } - runner = RunAction(self.action_name, [task], self.project_path, False) + runner = RunAction(self.action_name, [task], self.project_path, debug=False) result = runner.run() self.assertTrue(result) @@ -129,7 +128,7 @@ class TestRunAction(unittest.TestCase): mock_cmd.return_value = ("", "", 0) # 非字典格式的任务 - runner = RunAction(self.action_name, ["invalid_task"], self.project_path, False) + runner = RunAction(self.action_name, ["invalid_task"], self.project_path, debug=False) result = runner.run() self.assertFalse(result) @@ -146,7 +145,7 @@ class TestRunAction(unittest.TestCase): "playbook": "install.yml", "vars": "missing_vars.yml" } - runner = RunAction(self.action_name, [task], self.project_path, False) + runner = RunAction(self.action_name, [task], self.project_path, debug=False) result = runner.run() self.assertFalse(result) @@ -158,7 +157,7 @@ class TestRunAction(unittest.TestCase): mock_exists.return_value = True mock_cmd.side_effect = Exception("Command failed") - runner = RunAction(self.action_name, [self.valid_task], self.project_path, False) + runner = RunAction(self.action_name, [self.valid_task], self.project_path, debug=False) result = runner.run() self.assertFalse(result) @@ -170,7 +169,7 @@ class TestRunAction(unittest.TestCase): mock_exists.return_value = True mock_cmd.return_value = ("", "", 0) - runner = RunAction(self.action_name, [self.valid_task], self.project_path, False) + runner = RunAction(self.action_name, [self.valid_task], self.project_path, debug=False) with self.assertLogs(level='INFO') as log: result = runner.run() @@ -192,7 +191,7 @@ class TestRunAction(unittest.TestCase): {"name": "all_scope", "playbook": "install.yml", "scope": "all"}, {"name": "custom_scope", "playbook": "install.yml", "scope": "web_servers"} ] - runner = RunAction(self.action_name, tasks, self.project_path, False) + runner = RunAction(self.action_name, tasks, self.project_path, debug=False) result = runner.run() self.assertTrue(result) @@ -205,7 +204,7 @@ class TestRunAction(unittest.TestCase): mock_exists.return_value = True mock_cmd.return_value = ("", "", 0) - runner = RunAction(self.action_name, [], self.project_path, False) + runner = RunAction(self.action_name, [], self.project_path, debug=False) result = runner.run() self.assertTrue(result) @@ -219,7 +218,7 @@ class TestRunAction(unittest.TestCase): mock_cmd.return_value = ("", "", 0) task = {"playbook": "install.yml"} - runner = RunAction(self.action_name, [task], self.project_path, False) + runner = RunAction(self.action_name, [task], self.project_path, debug=False) with self.assertLogs(level='INFO') as log: result = runner.run() @@ -243,7 +242,7 @@ class TestRunAction(unittest.TestCase): {"name": "failed_task", "playbook": "failed.yml"}, {"name": "skipped_task", "playbook": "skipped.yml"} ] - runner = RunAction(self.action_name, tasks, self.project_path, False) + runner = RunAction(self.action_name, tasks, self.project_path, debug=False) result = runner.run() self.assertFalse(result) @@ -260,7 +259,7 @@ class TestRunAction(unittest.TestCase): {"name": "normal_task", "playbook": "normal.yml"}, {"name": "debug_disabled_task", "playbook": "debug.yml", "disabled_in_debug": True} ] - runner = RunAction(self.action_name, tasks, self.project_path, True) + runner = RunAction(self.action_name, tasks, self.project_path, debug=True) with self.assertLogs(level='INFO') as log: result = runner.run() @@ -282,7 +281,7 @@ class TestRunAction(unittest.TestCase): "vars": "vars1.yml", "scope": "nodes" } - runner = RunAction(self.action_name, [task], self.project_path, False) + runner = RunAction(self.action_name, [task], self.project_path, debug=False) result = runner.run() self.assertTrue(result)