jumpserver/apps/ops/ansible/runner.py

245 lines
7.6 KiB
Python
Raw Normal View History

2017-03-05 12:53:24 +00:00
# ~*~ coding: utf-8 ~*~
2017-03-05 15:30:14 +00:00
import os
2017-12-06 10:31:51 +00:00
from collections import namedtuple
2017-03-05 15:30:14 +00:00
from ansible.executor.task_queue_manager import TaskQueueManager
2017-12-06 10:31:51 +00:00
from ansible.vars.manager import VariableManager
2017-03-05 15:30:14 +00:00
from ansible.parsing.dataloader import DataLoader
from ansible.executor.playbook_executor import PlaybookExecutor
from ansible.playbook.play import Play
import ansible.constants as C
2018-03-30 14:03:43 +00:00
from ansible.utils.display import Display
2017-03-05 15:30:14 +00:00
2017-03-13 16:58:25 +00:00
from .callback import AdHocResultCallback, PlaybookResultCallBack, \
CommandResultCallback
2017-03-05 15:30:14 +00:00
from common.utils import get_logger
2017-12-06 10:31:51 +00:00
from .exceptions import AnsibleError
2017-03-05 15:30:14 +00:00
__all__ = ["AdHocRunner", "PlayBookRunner"]
2017-03-05 15:30:14 +00:00
C.HOST_KEY_CHECKING = False
logger = get_logger(__name__)
2018-03-30 14:03:43 +00:00
class CustomDisplay(Display):
def display(self, msg, color=None, stderr=False, screen_only=False, log_only=False):
pass
display = CustomDisplay()
2017-12-06 10:31:51 +00:00
Options = namedtuple('Options', [
'listtags', 'listtasks', 'listhosts', 'syntax', 'connection',
'module_path', 'forks', 'remote_user', 'private_key_file', 'timeout',
'ssh_common_args', 'ssh_extra_args', 'sftp_extra_args',
'scp_extra_args', 'become', 'become_method', 'become_user',
'verbosity', 'check', 'extra_vars', 'playbook_path', 'passwords',
2017-12-28 04:03:02 +00:00
'diff', 'gathering', 'remote_tmp',
2017-12-06 10:31:51 +00:00
])
def get_default_options():
options = Options(
listtags=False,
listtasks=False,
listhosts=False,
syntax=False,
timeout=60,
connection='ssh',
module_path='',
forks=10,
remote_user='root',
private_key_file=None,
ssh_common_args="",
ssh_extra_args="",
sftp_extra_args="",
scp_extra_args="",
become=None,
become_method=None,
become_user=None,
verbosity=None,
extra_vars=[],
check=False,
playbook_path='/etc/ansible/',
passwords=None,
diff=False,
2017-12-07 08:25:50 +00:00
gathering='implicit',
2017-12-28 04:03:02 +00:00
remote_tmp='/tmp/.ansible'
2017-12-06 10:31:51 +00:00
)
return options
2017-03-13 16:58:25 +00:00
# Jumpserver not use playbook
2017-12-06 10:31:51 +00:00
class PlayBookRunner:
2017-03-05 15:30:14 +00:00
"""
用于执行AnsiblePlaybook的接口.简化Playbook对象的使用.
"""
2017-12-06 10:31:51 +00:00
# Default results callback
results_callback_class = PlaybookResultCallBack
loader_class = DataLoader
variable_manager_class = VariableManager
options = get_default_options()
2017-12-10 16:29:25 +00:00
def __init__(self, inventory=None, options=None):
2017-12-06 10:31:51 +00:00
"""
:param options: Ansible options like ansible.cfg
2017-12-10 16:29:25 +00:00
:param inventory: Ansible inventory
2017-12-06 10:31:51 +00:00
"""
if options:
self.options = options
2017-03-05 15:30:14 +00:00
C.RETRY_FILES_ENABLED = False
2017-12-10 16:29:25 +00:00
self.inventory = inventory
2017-12-06 10:31:51 +00:00
self.loader = self.loader_class()
self.results_callback = self.results_callback_class()
self.playbook_path = options.playbook_path
self.variable_manager = self.variable_manager_class(
loader=self.loader, inventory=self.inventory
2017-03-05 15:30:14 +00:00
)
2017-12-06 10:31:51 +00:00
self.passwords = options.passwords
self.__check()
2017-03-05 15:30:14 +00:00
2017-12-06 10:31:51 +00:00
def __check(self):
if self.options.playbook_path is None or \
not os.path.exists(self.options.playbook_path):
raise AnsibleError(
"Not Found the playbook file: {}.".format(self.options.playbook_path)
)
if not self.inventory.list_hosts('all'):
raise AnsibleError('Inventory is empty')
2017-03-05 15:30:14 +00:00
2017-12-06 10:31:51 +00:00
def run(self):
executor = PlaybookExecutor(
2017-03-05 15:30:14 +00:00
playbooks=[self.playbook_path],
inventory=self.inventory,
variable_manager=self.variable_manager,
loader=self.loader,
options=self.options,
2017-12-06 10:31:51 +00:00
passwords=self.passwords
)
2017-03-05 15:30:14 +00:00
2017-12-06 10:31:51 +00:00
if executor._tqm:
executor._tqm._stdout_callback = self.results_callback
executor.run()
executor._tqm.cleanup()
return self.results_callback.output
2017-03-05 15:30:14 +00:00
2017-12-06 10:31:51 +00:00
class AdHocRunner:
2017-03-05 15:30:14 +00:00
"""
2017-12-06 10:31:51 +00:00
ADHoc Runner接口
2017-03-05 15:30:14 +00:00
"""
2017-03-13 16:58:25 +00:00
results_callback_class = AdHocResultCallback
2018-03-30 14:03:43 +00:00
results_callback = None
2017-12-06 10:31:51 +00:00
loader_class = DataLoader
variable_manager_class = VariableManager
default_options = get_default_options()
2017-03-13 16:58:25 +00:00
2017-12-10 16:29:25 +00:00
def __init__(self, inventory, options=None):
2018-03-30 14:03:43 +00:00
self.options = self.update_options(options)
2017-12-10 16:29:25 +00:00
self.inventory = inventory
2017-03-05 15:30:14 +00:00
self.loader = DataLoader()
2017-12-10 16:29:25 +00:00
self.variable_manager = VariableManager(
loader=self.loader, inventory=self.inventory
)
2017-03-06 15:34:54 +00:00
2018-03-30 14:03:43 +00:00
def get_result_callback(self, file_obj=None):
return self.__class__.results_callback_class(file_obj=file_obj)
2017-03-06 15:34:54 +00:00
@staticmethod
def check_module_args(module_name, module_args=''):
if module_name in C.MODULE_REQUIRE_ARGS and not module_args:
err = "No argument passed to '%s' module." % module_name
2017-12-06 10:31:51 +00:00
raise AnsibleError(err)
def check_pattern(self, pattern):
2017-12-10 16:29:25 +00:00
if not pattern:
raise AnsibleError("Pattern `{}` is not valid!".format(pattern))
2017-12-06 10:31:51 +00:00
if not self.inventory.list_hosts("all"):
raise AnsibleError("Inventory is empty.")
if not self.inventory.list_hosts(pattern):
raise AnsibleError(
"pattern: %s dose not match any hosts." % pattern
)
2017-03-06 15:34:54 +00:00
2017-12-10 16:29:25 +00:00
def clean_tasks(self, tasks):
cleaned_tasks = []
for task in tasks:
self.check_module_args(task['action']['module'], task['action'].get('args'))
cleaned_tasks.append(task)
return cleaned_tasks
2018-03-30 14:03:43 +00:00
def update_options(self, options):
if options and isinstance(options, dict):
options = self.__class__.default_options._replace(**options)
else:
options = self.__class__.default_options
return options
2017-12-07 05:01:33 +00:00
2018-03-30 14:03:43 +00:00
def run(self, tasks, pattern, play_name='Ansible Ad-hoc', gather_facts='no', file_obj=None):
2017-03-06 15:34:54 +00:00
"""
2017-12-06 10:31:51 +00:00
:param tasks: [{'action': {'module': 'shell', 'args': 'ls'}, ...}, ]
:param pattern: all, *, or others
:param play_name: The play name
2018-03-30 14:03:43 +00:00
:param gather_facts:
:param file_obj: logging to file_obj
2017-03-06 15:34:54 +00:00
:return:
"""
2017-12-10 16:29:25 +00:00
self.check_pattern(pattern)
2018-03-30 14:03:43 +00:00
self.results_callback = self.get_result_callback(file_obj)
2017-12-10 16:29:25 +00:00
cleaned_tasks = self.clean_tasks(tasks)
2017-12-06 10:31:51 +00:00
play_source = dict(
name=play_name,
2017-03-06 15:34:54 +00:00
hosts=pattern,
2017-12-06 10:31:51 +00:00
gather_facts=gather_facts,
2017-12-10 16:29:25 +00:00
tasks=cleaned_tasks
2017-03-05 15:30:14 +00:00
)
2017-12-06 10:31:51 +00:00
play = Play().load(
play_source,
2017-03-05 15:30:14 +00:00
variable_manager=self.variable_manager,
loader=self.loader,
)
2017-12-06 10:31:51 +00:00
tqm = TaskQueueManager(
2017-03-05 15:30:14 +00:00
inventory=self.inventory,
variable_manager=self.variable_manager,
loader=self.loader,
options=self.options,
2018-03-30 14:03:43 +00:00
stdout_callback=self.results_callback,
2017-12-06 10:31:51 +00:00
passwords=self.options.passwords,
2017-03-05 15:30:14 +00:00
)
2018-03-30 14:03:43 +00:00
print("Get matched hosts: {}".format(
2017-12-10 16:29:25 +00:00
self.inventory.get_matched_hosts(pattern)
))
2017-03-05 15:30:14 +00:00
try:
2017-12-06 10:31:51 +00:00
tqm.run(play)
2018-03-30 14:03:43 +00:00
return self.results_callback
2017-03-05 15:30:14 +00:00
except Exception as e:
2017-12-06 10:31:51 +00:00
raise AnsibleError(e)
2017-03-05 15:30:14 +00:00
finally:
2017-12-06 10:31:51 +00:00
tqm.cleanup()
self.loader.cleanup_all_tmp_files()
2017-03-05 15:30:14 +00:00
2017-12-06 10:31:51 +00:00
class CommandRunner(AdHocRunner):
results_callback_class = CommandResultCallback
modules_choices = ('shell', 'raw', 'command', 'script')
def execute(self, cmd, pattern, module=None):
if module and module not in self.modules_choices:
raise AnsibleError("Module should in {}".format(self.modules_choices))
else:
module = "shell"
tasks = [
{"action": {"module": module, "args": cmd}}
]
hosts = self.inventory.get_hosts(pattern=pattern)
name = "Run command {} on {}".format(cmd, ", ".join([host.name for host in hosts]))
return self.run(tasks, pattern, play_name=name)
2017-03-05 15:30:14 +00:00