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

  

 

           

posted @ 2019-06-05 15:23  Django_gege  阅读(...)  评论(...编辑  收藏