From 574639d5e18fa4922a6389795f85bd1c6c9cb147 Mon Sep 17 00:00:00 2001 From: Aaron3S Date: Thu, 11 Apr 2024 17:37:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=20ansible=20receptor?= =?UTF-8?q?=20private=20=E6=96=B9=E5=BC=8F=E8=AE=A4=E8=AF=81,=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E8=BF=90=E8=A1=8C=E5=AE=8C=E6=88=90=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E7=A9=BA=E9=97=B4=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/accounts/models/base.py | 13 ++++--- apps/assets/models/gateway.py | 4 +++ apps/ops/ansible/cleaner.py | 36 ++++++++++++++++++++ apps/ops/ansible/inventory.py | 32 ++++++++--------- apps/ops/ansible/receptor/receptor_runner.py | 10 +++++- 5 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 apps/ops/ansible/cleaner.py diff --git a/apps/accounts/models/base.py b/apps/accounts/models/base.py index 87cc14e2b..4b55752d6 100644 --- a/apps/accounts/models/base.py +++ b/apps/accounts/models/base.py @@ -137,16 +137,13 @@ class BaseAccount(VaultModelMixin, JMSOrgBaseModel): else: return None - @property - def private_key_path(self): + def get_private_key_path(self, path): if self.secret_type != SecretType.SSH_KEY \ or not self.secret \ or not self.private_key: return None - project_dir = settings.PROJECT_DIR - tmp_dir = os.path.join(project_dir, 'tmp') key_name = '.' + md5(self.private_key.encode('utf-8')).hexdigest() - key_path = os.path.join(tmp_dir, key_name) + key_path = os.path.join(path, key_name) if not os.path.exists(key_path): # https://github.com/ansible/ansible-runner/issues/544 # ssh requires OpenSSH format keys to have a full ending newline. @@ -158,6 +155,12 @@ class BaseAccount(VaultModelMixin, JMSOrgBaseModel): os.chmod(key_path, 0o400) return key_path + @property + def private_key_path(self): + project_dir = settings.PROJECT_DIR + tmp_dir = os.path.join(project_dir, 'tmp') + return self.get_private_key_path(tmp_dir) + def get_private_key(self): if not self.private_key: return None diff --git a/apps/assets/models/gateway.py b/apps/assets/models/gateway.py index c474da26d..36b2a5a72 100644 --- a/apps/assets/models/gateway.py +++ b/apps/assets/models/gateway.py @@ -73,3 +73,7 @@ class Gateway(Host): def private_key_path(self): account = self.select_account return account.private_key_path if account else None + + def get_private_key_path(self, path): + account = self.select_account + return account.get_private_key_path(path) if account else None diff --git a/apps/ops/ansible/cleaner.py b/apps/ops/ansible/cleaner.py new file mode 100644 index 000000000..5a2f29d2f --- /dev/null +++ b/apps/ops/ansible/cleaner.py @@ -0,0 +1,36 @@ +import os +import shutil +from functools import wraps + +from settings.api import settings + + +def cleanup_post_run(func): + def get_instance(*args): + if not len(args) > 0: + return + return args[0] + + @wraps(func) + def wrapper(*args, **kwargs): + instance = get_instance(*args) + if not instance or not issubclass(type(instance), WorkPostRunCleaner): + raise NotImplementedError("you should extend 'WorkPostRunCleaner'") + try: + return func(*args, **kwargs) + finally: + instance.clean_post_run() + + return wrapper + + +class WorkPostRunCleaner: + @property + def clean_dir(self): + raise NotImplemented + + def clean_post_run(self): + if settings.DEBUG_DEV: + return + if self.clean_dir and os.path.exists(self.clean_dir): + shutil.rmtree(self.clean_dir) diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 09853ad24..804f1ba18 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -45,7 +45,7 @@ class JMSInventory: return groups @staticmethod - def make_proxy_command(gateway): + def make_proxy_command(gateway, path_dir): proxy_command_list = [ "ssh", "-o", "Port={}".format(gateway.port), "-o", "StrictHostKeyChecking=no", @@ -58,7 +58,7 @@ class JMSInventory: 0, "sshpass -p {}".format(gateway.password) ) if gateway.private_key: - proxy_command_list.append("-i {}".format(gateway.private_key_path)) + proxy_command_list.append("-i {}".format(gateway.get_private_key_path(path_dir))) proxy_command = "-o ProxyCommand='{}'".format( " ".join(proxy_command_list) @@ -66,7 +66,7 @@ class JMSInventory: return {"ansible_ssh_common_args": proxy_command} @staticmethod - def make_account_ansible_vars(account): + def make_account_ansible_vars(account, path_dir): var = { 'ansible_user': account.username, } @@ -76,18 +76,18 @@ class JMSInventory: if account.secret_type == 'password': var['ansible_password'] = account.escape_jinja2_syntax(account.secret) elif account.secret_type == 'ssh_key': - var['ansible_ssh_private_key_file'] = account.private_key_path + var['ansible_ssh_private_key_file'] = account.get_private_key_path(path_dir) return var @staticmethod - def make_custom_become_ansible_vars(account, su_from_auth): + def make_custom_become_ansible_vars(account, su_from_auth, path_dir): su_method = su_from_auth['ansible_become_method'] var = { 'custom_become': True, 'custom_become_method': su_method, 'custom_become_user': account.su_from.username, 'custom_become_password': account.escape_jinja2_syntax(account.su_from.secret), - 'custom_become_private_key_path': account.su_from.private_key_path + 'custom_become_private_key_path': account.su_from.get_private_key_path(path_dir) } return var @@ -100,7 +100,7 @@ class JMSInventory: setting = getattr(p, 'setting') host['old_ssh_version'] = setting.get('old_ssh_version', False) - def make_account_vars(self, host, asset, account, automation, protocol, platform, gateway): + def make_account_vars(self, host, asset, account, automation, protocol, platform, gateway, path_dir): from accounts.const import AutomationTypes if not account: host['error'] = _("No account available") @@ -114,15 +114,15 @@ class JMSInventory: if platform.su_enabled and su_from: su_from_auth = account.get_ansible_become_auth() host.update(su_from_auth) - host.update(self.make_custom_become_ansible_vars(account, su_from_auth)) + host.update(self.make_custom_become_ansible_vars(account, su_from_auth, path_dir)) elif platform.su_enabled and not su_from and \ self.task_type in (AutomationTypes.change_secret, AutomationTypes.push_account): - host.update(self.make_account_ansible_vars(account)) + host.update(self.make_account_ansible_vars(account, path_dir)) host['ansible_become'] = True host['ansible_become_user'] = 'root' host['ansible_become_password'] = account.escape_jinja2_syntax(account.secret) else: - host.update(self.make_account_ansible_vars(account)) + host.update(self.make_account_ansible_vars(account, path_dir)) if platform.name == 'Huawei': host['ansible_connection'] = 'network_cli' @@ -134,11 +134,11 @@ class JMSInventory: host['gateway'] = { 'address': gateway.address, 'port': gateway.port, 'username': gateway.username, 'secret': gateway.password, - 'private_key_path': gateway.private_key_path + 'private_key_path': gateway.get_private_key_path(path_dir) } host['jms_asset']['port'] = port else: - ansible_ssh_common_args = self.make_proxy_command(gateway) + ansible_ssh_common_args = self.make_proxy_command(gateway, path_dir) host['jms_asset'].update(ansible_ssh_common_args) host.update(ansible_ssh_common_args) @@ -168,7 +168,7 @@ class JMSInventory: ansible_config['ansible_winrm_connection_timeout'] = 120 return ansible_config - def asset_to_host(self, asset, account, automation, protocols, platform): + def asset_to_host(self, asset, account, automation, protocols, platform, path_dir): try: ansible_config = dict(automation.ansible_config) except (AttributeError, TypeError): @@ -191,7 +191,7 @@ class JMSInventory: 'jms_account': { 'id': str(account.id), 'username': account.username, 'secret': account.escape_jinja2_syntax(account.secret), - 'secret_type': account.secret_type, 'private_key_path': account.private_key_path + 'secret_type': account.secret_type, 'private_key_path': account.get_private_key_path(path_dir) } if account else None } @@ -210,7 +210,7 @@ class JMSInventory: gateway = asset.domain.select_gateway() self.make_account_vars( - host, asset, account, automation, protocol, platform, gateway + host, asset, account, automation, protocol, platform, gateway, path_dir ) return host @@ -274,7 +274,7 @@ class JMSInventory: for asset in assets: protocols = self.set_platform_protocol_setting_to_asset(asset, platform_protocols) account = self.select_account(asset) - host = self.asset_to_host(asset, account, automation, protocols, platform) + host = self.asset_to_host(asset, account, automation, protocols, platform, path_dir) if not automation.ansible_enabled: host['error'] = _('Ansible disabled') diff --git a/apps/ops/ansible/receptor/receptor_runner.py b/apps/ops/ansible/receptor/receptor_runner.py index f15b75bd9..4e1b1c410 100644 --- a/apps/ops/ansible/receptor/receptor_runner.py +++ b/apps/ops/ansible/receptor/receptor_runner.py @@ -2,11 +2,14 @@ import concurrent.futures import os import queue import socket + from django.conf import settings import ansible_runner from django.utils.functional import LazyObject from receptorctl import ReceptorControl +from ops.ansible.cleaner import WorkPostRunCleaner, cleanup_post_run + class WarpedReceptorctl(LazyObject): def _setup(self): @@ -33,7 +36,7 @@ def run(**kwargs): return receptor_runner.run() -class AnsibleReceptorRunner: +class AnsibleReceptorRunner(WorkPostRunCleaner): def __init__(self, **kwargs): self.runner_params = kwargs self.unit_id = None @@ -46,6 +49,11 @@ class AnsibleReceptorRunner: f.write(self.unit_id) f.flush() + @property + def clean_dir(self): + return self.runner_params.get("private_data_dir", None) + + @cleanup_post_run def run(self): input, output = socket.socketpair()