Ansible命令讲解
ansible 0.8 版本
研究ansible 0.8 版本源码
wget https://pypi.python.org/packages/source/s/setuptools/setuptools-20.9.0.tar.gz#md5=e5f4d80fcb68bcac179b327bf1791dec
tar -zxf setuptools-20.9.0.tar.gz
cd setuptools-20.9.0
python setup.py install
cd ..
tar -zxf setuptools-0.6c11.tar.gz
cd setuptools-0.6c11
python setup.py install
cd ..
easy_install jinja2
easy_install pyyaml
unzip ansible-0.8.zip
cd ansible-0.8
python setup.py install
ansible模块安装位置:/usr/lib/python2.6/site-packages/
#!/usr/bin/env python # (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see <http://www.gnu.org/licenses/>. ######################################################## import sys import getpass from ansible.runner import Runner # API模块 import ansible.constants as C # 定义命令行的配置参数(包含一些可以配置的参数,一些不可配置的参数) from ansible import utils # 工具集合,即常用的函数模块 from ansible import errors # 常见的ansible错误类型定义 from ansible import callbacks from ansible import inventory # 主机列表清单 ######################################################## class Cli(object): ''' code behind bin/ansible ''' # ---------------------------------------------- def __init__(self): self.stats = callbacks.AggregateStats() # 统计playbook运行时各种统计信息 self.callbacks = callbacks.CliRunnerCallbacks() # 回调函数供/usr/bin/ansible使用 # ---------------------------------------------- def parse(self): # bin/ansible命令行帮助信息 ''' create an options parser for bin/ansible ''' parser = utils.base_parser(constants=C, runas_opts=True, subset_opts=True, async_opts=True, output_opts=True, connect_opts=True, usage='%prog <host-pattern> [options]') parser.add_option('-a', '--args', dest='module_args', help="module arguments", default=C.DEFAULT_MODULE_ARGS) parser.add_option('-m', '--module-name', dest='module_name', help="module name to execute (default=%s)" % C.DEFAULT_MODULE_NAME, default=C.DEFAULT_MODULE_NAME) options, args = parser.parse_args()
print(options, args) # 添加这行代码,可以查看ansible从终端获取的到有配置项,例如:
[root@zabbix ~]# ansible all -m shell -a 'date'
(<Values at 0x24e25f0: {'subset': None, 'ask_pass': False, 'remote_user': 'root', 'seconds': 0, 'poll_interval': 15, 'sudo': False, 'tree': None, 'private_key_file': None, 'one_line': None, 'connection': 'paramiko', 'inventory': '/etc/ansible/hosts', 'timeout': 10, 'module_name': 'shell', 'module_path': '/usr/share/ansible/', 'sudo_user': None, 'forks': 5, 'ask_sudo_pass': False, 'module_args': 'date'}>, ['all']) self.callbacks.options = options if len(args) == 0 or len(args) > 1: # 判断命令行输入的参数个数,不满足条件则输出帮助信息,并退出 parser.print_help() sys.exit(1) return (options, args) # ---------------------------------------------- def run(self, options, args): ''' use Runner lib to do SSH things ''' pattern = args[0] print(pattern) # 获取要执行命令的主机或者主机组,本例为test主机 inventory_manager = inventory.Inventory(options.inventory) # 执行inventory目录下的__init__.py文件中的Inventory类获取主机列表文件,根据上一步获取的主机列表文件而定,一般默认为/etc/ansible/hosts
其中options为上一步传递过来的字典格式的参数内容,options.inventory 即获取参数字典inventory 这个key的值
hosts = inventory_manager.list_hosts(pattern)
# 即调用inventory目录下的__init__.py文件中的Inventory类的list_hosts方法,list_hosts方法调用get_hosts方法,get_hosts方法又调用了_get_host方法
print(hosts) # 获取主机列表 ['test1', 'test2'],即/etc/ansible/hosts里面主机第一类的内容
# 主机不存在的情况 if len(hosts) == 0: print >>sys.stderr, "No hosts matched" sys.exit(1)
# 获取密码内容 sshpass = None sudopass = None if options.ask_pass: sshpass = getpass.getpass(prompt="SSH password: ") if options.ask_sudo_pass: sudopass = getpass.getpass(prompt="sudo password: ") options.sudo = True if options.sudo_user: options.sudo = True options.sudo_user = options.sudo_user or C.DEFAULT_SUDO_USER if options.tree: utils.prepare_writeable_dir(options.tree) # 将参数封装给runner模块 runner = Runner( module_name=options.module_name, module_path=options.module_path, module_args=options.module_args, remote_user=options.remote_user, remote_pass=sshpass, inventory=inventory_manager, timeout=options.timeout, private_key_file=options.private_key_file, forks=options.forks, pattern=pattern, callbacks=self.callbacks, sudo=options.sudo, sudo_pass=sudopass,sudo_user=options.sudo_user, transport=options.connection, subset=options.subset ) if options.seconds: print "background launch...\n\n" results, poller = runner.run_async(options.seconds) results = self.poll_while_needed(poller, options) else: results = runner.run() # 调用lib/ansible/runner/__init__.py中Runner类的run方法
print(results)
# 字典格式的返回结果
{
'dark': {
'test1': {
},
'test2': {
}
},
'contacted': {
'172.16.1.151': {
u'changed': True,
u'end': u'2016-08-0406: 07: 56.768411',
u'stdout': u'2016\u5e7408\u670804\u65e5\u661f\u671f\u56db06: 07: 56CST',
u'cmd': u'date',
u'rc': 0,
u'start': u'2016-08-0406: 07: 56.765674',
u'stderr': u'',
u'delta': u'0: 00: 00.002737',
'invocation': {
'module_name': u'shell',
'module_args': u'date'
}
}
}
}
return (runner, results) # ---------------------------------------------- def poll_while_needed(self, poller, options): ''' summarize results from Runner ''' # BACKGROUND POLL LOGIC when -B and -P are specified if options.seconds and options.poll_interval > 0: poller.wait(options.seconds, options.poll_interval) return poller.results ######################################################## if __name__ == '__main__': cli = Cli() # 实例化自定义命令行界面类 (options, args) = cli.parse() # 通过命令行获取执行参数(可配置的及不可配置的参数) try: (runner, results) = cli.run(options, args) # 去执行命令,并返回结果,字典格式 for result in results['contacted'].values(): # 将字典格式的返回结果做处理,最后将结果显示给用户 if 'failed' in result or result.get('rc', 0) != 0: sys.exit(2) if results['dark']: sys.exit(2) except errors.AnsibleError, e: # Generic handler for ansible specific errors print "ERROR: %s" % str(e) sys.exit(1)
ansible1.9.0.0版本
#!/usr/bin/env python # (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see <http://www.gnu.org/licenses/>. ######################################################## __requires__ = ['ansible'] try: import pkg_resources except Exception: # Use pkg_resources to find the correct versions of libraries and set # sys.path appropriately when there are multiversion installs. But we # have code that better expresses the errors in the places where the code # is actually used (the deps are optional for many code paths) so we don't # want to fail here. pass
# 导入常用的Python模块 import os import sys from ansible.runner import Runner # Python的API模块 import ansible.constants as C from ansible import utils from ansible import errors from ansible import callbacks from ansible import inventory ######################################################## class Cli(object): ''' code behind bin/ansible ''' # ---------------------------------------------- def __init__(self): self.stats = callbacks.AggregateStats() self.callbacks = callbacks.CliRunnerCallbacks() if C.DEFAULT_LOAD_CALLBACK_PLUGINS: callbacks.load_callback_plugins() # ---------------------------------------------- def parse(self): ''' create an options parser for bin/ansible ''' parser = utils.base_parser( constants=C, runas_opts=True, subset_opts=True, async_opts=True, output_opts=True, connect_opts=True, check_opts=True, diff_opts=False, usage='%prog <host-pattern> [options]' ) parser.add_option('-a', '--args', dest='module_args', help="module arguments", default=C.DEFAULT_MODULE_ARGS) parser.add_option('-m', '--module-name', dest='module_name', help="module name to execute (default=%s)" % C.DEFAULT_MODULE_NAME, default=C.DEFAULT_MODULE_NAME) options, args = parser.parse_args() self.callbacks.options = options if len(args) == 0 or len(args) > 1: parser.print_help() sys.exit(1) # privlege escalation command line arguments need to be mutually exclusive utils.check_mutually_exclusive_privilege(options, parser) if (options.ask_vault_pass and options.vault_password_file): parser.error("--ask-vault-pass and --vault-password-file are mutually exclusive") return (options, args) # ---------------------------------------------- def run(self, options, args): ''' use Runner lib to do SSH things ''' pattern = args[0] sshpass = becomepass = vault_pass = become_method = None # Never ask for an SSH password when we run with local connection if options.connection == "local": options.ask_pass = False else: options.ask_pass = options.ask_pass or C.DEFAULT_ASK_PASS options.ask_vault_pass = options.ask_vault_pass or C.DEFAULT_ASK_VAULT_PASS # become utils.normalize_become_options(options) prompt_method = utils.choose_pass_prompt(options) (sshpass, becomepass, vault_pass) = utils.ask_passwords(ask_pass=options.ask_pass, become_ask_pass=options.become_ask_pass, ask_vault_pass=options.ask_vault_pass, become_method=prompt_method) # read vault_pass from a file if not options.ask_vault_pass and options.vault_password_file: vault_pass = utils.read_vault_file(options.vault_password_file) extra_vars = utils.parse_extra_vars(options.extra_vars, vault_pass) inventory_manager = inventory.Inventory(options.inventory, vault_password=vault_pass) if options.subset: inventory_manager.subset(options.subset) hosts = inventory_manager.list_hosts(pattern) if len(hosts) == 0: callbacks.display("No hosts matched", stderr=True) sys.exit(0) if options.listhosts: for host in hosts: callbacks.display(' %s' % host) sys.exit(0) if options.module_name in ['command','shell'] and not options.module_args: callbacks.display("No argument passed to %s module" % options.module_name, color='red', stderr=True) sys.exit(1) if options.tree: utils.prepare_writeable_dir(options.tree) runner = Runner( module_name=options.module_name, module_path=options.module_path, module_args=options.module_args, remote_user=options.remote_user, remote_pass=sshpass, inventory=inventory_manager, timeout=options.timeout, private_key_file=options.private_key_file, forks=options.forks, pattern=pattern, callbacks=self.callbacks, transport=options.connection, subset=options.subset, check=options.check, diff=options.check, vault_pass=vault_pass, become=options.become, become_method=options.become_method, become_pass=becomepass, become_user=options.become_user, extra_vars=extra_vars, ) if options.seconds: callbacks.display("background launch...\n\n", color='cyan') results, poller = runner.run_async(options.seconds) results = self.poll_while_needed(poller, options) else: results = runner.run() return (runner, results) # ---------------------------------------------- def poll_while_needed(self, poller, options): ''' summarize results from Runner ''' # BACKGROUND POLL LOGIC when -B and -P are specified if options.seconds and options.poll_interval > 0: poller.wait(options.seconds, options.poll_interval) return poller.results ######################################################## if __name__ == '__main__': callbacks.display("", log_only=True) callbacks.display(" ".join(sys.argv), log_only=True) callbacks.display("", log_only=True) cli = Cli() (options, args) = cli.parse() try: (runner, results) = cli.run(options, args) for result in results['contacted'].values(): if 'failed' in result or result.get('rc', 0) != 0: sys.exit(2) if results['dark']: sys.exit(3) except errors.AnsibleError, e: # Generic handler for ansible specific errors callbacks.display("ERROR: %s" % str(e), stderr=True, color='red') sys.exit(1)
ansible-2.1.0.0-0.1.rc1 版本
#!/usr/bin/env python # (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> # # This file is part of Ansible # # Ansible is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Ansible is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ansible. If not, see <http://www.gnu.org/licenses/>. ######################################################## from __future__ import (absolute_import, division, print_function) # __future__模块:提供某些将要引入的特性的模块 详解附录__future__模块讲解
__metaclass__ = type __requires__ = ['ansible'] try: import pkg_resources except Exception: # Use pkg_resources to find the correct versions of libraries and set # sys.path appropriately when there are multiversion installs. But we # have code that better expresses the errors in the places where the code # is actually used (the deps are optional for many code paths) so we don't # want to fail here. pass
# 引入常用的Python模块 import os import shutil import sys import traceback # for debug from multiprocessing import Lock debug_lock = Lock() import ansible.constants as C from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError from ansible.utils.display import Display from ansible.utils.unicode import to_unicode ######################################## ### OUTPUT OF LAST RESORT ###
# 定义一个类,用于最后输出结果排序
class LastResort(object): def display(self, msg): print(msg, file=sys.stderr) def error(self, msg, wrap_text=None): print(msg, file=sys.stderr) ######################################## if __name__ == '__main__': display = LastResort() # 实例化上边定义的LastResort类 cli = None me = os.path.basename(sys.argv[0]) # ansible命令本身 try: display = Display() display.debug("starting run") sub = None try: if me.find('-') != -1: target = me.split('-') if len(target) > 1: sub = target[1] myclass = "%sCLI" % sub.capitalize() mycli = getattr(__import__("ansible.cli.%s" % sub, fromlist=[myclass]), myclass) elif me == 'ansible': from ansible.cli.adhoc import AdHocCLI as mycli else: raise AnsibleError("Unknown Ansible alias: %s" % me) except ImportError as e: if e.message.endswith(' %s' % sub): raise AnsibleError("Ansible sub-program not implemented: %s" % me) else: raise cli = mycli(sys.argv) cli.parse() exit_code = cli.run() except AnsibleOptionsError as e: cli.parser.print_help() display.error(to_unicode(e), wrap_text=False) exit_code = 5 except AnsibleParserError as e: display.error(to_unicode(e), wrap_text=False) exit_code = 4 # TQM takes care of these, but leaving comment to reserve the exit codes # except AnsibleHostUnreachable as e: # display.error(str(e)) # exit_code = 3 # except AnsibleHostFailed as e: # display.error(str(e)) # exit_code = 2 except AnsibleError as e: display.error(to_unicode(e), wrap_text=False) exit_code = 1 except KeyboardInterrupt: display.error("User interrupted execution") exit_code = 99 except Exception as e: have_cli_options = cli is not None and cli.options is not None display.error("Unexpected Exception: %s" % to_unicode(e), wrap_text=False) if not have_cli_options or have_cli_options and cli.options.verbosity > 2: display.display(u"the full traceback was:\n\n%s" % to_unicode(traceback.format_exc())) else: display.display("to see the full traceback, use -vvv") exit_code = 250 finally: # Remove ansible tempdir shutil.rmtree(C.DEFAULT_LOCAL_TMP, True) sys.exit(exit_code)
附录
1、__future__模块
__future__模块:提供某些将要引入的特性,基本上都是Python 3的特性
>>>import __future__ >>> __future__. __future__.CO_FUTURE_ABSOLUTE_IMPORT __future__.all_feature_names __future__.CO_FUTURE_DIVISION __future__.division __future__.CO_FUTURE_PRINT_FUNCTION __future__.generators __future__.CO_FUTURE_UNICODE_LITERALS __future__.nested_scopes __future__.CO_FUTURE_WITH_STATEMENT __future__.print_function __future__.CO_GENERATOR_ALLOWED __future__.unicode_literals __future__.CO_NESTED __future__.with_statement __future__.absolute_import
可以导入的新特性
>>> for i in __future__.all_feature_names: ... print(i) ... nested_scopes generators division absolute_import with_statement print_function unicode_literals barry_as_FLUFL generator_stop
对于功能如下:
division
新的除法特性,本来的除号`/`对于分子分母是整数的情况会取整,但新特性中在此情况下的除法不会取整,取整的使用`//`。
print_function
新的print是一个函数,如果导入此特性,之前的print语句就不能用了。
unicode_literals
这个是对字符串使用unicode字符
nested_scopes
这个是修改嵌套函数或lambda函数中变量的搜索顺序,从`当前函数命名空间->模块命名空间`的顺序更改为了`当前函数命名空间->父函数命名空间->模块命名空间`,python2.7.5中默认使用
generators
生成器,对应yield的语法,python2.7.5中默认使用
with_statement
是使用with关键字,python2.7.5是默认使用
2、methclass
methclass作用
1. 你可以自由的、动态的修改/增加/删除 类的或者实例中的方法或者属性
2. 批量的对某些方法使用decorator,而不需要每次都在方法的上面加入@decorator_func
3. 当引入第三方库的时候,如果该库某些类需要patch的时候可以用metaclass
4. 可以用于序列化(参见yaml这个库的实现,我没怎么仔细看)
5. 提供接口注册,接口格式检查等
6. 自动委托(auto delegate)
7. more...
3、__requires__
__requires__是一个控制台生成脚本的一部分。Python本身没有意义,只有setuptools库本身使用这些信息。
4、pkg_resources
5、traceback
追踪异常
6、multiprocessing
多进程并发
参考资料
__future__模块讲解:http://www.cnblogs.com/ksedz/archive/2013/07/15/3190208.html
methclass讲解:http://www.cnblogs.com/huangcong/archive/2011/08/28/2156307.html