From 3d2fbd124e05308bfe4675a65021100e3e48a61f Mon Sep 17 00:00:00 2001 From: dingjiahuichina Date: Mon, 7 Jul 2025 14:28:02 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20oedp=E6=94=AF=E6=8C=81=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E9=83=A8=E7=BD=B2=E5=8F=82=E6=95=B0=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=89=93=E5=8D=B0=E6=AF=8F=E4=B8=AA=E6=AD=A5=E9=AA=A4?= =?UTF-8?q?=E7=9A=84=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/zh/Command.md | 14 ++++++++------ oedp/build/constants.sh | 2 +- oedp/build/oedp.spec | 6 +++++- oedp/build/static/oeDeploy.desktop | 2 +- oedp/src/commands/run/run_action.py | 18 ++++++++++++++++-- oedp/src/commands/run/run_cmd.py | 8 ++++++-- oedp/src/constants/const.py | 2 +- oedp/src/parsers/oedp_parser.py | 16 ++++++++++++++-- oedp/src/utils/command/command_executor.py | 20 +++++++++++++++----- 9 files changed, 67 insertions(+), 21 deletions(-) diff --git a/doc/zh/Command.md b/doc/zh/Command.md index f423569..a174819 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 33c924e..1ccd928 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 6742bf3..66169b1 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 02fd683..5b5cca7 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 356aa5d..9f87288 100644 --- a/oedp/src/commands/run/run_action.py +++ b/oedp/src/commands/run/run_action.py @@ -18,7 +18,7 @@ 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, debug: bool, time: bool = False, local: bool = False): """ 执行指定项目的指定方法。 @@ -26,11 +26,15 @@ class RunAction: :param action: 方法名称 :param tasks: 方法代码路径 :param debug: 是否启用调试模式 + :param time: 是否记录任务时间 + :param local: 是否部署到本地 """ self.action = action self.tasks = tasks self.project = project self.debug = debug + self.time = time + self.local = local self.log = LoggerGenerator().get_logger('run_action') def run(self) -> bool: @@ -60,6 +64,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 +72,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 +82,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 46b6640..2fb648a 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, 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,7 @@ 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, self.debug, self.time, 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 717222b..48da440 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 36fbf7e..667a8e3 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 421ffbd..f92fac4 100644 --- a/oedp/src/utils/command/command_executor.py +++ b/oedp/src/utils/command/command_executor.py @@ -23,19 +23,24 @@ class CommandExecutor: """ @staticmethod - def run_single_cmd(cmd, timeout=600, raise_exception=False, show_error=True, encoding=None, print_on_console=False): + def run_single_cmd(cmd, timeout=600, raise_exception=False, show_error=True, encoding=None, print_on_console=False, 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) 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 +63,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 +79,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: -- Gitee From 77e06ef4945520143e3ff005b09149c268f0995c Mon Sep 17 00:00:00 2001 From: dingjiahuichina Date: Fri, 18 Jul 2025 15:08:25 +0800 Subject: [PATCH 2/2] =?UTF-8?q?opt:=20=E6=A3=80=E8=A7=86=E6=84=8F=E8=A7=81?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=EF=BC=8C=E5=85=A5=E5=8F=82=E8=BE=83=E5=A4=9A?= =?UTF-8?q?=E7=9A=84=E5=87=BD=E6=95=B0=E6=94=B9=E7=94=A8=E5=AD=97=E5=85=B8?= =?UTF-8?q?=E4=BC=A0=E5=8F=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- oedp/src/commands/run/run_action.py | 20 ++++++------ oedp/src/commands/run/run_cmd.py | 9 ++++-- oedp/src/utils/command/command_executor.py | 11 +++++-- unittest/test_run_action.py | 37 +++++++++++----------- 4 files changed, 44 insertions(+), 33 deletions(-) diff --git a/oedp/src/commands/run/run_action.py b/oedp/src/commands/run/run_action.py index 9f87288..9637222 100644 --- a/oedp/src/commands/run/run_action.py +++ b/oedp/src/commands/run/run_action.py @@ -18,23 +18,25 @@ from src.utils.log.logger_generator import LoggerGenerator class RunAction: - def __init__(self, action: str, tasks: list, project: str, debug: bool, time: bool = False, local: bool = False): + def __init__(self, action: str, tasks: list, project: str, **kwargs): """ 执行指定项目的指定方法。 - :param project: 项目目录路径 :param action: 方法名称 - :param tasks: 方法代码路径 - :param debug: 是否启用调试模式 - :param time: 是否记录任务时间 - :param local: 是否部署到本地 + :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.time = time - self.local = local + 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: diff --git a/oedp/src/commands/run/run_cmd.py b/oedp/src/commands/run/run_cmd.py index 2fb648a..4adcc6e 100644 --- a/oedp/src/commands/run/run_cmd.py +++ b/oedp/src/commands/run/run_cmd.py @@ -21,7 +21,7 @@ from decimal import Decimal class RunCmd: - def __init__(self, action: str, project: str, debug: bool, time: bool = False, local: bool = False): + def __init__(self, action: str, project: str, debug: bool = False, time: bool = False, local: bool = False): """ 执行一个项目中的方法。 @@ -58,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, self.time, self.local).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/utils/command/command_executor.py b/oedp/src/utils/command/command_executor.py index f92fac4..e85bc24 100644 --- a/oedp/src/utils/command/command_executor.py +++ b/oedp/src/utils/command/command_executor.py @@ -23,9 +23,14 @@ class CommandExecutor: """ @staticmethod - def run_single_cmd(cmd, timeout=600, raise_exception=False, show_error=True, encoding=None, print_on_console=False, env=None): - 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() diff --git a/unittest/test_run_action.py b/unittest/test_run_action.py index 8bd2b78..d30d062 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) -- Gitee