From d74d416bebdf8b5b405b94327a147394aa3c28ef Mon Sep 17 00:00:00 2001 From: Abhijeet Kasurde Date: Sat, 17 Feb 2018 22:00:53 +0530 Subject: [PATCH] Refactor DigitalOcean modules (#35934) - Updated documentation fragment - Updated Argument spec - Use common REST class for API calls Signed-off-by: Abhijeet Kasurde --- lib/ansible/module_utils/digital_ocean.py | 31 +++++++---- .../digital_ocean_block_storage.py | 38 ++++++-------- .../digital_ocean_certificate.py | 31 +++++------ .../digital_ocean/digital_ocean_domain.py | 28 ++++------ .../digital_ocean_floating_ip_facts.py | 52 ++++++------------- .../digital_ocean_sshkey_facts.py | 28 ++-------- .../cloud/digital_ocean/digital_ocean_tag.py | 26 ++++------ .../module_docs_fragments/digital_ocean.py | 26 ++++++++++ 8 files changed, 116 insertions(+), 144 deletions(-) create mode 100644 lib/ansible/utils/module_docs_fragments/digital_ocean.py diff --git a/lib/ansible/module_utils/digital_ocean.py b/lib/ansible/module_utils/digital_ocean.py index 2cf1443d9ad..8304e064526 100644 --- a/lib/ansible/module_utils/digital_ocean.py +++ b/lib/ansible/module_utils/digital_ocean.py @@ -30,6 +30,7 @@ import json import os from ansible.module_utils.urls import fetch_url from ansible.module_utils._text import to_text +from ansible.module_utils.basic import env_fallback class Response(object): @@ -62,10 +63,16 @@ class DigitalOceanHelper: self.module = module self.baseurl = 'https://api.digitalocean.com/v2' self.oauth_token = None - self.get_do_oauth_token() self.headers = {'Authorization': 'Bearer {0}'.format(self.oauth_token), 'Content-type': 'application/json'} + # Check if api_token is valid or not + response = self.get('account') + if response.status_code == 401: + module.fail_json(msg='Failed to login using API token, please verify validity of API token.') + + self.timeout = module.params.get('timeout', 30) + def _url_builder(self, path): if path[0] == '/': path = path[1:] @@ -75,7 +82,7 @@ class DigitalOceanHelper: url = self._url_builder(path) data = self.module.jsonify(data) - resp, info = fetch_url(self.module, url, data=data, headers=self.headers, method=method) + resp, info = fetch_url(self.module, url, data=data, headers=self.headers, method=method, timeout=self.timeout) return Response(resp, info) @@ -91,11 +98,15 @@ class DigitalOceanHelper: def delete(self, path, data=None): return self.send('DELETE', path, data) - def get_do_oauth_token(self): - self.oauth_token = self.module.params.get('oauth_token') or \ - self.module.params.get('api_token') or \ - os.environ.get('DO_API_TOKEN') or \ - os.environ.get('DO_API_KEY') or \ - os.environ.get('OAUTH_TOKEN') - if self.oauth_token is None: - self.module.fail_json(msg='Unable to load api key: oauth_token') + @staticmethod + def digital_ocean_argument_spec(): + return dict( + validate_certs=dict(type='bool', required=False, default=True), + oauth_token=dict( + no_log=True, + # Support environment variable for DigitalOcean OAuth Token + fallback=(env_fallback, ['DO_API_TOKEN', 'DO_API_KEY', 'DO_OAUTH_TOKEN', 'OAUTH_TOKEN']), + required=False, + ), + timeout=dict(type='int', default=30), + ) diff --git a/lib/ansible/modules/cloud/digital_ocean/digital_ocean_block_storage.py b/lib/ansible/modules/cloud/digital_ocean/digital_ocean_block_storage.py index d2c8264e85d..4c5f27079bc 100644 --- a/lib/ansible/modules/cloud/digital_ocean/digital_ocean_block_storage.py +++ b/lib/ansible/modules/cloud/digital_ocean/digital_ocean_block_storage.py @@ -31,10 +31,6 @@ options: - Indicate desired state of the target. choices: ['present', 'absent'] required: true - api_token: - description: - - DigitalOcean api token. - required: true block_size: description: - The size of the Block Storage volume in gigabytes. Required when command=create and state=present. If snapshot_id is included, this will be ignored. @@ -56,11 +52,7 @@ options: droplet_id: description: - The droplet id you want to operate on. Required when command=attach. - timeout: - description: - - The timeout in seconds used for polling DigitalOcean's API. - default: 10 - +extends_documentation_fragment: digital_ocean.documentation notes: - Two environment variables can be used, DO_API_KEY and DO_API_TOKEN. They both refer to the v2 token. @@ -267,20 +259,21 @@ def handle_request(module): def main(): - module = AnsibleModule( - argument_spec=dict( - state=dict(choices=['present', 'absent'], required=True), - command=dict(choices=['create', 'attach'], required=True), - api_token=dict(aliases=['API_TOKEN'], no_log=True), - block_size=dict(type='int', required=False), - volume_name=dict(type='str', required=True), - description=dict(type='str'), - region=dict(type='str', required=False), - snapshot_id=dict(type='str', required=False), - droplet_id=dict(type='int'), - timeout=dict(type='int', default=10), - ), + argument_spec = DigitalOceanHelper.digital_ocean_argument_spec() + argument_spec.update( + state=dict(choices=['present', 'absent'], required=True), + command=dict(choices=['create', 'attach'], required=True), + block_size=dict(type='int', required=False), + volume_name=dict(type='str', required=True), + description=dict(type='str'), + region=dict(type='str', required=False), + snapshot_id=dict(type='str', required=False), + droplet_id=dict(type='int'), + timeout=dict(type='int', default=10) ) + + module = AnsibleModule(argument_spec=argument_spec) + try: handle_request(module) except DOBlockStorageException as e: @@ -288,5 +281,6 @@ def main(): except KeyError as e: module.fail_json(msg='Unable to load %s' % e.message, exception=traceback.format_exc()) + if __name__ == '__main__': main() diff --git a/lib/ansible/modules/cloud/digital_ocean/digital_ocean_certificate.py b/lib/ansible/modules/cloud/digital_ocean/digital_ocean_certificate.py index 258d37fc113..24721053ec8 100644 --- a/lib/ansible/modules/cloud/digital_ocean/digital_ocean_certificate.py +++ b/lib/ansible/modules/cloud/digital_ocean/digital_ocean_certificate.py @@ -39,12 +39,7 @@ options: - Whether the certificate should be present or absent. default: present choices: ['present', 'absent'] - oauth_token: - description: - - DigitalOcean OAuth token. - required: true - aliases: ['DO_API_TOKEN', 'DO_API_KEY', 'DO_OAUTH_TOKEN'] - +extends_documentation_fragment: digital_ocean.documentation notes: - Two environment variables can be used, DO_API_KEY, DO_OAUTH_TOKEN and DO_API_TOKEN. They both refer to the v2 token. @@ -101,11 +96,6 @@ def core(module): results = dict(changed=False) - # Check if oauth_token is valid or not - response = rest.get('account') - if response.status_code == 401: - module.fail_json(msg='Failed to login using oauth_token, please verify validity of oauth_token') - response = rest.get('certificates') status_code = response.status_code resp_json = response.json @@ -157,15 +147,17 @@ def core(module): def main(): + argument_spec = DigitalOceanHelper.digital_ocean_argument_spec() + argument_spec.update( + name=dict(type='str'), + leaf_certificate=dict(type='str'), + private_key=dict(type='str', no_log=True), + state=dict(choices=['present', 'absent'], default='present'), + certificate_chain=dict(type='str') + ) + module = AnsibleModule( - argument_spec=dict( - name=dict(type='str'), - leaf_certificate=dict(type='str'), - private_key=dict(type='str', no_log=True), - state=dict(choices=['present', 'absent'], default='present'), - certificate_chain=dict(type='str'), - oauth_token=dict(aliases=['DO_API_TOKEN', 'DO_API_KEY', 'DO_OAUTH_TOKEN'], no_log=True), - ), + argument_spec=argument_spec, required_if=[('state', 'present', ['name', 'leaf_certificate', 'private_key']), ('state', 'absent', ['name']) ], @@ -176,5 +168,6 @@ def main(): except Exception as e: module.fail_json(msg=to_native(e)) + if __name__ == '__main__': main() diff --git a/lib/ansible/modules/cloud/digital_ocean/digital_ocean_domain.py b/lib/ansible/modules/cloud/digital_ocean/digital_ocean_domain.py index 3e0f9017f12..7b3738d2414 100644 --- a/lib/ansible/modules/cloud/digital_ocean/digital_ocean_domain.py +++ b/lib/ansible/modules/cloud/digital_ocean/digital_ocean_domain.py @@ -27,11 +27,6 @@ options: - Indicate desired state of the target. default: present choices: ['present', 'absent'] - oauth_token: - description: - - DigitalOcean api token. - version_added: "1.9.5" - aliases: ['API_TOKEN'] id: description: - Numeric, the droplet id you want to operate on. @@ -42,7 +37,7 @@ options: ip: description: - The IP address to point a domain at. - +extends_documentation_fragment: digital_ocean.documentation notes: - Environment variables DO_OAUTH_TOKEN can be used for the oauth_token. - As of Ansible 1.9.5 and 2.0, Version 2 of the DigitalOcean API is used, this removes C(client_id) and C(api_key) options in favor of C(oauth_token). @@ -84,7 +79,6 @@ import traceback from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.digital_ocean import DigitalOceanHelper from ansible.module_utils._text import to_native -from ansible.module_utils.basic import env_fallback class DoManager(DigitalOceanHelper, object): @@ -185,18 +179,16 @@ def core(module): def main(): + argument_spec = DigitalOceanHelper.digital_ocean_argument_spec() + argument_spec.update( + state=dict(choices=['present', 'absent'], default='present'), + name=dict(type='str'), + id=dict(aliases=['droplet_id'], type='int'), + ip=dict(type='str') + ) + module = AnsibleModule( - argument_spec=dict( - state=dict(choices=['present', 'absent'], default='present'), - oauth_token=dict( - aliases=['API_TOKEN'], - no_log=True, - fallback=(env_fallback, ['DO_API_TOKEN', 'DO_API_KEY', 'DO_OAUTH_TOKEN']) - ), - name=dict(type='str'), - id=dict(aliases=['droplet_id'], type='int'), - ip=dict(type='str'), - ), + argument_spec=argument_spec, required_one_of=( ['id', 'name'], ), diff --git a/lib/ansible/modules/cloud/digital_ocean/digital_ocean_floating_ip_facts.py b/lib/ansible/modules/cloud/digital_ocean/digital_ocean_floating_ip_facts.py index e2a23afba52..3b70ceea440 100644 --- a/lib/ansible/modules/cloud/digital_ocean/digital_ocean_floating_ip_facts.py +++ b/lib/ansible/modules/cloud/digital_ocean/digital_ocean_floating_ip_facts.py @@ -1,35 +1,27 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - -# Copyright: Ansible Project +# Copyright: (C) 2017-18, Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type -ANSIBLE_METADATA = {'status': ['preview'], - 'supported_by': 'community', - 'metadata_version': '1.1'} +ANSIBLE_METADATA = { + 'status': ['preview'], + 'supported_by': 'community', + 'metadata_version': '1.1' +} DOCUMENTATION = ''' --- module: digital_ocean_floating_ip_facts short_description: DigitalOcean Floating IPs facts description: - - Fetch DigitalOcean Floating IPs facts. + - This module can be used to fetch DigitalOcean Floating IPs facts. version_added: "2.5" author: "Patrick Marques (@pmarques)" -options: - oauth_token: - description: - - DigitalOcean OAuth token. - required: true - timeout: - description: - - The timeout in seconds used for polling DigitalOcean's API. - default: 30 - +extends_documentation_fragment: digital_ocean.documentation notes: - Version 2 of DigitalOcean API is used. requirements: @@ -84,12 +76,9 @@ floating_ips: ] ''' -import json -import os - -from ansible.module_utils.basic import env_fallback from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.digital_ocean import DigitalOceanHelper +from ansible.module_utils._text import to_native def core(module): @@ -98,41 +87,34 @@ def core(module): page = 1 has_next = True floating_ips = [] - while has_next or 200 != status_code: + status_code = None + while has_next or status_code != 200: response = rest.get("floating_ips?page={0}&per_page=20".format(page)) status_code = response.status_code # stop if any error during pagination - if 200 != status_code: + if status_code != 200: break - page = page + 1 + page += 1 floating_ips.extend(response.json["floating_ips"]) has_next = "pages" in response.json["links"] and "next" in response.json["links"]["pages"] if status_code == 200: module.exit_json(changed=False, floating_ips=floating_ips) else: - module.fail_json(msg="Error fecthing facts [{0}: {1}]".format( + module.fail_json(msg="Error fetching facts [{0}: {1}]".format( status_code, response.json["message"])) def main(): module = AnsibleModule( - argument_spec=dict( - oauth_token=dict( - no_log=True, - # Support environment variable for DigitalOcean OAuth Token - fallback=(env_fallback, ['DO_API_TOKEN', 'DO_API_KEY', 'DO_OAUTH_TOKEN']), - required=True, - ), - validate_certs=dict(type='bool', default=True), - timeout=dict(type='int', default=30), - ), + argument_spec=DigitalOceanHelper.digital_ocean_argument_spec() ) try: core(module) except Exception as e: - module.fail_json(msg=str(e)) + module.fail_json(msg=to_native(e)) + if __name__ == '__main__': main() diff --git a/lib/ansible/modules/cloud/digital_ocean/digital_ocean_sshkey_facts.py b/lib/ansible/modules/cloud/digital_ocean/digital_ocean_sshkey_facts.py index 018e55a8533..d403dd39ba0 100644 --- a/lib/ansible/modules/cloud/digital_ocean/digital_ocean_sshkey_facts.py +++ b/lib/ansible/modules/cloud/digital_ocean/digital_ocean_sshkey_facts.py @@ -22,16 +22,7 @@ description: - Fetch DigitalOcean SSH keys facts. version_added: "2.5" author: "Patrick Marques (@pmarques)" -options: - oauth_token: - description: - - DigitalOcean API token. - required: true - timeout: - description: - - The timeout in seconds used for polling DigitalOcean's API. - default: 30 - +extends_documentation_fragment: digital_ocean.documentation notes: - Version 2 of DigitalOcean API is used. requirements: @@ -77,7 +68,6 @@ data: } ''' -from ansible.module_utils.basic import env_fallback from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.digital_ocean import DigitalOceanHelper @@ -91,26 +81,18 @@ def core(module): if status_code == 200: module.exit_json(changed=False, ansible_facts=json) else: - module.fail_json(msg='Error fecthing facts [{0}: {1}]'.format( + module.fail_json(msg='Error fetching facts [{0}: {1}]'.format( status_code, response.json['message'])) def main(): module = AnsibleModule( - argument_spec=dict( - oauth_token=dict( - no_log=True, - # Support environment variable for DigitalOcean OAuth Token - fallback=(env_fallback, ['DO_API_TOKEN', 'DO_API_KEY', 'DO_OAUTH_TOKEN']), - required=True, - ), - validate_certs=dict(type='bool', default=True), - timeout=dict(type='int', default=30), - ), - supports_check_mode=True, + argument_spec=DigitalOceanHelper.digital_ocean_argument_spec(), + supports_check_mode=False, ) core(module) + if __name__ == '__main__': main() diff --git a/lib/ansible/modules/cloud/digital_ocean/digital_ocean_tag.py b/lib/ansible/modules/cloud/digital_ocean/digital_ocean_tag.py index 30a318520c9..4dcafea6926 100644 --- a/lib/ansible/modules/cloud/digital_ocean/digital_ocean_tag.py +++ b/lib/ansible/modules/cloud/digital_ocean/digital_ocean_tag.py @@ -43,10 +43,7 @@ options: - Whether the tag should be present or absent on the resource. default: present choices: ['present', 'absent'] - api_token: - description: - - DigitalOcean api token. - +extends_documentation_fragment: digital_ocean.documentation notes: - Two environment variables can be used, DO_API_KEY and DO_API_TOKEN. They both refer to the v2 token. @@ -119,11 +116,6 @@ def core(module): rest = DigitalOceanHelper(module) - # Check if api_token is valid or not - response = rest.get('account') - if response.status_code == 401: - module.fail_json(msg='Failed to login using api_token, please verify ' - 'validity of api_token') if state == 'present': response = rest.get('tags/{0}'.format(name)) status_code = response.status_code @@ -197,16 +189,16 @@ def core(module): def main(): - module = AnsibleModule( - argument_spec=dict( - name=dict(type='str', required=True), - resource_id=dict(aliases=['droplet_id'], type='str'), - resource_type=dict(choices=['droplet'], default='droplet'), - state=dict(choices=['present', 'absent'], default='present'), - api_token=dict(aliases=['API_TOKEN'], no_log=True), - ) + argument_spec = DigitalOceanHelper.digital_ocean_argument_spec() + argument_spec.update( + name=dict(type='str', required=True), + resource_id=dict(aliases=['droplet_id'], type='str'), + resource_type=dict(choices=['droplet'], default='droplet'), + state=dict(choices=['present', 'absent'], default='present'), ) + module = AnsibleModule(argument_spec=argument_spec) + try: core(module) except Exception as e: diff --git a/lib/ansible/utils/module_docs_fragments/digital_ocean.py b/lib/ansible/utils/module_docs_fragments/digital_ocean.py new file mode 100644 index 00000000000..52da7d75799 --- /dev/null +++ b/lib/ansible/utils/module_docs_fragments/digital_ocean.py @@ -0,0 +1,26 @@ +# Copyright (c) 2018, Ansible Project +# Copyright (c) 2018, Abhijeet Kasurde (akasurde@redhat.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +class ModuleDocFragment(object): + # Parameters for DigitalOcean modules + DOCUMENTATION = ''' +options: + oauth_token: + description: + - DigitalOcean OAuth token. + - "There are several other environment variables which can be used to provide this value." + - "i.e., - 'DO_API_TOKEN', 'DO_API_KEY', 'DO_OAUTH_TOKEN' and 'OAUTH_TOKEN'" + required: false + timeout: + description: + - The timeout in seconds used for polling DigitalOcean's API. + default: 30 + validate_certs: + description: + - If set to C(no), the SSL certificates will not be validated. + - This should only set to C(no) used on personally controlled sites using self-signed certificates. + default: true + +'''