Creating your first Ansible module

In this blog, I'll show you how to create your first Ansible module.













Of course, there is documentation available on Ansible.com, but it's hard to figure it out. Starting my first module on the basis of this introduction was given to me with great difficulty. That's why I created this walkthrough. New users deserve a better starting point.







This blog covers the following topics:







  • What is Ansible module
  • Setting up our build environment
  • Server API
  • Development of the module itself


What is Ansible module



If you are familiar with Ansible, you probably know that every task you perform in Ansible is an Ansible module. If not, now you know it.







, Ansible:







- name:    python-requests
  yum:
    name: python-requests
    state: latest
      
      





Ansible yum



.







. .







- , . , , - Ansible Galaxy, Ansible.







Ansible, API . , , , , , , .







Ansible, .









VSCode Ansible. - , , , -.







, Ansible API, .







API . — . .







, VSCode.







git clone https://gitlab.com/techforce1/ansible-module.git -b blog-setup
      
      





, blog-setup!







3 (.devcontainer



, ansible



, api-server



). API. cmd



, api-server



docker build -t api-server



( ).







. docker run -it -d -p 5000: 5000 api-server



. API-. http://localhost:5000, -.







, , Ansible, — VSCode. .devcontainer. VSCode , devcontainer



.







devcontainer



VSCode devcontainer



.







, , devcontainer



. Ansible devcontainer



. , Ansible , VSCode.







VSCode , , Reopen in container



.













.







API



API API, . * http://localhost:5000/. - , . API, http://localhost:5000/API/users.







, « », , API . , http://localhost:5000/API/get-token. . admin



initial_password



.







, , API







API , - , , - .







API curl



. curl, :







$ curl -u admin:initial_password http:/172.17.0.1:5000/API/get-token
{
  "duration": 600, 
  "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjA1NzE0NjI5Ljk1NzU4ODd9.8yDkOzN0umO2hN_D84KLV4Q4OuWzQoNf8puXWku9F14"
}
      
      





URL . , VSCode. , , IP- , API. .







, API, curl



:







$ curl -H 'Accept: application/json' -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNjA1NzE0NjI5Ljk1NzU4ODd9.8yDkOzN0umO2hN_D84KLV4Q4OuWzQoNf8puXWku9F14" http:/172.17.0.1:5000/API/users
{
  "Users": [
    {
      "admin": true, 
      "created": "Wed, 18 Nov 2020 14:41:31 GMT", 
      "email": "admin@api.local", 
      "id": 1, 
      "username": "admin"
    }
  ]
}
      
      





, API, GET



POST



:







{
  "Users": [
    {
      "username": "test",
      "email": "test@api.local", 
      "password": "password",
      "admin": true
    }
  ]
}
      
      





Ansible



. ansible/tasks/main.yml



. :







name: Add test user to API
our_api_module:
  name: test1
  state: present
  email: test1@test.local
  admin: False
      
      





our_api_module



, 4 . . , Ansible.







. , , playbook. , Ansible , .







. 44, , , Ansible . arguments_spec



, playbook. false. , Ansible.







def main():
    module = AnsibleModule(
        argument_spec=dict(
            state=dict(type='str', default='present',
                       choices=['absent', 'present']),
            name=dict(type='str', required=True),
            email=dict(type='str', required=True),
            admin=dict(type='bool', default=False),
            base_url=dict(requred=False, default=None),
            username=dict(requred=False, default=None),
            password=dict(requred=False, default=None, no_log=True),
        ),
        supports_check_mode=False,
    )
      
      





59 ApiModule



, , 23. init. API. , , getToken



37.







urls



Ansible 3 .







def getToken(self):
    url = "{baseUrl}/API/get-token".format(baseUrl=self.baseUrl)
    response = open_url(url, method="GET", url_username=self.username, url_password=self.password, validate_certs=self.verifySsl)
    return json.loads(response.read())['token']
      
      





. , . 69. , . 2 : , (state). , if



, :







if api.state == 'absent':
    if api.user_exist(api.name):
       # do something to delete user
elif api.state == 'present':
    if not api.user_exist(api.name):
       # do something to add user
      
      





. user_exist. ApiModule



:







def user_exist(self, name):
    url = "{baseUrl}/API/users".format(baseUrl=self.baseUrl)
    headers = {
        'Accept': "application/json",
        "Authorization": "Bearer {}" . format(self.token),
    }
    response = open_url(url, method="GET", headers=headers, validate_certs=self.verifySsl)
    results = json.loads(response.read())
    for user in results['Users']:
        if name == user['username']:
            return True
    return False
      
      





, , , . api endpoint /API/users , . True, False.







, , .







:







def user_add(self):
        url = "{baseUrl}/API/users".format(baseUrl=self.baseUrl)
        headers = {
            'Accept': "application/json",
            'Content-Type': "application/json",
            "Authorization": "Bearer {}" . format(self.token),
        }
        data = {
            'username': self.name,
            'email': self.email,
            'admin': self.admin,
            'password': self.password
        }
        json_data = json.dumps(data, ensure_ascii=False)
        try:
            open_url(url, method="POST", headers=headers, data=json_data, validate_certs=self.verifySsl)
            return True
        except:
            return False
      
      





. user_add . : HTTP



POST



DELETE



, , URL-.

, URL- :







url = "{baseUrl}/API/users/{username}".format(baseUrl=self.baseUrl, username=self.name)
      
      





if.







, playbook (tasks/main.yml



) :







- name: Add test2 user to API
  our_api_module:
    name: test2
    state: present
    email: test2@test.local
    admin: False
    password: "test2test2"

- name: Delete test1 user to API
  our_api_module:
    name: test1
    state: absent
    email: test1@test.local
    admin: False
    password: "test3test3"
      
      





playbook, , 2 , 1 , .









Congratulations! You now have a functional Ansible module. Have you tried adding a user change feature? Also consider adding error handling to this module as if you were doing it in a real module to take care of errors and return them correctly to Ansible.







Not that hard, right? This is not true. Simple Python code is essential.

If you want to see the full end result, take a look at the branch result blog.














All Articles