ansible源码分析之程序入口
首先我们先来看下装完ansible之后 ansible里面是些上面内容
[root@node1 scripts]# cat /bin/ansible #!/usr/bin/python # EASY-INSTALL-SCRIPT: 'ansible==2.3.4.0','ansible' __requires__ = 'ansible==2.3.4.0'
#指定库的版本 指定了要执行的脚本名字 __import__('pkg_resources').run_script('ansible==2.3.4.0', 'ansible')
发现就简单的几行代码。关于这里可以去了解一下python pkg_resources用法。
这几行代码最终会执行/usr/lib/python2.7/site-packages/ansible-2.3.4.0-py2.7.egg/EGG-INFO/scripts下面对应的脚本
上面传来的参数是ansible所以会执行ansible脚本。
所以我们直接看/usr/lib/python2.7/site-packages/ansible-2.3.4.0-py2.7.egg/EGG-INFO/scripts/ansible 文件吧
if __name__ == '__main__': display = LastResort() cli = None me = os.path.basename(sys.argv[0]) try: display = Display() display.debug("starting run") sub = None ###下面这一堆代码都是去找文件名字适配的,和兼容行适配的,无需大多关注 target = me.split('-') ##这里是为了去小版本号的 比如ansible-1.4.2 if target[-1][0].isdigit(): target = target[:-1] if len(target) > 1: sub = target[1] myclass = "%sCLI" % sub.capitalize() elif target[0] == 'ansible': sub = 'adhoc' myclass = 'AdHocCLI' else: raise AnsibleError("Unknown Ansible alias: %s" % me) try: #反射导入ansible.cli.AdHocCLI类。 mycli = getattr(__import__("ansible.cli.%s" % sub, fromlist=[myclass]), myclass) except ImportError as e: if 'msg' in dir(e): msg = e.msg else: msg = e.message if msg.endswith(' %s' % sub): raise AnsibleError("Ansible sub-program not implemented: %s" % me) else: raise try: #各种检查 args = [to_text(a, errors='surrogate_or_strict') for a in sys.argv] ### except UnicodeError: display.error('Command line args are not in utf-8, unable to continue. Ansible currently only understands utf-8') display.display(u"The full traceback was:\n\n%s" % to_text(traceback.format_exc())) exit_code = 6 else: # 此时的args[u'/usr/bin/ansible', u'jack', u'-o'] cli = mycli(args) cli.parse() exit_code = cli.run() except AnsibleOptionsError as e: cli.parser.print_help() display.error(to_text(e), wrap_text=False) exit_code = 5 except AnsibleParserError as e: display.error(to_text(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_text(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_text(e), wrap_text=False) if not have_cli_options or have_cli_options and cli.options.verbosity > 2: log_only = False else: display.display("to see the full traceback, use -vvv") log_only = True display.display(u"the full traceback was:\n\n%s" % to_text(traceback.format_exc()), log_only=log_only) exit_code = 250 finally: # Remove ansible tempdir shutil.rmtree(C.DEFAULT_LOCAL_TMP, True) sys.exit(exit_code) 下面我们看下这个类mycli.parse的源代码: 这个方法是做参数检测的,多了少了,参数不对都将会报错 def parse(self): ''' create an options parser for bin/ansible ''' self.parser = CLI.base_parser( usage='%prog <host-pattern> [options]', runas_opts=True, inventory_opts=True, async_opts=True, output_opts=True, connect_opts=True, check_opts=True, runtask_opts=True, vault_opts=True, fork_opts=True, module_opts=True, ) # options unique to ansible ad-hoc self.parser.add_option('-a', '--args', dest='module_args', help="module arguments", default=C.DEFAULT_MODULE_ARGS) self.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) super(AdHocCLI, self).parse() #没有输入要执行的组或者ip就会报这条语句 if len(self.args) < 1: raise AnsibleOptionsError("Missing target hosts") #多输出了 参数也会报错,所以这个 self.args 只能等于一也就是。 elif len(self.args) > 1: raise AnsibleOptionsError("Extraneous options or arguments") display.verbosity = self.options.verbosity self.validate_conflicts(runas_opts=True, vault_opts=True, fork_opts=True) 下面我们看下这个类mycli.run的源代码: def run(self): ''' use Runner lib to do SSH things '''
###这里其实是实现了接口类。如果当前类中没有run方法就会报错了。 super(AdHocCLI, self).run() # only thing left should be host pattern pattern = to_text(self.args[0], errors='surrogate_or_strict') # ignore connection password cause we are local #这里的if判断的是执行的命令的主机是否是 local 如果是local话就不会调用远程执行命令方法了。 if self.options.connection == "local": self.options.ask_pass = False sshpass = None becomepass = None b_vault_pass = None self.normalize_become_options() ###从配置文件里面获取账号密码 (sshpass, becomepass) = self.ask_passwords() passwords = { 'conn_pass': sshpass, 'become_pass': becomepass } loader = DataLoader() if self.options.vault_password_file: # read vault_pass from a file b_vault_pass = CLI.read_vault_password_file(self.options.vault_password_file, loader=loader) loader.set_vault_password(b_vault_pass) elif self.options.ask_vault_pass: b_vault_pass = self.ask_vault_passwords() loader.set_vault_password(b_vault_pass) variable_manager = VariableManager() variable_manager.extra_vars = load_extra_vars(loader=loader, options=self.options) variable_manager.options_vars = load_options_vars(self.options) inventory = Inventory(loader=loader, variable_manager=variable_manager, host_list=self.options.inventory) variable_manager.set_inventory(inventory) no_hosts = False #判断需要执行命令的主机,如果为0的话表示配置文件里面定义的组里面没有主机 if len(inventory.list_hosts()) == 0: # Empty inventory display.warning("provided hosts list is empty, only localhost is available") no_hosts = True inventory.subset(self.options.subset) hosts = inventory.list_hosts(pattern) #下面这驼if就是各种环境参数判断,这里可以忽略不管。 if len(hosts) == 0: if no_hosts is False and self.options.subset: # Invalid limit raise AnsibleError("Specified --limit does not match any hosts") else: display.warning("No hosts matched, nothing to do") if self.options.listhosts: display.display(' hosts (%d):' % len(hosts)) for host in hosts: display.display(' %s' % host) return 0 if self.options.module_name in C.MODULE_REQUIRE_ARGS and not self.options.module_args: err = "No argument passed to %s module" % self.options.module_name if pattern.endswith(".yml"): err = err + ' (did you mean to run ansible-playbook?)' raise AnsibleOptionsError(err) # Avoid modules that don't work with ad-hoc if self.options.module_name in ('include', 'include_role'): raise AnsibleOptionsError("'%s' is not a valid action for ad-hoc commands" % self.options.module_name) # dynamically load any plugins from the playbook directory for name, obj in get_all_plugin_loaders(): if obj.subdir: plugin_path = os.path.join('.', obj.subdir) if os.path.isdir(plugin_path): obj.add_directory(plugin_path) play_ds = self._play_ds(pattern, self.options.seconds, self.options.poll_interval) play = Play().load(play_ds, variable_manager=variable_manager, loader=loader) if self.callback: cb = self.callback elif self.options.one_line: cb = 'oneline' # Respect custom 'stdout_callback' only with enabled 'bin_ansible_callbacks' elif C.DEFAULT_LOAD_CALLBACK_PLUGINS and C.DEFAULT_STDOUT_CALLBACK != 'default': cb = C.DEFAULT_STDOUT_CALLBACK else: cb = 'minimal' run_tree=False if self.options.tree: C.DEFAULT_CALLBACK_WHITELIST.append('tree') C.TREE_DIR = self.options.tree run_tree=True # now create a task queue manager to execute the play self._tqm = None try: self._tqm = TaskQueueManager( inventory=inventory, variable_manager=variable_manager, loader=loader, options=self.options, passwords=passwords, stdout_callback=cb, run_additional_callbacks=C.DEFAULT_LOAD_CALLBACK_PLUGINS, run_tree=run_tree, ) #这里的result 就是一个退出状态码,执命令的过程都在self._tqm.run里面 result = self._tqm.run(play) finally: if self._tqm: self._tqm.cleanup() if loader: loader.cleanup_all_tmp_files() return result
Welcome to visit