mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-26 05:50:36 -07:00 
			
		
		
		
	
		
			Some checks failed
		
		
	
	EOL CI / EOL Sanity (Ⓐ2.17) (push) Has been cancelled
				
			EOL CI / EOL Units (Ⓐ2.17+py3.10) (push) Has been cancelled
				
			EOL CI / EOL Units (Ⓐ2.17+py3.12) (push) Has been cancelled
				
			EOL CI / EOL Units (Ⓐ2.17+py3.7) (push) Has been cancelled
				
			EOL CI / EOL I (Ⓐ2.17+alpine319+py:azp/posix/1/) (push) Has been cancelled
				
			EOL CI / EOL I (Ⓐ2.17+alpine319+py:azp/posix/2/) (push) Has been cancelled
				
			EOL CI / EOL I (Ⓐ2.17+alpine319+py:azp/posix/3/) (push) Has been cancelled
				
			EOL CI / EOL I (Ⓐ2.17+fedora39+py:azp/posix/1/) (push) Has been cancelled
				
			EOL CI / EOL I (Ⓐ2.17+fedora39+py:azp/posix/2/) (push) Has been cancelled
				
			EOL CI / EOL I (Ⓐ2.17+fedora39+py:azp/posix/3/) (push) Has been cancelled
				
			EOL CI / EOL I (Ⓐ2.17+ubuntu2004+py:azp/posix/1/) (push) Has been cancelled
				
			EOL CI / EOL I (Ⓐ2.17+ubuntu2004+py:azp/posix/2/) (push) Has been cancelled
				
			EOL CI / EOL I (Ⓐ2.17+ubuntu2004+py:azp/posix/3/) (push) Has been cancelled
				
			nox / Run extra sanity tests (push) Has been cancelled
				
			* modules: update code to python3 * pamd: rollback changes * add changelog frag * fix/improve assignments using generators * Update plugins/modules/launchd.py Co-authored-by: Felix Fontein <felix@fontein.de> --------- Co-authored-by: Felix Fontein <felix@fontein.de>
		
			
				
	
	
		
			495 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			495 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/python
 | |
| #
 | |
| # Copyright (C) 2018 Huawei
 | |
| # 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 annotations
 | |
| 
 | |
| ###############################################################################
 | |
| # Documentation
 | |
| ###############################################################################
 | |
| 
 | |
| DOCUMENTATION = r"""
 | |
| module: hwc_network_vpc
 | |
| description:
 | |
|   - Represents an vpc resource.
 | |
| short_description: Creates a Huawei Cloud VPC
 | |
| author: Huawei Inc. (@huaweicloud)
 | |
| requirements:
 | |
|   - requests >= 2.18.4
 | |
|   - keystoneauth1 >= 3.6.0
 | |
| attributes:
 | |
|   check_mode:
 | |
|     support: full
 | |
|   diff_mode:
 | |
|     support: none
 | |
| options:
 | |
|   state:
 | |
|     description:
 | |
|       - Whether the given object should exist in VPC.
 | |
|     type: str
 | |
|     choices: ['present', 'absent']
 | |
|     default: 'present'
 | |
|   timeouts:
 | |
|     description:
 | |
|       - The timeouts for each operations.
 | |
|     type: dict
 | |
|     default: {}
 | |
|     suboptions:
 | |
|       create:
 | |
|         description:
 | |
|           - The timeout for create operation.
 | |
|         type: str
 | |
|         default: '15m'
 | |
|       update:
 | |
|         description:
 | |
|           - The timeout for update operation.
 | |
|         type: str
 | |
|         default: '15m'
 | |
|       delete:
 | |
|         description:
 | |
|           - The timeout for delete operation.
 | |
|         type: str
 | |
|         default: '15m'
 | |
|   name:
 | |
|     description:
 | |
|       - The name of vpc.
 | |
|     type: str
 | |
|     required: true
 | |
|   cidr:
 | |
|     description:
 | |
|       - The range of available subnets in the VPC.
 | |
|     type: str
 | |
|     required: true
 | |
| extends_documentation_fragment:
 | |
|   - community.general.hwc
 | |
|   - community.general.attributes
 | |
| """
 | |
| 
 | |
| EXAMPLES = r"""
 | |
| - name: Create a vpc
 | |
|   community.general.hwc_network_vpc:
 | |
|     identity_endpoint: "{{ identity_endpoint }}"
 | |
|     user: "{{ user }}"
 | |
|     password: "{{ password }}"
 | |
|     domain: "{{ domain }}"
 | |
|     project: "{{ project }}"
 | |
|     region: "{{ region }}"
 | |
|     name: "vpc_1"
 | |
|     cidr: "192.168.100.0/24"
 | |
|     state: present
 | |
| """
 | |
| 
 | |
| RETURN = r"""
 | |
| id:
 | |
|   description:
 | |
|     - The ID of VPC.
 | |
|   type: str
 | |
|   returned: success
 | |
| name:
 | |
|   description:
 | |
|     - The name of VPC.
 | |
|   type: str
 | |
|   returned: success
 | |
| cidr:
 | |
|   description:
 | |
|     - The range of available subnets in the VPC.
 | |
|   type: str
 | |
|   returned: success
 | |
| status:
 | |
|   description:
 | |
|     - The status of VPC.
 | |
|   type: str
 | |
|   returned: success
 | |
| routes:
 | |
|   description:
 | |
|     - The route information.
 | |
|   type: complex
 | |
|   returned: success
 | |
|   contains:
 | |
|     destination:
 | |
|       description:
 | |
|         - The destination network segment of a route.
 | |
|       type: str
 | |
|       returned: success
 | |
|     next_hop:
 | |
|       description:
 | |
|         - The next hop of a route. If the route type is peering, it provides VPC peering connection ID.
 | |
|       type: str
 | |
|       returned: success
 | |
| enable_shared_snat:
 | |
|   description:
 | |
|     - Show whether the shared SNAT is enabled.
 | |
|   type: bool
 | |
|   returned: success
 | |
| """
 | |
| 
 | |
| ###############################################################################
 | |
| # Imports
 | |
| ###############################################################################
 | |
| 
 | |
| from ansible_collections.community.general.plugins.module_utils.hwc_utils import (Config, HwcClientException,
 | |
|                                                                                   HwcClientException404, HwcModule,
 | |
|                                                                                   are_different_dicts, is_empty_value,
 | |
|                                                                                   wait_to_finish, get_region,
 | |
|                                                                                   build_path, navigate_value)
 | |
| import re
 | |
| 
 | |
| ###############################################################################
 | |
| # Main
 | |
| ###############################################################################
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     """Main function"""
 | |
| 
 | |
|     module = HwcModule(
 | |
|         argument_spec=dict(
 | |
|             state=dict(
 | |
|                 default='present', choices=['present', 'absent'], type='str'),
 | |
|             timeouts=dict(type='dict', options=dict(
 | |
|                 create=dict(default='15m', type='str'),
 | |
|                 update=dict(default='15m', type='str'),
 | |
|                 delete=dict(default='15m', type='str'),
 | |
|             ), default=dict()),
 | |
|             name=dict(required=True, type='str'),
 | |
|             cidr=dict(required=True, type='str')
 | |
|         ),
 | |
|         supports_check_mode=True,
 | |
|     )
 | |
|     config = Config(module, 'vpc')
 | |
| 
 | |
|     state = module.params['state']
 | |
| 
 | |
|     if (not module.params.get("id")) and module.params.get("name"):
 | |
|         module.params['id'] = get_id_by_name(config)
 | |
| 
 | |
|     fetch = None
 | |
|     link = self_link(module)
 | |
|     # the link will include Nones if required format parameters are missed
 | |
|     if not re.search('/None/|/None$', link):
 | |
|         client = config.client(get_region(module), "vpc", "project")
 | |
|         fetch = fetch_resource(module, client, link)
 | |
|         if fetch:
 | |
|             fetch = fetch.get('vpc')
 | |
|     changed = False
 | |
| 
 | |
|     if fetch:
 | |
|         if state == 'present':
 | |
|             expect = _get_editable_properties(module)
 | |
|             current_state = response_to_hash(module, fetch)
 | |
|             current = {"cidr": current_state["cidr"]}
 | |
|             if are_different_dicts(expect, current):
 | |
|                 if not module.check_mode:
 | |
|                     fetch = update(config, self_link(module))
 | |
|                     fetch = response_to_hash(module, fetch.get('vpc'))
 | |
|                 changed = True
 | |
|             else:
 | |
|                 fetch = current_state
 | |
|         else:
 | |
|             if not module.check_mode:
 | |
|                 delete(config, self_link(module))
 | |
|                 fetch = {}
 | |
|             changed = True
 | |
|     else:
 | |
|         if state == 'present':
 | |
|             if not module.check_mode:
 | |
|                 fetch = create(config, "vpcs")
 | |
|                 fetch = response_to_hash(module, fetch.get('vpc'))
 | |
|             changed = True
 | |
|         else:
 | |
|             fetch = {}
 | |
| 
 | |
|     fetch.update({'changed': changed})
 | |
| 
 | |
|     module.exit_json(**fetch)
 | |
| 
 | |
| 
 | |
| def create(config, link):
 | |
|     module = config.module
 | |
|     client = config.client(get_region(module), "vpc", "project")
 | |
| 
 | |
|     r = None
 | |
|     try:
 | |
|         r = client.post(link, resource_to_create(module))
 | |
|     except HwcClientException as ex:
 | |
|         msg = ("module(hwc_network_vpc): error creating "
 | |
|                "resource, error: %s" % str(ex))
 | |
|         module.fail_json(msg=msg)
 | |
| 
 | |
|     wait_done = wait_for_operation(config, 'create', r)
 | |
|     v = ""
 | |
|     try:
 | |
|         v = navigate_value(wait_done, ['vpc', 'id'])
 | |
|     except Exception as ex:
 | |
|         module.fail_json(msg=str(ex))
 | |
| 
 | |
|     url = build_path(module, 'vpcs/{op_id}', {'op_id': v})
 | |
|     return fetch_resource(module, client, url)
 | |
| 
 | |
| 
 | |
| def update(config, link):
 | |
|     module = config.module
 | |
|     client = config.client(get_region(module), "vpc", "project")
 | |
| 
 | |
|     r = None
 | |
|     try:
 | |
|         r = client.put(link, resource_to_update(module))
 | |
|     except HwcClientException as ex:
 | |
|         msg = ("module(hwc_network_vpc): error updating "
 | |
|                "resource, error: %s" % str(ex))
 | |
|         module.fail_json(msg=msg)
 | |
| 
 | |
|     wait_for_operation(config, 'update', r)
 | |
| 
 | |
|     return fetch_resource(module, client, link)
 | |
| 
 | |
| 
 | |
| def delete(config, link):
 | |
|     module = config.module
 | |
|     client = config.client(get_region(module), "vpc", "project")
 | |
| 
 | |
|     try:
 | |
|         client.delete(link)
 | |
|     except HwcClientException as ex:
 | |
|         msg = ("module(hwc_network_vpc): error deleting "
 | |
|                "resource, error: %s" % str(ex))
 | |
|         module.fail_json(msg=msg)
 | |
| 
 | |
|     wait_for_delete(module, client, link)
 | |
| 
 | |
| 
 | |
| def fetch_resource(module, client, link):
 | |
|     try:
 | |
|         return client.get(link)
 | |
|     except HwcClientException as ex:
 | |
|         msg = ("module(hwc_network_vpc): error fetching "
 | |
|                "resource, error: %s" % str(ex))
 | |
|         module.fail_json(msg=msg)
 | |
| 
 | |
| 
 | |
| def get_id_by_name(config):
 | |
|     module = config.module
 | |
|     client = config.client(get_region(module), "vpc", "project")
 | |
|     name = module.params.get("name")
 | |
|     link = "vpcs"
 | |
|     query_link = "?marker={marker}&limit=10"
 | |
|     link += query_link
 | |
|     not_format_keys = re.findall("={marker}", link)
 | |
|     none_values = re.findall("=None", link)
 | |
| 
 | |
|     if not (not_format_keys or none_values):
 | |
|         r = None
 | |
|         try:
 | |
|             r = client.get(link)
 | |
|         except Exception:
 | |
|             pass
 | |
|         if r is None:
 | |
|             return None
 | |
|         r = r.get('vpcs', [])
 | |
|         ids = [
 | |
|             i.get('id') for i in r if i.get('name', '') == name
 | |
|         ]
 | |
|         if not ids:
 | |
|             return None
 | |
|         elif len(ids) == 1:
 | |
|             return ids[0]
 | |
|         else:
 | |
|             module.fail_json(
 | |
|                 msg="Multiple resources with same name are found.")
 | |
|     elif none_values:
 | |
|         module.fail_json(
 | |
|             msg="Can not find id by name because url includes None.")
 | |
|     else:
 | |
|         p = {'marker': ''}
 | |
|         ids = set()
 | |
|         while True:
 | |
|             r = None
 | |
|             try:
 | |
|                 r = client.get(link.format(**p))
 | |
|             except Exception:
 | |
|                 pass
 | |
|             if r is None:
 | |
|                 break
 | |
|             r = r.get('vpcs', [])
 | |
|             if r == []:
 | |
|                 break
 | |
|             for i in r:
 | |
|                 if i.get('name') == name:
 | |
|                     ids.add(i.get('id'))
 | |
|             if len(ids) >= 2:
 | |
|                 module.fail_json(
 | |
|                     msg="Multiple resources with same name are found.")
 | |
| 
 | |
|             p['marker'] = r[-1].get('id')
 | |
| 
 | |
|         return ids.pop() if ids else None
 | |
| 
 | |
| 
 | |
| def self_link(module):
 | |
|     return build_path(module, "vpcs/{id}")
 | |
| 
 | |
| 
 | |
| def resource_to_create(module):
 | |
|     params = dict()
 | |
| 
 | |
|     v = module.params.get('cidr')
 | |
|     if not is_empty_value(v):
 | |
|         params["cidr"] = v
 | |
| 
 | |
|     v = module.params.get('name')
 | |
|     if not is_empty_value(v):
 | |
|         params["name"] = v
 | |
| 
 | |
|     if not params:
 | |
|         return params
 | |
| 
 | |
|     params = {"vpc": params}
 | |
| 
 | |
|     return params
 | |
| 
 | |
| 
 | |
| def resource_to_update(module):
 | |
|     params = dict()
 | |
| 
 | |
|     v = module.params.get('cidr')
 | |
|     if not is_empty_value(v):
 | |
|         params["cidr"] = v
 | |
| 
 | |
|     if not params:
 | |
|         return params
 | |
| 
 | |
|     params = {"vpc": params}
 | |
| 
 | |
|     return params
 | |
| 
 | |
| 
 | |
| def _get_editable_properties(module):
 | |
|     return {
 | |
|         "cidr": module.params.get("cidr"),
 | |
|     }
 | |
| 
 | |
| 
 | |
| def response_to_hash(module, response):
 | |
|     """ Remove unnecessary properties from the response.
 | |
|         This is for doing comparisons with Ansible's current parameters.
 | |
|     """
 | |
|     return {
 | |
|         'id': response.get('id'),
 | |
|         'name': response.get('name'),
 | |
|         'cidr': response.get('cidr'),
 | |
|         'status': response.get('status'),
 | |
|         'routes': VpcRoutesArray(
 | |
|             response.get('routes', []), module).from_response(),
 | |
|         'enable_shared_snat': response.get('enable_shared_snat')
 | |
|     }
 | |
| 
 | |
| 
 | |
| def wait_for_operation(config, op_type, op_result):
 | |
|     module = config.module
 | |
|     op_id = ""
 | |
|     try:
 | |
|         op_id = navigate_value(op_result, ['vpc', 'id'])
 | |
|     except Exception as ex:
 | |
|         module.fail_json(msg=str(ex))
 | |
| 
 | |
|     url = build_path(module, "vpcs/{op_id}", {'op_id': op_id})
 | |
|     timeout = 60 * int(module.params['timeouts'][op_type].rstrip('m'))
 | |
|     states = {
 | |
|         'create': {
 | |
|             'allowed': ['CREATING', 'DONW', 'OK'],
 | |
|             'complete': ['OK'],
 | |
|         },
 | |
|         'update': {
 | |
|             'allowed': ['PENDING_UPDATE', 'DONW', 'OK'],
 | |
|             'complete': ['OK'],
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return wait_for_completion(url, timeout, states[op_type]['allowed'],
 | |
|                                states[op_type]['complete'], config)
 | |
| 
 | |
| 
 | |
| def wait_for_completion(op_uri, timeout, allowed_states,
 | |
|                         complete_states, config):
 | |
|     module = config.module
 | |
|     client = config.client(get_region(module), "vpc", "project")
 | |
| 
 | |
|     def _refresh_status():
 | |
|         r = None
 | |
|         try:
 | |
|             r = fetch_resource(module, client, op_uri)
 | |
|         except Exception:
 | |
|             return None, ""
 | |
| 
 | |
|         status = ""
 | |
|         try:
 | |
|             status = navigate_value(r, ['vpc', 'status'])
 | |
|         except Exception:
 | |
|             return None, ""
 | |
| 
 | |
|         return r, status
 | |
| 
 | |
|     try:
 | |
|         return wait_to_finish(complete_states, allowed_states,
 | |
|                               _refresh_status, timeout)
 | |
|     except Exception as ex:
 | |
|         module.fail_json(msg=str(ex))
 | |
| 
 | |
| 
 | |
| def wait_for_delete(module, client, link):
 | |
| 
 | |
|     def _refresh_status():
 | |
|         try:
 | |
|             client.get(link)
 | |
|         except HwcClientException404:
 | |
|             return True, "Done"
 | |
| 
 | |
|         except Exception:
 | |
|             return None, ""
 | |
| 
 | |
|         return True, "Pending"
 | |
| 
 | |
|     timeout = 60 * int(module.params['timeouts']['delete'].rstrip('m'))
 | |
|     try:
 | |
|         return wait_to_finish(["Done"], ["Pending"], _refresh_status, timeout)
 | |
|     except Exception as ex:
 | |
|         module.fail_json(msg=str(ex))
 | |
| 
 | |
| 
 | |
| class VpcRoutesArray(object):
 | |
|     def __init__(self, request, module):
 | |
|         self.module = module
 | |
|         if request:
 | |
|             self.request = request
 | |
|         else:
 | |
|             self.request = []
 | |
| 
 | |
|     def to_request(self):
 | |
|         items = []
 | |
|         for item in self.request:
 | |
|             items.append(self._request_for_item(item))
 | |
|         return items
 | |
| 
 | |
|     def from_response(self):
 | |
|         items = []
 | |
|         for item in self.request:
 | |
|             items.append(self._response_from_item(item))
 | |
|         return items
 | |
| 
 | |
|     def _request_for_item(self, item):
 | |
|         return {
 | |
|             'destination': item.get('destination'),
 | |
|             'nexthop': item.get('next_hop')
 | |
|         }
 | |
| 
 | |
|     def _response_from_item(self, item):
 | |
|         return {
 | |
|             'destination': item.get('destination'),
 | |
|             'next_hop': item.get('nexthop')
 | |
|         }
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |