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.general
def hello():
    """Hello World!"""
    print('Hello World!')

好,此时我们已经拥有了一个可执行的子命令,如何证明呢?再执行一次 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.general 这个装饰器中了。

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

$ mohand hello
Hello World!

看我们实现的命令被执行了,打印出了 Hello World!

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

注解

另外, mohand 还支持对 handfile 模块的加载,即您可以实现一个以 handfile 命名的 Python 包,并在其 __init__.py 中将想要被加载的命令导入,这样 mohand 就会对其进行加载处理,并注册为子命令。

高级用法

由于 mohand 的终端命令方案使用的是 click ,故您可以在实现子命令时灵活的增加命令参数, 继续以上述 hello 命令为例,为其添加一个终端参数:

@hand._click.option('--who', '-w', default='World', help='默认值为 `World` ')
@hand.general
def hello(who):
    """Hello World!"""
    print('Hello {}!'.format(who))

我们为 hello 命令增加了一个 who 的可选参数,该参数默认值为 World ,调用看看:

$ mohand hello --help
Usage: mohand hello [OPTIONS]

  Hello World!

Options:
  -w, --who TEXT  默认值为 `World`
  --help          Show this message and exit.

$ mohand hello
Hello World!

$ mohand hello -w MoHand
Hello MoHand!

我们通过传参定制了输出的结果,您可以通过这个方法获得一个强大的命令扩展系统。

插件实现

实现您自己的 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
可用于自动化登录堡垒机完成跳转选择、账户密码输入等操作
mohand-plugin-otp
可用于提供定制化的一次性密码生成服务

装饰模块

此模块提供随包附带的基础 hand ,用以实现最基础功能,即将 handfile 中的目标方法装饰为一个 mohand 子命令的 general 装饰器,更多扩展 hand 您可以通过安装扩展包来获得

mohand.decorator.general(*dargs, **dkwargs)[源代码]

将被装饰函数封装为一个 click.core.Command 类, 此装饰器并不提供额外的复杂功能,仅提供将被装饰方法注册为一个 mohand 子命令的功能

该装饰器作为一个一般装饰器使用(如: @hand.general

注解

该装饰器会在插件系统加载外部插件前辈注册到 hands.hand 中。

此处的 general 装饰器同时兼容有参和无参调用方式

参数:log_level (int) – 当前子命令的日志输出等级,默认为: logging.INFO
返回:被封装后的函数
返回类型:function

异常模块

自定义异常类

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.MohandDict

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', 'plugin_namespace': 'mohand.plugin.hand', 'version': {'mohand': '1.2.0a1'}}

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

工具模块

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

class mohand.utils.MohandDict[源代码]

基类:dict

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

举个栗子:

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

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

>>> m = MohandDict({'foo': 'bar', 'biz': 'baz'})
>>> m.first('wrong', 'incorrect', 'foo', 'biz')
'bar'
__getattribute__(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.1.1 (2018-09-06 10:30:30)

Bugfix

  1. 修复当未安装插件包时,加载插件逻辑执行失败的 Bug

v1.1.0 (2018-06-03 20:05:43)

Feature

  1. 实现默认支持的 hand 装饰器 general ,用以提供基础的子命令注册功能
  2. 增强对 Python2.X 的兼容性
  3. 去除对子插件 mohand-plugin-expect 的依赖

Bugfix

  1. 修复对 handfile 模块的加载失败 Bug

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. 实现全局参数声明管理模块