mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-26 13:56:09 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			301 lines
		
	
	
	
		
			9.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			301 lines
		
	
	
	
		
			9.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/python
 | |
| # -*- coding: utf-8 -*-
 | |
| # Copyright 2015 WP Engine, Inc. All rights reserved.
 | |
| # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
 | |
| # SPDX-License-Identifier: GPL-3.0-or-later
 | |
| 
 | |
| from __future__ import absolute_import, division, print_function
 | |
| __metaclass__ = type
 | |
| 
 | |
| 
 | |
| DOCUMENTATION = '''
 | |
| ---
 | |
| module: znode
 | |
| short_description: Create, delete, retrieve, and update znodes using ZooKeeper
 | |
| description:
 | |
|     - Create, delete, retrieve, and update znodes using ZooKeeper.
 | |
| attributes:
 | |
|     check_mode:
 | |
|         support: none
 | |
|     diff_mode:
 | |
|         support: none
 | |
| extends_documentation_fragment:
 | |
|     - community.general.attributes
 | |
| options:
 | |
|     hosts:
 | |
|         description:
 | |
|             - A list of ZooKeeper servers (format '[server]:[port]').
 | |
|         required: true
 | |
|         type: str
 | |
|     name:
 | |
|         description:
 | |
|             - The path of the znode.
 | |
|         required: true
 | |
|         type: str
 | |
|     value:
 | |
|         description:
 | |
|             - The value assigned to the znode.
 | |
|         type: str
 | |
|     op:
 | |
|         description:
 | |
|             - An operation to perform. Mutually exclusive with state.
 | |
|         choices: [ get, wait, list ]
 | |
|         type: str
 | |
|     state:
 | |
|         description:
 | |
|             - The state to enforce. Mutually exclusive with op.
 | |
|         choices: [ present, absent ]
 | |
|         type: str
 | |
|     timeout:
 | |
|         description:
 | |
|             - The amount of time to wait for a node to appear.
 | |
|         default: 300
 | |
|         type: int
 | |
|     recursive:
 | |
|         description:
 | |
|             - Recursively delete node and all its children.
 | |
|         type: bool
 | |
|         default: false
 | |
|     auth_scheme:
 | |
|         description:
 | |
|             - 'Authentication scheme.'
 | |
|         choices: [ digest, sasl ]
 | |
|         type: str
 | |
|         default: "digest"
 | |
|         required: false
 | |
|         version_added: 5.8.0
 | |
|     auth_credential:
 | |
|         description:
 | |
|             - The authentication credential value. Depends on O(auth_scheme).
 | |
|             - The format for O(auth_scheme=digest) is C(user:password),
 | |
|               and the format for O(auth_scheme=sasl) is C(user:password).
 | |
|         type: str
 | |
|         required: false
 | |
|         version_added: 5.8.0
 | |
|     use_tls:
 | |
|         description:
 | |
|             - Using TLS/SSL or not.
 | |
|         type: bool
 | |
|         default: false
 | |
|         required: false
 | |
|         version_added: '6.5.0'
 | |
| requirements:
 | |
|     - kazoo >= 2.1
 | |
|     - python >= 2.6
 | |
| author: "Trey Perry (@treyperry)"
 | |
| '''
 | |
| 
 | |
| EXAMPLES = """
 | |
| - name: Creating or updating a znode with a given value
 | |
|   community.general.znode:
 | |
|     hosts: 'localhost:2181'
 | |
|     name: /mypath
 | |
|     value: myvalue
 | |
|     state: present
 | |
| 
 | |
| - name: Getting the value and stat structure for a znode
 | |
|   community.general.znode:
 | |
|     hosts: 'localhost:2181'
 | |
|     name: /mypath
 | |
|     op: get
 | |
| 
 | |
| - name: Getting the value and stat structure for a znode using digest authentication
 | |
|   community.general.znode:
 | |
|     hosts: 'localhost:2181'
 | |
|     auth_credential: 'user1:s3cr3t'
 | |
|     name: /secretmypath
 | |
|     op: get
 | |
| 
 | |
| - name: Listing a particular znode's children
 | |
|   community.general.znode:
 | |
|     hosts: 'localhost:2181'
 | |
|     name: /zookeeper
 | |
|     op: list
 | |
| 
 | |
| - name: Waiting 20 seconds for a znode to appear at path /mypath
 | |
|   community.general.znode:
 | |
|     hosts: 'localhost:2181'
 | |
|     name: /mypath
 | |
|     op: wait
 | |
|     timeout: 20
 | |
| 
 | |
| - name: Deleting a znode at path /mypath
 | |
|   community.general.znode:
 | |
|     hosts: 'localhost:2181'
 | |
|     name: /mypath
 | |
|     state: absent
 | |
| 
 | |
| - name: Creating or updating a znode with a given value on a remote Zookeeper
 | |
|   community.general.znode:
 | |
|     hosts: 'my-zookeeper-node:2181'
 | |
|     name: /mypath
 | |
|     value: myvalue
 | |
|     state: present
 | |
|   delegate_to: 127.0.0.1
 | |
| """
 | |
| 
 | |
| import time
 | |
| import traceback
 | |
| 
 | |
| KAZOO_IMP_ERR = None
 | |
| try:
 | |
|     from kazoo.client import KazooClient
 | |
|     from kazoo.handlers.threading import KazooTimeoutError
 | |
|     KAZOO_INSTALLED = True
 | |
| except ImportError:
 | |
|     KAZOO_IMP_ERR = traceback.format_exc()
 | |
|     KAZOO_INSTALLED = False
 | |
| 
 | |
| from ansible.module_utils.basic import AnsibleModule, missing_required_lib
 | |
| from ansible.module_utils.common.text.converters import to_bytes
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     module = AnsibleModule(
 | |
|         argument_spec=dict(
 | |
|             hosts=dict(required=True, type='str'),
 | |
|             name=dict(required=True, type='str'),
 | |
|             value=dict(type='str'),
 | |
|             op=dict(choices=['get', 'wait', 'list']),
 | |
|             state=dict(choices=['present', 'absent']),
 | |
|             timeout=dict(default=300, type='int'),
 | |
|             recursive=dict(default=False, type='bool'),
 | |
|             auth_scheme=dict(default='digest', choices=['digest', 'sasl']),
 | |
|             auth_credential=dict(type='str', no_log=True),
 | |
|             use_tls=dict(default=False, type='bool'),
 | |
|         ),
 | |
|         supports_check_mode=False
 | |
|     )
 | |
| 
 | |
|     if not KAZOO_INSTALLED:
 | |
|         module.fail_json(msg=missing_required_lib('kazoo >= 2.1'), exception=KAZOO_IMP_ERR)
 | |
| 
 | |
|     check = check_params(module.params)
 | |
|     if not check['success']:
 | |
|         module.fail_json(msg=check['msg'])
 | |
| 
 | |
|     zoo = KazooCommandProxy(module)
 | |
|     try:
 | |
|         zoo.start()
 | |
|     except KazooTimeoutError:
 | |
|         module.fail_json(msg='The connection to the ZooKeeper ensemble timed out.')
 | |
| 
 | |
|     command_dict = {
 | |
|         'op': {
 | |
|             'get': zoo.get,
 | |
|             'list': zoo.list,
 | |
|             'wait': zoo.wait
 | |
|         },
 | |
|         'state': {
 | |
|             'present': zoo.present,
 | |
|             'absent': zoo.absent
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     command_type = 'op' if 'op' in module.params and module.params['op'] is not None else 'state'
 | |
|     method = module.params[command_type]
 | |
|     result, result_dict = command_dict[command_type][method]()
 | |
|     zoo.shutdown()
 | |
| 
 | |
|     if result:
 | |
|         module.exit_json(**result_dict)
 | |
|     else:
 | |
|         module.fail_json(**result_dict)
 | |
| 
 | |
| 
 | |
| def check_params(params):
 | |
|     if not params['state'] and not params['op']:
 | |
|         return {'success': False, 'msg': 'Please define an operation (op) or a state.'}
 | |
| 
 | |
|     if params['state'] and params['op']:
 | |
|         return {'success': False, 'msg': 'Please choose an operation (op) or a state, but not both.'}
 | |
| 
 | |
|     return {'success': True}
 | |
| 
 | |
| 
 | |
| class KazooCommandProxy():
 | |
|     def __init__(self, module):
 | |
|         self.module = module
 | |
|         self.zk = KazooClient(module.params['hosts'], use_ssl=module.params['use_tls'])
 | |
| 
 | |
|     def absent(self):
 | |
|         return self._absent(self.module.params['name'])
 | |
| 
 | |
|     def exists(self, znode):
 | |
|         return self.zk.exists(znode)
 | |
| 
 | |
|     def list(self):
 | |
|         children = self.zk.get_children(self.module.params['name'])
 | |
|         return True, {'count': len(children), 'items': children, 'msg': 'Retrieved znodes in path.',
 | |
|                       'znode': self.module.params['name']}
 | |
| 
 | |
|     def present(self):
 | |
|         return self._present(self.module.params['name'], self.module.params['value'])
 | |
| 
 | |
|     def get(self):
 | |
|         return self._get(self.module.params['name'])
 | |
| 
 | |
|     def shutdown(self):
 | |
|         self.zk.stop()
 | |
|         self.zk.close()
 | |
| 
 | |
|     def start(self):
 | |
|         self.zk.start()
 | |
|         if self.module.params['auth_credential']:
 | |
|             self.zk.add_auth(self.module.params['auth_scheme'], self.module.params['auth_credential'])
 | |
| 
 | |
|     def wait(self):
 | |
|         return self._wait(self.module.params['name'], self.module.params['timeout'])
 | |
| 
 | |
|     def _absent(self, znode):
 | |
|         if self.exists(znode):
 | |
|             self.zk.delete(znode, recursive=self.module.params['recursive'])
 | |
|             return True, {'changed': True, 'msg': 'The znode was deleted.'}
 | |
|         else:
 | |
|             return True, {'changed': False, 'msg': 'The znode does not exist.'}
 | |
| 
 | |
|     def _get(self, path):
 | |
|         if self.exists(path):
 | |
|             value, zstat = self.zk.get(path)
 | |
|             stat_dict = {}
 | |
|             for i in dir(zstat):
 | |
|                 if not i.startswith('_'):
 | |
|                     attr = getattr(zstat, i)
 | |
|                     if isinstance(attr, (int, str)):
 | |
|                         stat_dict[i] = attr
 | |
|             result = True, {'msg': 'The node was retrieved.', 'znode': path, 'value': value,
 | |
|                             'stat': stat_dict}
 | |
|         else:
 | |
|             result = False, {'msg': 'The requested node does not exist.'}
 | |
| 
 | |
|         return result
 | |
| 
 | |
|     def _present(self, path, value):
 | |
|         if self.exists(path):
 | |
|             (current_value, zstat) = self.zk.get(path)
 | |
|             if value != current_value:
 | |
|                 self.zk.set(path, to_bytes(value))
 | |
|                 return True, {'changed': True, 'msg': 'Updated the znode value.', 'znode': path,
 | |
|                               'value': value}
 | |
|             else:
 | |
|                 return True, {'changed': False, 'msg': 'No changes were necessary.', 'znode': path, 'value': value}
 | |
|         else:
 | |
|             self.zk.create(path, to_bytes(value), makepath=True)
 | |
|             return True, {'changed': True, 'msg': 'Created a new znode.', 'znode': path, 'value': value}
 | |
| 
 | |
|     def _wait(self, path, timeout, interval=5):
 | |
|         lim = time.time() + timeout
 | |
| 
 | |
|         while time.time() < lim:
 | |
|             if self.exists(path):
 | |
|                 return True, {'msg': 'The node appeared before the configured timeout.',
 | |
|                               'znode': path, 'timeout': timeout}
 | |
|             else:
 | |
|                 time.sleep(interval)
 | |
| 
 | |
|         return False, {'msg': 'The node did not appear before the operation timed out.', 'timeout': timeout,
 | |
|                        'znode': path}
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |