这一系列文章将通过一些常见的 Ansible 使用例子来解析 Ansible 内部代码实现
Ansible 有很多命令,ansible、ansible-playbook、ansible-doc、ansible-galaxy、ansible-console 等,笔者在看 Ansible 入口代码的时候发现 Ansible 对于不同命令的处理方式很特别
笔者的环境是 Github ansible/ansible 仓库的 stable-2.2
分支
git branch |
先来看看入口文件 setup.py
,了解 setuptools
的朋友应该对这个参数不陌生,简单来说,就是这些文件将会被复制到系统 PATH
中,供用户调用
scripts=[ |
到这里,没有什么问题,正常的写法应该是每一个命令对应某一个 Cli
类去处理,但是 Ansible 不是这样做的,可以看到所有的命令其实都是 ansible 命令的软链接,也就是说,无论我们调用 ansible-playbook、ansible-doc、ansible-galaxy 命令最终都是去执行 ansible 命令
ls bin -l |
那 Ansible 是如何区分不同命令调用的?
ansible 命令解析
代码路径 ansible/bin/ansible
下面是简化过的 ansible 命令入口代码
""" |
Ansible 通过调用命令的名称来动态的引入对应的类
__imoprt__
是 Python 的内置函数,import
也是调用 __import__
实现的,下面是两种方式引用的对比
from ansible.cli.playbook import PlaybookCLI as mycli |
__import__
可以以字符串的形式引入模块/类/方法,更适合动态引入
这样我们可以得知,如果调用 ansible 命令,会引入 ansible.cli.adhoc.AdhocCLI
类,而 调用 ansible-playbook 命令,会引入 ansible.cli.playbook.PlaybookCLI
类,然后实例化对象,将命令行参数传递给对象进行解析,再调用对象的 run
方法完成此次调用
CLI 类
那么我们来看看具体 CLI
类的实现,从 ansible/cli/adhoc.py 这个文件中可以看出,所有 Ansible 的子命令类都是继承于 ansible.cli.CLI
(ansible/cli/init.py) 实现的
CLI
是一个抽象类,具体看 Python装饰器、metaclass、abc模块学习笔记 这篇文章
ansible.cli.CLI
最核心的方法是下面这几个
|
base_parser
定义一些基础分类的命令行选项,并返回 SortedOptParser
对象
子类通过传递参数来选择是否需要特定分类的命令行选项,再通过重写的 parse
方法中调用 CLI.base_parser
并添加额外选项,再解析传入的命令行参数,AdhocCLI
的实现如下
|
AdhocCLI
子类添加了两个新的选项 -a
、-m
用来指定模块参数和模块名称,然后通过调用 validate_conflicts
来验证命令行参数合法性
run
方法则是用来运行特定任务的,AdhocCLI
中用来创建单个 play task
,关于这里的代码,下篇文章会有介绍
ansible.cli.adhoc.AdhocCLI.run
总结
画了个思维导图,不是很清晰,但是大致就是这样,点击图片可放大查看