diff options
Diffstat (limited to 'ansible_collections/community/general/plugins/modules/etcd3.py')
-rw-r--r-- | ansible_collections/community/general/plugins/modules/etcd3.py | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/ansible_collections/community/general/plugins/modules/etcd3.py b/ansible_collections/community/general/plugins/modules/etcd3.py new file mode 100644 index 000000000..9cd027406 --- /dev/null +++ b/ansible_collections/community/general/plugins/modules/etcd3.py @@ -0,0 +1,261 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, Jean-Philippe Evrard <jean-philippe@evrard.me> +# 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: etcd3 +short_description: Set or delete key value pairs from an etcd3 cluster +requirements: + - etcd3 +description: + - Sets or deletes values in etcd3 cluster using its v3 api. + - Needs python etcd3 lib to work +extends_documentation_fragment: + - community.general.attributes +attributes: + check_mode: + support: full + diff_mode: + support: none +options: + key: + type: str + description: + - the key where the information is stored in the cluster + required: true + value: + type: str + description: + - the information stored + required: true + host: + type: str + description: + - the IP address of the cluster + default: 'localhost' + port: + type: int + description: + - the port number used to connect to the cluster + default: 2379 + state: + type: str + description: + - the state of the value for the key. + - can be present or absent + required: true + choices: [ present, absent ] + user: + type: str + description: + - The etcd user to authenticate with. + password: + type: str + description: + - The password to use for authentication. + - Required if I(user) is defined. + ca_cert: + type: path + description: + - The Certificate Authority to use to verify the etcd host. + - Required if I(client_cert) and I(client_key) are defined. + client_cert: + type: path + description: + - PEM formatted certificate chain file to be used for SSL client authentication. + - Required if I(client_key) is defined. + client_key: + type: path + description: + - PEM formatted file that contains your private key to be used for SSL client authentication. + - Required if I(client_cert) is defined. + timeout: + type: int + description: + - The socket level timeout in seconds. +author: + - Jean-Philippe Evrard (@evrardjp) + - Victor Fauth (@vfauth) +''' + +EXAMPLES = """ +- name: Store a value "bar" under the key "foo" for a cluster located "http://localhost:2379" + community.general.etcd3: + key: "foo" + value: "baz3" + host: "localhost" + port: 2379 + state: "present" + +- name: Authenticate using user/password combination with a timeout of 10 seconds + community.general.etcd3: + key: "foo" + value: "baz3" + state: "present" + user: "someone" + password: "password123" + timeout: 10 + +- name: Authenticate using TLS certificates + community.general.etcd3: + key: "foo" + value: "baz3" + state: "present" + ca_cert: "/etc/ssl/certs/CA_CERT.pem" + client_cert: "/etc/ssl/certs/cert.crt" + client_key: "/etc/ssl/private/key.pem" +""" + +RETURN = ''' +key: + description: The key that was queried + returned: always + type: str +old_value: + description: The previous value in the cluster + returned: always + type: str +''' + +import traceback + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils.common.text.converters import to_native + + +try: + import etcd3 + HAS_ETCD = True + ETCD_IMP_ERR = None +except ImportError: + ETCD_IMP_ERR = traceback.format_exc() + HAS_ETCD = False + + +def run_module(): + # define the available arguments/parameters that a user can pass to + # the module + module_args = dict( + key=dict(type='str', required=True, no_log=False), + value=dict(type='str', required=True), + host=dict(type='str', default='localhost'), + port=dict(type='int', default=2379), + state=dict(type='str', required=True, choices=['present', 'absent']), + user=dict(type='str'), + password=dict(type='str', no_log=True), + ca_cert=dict(type='path'), + client_cert=dict(type='path'), + client_key=dict(type='path'), + timeout=dict(type='int'), + ) + + # seed the result dict in the object + # we primarily care about changed and state + # change is if this module effectively modified the target + # state will include any data that you want your module to pass back + # for consumption, for example, in a subsequent task + result = dict( + changed=False, + ) + + # the AnsibleModule object will be our abstraction working with Ansible + # this includes instantiation, a couple of common attr would be the + # args/params passed to the execution, as well as if the module + # supports check mode + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_together=[['client_cert', 'client_key'], ['user', 'password']], + ) + + # It is possible to set `ca_cert` to verify the server identity without + # setting `client_cert` or `client_key` to authenticate the client + # so required_together is enough + # Due to `required_together=[['client_cert', 'client_key']]`, checking the presence + # of either `client_cert` or `client_key` is enough + if module.params['ca_cert'] is None and module.params['client_cert'] is not None: + module.fail_json(msg="The 'ca_cert' parameter must be defined when 'client_cert' and 'client_key' are present.") + + result['key'] = module.params.get('key') + module.params['cert_cert'] = module.params.pop('client_cert') + module.params['cert_key'] = module.params.pop('client_key') + + if not HAS_ETCD: + module.fail_json(msg=missing_required_lib('etcd3'), exception=ETCD_IMP_ERR) + + allowed_keys = ['host', 'port', 'ca_cert', 'cert_cert', 'cert_key', + 'timeout', 'user', 'password'] + # TODO(evrardjp): Move this back to a dict comprehension when python 2.7 is + # the minimum supported version + # client_params = {key: value for key, value in module.params.items() if key in allowed_keys} + client_params = dict() + for key, value in module.params.items(): + if key in allowed_keys: + client_params[key] = value + try: + etcd = etcd3.client(**client_params) + except Exception as exp: + module.fail_json(msg='Cannot connect to etcd cluster: %s' % (to_native(exp)), + exception=traceback.format_exc()) + try: + cluster_value = etcd.get(module.params['key']) + except Exception as exp: + module.fail_json(msg='Cannot reach data: %s' % (to_native(exp)), + exception=traceback.format_exc()) + + # Make the cluster_value[0] a string for string comparisons + result['old_value'] = to_native(cluster_value[0]) + + if module.params['state'] == 'absent': + if cluster_value[0] is not None: + if module.check_mode: + result['changed'] = True + else: + try: + etcd.delete(module.params['key']) + except Exception as exp: + module.fail_json(msg='Cannot delete %s: %s' % (module.params['key'], to_native(exp)), + exception=traceback.format_exc()) + else: + result['changed'] = True + elif module.params['state'] == 'present': + if result['old_value'] != module.params['value']: + if module.check_mode: + result['changed'] = True + else: + try: + etcd.put(module.params['key'], module.params['value']) + except Exception as exp: + module.fail_json(msg='Cannot add or edit key %s: %s' % (module.params['key'], to_native(exp)), + exception=traceback.format_exc()) + else: + result['changed'] = True + else: + module.fail_json(msg="State not recognized") + + # manipulate or modify the state as needed (this is going to be the + # part where your module will do what it needs to do) + + # during the execution of the module, if there is an exception or a + # conditional state that effectively causes a failure, run + # AnsibleModule.fail_json() to pass in the message and the result + + # in the event of a successful module execution, you will want to + # simple AnsibleModule.exit_json(), passing the key/value results + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() |