MoHand

概览

MoHand 为通用自动化处理工具,主要用于运维自动化。

灵感来自于 Python 下很经典的运维工具 Fabric ,故您在使用中会看到很多 Fabric 的影子。

最初开发 MoHand 是为了在公司自动化登录堡垒机,由于所在公司的业务较复杂, 故需要登录全国各地的堡垒机,然后跳到对应的私有云内,进行开发&调试工作。

考虑到高扩展性,同时也是因为深知自己能力有限不可多知多能,故在设计整体架构时, 参考了 Gulp 以及 PostCSS 的组件思想,通过 Python 的 stevedore 实现了高扩展性的插件系统,并自己实现了一个用于自动化登录堡垒机的插件 mohand-plugin-expect

如果您使用后觉得有意思,欢迎实现更多的插件分享给大家,我会及时在该文档中更新 plugin 列表, 供后来人参考&使用。若其足够优秀,我将考虑将其加入到 MoHand 的 install_requires 中!

安装方法

您可以通过 pip 进行安装,本包仅在 Python 3.X 下测试通过:

pip3 install mohand

提示

v1.0.1 版本开始,增加了对 Python 2.X 的支持,但由于我主要在 Py3 环境下使用,所以强烈建议您在 Py3 下使用。如果您在 Py2 环境下遇到任何异常, 可以及时提 Issues 给我,我会努力在搬砖的间隙进行修复。。。

注解

建议使用 virtualenv 来安装,避免与其他包产生依赖冲突。

如果您感兴趣的话,可以了解下 virtualenvwrapper ,用其来管理虚拟环境可谓丝般顺滑!

使用说明

安装成功后,您将获得 mohand 终端命令,该命令在调用前会加载当前路径下的 handfile.py 文件,若没有找到,则会递归向父级路径查找,直到根路径结束。若未找到该文件,则会返回信息如下:

$ mohand
[ERROR] 未找到 handfile 文件!

当其找到该文件后,会立即加载该文件,并停止继续递归。此处我们在当前路径创建该文件,然后再次执行:

$ touch handfile.py
$ mohand
Usage: mohand [OPTIONS] COMMAND [ARGS]...

  通用自动化处理工具

  详情参考 `GitHub <https://github.com/littlemo/mohand>`_

Options:
  --author   作者信息
  --version  版本信息
  --help     Show this message and exit.

此时 mohand 已经可以正常运行了,但是我们还没有可用于执行的子命令,接下来我们在 handfile.py 文件中实现一个子命令:

from mohand.hands import hand


@hand.expect(cmd='ls')
def hello(o):
    """Hello World!"""
    print('Hello World!')

注解

此处的 expect 为通过 mohand-plugin-expect 实现的一个 hand , 其主要用于 SSH , FTP 等一系列远程连接命令,此处用其执行 ls 仅作为演示, 其正确使用姿势可参考该扩展包中的具体文档说明

好,此时我们已经拥有了一个可执行的子命令,如何证明呢?再执行一次 mohand 看看吧:

$ mohand
Usage: mohand [OPTIONS] COMMAND [ARGS]...

  通用自动化处理工具

  详情参考 `GitHub <https://github.com/littlemo/mohand>`_

Options:
  --author   作者信息
  --version  版本信息
  --help     Show this message and exit.

Commands:
  hello  Hello World!

注意最后两行,现在已经多了一条子命令,其命令名即为我们之前实现的函数名,说明即该函数的 __doc__ 。 这里的 CLI 是基于 click 包实现的,故您基本感受不到其存在,相关逻辑已经被封装到 hand.expect 这个装饰器中了。

接下来就是见证奇迹的时刻了,我们来执行以下这个子命令看看:

$ mohand hello
ls
Hello World!
handfile.py

看我们实现的命令被执行了,打印出了通过装饰器传入的 cmdHello World! , 以及当前路径下的文件。

还记得我之前说过的循环递归查找 handfile.py 文件么?这个性质将很方便,比如将我们刚实现的 handfile.py 移到 ~ 下,这样我们在 ~ 目录下就都可以加载到这个文件中的子命令了。

插件实现

实现您自己的 hand 插件并不难,您可以参考GitHub上 mohand-plugin-expect 的实现。 您需要做的事情如下:

  1. 实现一个 mohand.hands.HandBase 的子类,用于注册您的 hand 装饰器、提供版本信息
  2. 实现一个 hand ,如 mohand-plugin-expect 中的 hand.expect
  3. setup.py 中添加一个 mohand.plugin.handentry_points

插件列表

您可以直接通过名称进行 pip 安装

mohand-plugin-expect
可用于自动化登录堡垒机完成跳转选择、账户密码输入等操作

异常模块

自定义异常类

exception mohand.exception.HandDuplicationOfNameError(name, msg=None, *args, **kwargs)[源代码]

基类:Exception

Hand注册重名错误

hands模块

class mohand.hands.HandBase[源代码]

基类:object

用于定义hand任务的基类

register(*args, **kwargs)[源代码]

注册hand任务

返回:返回待注册的hand函数
返回类型:function
version()[源代码]

版本信息

返回:返回包名,版本号元组
返回类型:(str, str)
class mohand.hands.HandDict(*args, **kwargs)[源代码]

基类:mohand.utils._AttributeDict

Hand扩展插件集合字典(单例)

mohand.hands.load_hands()[源代码]

加载hand扩展插件

返回:返回hand注册字典(单例)
返回类型:HandDict
mohand.hands.hand

所有已注册hand插件的装饰器集合实例,可通过 . 语法获取

加载模块

mohand.load_file.find_handfile(names=None)[源代码]

尝试定位 handfile 文件,明确指定或逐级搜索父路径

参数:names (str) – 可选,待查找的文件名,主要用于调试,默认使用终端传入的配置
返回:handfile 文件所在的绝对路径,默认为 None
返回类型:str
mohand.load_file.get_commands_from_module(imported)[源代码]

从传入的 imported 中获取所有 click.core.Command

参数:imported (module) – 导入的Python包
返回:包描述文档,仅含终端命令函数的对象字典
返回类型:(str, dict(str, object))
mohand.load_file.is_command_object(obj)[源代码]

验证传入的 obj 是否为一个 CLI 命令对象

参数:obj (object) – 待判断对象
返回:是否为 click.core.Command 命令对象
返回类型:bool
mohand.load_file.extract_commands(imported_vars)[源代码]

从传入的变量列表中提取命令( click.core.Command )对象

参数:imported_vars (dict_items) – 字典的键值条目列表
返回:判定为终端命令的对象字典
返回类型:dict(str, object)
mohand.load_file.load_handfile(path, importer=None)[源代码]

导入传入的 handfile 文件路径,并返回(docstring, callables)

也就是 handfile 包的 __doc__ 属性 (字符串) 和一个 {'name': callable} 的字典,包含所有通过 mohand 的 command 测试的 callables

参数:
  • path (str) – 待导入的 handfile 文件路径
  • importer (function) – 可选,包导入函数,默认为 __import__
返回:

包描述文档,仅含终端命令函数的对象字典

返回类型:

(str, dict(str, object))

声明模块

用以提供全局参数的声明

mohand.state.env = {'handfile': 'handfile.py', 'plugin_namespace': 'mohand.plugin.hand', 'version': OrderedDict([('mohand', '1.0.1')])}

全局环境字典,包含所有的配置 部分配置,如 version ,会在加载完扩展插件后进行再次填充

工具模块

用以提供全局通用工具方法&类

class mohand.utils._AttributeDict[源代码]

基类:dict

允许通过查找/赋值属性来操作键值的字典子类

举个栗子:

>>> m = _AttributeDict({'foo': 'bar'})
>>> m.foo
'bar'
>>> m.foo = 'not bar'
>>> m['foo']
'not bar'

_AttributeDict 对象还提供了一个 .first() 方法,起功能类似 .get() ,但接受多个键名作为列表多参,并返回第一个命中的键名的值 再举个栗子:

>>> m = _AttributeDict({'foo': 'bar', 'biz': 'baz'})
>>> m.first('wrong', 'incorrect', 'foo', 'biz')
'bar'
__getattr__(key)[源代码]

重载获取属性方法,使字典支持点语法取值

__setattr__(key, value)[源代码]

重载设置属性方法,使字典支持点语法赋值

first(*names)[源代码]

返回列表key在字典中第一个不为空的值

__weakref__

list of weak references to the object (if defined)

class mohand.utils.Singleton[源代码]

基类:type

单例类实现(线程安全)

注解

参考实现 singleton

static __new__(mcs, *args, **kwargs)[源代码]

元类msc通过__new__组建类对象,其中msc指Singleton

参数:
  • args (list) – 可以包含类构建所需要三元素, 类名父类命名空间, 其中命名空间中 __qualname__ 和函数的 __qualname__ 均含有 classname 做为前缀,在这里,如果想替换类名,需要把以上全部替换才可以。
  • kwargs (dict) – 可以自定义传递一些参数
返回:

返回类对象,通过super(Singleton, mcs).__new__此时已经组装好了类

返回类型:

class

__call__(*args, **kwargs)[源代码]

在调用时在线程锁中实现单例实例化

发布说明

v1.0.1 (2018-05-19 12:15:06)

Feature

  1. 增加了对 Python 2.X 的支持

v1.0.0 (2018-05-13 23:02:46)

Feature

  1. 实现 mohand 终端命令
  2. 实现对 handfile.py 文件的递归查找并加载
  3. 实现 mohand 子命令的注册
  4. 实现扩展插件的加载&注册
  5. 实现全局参数声明管理模块