ansible源码解析-ansible配置文件加载过程

前言

​ ansible作为自动化工具,在执行时会读取/etc/ansible/ansible.cfg 配置文件,获取其中配置参数用于后续执行,那么ansible执行时,代码是如何加载这个配置文件的呢?

​ 本文代码示例为ansible 2.11版本

ansible命令执行

举个栗子:

ansible -i /etc/ansible/hosts localhost -c  local -m shell -a "ls /tmp"

在执行这个代码,我们是运行了/usr/bin/ansible命令,查看/usr/bin/ansible中68~92行:

    # 获取执行文件名
    me = os.path.basename(sys.argv[0])

    try:
        display = Display()
        display.debug("starting run")

        sub = None
        # 使用"-"分割文件名,如: ansible-playbook 会分割为["ansible","playbook"]
        target = me.split('-')
        # 存在带版本的 ansible-3.6 情况,如果带版本,则去除调版本部分
        if target[-1][0].isdigit():
            # Remove any version or python version info as downstreams
            # sometimes add that
            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:
            # 通过反射找到对应执行方法
            mycli = getattr(__import__("ansible.cli.%s" % sub, fromlist=[myclass]), myclass)
            ...
            ...
         # 实例化反射类,ansible.cli.adhoc.AdHocCLI 
         cli = mycli(args)
         # 执行run方法
         exit_code = cli.run()

通过命令可以发现ansible-playbook,ansible-doc等命令的软连接到/usr/bin/ansible文件

# ll /usr/bin/ansible*2.7
-rwxr-xr-x 1 root root 5933 Aug 22 04:06 /usr/bin/ansible-2.7
lrwxrwxrwx 1 root root    7 Dec 13 19:41 /usr/bin/ansible-console-2.7 -> ansible
lrwxrwxrwx 1 root root    7 Dec 13 19:41 /usr/bin/ansible-doc-2.7 -> ansible
lrwxrwxrwx 1 root root    7 Dec 13 19:41 /usr/bin/ansible-galaxy-2.7 -> ansible
lrwxrwxrwx 1 root root    7 Dec 13 19:41 /usr/bin/ansible-playbook-2.7 -> ansible
lrwxrwxrwx 1 root root    7 Dec 13 19:41 /usr/bin/ansible-pull-2.7 -> ansible
lrwxrwxrwx 1 root root    7 Dec 13 19:41 /usr/bin/ansible-vault-2.7 -> ansible

通过解读/usr/bin/ansible文件的意义就是,根据命令进行解析,通过反射调用对应的执行模块,而ansible命令执行模块为ansible.cli.adhoc,解析此模块:

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

# 在ansible文件导入ansible.cli.adhoc,adhoc导入了ansible.constants
from ansible import constants as C
# 而ansible.constants执行了加载/etc/ansible/ansible.cfg的动作

上代码,查看ansible.constants源码,在180~205行:

# 获取配置文件的模块和类
from ansible.config.manager import ConfigManager, ensure_type, get_ini_config_value

# POPULATE SETTINGS FROM CONFIG ###
# 实例化ConfigManager类
config = ConfigManager()

# Generate constants from config
# 解析配置文件,生成静态变量
for setting in config.data.get_settings():

    value = setting.value
    if setting.origin == 'default' and \
       isinstance(setting.value, string_types) and \
       (setting.value.startswith('{{') and setting.value.endswith('}}')):
        try:
            t = Template(setting.value)
            value = t.render(vars())
            try:
                value = literal_eval(value)
            except ValueError:
                pass  # not a python data structure
        except Exception:
            pass  # not templatable

        value = ensure_type(value, setting.type)

    set_constant(setting.name, value)

for warn in config.WARNINGS:
    _warning(warn)

到了这里,我们看下ansible.config.manager模块中ConfigManager类实例化的时候做了什么

class ConfigManager(object):

    DEPRECATED = []
    WARNINGS = set()

    def __init__(self, conf_file=None, defs_file=None):

        self._base_defs = {}
        self._plugins = {}
        self._parsers = {}

        self._config_file = conf_file
        self.data = ConfigData()

        self._base_defs = self._read_config_yaml_file(defs_file or ('%s/base.yml' % os.path.dirname(__file__)))
        _add_base_defs_deprecations(self._base_defs)
		
		# 实例化是没有代入配置文件,则会使用find_ini_config_file方法查找配置文件
        # 在`/etc/ansible.cfg`或`~/ansibel.cfg`找到则返回文件地址,没有找到则返回None
        if self._config_file is None:
            # set config using ini
            self._config_file = find_ini_config_file(self.WARNINGS)
        # consume configuration
        if self._config_file:
            # initialize parser and read config
            # 解析配置文件
            self._parse_config_file()

        # update constants
        # 更新到静态变量中
        self.update_config_data()

            ...
            ...
            
            
# 此方法获取配置文件 ansible.cfg        
def find_ini_config_file(warnings=None):
    ''' Load INI Config File order(first found is used): ENV, CWD, HOME, /etc/ansible '''
    # FIXME: eventually deprecate ini configs

    if warnings is None:
        # Note: In this case, warnings does nothing
        warnings = set()

    # A value that can never be a valid path so that we can tell if ANSIBLE_CONFIG was set later
    # We can't use None because we could set path to None.
    SENTINEL = object

    potential_paths = []

    # Environment setting
    path_from_env = os.getenv("ANSIBLE_CONFIG", SENTINEL)
    if path_from_env is not SENTINEL:
        path_from_env = unfrackpath(path_from_env, follow=False)
        if os.path.isdir(to_bytes(path_from_env)):
            path_from_env = os.path.join(path_from_env, "ansible.cfg")
        potential_paths.append(path_from_env)

    # Current working directory
    warn_cmd_public = False
    try:
        cwd = os.getcwd()
        perms = os.stat(cwd)
        cwd_cfg = os.path.join(cwd, "ansible.cfg")
        if perms.st_mode & stat.S_IWOTH:
            # Working directory is world writable so we'll skip it.
            # Still have to look for a file here, though, so that we know if we have to warn
            if os.path.exists(cwd_cfg):
                warn_cmd_public = True
        else:
            potential_paths.append(to_text(cwd_cfg, errors='surrogate_or_strict'))
    except OSError:
        # If we can't access cwd, we'll simply skip it as a possible config source
        pass

    # Per user location
    potential_paths.append(unfrackpath("~/.ansible.cfg", follow=False))

    # System location
    potential_paths.append("/etc/ansible/ansible.cfg")

    for path in potential_paths:
        b_path = to_bytes(path)
        if os.path.exists(b_path) and os.access(b_path, os.R_OK):
            break
    else:
        path = None

    # Emit a warning if all the following are true:
    # * We did not use a config from ANSIBLE_CONFIG
    # * There's an ansible.cfg in the current working directory that we skipped
    if path_from_env != path and warn_cmd_public:
        warnings.add(u"Ansible is being run in a world writable directory (%s),"
                     u" ignoring it as an ansible.cfg source."
                     u" For more information see"
                     u" https://docs.ansible.com/ansible/devel/reference_appendices/config.html#cfg-in-world-writable-dir"
                     % to_text(cwd))

    return path

总结

执行ansible命令代码,通过反射ansible.cli目录下对应的执行模块和类,只要加载from ansible import constants模块,就会实例化ansible.config.manager.ConfigManager类,从而获取所在主机/etc/ansible.cfg~/ansibel.cfg是否有ansible配置文件。

你可能感兴趣的:(ansible,python,ansible)