mirror of
https://github.com/ansible-collections/google.cloud.git
synced 2025-04-18 00:41:22 -07:00
cleanup
This commit is contained in:
parent
de1449829f
commit
d56be5f13f
7 changed files with 0 additions and 1189 deletions
|
@ -1,359 +0,0 @@
|
||||||
# Copyright (c) 2017 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
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
name: gcp_compute
|
|
||||||
plugin_type: inventory
|
|
||||||
short_description: Google Cloud Compute Engine inventory source
|
|
||||||
requirements:
|
|
||||||
- requests >= 2.18.4
|
|
||||||
- google-auth >= 1.3.0
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- constructed
|
|
||||||
- inventory_cache
|
|
||||||
description:
|
|
||||||
- Get inventory hosts from Google Cloud Platform GCE.
|
|
||||||
- Uses a <name>.gcp.yaml (or <name>.gcp.yml) YAML configuration file.
|
|
||||||
options:
|
|
||||||
zones:
|
|
||||||
description: A list of regions in which to describe GCE instances.
|
|
||||||
default: all zones available to a given project
|
|
||||||
projects:
|
|
||||||
description: A list of projects in which to describe GCE instances.
|
|
||||||
filters:
|
|
||||||
description: >
|
|
||||||
A list of filter value pairs. Available filters are listed here
|
|
||||||
U(https://cloud.google.com/compute/docs/reference/rest/v1/instances/list).
|
|
||||||
Each additional filter in the list will act be added as an AND condition
|
|
||||||
(filter1 and filter2)
|
|
||||||
hostnames:
|
|
||||||
description: A list of options that describe the ordering for which
|
|
||||||
hostnames should be assigned. Currently supported hostnames are
|
|
||||||
'public_ip', 'private_ip', or 'name'.
|
|
||||||
default: ['public_ip', 'private_ip', 'name']
|
|
||||||
auth_kind:
|
|
||||||
description:
|
|
||||||
- The type of credential used.
|
|
||||||
service_account_file:
|
|
||||||
description:
|
|
||||||
- The path of a Service Account JSON file if serviceaccount is selected as type.
|
|
||||||
service_account_email:
|
|
||||||
description:
|
|
||||||
- An optional service account email address if machineaccount is selected
|
|
||||||
and the user does not wish to use the default email.
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
plugin: gcp_compute
|
|
||||||
zones: # populate inventory with instances in these regions
|
|
||||||
- us-east1-a
|
|
||||||
projects:
|
|
||||||
- gcp-prod-gke-100
|
|
||||||
- gcp-cicd-101
|
|
||||||
filters:
|
|
||||||
- machineType = n1-standard-1
|
|
||||||
- scheduling.automaticRestart = true AND machineType = n1-standard-1
|
|
||||||
|
|
||||||
scopes:
|
|
||||||
- https://www.googleapis.com/auth/compute
|
|
||||||
service_account_file: /tmp/service_account.json
|
|
||||||
auth_kind: serviceaccount
|
|
||||||
'''
|
|
||||||
|
|
||||||
from ansible.errors import AnsibleError, AnsibleParserError
|
|
||||||
from ansible.module_utils._text import to_native, to_text
|
|
||||||
from ansible.module_utils.six import string_types
|
|
||||||
from ansible.module_utils.gcp_utils import GcpSession, navigate_hash, GcpRequestException
|
|
||||||
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable, to_safe_group_name
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
# The mappings give an array of keys to get from the filter name to the value
|
|
||||||
# returned by boto3's GCE describe_instances method.
|
|
||||||
class GcpMockModule(object):
|
|
||||||
def __init__(self, params):
|
|
||||||
self.params = params
|
|
||||||
|
|
||||||
def fail_json(self, *args, **kwargs):
|
|
||||||
raise AnsibleError(kwargs['msg'])
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
|
|
||||||
|
|
||||||
NAME = 'gcp_compute'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(InventoryModule, self).__init__()
|
|
||||||
|
|
||||||
self.group_prefix = 'gcp_'
|
|
||||||
|
|
||||||
def _populate_host(self, item):
|
|
||||||
'''
|
|
||||||
:param item: A GCP instance
|
|
||||||
'''
|
|
||||||
hostname = self._get_hostname(item)
|
|
||||||
self.inventory.add_host(hostname)
|
|
||||||
for key in item:
|
|
||||||
self.inventory.set_variable(hostname, key, item[key])
|
|
||||||
self.inventory.add_child('all', hostname)
|
|
||||||
|
|
||||||
def verify_file(self, path):
|
|
||||||
'''
|
|
||||||
:param path: the path to the inventory config file
|
|
||||||
:return the contents of the config file
|
|
||||||
'''
|
|
||||||
if super(InventoryModule, self).verify_file(path):
|
|
||||||
if path.endswith('.gcp.yml') or path.endswith('.gcp.yaml'):
|
|
||||||
return True
|
|
||||||
elif path.endswith('.gcp_compute.yml') or path.endswith('.gcp_compute.yaml'):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def self_link(self, params):
|
|
||||||
'''
|
|
||||||
:param params: a dict containing all of the fields relevant to build URL
|
|
||||||
:return the formatted URL as a string.
|
|
||||||
'''
|
|
||||||
return "https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/instances".format(**params)
|
|
||||||
|
|
||||||
def fetch_list(self, params, link, query):
|
|
||||||
'''
|
|
||||||
:param params: a dict containing all of the fields relevant to build URL
|
|
||||||
:param link: a formatted URL
|
|
||||||
:param query: a formatted query string
|
|
||||||
:return the JSON response containing a list of instances.
|
|
||||||
'''
|
|
||||||
module = GcpMockModule(params)
|
|
||||||
auth = GcpSession(module, 'compute')
|
|
||||||
response = auth.get(link, params={'filter': query})
|
|
||||||
return self._return_if_object(module, response)
|
|
||||||
|
|
||||||
def _get_zones(self, config_data):
|
|
||||||
'''
|
|
||||||
:param config_data: dict of info from inventory file
|
|
||||||
:return an array of zones that this project has access to
|
|
||||||
'''
|
|
||||||
link = "https://www.googleapis.com/compute/v1/projects/{project}/zones".format(**config_data)
|
|
||||||
zones = []
|
|
||||||
zones_response = self.fetch_list(config_data, link, '')
|
|
||||||
for item in zones_response['items']:
|
|
||||||
zones.append(item['name'])
|
|
||||||
return zones
|
|
||||||
|
|
||||||
def _get_query_options(self, filters):
|
|
||||||
'''
|
|
||||||
:param config_data: contents of the inventory config file
|
|
||||||
:return A fully built query string
|
|
||||||
'''
|
|
||||||
if not filters:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
if len(filters) == 1:
|
|
||||||
return filters[0]
|
|
||||||
else:
|
|
||||||
queries = []
|
|
||||||
for f in filters:
|
|
||||||
# For multiple queries, all queries should have ()
|
|
||||||
if f[0] != '(' and f[-1] != ')':
|
|
||||||
queries.append("(%s)" % ''.join(f))
|
|
||||||
else:
|
|
||||||
queries.append(f)
|
|
||||||
|
|
||||||
return ' '.join(queries)
|
|
||||||
|
|
||||||
def _return_if_object(self, module, response):
|
|
||||||
'''
|
|
||||||
:param module: A GcpModule
|
|
||||||
:param response: A Requests response object
|
|
||||||
:return JSON response
|
|
||||||
'''
|
|
||||||
# If not found, return nothing.
|
|
||||||
if response.status_code == 404:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# If no content, return nothing.
|
|
||||||
if response.status_code == 204:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
response.raise_for_status
|
|
||||||
result = response.json()
|
|
||||||
except getattr(json.decoder, 'JSONDecodeError', ValueError) as inst:
|
|
||||||
module.fail_json(msg="Invalid JSON response with error: %s" % inst)
|
|
||||||
except GcpRequestException as inst:
|
|
||||||
module.fail_json(msg="Network error: %s" % inst)
|
|
||||||
|
|
||||||
if navigate_hash(result, ['error', 'errors']):
|
|
||||||
module.fail_json(msg=navigate_hash(result, ['error', 'errors']))
|
|
||||||
if result['kind'] != 'compute#instanceList' and result['kind'] != 'compute#zoneList':
|
|
||||||
module.fail_json(msg="Incorrect result: {kind}".format(**result))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _format_items(self, items):
|
|
||||||
'''
|
|
||||||
:param items: A list of hosts
|
|
||||||
'''
|
|
||||||
for host in items:
|
|
||||||
if 'zone' in host:
|
|
||||||
host['zone_selflink'] = host['zone']
|
|
||||||
host['zone'] = host['zone'].split('/')[-1]
|
|
||||||
if 'machineType' in host:
|
|
||||||
host['machineType_selflink'] = host['machineType']
|
|
||||||
host['machineType'] = host['machineType'].split('/')[-1]
|
|
||||||
|
|
||||||
if 'networkInterfaces' in host:
|
|
||||||
for network in host['networkInterfaces']:
|
|
||||||
if 'network' in network:
|
|
||||||
network['network'] = self._format_network_info(network['network'])
|
|
||||||
if 'subnetwork' in network:
|
|
||||||
network['subnetwork'] = self._format_network_info(network['subnetwork'])
|
|
||||||
|
|
||||||
host['project'] = host['selfLink'].split('/')[6]
|
|
||||||
return items
|
|
||||||
|
|
||||||
def _add_hosts(self, items, config_data, format_items=True):
|
|
||||||
'''
|
|
||||||
:param items: A list of hosts
|
|
||||||
:param config_data: configuration data
|
|
||||||
:param format_items: format items or not
|
|
||||||
'''
|
|
||||||
if not items:
|
|
||||||
return
|
|
||||||
if format_items:
|
|
||||||
items = self._format_items(items)
|
|
||||||
|
|
||||||
for host in items:
|
|
||||||
self._populate_host(host)
|
|
||||||
|
|
||||||
hostname = self._get_hostname(host)
|
|
||||||
self._set_composite_vars(self.get_option('compose'), host, hostname)
|
|
||||||
self._add_host_to_composed_groups(self.get_option('groups'), host, hostname)
|
|
||||||
self._add_host_to_keyed_groups(self.get_option('keyed_groups'), host, hostname)
|
|
||||||
|
|
||||||
def _format_network_info(self, address):
|
|
||||||
'''
|
|
||||||
:param address: A GCP network address
|
|
||||||
:return a dict with network shortname and region
|
|
||||||
'''
|
|
||||||
split = address.split('/')
|
|
||||||
region = ''
|
|
||||||
if 'global' in split:
|
|
||||||
region = 'global'
|
|
||||||
else:
|
|
||||||
region = split[8]
|
|
||||||
return {
|
|
||||||
'region': region,
|
|
||||||
'name': split[-1],
|
|
||||||
'selfLink': address
|
|
||||||
}
|
|
||||||
|
|
||||||
def _get_hostname(self, item):
|
|
||||||
'''
|
|
||||||
:param item: A host response from GCP
|
|
||||||
:return the hostname of this instance
|
|
||||||
'''
|
|
||||||
hostname_ordering = ['public_ip', 'private_ip', 'name']
|
|
||||||
if self.get_option('hostnames'):
|
|
||||||
hostname_ordering = self.get_option('hostnames')
|
|
||||||
|
|
||||||
for order in hostname_ordering:
|
|
||||||
name = None
|
|
||||||
if order == 'public_ip':
|
|
||||||
name = self._get_publicip(item)
|
|
||||||
elif order == 'private_ip':
|
|
||||||
name = self._get_privateip(item)
|
|
||||||
elif order == 'name':
|
|
||||||
name = item[u'name']
|
|
||||||
else:
|
|
||||||
raise AnsibleParserError("%s is not a valid hostname precedent" % order)
|
|
||||||
|
|
||||||
if name:
|
|
||||||
return name
|
|
||||||
|
|
||||||
raise AnsibleParserError("No valid name found for host")
|
|
||||||
|
|
||||||
def _get_publicip(self, item):
|
|
||||||
'''
|
|
||||||
:param item: A host response from GCP
|
|
||||||
:return the publicIP of this instance or None
|
|
||||||
'''
|
|
||||||
# Get public IP if exists
|
|
||||||
for interface in item['networkInterfaces']:
|
|
||||||
if 'accessConfigs' in interface:
|
|
||||||
for accessConfig in interface['accessConfigs']:
|
|
||||||
if 'natIP' in accessConfig:
|
|
||||||
return accessConfig[u'natIP']
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _get_privateip(self, item):
|
|
||||||
'''
|
|
||||||
:param item: A host response from GCP
|
|
||||||
:return the privateIP of this instance or None
|
|
||||||
'''
|
|
||||||
# Fallback: Get private IP
|
|
||||||
for interface in item[u'networkInterfaces']:
|
|
||||||
if 'networkIP' in interface:
|
|
||||||
return interface[u'networkIP']
|
|
||||||
|
|
||||||
def parse(self, inventory, loader, path, cache=True):
|
|
||||||
super(InventoryModule, self).parse(inventory, loader, path)
|
|
||||||
|
|
||||||
config_data = {}
|
|
||||||
if self.verify_file(path):
|
|
||||||
config_data = self._read_config_data(path)
|
|
||||||
|
|
||||||
# get user specifications
|
|
||||||
if 'zones' in config_data:
|
|
||||||
if not isinstance(config_data['zones'], list):
|
|
||||||
raise AnsibleParserError("Zones must be a list in GCP inventory YAML files")
|
|
||||||
|
|
||||||
# get user specifications
|
|
||||||
if 'projects' not in config_data:
|
|
||||||
raise AnsibleParserError("Projects must be included in inventory YAML file")
|
|
||||||
|
|
||||||
if not isinstance(config_data['projects'], list):
|
|
||||||
raise AnsibleParserError("Projects must be a list in GCP inventory YAML files")
|
|
||||||
|
|
||||||
projects = config_data['projects']
|
|
||||||
zones = config_data.get('zones')
|
|
||||||
config_data['scopes'] = ['https://www.googleapis.com/auth/compute']
|
|
||||||
|
|
||||||
query = self._get_query_options(config_data['filters'])
|
|
||||||
|
|
||||||
# Cache logic
|
|
||||||
if cache:
|
|
||||||
cache = self.get_option('cache')
|
|
||||||
cache_key = self.get_cache_key(path)
|
|
||||||
else:
|
|
||||||
cache_key = None
|
|
||||||
|
|
||||||
cache_needs_update = False
|
|
||||||
if cache:
|
|
||||||
try:
|
|
||||||
results = self.cache.get(cache_key)
|
|
||||||
for project in results:
|
|
||||||
for zone in results[project]:
|
|
||||||
self._add_hosts(results[project][zone], config_data, False)
|
|
||||||
except KeyError:
|
|
||||||
cache_needs_update = True
|
|
||||||
|
|
||||||
if not cache or cache_needs_update:
|
|
||||||
cached_data = {}
|
|
||||||
for project in projects:
|
|
||||||
cached_data[project] = {}
|
|
||||||
config_data['project'] = project
|
|
||||||
if not zones:
|
|
||||||
zones = self._get_zones(config_data)
|
|
||||||
for zone in zones:
|
|
||||||
config_data['zone'] = zone
|
|
||||||
link = self.self_link(config_data)
|
|
||||||
resp = self.fetch_list(config_data, link, query)
|
|
||||||
self._add_hosts(resp.get('items'), config_data)
|
|
||||||
cached_data[project][zone] = resp.get('items')
|
|
||||||
|
|
||||||
if cache_needs_update:
|
|
||||||
self.cache.set(cache_key, cached_data)
|
|
|
@ -1,370 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# Copyright (C) 2017 Google
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# *** AUTO GENERATED CODE *** AUTO GENERATED CODE ***
|
|
||||||
#
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# This file is automatically generated by Magic Modules and manual
|
|
||||||
# changes will be clobbered when the file is regenerated.
|
|
||||||
#
|
|
||||||
# Please read more about how to change this file at
|
|
||||||
# https://www.github.com/GoogleCloudPlatform/magic-modules
|
|
||||||
#
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
from __future__ import (absolute_import, division, print_function)
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
DOCUMENTATION = """
|
|
||||||
lookup: gcp_lookup
|
|
||||||
author:
|
|
||||||
- Google (@googlecloudplatform)
|
|
||||||
version_added: "2.6"
|
|
||||||
requirements:
|
|
||||||
- python >= 2.6
|
|
||||||
- requests >= 2.18.4
|
|
||||||
- google-auth >= 1.3.0
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- gcp
|
|
||||||
short_description: Look up and verify GCP Attributes
|
|
||||||
description:
|
|
||||||
- Describes attributes on GCP. You can specify one of the listed
|
|
||||||
attribute choices or omit it to see all attributes.
|
|
||||||
options:
|
|
||||||
attribute:
|
|
||||||
description: The attribute for which to get the value(s).
|
|
||||||
default: null
|
|
||||||
choices:
|
|
||||||
- disk_type
|
|
||||||
- license
|
|
||||||
- machine_type
|
|
||||||
- region
|
|
||||||
- zone
|
|
||||||
- instance_config
|
|
||||||
return:
|
|
||||||
description: An optional value to describe what part of the attribute should
|
|
||||||
be returned
|
|
||||||
default: null
|
|
||||||
"""
|
|
||||||
|
|
||||||
EXAMPLES = """
|
|
||||||
vars:
|
|
||||||
account_details: "{{ lookup('gcp_lookup',
|
|
||||||
attribute='region',
|
|
||||||
label='us-west1',
|
|
||||||
auth_kind='serviceaccount',
|
|
||||||
service_account_file='/tmp/my_account.json',
|
|
||||||
project='test-project',
|
|
||||||
scopes=['https://www.googleapis.com/auth/compute']) }}"
|
|
||||||
# us-west1
|
|
||||||
account_details: "{{ lookup('gcp_lookup',
|
|
||||||
attribute='region',
|
|
||||||
return='self_link',
|
|
||||||
label='us-west1',
|
|
||||||
auth_kind='serviceaccount',
|
|
||||||
service_account_file='/tmp/my_account.json',
|
|
||||||
project='test-project',
|
|
||||||
scopes=['https://www.googleapis.com/auth/compute']) }}"
|
|
||||||
# us-west1
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
RETURN = """
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Imports
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
from ansible.errors import AnsibleError
|
|
||||||
from ansible.plugins import AnsiblePlugin
|
|
||||||
from ansible.plugins.lookup import LookupBase
|
|
||||||
from ansible.module_utils.gcp_utils import navigate_hash, GcpSession, GcpRequestException
|
|
||||||
import json
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Main
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
|
|
||||||
class GcpModule(object):
|
|
||||||
def __init__(self, options):
|
|
||||||
self.params = options
|
|
||||||
if 'label' in self.params:
|
|
||||||
self.params['name'] = self.params['label']
|
|
||||||
del self.params['label']
|
|
||||||
|
|
||||||
def fail_json(self, **kwargs):
|
|
||||||
raise AnsibleError(kwargs['msg'])
|
|
||||||
|
|
||||||
|
|
||||||
class GcpDiskType(object):
|
|
||||||
def __init__(self, options):
|
|
||||||
self.module = GcpModule(options)
|
|
||||||
|
|
||||||
self.kind = 'compute#diskType'
|
|
||||||
self.link = "https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/diskTypes/{name}".format(**self.module.params)
|
|
||||||
|
|
||||||
def _fetch_resource(self):
|
|
||||||
auth = GcpSession(self.module, 'compute')
|
|
||||||
return self._return_if_object(auth.get(self.link))
|
|
||||||
|
|
||||||
def _return_if_object(self, response):
|
|
||||||
# If not found, return nothing.
|
|
||||||
if response.status_code == 404:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# If no content, return nothing.
|
|
||||||
if response.status_code == 204:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
response.raise_for_status
|
|
||||||
result = response.json()
|
|
||||||
except getattr(json.decoder, 'JSONDecodeError', ValueError) as inst:
|
|
||||||
self.module.fail_json(msg="Invalid JSON response with error: %s" % inst)
|
|
||||||
except GcpRequestException as inst:
|
|
||||||
self.module.fail_json(msg="Network error: %s" % inst)
|
|
||||||
|
|
||||||
if navigate_hash(result, ['error', 'errors']):
|
|
||||||
self.module.fail_json(msg=navigate_hash(result, ['error', 'errors']))
|
|
||||||
if result['kind'] != self.kind:
|
|
||||||
self.module.fail_json(msg="Incorrect result: {kind}".format(**result))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
response = self._fetch_resource()
|
|
||||||
if 'return' in self.module.params:
|
|
||||||
return response[self.module.params['return']]
|
|
||||||
return response['self_link']
|
|
||||||
|
|
||||||
|
|
||||||
class GcpLicense(object):
|
|
||||||
def __init__(self, options):
|
|
||||||
self.module = GcpModule(options)
|
|
||||||
|
|
||||||
self.kind = 'compute#license'
|
|
||||||
self.link = "https://www.googleapis.com/compute/v1//projects/{project}/global/licenses/{name}".format(**self.module.params)
|
|
||||||
|
|
||||||
def _fetch_resource(self):
|
|
||||||
auth = GcpSession(self.module, 'compute')
|
|
||||||
return self._return_if_object(auth.get(self.link))
|
|
||||||
|
|
||||||
def _return_if_object(self, response):
|
|
||||||
# If not found, return nothing.
|
|
||||||
if response.status_code == 404:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# If no content, return nothing.
|
|
||||||
if response.status_code == 204:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
response.raise_for_status
|
|
||||||
result = response.json()
|
|
||||||
except getattr(json.decoder, 'JSONDecodeError', ValueError) as inst:
|
|
||||||
self.module.fail_json(msg="Invalid JSON response with error: %s" % inst)
|
|
||||||
except GcpRequestException as inst:
|
|
||||||
self.module.fail_json(msg="Network error: %s" % inst)
|
|
||||||
|
|
||||||
if navigate_hash(result, ['error', 'errors']):
|
|
||||||
self.module.fail_json(msg=navigate_hash(result, ['error', 'errors']))
|
|
||||||
if result['kind'] != self.kind:
|
|
||||||
self.module.fail_json(msg="Incorrect result: {kind}".format(**result))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
response = self._fetch_resource()
|
|
||||||
if 'return' in self.module.params:
|
|
||||||
return response[self.module.params['return']]
|
|
||||||
return response['self_link']
|
|
||||||
|
|
||||||
|
|
||||||
class GcpMachineType(object):
|
|
||||||
def __init__(self, options):
|
|
||||||
self.module = GcpModule(options)
|
|
||||||
|
|
||||||
self.kind = 'compute#machineType'
|
|
||||||
self.link = "https://www.googleapis.com/compute/v1/projects/{project}/zones/{zone}/machineTypes/{name}".format(**self.module.params)
|
|
||||||
|
|
||||||
def _fetch_resource(self):
|
|
||||||
auth = GcpSession(self.module, 'compute')
|
|
||||||
return self._return_if_object(auth.get(self.link))
|
|
||||||
|
|
||||||
def _return_if_object(self, response):
|
|
||||||
# If not found, return nothing.
|
|
||||||
if response.status_code == 404:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# If no content, return nothing.
|
|
||||||
if response.status_code == 204:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
response.raise_for_status
|
|
||||||
result = response.json()
|
|
||||||
except getattr(json.decoder, 'JSONDecodeError', ValueError) as inst:
|
|
||||||
self.module.fail_json(msg="Invalid JSON response with error: %s" % inst)
|
|
||||||
except GcpRequestException as inst:
|
|
||||||
self.module.fail_json(msg="Network error: %s" % inst)
|
|
||||||
|
|
||||||
if navigate_hash(result, ['error', 'errors']):
|
|
||||||
self.module.fail_json(msg=navigate_hash(result, ['error', 'errors']))
|
|
||||||
if result['kind'] != self.kind:
|
|
||||||
self.module.fail_json(msg="Incorrect result: {kind}".format(**result))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
response = self._fetch_resource()
|
|
||||||
if 'return' in self.module.params:
|
|
||||||
return response[self.module.params['return']]
|
|
||||||
return response['name']
|
|
||||||
|
|
||||||
|
|
||||||
class GcpRegion(object):
|
|
||||||
def __init__(self, options):
|
|
||||||
self.module = GcpModule(options)
|
|
||||||
|
|
||||||
self.kind = 'compute#region'
|
|
||||||
self.link = "https://www.googleapis.com/compute/v1/projects/{project}/regions/{name}".format(**self.module.params)
|
|
||||||
|
|
||||||
def _fetch_resource(self):
|
|
||||||
auth = GcpSession(self.module, 'compute')
|
|
||||||
return self._return_if_object(auth.get(self.link))
|
|
||||||
|
|
||||||
def _return_if_object(self, response):
|
|
||||||
# If not found, return nothing.
|
|
||||||
if response.status_code == 404:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# If no content, return nothing.
|
|
||||||
if response.status_code == 204:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
response.raise_for_status
|
|
||||||
result = response.json()
|
|
||||||
except getattr(json.decoder, 'JSONDecodeError', ValueError) as inst:
|
|
||||||
self.module.fail_json(msg="Invalid JSON response with error: %s" % inst)
|
|
||||||
except GcpRequestException as inst:
|
|
||||||
self.module.fail_json(msg="Network error: %s" % inst)
|
|
||||||
|
|
||||||
if navigate_hash(result, ['error', 'errors']):
|
|
||||||
self.module.fail_json(msg=navigate_hash(result, ['error', 'errors']))
|
|
||||||
if result['kind'] != self.kind:
|
|
||||||
self.module.fail_json(msg="Incorrect result: {kind}".format(**result))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
response = self._fetch_resource()
|
|
||||||
if 'return' in self.module.params:
|
|
||||||
return response[self.module.params['return']]
|
|
||||||
return response['name']
|
|
||||||
|
|
||||||
|
|
||||||
class GcpZone(object):
|
|
||||||
def __init__(self, options):
|
|
||||||
self.module = GcpModule(options)
|
|
||||||
|
|
||||||
self.kind = 'compute#zone'
|
|
||||||
self.link = "https://www.googleapis.com/compute/v1/projects/{project}/zones/{name}".format(**self.module.params)
|
|
||||||
|
|
||||||
def _fetch_resource(self):
|
|
||||||
auth = GcpSession(self.module, 'compute')
|
|
||||||
return self._return_if_object(auth.get(self.link))
|
|
||||||
|
|
||||||
def _return_if_object(self, response):
|
|
||||||
# If not found, return nothing.
|
|
||||||
if response.status_code == 404:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# If no content, return nothing.
|
|
||||||
if response.status_code == 204:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
response.raise_for_status
|
|
||||||
result = response.json()
|
|
||||||
except getattr(json.decoder, 'JSONDecodeError', ValueError) as inst:
|
|
||||||
self.module.fail_json(msg="Invalid JSON response with error: %s" % inst)
|
|
||||||
except GcpRequestException as inst:
|
|
||||||
self.module.fail_json(msg="Network error: %s" % inst)
|
|
||||||
|
|
||||||
if navigate_hash(result, ['error', 'errors']):
|
|
||||||
self.module.fail_json(msg=navigate_hash(result, ['error', 'errors']))
|
|
||||||
if result['kind'] != self.kind:
|
|
||||||
self.module.fail_json(msg="Incorrect result: {kind}".format(**result))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
response = self._fetch_resource()
|
|
||||||
if 'return' in self.module.params:
|
|
||||||
return response[self.module.params['return']]
|
|
||||||
return response['name']
|
|
||||||
|
|
||||||
|
|
||||||
class GcpInstanceConfig(object):
|
|
||||||
def __init__(self, options):
|
|
||||||
self.module = GcpModule(options)
|
|
||||||
|
|
||||||
self.link = "https://spanner.googleapis.com/v1/projects/{project}/instanceConfigs/{name}".format(**self.module.params)
|
|
||||||
|
|
||||||
def _fetch_resource(self):
|
|
||||||
auth = GcpSession(self.module, 'spanner')
|
|
||||||
return self._return_if_object(auth.get(self.link))
|
|
||||||
|
|
||||||
def _return_if_object(self, response):
|
|
||||||
# If not found, return nothing.
|
|
||||||
if response.status_code == 404:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# If no content, return nothing.
|
|
||||||
if response.status_code == 204:
|
|
||||||
return None
|
|
||||||
|
|
||||||
try:
|
|
||||||
response.raise_for_status
|
|
||||||
result = response.json()
|
|
||||||
except getattr(json.decoder, 'JSONDecodeError', ValueError) as inst:
|
|
||||||
self.module.fail_json(msg="Invalid JSON response with error: %s" % inst)
|
|
||||||
except GcpRequestException as inst:
|
|
||||||
self.module.fail_json(msg="Network error: %s" % inst)
|
|
||||||
|
|
||||||
if navigate_hash(result, ['error', 'errors']):
|
|
||||||
self.module.fail_json(msg=navigate_hash(result, ['error', 'errors']))
|
|
||||||
if result['kind'] != self.kind:
|
|
||||||
self.module.fail_json(msg="Incorrect result: {kind}".format(**result))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
response = self._fetch_resource()
|
|
||||||
if 'return' in self.module.params:
|
|
||||||
return response[self.module.params['return']]
|
|
||||||
return response['name']
|
|
||||||
|
|
||||||
|
|
||||||
class LookupModule(LookupBase):
|
|
||||||
def run(self, terms, variables, **kwargs):
|
|
||||||
|
|
||||||
self.set_options(var_options=variables, direct=kwargs)
|
|
||||||
options = {
|
|
||||||
'disk_type': GcpDiskType,
|
|
||||||
'license': GcpLicense,
|
|
||||||
'machine_type': GcpMachineType,
|
|
||||||
'region': GcpRegion,
|
|
||||||
'zone': GcpZone,
|
|
||||||
'instance_config': GcpInstanceConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
return str(options[kwargs['attribute']](kwargs).run())
|
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"change_description": {
|
|
||||||
"changed_paths": [],
|
|
||||||
"command": "",
|
|
||||||
"deleted_paths": [],
|
|
||||||
"focused_command_targets": {},
|
|
||||||
"no_integration_paths": [],
|
|
||||||
"regular_command_targets": {}
|
|
||||||
},
|
|
||||||
"changes": {},
|
|
||||||
"ci_provider": "",
|
|
||||||
"cloud_config": null,
|
|
||||||
"instance_config": null
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
# This is the configuration template for ansible-test GCP integration tests.
|
|
||||||
#
|
|
||||||
# You do not need this template if you are:
|
|
||||||
#
|
|
||||||
# 1) Running integration tests without using ansible-test.
|
|
||||||
# 2) Using the automatically provisioned cloudstack-sim docker container in ansible-test.
|
|
||||||
#
|
|
||||||
# If you do not want to use the automatically provided GCP simulator,
|
|
||||||
# fill in the @VAR placeholders below and save this file without the .template extension.
|
|
||||||
# This will cause ansible-test to use the given configuration and not launch the simulator.
|
|
||||||
#
|
|
||||||
# It is recommended that you DO NOT use this template unless you cannot use the simulator.
|
|
||||||
|
|
||||||
gcp_project: @PROJECT
|
|
||||||
gcp_cred_file: @CRED_FILE
|
|
||||||
gcp_cred_kind: @CRED_KIND
|
|
||||||
gcp_cred_email: @CRED_EMAIL
|
|
|
@ -1,67 +0,0 @@
|
||||||
# Copyright: (c) 2018, Google Inc.
|
|
||||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
||||||
"""GCP plugin for integration tests."""
|
|
||||||
from __future__ import absolute_import, print_function
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from lib.util import (
|
|
||||||
ApplicationError,
|
|
||||||
display,
|
|
||||||
is_shippable,
|
|
||||||
)
|
|
||||||
|
|
||||||
from lib.cloud import (
|
|
||||||
CloudProvider,
|
|
||||||
CloudEnvironment,
|
|
||||||
)
|
|
||||||
|
|
||||||
from lib.core_ci import (
|
|
||||||
AnsibleCoreCI, )
|
|
||||||
|
|
||||||
|
|
||||||
class GcpCloudProvider(CloudProvider):
|
|
||||||
"""GCP cloud provider plugin. Sets up cloud resources before delegation."""
|
|
||||||
|
|
||||||
def filter(self, targets, exclude):
|
|
||||||
"""Filter out the cloud tests when the necessary config and resources are not available.
|
|
||||||
:type targets: tuple[TestTarget]
|
|
||||||
:type exclude: list[str]
|
|
||||||
"""
|
|
||||||
|
|
||||||
if os.path.isfile(self.config_static_path):
|
|
||||||
return
|
|
||||||
|
|
||||||
super(GcpCloudProvider, self).filter(targets, exclude)
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
"""Setup the cloud resource before delegation and register a cleanup callback."""
|
|
||||||
super(GcpCloudProvider, self).setup()
|
|
||||||
|
|
||||||
if not self._use_static_config():
|
|
||||||
display.notice(
|
|
||||||
'static configuration could not be used. are you missing a template file?'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GcpCloudEnvironment(CloudEnvironment):
|
|
||||||
"""GCP cloud environment plugin. Updates integration test environment after delegation."""
|
|
||||||
|
|
||||||
def configure_environment(self, env, cmd):
|
|
||||||
"""
|
|
||||||
:type env: dict[str, str]
|
|
||||||
:type cmd: list[str]
|
|
||||||
"""
|
|
||||||
cmd.append('-e')
|
|
||||||
cmd.append('@%s' % self.config_path)
|
|
||||||
|
|
||||||
cmd.append('-e')
|
|
||||||
cmd.append('resource_prefix=%s' % self.resource_prefix)
|
|
||||||
|
|
||||||
def on_failure(self, target, tries):
|
|
||||||
"""
|
|
||||||
:type target: TestTarget
|
|
||||||
:type tries: int
|
|
||||||
"""
|
|
||||||
if not tries and self.managed:
|
|
||||||
display.notice('%s failed' % target.name)
|
|
|
@ -1,189 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# (c) 2019, Google Inc.
|
|
||||||
#
|
|
||||||
# This file is part of Ansible
|
|
||||||
#
|
|
||||||
# Ansible is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Ansible is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from pytest import importorskip
|
|
||||||
from units.compat import unittest
|
|
||||||
from units.compat.mock import patch
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from ansible.module_utils.gcp_utils import GcpSession
|
|
||||||
import responses
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
importorskip("requests")
|
|
||||||
importorskip("google.auth")
|
|
||||||
importorskip("responses")
|
|
||||||
|
|
||||||
from google.auth.credentials import AnonymousCredentials
|
|
||||||
|
|
||||||
|
|
||||||
class FakeModule(object):
|
|
||||||
def __init__(self, params):
|
|
||||||
self.params = params
|
|
||||||
|
|
||||||
def fail_json(self, **kwargs):
|
|
||||||
raise kwargs["msg"]
|
|
||||||
|
|
||||||
|
|
||||||
class GcpSessionTestCase(unittest.TestCase):
|
|
||||||
success_json = {"status": "SUCCESS"}
|
|
||||||
user_agent = "Google-Ansible-MM-mock"
|
|
||||||
url = "http://www.googleapis.com/compute/test_instance"
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def setup_auth(self):
|
|
||||||
"""
|
|
||||||
This is a context manager that mocks out
|
|
||||||
the google-auth library and uses the built-in
|
|
||||||
AnonymousCredentials for sending requests.
|
|
||||||
"""
|
|
||||||
with patch(
|
|
||||||
"google.oauth2.service_account.Credentials.from_service_account_file"
|
|
||||||
) as mock:
|
|
||||||
with patch.object(
|
|
||||||
AnonymousCredentials, "with_scopes", create=True
|
|
||||||
) as mock2:
|
|
||||||
creds = AnonymousCredentials()
|
|
||||||
mock2.return_value = creds
|
|
||||||
mock.return_value = creds
|
|
||||||
yield
|
|
||||||
|
|
||||||
@responses.activate
|
|
||||||
def test_get(self):
|
|
||||||
responses.add(responses.GET, self.url, status=200, json=self.success_json)
|
|
||||||
|
|
||||||
with self.setup_auth():
|
|
||||||
module = FakeModule(
|
|
||||||
{
|
|
||||||
"scopes": "foo",
|
|
||||||
"service_account_file": "file_name",
|
|
||||||
"project": "test_project",
|
|
||||||
"auth_kind": "serviceaccount",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
session = GcpSession(module, "mock")
|
|
||||||
resp = session.get(self.url)
|
|
||||||
|
|
||||||
assert responses.calls[0].request.headers["User-Agent"] == self.user_agent
|
|
||||||
assert resp.json() == self.success_json
|
|
||||||
assert resp.status_code == 200
|
|
||||||
|
|
||||||
@responses.activate
|
|
||||||
def test_post(self):
|
|
||||||
responses.add(responses.POST, self.url, status=200, json=self.success_json)
|
|
||||||
|
|
||||||
with self.setup_auth():
|
|
||||||
body = {"content": "some_content"}
|
|
||||||
module = FakeModule(
|
|
||||||
{
|
|
||||||
"scopes": "foo",
|
|
||||||
"service_account_file": "file_name",
|
|
||||||
"project": "test_project",
|
|
||||||
"auth_kind": "serviceaccount",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
session = GcpSession(module, "mock")
|
|
||||||
resp = session.post(
|
|
||||||
self.url, body=body, headers={"x-added-header": "my-header"}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ensure Google header added.
|
|
||||||
assert responses.calls[0].request.headers["User-Agent"] == self.user_agent
|
|
||||||
|
|
||||||
# Ensure all content was passed along.
|
|
||||||
assert responses.calls[0].request.headers["x-added-header"] == "my-header"
|
|
||||||
|
|
||||||
# Ensure proper request was made.
|
|
||||||
assert resp.json() == self.success_json
|
|
||||||
assert resp.status_code == 200
|
|
||||||
|
|
||||||
@responses.activate
|
|
||||||
def test_delete(self):
|
|
||||||
responses.add(responses.DELETE, self.url, status=200, json=self.success_json)
|
|
||||||
|
|
||||||
with self.setup_auth():
|
|
||||||
body = {"content": "some_content"}
|
|
||||||
module = FakeModule(
|
|
||||||
{
|
|
||||||
"scopes": "foo",
|
|
||||||
"service_account_file": "file_name",
|
|
||||||
"project": "test_project",
|
|
||||||
"auth_kind": "serviceaccount",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
session = GcpSession(module, "mock")
|
|
||||||
resp = session.delete(self.url)
|
|
||||||
|
|
||||||
# Ensure Google header added.
|
|
||||||
assert responses.calls[0].request.headers["User-Agent"] == self.user_agent
|
|
||||||
|
|
||||||
# Ensure proper request was made.
|
|
||||||
assert resp.json() == self.success_json
|
|
||||||
assert resp.status_code == 200
|
|
||||||
|
|
||||||
@responses.activate
|
|
||||||
def test_put(self):
|
|
||||||
responses.add(responses.PUT, self.url, status=200, json=self.success_json)
|
|
||||||
|
|
||||||
with self.setup_auth():
|
|
||||||
body = {"content": "some_content"}
|
|
||||||
module = FakeModule(
|
|
||||||
{
|
|
||||||
"scopes": "foo",
|
|
||||||
"service_account_file": "file_name",
|
|
||||||
"project": "test_project",
|
|
||||||
"auth_kind": "serviceaccount",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
session = GcpSession(module, "mock")
|
|
||||||
resp = session.put(self.url, body={"foo": "bar"})
|
|
||||||
|
|
||||||
# Ensure Google header added.
|
|
||||||
assert responses.calls[0].request.headers["User-Agent"] == self.user_agent
|
|
||||||
|
|
||||||
# Ensure proper request was made.
|
|
||||||
assert resp.json() == self.success_json
|
|
||||||
assert resp.status_code == 200
|
|
||||||
|
|
||||||
@responses.activate
|
|
||||||
def test_patch(self):
|
|
||||||
responses.add(responses.PATCH, self.url, status=200, json=self.success_json)
|
|
||||||
|
|
||||||
with self.setup_auth():
|
|
||||||
body = {"content": "some_content"}
|
|
||||||
module = FakeModule(
|
|
||||||
{
|
|
||||||
"scopes": "foo",
|
|
||||||
"service_account_file": "file_name",
|
|
||||||
"project": "test_project",
|
|
||||||
"auth_kind": "serviceaccount",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
session = GcpSession(module, "mock")
|
|
||||||
resp = session.patch(self.url, body={"foo": "bar"})
|
|
||||||
|
|
||||||
# Ensure Google header added.
|
|
||||||
assert responses.calls[0].request.headers["User-Agent"] == self.user_agent
|
|
||||||
|
|
||||||
# Ensure proper request was made.
|
|
||||||
assert resp.json() == self.success_json
|
|
||||||
assert resp.status_code == 200
|
|
|
@ -1,173 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# (c) 2016, Tom Melendez <tom@supertom.com>
|
|
||||||
#
|
|
||||||
# This file is part of Ansible
|
|
||||||
#
|
|
||||||
# Ansible is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Ansible is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from ansible.compat.tests import mock, unittest
|
|
||||||
from ansible.module_utils.gcp_utils import GcpRequest
|
|
||||||
|
|
||||||
|
|
||||||
class GCPRequestDifferenceTestCase(unittest.TestCase):
|
|
||||||
def test_simple_no_difference(self):
|
|
||||||
value1 = {
|
|
||||||
'foo': 'bar',
|
|
||||||
'test': 'original'
|
|
||||||
}
|
|
||||||
request = GcpRequest(value1)
|
|
||||||
self.assertEquals(request == request, True)
|
|
||||||
|
|
||||||
def test_simple_different(self):
|
|
||||||
value1 = {
|
|
||||||
'foo': 'bar',
|
|
||||||
'test': 'original'
|
|
||||||
}
|
|
||||||
value2 = {
|
|
||||||
'foo': 'bar',
|
|
||||||
'test': 'different'
|
|
||||||
}
|
|
||||||
difference = {
|
|
||||||
'test': 'original'
|
|
||||||
}
|
|
||||||
request1 = GcpRequest(value1)
|
|
||||||
request2 = GcpRequest(value2)
|
|
||||||
self.assertEquals(request1 == request2, False)
|
|
||||||
self.assertEquals(request1.difference(request2), difference)
|
|
||||||
|
|
||||||
def test_nested_dictionaries_no_difference(self):
|
|
||||||
value1 = {
|
|
||||||
'foo': {
|
|
||||||
'quiet': {
|
|
||||||
'tree': 'test'
|
|
||||||
},
|
|
||||||
'bar': 'baz'
|
|
||||||
},
|
|
||||||
'test': 'original'
|
|
||||||
}
|
|
||||||
request = GcpRequest(value1)
|
|
||||||
self.assertEquals(request == request, True)
|
|
||||||
|
|
||||||
def test_nested_dictionaries_with_difference(self):
|
|
||||||
value1 = {
|
|
||||||
'foo': {
|
|
||||||
'quiet': {
|
|
||||||
'tree': 'test'
|
|
||||||
},
|
|
||||||
'bar': 'baz'
|
|
||||||
},
|
|
||||||
'test': 'original'
|
|
||||||
}
|
|
||||||
value2 = {
|
|
||||||
'foo': {
|
|
||||||
'quiet': {
|
|
||||||
'tree': 'baz'
|
|
||||||
},
|
|
||||||
'bar': 'hello'
|
|
||||||
},
|
|
||||||
'test': 'original'
|
|
||||||
}
|
|
||||||
difference = {
|
|
||||||
'foo': {
|
|
||||||
'quiet': {
|
|
||||||
'tree': 'test'
|
|
||||||
},
|
|
||||||
'bar': 'baz'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
request1 = GcpRequest(value1)
|
|
||||||
request2 = GcpRequest(value2)
|
|
||||||
self.assertEquals(request1 == request2, False)
|
|
||||||
self.assertEquals(request1.difference(request2), difference)
|
|
||||||
|
|
||||||
def test_arrays_strings_no_difference(self):
|
|
||||||
value1 = {
|
|
||||||
'foo': [
|
|
||||||
'baz',
|
|
||||||
'bar'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
request = GcpRequest(value1)
|
|
||||||
self.assertEquals(request == request, True)
|
|
||||||
|
|
||||||
def test_arrays_strings_with_difference(self):
|
|
||||||
value1 = {
|
|
||||||
'foo': [
|
|
||||||
'baz',
|
|
||||||
'bar',
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
value2 = {
|
|
||||||
'foo': [
|
|
||||||
'baz',
|
|
||||||
'hello'
|
|
||||||
]
|
|
||||||
}
|
|
||||||
difference = {
|
|
||||||
'foo': [
|
|
||||||
'bar',
|
|
||||||
]
|
|
||||||
}
|
|
||||||
request1 = GcpRequest(value1)
|
|
||||||
request2 = GcpRequest(value2)
|
|
||||||
self.assertEquals(request1 == request2, False)
|
|
||||||
self.assertEquals(request1.difference(request2), difference)
|
|
||||||
|
|
||||||
def test_arrays_dicts_with_no_difference(self):
|
|
||||||
value1 = {
|
|
||||||
'foo': [
|
|
||||||
{
|
|
||||||
'test': 'value',
|
|
||||||
'foo': 'bar'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'different': 'dict'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
request = GcpRequest(value1)
|
|
||||||
self.assertEquals(request == request, True)
|
|
||||||
|
|
||||||
def test_arrays_dicts_with_difference(self):
|
|
||||||
value1 = {
|
|
||||||
'foo': [
|
|
||||||
{
|
|
||||||
'test': 'value',
|
|
||||||
'foo': 'bar'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
value2 = {
|
|
||||||
'foo': [
|
|
||||||
{
|
|
||||||
'test': 'value2',
|
|
||||||
'foo': 'bar2'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
difference = {
|
|
||||||
'foo': [
|
|
||||||
{
|
|
||||||
'test': 'value',
|
|
||||||
'foo': 'bar'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
request1 = GcpRequest(value1)
|
|
||||||
request2 = GcpRequest(value2)
|
|
||||||
self.assertEquals(request1 == request2, False)
|
|
||||||
self.assertEquals(request1.difference(request2), difference)
|
|
Loading…
Add table
Add a link
Reference in a new issue