diff options
author | Ansible Core Team <info@ansible.com> | 2020-03-09 09:40:37 +0000 |
---|---|---|
committer | Ansible Core Team <info@ansible.com> | 2020-03-09 09:40:37 +0000 |
commit | 174b9c0e4ce30e5c47b44318e35a782a7713c710 (patch) | |
tree | 75a9c4a34fbaded9ee9f760dc26c478d18e237c6 /lib/ansible/module_utils/gcp_utils.py | |
parent | 775a89254535f6698d191432b80caf8163321b6f (diff) | |
download | ansible-174b9c0e4ce30e5c47b44318e35a782a7713c710.tar.gz |
Migrated to google.cloud
Diffstat (limited to 'lib/ansible/module_utils/gcp_utils.py')
-rw-r--r-- | lib/ansible/module_utils/gcp_utils.py | 452 |
1 files changed, 0 insertions, 452 deletions
diff --git a/lib/ansible/module_utils/gcp_utils.py b/lib/ansible/module_utils/gcp_utils.py deleted file mode 100644 index 44cca3909a..0000000000 --- a/lib/ansible/module_utils/gcp_utils.py +++ /dev/null @@ -1,452 +0,0 @@ -# Copyright (c), Google Inc, 2017 -# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) - -try: - import requests - HAS_REQUESTS = True -except ImportError: - HAS_REQUESTS = False - -try: - import google.auth - import google.auth.compute_engine - from google.oauth2 import service_account - from google.auth.transport.requests import AuthorizedSession - HAS_GOOGLE_LIBRARIES = True -except ImportError: - HAS_GOOGLE_LIBRARIES = False - -from ansible.module_utils.basic import AnsibleModule, env_fallback -from ansible.module_utils.six import string_types -from ansible.module_utils._text import to_text, to_native -import ast -import os -import json - - -def navigate_hash(source, path, default=None): - if not source: - return None - - key = path[0] - path = path[1:] - if key not in source: - return default - result = source[key] - if path: - return navigate_hash(result, path, default) - else: - return result - - -class GcpRequestException(Exception): - pass - - -def remove_nones_from_dict(obj): - new_obj = {} - for key in obj: - value = obj[key] - if value is not None and value != {} and value != []: - new_obj[key] = value - - # Blank dictionaries should return None or GCP API may complain. - if not new_obj: - return None - return new_obj - - -# Handles the replacement of dicts with values -> the needed value for GCP API -def replace_resource_dict(item, value): - if isinstance(item, list): - items = [] - for i in item: - items.append(replace_resource_dict(i, value)) - return items - else: - if not item: - return item - else: - return item.get(value) - - -# Handles all authentication and HTTP sessions for GCP API calls. -class GcpSession(object): - def __init__(self, module, product): - self.module = module - self.product = product - self._validate() - - def get(self, url, body=None, **kwargs): - """ - This method should be avoided in favor of full_get - """ - kwargs.update({'json': body}) - return self.full_get(url, **kwargs) - - def post(self, url, body=None, headers=None, **kwargs): - """ - This method should be avoided in favor of full_post - """ - kwargs.update({'json': body, 'headers': headers}) - return self.full_post(url, **kwargs) - - def post_contents(self, url, file_contents=None, headers=None, **kwargs): - """ - This method should be avoided in favor of full_post - """ - kwargs.update({'data': file_contents, 'headers': headers}) - return self.full_post(url, **kwargs) - - def delete(self, url, body=None): - """ - This method should be avoided in favor of full_delete - """ - kwargs = {'json': body} - return self.full_delete(url, **kwargs) - - def put(self, url, body=None): - """ - This method should be avoided in favor of full_put - """ - kwargs = {'json': body} - return self.full_put(url, **kwargs) - - def patch(self, url, body=None, **kwargs): - """ - This method should be avoided in favor of full_patch - """ - kwargs.update({'json': body}) - return self.full_patch(url, **kwargs) - - def list(self, url, callback, params=None, array_name='items', - pageToken='nextPageToken', **kwargs): - """ - This should be used for calling the GCP list APIs. It will return - an array of items - - This takes a callback to a `return_if_object(module, response)` - function that will decode the response + return a dictionary. Some - modules handle the decode + error processing differently, so we should - defer to the module to handle this. - """ - resp = callback(self.module, self.full_get(url, params, **kwargs)) - items = resp.get(array_name) if resp.get(array_name) else [] - while resp.get(pageToken): - if params: - params['pageToken'] = resp.get(pageToken) - else: - params = {'pageToken': resp[pageToken]} - - resp = callback(self.module, self.full_get(url, params, **kwargs)) - if resp.get(array_name): - items = items + resp.get(array_name) - return items - - # The following methods fully mimic the requests API and should be used. - def full_get(self, url, params=None, **kwargs): - kwargs['headers'] = self._set_headers(kwargs.get('headers')) - try: - return self.session().get(url, params=params, **kwargs) - except getattr(requests.exceptions, 'RequestException') as inst: - # Only log the message to avoid logging any sensitive info. - self.module.fail_json(msg=inst.message) - - def full_post(self, url, data=None, json=None, **kwargs): - kwargs['headers'] = self._set_headers(kwargs.get('headers')) - - try: - return self.session().post(url, data=data, json=json, **kwargs) - except getattr(requests.exceptions, 'RequestException') as inst: - self.module.fail_json(msg=inst.message) - - def full_put(self, url, data=None, **kwargs): - kwargs['headers'] = self._set_headers(kwargs.get('headers')) - - try: - return self.session().put(url, data=data, **kwargs) - except getattr(requests.exceptions, 'RequestException') as inst: - self.module.fail_json(msg=inst.message) - - def full_patch(self, url, data=None, **kwargs): - kwargs['headers'] = self._set_headers(kwargs.get('headers')) - - try: - return self.session().patch(url, data=data, **kwargs) - except getattr(requests.exceptions, 'RequestException') as inst: - self.module.fail_json(msg=inst.message) - - def full_delete(self, url, **kwargs): - kwargs['headers'] = self._set_headers(kwargs.get('headers')) - - try: - return self.session().delete(url, **kwargs) - except getattr(requests.exceptions, 'RequestException') as inst: - self.module.fail_json(msg=inst.message) - - def _set_headers(self, headers): - if headers: - return self._merge_dictionaries(headers, self._headers()) - else: - return self._headers() - - def session(self): - return AuthorizedSession( - self._credentials()) - - def _validate(self): - if not HAS_REQUESTS: - self.module.fail_json(msg="Please install the requests library") - - if not HAS_GOOGLE_LIBRARIES: - self.module.fail_json(msg="Please install the google-auth library") - - if self.module.params.get('service_account_email') is not None and self.module.params['auth_kind'] != 'machineaccount': - self.module.fail_json( - msg="Service Account Email only works with Machine Account-based authentication" - ) - - if (self.module.params.get('service_account_file') is not None or - self.module.params.get('service_account_contents') is not None) and self.module.params['auth_kind'] != 'serviceaccount': - self.module.fail_json( - msg="Service Account File only works with Service Account-based authentication" - ) - - def _credentials(self): - cred_type = self.module.params['auth_kind'] - if cred_type == 'application': - credentials, project_id = google.auth.default(scopes=self.module.params['scopes']) - return credentials - elif cred_type == 'serviceaccount' and self.module.params.get('service_account_file'): - path = os.path.realpath(os.path.expanduser(self.module.params['service_account_file'])) - return service_account.Credentials.from_service_account_file(path).with_scopes(self.module.params['scopes']) - elif cred_type == 'serviceaccount' and self.module.params.get('service_account_contents'): - try: - cred = json.loads(self.module.params.get('service_account_contents')) - except json.decoder.JSONDecodeError as e: - self.module.fail_json( - msg="Unable to decode service_account_contents as JSON" - ) - return service_account.Credentials.from_service_account_info(cred).with_scopes(self.module.params['scopes']) - elif cred_type == 'machineaccount': - return google.auth.compute_engine.Credentials( - self.module.params['service_account_email']) - else: - self.module.fail_json(msg="Credential type '%s' not implemented" % cred_type) - - def _headers(self): - if self.module.params.get('env_type'): - return { - 'User-Agent': "Google-Ansible-MM-{0}-{1}".format(self.product, self.module.params.get('env_type')) - } - else: - return { - 'User-Agent': "Google-Ansible-MM-{0}".format(self.product) - } - - def _merge_dictionaries(self, a, b): - new = a.copy() - new.update(b) - return new - - -class GcpModule(AnsibleModule): - def __init__(self, *args, **kwargs): - arg_spec = {} - if 'argument_spec' in kwargs: - arg_spec = kwargs['argument_spec'] - - kwargs['argument_spec'] = self._merge_dictionaries( - arg_spec, - dict( - project=dict( - required=False, - type='str', - fallback=(env_fallback, ['GCP_PROJECT'])), - auth_kind=dict( - required=True, - fallback=(env_fallback, ['GCP_AUTH_KIND']), - choices=['machineaccount', 'serviceaccount', 'application'], - type='str'), - service_account_email=dict( - required=False, - fallback=(env_fallback, ['GCP_SERVICE_ACCOUNT_EMAIL']), - type='str'), - service_account_file=dict( - required=False, - fallback=(env_fallback, ['GCP_SERVICE_ACCOUNT_FILE']), - type='path'), - service_account_contents=dict( - required=False, - fallback=(env_fallback, ['GCP_SERVICE_ACCOUNT_CONTENTS']), - no_log=True, - type='jsonarg'), - scopes=dict( - required=False, - fallback=(env_fallback, ['GCP_SCOPES']), - type='list'), - env_type=dict( - required=False, - fallback=(env_fallback, ['GCP_ENV_TYPE']), - type='str') - ) - ) - - mutual = [] - if 'mutually_exclusive' in kwargs: - mutual = kwargs['mutually_exclusive'] - - kwargs['mutually_exclusive'] = mutual.append( - ['service_account_email', 'service_account_file', 'service_account_contents'] - ) - - AnsibleModule.__init__(self, *args, **kwargs) - - def raise_for_status(self, response): - try: - response.raise_for_status() - except getattr(requests.exceptions, 'RequestException') as inst: - self.fail_json(msg="GCP returned error: %s" % response.json()) - - def _merge_dictionaries(self, a, b): - new = a.copy() - new.update(b) - return new - - -# This class does difference checking according to a set of GCP-specific rules. -# This will be primarily used for checking dictionaries. -# In an equivalence check, the left-hand dictionary will be the request and -# the right-hand side will be the response. - -# Rules: -# Extra keys in response will be ignored. -# Ordering of lists does not matter. -# - exception: lists of dictionaries are -# assumed to be in sorted order. -class GcpRequest(object): - def __init__(self, request): - self.request = request - - def __eq__(self, other): - return not self.difference(other) - - def __ne__(self, other): - return not self.__eq__(other) - - # Returns the difference between a request + response. - # While this is used under the hood for __eq__ and __ne__, - # it is useful for debugging. - def difference(self, response): - return self._compare_value(self.request, response.request) - - def _compare_dicts(self, req_dict, resp_dict): - difference = {} - for key in req_dict: - if resp_dict.get(key): - difference[key] = self._compare_value(req_dict.get(key), resp_dict.get(key)) - - # Remove all empty values from difference. - sanitized_difference = {} - for key in difference: - if difference[key]: - sanitized_difference[key] = difference[key] - - return sanitized_difference - - # Takes in two lists and compares them. - # All things in the list should be identical (even if a dictionary) - def _compare_lists(self, req_list, resp_list): - # Have to convert each thing over to unicode. - # Python doesn't handle equality checks between unicode + non-unicode well. - difference = [] - new_req_list = self._convert_value(req_list) - new_resp_list = self._convert_value(resp_list) - - # We have to compare each thing in the request to every other thing - # in the response. - # This is because the request value will be a subset of the response value. - # The assumption is that these lists will be small enough that it won't - # be a performance burden. - for req_item in new_req_list: - found_item = False - for resp_item in new_resp_list: - # Looking for a None value here. - if not self._compare_value(req_item, resp_item): - found_item = True - if not found_item: - difference.append(req_item) - - difference2 = [] - for value in difference: - if value: - difference2.append(value) - - return difference2 - - # Compare two values of arbitrary types. - def _compare_value(self, req_value, resp_value): - diff = None - # If a None is found, a difference does not exist. - # Only differing values matter. - if not resp_value: - return None - - # Can assume non-None types at this point. - try: - if isinstance(req_value, list): - diff = self._compare_lists(req_value, resp_value) - elif isinstance(req_value, dict): - diff = self._compare_dicts(req_value, resp_value) - elif isinstance(req_value, bool): - diff = self._compare_boolean(req_value, resp_value) - # Always use to_text values to avoid unicode issues. - elif to_text(req_value) != to_text(resp_value): - diff = req_value - # to_text may throw UnicodeErrors. - # These errors shouldn't crash Ansible and should be hidden. - except UnicodeError: - pass - - return diff - - # Compare two boolean values. - def _compare_boolean(self, req_value, resp_value): - try: - # Both True - if req_value and isinstance(resp_value, bool) and resp_value: - return None - # Value1 True, resp_value 'true' - elif req_value and to_text(resp_value) == 'true': - return None - # Both False - elif not req_value and isinstance(resp_value, bool) and not resp_value: - return None - # Value1 False, resp_value 'false' - elif not req_value and to_text(resp_value) == 'false': - return None - else: - return resp_value - - # to_text may throw UnicodeErrors. - # These errors shouldn't crash Ansible and should be hidden. - except UnicodeError: - return None - - # Python (2 esp.) doesn't do comparisons between unicode + non-unicode well. - # This leads to a lot of false positives when diffing values. - # The Ansible to_text() function is meant to get all strings - # into a standard format. - def _convert_value(self, value): - if isinstance(value, list): - new_list = [] - for item in value: - new_list.append(self._convert_value(item)) - return new_list - elif isinstance(value, dict): - new_dict = {} - for key in value: - new_dict[key] = self._convert_value(value[key]) - return new_dict - else: - return to_text(value) |