什么是 ansible module?
Ansible modules 是一种使用python写成的一些功能块,可以在yaml文件中调用,也就是playbook中可以调用的模块,例如常见的模块 copy , debug
Ansible 本身就提供了很多很多的模块 地址
什么时候和什么情况下我们会需要自己写module?
绝大多数情况下,我们不需要创建自己的模块module,然而,有些时候官方提供的模块无法提供我们需要的功能,我们只能通过自己的模块来处理一些代码
通常,module和哪些提供了丰富api接口的服务对接起来会更加流畅,例如Github 或者 Pivotal , 当然我们可以通过url来和这些服务进行交互,但是,有时候这个方法太不cool 了,我们要牛B的方法
创建一个简单的例子
幸运的是创建一个ansible module是非常容易的
我们将创建一个简单的例子:从github上删除或者创建一个repo
我们先来一个简单的输出的例子
创建如下文件:
1 2 3 4 5 |
play.yml [library] |_ github_repo.py |_ test_github_repo.py |
library/github_repo.py
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/usr/bin/python from ansible.module_utils.basic import * def main(): module = AnsibleModule(argument_spec={}) response = {"hello": "world"} module.exit_json(changed=False, meta=response) if __name__ == '__main__': main() |
Notes
main()
是你的程序入口#!/usr/bin/python
is 这个是必须的,就和些shell脚本一样AnsibleModule
是从ansible.module_utils.basic 总导入的,import * 必须是导入全部的
*
AnsibleModule
有一些已经写好的方法让我们调用,例如exit_json
play.yml
1 2 3 4 5 6 7 |
- hosts: localhost tasks: - name: Test that my module works github_repo: register: result - debug: var=result |
上边是一个非常简单的例子,调用了我们的module: github_repo, 并注册到一个变量result中,并打印这个变量的值
我们来跑一下:
1 2 |
$ ansible-playbook play.yml |
(note: 命令中未指定 -i , 所以旧的版本可能需要你手动指定一个host文件-i).
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
PLAY *************************************************************************** TASK [setup] ******************************************************************* ok: [localhost] TASK [Test that my module works] *********************************************** ok: [localhost] TASK [debug] ******************************************************************* ok: [localhost] => { "result": { "changed": false, "meta": { "hello": "world" } } } PLAY RECAP ********************************************************************* localhost : ok=3 changed=0 unreachable=0 failed=0 |
非常漂亮,运行成功了
加入一些变量
当然,一个好的模块一定会有输入的,因为我们要创建repo,所以我们直接拿来一些代码复用 (其实就是创建一个repo需要的参数)
1 2 3 4 5 6 7 8 9 10 11 |
- name: Create a github Repo github_repo: github_auth_key: "..." name: "Hello-World", description: "This is your first repository", private: yes has_issues: no has_wiki: no has_downloads: no state: present register: result |
好,然后我们在代码中添加如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def main(): fields = { "github_auth_key": {"required": True, "type": "str"}, "name": {"required": True, "type": "str" }, "description": {"required": False, "type": "str"}, "private": {"default": False, "type": "bool" }, "has_issues": {"default": True, "type": "bool" }, "has_wiki": {"default": True, "type": "bool" }, "has_downloads": {"default": True, "type": "bool" }, "state": { "default": "present", "choices": ['present', 'absent'], "type": 'str' }, } module = AnsibleModule(argument_spec=fields) module.exit_json(changed=False, meta=module.params) |
注意:
- 输入的变量是字典形式错在的
- 参数类型: str, bool, dict, list, …
- 我们可以指定一些default,required之类的.
上边的例子,我们什么都没有做,只是把传递的值有传递到了exit_json
如果这时候跑一下的话,输出结果将会是我们传递的字典内容
好的,我们现在将一下如何处理这些传递进module的参数
在pythyon的头部我们添加两个函数(函数名称随意,但是要和下变的对应)
1 2 3 4 5 6 7 8 |
def github_repo_present(data): has_changed = False meta = {"present": "not yet implemented"} return (has_changed, meta) def github_repo_absent(data=None): has_changed = False meta = {"absent": "not yet implemented"} |
These are the functions that will actually do the work. Back in our main()
method, let’s add some code to call the desired function:
这两个方法是真正用来处理数据的地方,让我们在main还数中添加如何调用这两个函数的部分
1 2 3 4 5 6 7 8 9 |
def main(): fields = {..} choice_map = { "present": github_repo_present, "absent": github_repo_absent, } module = AnsibleModule(argument_spec=fields) has_changed, result = choice_map.get(module.params['state'])(module.params) module.exit_json(changed=has_changed, meta=result) |
We use a map to map the state
provided to a function that will handle that state.
我们通过map来控制这两个函数
最后的事情就是,让我们的函数真正具有创建和删除的功能(就是调用github的接口,原理还是通过url来访问接口)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
def github_repo_present(data): api_key = data['github_auth_key'] del data['state'] del data['github_auth_key'] headers = { "Authorization": "token {}" . format(api_key) } url = "{}{}" . format(api_url, '/user/repos') result = requests.post(url, json.dumps(data), headers=headers) if result.status_code == 201: return False, True, result.json() if result.status_code == 422: return False, False, result.json() # default: something went wrong meta = {"status": result.status_code, 'response': result.json()} return True, False, meta def github_repo_absent(data=None): headers = { "Authorization": "token {}" . format(data['github_auth_key']) } url = "{}/repos/{}/{}" . format(api_url, "toast38coza", data['name']) result = requests.delete(url, headers=headers) if result.status_code == 204: return False, True, {"status": "SUCCESS"} if result.status_code == 404: result = {"status": result.status_code, "data": result.json()} return False, False, result else: result = {"status": result.status_code, "data": result.json()} return True, False, result |
我们再添加一块来删除刚刚创建的repo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
- hosts: localhost vars: - github_token: "..." tasks: - name: Create a github Repo github_repo: github_auth_key: "{{github_token}}" name: "Hello-World" description: "This is your first repository" private: yes has_issues: no has_wiki: no has_downloads: no - name: Delete that repo github_repo: github_auth_key: "{{github_token}}" name: "Hello-World" state: absent |
最后一步,我们来添加一些主事,和测试的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
DOCUMENTATION = ''' --- module: github_repo short_description: Manage your repos on Github ''' EXAMPLES = ''' - name: Create a github Repo github_repo: github_auth_key: "..." name: "Hello-World" description: "This is your first repository" private: yes has_issues: no has_wiki: no has_downloads: no register: result - name: Delete that repo github_repo: github_auth_key: "..." name: "Hello-World" state: absent register: result ''' |
结论:
你已经成功的创建了你第一个module.
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
#!/usr/bin/python DOCUMENTATION = ''' --- module: github_repo short_description: Manage your repos on Github ''' EXAMPLES = ''' - name: Create a github Repo github_repo: github_auth_key: "..." name: "Hello-World" description: "This is your first repository" private: yes has_issues: no has_wiki: no has_downloads: no register: result - name: Delete that repo github_repo: github_auth_key: "..." name: "Hello-World" state: absent register: result ''' from ansible.module_utils.basic import * import requests api_url = "https://api.github.com" def github_repo_present(data): api_key = data['github_auth_key'] del data['state'] del data['github_auth_key'] headers = { "Authorization": "token {}" . format(api_key) } url = "{}{}" . format(api_url, '/user/repos') result = requests.post(url, json.dumps(data), headers=headers) if result.status_code == 201: return False, True, result.json() if result.status_code == 422: return False, False, result.json() # default: something went wrong meta = {"status": result.status_code, 'response': result.json()} return True, False, meta def github_repo_absent(data=None): headers = { "Authorization": "token {}" . format(data['github_auth_key']) } url = "{}/repos/{}/{}" . format(api_url, "toast38coza", data['name']) result = requests.delete(url, headers=headers) if result.status_code == 204: return False, True, {"status": "SUCCESS"} if result.status_code == 404: result = {"status": result.status_code, "data": result.json()} return False, False, result else: result = {"status": result.status_code, "data": result.json()} return True, False, result def main(): fields = { "github_auth_key": {"required": True, "type": "str"}, "name": {"required": True, "type": "str"}, "description": {"required": False, "type": "str"}, "private": {"default": False, "type": "bool"}, "has_issues": {"default": True, "type": "bool"}, "has_wiki": {"default": True, "type": "bool"}, "has_downloads": {"default": True, "type": "bool"}, "state": { "default": "present", "choices": ['present', 'absent'], "type": 'str' }, } choice_map = { "present": github_repo_present, "absent": github_repo_absent, } module = AnsibleModule(argument_spec=fields) is_error, has_changed, result = choice_map.get( module.params['state'])(module.params) if not is_error: module.exit_json(changed=has_changed, meta=result) else: module.fail_json(msg="Error deleting repo", meta=result) if __name__ == '__main__': main() |
翻译原文地址:http://blog.toast38coza.me/custom-ansible-module-hello-world/
Latest posts by Zhiming Zhang (see all)
- aws eks node 自动化扩展工具 Karpenter - 8月 10, 2022
- ReplicationController and ReplicaSet in Kubernetes - 12月 20, 2021
- public key fingerprint - 5月 27, 2021
王 2017/03/12 10:11
不错~~~~
kevin 2018/08/05 11:24
博主,您好~ 刚接触ansible,我想请教一下,如何在ansible里面导入已经开发好的第三方模块lib,忘不吝赐教~
Zhiming Zhang 博主 2018/08/06 07:28
@ 第三方和你自己写的其实是一样的,最简单的方法应该就是和本文写的一样放到指定目录下