1
This commit is contained in:
parent
2431ecea20
commit
8e0fb41973
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
// 使用 IntelliSense 了解相关属性。
|
||||||
|
// 悬停以查看现有属性的描述。
|
||||||
|
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Python: 当前文件",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${file}",
|
||||||
|
"console": "integratedTerminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
0
scripts/common/ansible/__init__.py
Normal file
0
scripts/common/ansible/__init__.py
Normal file
194
scripts/common/ansible/ansible2.9.5.py
Normal file
194
scripts/common/ansible/ansible2.9.5.py
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
from ansible.module_utils.common.collections import ImmutableDict
|
||||||
|
from ansible.parsing.dataloader import DataLoader
|
||||||
|
from ansible.vars.manager import VariableManager
|
||||||
|
from ansible.inventory.manager import InventoryManager
|
||||||
|
from ansible.playbook.play import Play
|
||||||
|
from ansible.executor.task_queue_manager import TaskQueueManager
|
||||||
|
from ansible.plugins.callback import CallbackBase
|
||||||
|
from ansible import context
|
||||||
|
import ansible.constants as C
|
||||||
|
|
||||||
|
|
||||||
|
class ResultCallback(CallbackBase):
|
||||||
|
"""
|
||||||
|
重写callbackBase类的部分方法
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.host_ok = {}
|
||||||
|
self.host_unreachable = {}
|
||||||
|
self.host_failed = {}
|
||||||
|
self.task_ok = {}
|
||||||
|
|
||||||
|
def v2_runner_on_unreachable(self, result):
|
||||||
|
self.host_unreachable[result._host.get_name()] = result
|
||||||
|
|
||||||
|
def v2_runner_on_ok(self, result, **kwargs):
|
||||||
|
self.host_ok[result._host.get_name()] = result
|
||||||
|
|
||||||
|
def v2_runner_on_failed(self, result, **kwargs):
|
||||||
|
self.host_failed[result._host.get_name()] = result
|
||||||
|
|
||||||
|
|
||||||
|
class MyAnsiable2():
|
||||||
|
def __init__(self,
|
||||||
|
connection='local', # 连接方式 local 本地方式,smart ssh方式
|
||||||
|
remote_user=None, # ssh 用户
|
||||||
|
remote_password=None, # ssh 用户的密码,应该是一个字典, key 必须是 conn_pass
|
||||||
|
private_key_file=None, # 指定自定义的私钥地址
|
||||||
|
sudo=None, sudo_user=None, ask_sudo_pass=None,
|
||||||
|
module_path=None, # 模块路径,可以指定一个自定义模块的路径
|
||||||
|
become=None, # 是否提权
|
||||||
|
become_method=None, # 提权方式 默认 sudo 可以是 su
|
||||||
|
become_user=None, # 提权后,要成为的用户,并非登录用户
|
||||||
|
check=False, diff=False,
|
||||||
|
listhosts=None, listtasks=None, listtags=None,
|
||||||
|
verbosity=3,
|
||||||
|
syntax=None,
|
||||||
|
start_at_task=None,
|
||||||
|
inventory=None):
|
||||||
|
|
||||||
|
# 函数文档注释
|
||||||
|
"""
|
||||||
|
初始化函数,定义的默认的选项值,
|
||||||
|
在初始化的时候可以传参,以便覆盖默认选项的值
|
||||||
|
"""
|
||||||
|
context.CLIARGS = ImmutableDict(
|
||||||
|
connection=connection,
|
||||||
|
remote_user=remote_user,
|
||||||
|
private_key_file=private_key_file,
|
||||||
|
sudo=sudo,
|
||||||
|
sudo_user=sudo_user,
|
||||||
|
ask_sudo_pass=ask_sudo_pass,
|
||||||
|
module_path=module_path,
|
||||||
|
become=become,
|
||||||
|
become_method=become_method,
|
||||||
|
become_user=become_user,
|
||||||
|
verbosity=verbosity,
|
||||||
|
listhosts=listhosts,
|
||||||
|
listtasks=listtasks,
|
||||||
|
listtags=listtags,
|
||||||
|
syntax=syntax,
|
||||||
|
start_at_task=start_at_task,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 三元表达式,假如没有传递 inventory, 就使用 "localhost,"
|
||||||
|
# 指定 inventory 文件
|
||||||
|
# inventory 的值可以是一个 资产清单文件
|
||||||
|
# 也可以是一个包含主机的元组,这个仅仅适用于测试
|
||||||
|
# 比如 : 1.1.1.1, # 如果只有一个 IP 最后必须有英文的逗号
|
||||||
|
# 或者: 1.1.1.1, 2.2.2.2
|
||||||
|
|
||||||
|
self.inventory = inventory if inventory else "localhost,"
|
||||||
|
|
||||||
|
# 实例化数据解析器
|
||||||
|
self.loader = DataLoader()
|
||||||
|
|
||||||
|
# 实例化 资产配置对象
|
||||||
|
self.inv_obj = InventoryManager(
|
||||||
|
loader=self.loader, sources=self.inventory)
|
||||||
|
|
||||||
|
# 设置密码
|
||||||
|
self.passwords = remote_password
|
||||||
|
|
||||||
|
# 实例化回调插件对象
|
||||||
|
self.results_callback = ResultCallback()
|
||||||
|
|
||||||
|
# 变量管理器
|
||||||
|
self.variable_manager = VariableManager(self.loader, self.inv_obj)
|
||||||
|
|
||||||
|
def run(self, hosts='localhost', gether_facts="no", module="ping", args='', task_time=0):
|
||||||
|
"""
|
||||||
|
参数说明:
|
||||||
|
task_time -- 执行异步任务时等待的秒数,这个需要大于 0 ,等于 0 的时候不支持异步(默认值)。这个值应该等于执行任务实际耗时时间为好
|
||||||
|
"""
|
||||||
|
play_source = dict(
|
||||||
|
name="Ad-hoc",
|
||||||
|
hosts=hosts,
|
||||||
|
gather_facts=gether_facts,
|
||||||
|
tasks=[
|
||||||
|
# 这里每个 task 就是这个列表中的一个元素,格式是嵌套的字典
|
||||||
|
# 也可以作为参数传递过来,这里就简单化了。
|
||||||
|
{"action": {"module": module, "args": args}, "async": task_time, "poll": 0}])
|
||||||
|
|
||||||
|
play = Play().load(play_source, variable_manager=self.variable_manager, loader=self.loader)
|
||||||
|
|
||||||
|
tqm = None
|
||||||
|
try:
|
||||||
|
tqm = TaskQueueManager(
|
||||||
|
inventory=self.inv_obj,
|
||||||
|
variable_manager=self.variable_manager,
|
||||||
|
loader=self.loader,
|
||||||
|
passwords=self.passwords,
|
||||||
|
stdout_callback=self.results_callback)
|
||||||
|
|
||||||
|
result = tqm.run(play)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
if tqm is not None:
|
||||||
|
tqm.cleanup()
|
||||||
|
shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)
|
||||||
|
|
||||||
|
def playbook(self, playbooks, run_data):
|
||||||
|
"""
|
||||||
|
Keyword arguments:
|
||||||
|
playbooks -- 需要是一个列表类型
|
||||||
|
"""
|
||||||
|
from ansible.executor.playbook_executor import PlaybookExecutor
|
||||||
|
self.variable_manager.extra_vars = run_data
|
||||||
|
playbook = PlaybookExecutor(playbooks=playbooks,
|
||||||
|
inventory=self.inv_obj,
|
||||||
|
variable_manager=self.variable_manager,
|
||||||
|
loader=self.loader,
|
||||||
|
passwords=self.passwords)
|
||||||
|
|
||||||
|
# 使用回调函数
|
||||||
|
playbook._tqm._stdout_callback = self.results_callback
|
||||||
|
|
||||||
|
result = playbook.run()
|
||||||
|
|
||||||
|
def get_result(self):
|
||||||
|
result_raw = {'success': {}, 'failed': {}, 'unreachable': {}}
|
||||||
|
|
||||||
|
# print(self.results_callback.host_ok)
|
||||||
|
for host, result in self.results_callback.host_ok.items():
|
||||||
|
result_raw['success'][host] = result._result
|
||||||
|
for host, result in self.results_callback.host_failed.items():
|
||||||
|
result_raw['failed'][host] = result._result
|
||||||
|
for host, result in self.results_callback.host_unreachable.items():
|
||||||
|
result_raw['unreachable'][host] = result._result
|
||||||
|
|
||||||
|
# 最终打印结果,并且使用 JSON 继续格式化
|
||||||
|
print(json.dumps(result_raw, indent=4))
|
||||||
|
|
||||||
|
return json.dumps(result_raw)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_cmd():
|
||||||
|
hosts = "192.168.100.42"
|
||||||
|
ansible3 = MyAnsiable2(
|
||||||
|
inventory='hosts', connection='smart') # 创建资源库对象
|
||||||
|
ansible3.run(hosts=hosts, module="shell",
|
||||||
|
args=' ps -ef |grep python')
|
||||||
|
stdout_dict = json.loads(ansible3.get_result())
|
||||||
|
print(stdout_dict, type(stdout_dict))
|
||||||
|
print(stdout_dict['success'][hosts]['stdout'], '######wc')
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_playbook():
|
||||||
|
ansible3 = MyAnsiable2(
|
||||||
|
inventory='hosts', connection='smart')
|
||||||
|
run_data = {
|
||||||
|
"dest_paths": "/data/apps/temp",
|
||||||
|
"source": "/root/temp/host",
|
||||||
|
"dest_filename": "/data/apps/temp/host.new"
|
||||||
|
}
|
||||||
|
ansible3.playbook(playbooks=['test.yml'])
|
||||||
|
stdout_dict = json.loads(ansible3.get_result())
|
||||||
|
print(stdout_dict, type(stdout_dict))
|
||||||
|
# print(stdout_dict['success']['192.168.0.94']['stdout'])
|
137
scripts/common/ansible/ansible4.py
Normal file
137
scripts/common/ansible/ansible4.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# pip3 install ansible
|
||||||
|
# pip3 install ansible_runner
|
||||||
|
# pip3 install ansible_inventory
|
||||||
|
# pip3 install ansible_playbook
|
||||||
|
# Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
import ansible.constants as C
|
||||||
|
from ansible.executor.task_queue_manager import TaskQueueManager
|
||||||
|
from ansible.module_utils.common.collections import ImmutableDict
|
||||||
|
from ansible.inventory.manager import InventoryManager
|
||||||
|
from ansible.parsing.dataloader import DataLoader
|
||||||
|
from ansible.playbook.play import Play
|
||||||
|
from ansible.plugins.callback import CallbackBase
|
||||||
|
from ansible.vars.manager import VariableManager
|
||||||
|
from ansible import context
|
||||||
|
|
||||||
|
|
||||||
|
# Create a callback plugin so we can capture the output
|
||||||
|
class ResultsCollectorJSONCallback(CallbackBase):
|
||||||
|
"""A sample callback plugin used for performing an action as results come in.
|
||||||
|
|
||||||
|
If you want to collect all results into a single object for processing at
|
||||||
|
the end of the execution, look into utilizing the ``json`` callback plugin
|
||||||
|
or writing your own custom callback plugin.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(ResultsCollectorJSONCallback, self).__init__(*args, **kwargs)
|
||||||
|
self.host_ok = {}
|
||||||
|
self.host_unreachable = {}
|
||||||
|
self.host_failed = {}
|
||||||
|
|
||||||
|
def v2_runner_on_unreachable(self, result):
|
||||||
|
host = result._host
|
||||||
|
self.host_unreachable[host.get_name()] = result
|
||||||
|
|
||||||
|
def v2_runner_on_ok(self, result, *args, **kwargs):
|
||||||
|
"""Print a json representation of the result.
|
||||||
|
|
||||||
|
Also, store the result in an instance attribute for retrieval later
|
||||||
|
"""
|
||||||
|
host = result._host
|
||||||
|
self.host_ok[host.get_name()] = result
|
||||||
|
print(json.dumps({host.name: result._result}, indent=4))
|
||||||
|
|
||||||
|
def v2_runner_on_failed(self, result, *args, **kwargs):
|
||||||
|
host = result._host
|
||||||
|
self.host_failed[host.get_name()] = result
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
host_list = ['localhost', 'www.example.com', 'www.google.com']
|
||||||
|
# since the API is constructed for CLI it expects certain options to always be set in the context object
|
||||||
|
context.CLIARGS = ImmutableDict(connection='smart', module_path=['/to/mymodules', '/usr/share/ansible'], forks=10, become=None,
|
||||||
|
become_method=None, become_user=None, check=False, diff=False)
|
||||||
|
# required for
|
||||||
|
# https://github.com/ansible/ansible/blob/devel/lib/ansible/inventory/manager.py#L204
|
||||||
|
sources = ','.join(host_list)
|
||||||
|
if len(host_list) == 1:
|
||||||
|
sources += ','
|
||||||
|
|
||||||
|
# initialize needed objects
|
||||||
|
loader = DataLoader() # Takes care of finding and reading yaml, json and ini files
|
||||||
|
passwords = dict(vault_pass='secret')
|
||||||
|
|
||||||
|
# Instantiate our ResultsCollectorJSONCallback for handling results as they come in. Ansible expects this to be one of its main display outlets
|
||||||
|
results_callback = ResultsCollectorJSONCallback()
|
||||||
|
|
||||||
|
# create inventory, use path to host config file as source or hosts in a comma separated string
|
||||||
|
inventory = InventoryManager(loader=loader, sources=sources)
|
||||||
|
|
||||||
|
# variable manager takes care of merging all the different sources to give you a unified view of variables available in each context
|
||||||
|
variable_manager = VariableManager(loader=loader, inventory=inventory)
|
||||||
|
|
||||||
|
# instantiate task queue manager, which takes care of forking and setting up all objects to iterate over host list and tasks
|
||||||
|
# IMPORTANT: This also adds library dirs paths to the module loader
|
||||||
|
# IMPORTANT: and so it must be initialized before calling `Play.load()`.
|
||||||
|
tqm = TaskQueueManager(
|
||||||
|
inventory=inventory,
|
||||||
|
variable_manager=variable_manager,
|
||||||
|
loader=loader,
|
||||||
|
passwords=passwords,
|
||||||
|
# Use our custom callback instead of the ``default`` callback plugin, which prints to stdout
|
||||||
|
stdout_callback=results_callback,
|
||||||
|
)
|
||||||
|
|
||||||
|
# create data structure that represents our play, including tasks, this is basically what our YAML loader does internally.
|
||||||
|
play_source = dict(
|
||||||
|
name="Ansible Play",
|
||||||
|
hosts=host_list,
|
||||||
|
gather_facts='no',
|
||||||
|
tasks=[
|
||||||
|
dict(action=dict(module='shell', args='ls'), register='shell_out'),
|
||||||
|
dict(action=dict(module='debug', args=dict(
|
||||||
|
msg='{{shell_out.stdout}}'))),
|
||||||
|
dict(action=dict(module='command', args=dict(cmd='/usr/bin/uptime'))),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create play object, playbook objects use .load instead of init or new methods,
|
||||||
|
# this will also automatically create the task objects from the info provided in play_source
|
||||||
|
play = Play().load(play_source, variable_manager=variable_manager, loader=loader)
|
||||||
|
|
||||||
|
# Actually run it
|
||||||
|
try:
|
||||||
|
# most interesting data for a play is actually sent to the callback's methods
|
||||||
|
result = tqm.run(play)
|
||||||
|
finally:
|
||||||
|
# we always need to cleanup child procs and the structures we use to communicate with them
|
||||||
|
tqm.cleanup()
|
||||||
|
if loader:
|
||||||
|
loader.cleanup_all_tmp_files()
|
||||||
|
|
||||||
|
# Remove ansible tmpdir
|
||||||
|
shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)
|
||||||
|
|
||||||
|
print("UP ***********")
|
||||||
|
for host, result in results_callback.host_ok.items():
|
||||||
|
print('{0} >>> {1}'.format(host, result._result['stdout']))
|
||||||
|
|
||||||
|
print("FAILED *******")
|
||||||
|
for host, result in results_callback.host_failed.items():
|
||||||
|
print('{0} >>> {1}'.format(host, result._result['msg']))
|
||||||
|
|
||||||
|
print("DOWN *********")
|
||||||
|
for host, result in results_callback.host_unreachable.items():
|
||||||
|
print('{0} >>> {1}'.format(host, result._result['msg']))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
198
scripts/common/ansible/mylog.py
Normal file
198
scripts/common/ansible/mylog.py
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from ansible.plugins.callback import CallbackBase
|
||||||
|
|
||||||
|
# A custom object to store to the database
|
||||||
|
from some_project.storage import Logs
|
||||||
|
|
||||||
|
|
||||||
|
class PlayLogger:
|
||||||
|
"""Store log output in a single object.
|
||||||
|
We create a new object per Ansible run
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.log = ''
|
||||||
|
self.runtime = 0
|
||||||
|
|
||||||
|
def append(self, log_line):
|
||||||
|
"""append to log"""
|
||||||
|
self.log += log_line+"\n\n"
|
||||||
|
|
||||||
|
def banner(self, msg):
|
||||||
|
"""Output Trailing Stars"""
|
||||||
|
width = 78 - len(msg)
|
||||||
|
if width < 3:
|
||||||
|
width = 3
|
||||||
|
filler = "*" * width
|
||||||
|
return "\n%s %s " % (msg, filler)
|
||||||
|
|
||||||
|
|
||||||
|
class CallbackModule(CallbackBase):
|
||||||
|
"""
|
||||||
|
Reference: https://github.com/ansible/ansible/blob/v2.0.0.2-1/lib/ansible/plugins/callback/default.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
CALLBACK_VERSION = 2.0
|
||||||
|
CALLBACK_TYPE = 'stored'
|
||||||
|
CALLBACK_NAME = 'database'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(CallbackModule, self).__init__()
|
||||||
|
self.logger = PlayLogger()
|
||||||
|
self.start_time = datetime.now()
|
||||||
|
|
||||||
|
def v2_runner_on_failed(self, result, ignore_errors=False):
|
||||||
|
delegated_vars = result._result.get('_ansible_delegated_vars', None)
|
||||||
|
|
||||||
|
# Catch an exception
|
||||||
|
# This may never be called because default handler deletes
|
||||||
|
# the exception, since Ansible thinks it knows better
|
||||||
|
if 'exception' in result._result:
|
||||||
|
# Extract the error message and log it
|
||||||
|
error = result._result['exception'].strip().split('\n')[-1]
|
||||||
|
self.logger.append(error)
|
||||||
|
|
||||||
|
# Remove the exception from the result so it's not shown every time
|
||||||
|
del result._result['exception']
|
||||||
|
|
||||||
|
# Else log the reason for the failure
|
||||||
|
if result._task.loop and 'results' in result._result:
|
||||||
|
# item_on_failed, item_on_skipped, item_on_ok
|
||||||
|
self._process_items(result)
|
||||||
|
else:
|
||||||
|
if delegated_vars:
|
||||||
|
self.logger.append("fatal: [%s -> %s]: FAILED! => %s" % (result._host.get_name(
|
||||||
|
), delegated_vars['ansible_host'], self._dump_results(result._result)))
|
||||||
|
else:
|
||||||
|
self.logger.append("fatal: [%s]: FAILED! => %s" % (
|
||||||
|
result._host.get_name(), self._dump_results(result._result)))
|
||||||
|
|
||||||
|
def v2_runner_on_ok(self, result):
|
||||||
|
self._clean_results(result._result, result._task.action)
|
||||||
|
delegated_vars = result._result.get('_ansible_delegated_vars', None)
|
||||||
|
if result._task.action == 'include':
|
||||||
|
return
|
||||||
|
elif result._result.get('changed', False):
|
||||||
|
if delegated_vars:
|
||||||
|
msg = "changed: [%s -> %s]" % (result._host.get_name(),
|
||||||
|
delegated_vars['ansible_host'])
|
||||||
|
else:
|
||||||
|
msg = "changed: [%s]" % result._host.get_name()
|
||||||
|
else:
|
||||||
|
if delegated_vars:
|
||||||
|
msg = "ok: [%s -> %s]" % (result._host.get_name(),
|
||||||
|
delegated_vars['ansible_host'])
|
||||||
|
else:
|
||||||
|
msg = "ok: [%s]" % result._host.get_name()
|
||||||
|
|
||||||
|
if result._task.loop and 'results' in result._result:
|
||||||
|
# item_on_failed, item_on_skipped, item_on_ok
|
||||||
|
self._process_items(result)
|
||||||
|
else:
|
||||||
|
self.logger.append(msg)
|
||||||
|
|
||||||
|
def v2_runner_on_skipped(self, result):
|
||||||
|
if result._task.loop and 'results' in result._result:
|
||||||
|
# item_on_failed, item_on_skipped, item_on_ok
|
||||||
|
self._process_items(result)
|
||||||
|
else:
|
||||||
|
msg = "skipping: [%s]" % result._host.get_name()
|
||||||
|
self.logger.append(msg)
|
||||||
|
|
||||||
|
def v2_runner_on_unreachable(self, result):
|
||||||
|
delegated_vars = result._result.get('_ansible_delegated_vars', None)
|
||||||
|
if delegated_vars:
|
||||||
|
self.logger.append("fatal: [%s -> %s]: UNREACHABLE! => %s" % (result._host.get_name(
|
||||||
|
), delegated_vars['ansible_host'], self._dump_results(result._result)))
|
||||||
|
else:
|
||||||
|
self.logger.append("fatal: [%s]: UNREACHABLE! => %s" % (
|
||||||
|
result._host.get_name(), self._dump_results(result._result)))
|
||||||
|
|
||||||
|
def v2_runner_on_no_hosts(self, task):
|
||||||
|
self.logger.append("skipping: no hosts matched")
|
||||||
|
|
||||||
|
def v2_playbook_on_task_start(self, task, is_conditional):
|
||||||
|
self.logger.append("TASK [%s]" % task.get_name().strip())
|
||||||
|
|
||||||
|
def v2_playbook_on_play_start(self, play):
|
||||||
|
name = play.get_name().strip()
|
||||||
|
if not name:
|
||||||
|
msg = "PLAY"
|
||||||
|
else:
|
||||||
|
msg = "PLAY [%s]" % name
|
||||||
|
|
||||||
|
self.logger.append(msg)
|
||||||
|
|
||||||
|
def v2_playbook_item_on_ok(self, result):
|
||||||
|
delegated_vars = result._result.get('_ansible_delegated_vars', None)
|
||||||
|
if result._task.action == 'include':
|
||||||
|
return
|
||||||
|
elif result._result.get('changed', False):
|
||||||
|
if delegated_vars:
|
||||||
|
msg = "changed: [%s -> %s]" % (result._host.get_name(),
|
||||||
|
delegated_vars['ansible_host'])
|
||||||
|
else:
|
||||||
|
msg = "changed: [%s]" % result._host.get_name()
|
||||||
|
else:
|
||||||
|
if delegated_vars:
|
||||||
|
msg = "ok: [%s -> %s]" % (result._host.get_name(),
|
||||||
|
delegated_vars['ansible_host'])
|
||||||
|
else:
|
||||||
|
msg = "ok: [%s]" % result._host.get_name()
|
||||||
|
|
||||||
|
msg += " => (item=%s)" % (result._result['item'])
|
||||||
|
|
||||||
|
self.logger.append(msg)
|
||||||
|
|
||||||
|
def v2_playbook_item_on_failed(self, result):
|
||||||
|
delegated_vars = result._result.get('_ansible_delegated_vars', None)
|
||||||
|
if 'exception' in result._result:
|
||||||
|
# Extract the error message and log it
|
||||||
|
error = result._result['exception'].strip().split('\n')[-1]
|
||||||
|
self.logger.append(error)
|
||||||
|
|
||||||
|
# Remove the exception from the result so it's not shown every time
|
||||||
|
del result._result['exception']
|
||||||
|
|
||||||
|
if delegated_vars:
|
||||||
|
self.logger.append("failed: [%s -> %s] => (item=%s) => %s" % (result._host.get_name(
|
||||||
|
), delegated_vars['ansible_host'], result._result['item'], self._dump_results(result._result)))
|
||||||
|
else:
|
||||||
|
self.logger.append("failed: [%s] => (item=%s) => %s" % (result._host.get_name(
|
||||||
|
), result._result['item'], self._dump_results(result._result)))
|
||||||
|
|
||||||
|
def v2_playbook_item_on_skipped(self, result):
|
||||||
|
msg = "skipping: [%s] => (item=%s) " % (
|
||||||
|
result._host.get_name(), result._result['item'])
|
||||||
|
self.logger.append(msg)
|
||||||
|
|
||||||
|
def v2_playbook_on_stats(self, stats):
|
||||||
|
run_time = datetime.now() - self.start_time
|
||||||
|
# returns an int, unlike run_time.total_seconds()
|
||||||
|
self.logger.runtime = run_time.seconds
|
||||||
|
|
||||||
|
hosts = sorted(stats.processed.keys())
|
||||||
|
for h in hosts:
|
||||||
|
t = stats.summarize(h)
|
||||||
|
|
||||||
|
msg = "PLAY RECAP [%s] : %s %s %s %s %s" % (
|
||||||
|
h,
|
||||||
|
"ok: %s" % (t['ok']),
|
||||||
|
"changed: %s" % (t['changed']),
|
||||||
|
"unreachable: %s" % (t['unreachable']),
|
||||||
|
"skipped: %s" % (t['skipped']),
|
||||||
|
"failed: %s" % (t['failures']),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.logger.append(msg)
|
||||||
|
|
||||||
|
def record_logs(self, user_id, success=False):
|
||||||
|
"""
|
||||||
|
Special callback added to this callback plugin
|
||||||
|
Called by Runner objet
|
||||||
|
:param user_id:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
|
log_storage = Logs()
|
||||||
|
return log_storage.save_log(user_id, self.logger.log, self.logger.runtime, success)
|
23
scripts/common/ansible/run_playbook.py
Normal file
23
scripts/common/ansible/run_playbook.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from .task import Runner
|
||||||
|
# You may want this to run as user root instead
|
||||||
|
# or make this an environmental variable, or
|
||||||
|
# a CLI prompt. Whatever you want!
|
||||||
|
become_user_password = 'foo-whatever'
|
||||||
|
run_data = {
|
||||||
|
"user_id": 12345,
|
||||||
|
'foo': 'bar',
|
||||||
|
'baz': 'cux-or-whatever-this-one-is'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
runner = Runner(hostnames='192.168.10.233'
|
||||||
|
playbook='run.yaml',
|
||||||
|
private_key='/home/user/.ssh/id_whatever',
|
||||||
|
run_data=run_data,
|
||||||
|
become_pass=become_user_password,
|
||||||
|
verbosity=0)
|
||||||
|
|
||||||
|
|
||||||
|
stats = runner.run()
|
||||||
|
|
||||||
|
# Maybe do something with stats here? If you want!
|
159
scripts/common/ansible/task.py
Normal file
159
scripts/common/ansible/task.py
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import os
|
||||||
|
from tempfile import NamedTemporaryFile
|
||||||
|
from ansible.inventory import Inventory
|
||||||
|
from ansible.vars import VariableManager
|
||||||
|
from ansible.parsing.dataloader import DataLoader
|
||||||
|
from ansible.executor import playbook_executor
|
||||||
|
from ansible.utils.display import Display
|
||||||
|
|
||||||
|
|
||||||
|
class Options(object):
|
||||||
|
"""
|
||||||
|
Options class to replace Ansible OptParser
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, verbosity=None, inventory=None, listhosts=None, subset=None, module_paths=None, extra_vars=None,
|
||||||
|
forks=None, ask_vault_pass=None, vault_password_files=None, new_vault_password_file=None,
|
||||||
|
output_file=None, tags=None, skip_tags=None, one_line=None, tree=None, ask_sudo_pass=None, ask_su_pass=None,
|
||||||
|
sudo=None, sudo_user=None, become=None, become_method=None, become_user=None, become_ask_pass=None,
|
||||||
|
ask_pass=None, private_key_file=None, remote_user=None, connection=None, timeout=None, ssh_common_args=None,
|
||||||
|
sftp_extra_args=None, scp_extra_args=None, ssh_extra_args=None, poll_interval=None, seconds=None, check=None,
|
||||||
|
syntax=None, diff=None, force_handlers=None, flush_cache=None, listtasks=None, listtags=None, module_path=None):
|
||||||
|
self.verbosity = verbosity
|
||||||
|
self.inventory = inventory
|
||||||
|
self.listhosts = listhosts
|
||||||
|
self.subset = subset
|
||||||
|
self.module_paths = module_paths
|
||||||
|
self.extra_vars = extra_vars
|
||||||
|
self.forks = forks
|
||||||
|
self.ask_vault_pass = ask_vault_pass
|
||||||
|
self.vault_password_files = vault_password_files
|
||||||
|
self.new_vault_password_file = new_vault_password_file
|
||||||
|
self.output_file = output_file
|
||||||
|
self.tags = tags
|
||||||
|
self.skip_tags = skip_tags
|
||||||
|
self.one_line = one_line
|
||||||
|
self.tree = tree
|
||||||
|
self.ask_sudo_pass = ask_sudo_pass
|
||||||
|
self.ask_su_pass = ask_su_pass
|
||||||
|
self.sudo = sudo
|
||||||
|
self.sudo_user = sudo_user
|
||||||
|
self.become = become
|
||||||
|
self.become_method = become_method
|
||||||
|
self.become_user = become_user
|
||||||
|
self.become_ask_pass = become_ask_pass
|
||||||
|
self.ask_pass = ask_pass
|
||||||
|
self.private_key_file = private_key_file
|
||||||
|
self.remote_user = remote_user
|
||||||
|
self.connection = connection
|
||||||
|
self.timeout = timeout
|
||||||
|
self.ssh_common_args = ssh_common_args
|
||||||
|
self.sftp_extra_args = sftp_extra_args
|
||||||
|
self.scp_extra_args = scp_extra_args
|
||||||
|
self.ssh_extra_args = ssh_extra_args
|
||||||
|
self.poll_interval = poll_interval
|
||||||
|
self.seconds = seconds
|
||||||
|
self.check = check
|
||||||
|
self.syntax = syntax
|
||||||
|
self.diff = diff
|
||||||
|
self.force_handlers = force_handlers
|
||||||
|
self.flush_cache = flush_cache
|
||||||
|
self.listtasks = listtasks
|
||||||
|
self.listtags = listtags
|
||||||
|
self.module_path = module_path
|
||||||
|
|
||||||
|
|
||||||
|
class Runner(object):
|
||||||
|
|
||||||
|
def __init__(self, hostnames, playbook, private_key_file, run_data, become_pass, verbosity=0):
|
||||||
|
|
||||||
|
self.run_data = run_data
|
||||||
|
|
||||||
|
self.options = Options()
|
||||||
|
self.options.private_key_file = private_key_file
|
||||||
|
self.options.verbosity = verbosity
|
||||||
|
self.options.connection = 'ssh' # Need a connection type "smart" or "ssh"
|
||||||
|
self.options.become = True
|
||||||
|
self.options.become_method = 'sudo'
|
||||||
|
self.options.become_user = 'root'
|
||||||
|
|
||||||
|
# Set global verbosity
|
||||||
|
self.display = Display()
|
||||||
|
self.display.verbosity = self.options.verbosity
|
||||||
|
# Executor appears to have it's own
|
||||||
|
# verbosity object/setting as well
|
||||||
|
playbook_executor.verbosity = self.options.verbosity
|
||||||
|
|
||||||
|
# Become Pass Needed if not logging in as user root
|
||||||
|
passwords = {'become_pass': become_pass}
|
||||||
|
|
||||||
|
# Gets data from YAML/JSON files
|
||||||
|
self.loader = DataLoader()
|
||||||
|
self.loader.set_vault_password(os.environ['VAULT_PASS'])
|
||||||
|
|
||||||
|
# All the variables from all the various places
|
||||||
|
self.variable_manager = VariableManager()
|
||||||
|
self.variable_manager.extra_vars = self.run_data
|
||||||
|
|
||||||
|
# Parse hosts, I haven't found a good way to
|
||||||
|
# pass hosts in without using a parsed template :(
|
||||||
|
# (Maybe you know how?)
|
||||||
|
self.hosts = NamedTemporaryFile(delete=False)
|
||||||
|
self.hosts.write("""[run_hosts]
|
||||||
|
%s
|
||||||
|
""" % hostnames)
|
||||||
|
self.hosts.close()
|
||||||
|
|
||||||
|
# This was my attempt to pass in hosts directly.
|
||||||
|
#
|
||||||
|
# Also Note: In py2.7, "isinstance(foo, str)" is valid for
|
||||||
|
# latin chars only. Luckily, hostnames are
|
||||||
|
# ascii-only, which overlaps latin charset
|
||||||
|
## if isinstance(hostnames, str):
|
||||||
|
## hostnames = {"customers": {"hosts": [hostnames]}}
|
||||||
|
|
||||||
|
# Set inventory, using most of above objects
|
||||||
|
self.inventory = Inventory(
|
||||||
|
loader=self.loader, variable_manager=self.variable_manager, host_list=self.hosts.name)
|
||||||
|
self.variable_manager.set_inventory(self.inventory)
|
||||||
|
|
||||||
|
# Playbook to run. Assumes it is
|
||||||
|
# local to this python file
|
||||||
|
pb_dir = os.path.dirname(__file__)
|
||||||
|
playbook = "%s/%s" % (pb_dir, playbook)
|
||||||
|
|
||||||
|
# Setup playbook executor, but don't run until run() called
|
||||||
|
self.pbex = playbook_executor.PlaybookExecutor(
|
||||||
|
playbooks=[playbook],
|
||||||
|
inventory=self.inventory,
|
||||||
|
variable_manager=self.variable_manager,
|
||||||
|
loader=self.loader,
|
||||||
|
options=self.options,
|
||||||
|
passwords=passwords)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# Results of PlaybookExecutor
|
||||||
|
self.pbex.run()
|
||||||
|
stats = self.pbex._tqm._stats
|
||||||
|
|
||||||
|
# Test if success for record_logs
|
||||||
|
run_success = True
|
||||||
|
hosts = sorted(stats.processed.keys())
|
||||||
|
for h in hosts:
|
||||||
|
t = stats.summarize(h)
|
||||||
|
if t['unreachable'] > 0 or t['failures'] > 0:
|
||||||
|
run_success = False
|
||||||
|
|
||||||
|
# Dirty hack to send callback to save logs with data we want
|
||||||
|
# Note that function "record_logs" is one I created and put into
|
||||||
|
# the playbook callback file
|
||||||
|
self.pbex._tqm.send_callback(
|
||||||
|
'record_logs',
|
||||||
|
user_id=self.run_data['user_id'],
|
||||||
|
success=run_success
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove created temporary files
|
||||||
|
os.remove(self.hosts.name)
|
||||||
|
|
||||||
|
return stats
|
235
scripts/common/myansible.py
Normal file
235
scripts/common/myansible.py
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
from collections import namedtuple
|
||||||
|
from ansible.parsing.dataloader import DataLoader
|
||||||
|
from ansible.vars.manager import VariableManager
|
||||||
|
from ansible.inventory.manager import InventoryManager
|
||||||
|
from ansible.playbook.play import Play
|
||||||
|
from ansible.executor.playbook_executor import PlaybookExecutor
|
||||||
|
from ansible.executor.task_queue_manager import TaskQueueManager
|
||||||
|
from ansible.plugins.callback import CallbackBase
|
||||||
|
import json
|
||||||
|
import pdb
|
||||||
|
import copy
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def write_host(ip_addr):
|
||||||
|
host_file = "%s/host%s" % ('/tmp', time.strftime("%Y%m%d%H%M%s"))
|
||||||
|
with open(host_file, 'a+') as f:
|
||||||
|
for x in ip_addr.split(','):
|
||||||
|
f.writelines(x + '\n')
|
||||||
|
return host_file
|
||||||
|
|
||||||
|
|
||||||
|
class ResultsCallback(CallbackBase):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(ResultsCallback, self).__init__(*args, **kwargs)
|
||||||
|
self.task_ok = {}
|
||||||
|
self.task_unreachable = {}
|
||||||
|
self.task_failed = {}
|
||||||
|
self.task_skipped = {}
|
||||||
|
self.task_stats = {}
|
||||||
|
# self.host_ok = {}
|
||||||
|
# self.host_unreachable = {}
|
||||||
|
# self.host_failed = {}
|
||||||
|
|
||||||
|
def v2_runner_on_unreachable(self, result):
|
||||||
|
self.task_unreachable[result._host.get_name()] = result
|
||||||
|
|
||||||
|
def v2_runner_on_ok(self, result, *args, **kwargs):
|
||||||
|
self.task_ok[result._host.get_name()] = result
|
||||||
|
|
||||||
|
def v2_runner_on_failed(self, result, *args, **kwargs):
|
||||||
|
self.task_failed[result._host.get_name()] = result
|
||||||
|
|
||||||
|
def v2_runner_on_skipped(self, result, *args, **kwargs):
|
||||||
|
self.task_skipped[result._host.get_name()] = result
|
||||||
|
|
||||||
|
def v2_runner_on_stats(self, result, *args, **kwargs):
|
||||||
|
self.task_stats[result._host.get_name()] = result
|
||||||
|
|
||||||
|
|
||||||
|
class AnsibleAPI(object):
|
||||||
|
def __init__(self, hostfile, *args, **kwargs):
|
||||||
|
self.loader = DataLoader()
|
||||||
|
self.results_callback = ResultsCallback()
|
||||||
|
# if not os.path.isfile(hostfile):
|
||||||
|
# raise Exception("%s file not found!" % hostfile)
|
||||||
|
self.inventory = InventoryManager(
|
||||||
|
loader=self.loader, sources=[hostfile])
|
||||||
|
self.variable_manager = VariableManager(
|
||||||
|
loader=self.loader, inventory=self.inventory)
|
||||||
|
self.passwords = None
|
||||||
|
Options = namedtuple('Options',
|
||||||
|
['connection',
|
||||||
|
'remote_user',
|
||||||
|
'ask_sudo_pass',
|
||||||
|
'verbosity',
|
||||||
|
'ack_pass',
|
||||||
|
'module_path',
|
||||||
|
'forks',
|
||||||
|
'become',
|
||||||
|
'become_method',
|
||||||
|
'become_user',
|
||||||
|
'check',
|
||||||
|
'listhosts',
|
||||||
|
'listtasks',
|
||||||
|
'listtags',
|
||||||
|
'syntax',
|
||||||
|
'sudo_user',
|
||||||
|
'sudo',
|
||||||
|
'diff'])
|
||||||
|
# 初始化需要的对象
|
||||||
|
self.options = Options(connection='smart',
|
||||||
|
remote_user=None,
|
||||||
|
ack_pass=None,
|
||||||
|
sudo_user=None,
|
||||||
|
forks=5,
|
||||||
|
sudo=None,
|
||||||
|
ask_sudo_pass=False,
|
||||||
|
verbosity=5,
|
||||||
|
module_path=None,
|
||||||
|
become=None,
|
||||||
|
become_method=None,
|
||||||
|
become_user=None,
|
||||||
|
check=False,
|
||||||
|
diff=False,
|
||||||
|
listhosts=None,
|
||||||
|
listtasks=None,
|
||||||
|
listtags=None,
|
||||||
|
syntax=None)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def deal_result(info):
|
||||||
|
host_ips = list(info.get('success').keys())
|
||||||
|
info['success'] = host_ips
|
||||||
|
|
||||||
|
error_ips = info.get('failed')
|
||||||
|
error_msg = {}
|
||||||
|
for key, value in error_ips.items():
|
||||||
|
temp = {}
|
||||||
|
temp[key] = value.get('stderr')
|
||||||
|
error_msg.update(temp)
|
||||||
|
info['failed'] = error_msg
|
||||||
|
return info
|
||||||
|
# return json.dumps(info)
|
||||||
|
|
||||||
|
def run(self, module_name, module_args):
|
||||||
|
play_source = dict(
|
||||||
|
name="Ansible Play",
|
||||||
|
hosts='all',
|
||||||
|
gather_facts='no',
|
||||||
|
tasks=[
|
||||||
|
dict(action=dict(module=module_name, args=module_args),
|
||||||
|
register='shell_out'),
|
||||||
|
# dict(action=dict(module='debug', args=dict(msg='{{shell_out.stdout}}')))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
play = Play().load(play_source, variable_manager=self.variable_manager, loader=self.loader)
|
||||||
|
|
||||||
|
tqm = None
|
||||||
|
try:
|
||||||
|
tqm = TaskQueueManager(
|
||||||
|
inventory=self.inventory,
|
||||||
|
variable_manager=self.variable_manager,
|
||||||
|
loader=self.loader,
|
||||||
|
options=self.options,
|
||||||
|
passwords=self.passwords,
|
||||||
|
stdout_callback=self.results_callback,
|
||||||
|
)
|
||||||
|
result = tqm.run(play)
|
||||||
|
finally:
|
||||||
|
if tqm is not None:
|
||||||
|
tqm.cleanup()
|
||||||
|
|
||||||
|
# 定义字典用于接收或者处理结果
|
||||||
|
result_raw = {'success': {}, 'failed': {},
|
||||||
|
'unreachable': {}, 'skipped': {}, 'status': {}}
|
||||||
|
|
||||||
|
# 循环打印这个结果,success,failed,unreachable需要每个都定义一个
|
||||||
|
for host, result in self.results_callback.task_ok.items():
|
||||||
|
result_raw['success'][host] = result._result
|
||||||
|
for host, result in self.results_callback.task_failed.items():
|
||||||
|
result_raw['failed'][host] = result._result
|
||||||
|
for host, result in self.results_callback.task_unreachable.items():
|
||||||
|
result_raw['unreachable'][host] = result._result
|
||||||
|
|
||||||
|
result_full = copy.deepcopy(result_raw)
|
||||||
|
result_json = self.deal_result(result_raw)
|
||||||
|
if not result_json['failed']:
|
||||||
|
return result_json
|
||||||
|
else:
|
||||||
|
return result_full
|
||||||
|
# return result_raw
|
||||||
|
|
||||||
|
def run_playbook(self, file, **kwargs):
|
||||||
|
if not os.path.isfile(file):
|
||||||
|
raise Exception("%s file not found!" % file)
|
||||||
|
try:
|
||||||
|
# extra_vars = {} # 额外的参数 sudoers.yml以及模板中的参数,它对应ansible-playbook test.yml --extra-vars "host='aa' name='cc' "
|
||||||
|
self.variable_manager.extra_vars = kwargs
|
||||||
|
|
||||||
|
playbook = PlaybookExecutor(playbooks=['' + file], inventory=self.inventory,
|
||||||
|
variable_manager=self.variable_manager,
|
||||||
|
loader=self.loader, options=self.options, passwords=self.passwords)
|
||||||
|
|
||||||
|
playbook._tqm._stdout_callback = self.results_callback
|
||||||
|
playbook.run()
|
||||||
|
except Exception as e:
|
||||||
|
print("error:", e.message)
|
||||||
|
|
||||||
|
# 定义字典用于接收或者处理结果
|
||||||
|
result_raw = {'success': {}, 'failed': {},
|
||||||
|
'unreachable': {}, 'skipped': {}, 'status': {}}
|
||||||
|
|
||||||
|
# 循环打印这个结果,success,failed,unreachable需要每个都定义一个
|
||||||
|
for host, result in self.results_callback.task_ok.items():
|
||||||
|
result_raw['success'][host] = result._result
|
||||||
|
for host, result in self.results_callback.task_failed.items():
|
||||||
|
result_raw['failed'][host] = result._result
|
||||||
|
for host, result in self.results_callback.task_unreachable.items():
|
||||||
|
result_raw['unreachable'][host] = result._result
|
||||||
|
|
||||||
|
# for host, result in self.results_callback.task_skipped.items():
|
||||||
|
# result_raw['skipped'][host] = result._result
|
||||||
|
#
|
||||||
|
# for host, result in self.results_callback.task_stats.items():
|
||||||
|
# result_raw['status'][host] = result._result
|
||||||
|
|
||||||
|
result_full = copy.deepcopy(result_raw)
|
||||||
|
result_json = self.deal_result(result_raw)
|
||||||
|
if not result_json['failed']:
|
||||||
|
return result_json
|
||||||
|
else:
|
||||||
|
return result_full
|
||||||
|
|
||||||
|
|
||||||
|
class AnsiInterface(AnsibleAPI):
|
||||||
|
def __init__(self, hostfile, *args, **kwargs):
|
||||||
|
super(AnsiInterface, self).__init__(hostfile, *args, **kwargs)
|
||||||
|
|
||||||
|
def copy_file(self, src=None, dest=None):
|
||||||
|
"""
|
||||||
|
copy file
|
||||||
|
"""
|
||||||
|
module_args = "src=%s dest=%s" % (src, dest)
|
||||||
|
result = self.run('copy', module_args)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def exec_command(self, cmds):
|
||||||
|
"""
|
||||||
|
commands
|
||||||
|
"""
|
||||||
|
result = self.run('command', cmds)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def exec_script(self, path):
|
||||||
|
"""
|
||||||
|
在远程主机执行shell命令或者.sh脚本
|
||||||
|
"""
|
||||||
|
result = self.run('shell', path)
|
||||||
|
return result
|
Loading…
x
Reference in New Issue
Block a user