ansible作为自动化工具,在执行时会读取/etc/ansible/ansible.cfg
配置文件,获取其中配置参数用于后续执行,那么ansible执行时,代码是如何加载这个配置文件的呢?
本文代码示例为ansible 2.11版本
举个栗子:
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配置文件。