diff --git a/changelogs/fragments/remove-deprecated-modules-2.yml b/changelogs/fragments/remove-deprecated-modules-2.yml new file mode 100644 index 0000000000..488c2b35e3 --- /dev/null +++ b/changelogs/fragments/remove-deprecated-modules-2.yml @@ -0,0 +1,10 @@ +removed_features: +- The deprecated ``gcdns_record`` module has been removed. Use ``google.cloud.gcp_dns_resource_record_set`` instead (https://github.com/ansible-collections/community.general/pull/1370). +- The deprecated ``gcdns_zone`` module has been removed. Use ``google.cloud.gcp_dns_managed_zone`` instead (https://github.com/ansible-collections/community.general/pull/1370). +- The deprecated ``gce`` module has been removed. Use ``google.cloud.gcp_compute_instance`` instead (https://github.com/ansible-collections/community.general/pull/1370). +- The deprecated ``gcp_backend_service`` module has been removed. Use ``google.cloud.gcp_compute_backend_service`` instead (https://github.com/ansible-collections/community.general/pull/1370). +- The deprecated ``gcp_forwarding_rule`` module has been removed. Use ``google.cloud.gcp_compute_forwarding_rule`` or ``google.cloud.gcp_compute_global_forwarding_rule`` instead (https://github.com/ansible-collections/community.general/pull/1370). +- The deprecated ``gcp_healthcheck`` module has been removed. Use ``google.cloud.gcp_compute_health_check``, ``google.cloud.gcp_compute_http_health_check`` or ``google.cloud.gcp_compute_https_health_check`` instead (https://github.com/ansible-collections/community.general/pull/1370). +- The deprecated ``gcp_target_proxy`` module has been removed. Use ``google.cloud.gcp_compute_target_http_proxy`` instead (https://github.com/ansible-collections/community.general/pull/1370). +- The deprecated ``gcp_url_map`` module has been removed. Use ``google.cloud.gcp_compute_url_map`` instead (https://github.com/ansible-collections/community.general/pull/1370). +- The deprecated ``gcspanner`` module has been removed. Use ``google.cloud.gcp_spanner_database`` and/or ``google.cloud.gcp_spanner_instance`` instead (https://github.com/ansible-collections/community.general/pull/1370). diff --git a/meta/runtime.yml b/meta/runtime.yml index 578e9f16e6..31ad5df533 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -101,45 +101,45 @@ plugin_routing: removal_version: 2.0.0 warning_text: Use the modules from the theforeman.foreman collection instead. gcdns_record: - deprecation: + tombstone: removal_version: 2.0.0 - warning_text: see plugin documentation for details + warning_text: Use google.cloud.gcp_dns_resource_record_set instead. gcdns_zone: - deprecation: + tombstone: removal_version: 2.0.0 - warning_text: see plugin documentation for details + warning_text: Use google.cloud.gcp_dns_managed_zone instead. gce: - deprecation: + tombstone: removal_version: 2.0.0 - warning_text: see plugin documentation for details + warning_text: Use google.cloud.gcp_compute_instance instead. gcp_backend_service: - deprecation: + tombstone: removal_version: 2.0.0 - warning_text: see plugin documentation for details + warning_text: Use google.cloud.gcp_compute_backend_service instead. gcp_forwarding_rule: - deprecation: + tombstone: removal_version: 2.0.0 - warning_text: see plugin documentation for details + warning_text: Use google.cloud.gcp_compute_forwarding_rule or google.cloud.gcp_compute_global_forwarding_rule instead. gcp_healthcheck: - deprecation: + tombstone: removal_version: 2.0.0 - warning_text: see plugin documentation for details + warning_text: Use google.cloud.gcp_compute_health_check, google.cloud.gcp_compute_http_health_check or google.cloud.gcp_compute_https_health_check instead. gcp_target_proxy: - deprecation: + tombstone: removal_version: 2.0.0 - warning_text: see plugin documentation for details + warning_text: Use google.cloud.gcp_compute_target_http_proxy instead. gcp_url_map: - deprecation: + tombstone: removal_version: 2.0.0 - warning_text: see plugin documentation for details + warning_text: Use google.cloud.gcp_compute_url_map instead. gcpubsub_facts: deprecation: removal_version: 3.0.0 warning_text: see plugin documentation for details gcspanner: - deprecation: + tombstone: removal_version: 2.0.0 - warning_text: see plugin documentation for details + warning_text: Use google.cloud.gcp_spanner_database and/or google.cloud.gcp_spanner_instance instead. github_hooks: tombstone: removal_version: 2.0.0 diff --git a/plugins/modules/cloud/google/gcdns_record.py b/plugins/modules/cloud/google/gcdns_record.py deleted file mode 100644 index b97377b2a3..0000000000 --- a/plugins/modules/cloud/google/gcdns_record.py +++ /dev/null @@ -1,780 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2015 CallFire Inc. -# 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 -################################################################################ - -DOCUMENTATION = ''' ---- -module: gcdns_record -short_description: Creates or removes resource records in Google Cloud DNS -description: - - Creates or removes resource records in Google Cloud DNS. -author: "William Albert (@walbert947)" -requirements: - - "python >= 2.6" - - "apache-libcloud >= 0.19.0" -deprecated: - removed_in: 2.0.0 # was Ansible 2.12 - why: Updated modules released with increased functionality - alternative: Use M(google.cloud.gcp_dns_resource_record_set) instead. -options: - state: - type: str - description: - - Whether the given resource record should or should not be present. - choices: ["present", "absent"] - default: "present" - record: - type: str - description: - - The fully-qualified domain name of the resource record. - required: true - aliases: ['name'] - zone: - type: str - description: - - The DNS domain name of the zone (e.g., example.com). - - One of either I(zone) or I(zone_id) must be specified as an - option, or the module will fail. - - If both I(zone) and I(zone_id) are specified, I(zone_id) will be - used. - zone_id: - type: str - description: - - The Google Cloud ID of the zone (e.g., example-com). - - One of either I(zone) or I(zone_id) must be specified as an - option, or the module will fail. - - These usually take the form of domain names with the dots replaced - with dashes. A zone ID will never have any dots in it. - - I(zone_id) can be faster than I(zone) in projects with a large - number of zones. - - If both I(zone) and I(zone_id) are specified, I(zone_id) will be - used. - type: - type: str - description: - - The type of resource record to add. - required: true - choices: [ 'A', 'AAAA', 'CNAME', 'SRV', 'TXT', 'SOA', 'NS', 'MX', 'SPF', 'PTR' ] - record_data: - type: list - description: - - The record_data to use for the resource record. - - I(record_data) must be specified if I(state) is C(present) or - I(overwrite) is C(True), or the module will fail. - - Valid record_data vary based on the record's I(type). In addition, - resource records that contain a DNS domain name in the value - field (e.g., CNAME, PTR, SRV, .etc) MUST include a trailing dot - in the value. - - Individual string record_data for TXT records must be enclosed in - double quotes. - - For resource records that have the same name but different - record_data (e.g., multiple A records), they must be defined as - multiple list entries in a single record. - required: false - aliases: ['value'] - ttl: - type: int - description: - - The amount of time in seconds that a resource record will remain - cached by a caching resolver. - default: 300 - overwrite: - description: - - Whether an attempt to overwrite an existing record should succeed - or fail. The behavior of this option depends on I(state). - - If I(state) is C(present) and I(overwrite) is C(True), this - module will replace an existing resource record of the same name - with the provided I(record_data). If I(state) is C(present) and - I(overwrite) is C(False), this module will fail if there is an - existing resource record with the same name and type, but - different resource data. - - If I(state) is C(absent) and I(overwrite) is C(True), this - module will remove the given resource record unconditionally. - If I(state) is C(absent) and I(overwrite) is C(False), this - module will fail if the provided record_data do not match exactly - with the existing resource record's record_data. - type: bool - default: 'no' - service_account_email: - type: str - description: - - The e-mail address for a service account with access to Google - Cloud DNS. - pem_file: - type: path - description: - - The path to the PEM file associated with the service account - email. - - This option is deprecated and may be removed in a future release. - Use I(credentials_file) instead. - credentials_file: - type: path - description: - - The path to the JSON file associated with the service account - email. - project_id: - type: str - description: - - The Google Cloud Platform project ID to use. -notes: - - See also M(community.general.gcdns_zone). - - This modules's underlying library does not support in-place updates for - DNS resource records. Instead, resource records are quickly deleted and - recreated. - - SOA records are technically supported, but their functionality is limited - to verifying that a zone's existing SOA record matches a pre-determined - value. The SOA record cannot be updated. - - Root NS records cannot be updated. - - NAPTR records are not supported. -''' - -EXAMPLES = ''' -- name: Create an A record - community.general.gcdns_record: - record: 'www1.example.com' - zone: 'example.com' - type: A - value: '1.2.3.4' - -- name: Update an existing record - community.general.gcdns_record: - record: 'www1.example.com' - zone: 'example.com' - type: A - overwrite: true - value: '5.6.7.8' - -- name: Remove an A record - community.general.gcdns_record: - record: 'www1.example.com' - zone_id: 'example-com' - state: absent - type: A - value: '5.6.7.8' - -- name: Create a CNAME record. Note the trailing dot of value - community.general.gcdns_record: - record: 'www.example.com' - zone_id: 'example-com' - type: CNAME - value: 'www.example.com.' - -- name: Create an MX record with a custom TTL. Note the trailing dot of value - community.general.gcdns_record: - record: 'example.com' - zone: 'example.com' - type: MX - ttl: 3600 - value: '10 mail.example.com.' - -- name: Create multiple A records with the same name - community.general.gcdns_record: - record: 'api.example.com' - zone_id: 'example-com' - type: A - record_data: - - '192.0.2.23' - - '10.4.5.6' - - '198.51.100.5' - - '203.0.113.10' - -- name: Change the value of an existing record with multiple record_data - community.general.gcdns_record: - record: 'api.example.com' - zone: 'example.com' - type: A - overwrite: true - record_data: # WARNING: All values in a record will be replaced - - '192.0.2.23' - - '192.0.2.42' # The changed record - - '198.51.100.5' - - '203.0.113.10' - -- name: Safely remove a multi-line record - community.general.gcdns_record: - record: 'api.example.com' - zone_id: 'example-com' - state: absent - type: A - record_data: # NOTE: All of the values must match exactly - - '192.0.2.23' - - '192.0.2.42' - - '198.51.100.5' - - '203.0.113.10' - -- name: Unconditionally remove a record - community.general.gcdns_record: - record: 'api.example.com' - zone_id: 'example-com' - state: absent - overwrite: true # overwrite is true, so no values are needed - type: A - -- name: Create an AAAA record - community.general.gcdns_record: - record: 'www1.example.com' - zone: 'example.com' - type: AAAA - value: 'fd00:db8::1' - -- name: Create a PTR record - community.general.gcdns_record: - record: '10.5.168.192.in-addr.arpa' - zone: '5.168.192.in-addr.arpa' - type: PTR - value: 'api.example.com.' # Note the trailing dot. - -- name: Create an NS record - community.general.gcdns_record: - record: 'subdomain.example.com' - zone: 'example.com' - type: NS - ttl: 21600 - record_data: - - 'ns-cloud-d1.googledomains.com.' # Note the trailing dots on values - - 'ns-cloud-d2.googledomains.com.' - - 'ns-cloud-d3.googledomains.com.' - - 'ns-cloud-d4.googledomains.com.' - -- name: Create a TXT record - community.general.gcdns_record: - record: 'example.com' - zone_id: 'example-com' - type: TXT - record_data: - - '"v=spf1 include:_spf.google.com -all"' # A single-string TXT value - - '"hello " "world"' # A multi-string TXT value -''' - -RETURN = ''' -overwrite: - description: Whether to the module was allowed to overwrite the record - returned: success - type: bool - sample: True -record: - description: Fully-qualified domain name of the resource record - returned: success - type: str - sample: mail.example.com. -state: - description: Whether the record is present or absent - returned: success - type: str - sample: present -ttl: - description: The time-to-live of the resource record - returned: success - type: int - sample: 300 -type: - description: The type of the resource record - returned: success - type: str - sample: A -record_data: - description: The resource record values - returned: success - type: list - sample: ['5.6.7.8', '9.10.11.12'] -zone: - description: The dns name of the zone - returned: success - type: str - sample: example.com. -zone_id: - description: The Google Cloud DNS ID of the zone - returned: success - type: str - sample: example-com -''' - - -################################################################################ -# Imports -################################################################################ - -import socket -from distutils.version import LooseVersion - -try: - from libcloud import __version__ as LIBCLOUD_VERSION - from libcloud.common.google import InvalidRequestError - from libcloud.common.types import LibcloudError - from libcloud.dns.types import Provider - from libcloud.dns.types import RecordDoesNotExistError - from libcloud.dns.types import ZoneDoesNotExistError - HAS_LIBCLOUD = True - # The libcloud Google Cloud DNS provider. - PROVIDER = Provider.GOOGLE -except ImportError: - HAS_LIBCLOUD = False - PROVIDER = None - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.community.general.plugins.module_utils.gcdns import gcdns_connect - - -################################################################################ -# Constants -################################################################################ - -# Apache libcloud 0.19.0 was the first to contain the non-beta Google Cloud DNS -# v1 API. Earlier versions contained the beta v1 API, which has since been -# deprecated and decommissioned. -MINIMUM_LIBCLOUD_VERSION = '0.19.0' - -# The records that libcloud's Google Cloud DNS provider supports. -# -# Libcloud has a RECORD_TYPE_MAP dictionary in the provider that also contains -# this information and is the authoritative source on which records are -# supported, but accessing the dictionary requires creating a Google Cloud DNS -# driver object, which is done in a helper module. -# -# I'm hard-coding the supported record types here, because they (hopefully!) -# shouldn't change much, and it allows me to use it as a "choices" parameter -# in an AnsibleModule argument_spec. -SUPPORTED_RECORD_TYPES = ['A', 'AAAA', 'CNAME', 'SRV', 'TXT', 'SOA', 'NS', 'MX', 'SPF', 'PTR'] - - -################################################################################ -# Functions -################################################################################ - -def create_record(module, gcdns, zone, record): - """Creates or overwrites a resource record.""" - - overwrite = module.boolean(module.params['overwrite']) - record_name = module.params['record'] - record_type = module.params['type'] - ttl = module.params['ttl'] - record_data = module.params['record_data'] - data = dict(ttl=ttl, rrdatas=record_data) - - # Google Cloud DNS wants the trailing dot on all DNS names. - if record_name[-1] != '.': - record_name = record_name + '.' - - # If we found a record, we need to check if the values match. - if record is not None: - # If the record matches, we obviously don't have to change anything. - if _records_match(record.data['ttl'], record.data['rrdatas'], ttl, record_data): - return False - - # The record doesn't match, so we need to check if we can overwrite it. - if not overwrite: - module.fail_json( - msg='cannot overwrite existing record, overwrite protection enabled', - changed=False - ) - - # The record either doesn't exist, or it exists and we can overwrite it. - if record is None and not module.check_mode: - # There's no existing record, so we'll just create it. - try: - gcdns.create_record(record_name, zone, record_type, data) - except InvalidRequestError as error: - if error.code == 'invalid': - # The resource record name and type are valid by themselves, but - # not when combined (e.g., an 'A' record with "www.example.com" - # as its value). - module.fail_json( - msg='value is invalid for the given type: ' + - "%s, got value: %s" % (record_type, record_data), - changed=False - ) - - elif error.code == 'cnameResourceRecordSetConflict': - # We're attempting to create a CNAME resource record when we - # already have another type of resource record with the name - # domain name. - module.fail_json( - msg="non-CNAME resource record already exists: %s" % record_name, - changed=False - ) - - else: - # The error is something else that we don't know how to handle, - # so we'll just re-raise the exception. - raise - - elif record is not None and not module.check_mode: - # The Google provider in libcloud doesn't support updating a record in - # place, so if the record already exists, we need to delete it and - # recreate it using the new information. - gcdns.delete_record(record) - - try: - gcdns.create_record(record_name, zone, record_type, data) - except InvalidRequestError: - # Something blew up when creating the record. This will usually be a - # result of invalid value data in the new record. Unfortunately, we - # already changed the state of the record by deleting the old one, - # so we'll try to roll back before failing out. - try: - gcdns.create_record(record.name, record.zone, record.type, record.data) - module.fail_json( - msg='error updating record, the original record was restored', - changed=False - ) - except LibcloudError: - # We deleted the old record, couldn't create the new record, and - # couldn't roll back. That really sucks. We'll dump the original - # record to the failure output so the user can restore it if - # necessary. - module.fail_json( - msg='error updating record, and could not restore original record, ' + - "original name: %s " % record.name + - "original zone: %s " % record.zone + - "original type: %s " % record.type + - "original data: %s" % record.data, - changed=True) - - return True - - -def remove_record(module, gcdns, record): - """Remove a resource record.""" - - overwrite = module.boolean(module.params['overwrite']) - ttl = module.params['ttl'] - record_data = module.params['record_data'] - - # If there is no record, we're obviously done. - if record is None: - return False - - # If there is an existing record, do our values match the values of the - # existing record? - if not overwrite: - if not _records_match(record.data['ttl'], record.data['rrdatas'], ttl, record_data): - module.fail_json( - msg='cannot delete due to non-matching ttl or record_data: ' + - "ttl: %d, record_data: %s " % (ttl, record_data) + - "original ttl: %d, original record_data: %s" % (record.data['ttl'], record.data['rrdatas']), - changed=False - ) - - # If we got to this point, we're okay to delete the record. - if not module.check_mode: - gcdns.delete_record(record) - - return True - - -def _get_record(gcdns, zone, record_type, record_name): - """Gets the record object for a given FQDN.""" - - # The record ID is a combination of its type and FQDN. For example, the - # ID of an A record for www.example.com would be 'A:www.example.com.' - record_id = "%s:%s" % (record_type, record_name) - - try: - return gcdns.get_record(zone.id, record_id) - except RecordDoesNotExistError: - return None - - -def _get_zone(gcdns, zone_name, zone_id): - """Gets the zone object for a given domain name.""" - - if zone_id is not None: - try: - return gcdns.get_zone(zone_id) - except ZoneDoesNotExistError: - return None - - # To create a zone, we need to supply a domain name. However, to delete a - # zone, we need to supply a zone ID. Zone ID's are often based on domain - # names, but that's not guaranteed, so we'll iterate through the list of - # zones to see if we can find a matching domain name. - available_zones = gcdns.iterate_zones() - found_zone = None - - for zone in available_zones: - if zone.domain == zone_name: - found_zone = zone - break - - return found_zone - - -def _records_match(old_ttl, old_record_data, new_ttl, new_record_data): - """Checks to see if original and new TTL and values match.""" - - matches = True - - if old_ttl != new_ttl: - matches = False - if old_record_data != new_record_data: - matches = False - - return matches - - -def _sanity_check(module): - """Run sanity checks that don't depend on info from the zone/record.""" - - overwrite = module.params['overwrite'] - record_name = module.params['record'] - record_type = module.params['type'] - state = module.params['state'] - ttl = module.params['ttl'] - record_data = module.params['record_data'] - - # Apache libcloud needs to be installed and at least the minimum version. - if not HAS_LIBCLOUD: - module.fail_json( - msg='This module requires Apache libcloud %s or greater' % MINIMUM_LIBCLOUD_VERSION, - changed=False - ) - elif LooseVersion(LIBCLOUD_VERSION) < MINIMUM_LIBCLOUD_VERSION: - module.fail_json( - msg='This module requires Apache libcloud %s or greater' % MINIMUM_LIBCLOUD_VERSION, - changed=False - ) - - # A negative TTL is not permitted (how would they even work?!). - if ttl < 0: - module.fail_json( - msg='TTL cannot be less than zero, got: %d' % ttl, - changed=False - ) - - # Deleting SOA records is not permitted. - if record_type == 'SOA' and state == 'absent': - module.fail_json(msg='cannot delete SOA records', changed=False) - - # Updating SOA records is not permitted. - if record_type == 'SOA' and state == 'present' and overwrite: - module.fail_json(msg='cannot update SOA records', changed=False) - - # Some sanity checks depend on what value was supplied. - if record_data is not None and (state == 'present' or not overwrite): - # A records must contain valid IPv4 addresses. - if record_type == 'A': - for value in record_data: - try: - socket.inet_aton(value) - except socket.error: - module.fail_json( - msg='invalid A record value, got: %s' % value, - changed=False - ) - - # AAAA records must contain valid IPv6 addresses. - if record_type == 'AAAA': - for value in record_data: - try: - socket.inet_pton(socket.AF_INET6, value) - except socket.error: - module.fail_json( - msg='invalid AAAA record value, got: %s' % value, - changed=False - ) - - # CNAME and SOA records can't have multiple values. - if record_type in ['CNAME', 'SOA'] and len(record_data) > 1: - module.fail_json( - msg='CNAME or SOA records cannot have more than one value, ' + - "got: %s" % record_data, - changed=False - ) - - # Google Cloud DNS does not support wildcard NS records. - if record_type == 'NS' and record_name[0] == '*': - module.fail_json( - msg="wildcard NS records not allowed, got: %s" % record_name, - changed=False - ) - - # Values for txt records must begin and end with a double quote. - if record_type == 'TXT': - for value in record_data: - if value[0] != '"' and value[-1] != '"': - module.fail_json( - msg='TXT record_data must be enclosed in double quotes, ' + - 'got: %s' % value, - changed=False - ) - - -def _additional_sanity_checks(module, zone): - """Run input sanity checks that depend on info from the zone/record.""" - - overwrite = module.params['overwrite'] - record_name = module.params['record'] - record_type = module.params['type'] - state = module.params['state'] - - # CNAME records are not allowed to have the same name as the root domain. - if record_type == 'CNAME' and record_name == zone.domain: - module.fail_json( - msg='CNAME records cannot match the zone name', - changed=False - ) - - # The root domain must always have an NS record. - if record_type == 'NS' and record_name == zone.domain and state == 'absent': - module.fail_json( - msg='cannot delete root NS records', - changed=False - ) - - # Updating NS records with the name as the root domain is not allowed - # because libcloud does not support in-place updates and root domain NS - # records cannot be removed. - if record_type == 'NS' and record_name == zone.domain and overwrite: - module.fail_json( - msg='cannot update existing root NS records', - changed=False - ) - - # SOA records with names that don't match the root domain are not permitted - # (and wouldn't make sense anyway). - if record_type == 'SOA' and record_name != zone.domain: - module.fail_json( - msg='non-root SOA records are not permitted, got: %s' % record_name, - changed=False - ) - - -################################################################################ -# Main -################################################################################ - -def main(): - """Main function""" - - module = AnsibleModule( - argument_spec=dict( - state=dict(default='present', choices=['present', 'absent'], type='str'), - record=dict(required=True, aliases=['name'], type='str'), - zone=dict(type='str'), - zone_id=dict(type='str'), - type=dict(required=True, choices=SUPPORTED_RECORD_TYPES, type='str'), - record_data=dict(aliases=['value'], type='list'), - ttl=dict(default=300, type='int'), - overwrite=dict(default=False, type='bool'), - service_account_email=dict(type='str'), - pem_file=dict(type='path'), - credentials_file=dict(type='path'), - project_id=dict(type='str') - ), - required_if=[ - ('state', 'present', ['record_data']), - ('overwrite', False, ['record_data']) - ], - required_one_of=[['zone', 'zone_id']], - supports_check_mode=True - ) - - _sanity_check(module) - - record_name = module.params['record'] - record_type = module.params['type'] - state = module.params['state'] - ttl = module.params['ttl'] - zone_name = module.params['zone'] - zone_id = module.params['zone_id'] - - json_output = dict( - state=state, - record=record_name, - zone=zone_name, - zone_id=zone_id, - type=record_type, - record_data=module.params['record_data'], - ttl=ttl, - overwrite=module.boolean(module.params['overwrite']) - ) - - # Google Cloud DNS wants the trailing dot on all DNS names. - if zone_name is not None and zone_name[-1] != '.': - zone_name = zone_name + '.' - if record_name[-1] != '.': - record_name = record_name + '.' - - # Build a connection object that we can use to connect with Google Cloud - # DNS. - gcdns = gcdns_connect(module, provider=PROVIDER) - - # We need to check that the zone we're creating a record for actually - # exists. - zone = _get_zone(gcdns, zone_name, zone_id) - if zone is None and zone_name is not None: - module.fail_json( - msg='zone name was not found: %s' % zone_name, - changed=False - ) - elif zone is None and zone_id is not None: - module.fail_json( - msg='zone id was not found: %s' % zone_id, - changed=False - ) - - # Populate the returns with the actual zone information. - json_output['zone'] = zone.domain - json_output['zone_id'] = zone.id - - # We also need to check if the record we want to create or remove actually - # exists. - try: - record = _get_record(gcdns, zone, record_type, record_name) - except InvalidRequestError: - # We gave Google Cloud DNS an invalid DNS record name. - module.fail_json( - msg='record name is invalid: %s' % record_name, - changed=False - ) - - _additional_sanity_checks(module, zone) - - diff = dict() - - # Build the 'before' diff - if record is None: - diff['before'] = '' - diff['before_header'] = '' - else: - diff['before'] = dict( - record=record.data['name'], - type=record.data['type'], - record_data=record.data['rrdatas'], - ttl=record.data['ttl'] - ) - diff['before_header'] = "%s:%s" % (record_type, record_name) - - # Create, remove, or modify the record. - if state == 'present': - diff['after'] = dict( - record=record_name, - type=record_type, - record_data=module.params['record_data'], - ttl=ttl - ) - diff['after_header'] = "%s:%s" % (record_type, record_name) - - changed = create_record(module, gcdns, zone, record) - - elif state == 'absent': - diff['after'] = '' - diff['after_header'] = '' - - changed = remove_record(module, gcdns, record) - - module.exit_json(changed=changed, diff=diff, **json_output) - - -if __name__ == '__main__': - main() diff --git a/plugins/modules/cloud/google/gcdns_zone.py b/plugins/modules/cloud/google/gcdns_zone.py deleted file mode 100644 index 6f66b5fea5..0000000000 --- a/plugins/modules/cloud/google/gcdns_zone.py +++ /dev/null @@ -1,372 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright (C) 2015 CallFire Inc. -# 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 -################################################################################ - -DOCUMENTATION = ''' ---- -module: gcdns_zone -short_description: Creates or removes zones in Google Cloud DNS -description: - - Creates or removes managed zones in Google Cloud DNS. -author: "William Albert (@walbert947)" -requirements: - - "apache-libcloud >= 0.19.0" -deprecated: - removed_in: 2.0.0 # was Ansible 2.12 - why: Updated modules released with increased functionality - alternative: Use M(google.cloud.gcp_dns_managed_zone) instead. -options: - state: - type: str - description: - - Whether the given zone should or should not be present. - choices: ["present", "absent"] - default: "present" - zone: - type: str - description: - - The DNS domain name of the zone. - - This is NOT the Google Cloud DNS zone ID (e.g., example-com). If - you attempt to specify a zone ID, this module will attempt to - create a TLD and will fail. - required: true - aliases: ['name'] - description: - type: str - description: - - An arbitrary text string to use for the zone description. - default: "" - service_account_email: - type: str - description: - - The e-mail address for a service account with access to Google - Cloud DNS. - pem_file: - type: path - description: - - The path to the PEM file associated with the service account - email. - - This option is deprecated and may be removed in a future release. - Use I(credentials_file) instead. - credentials_file: - type: path - description: - - The path to the JSON file associated with the service account - email. - project_id: - type: str - description: - - The Google Cloud Platform project ID to use. -notes: - - See also M(community.general.gcdns_record). - - Zones that are newly created must still be set up with a domain registrar - before they can be used. -''' - -EXAMPLES = ''' -# Basic zone creation example. -- name: Create a basic zone with the minimum number of parameters. - community.general.gcdns_zone: zone=example.com - -# Zone removal example. -- name: Remove a zone. - community.general.gcdns_zone: zone=example.com state=absent - -# Zone creation with description -- name: Creating a zone with a description - community.general.gcdns_zone: zone=example.com description="This is an awesome zone" -''' - -RETURN = ''' -description: - description: The zone's description - returned: success - type: str - sample: This is an awesome zone -state: - description: Whether the zone is present or absent - returned: success - type: str - sample: present -zone: - description: The zone's DNS name - returned: success - type: str - sample: example.com. -''' - - -################################################################################ -# Imports -################################################################################ - -from distutils.version import LooseVersion - -try: - from libcloud import __version__ as LIBCLOUD_VERSION - from libcloud.common.google import InvalidRequestError - from libcloud.common.google import ResourceExistsError - from libcloud.common.google import ResourceNotFoundError - from libcloud.dns.types import Provider - # The libcloud Google Cloud DNS provider. - PROVIDER = Provider.GOOGLE - HAS_LIBCLOUD = True -except ImportError: - HAS_LIBCLOUD = False - PROVIDER = None - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.community.general.plugins.module_utils.gcdns import gcdns_connect - - -################################################################################ -# Constants -################################################################################ - -# Apache libcloud 0.19.0 was the first to contain the non-beta Google Cloud DNS -# v1 API. Earlier versions contained the beta v1 API, which has since been -# deprecated and decommissioned. -MINIMUM_LIBCLOUD_VERSION = '0.19.0' - -# The URL used to verify ownership of a zone in Google Cloud DNS. -ZONE_VERIFICATION_URL = 'https://www.google.com/webmasters/verification/' - -################################################################################ -# Functions -################################################################################ - - -def create_zone(module, gcdns, zone): - """Creates a new Google Cloud DNS zone.""" - - description = module.params['description'] - extra = dict(description=description) - zone_name = module.params['zone'] - - # Google Cloud DNS wants the trailing dot on the domain name. - if zone_name[-1] != '.': - zone_name = zone_name + '.' - - # If we got a zone back, then the domain exists. - if zone is not None: - return False - - # The zone doesn't exist yet. - try: - if not module.check_mode: - gcdns.create_zone(domain=zone_name, extra=extra) - return True - - except ResourceExistsError: - # The zone already exists. We checked for this already, so either - # Google is lying, or someone was a ninja and created the zone - # within milliseconds of us checking for its existence. In any case, - # the zone has already been created, so we have nothing more to do. - return False - - except InvalidRequestError as error: - if error.code == 'invalid': - # The zone name or a parameter might be completely invalid. This is - # typically caused by an illegal DNS name (e.g. foo..com). - module.fail_json( - msg="zone name is not a valid DNS name: %s" % zone_name, - changed=False - ) - - elif error.code == 'managedZoneDnsNameNotAvailable': - # Google Cloud DNS will refuse to create zones with certain domain - # names, such as TLDs, ccTLDs, or special domain names such as - # example.com. - module.fail_json( - msg="zone name is reserved or already in use: %s" % zone_name, - changed=False - ) - - elif error.code == 'verifyManagedZoneDnsNameOwnership': - # This domain name needs to be verified before Google will create - # it. This occurs when a user attempts to create a zone which shares - # a domain name with a zone hosted elsewhere in Google Cloud DNS. - module.fail_json( - msg="ownership of zone %s needs to be verified at %s" % (zone_name, ZONE_VERIFICATION_URL), - changed=False - ) - - else: - # The error is something else that we don't know how to handle, - # so we'll just re-raise the exception. - raise - - -def remove_zone(module, gcdns, zone): - """Removes an existing Google Cloud DNS zone.""" - - # If there's no zone, then we're obviously done. - if zone is None: - return False - - # An empty zone will have two resource records: - # 1. An NS record with a list of authoritative name servers - # 2. An SOA record - # If any additional resource records are present, Google Cloud DNS will - # refuse to remove the zone. - if len(zone.list_records()) > 2: - module.fail_json( - msg="zone is not empty and cannot be removed: %s" % zone.domain, - changed=False - ) - - try: - if not module.check_mode: - gcdns.delete_zone(zone) - return True - - except ResourceNotFoundError: - # When we performed our check, the zone existed. It may have been - # deleted by something else. It's gone, so whatever. - return False - - except InvalidRequestError as error: - if error.code == 'containerNotEmpty': - # When we performed our check, the zone existed and was empty. In - # the milliseconds between the check and the removal command, - # records were added to the zone. - module.fail_json( - msg="zone is not empty and cannot be removed: %s" % zone.domain, - changed=False - ) - - else: - # The error is something else that we don't know how to handle, - # so we'll just re-raise the exception. - raise - - -def _get_zone(gcdns, zone_name): - """Gets the zone object for a given domain name.""" - - # To create a zone, we need to supply a zone name. However, to delete a - # zone, we need to supply a zone ID. Zone ID's are often based on zone - # names, but that's not guaranteed, so we'll iterate through the list of - # zones to see if we can find a matching name. - available_zones = gcdns.iterate_zones() - found_zone = None - - for zone in available_zones: - if zone.domain == zone_name: - found_zone = zone - break - - return found_zone - - -def _sanity_check(module): - """Run module sanity checks.""" - - zone_name = module.params['zone'] - - # Apache libcloud needs to be installed and at least the minimum version. - if not HAS_LIBCLOUD: - module.fail_json( - msg='This module requires Apache libcloud %s or greater' % MINIMUM_LIBCLOUD_VERSION, - changed=False - ) - elif LooseVersion(LIBCLOUD_VERSION) < MINIMUM_LIBCLOUD_VERSION: - module.fail_json( - msg='This module requires Apache libcloud %s or greater' % MINIMUM_LIBCLOUD_VERSION, - changed=False - ) - - # Google Cloud DNS does not support the creation of TLDs. - if '.' not in zone_name or len([label for label in zone_name.split('.') if label]) == 1: - module.fail_json( - msg='cannot create top-level domain: %s' % zone_name, - changed=False - ) - -################################################################################ -# Main -################################################################################ - - -def main(): - """Main function""" - - module = AnsibleModule( - argument_spec=dict( - state=dict(default='present', choices=['present', 'absent'], type='str'), - zone=dict(required=True, aliases=['name'], type='str'), - description=dict(default='', type='str'), - service_account_email=dict(type='str'), - pem_file=dict(type='path'), - credentials_file=dict(type='path'), - project_id=dict(type='str') - ), - supports_check_mode=True - ) - - _sanity_check(module) - - zone_name = module.params['zone'] - state = module.params['state'] - - # Google Cloud DNS wants the trailing dot on the domain name. - if zone_name[-1] != '.': - zone_name = zone_name + '.' - - json_output = dict( - state=state, - zone=zone_name, - description=module.params['description'] - ) - - # Build a connection object that was can use to connect with Google - # Cloud DNS. - gcdns = gcdns_connect(module, provider=PROVIDER) - - # We need to check if the zone we're attempting to create already exists. - zone = _get_zone(gcdns, zone_name) - - diff = dict() - - # Build the 'before' diff - if zone is None: - diff['before'] = '' - diff['before_header'] = '' - else: - diff['before'] = dict( - zone=zone.domain, - description=zone.extra['description'] - ) - diff['before_header'] = zone_name - - # Create or remove the zone. - if state == 'present': - diff['after'] = dict( - zone=zone_name, - description=module.params['description'] - ) - diff['after_header'] = zone_name - - changed = create_zone(module, gcdns, zone) - - elif state == 'absent': - diff['after'] = '' - diff['after_header'] = '' - - changed = remove_zone(module, gcdns, zone) - - module.exit_json(changed=changed, diff=diff, **json_output) - - -if __name__ == '__main__': - main() diff --git a/plugins/modules/cloud/google/gce.py b/plugins/modules/cloud/google/gce.py deleted file mode 100644 index 7e65878612..0000000000 --- a/plugins/modules/cloud/google/gce.py +++ /dev/null @@ -1,753 +0,0 @@ -#!/usr/bin/python -# Copyright 2013 Google Inc. -# 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 = ''' ---- -module: gce -short_description: create or terminate GCE instances -description: - - Creates or terminates Google Compute Engine (GCE) instances. See - U(https://cloud.google.com/compute) for an overview. - Full install/configuration instructions for the gce* modules can - be found in the comments of ansible/test/gce_tests.py. -deprecated: - removed_in: 2.0.0 # was Ansible 2.12 - why: Updated modules released with increased functionality - alternative: Use M(google.cloud.gcp_compute_instance) instead. -options: - image: - type: str - description: - - image string to use for the instance (default will follow latest - stable debian image) - default: "debian-8" - image_family: - type: str - description: - - image family from which to select the image. The most recent - non-deprecated image in the family will be used. - external_projects: - type: list - description: - - A list of other projects (accessible with the provisioning credentials) - to be searched for the image. - instance_names: - type: str - description: - - a comma-separated list of instance names to create or destroy - machine_type: - type: str - description: - - machine type to use for the instance, use 'n1-standard-1' by default - default: "n1-standard-1" - metadata: - type: str - description: - - a hash/dictionary of custom data for the instance; - '{"key":"value", ...}' - service_account_email: - type: str - description: - - service account email - service_account_permissions: - type: list - description: - - service account permissions (see - U(https://cloud.google.com/sdk/gcloud/reference/compute/instances/create), - --scopes section for detailed information) - - > - Available choices are: - C(bigquery), C(cloud-platform), C(compute-ro), C(compute-rw), - C(useraccounts-ro), C(useraccounts-rw), C(datastore), C(logging-write), - C(monitoring), C(sql-admin), C(storage-full), C(storage-ro), - C(storage-rw), C(taskqueue), C(userinfo-email). - pem_file: - type: path - description: - - path to the pem file associated with the service account email - This option is deprecated. Use 'credentials_file'. - credentials_file: - type: path - description: - - path to the JSON file associated with the service account email - project_id: - type: str - description: - - your GCE project ID - name: - type: str - description: - - either a name of a single instance or when used with 'num_instances', - the base name of a cluster of nodes - aliases: ['base_name'] - num_instances: - type: int - description: - - can be used with 'name', specifies - the number of nodes to provision using 'name' - as a base name - network: - type: str - description: - - name of the network, 'default' will be used if not specified - default: "default" - subnetwork: - type: str - description: - - name of the subnetwork in which the instance should be created - persistent_boot_disk: - description: - - if set, create the instance with a persistent boot disk - type: bool - default: 'no' - disks: - type: list - description: - - a list of persistent disks to attach to the instance; a string value - gives the name of the disk; alternatively, a dictionary value can - define 'name' and 'mode' ('READ_ONLY' or 'READ_WRITE'). The first entry - will be the boot disk (which must be READ_WRITE). - state: - type: str - description: - - desired state of the resource - default: "present" - choices: ["active", "present", "absent", "deleted", "started", "stopped", "terminated"] - tags: - type: list - description: - - a comma-separated list of tags to associate with the instance - zone: - type: str - description: - - the GCE zone to use. The list of available zones is at U(https://cloud.google.com/compute/docs/regions-zones/regions-zones#available). - default: "us-central1-a" - ip_forward: - description: - - set to C(yes) if the instance can forward ip packets (useful for - gateways) - type: bool - default: 'no' - external_ip: - type: str - description: - - type of external ip, ephemeral by default; alternatively, a fixed gce ip or ip name can be given. Specify 'none' if no external ip is desired. - default: "ephemeral" - disk_auto_delete: - description: - - if set boot disk will be removed after instance destruction - type: bool - default: 'yes' - preemptible: - description: - - if set to C(yes), instances will be preemptible and time-limited. - (requires libcloud >= 0.20.0) - type: bool - disk_size: - type: int - description: - - The size of the boot disk created for this instance (in GB) - default: 10 - -requirements: - - "python >= 2.6" - - "apache-libcloud >= 0.13.3, >= 0.17.0 if using JSON credentials, - >= 0.20.0 if using preemptible option" -notes: - - Either I(instance_names) or I(name) is required. - - JSON credentials strongly preferred. -author: - - Eric Johnson (@erjohnso) - - Tom Melendez (@supertom) -''' - -EXAMPLES = ''' -# Basic provisioning example. Create a single Debian 8 instance in the -# us-central1-a Zone of the n1-standard-1 machine type. -# Create multiple instances by specifying multiple names, separated by -# commas in the instance_names field -# (e.g. my-test-instance1,my-test-instance2) - - community.general.gce: - instance_names: my-test-instance1 - zone: us-central1-a - machine_type: n1-standard-1 - image: debian-8 - state: present - service_account_email: "your-sa@your-project-name.iam.gserviceaccount.com" - credentials_file: "/path/to/your-key.json" - project_id: "your-project-name" - disk_size: 32 - -# Create a single instance of an image from the "my-base-image" image family -# in the us-central1-a Zone of the n1-standard-1 machine type. -# This image family is in the "my-other-project" GCP project. - - community.general.gce: - instance_names: my-test-instance1 - zone: us-central1-a - machine_type: n1-standard-1 - image_family: my-base-image - external_projects: - - my-other-project - state: present - service_account_email: "your-sa@your-project-name.iam.gserviceaccount.com" - credentials_file: "/path/to/your-key.json" - project_id: "your-project-name" - disk_size: 32 - -# Create a single Debian 8 instance in the us-central1-a Zone -# Use existing disks, custom network/subnetwork, set service account permissions -# add tags and metadata. - - community.general.gce: - instance_names: my-test-instance - zone: us-central1-a - machine_type: n1-standard-1 - state: present - metadata: '{"db":"postgres", "group":"qa", "id":500}' - tags: - - http-server - - my-other-tag - disks: - - name: disk-2 - mode: READ_WRITE - - name: disk-3 - mode: READ_ONLY - disk_auto_delete: false - network: foobar-network - subnetwork: foobar-subnetwork-1 - preemptible: true - ip_forward: true - service_account_permissions: - - storage-full - - taskqueue - - bigquery - - https://www.googleapis.com/auth/ndev.clouddns.readwrite - service_account_email: "your-sa@your-project-name.iam.gserviceaccount.com" - credentials_file: "/path/to/your-key.json" - project_id: "your-project-name" - ---- -# Example Playbook -- name: Compute Engine Instance Examples - hosts: localhost - vars: - service_account_email: "your-sa@your-project-name.iam.gserviceaccount.com" - credentials_file: "/path/to/your-key.json" - project_id: "your-project-name" - tasks: - - name: Create multiple instances - # Basic provisioning example. Create multiple Debian 8 instances in the - # us-central1-a Zone of n1-standard-1 machine type. - community.general.gce: - instance_names: test1,test2,test3 - zone: us-central1-a - machine_type: n1-standard-1 - image: debian-8 - state: present - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - metadata : '{ "startup-script" : "apt-get update" }' - register: gce - - - name: Save host data - ansible.builtin.add_host: - hostname: "{{ item.public_ip }}" - groupname: gce_instances_ips - with_items: "{{ gce.instance_data }}" - - - name: Wait for SSH for instances - ansible.builtin.wait_for: - delay: 1 - host: "{{ item.public_ip }}" - port: 22 - state: started - timeout: 30 - with_items: "{{ gce.instance_data }}" - - - name: Configure Hosts - hosts: gce_instances_ips - become: yes - become_method: sudo - roles: - - my-role-one - - my-role-two - tags: - - config - - - name: Delete test-instances - # Basic termination of instance. - community.general.gce: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - instance_names: "{{ gce.instance_names }}" - zone: us-central1-a - state: absent - tags: - - delete -''' - -import socket -import logging - -try: - from ast import literal_eval - - HAS_PYTHON26 = True -except ImportError: - HAS_PYTHON26 = False - -try: - import libcloud - from libcloud.compute.types import Provider - from libcloud.compute.providers import get_driver - from libcloud.common.google import GoogleBaseError, QuotaExceededError, \ - ResourceExistsError, ResourceInUseError, ResourceNotFoundError - from libcloud.compute.drivers.gce import GCEAddress - - _ = Provider.GCE - HAS_LIBCLOUD = True -except ImportError: - HAS_LIBCLOUD = False - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.community.general.plugins.module_utils.gce import gce_connect, unexpected_error_msg -from ansible_collections.community.general.plugins.module_utils.gcp import get_valid_location -from ansible.module_utils.six.moves import reduce - - -def get_instance_info(inst): - """Retrieves instance information from an instance object and returns it - as a dictionary. - - """ - metadata = {} - if 'metadata' in inst.extra and 'items' in inst.extra['metadata']: - for md in inst.extra['metadata']['items']: - metadata[md['key']] = md['value'] - - try: - netname = inst.extra['networkInterfaces'][0]['network'].split('/')[-1] - except Exception: - netname = None - try: - subnetname = inst.extra['networkInterfaces'][0]['subnetwork'].split('/')[-1] - except Exception: - subnetname = None - if 'disks' in inst.extra: - disk_names = [disk_info['source'].split('/')[-1] - for disk_info - in sorted(inst.extra['disks'], - key=lambda disk_info: disk_info['index'])] - else: - disk_names = [] - - if len(inst.public_ips) == 0: - public_ip = None - else: - public_ip = inst.public_ips[0] - - return ({ - 'image': inst.image is not None and inst.image.split('/')[-1] or None, - 'disks': disk_names, - 'machine_type': inst.size, - 'metadata': metadata, - 'name': inst.name, - 'network': netname, - 'subnetwork': subnetname, - 'private_ip': inst.private_ips[0], - 'public_ip': public_ip, - 'status': ('status' in inst.extra) and inst.extra['status'] or None, - 'tags': ('tags' in inst.extra) and inst.extra['tags'] or [], - 'zone': ('zone' in inst.extra) and inst.extra['zone'].name or None, - }) - - -def create_instances(module, gce, instance_names, number, lc_zone): - """Creates new instances. Attributes other than instance_names are picked - up from 'module' - - module : AnsibleModule object - community.general.gce: authenticated GCE libcloud driver - instance_names: python list of instance names to create - number: number of instances to create - lc_zone: GCEZone object - - Returns: - A list of dictionaries with instance information - about the instances that were launched. - - """ - image = module.params.get('image') - image_family = module.params.get('image_family') - external_projects = module.params.get('external_projects') - machine_type = module.params.get('machine_type') - metadata = module.params.get('metadata') - network = module.params.get('network') - subnetwork = module.params.get('subnetwork') - persistent_boot_disk = module.params.get('persistent_boot_disk') - disks = module.params.get('disks') - tags = module.params.get('tags') - ip_forward = module.params.get('ip_forward') - external_ip = module.params.get('external_ip') - disk_auto_delete = module.params.get('disk_auto_delete') - preemptible = module.params.get('preemptible') - disk_size = module.params.get('disk_size') - service_account_permissions = module.params.get('service_account_permissions') - - if external_ip == "none": - instance_external_ip = None - elif external_ip != "ephemeral": - instance_external_ip = external_ip - try: - # check if instance_external_ip is an ip or a name - try: - socket.inet_aton(instance_external_ip) - instance_external_ip = GCEAddress(id='unknown', name='unknown', address=instance_external_ip, region='unknown', driver=gce) - except socket.error: - instance_external_ip = gce.ex_get_address(instance_external_ip) - except GoogleBaseError as e: - module.fail_json(msg='Unexpected error attempting to get a static ip %s, error: %s' % (external_ip, e.value)) - else: - instance_external_ip = external_ip - - new_instances = [] - changed = False - - lc_disks = [] - disk_modes = [] - for i, disk in enumerate(disks or []): - if isinstance(disk, dict): - lc_disks.append(gce.ex_get_volume(disk['name'], lc_zone)) - disk_modes.append(disk['mode']) - else: - lc_disks.append(gce.ex_get_volume(disk, lc_zone)) - # boot disk is implicitly READ_WRITE - disk_modes.append('READ_ONLY' if i > 0 else 'READ_WRITE') - lc_network = gce.ex_get_network(network) - lc_machine_type = gce.ex_get_size(machine_type, lc_zone) - - # Try to convert the user's metadata value into the format expected - # by GCE. First try to ensure user has proper quoting of a - # dictionary-like syntax using 'literal_eval', then convert the python - # dict into a python list of 'key' / 'value' dicts. Should end up - # with: - # [ {'key': key1, 'value': value1}, {'key': key2, 'value': value2}, ...] - if metadata: - if isinstance(metadata, dict): - md = metadata - else: - try: - md = literal_eval(str(metadata)) - if not isinstance(md, dict): - raise ValueError('metadata must be a dict') - except ValueError as e: - module.fail_json(msg='bad metadata: %s' % str(e)) - except SyntaxError as e: - module.fail_json(msg='bad metadata syntax') - - if hasattr(libcloud, '__version__') and libcloud.__version__ < '0.15': - items = [] - for k, v in md.items(): - items.append({"key": k, "value": v}) - metadata = {'items': items} - else: - metadata = md - - lc_image = LazyDiskImage(module, gce, image, lc_disks, family=image_family, projects=external_projects) - ex_sa_perms = [] - bad_perms = [] - if service_account_permissions: - for perm in service_account_permissions: - if perm not in gce.SA_SCOPES_MAP and not perm.startswith('https://www.googleapis.com/auth'): - bad_perms.append(perm) - if len(bad_perms) > 0: - module.fail_json(msg='bad permissions: %s' % str(bad_perms)) - ex_sa_perms.append({'email': "default"}) - ex_sa_perms[0]['scopes'] = service_account_permissions - - # These variables all have default values but check just in case - if not lc_network or not lc_machine_type or not lc_zone: - module.fail_json(msg='Missing required create instance variable', - changed=False) - - gce_args = dict( - location=lc_zone, - ex_network=network, ex_tags=tags, ex_metadata=metadata, - ex_can_ip_forward=ip_forward, - external_ip=instance_external_ip, ex_disk_auto_delete=disk_auto_delete, - ex_service_accounts=ex_sa_perms - ) - if preemptible is not None: - gce_args['ex_preemptible'] = preemptible - if subnetwork is not None: - gce_args['ex_subnetwork'] = subnetwork - - if isinstance(instance_names, str) and not number: - instance_names = [instance_names] - - if isinstance(instance_names, str) and number: - instance_responses = gce.ex_create_multiple_nodes(instance_names, lc_machine_type, - lc_image(), number, **gce_args) - for resp in instance_responses: - n = resp - if isinstance(resp, libcloud.compute.drivers.gce.GCEFailedNode): - try: - n = gce.ex_get_node(n.name, lc_zone) - except ResourceNotFoundError: - pass - else: - # Assure that at least one node has been created to set changed=True - changed = True - new_instances.append(n) - else: - for instance in instance_names: - pd = None - if lc_disks: - pd = lc_disks[0] - elif persistent_boot_disk: - try: - pd = gce.ex_get_volume("%s" % instance, lc_zone) - except ResourceNotFoundError: - pd = gce.create_volume(disk_size, "%s" % instance, image=lc_image()) - gce_args['ex_boot_disk'] = pd - - inst = None - try: - inst = gce.ex_get_node(instance, lc_zone) - except ResourceNotFoundError: - inst = gce.create_node( - instance, lc_machine_type, lc_image(), **gce_args - ) - changed = True - except GoogleBaseError as e: - module.fail_json(msg='Unexpected error attempting to create ' + - 'instance %s, error: %s' % (instance, e.value)) - if inst: - new_instances.append(inst) - - for inst in new_instances: - for i, lc_disk in enumerate(lc_disks): - # Check whether the disk is already attached - if (len(inst.extra['disks']) > i): - attached_disk = inst.extra['disks'][i] - if attached_disk['source'] != lc_disk.extra['selfLink']: - module.fail_json( - msg=("Disk at index %d does not match: requested=%s found=%s" % ( - i, lc_disk.extra['selfLink'], attached_disk['source']))) - elif attached_disk['mode'] != disk_modes[i]: - module.fail_json( - msg=("Disk at index %d is in the wrong mode: requested=%s found=%s" % ( - i, disk_modes[i], attached_disk['mode']))) - else: - continue - gce.attach_volume(inst, lc_disk, ex_mode=disk_modes[i]) - # Work around libcloud bug: attached volumes don't get added - # to the instance metadata. get_instance_info() only cares about - # source and index. - if len(inst.extra['disks']) != i + 1: - inst.extra['disks'].append( - {'source': lc_disk.extra['selfLink'], 'index': i}) - - instance_names = [] - instance_json_data = [] - for inst in new_instances: - d = get_instance_info(inst) - instance_names.append(d['name']) - instance_json_data.append(d) - - return (changed, instance_json_data, instance_names) - - -def change_instance_state(module, gce, instance_names, number, zone, state): - """Changes the state of a list of instances. For example, - change from started to stopped, or started to absent. - - module: Ansible module object - community.general.gce: authenticated GCE connection object - instance_names: a list of instance names to terminate - zone: GCEZone object where the instances reside prior to termination - state: 'state' parameter passed into module as argument - - Returns a dictionary of instance names that were changed. - - """ - changed = False - nodes = [] - state_instance_names = [] - - if isinstance(instance_names, str) and number: - node_names = ['%s-%03d' % (instance_names, i) for i in range(number)] - elif isinstance(instance_names, str) and not number: - node_names = [instance_names] - else: - node_names = instance_names - - for name in node_names: - inst = None - try: - inst = gce.ex_get_node(name, zone) - except ResourceNotFoundError: - state_instance_names.append(name) - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - else: - nodes.append(inst) - state_instance_names.append(name) - - if state in ['absent', 'deleted'] and number: - changed_nodes = gce.ex_destroy_multiple_nodes(nodes) or [False] - changed = reduce(lambda x, y: x or y, changed_nodes) - else: - for node in nodes: - if state in ['absent', 'deleted']: - gce.destroy_node(node) - changed = True - elif state == 'started' and node.state == libcloud.compute.types.NodeState.STOPPED: - gce.ex_start_node(node) - changed = True - elif state in ['stopped', 'terminated'] and node.state == libcloud.compute.types.NodeState.RUNNING: - gce.ex_stop_node(node) - changed = True - - return (changed, state_instance_names) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - image=dict(default='debian-8'), - image_family=dict(), - external_projects=dict(type='list'), - instance_names=dict(), - machine_type=dict(default='n1-standard-1'), - metadata=dict(), - name=dict(aliases=['base_name']), - num_instances=dict(type='int'), - network=dict(default='default'), - subnetwork=dict(), - persistent_boot_disk=dict(type='bool', default=False), - disks=dict(type='list'), - state=dict(choices=['active', 'present', 'absent', 'deleted', - 'started', 'stopped', 'terminated'], - default='present'), - tags=dict(type='list'), - zone=dict(default='us-central1-a'), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - pem_file=dict(type='path'), - credentials_file=dict(type='path'), - project_id=dict(), - ip_forward=dict(type='bool', default=False), - external_ip=dict(default='ephemeral'), - disk_auto_delete=dict(type='bool', default=True), - disk_size=dict(type='int', default=10), - preemptible=dict(type='bool', default=None), - ), - mutually_exclusive=[('instance_names', 'name')] - ) - - if not HAS_PYTHON26: - module.fail_json(msg="GCE module requires python's 'ast' module, python v2.6+") - if not HAS_LIBCLOUD: - module.fail_json(msg='libcloud with GCE support (0.17.0+) required for this module') - - gce = gce_connect(module) - - image = module.params.get('image') - image_family = module.params.get('image_family') - external_projects = module.params.get('external_projects') - instance_names = module.params.get('instance_names') - name = module.params.get('name') - number = module.params.get('num_instances') - subnetwork = module.params.get('subnetwork') - state = module.params.get('state') - zone = module.params.get('zone') - preemptible = module.params.get('preemptible') - changed = False - - inames = None - if isinstance(instance_names, list): - inames = instance_names - elif isinstance(instance_names, str): - inames = instance_names.split(',') - if name: - inames = name - if not inames: - module.fail_json(msg='Must specify a "name" or "instance_names"', - changed=False) - if not zone: - module.fail_json(msg='Must specify a "zone"', changed=False) - - lc_zone = get_valid_location(module, gce, zone) - if preemptible is not None and hasattr(libcloud, '__version__') and libcloud.__version__ < '0.20': - module.fail_json(msg="Apache Libcloud 0.20.0+ is required to use 'preemptible' option", - changed=False) - - if subnetwork is not None and not hasattr(gce, 'ex_get_subnetwork'): - module.fail_json(msg="Apache Libcloud 1.0.0+ is required to use 'subnetwork' option", - changed=False) - - json_output = {'zone': zone} - if state in ['absent', 'deleted', 'started', 'stopped', 'terminated']: - json_output['state'] = state - (changed, state_instance_names) = change_instance_state( - module, gce, inames, number, lc_zone, state) - - # based on what user specified, return the same variable, although - # value could be different if an instance could not be destroyed - if instance_names or name and number: - json_output['instance_names'] = state_instance_names - elif name: - json_output['name'] = name - - elif state in ['active', 'present']: - json_output['state'] = 'present' - (changed, instance_data, instance_name_list) = create_instances( - module, gce, inames, number, lc_zone) - json_output['instance_data'] = instance_data - if instance_names: - json_output['instance_names'] = instance_name_list - elif name: - json_output['name'] = name - - json_output['changed'] = changed - module.exit_json(**json_output) - - -class LazyDiskImage: - """ - Object for lazy instantiation of disk image - gce.ex_get_image is a very expensive call, so we want to avoid calling it as much as possible. - """ - - def __init__(self, module, gce, name, has_pd, family=None, projects=None): - self.image = None - self.was_called = False - self.gce = gce - self.name = name - self.has_pd = has_pd - self.module = module - self.family = family - self.projects = projects - - def __call__(self): - if not self.was_called: - self.was_called = True - if not self.has_pd: - if self.family: - self.image = self.gce.ex_get_image_from_family(self.family, ex_project_list=self.projects) - else: - self.image = self.gce.ex_get_image(self.name, ex_project_list=self.projects) - if not self.image: - self.module.fail_json(msg='image or disks missing for create instance', changed=False) - return self.image - - -if __name__ == '__main__': - main() diff --git a/plugins/modules/cloud/google/gcp_backend_service.py b/plugins/modules/cloud/google/gcp_backend_service.py deleted file mode 100644 index ee564ae097..0000000000 --- a/plugins/modules/cloud/google/gcp_backend_service.py +++ /dev/null @@ -1,420 +0,0 @@ -#!/usr/bin/python -# Copyright 2017 Google Inc. -# 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 = ''' -module: gcp_backend_service -short_description: Create or Destroy a Backend Service. -description: - - Create or Destroy a Backend Service. See - U(https://cloud.google.com/compute/docs/load-balancing/http/backend-service) for an overview. - Full install/configuration instructions for the Google Cloud modules can - be found in the comments of ansible/test/gce_tests.py. -requirements: - - "python >= 2.6" - - "apache-libcloud >= 1.3.0" -notes: - - Update is not currently supported. - - Only global backend services are currently supported. Regional backends not currently supported. - - Internal load balancing not currently supported. -deprecated: - removed_in: 2.0.0 # was Ansible 2.12 - why: Updated modules released with increased functionality - alternative: Use M(google.cloud.gcp_compute_backend_service) instead. -author: - - "Tom Melendez (@supertom) " -options: - backend_service_name: - type: str - description: - - Name of the Backend Service. - required: true - backends: - type: list - description: - - List of backends that make up the backend service. A backend is made up of - an instance group and optionally several other parameters. See - U(https://cloud.google.com/compute/docs/reference/latest/backendServices) - for details. - required: true - healthchecks: - type: list - description: - - List of healthchecks. Only one healthcheck is supported. - required: true - enable_cdn: - description: - - If true, enable Cloud CDN for this Backend Service. - type: bool - port_name: - type: str - description: - - Name of the port on the managed instance group (MIG) that backend - services can forward data to. Required for external load balancing. - protocol: - type: str - description: - - The protocol this Backend Service uses to communicate with backends. - Possible values are HTTP, HTTPS, TCP, and SSL. The default is TCP. - choices: [HTTP, HTTPS, TCP, SSL] - default: TCP - required: false - timeout: - type: int - description: - - How many seconds to wait for the backend before considering it a failed - request. Default is 30 seconds. Valid range is 1-86400. - required: false - service_account_email: - type: str - description: - - Service account email - service_account_permissions: - type: list - description: - - service account permissions - credentials_file: - type: str - description: - - Path to the JSON file associated with the service account email. - pem_file: - type: str - description: - - Path to the PEM file associated with the service account email. - project_id: - type: str - description: - - GCE project ID. - state: - type: str - description: - - Desired state of the resource - default: "present" - choices: ["absent", "present"] -''' - -EXAMPLES = ''' -- name: Create Minimum Backend Service - community.general.gcp_backend_service: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - backend_service_name: "{{ bes }}" - backends: - - instance_group: managed_instance_group_1 - healthchecks: - - healthcheck_name_for_backend_service - port_name: myhttpport - state: present - -- name: Create BES with extended backend parameters - community.general.gcp_backend_service: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - backend_service_name: "{{ bes }}" - backends: - - instance_group: managed_instance_group_1 - max_utilization: 0.6 - max_rate: 10 - - instance_group: managed_instance_group_2 - max_utilization: 0.5 - max_rate: 4 - healthchecks: - - healthcheck_name_for_backend_service - port_name: myhttpport - state: present - timeout: 60 -''' - -RETURN = ''' -backend_service_created: - description: Indicator Backend Service was created. - returned: When a Backend Service is created. - type: bool - sample: "True" -backend_service_deleted: - description: Indicator Backend Service was deleted. - returned: When a Backend Service is deleted. - type: bool - sample: "True" -backend_service_name: - description: Name of the Backend Service. - returned: Always. - type: str - sample: "my-backend-service" -backends: - description: List of backends (comprised of instance_group) that - make up a Backend Service. - returned: When a Backend Service exists. - type: list - sample: "[ { 'instance_group': 'mig_one', 'zone': 'us-central1-b'} ]" -enable_cdn: - description: If Cloud CDN is enabled. null if not set. - returned: When a backend service exists. - type: bool - sample: "True" -healthchecks: - description: List of healthchecks applied to the Backend Service. - returned: When a Backend Service exists. - type: list - sample: "[ 'my-healthcheck' ]" -protocol: - description: Protocol used to communicate with the Backends. - returned: When a Backend Service exists. - type: str - sample: "HTTP" -port_name: - description: Name of Backend Port. - returned: When a Backend Service exists. - type: str - sample: "myhttpport" -timeout: - description: In seconds, how long before a request sent to a backend is - considered failed. - returned: If specified. - type: int - sample: "myhttpport" -''' - -try: - from ast import literal_eval - HAS_PYTHON26 = True -except ImportError: - HAS_PYTHON26 = False - -try: - import libcloud - from libcloud.compute.types import Provider - from libcloud.compute.providers import get_driver - from libcloud.common.google import GoogleBaseError, QuotaExceededError, \ - ResourceExistsError, ResourceInUseError, ResourceNotFoundError - from libcloud.compute.drivers.gce import GCEAddress - _ = Provider.GCE - HAS_LIBCLOUD = True -except ImportError: - HAS_LIBCLOUD = False - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.community.general.plugins.module_utils.gce import gce_connect -from ansible_collections.community.general.plugins.module_utils.gcp import check_params - - -def _validate_params(params): - """ - Validate backend_service params. - - This function calls _validate_backend_params to verify - the backend-specific parameters. - - :param params: Ansible dictionary containing configuration. - :type params: ``dict`` - - :return: True or raises ValueError - :rtype: ``bool`` or `class:ValueError` - """ - fields = [ - {'name': 'timeout', 'type': int, 'min': 1, 'max': 86400}, - ] - try: - check_params(params, fields) - _validate_backend_params(params['backends']) - except Exception: - raise - - return (True, '') - - -def _validate_backend_params(backends): - """ - Validate configuration for backends. - - :param backends: Ansible dictionary containing backends configuration (only). - :type backends: ``dict`` - - :return: True or raises ValueError - :rtype: ``bool`` or `class:ValueError` - """ - fields = [ - {'name': 'balancing_mode', 'type': str, 'values': ['UTILIZATION', 'RATE', 'CONNECTION']}, - {'name': 'max_utilization', 'type': float}, - {'name': 'max_connections', 'type': int}, - {'name': 'max_rate', 'type': int}, - {'name': 'max_rate_per_instance', 'type': float}, - ] - - if not backends: - raise ValueError('backends should be a list.') - - for backend in backends: - try: - check_params(backend, fields) - except Exception: - raise - - if 'max_rate' in backend and 'max_rate_per_instance' in backend: - raise ValueError('Both maxRate or maxRatePerInstance cannot be set.') - - return (True, '') - - -def get_backend_service(gce, name): - """ - Get a Backend Service from GCE. - - :param gce: An initialized GCE driver object. - :type gce: :class: `GCENodeDriver` - - :param name: Name of the Backend Service. - :type name: ``str`` - - :return: A GCEBackendService object or None. - :rtype: :class: `GCEBackendService` or None - """ - try: - # Does the Backend Service already exist? - return gce.ex_get_backendservice(name=name) - - except ResourceNotFoundError: - return None - - -def get_healthcheck(gce, name): - return gce.ex_get_healthcheck(name) - - -def get_instancegroup(gce, name, zone=None): - return gce.ex_get_instancegroup(name=name, zone=zone) - - -def create_backend_service(gce, params): - """ - Create a new Backend Service. - - :param gce: An initialized GCE driver object. - :type gce: :class: `GCENodeDriver` - - :param params: Dictionary of parameters needed by the module. - :type params: ``dict`` - - :return: Tuple with changed stats - :rtype: tuple in the format of (bool, bool) - """ - from copy import deepcopy - - changed = False - return_data = False - # only one healthcheck is currently supported - hc_name = params['healthchecks'][0] - hc = get_healthcheck(gce, hc_name) - backends = [] - for backend in params['backends']: - ig = get_instancegroup(gce, backend['instance_group'], - backend.get('zone', None)) - kwargs = deepcopy(backend) - kwargs['instance_group'] = ig - backends.append(gce.ex_create_backend( - **kwargs)) - - bes = gce.ex_create_backendservice( - name=params['backend_service_name'], healthchecks=[hc], backends=backends, - enable_cdn=params['enable_cdn'], port_name=params['port_name'], - timeout_sec=params['timeout'], protocol=params['protocol']) - - if bes: - changed = True - return_data = True - - return (changed, return_data) - - -def delete_backend_service(bes): - """ - Delete a Backend Service. The Instance Groups are NOT destroyed. - """ - changed = False - return_data = False - if bes.destroy(): - changed = True - return_data = True - return (changed, return_data) - - -def main(): - module = AnsibleModule(argument_spec=dict( - backends=dict(type='list', required=True), - backend_service_name=dict(required=True), - healthchecks=dict(type='list', required=True), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - enable_cdn=dict(type='bool'), - port_name=dict(type='str'), - protocol=dict(type='str', default='TCP', - choices=['HTTP', 'HTTPS', 'SSL', 'TCP']), - timeout=dict(type='int'), - state=dict(choices=['absent', 'present'], default='present'), - pem_file=dict(), - credentials_file=dict(), - project_id=dict(), ), ) - - if not HAS_PYTHON26: - module.fail_json( - msg="GCE module requires python's 'ast' module, python v2.6+") - if not HAS_LIBCLOUD: - module.fail_json( - msg='libcloud with GCE Backend Service support (1.3+) required for this module.') - - gce = gce_connect(module) - if not hasattr(gce, 'ex_create_instancegroupmanager'): - module.fail_json( - msg='libcloud with GCE Backend Service support (1.3+) required for this module.', - changed=False) - - params = {} - params['state'] = module.params.get('state') - params['backend_service_name'] = module.params.get('backend_service_name') - params['backends'] = module.params.get('backends') - params['healthchecks'] = module.params.get('healthchecks') - params['enable_cdn'] = module.params.get('enable_cdn', None) - params['port_name'] = module.params.get('port_name', None) - params['protocol'] = module.params.get('protocol', None) - params['timeout'] = module.params.get('timeout', None) - - try: - _validate_params(params) - except Exception as e: - module.fail_json(msg=e.message, changed=False) - - changed = False - json_output = {'state': params['state']} - bes = get_backend_service(gce, params['backend_service_name']) - - if not bes: - if params['state'] == 'absent': - # Doesn't exist and state==absent. - changed = False - module.fail_json( - msg="Cannot delete unknown backend service: %s" % - (params['backend_service_name'])) - else: - # Create - (changed, json_output['backend_service_created']) = create_backend_service(gce, - params) - elif params['state'] == 'absent': - # Delete - (changed, json_output['backend_service_deleted']) = delete_backend_service(bes) - else: - # TODO(supertom): Add update support when it is available in libcloud. - changed = False - - json_output['changed'] = changed - json_output.update(params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/plugins/modules/cloud/google/gcp_forwarding_rule.py b/plugins/modules/cloud/google/gcp_forwarding_rule.py deleted file mode 100644 index 56dbfa7e22..0000000000 --- a/plugins/modules/cloud/google/gcp_forwarding_rule.py +++ /dev/null @@ -1,385 +0,0 @@ -#!/usr/bin/python -# Copyright 2017 Google Inc. -# 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 = ''' ---- -module: gcp_forwarding_rule -short_description: Create, Update or Destroy a Forwarding_Rule. -description: - - Create, Update or Destroy a Forwarding_Rule. See - U(https://cloud.google.com/compute/docs/load-balancing/http/target-proxies) for an overview. - More details on the Global Forwarding_Rule API can be found at - U(https://cloud.google.com/compute/docs/reference/latest/globalForwardingRules) - More details on the Forwarding Rules API can be found at - U(https://cloud.google.com/compute/docs/reference/latest/forwardingRules) -requirements: - - "python >= 2.6" - - "google-api-python-client >= 1.6.2" - - "google-auth >= 0.9.0" - - "google-auth-httplib2 >= 0.0.2" -deprecated: - removed_in: 2.0.0 # was Ansible 2.12 - why: Updated modules released with increased functionality - alternative: Use M(google.cloud.gcp_compute_forwarding_rule) or M(google.cloud.gcp_compute_global_forwarding_rule) instead. -notes: - - Currently only supports global forwarding rules. - As such, Load Balancing Scheme is always EXTERNAL. -author: - - "Tom Melendez (@supertom) " -options: - address: - type: str - description: - - IPv4 or named IP address. Must be of the same scope (regional, global). - Reserved addresses can (and probably should) be used for global - forwarding rules. You may reserve IPs from the console or - via the gce_eip module. - required: false - forwarding_rule_name: - type: str - description: - - Name of the Forwarding_Rule. - required: true - port_range: - type: str - description: - - For global forwarding rules, must be set to 80 or 8080 for TargetHttpProxy, and - 443 for TargetHttpsProxy or TargetSslProxy. - required: false - protocol: - type: str - description: - - For global forwarding rules, TCP, UDP, ESP, AH, SCTP or ICMP. Default is TCP. - required: false - choices: [TCP] - default: TCP - region: - type: str - description: - - The region for this forwarding rule. Currently, only 'global' is supported. - required: true - state: - type: str - description: - - The state of the Forwarding Rule. 'present' or 'absent' - required: true - choices: ["present", "absent"] - target: - type: str - description: - - Target resource for forwarding rule. For global proxy, this is a Global - TargetProxy resource. Required for external load balancing (including Global load balancing) - required: false - project_id: - type: str - description: - - The Google Cloud Platform project ID to use. - pem_file: - type: str - description: - - The path to the PEM file associated with the service account email. - - This option is deprecated and may be removed in a future release. Use I(credentials_file) instead. - credentials_file: - type: str - description: - - The path to the JSON file associated with the service account email. - service_account_email: - type: str - description: - - service account email - service_account_permissions: - type: list - description: - - service account permissions - load_balancing_scheme: - type: str - choices: [EXTERNAL] - default: EXTERNAL - description: - - Load balancing scheme. At the moment the only choice is EXTERNAL. -''' - -EXAMPLES = ''' -- name: Create Minimum GLOBAL Forwarding_Rule - community.general.gcp_forwarding_rule: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - forwarding_rule_name: my-forwarding_rule - protocol: TCP - port_range: 80 - region: global - target: my-target-proxy - state: present - -- name: Create Forwarding_Rule w/reserved static address - community.general.gcp_forwarding_rule: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - forwarding_rule_name: my-forwarding_rule - protocol: TCP - port_range: 80 - address: my-reserved-static-address-name - region: global - target: my-target-proxy - state: present -''' - -RETURN = ''' -forwarding_rule_name: - description: Name of the Forwarding_Rule - returned: Always - type: str - sample: my-target-proxy -forwarding_rule: - description: GCP Forwarding_Rule dictionary - returned: Always. Refer to GCP documentation for detailed field descriptions. - type: dict - sample: { "name": "my-forwarding_rule", "target": "..." } -region: - description: Region for Forwarding Rule. - returned: Always - type: bool - sample: true -state: - description: state of the Forwarding_Rule - returned: Always. - type: str - sample: present -''' - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.community.general.plugins.module_utils.gcp import get_google_api_client, GCPUtils - - -USER_AGENT_PRODUCT = 'ansible-forwarding_rule' -USER_AGENT_VERSION = '0.0.1' - - -def _build_global_forwarding_rule_dict(params, project_id=None): - """ - Reformat services in Ansible Params. - - :param params: Params from AnsibleModule object - :type params: ``dict`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: dictionary suitable for submission to GCP API. - :rtype ``dict`` - """ - url = '' - if project_id: - url = GCPUtils.build_googleapi_url(project_id) - gcp_dict = GCPUtils.params_to_gcp_dict(params, 'forwarding_rule_name') - if 'target' in gcp_dict: - gcp_dict['target'] = '%s/global/targetHttpProxies/%s' % (url, - gcp_dict['target']) - if 'address' in gcp_dict: - gcp_dict['IPAddress'] = '%s/global/addresses/%s' % (url, - gcp_dict['address']) - del gcp_dict['address'] - if 'protocol' in gcp_dict: - gcp_dict['IPProtocol'] = gcp_dict['protocol'] - del gcp_dict['protocol'] - return gcp_dict - - -def get_global_forwarding_rule(client, name, project_id=None): - """ - Get a Global Forwarding Rule from GCP. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Global Forwarding Rule. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: A dict resp from the respective GCP 'get' request. - :rtype: ``dict`` - """ - try: - req = client.globalForwardingRules().get( - project=project_id, forwardingRule=name) - return GCPUtils.execute_api_client_req(req, raise_404=False) - except Exception: - raise - - -def create_global_forwarding_rule(client, params, project_id): - """ - Create a new Global Forwarding Rule. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_global_forwarding_rule_dict(params, project_id) - try: - req = client.globalForwardingRules().insert(project=project_id, body=gcp_dict) - return_data = GCPUtils.execute_api_client_req(req, client, raw=False) - if not return_data: - return_data = get_global_forwarding_rule(client, - name=params['forwarding_rule_name'], - project_id=project_id) - return (True, return_data) - except Exception: - raise - - -def delete_global_forwarding_rule(client, name, project_id): - """ - Delete a Global Forwarding Rule. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Target Proxy. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - try: - req = client.globalForwardingRules().delete( - project=project_id, forwardingRule=name) - return_data = GCPUtils.execute_api_client_req(req, client) - return (True, return_data) - except Exception: - raise - - -def update_global_forwarding_rule(client, forwarding_rule, params, name, project_id): - """ - Update a Global Forwarding_Rule. Currently, only a target can be updated. - - If the forwarding_rule has not changed, the update will not occur. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param forwarding_rule: Name of the Target Proxy. - :type forwarding_rule: ``dict`` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :param name: Name of the Global Forwarding Rule. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_global_forwarding_rule_dict(params, project_id) - - GCPUtils.are_params_equal(forwarding_rule, gcp_dict) - if forwarding_rule['target'] == gcp_dict['target']: - return (False, 'no update necessary') - - try: - req = client.globalForwardingRules().setTarget(project=project_id, - forwardingRule=name, - body={'target': gcp_dict['target']}) - return_data = GCPUtils.execute_api_client_req( - req, client=client, raw=False) - return (True, return_data) - except Exception: - raise - - -def main(): - module = AnsibleModule(argument_spec=dict( - forwarding_rule_name=dict(required=True), - region=dict(required=True), - target=dict(required=False), - address=dict(type='str', required=False), - protocol=dict(required=False, default='TCP', choices=['TCP']), - port_range=dict(required=False), - load_balancing_scheme=dict( - required=False, default='EXTERNAL', choices=['EXTERNAL']), - state=dict(required=True, choices=['absent', 'present']), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - pem_file=dict(), - credentials_file=dict(), - project_id=dict(), ), ) - - client, conn_params = get_google_api_client(module, 'compute', user_agent_product=USER_AGENT_PRODUCT, - user_agent_version=USER_AGENT_VERSION) - - params = {} - params['state'] = module.params.get('state') - params['forwarding_rule_name'] = module.params.get('forwarding_rule_name') - params['region'] = module.params.get('region') - params['target'] = module.params.get('target', None) - params['protocol'] = module.params.get('protocol', None) - params['port_range'] = module.params.get('port_range') - if module.params.get('address', None): - params['address'] = module.params.get('address', None) - - if params['region'] != 'global': - # This module currently doesn't support regional rules. - module.fail_json( - msg=("%s - Only global forwarding rules currently supported. " - "Be sure to specify 'global' for the region option.") % - (params['forwarding_rule_name'])) - - changed = False - json_output = {'state': params['state']} - forwarding_rule = None - if params['region'] == 'global': - forwarding_rule = get_global_forwarding_rule(client, - name=params['forwarding_rule_name'], - project_id=conn_params['project_id']) - if not forwarding_rule: - if params['state'] == 'absent': - # Doesn't exist in GCE, and state==absent. - changed = False - module.fail_json( - msg="Cannot delete unknown forwarding_rule: %s" % - (params['forwarding_rule_name'])) - else: - # Create - changed, json_output['forwarding_rule'] = create_global_forwarding_rule(client, - params=params, - project_id=conn_params['project_id']) - elif params['state'] == 'absent': - # Delete - changed, json_output['forwarding_rule'] = delete_global_forwarding_rule(client, - name=params['forwarding_rule_name'], - project_id=conn_params['project_id']) - else: - changed, json_output['forwarding_rule'] = update_global_forwarding_rule(client, - forwarding_rule=forwarding_rule, - params=params, - name=params['forwarding_rule_name'], - project_id=conn_params['project_id']) - - json_output['changed'] = changed - json_output.update(params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/plugins/modules/cloud/google/gcp_healthcheck.py b/plugins/modules/cloud/google/gcp_healthcheck.py deleted file mode 100644 index 19b2865382..0000000000 --- a/plugins/modules/cloud/google/gcp_healthcheck.py +++ /dev/null @@ -1,457 +0,0 @@ -#!/usr/bin/python -# Copyright 2017 Google Inc. -# 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 = ''' ---- -module: gcp_healthcheck -short_description: Create, Update or Destroy a Healthcheck. -description: - - Create, Update or Destroy a Healthcheck. Currently only HTTP and - HTTPS Healthchecks are supported. Healthchecks are used to monitor - individual instances, managed instance groups and/or backend - services. Healtchecks are reusable. - - Visit - U(https://cloud.google.com/compute/docs/load-balancing/health-checks) - for an overview of Healthchecks on GCP. - - See - U(https://cloud.google.com/compute/docs/reference/latest/httpHealthChecks) for - API details on HTTP Healthchecks. - - See - U(https://cloud.google.com/compute/docs/reference/latest/httpsHealthChecks) - for more details on the HTTPS Healtcheck API. -requirements: - - "python >= 2.6" - - "google-api-python-client >= 1.6.2" - - "google-auth >= 0.9.0" - - "google-auth-httplib2 >= 0.0.2" -notes: - - Only supports HTTP and HTTPS Healthchecks currently. -deprecated: - removed_in: 2.0.0 # was Ansible 2.12 - why: Updated modules released with increased functionality - alternative: > - Use M(google.cloud.gcp_compute_health_check), M(google.cloud.gcp_compute_http_health_check) or - M(google.cloud.gcp_compute_https_health_check) instead. -author: - - "Tom Melendez (@supertom) " -options: - check_interval: - type: int - description: - - How often (in seconds) to send a health check. - default: 5 - healthcheck_name: - type: str - description: - - Name of the Healthcheck. - required: true - healthcheck_type: - type: str - description: - - Type of Healthcheck. - required: true - choices: ["HTTP", "HTTPS"] - host_header: - type: str - description: - - The value of the host header in the health check request. If left - empty, the public IP on behalf of which this health - check is performed will be used. - default: "" - port: - type: int - description: - - The TCP port number for the health check request. The default value is - 443 for HTTPS and 80 for HTTP. - request_path: - type: str - description: - - The request path of the HTTPS health check request. - required: false - default: "/" - state: - type: str - description: State of the Healthcheck. - choices: ["present", "absent"] - default: present - timeout: - type: int - description: - - How long (in seconds) to wait for a response before claiming - failure. It is invalid for timeout - to have a greater value than check_interval. - default: 5 - unhealthy_threshold: - type: int - description: - - A so-far healthy instance will be marked unhealthy after this - many consecutive failures. - default: 2 - healthy_threshold: - type: int - description: - - A so-far unhealthy instance will be marked healthy after this - many consecutive successes. - default: 2 - service_account_email: - type: str - description: - - service account email - service_account_permissions: - type: list - description: - - service account permissions (see - U(https://cloud.google.com/sdk/gcloud/reference/compute/instances/create), - --scopes section for detailed information) - - > - Available choices are: - C(bigquery), C(cloud-platform), C(compute-ro), C(compute-rw), - C(useraccounts-ro), C(useraccounts-rw), C(datastore), C(logging-write), - C(monitoring), C(sql-admin), C(storage-full), C(storage-ro), - C(storage-rw), C(taskqueue), C(userinfo-email). - credentials_file: - type: str - description: - - Path to the JSON file associated with the service account email - project_id: - type: str - description: - - Your GCP project ID -''' - -EXAMPLES = ''' -- name: Create Minimum HealthCheck - community.general.gcp_healthcheck: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - healthcheck_name: my-healthcheck - healthcheck_type: HTTP - state: present -- name: Create HTTP HealthCheck - community.general.gcp_healthcheck: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - healthcheck_name: my-healthcheck - healthcheck_type: HTTP - host: my-host - request_path: /hc - check_interval: 10 - timeout: 30 - unhealthy_threshhold: 2 - healthy_threshhold: 1 - state: present -- name: Create HTTPS HealthCheck - community.general.gcp_healthcheck: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - healthcheck_name: "{{ https_healthcheck }}" - healthcheck_type: HTTPS - host_header: my-host - request_path: /hc - check_interval: 5 - timeout: 5 - unhealthy_threshold: 2 - healthy_threshold: 1 - state: present -''' - -RETURN = ''' -state: - description: state of the Healthcheck - returned: Always. - type: str - sample: present -healthcheck_name: - description: Name of the Healthcheck - returned: Always - type: str - sample: my-url-map -healthcheck_type: - description: Type of the Healthcheck - returned: Always - type: str - sample: HTTP -healthcheck: - description: GCP Healthcheck dictionary - returned: Always. Refer to GCP documentation for detailed field descriptions. - type: dict - sample: { "name": "my-hc", "port": 443, "requestPath": "/foo" } -''' - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.community.general.plugins.module_utils.gcp import get_google_api_client, GCPUtils - - -USER_AGENT_PRODUCT = 'ansible-healthcheck' -USER_AGENT_VERSION = '0.0.1' - - -def _validate_healthcheck_params(params): - """ - Validate healthcheck params. - - Simple validation has already assumed by AnsibleModule. - - :param params: Ansible dictionary containing configuration. - :type params: ``dict`` - - :return: True or raises ValueError - :rtype: ``bool`` or `class:ValueError` - """ - if params['timeout'] > params['check_interval']: - raise ValueError("timeout (%s) is greater than check_interval (%s)" % ( - params['timeout'], params['check_interval'])) - - return (True, '') - - -def _build_healthcheck_dict(params): - """ - Reformat services in Ansible Params for GCP. - - :param params: Params from AnsibleModule object - :type params: ``dict`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: dictionary suitable for submission to GCP - HealthCheck (HTTP/HTTPS) API. - :rtype ``dict`` - """ - gcp_dict = GCPUtils.params_to_gcp_dict(params, 'healthcheck_name') - if 'timeout' in gcp_dict: - gcp_dict['timeoutSec'] = gcp_dict['timeout'] - del gcp_dict['timeout'] - - if 'checkInterval' in gcp_dict: - gcp_dict['checkIntervalSec'] = gcp_dict['checkInterval'] - del gcp_dict['checkInterval'] - - if 'hostHeader' in gcp_dict: - gcp_dict['host'] = gcp_dict['hostHeader'] - del gcp_dict['hostHeader'] - - if 'healthcheckType' in gcp_dict: - del gcp_dict['healthcheckType'] - return gcp_dict - - -def _get_req_resource(client, resource_type): - if resource_type == 'HTTPS': - return (client.httpsHealthChecks(), 'httpsHealthCheck') - else: - return (client.httpHealthChecks(), 'httpHealthCheck') - - -def get_healthcheck(client, name, project_id=None, resource_type='HTTP'): - """ - Get a Healthcheck from GCP. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Url Map. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: A dict resp from the respective GCP 'get' request. - :rtype: ``dict`` - """ - try: - resource, entity_name = _get_req_resource(client, resource_type) - args = {'project': project_id, entity_name: name} - req = resource.get(**args) - return GCPUtils.execute_api_client_req(req, raise_404=False) - except Exception: - raise - - -def create_healthcheck(client, params, project_id, resource_type='HTTP'): - """ - Create a new Healthcheck. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_healthcheck_dict(params) - try: - resource, _ = _get_req_resource(client, resource_type) - args = {'project': project_id, 'body': gcp_dict} - req = resource.insert(**args) - return_data = GCPUtils.execute_api_client_req(req, client, raw=False) - if not return_data: - return_data = get_healthcheck(client, - name=params['healthcheck_name'], - project_id=project_id) - return (True, return_data) - except Exception: - raise - - -def delete_healthcheck(client, name, project_id, resource_type='HTTP'): - """ - Delete a Healthcheck. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Url Map. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - try: - resource, entity_name = _get_req_resource(client, resource_type) - args = {'project': project_id, entity_name: name} - req = resource.delete(**args) - return_data = GCPUtils.execute_api_client_req(req, client) - return (True, return_data) - except Exception: - raise - - -def update_healthcheck(client, healthcheck, params, name, project_id, - resource_type='HTTP'): - """ - Update a Healthcheck. - - If the healthcheck has not changed, the update will not occur. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param healthcheck: Name of the Url Map. - :type healthcheck: ``dict`` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :param name: Name of the Url Map. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_healthcheck_dict(params) - ans = GCPUtils.are_params_equal(healthcheck, gcp_dict) - if ans: - return (False, 'no update necessary') - - try: - resource, entity_name = _get_req_resource(client, resource_type) - args = {'project': project_id, entity_name: name, 'body': gcp_dict} - req = resource.update(**args) - return_data = GCPUtils.execute_api_client_req( - req, client=client, raw=False) - return (True, return_data) - except Exception: - raise - - -def main(): - module = AnsibleModule(argument_spec=dict( - healthcheck_name=dict(required=True), - healthcheck_type=dict(required=True, - choices=['HTTP', 'HTTPS']), - request_path=dict(required=False, default='/'), - check_interval=dict(required=False, type='int', default=5), - healthy_threshold=dict(required=False, type='int', default=2), - unhealthy_threshold=dict(required=False, type='int', default=2), - host_header=dict(required=False, type='str', default=''), - timeout=dict(required=False, type='int', default=5), - port=dict(required=False, type='int'), - state=dict(choices=['absent', 'present'], default='present'), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - credentials_file=dict(), - project_id=dict(), ), ) - - client, conn_params = get_google_api_client(module, 'compute', user_agent_product=USER_AGENT_PRODUCT, - user_agent_version=USER_AGENT_VERSION) - - params = {} - - params['healthcheck_name'] = module.params.get('healthcheck_name') - params['healthcheck_type'] = module.params.get('healthcheck_type') - params['request_path'] = module.params.get('request_path') - params['check_interval'] = module.params.get('check_interval') - params['healthy_threshold'] = module.params.get('healthy_threshold') - params['unhealthy_threshold'] = module.params.get('unhealthy_threshold') - params['host_header'] = module.params.get('host_header') - params['timeout'] = module.params.get('timeout') - params['port'] = module.params.get('port', None) - params['state'] = module.params.get('state') - - if not params['port']: - params['port'] = 80 - if params['healthcheck_type'] == 'HTTPS': - params['port'] = 443 - try: - _validate_healthcheck_params(params) - except Exception as e: - module.fail_json(msg=e.message, changed=False) - - changed = False - json_output = {'state': params['state']} - healthcheck = get_healthcheck(client, - name=params['healthcheck_name'], - project_id=conn_params['project_id'], - resource_type=params['healthcheck_type']) - - if not healthcheck: - if params['state'] == 'absent': - # Doesn't exist in GCE, and state==absent. - changed = False - module.fail_json( - msg="Cannot delete unknown healthcheck: %s" % - (params['healthcheck_name'])) - else: - # Create - changed, json_output['healthcheck'] = create_healthcheck(client, - params=params, - project_id=conn_params['project_id'], - resource_type=params['healthcheck_type']) - elif params['state'] == 'absent': - # Delete - changed, json_output['healthcheck'] = delete_healthcheck(client, - name=params['healthcheck_name'], - project_id=conn_params['project_id'], - resource_type=params['healthcheck_type']) - else: - changed, json_output['healthcheck'] = update_healthcheck(client, - healthcheck=healthcheck, - params=params, - name=params['healthcheck_name'], - project_id=conn_params['project_id'], - resource_type=params['healthcheck_type']) - json_output['changed'] = changed - json_output.update(params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/plugins/modules/cloud/google/gcp_target_proxy.py b/plugins/modules/cloud/google/gcp_target_proxy.py deleted file mode 100644 index 611cee04f8..0000000000 --- a/plugins/modules/cloud/google/gcp_target_proxy.py +++ /dev/null @@ -1,320 +0,0 @@ -#!/usr/bin/python -# Copyright 2017 Google Inc. -# 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 = ''' ---- -module: gcp_target_proxy -short_description: Create, Update or Destroy a Target_Proxy. -description: - - Create, Update or Destroy a Target_Proxy. See - U(https://cloud.google.com/compute/docs/load-balancing/http/target-proxies) for an overview. - More details on the Target_Proxy API can be found at - U(https://cloud.google.com/compute/docs/reference/latest/targetHttpProxies#resource-representations). -requirements: - - "python >= 2.6" - - "google-api-python-client >= 1.6.2" - - "google-auth >= 0.9.0" - - "google-auth-httplib2 >= 0.0.2" -deprecated: - removed_in: 2.0.0 # was Ansible 2.12 - why: Updated modules released with increased functionality - alternative: Use M(google.cloud.gcp_compute_target_http_proxy) instead. -notes: - - Currently only supports global HTTP proxy. -author: - - "Tom Melendez (@supertom) " -options: - target_proxy_name: - type: str - description: - - Name of the Target_Proxy. - required: true - target_proxy_type: - type: str - description: - - Type of Target_Proxy. HTTP, HTTPS or SSL. Only HTTP is currently supported. - required: true - choices: [HTTP] - url_map_name: - type: str - description: - - Name of the Url Map. Required if type is HTTP or HTTPS proxy. - required: false - project_id: - type: str - description: - - your GCE project ID - pem_file: - type: str - description: - - path to the pem file associated with the service account email - This option is deprecated. Use 'credentials_file'. - credentials_file: - type: str - description: - - path to the JSON file associated with the service account email - service_account_email: - type: str - description: - - service account email - service_account_permissions: - type: list - description: - - service account permissions - state: - type: str - description: The state the target proxy should be in. C(present) or C(absent) are the only valid options. - required: true - choices: [present, absent] -''' - -EXAMPLES = ''' -- name: Create Minimum HTTP Target_Proxy - community.general.gcp_target_proxy: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - target_proxy_name: my-target_proxy - target_proxy_type: HTTP - url_map_name: my-url-map - state: present -''' - -RETURN = ''' -state: - description: state of the Target_Proxy - returned: Always. - type: str - sample: present -updated_target_proxy: - description: True if the target_proxy has been updated. Will not appear on - initial target_proxy creation. - returned: if the target_proxy has been updated. - type: bool - sample: true -target_proxy_name: - description: Name of the Target_Proxy - returned: Always - type: str - sample: my-target-proxy -target_proxy_type: - description: Type of Target_Proxy. One of HTTP, HTTPS or SSL. - returned: Always - type: str - sample: HTTP -target_proxy: - description: GCP Target_Proxy dictionary - returned: Always. Refer to GCP documentation for detailed field descriptions. - type: dict - sample: { "name": "my-target-proxy", "urlMap": "..." } -''' - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.community.general.plugins.module_utils.gcp import get_google_api_client, GCPUtils - - -USER_AGENT_PRODUCT = 'ansible-target_proxy' -USER_AGENT_VERSION = '0.0.1' - - -def _build_target_proxy_dict(params, project_id=None): - """ - Reformat services in Ansible Params. - - :param params: Params from AnsibleModule object - :type params: ``dict`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: dictionary suitable for submission to GCP UrlMap API. - :rtype ``dict`` - """ - url = '' - if project_id: - url = GCPUtils.build_googleapi_url(project_id) - gcp_dict = GCPUtils.params_to_gcp_dict(params, 'target_proxy_name') - if 'urlMap' in gcp_dict: - gcp_dict['urlMap'] = '%s/global/urlMaps/%s' % (url, - gcp_dict['urlMap']) - return gcp_dict - - -def get_target_http_proxy(client, name, project_id=None): - """ - Get a Target HTTP Proxy from GCP. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Target Proxy. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: A dict resp from the respective GCP 'get' request. - :rtype: ``dict`` - """ - req = client.targetHttpProxies().get(project=project_id, - targetHttpProxy=name) - return GCPUtils.execute_api_client_req(req, raise_404=False) - - -def create_target_http_proxy(client, params, project_id): - """ - Create a new Target_Proxy. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_target_proxy_dict(params, project_id) - try: - req = client.targetHttpProxies().insert(project=project_id, - body=gcp_dict) - return_data = GCPUtils.execute_api_client_req(req, client, raw=False) - if not return_data: - return_data = get_target_http_proxy(client, - name=params['target_proxy_name'], - project_id=project_id) - return (True, return_data) - except Exception: - raise - - -def delete_target_http_proxy(client, name, project_id): - """ - Delete a Target_Proxy. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Target Proxy. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - try: - req = client.targetHttpProxies().delete( - project=project_id, targetHttpProxy=name) - return_data = GCPUtils.execute_api_client_req(req, client) - return (True, return_data) - except Exception: - raise - - -def update_target_http_proxy(client, target_proxy, params, name, project_id): - """ - Update a HTTP Target_Proxy. Currently only the Url Map can be updated. - - If the target_proxy has not changed, the update will not occur. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param target_proxy: Name of the Target Proxy. - :type target_proxy: ``dict`` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :param name: Name of the Target Proxy. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_target_proxy_dict(params, project_id) - - GCPUtils.are_params_equal(target_proxy, gcp_dict) - if target_proxy['urlMap'] == gcp_dict['urlMap']: - return (False, 'no update necessary') - - try: - req = client.targetHttpProxies().setUrlMap(project=project_id, - targetHttpProxy=name, - body={"urlMap": gcp_dict['urlMap']}) - return_data = GCPUtils.execute_api_client_req( - req, client=client, raw=False) - return (True, return_data) - except Exception: - raise - - -def main(): - module = AnsibleModule(argument_spec=dict( - target_proxy_name=dict(required=True), - target_proxy_type=dict(required=True, choices=['HTTP']), - url_map_name=dict(required=False), - state=dict(required=True, choices=['absent', 'present']), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - pem_file=dict(), - credentials_file=dict(), - project_id=dict(), ), ) - - client, conn_params = get_google_api_client(module, 'compute', user_agent_product=USER_AGENT_PRODUCT, - user_agent_version=USER_AGENT_VERSION) - - params = {} - params['state'] = module.params.get('state') - params['target_proxy_name'] = module.params.get('target_proxy_name') - params['target_proxy_type'] = module.params.get('target_proxy_type') - params['url_map'] = module.params.get('url_map_name', None) - - changed = False - json_output = {'state': params['state']} - target_proxy = get_target_http_proxy(client, - name=params['target_proxy_name'], - project_id=conn_params['project_id']) - - if not target_proxy: - if params['state'] == 'absent': - # Doesn't exist in GCE, and state==absent. - changed = False - module.fail_json( - msg="Cannot delete unknown target_proxy: %s" % - (params['target_proxy_name'])) - else: - # Create - changed, json_output['target_proxy'] = create_target_http_proxy(client, - params=params, - project_id=conn_params['project_id']) - elif params['state'] == 'absent': - # Delete - changed, json_output['target_proxy'] = delete_target_http_proxy(client, - name=params['target_proxy_name'], - project_id=conn_params['project_id']) - else: - changed, json_output['target_proxy'] = update_target_http_proxy(client, - target_proxy=target_proxy, - params=params, - name=params['target_proxy_name'], - project_id=conn_params['project_id']) - json_output['updated_target_proxy'] = changed - - json_output['changed'] = changed - json_output.update(params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/plugins/modules/cloud/google/gcp_url_map.py b/plugins/modules/cloud/google/gcp_url_map.py deleted file mode 100644 index 3fc2c96be9..0000000000 --- a/plugins/modules/cloud/google/gcp_url_map.py +++ /dev/null @@ -1,535 +0,0 @@ -#!/usr/bin/python -# Copyright 2017 Google Inc. -# 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 = ''' ---- -module: gcp_url_map -short_description: Create, Update or Destroy a Url_Map. -description: - - Create, Update or Destroy a Url_Map. See - U(https://cloud.google.com/compute/docs/load-balancing/http/url-map) for an overview. - More details on the Url_Map API can be found at - U(https://cloud.google.com/compute/docs/reference/latest/urlMaps#resource). -requirements: - - "python >= 2.6" - - "google-api-python-client >= 1.6.2" - - "google-auth >= 0.9.0" - - "google-auth-httplib2 >= 0.0.2" -notes: - - Only supports global Backend Services. - - Url_Map tests are not currently supported. -author: - - "Tom Melendez (@supertom) " -deprecated: - removed_in: 2.0.0 # was Ansible 2.12 - why: Updated modules released with increased functionality - alternative: Use M(google.cloud.gcp_compute_url_map) instead. -options: - url_map_name: - type: str - description: - - Name of the Url_Map. - required: true - default_service: - type: str - description: - - Default Backend Service if no host rules match. - required: true - host_rules: - type: list - description: - - The list of HostRules to use against the URL. Contains - a list of hosts and an associated path_matcher. - - The 'hosts' parameter is a list of host patterns to match. They - must be valid hostnames, except * will match any string of - ([a-z0-9-.]*). In that case, * must be the first character - and must be followed in the pattern by either - or .. - - The 'path_matcher' parameter is name of the PathMatcher to use - to match the path portion of the URL if the hostRule matches the URL's - host portion. - required: false - path_matchers: - type: list - description: - - The list of named PathMatchers to use against the URL. Contains - path_rules, which is a list of paths and an associated service. A - default_service can also be specified for each path_matcher. - - The 'name' parameter to which this path_matcher is referred by the - host_rule. - - The 'default_service' parameter is the name of the - BackendService resource. This will be used if none of the path_rules - defined by this path_matcher is matched by the URL's path portion. - - The 'path_rules' parameter is a list of dictionaries containing a - list of paths and a service to direct traffic to. Each path item must - start with / and the only place a * is allowed is at the end following - a /. The string fed to the path matcher does not include any text after - the first ? or #, and those chars are not allowed here. - required: false - project_id: - type: str - description: - - The Google Cloud Platform project ID to use. - pem_file: - type: str - description: - - The path to the PEM file associated with the service account email. - - This option is deprecated and may be removed in a future release. Use I(credentials_file) instead. - credentials_file: - type: str - description: - - The path to the JSON file associated with the service account email. - service_account_email: - type: str - description: - - service account email - service_account_permissions: - type: list - description: - - service account permissions - state: - type: str - description: The state the URL map should be in. C(present) or C(absent) are the only valid options. - default: present - required: false - choices: [present, absent] -''' - -EXAMPLES = ''' -- name: Create Minimal Url_Map - community.general.gcp_url_map: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - url_map_name: my-url_map - default_service: my-backend-service - state: present -- name: Create UrlMap with pathmatcher - community.general.gcp_url_map: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - url_map_name: my-url-map-pm - default_service: default-backend-service - path_matchers: - - name: 'path-matcher-one' - description: 'path matcher one' - default_service: 'bes-pathmatcher-one-default' - path_rules: - - service: 'my-one-bes' - paths: - - '/data' - - '/aboutus' - host_rules: - - hosts: - - '*.' - path_matcher: 'path-matcher-one' - state: "present" -''' - -RETURN = ''' -host_rules: - description: List of HostRules. - returned: If specified. - type: dict - sample: [ { hosts: ["*."], "path_matcher": "my-pm" } ] -path_matchers: - description: The list of named PathMatchers to use against the URL. - returned: If specified. - type: dict - sample: [ { "name": "my-pm", "path_rules": [ { "paths": [ "/data" ] } ], "service": "my-service" } ] -state: - description: state of the Url_Map - returned: Always. - type: str - sample: present -updated_url_map: - description: True if the url_map has been updated. Will not appear on - initial url_map creation. - returned: if the url_map has been updated. - type: bool - sample: true -url_map_name: - description: Name of the Url_Map - returned: Always - type: str - sample: my-url-map -url_map: - description: GCP Url_Map dictionary - returned: Always. Refer to GCP documentation for detailed field descriptions. - type: dict - sample: { "name": "my-url-map", "hostRules": [...], "pathMatchers": [...] } -''' - -try: - from ast import literal_eval - HAS_PYTHON26 = True -except ImportError: - HAS_PYTHON26 = False - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.community.general.plugins.module_utils.gcp import check_params, get_google_api_client, GCPUtils -from ansible.module_utils.six import string_types - - -USER_AGENT_PRODUCT = 'ansible-url_map' -USER_AGENT_VERSION = '0.0.1' - - -def _validate_params(params): - """ - Validate url_map params. - - This function calls _validate_host_rules_params to verify - the host_rules-specific parameters. - - This function calls _validate_path_matchers_params to verify - the path_matchers-specific parameters. - - :param params: Ansible dictionary containing configuration. - :type params: ``dict`` - - :return: True or raises ValueError - :rtype: ``bool`` or `class:ValueError` - """ - fields = [ - {'name': 'default_service', 'type': str, 'required': True}, - {'name': 'host_rules', 'type': list}, - {'name': 'path_matchers', 'type': list}, - ] - try: - check_params(params, fields) - if 'path_matchers' in params and params['path_matchers'] is not None: - _validate_path_matcher_params(params['path_matchers']) - if 'host_rules' in params and params['host_rules'] is not None: - _validate_host_rules_params(params['host_rules']) - except Exception: - raise - - return (True, '') - - -def _validate_path_matcher_params(path_matchers): - """ - Validate configuration for path_matchers. - - :param path_matchers: Ansible dictionary containing path_matchers - configuration (only). - :type path_matchers: ``dict`` - - :return: True or raises ValueError - :rtype: ``bool`` or `class:ValueError` - """ - fields = [ - {'name': 'name', 'type': str, 'required': True}, - {'name': 'default_service', 'type': str, 'required': True}, - {'name': 'path_rules', 'type': list, 'required': True}, - {'name': 'max_rate', 'type': int}, - {'name': 'max_rate_per_instance', 'type': float}, - ] - pr_fields = [ - {'name': 'service', 'type': str, 'required': True}, - {'name': 'paths', 'type': list, 'required': True}, - ] - - if not path_matchers: - raise ValueError(('path_matchers should be a list. %s (%s) provided' - % (path_matchers, type(path_matchers)))) - - for pm in path_matchers: - try: - check_params(pm, fields) - for pr in pm['path_rules']: - check_params(pr, pr_fields) - for path in pr['paths']: - if not path.startswith('/'): - raise ValueError("path for %s must start with /" % ( - pm['name'])) - except Exception: - raise - - return (True, '') - - -def _validate_host_rules_params(host_rules): - """ - Validate configuration for host_rules. - - :param host_rules: Ansible dictionary containing host_rules - configuration (only). - :type host_rules ``dict`` - - :return: True or raises ValueError - :rtype: ``bool`` or `class:ValueError` - """ - fields = [ - {'name': 'path_matcher', 'type': str, 'required': True}, - ] - - if not host_rules: - raise ValueError('host_rules should be a list.') - - for hr in host_rules: - try: - check_params(hr, fields) - for host in hr['hosts']: - if not isinstance(host, string_types): - raise ValueError("host in hostrules must be a string") - elif '*' in host: - if host.index('*') != 0: - raise ValueError("wildcard must be first char in host, %s" % ( - host)) - else: - if host[1] not in ['.', '-', ]: - raise ValueError("wildcard be followed by a '.' or '-', %s" % ( - host)) - - except Exception: - raise - - return (True, '') - - -def _build_path_matchers(path_matcher_list, project_id): - """ - Reformat services in path matchers list. - - Specifically, builds out URLs. - - :param path_matcher_list: The GCP project ID. - :type path_matcher_list: ``list`` of ``dict`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: list suitable for submission to GCP - UrlMap API Path Matchers list. - :rtype ``list`` of ``dict`` - """ - url = '' - if project_id: - url = GCPUtils.build_googleapi_url(project_id) - for pm in path_matcher_list: - if 'defaultService' in pm: - pm['defaultService'] = '%s/global/backendServices/%s' % (url, - pm['defaultService']) - if 'pathRules' in pm: - for rule in pm['pathRules']: - if 'service' in rule: - rule['service'] = '%s/global/backendServices/%s' % (url, - rule['service']) - return path_matcher_list - - -def _build_url_map_dict(params, project_id=None): - """ - Reformat services in Ansible Params. - - :param params: Params from AnsibleModule object - :type params: ``dict`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: dictionary suitable for submission to GCP UrlMap API. - :rtype ``dict`` - """ - url = '' - if project_id: - url = GCPUtils.build_googleapi_url(project_id) - gcp_dict = GCPUtils.params_to_gcp_dict(params, 'url_map_name') - if 'defaultService' in gcp_dict: - gcp_dict['defaultService'] = '%s/global/backendServices/%s' % (url, - gcp_dict['defaultService']) - if 'pathMatchers' in gcp_dict: - gcp_dict['pathMatchers'] = _build_path_matchers(gcp_dict['pathMatchers'], project_id) - - return gcp_dict - - -def get_url_map(client, name, project_id=None): - """ - Get a Url_Map from GCP. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Url Map. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: A dict resp from the respective GCP 'get' request. - :rtype: ``dict`` - """ - try: - req = client.urlMaps().get(project=project_id, urlMap=name) - return GCPUtils.execute_api_client_req(req, raise_404=False) - except Exception: - raise - - -def create_url_map(client, params, project_id): - """ - Create a new Url_Map. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_url_map_dict(params, project_id) - try: - req = client.urlMaps().insert(project=project_id, body=gcp_dict) - return_data = GCPUtils.execute_api_client_req(req, client, raw=False) - if not return_data: - return_data = get_url_map(client, - name=params['url_map_name'], - project_id=project_id) - return (True, return_data) - except Exception: - raise - - -def delete_url_map(client, name, project_id): - """ - Delete a Url_Map. - - :param client: An initialized GCE Compute Discover resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Url Map. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - try: - req = client.urlMaps().delete(project=project_id, urlMap=name) - return_data = GCPUtils.execute_api_client_req(req, client) - return (True, return_data) - except Exception: - raise - - -def update_url_map(client, url_map, params, name, project_id): - """ - Update a Url_Map. - - If the url_map has not changed, the update will not occur. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param url_map: Name of the Url Map. - :type url_map: ``dict`` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :param name: Name of the Url Map. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_url_map_dict(params, project_id) - - ans = GCPUtils.are_params_equal(url_map, gcp_dict) - if ans: - return (False, 'no update necessary') - - gcp_dict['fingerprint'] = url_map['fingerprint'] - try: - req = client.urlMaps().update(project=project_id, - urlMap=name, body=gcp_dict) - return_data = GCPUtils.execute_api_client_req(req, client=client, raw=False) - return (True, return_data) - except Exception: - raise - - -def main(): - module = AnsibleModule(argument_spec=dict( - url_map_name=dict(required=True), - state=dict(choices=['absent', 'present'], default='present'), - default_service=dict(required=True), - path_matchers=dict(type='list', required=False), - host_rules=dict(type='list', required=False), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - pem_file=dict(), - credentials_file=dict(), - project_id=dict(), ), required_together=[ - ['path_matchers', 'host_rules'], ]) - - client, conn_params = get_google_api_client(module, 'compute', user_agent_product=USER_AGENT_PRODUCT, - user_agent_version=USER_AGENT_VERSION) - - params = {} - params['state'] = module.params.get('state') - params['url_map_name'] = module.params.get('url_map_name') - params['default_service'] = module.params.get('default_service') - if module.params.get('path_matchers'): - params['path_matchers'] = module.params.get('path_matchers') - if module.params.get('host_rules'): - params['host_rules'] = module.params.get('host_rules') - - try: - _validate_params(params) - except Exception as e: - module.fail_json(msg=e.message, changed=False) - - changed = False - json_output = {'state': params['state']} - url_map = get_url_map(client, - name=params['url_map_name'], - project_id=conn_params['project_id']) - - if not url_map: - if params['state'] == 'absent': - # Doesn't exist in GCE, and state==absent. - changed = False - module.fail_json( - msg="Cannot delete unknown url_map: %s" % - (params['url_map_name'])) - else: - # Create - changed, json_output['url_map'] = create_url_map(client, - params=params, - project_id=conn_params['project_id']) - elif params['state'] == 'absent': - # Delete - changed, json_output['url_map'] = delete_url_map(client, - name=params['url_map_name'], - project_id=conn_params['project_id']) - else: - changed, json_output['url_map'] = update_url_map(client, - url_map=url_map, - params=params, - name=params['url_map_name'], - project_id=conn_params['project_id']) - json_output['updated_url_map'] = changed - - json_output['changed'] = changed - json_output.update(params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/plugins/modules/cloud/google/gcspanner.py b/plugins/modules/cloud/google/gcspanner.py deleted file mode 100644 index e88fc26beb..0000000000 --- a/plugins/modules/cloud/google/gcspanner.py +++ /dev/null @@ -1,304 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright: (c) 2017, Google Inc. -# 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 = ''' ---- -module: gcspanner -short_description: Create and Delete Instances/Databases on Spanner -description: - - Create and Delete Instances/Databases on Spanner. - See U(https://cloud.google.com/spanner/docs) for an overview. -requirements: - - python >= 2.6 - - google-auth >= 0.5.0 - - google-cloud-spanner >= 0.23.0 -notes: - - Changing the configuration on an existing instance is not supported. -deprecated: - removed_in: 2.0.0 # was Ansible 2.12 - why: Updated modules released with increased functionality - alternative: Use M(google.cloud.gcp_spanner_database) and/or M(google.cloud.gcp_spanner_instance) instead. -author: - - Tom Melendez (@supertom) -options: - configuration: - type: str - description: - - Configuration the instance should use. - - Examples are us-central1, asia-east1 and europe-west1. - required: yes - instance_id: - type: str - description: - - GCP spanner instance name. - required: yes - database_name: - type: str - description: - - Name of database contained on the instance. - force_instance_delete: - description: - - To delete an instance, this argument must exist and be true (along with state being equal to absent). - type: bool - default: 'no' - instance_display_name: - type: str - description: - - Name of Instance to display. - - If not specified, instance_id will be used instead. - node_count: - type: int - description: - - Number of nodes in the instance. - default: 1 - state: - type: str - description: - - State of the instance or database. Applies to the most granular resource. - - If a C(database_name) is specified we remove it. - - If only C(instance_id) is specified, that is what is removed. - choices: [ absent, present ] - default: present - project_id: - type: str - description: - - your GCE project ID - credentials_file: - type: str - description: - - path to the JSON file associated with the service account email - service_account_email: - type: str - description: - - service account email -''' - -EXAMPLES = ''' -- name: Create instance - community.general.gcspanner: - instance_id: '{{ instance_id }}' - configuration: '{{ configuration }}' - state: present - node_count: 1 - -- name: Create database - community.general.gcspanner: - instance_id: '{{ instance_id }}' - configuration: '{{ configuration }}' - database_name: '{{ database_name }}' - state: present - -- name: Delete instance (and all databases) -- community.general.gcspanner: - instance_id: '{{ instance_id }}' - configuration: '{{ configuration }}' - state: absent - force_instance_delete: yes -''' - -RETURN = ''' -state: - description: The state of the instance or database. Value will be either 'absent' or 'present'. - returned: Always - type: str - sample: "present" - -database_name: - description: Name of database. - returned: When database name is specified - type: str - sample: "mydatabase" - -instance_id: - description: Name of instance. - returned: Always - type: str - sample: "myinstance" - -previous_values: - description: List of dictionaries containing previous values prior to update. - returned: When an instance update has occurred and a field has been modified. - type: dict - sample: "'previous_values': { 'instance': { 'instance_display_name': 'my-instance', 'node_count': 1 } }" - -updated: - description: Boolean field to denote an update has occurred. - returned: When an update has occurred. - type: bool - sample: True -''' -try: - from ast import literal_eval - HAS_PYTHON26 = True -except ImportError: - HAS_PYTHON26 = False - -try: - from google.cloud import spanner - from google.gax.errors import GaxError - HAS_GOOGLE_CLOUD_SPANNER = True -except ImportError as e: - HAS_GOOGLE_CLOUD_SPANNER = False - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.community.general.plugins.module_utils.gcp import check_min_pkg_version, get_google_cloud_credentials -from ansible.module_utils.six import string_types - - -CLOUD_CLIENT = 'google-cloud-spanner' -CLOUD_CLIENT_MINIMUM_VERSION = '0.23.0' -CLOUD_CLIENT_USER_AGENT = 'ansible-spanner-0.1' - - -def get_spanner_configuration_name(config_name, project_name): - config_name = 'projects/%s/instanceConfigs/regional-%s' % (project_name, - config_name) - return config_name - - -def instance_update(instance): - """ - Call update method on spanner client. - - Note: A ValueError exception is thrown despite the client succeeding. - So, we validate the node_count and instance_display_name parameters and then - ignore the ValueError exception. - - :param instance: a Spanner instance object - :type instance: class `google.cloud.spanner.Instance` - - :returns True on success, raises ValueError on type error. - :rtype ``bool`` - """ - errmsg = '' - if not isinstance(instance.node_count, int): - errmsg = 'node_count must be an integer %s (%s)' % ( - instance.node_count, type(instance.node_count)) - if instance.display_name and not isinstance(instance.display_name, - string_types): - errmsg = 'instance_display_name must be an string %s (%s)' % ( - instance.display_name, type(instance.display_name)) - if errmsg: - raise ValueError(errmsg) - - try: - instance.update() - except ValueError: - # The ValueError here is the one we 'expect'. - pass - - return True - - -def main(): - module = AnsibleModule( - argument_spec=dict( - instance_id=dict(type='str', required=True), - state=dict(type='str', default='present', choices=['absent', 'present']), - database_name=dict(type='str'), - configuration=dict(type='str', required=True), - node_count=dict(type='int', default=1), - instance_display_name=dict(type='str'), - force_instance_delete=dict(type='bool', default=False), - service_account_email=dict(type='str'), - credentials_file=dict(type='str'), - project_id=dict(type='str'), - ), - ) - - if not HAS_PYTHON26: - module.fail_json( - msg="GCE module requires python's 'ast' module, python v2.6+") - - if not HAS_GOOGLE_CLOUD_SPANNER: - module.fail_json(msg="Please install google-cloud-spanner.") - - if not check_min_pkg_version(CLOUD_CLIENT, CLOUD_CLIENT_MINIMUM_VERSION): - module.fail_json(msg="Please install %s client version %s" % - (CLOUD_CLIENT, CLOUD_CLIENT_MINIMUM_VERSION)) - - mod_params = {} - mod_params['state'] = module.params.get('state') - mod_params['instance_id'] = module.params.get('instance_id') - mod_params['database_name'] = module.params.get('database_name') - mod_params['configuration'] = module.params.get('configuration') - mod_params['node_count'] = module.params.get('node_count', None) - mod_params['instance_display_name'] = module.params.get('instance_display_name') - mod_params['force_instance_delete'] = module.params.get('force_instance_delete') - - creds, params = get_google_cloud_credentials(module) - spanner_client = spanner.Client(project=params['project_id'], - credentials=creds, - user_agent=CLOUD_CLIENT_USER_AGENT) - changed = False - json_output = {} - - i = None - if mod_params['instance_id']: - config_name = get_spanner_configuration_name( - mod_params['configuration'], params['project_id']) - i = spanner_client.instance(mod_params['instance_id'], - configuration_name=config_name) - d = None - if mod_params['database_name']: - # TODO(supertom): support DDL - ddl_statements = '' - d = i.database(mod_params['database_name'], ddl_statements) - - if mod_params['state'] == 'absent': - # Remove the most granular resource. If database is specified - # we remove it. If only instance is specified, that is what is removed. - if d is not None and d.exists(): - d.drop() - changed = True - else: - if i.exists(): - if mod_params['force_instance_delete']: - i.delete() - else: - module.fail_json( - msg=(("Cannot delete Spanner instance: " - "'force_instance_delete' argument not specified"))) - changed = True - elif mod_params['state'] == 'present': - if not i.exists(): - i = spanner_client.instance(mod_params['instance_id'], - configuration_name=config_name, - display_name=mod_params['instance_display_name'], - node_count=mod_params['node_count'] or 1) - i.create() - changed = True - else: - # update instance - i.reload() - inst_prev_vals = {} - if i.display_name != mod_params['instance_display_name']: - inst_prev_vals['instance_display_name'] = i.display_name - i.display_name = mod_params['instance_display_name'] - if mod_params['node_count']: - if i.node_count != mod_params['node_count']: - inst_prev_vals['node_count'] = i.node_count - i.node_count = mod_params['node_count'] - if inst_prev_vals: - changed = instance_update(i) - json_output['updated'] = changed - json_output['previous_values'] = {'instance': inst_prev_vals} - if d: - if not d.exists(): - d.create() - d.reload() - changed = True - - json_output['changed'] = changed - json_output.update(mod_params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/plugins/modules/gcdns_record.py b/plugins/modules/gcdns_record.py deleted file mode 120000 index d4c4d45f78..0000000000 --- a/plugins/modules/gcdns_record.py +++ /dev/null @@ -1 +0,0 @@ -./cloud/google/gcdns_record.py \ No newline at end of file diff --git a/plugins/modules/gcdns_zone.py b/plugins/modules/gcdns_zone.py deleted file mode 120000 index 14954c6fb9..0000000000 --- a/plugins/modules/gcdns_zone.py +++ /dev/null @@ -1 +0,0 @@ -./cloud/google/gcdns_zone.py \ No newline at end of file diff --git a/plugins/modules/gce.py b/plugins/modules/gce.py deleted file mode 120000 index b37c47be97..0000000000 --- a/plugins/modules/gce.py +++ /dev/null @@ -1 +0,0 @@ -./cloud/google/gce.py \ No newline at end of file diff --git a/plugins/modules/gcp_backend_service.py b/plugins/modules/gcp_backend_service.py deleted file mode 120000 index 04a387bb88..0000000000 --- a/plugins/modules/gcp_backend_service.py +++ /dev/null @@ -1 +0,0 @@ -./cloud/google/gcp_backend_service.py \ No newline at end of file diff --git a/plugins/modules/gcp_forwarding_rule.py b/plugins/modules/gcp_forwarding_rule.py deleted file mode 120000 index 16dbf69275..0000000000 --- a/plugins/modules/gcp_forwarding_rule.py +++ /dev/null @@ -1 +0,0 @@ -./cloud/google/gcp_forwarding_rule.py \ No newline at end of file diff --git a/plugins/modules/gcp_healthcheck.py b/plugins/modules/gcp_healthcheck.py deleted file mode 120000 index 47b97c6c93..0000000000 --- a/plugins/modules/gcp_healthcheck.py +++ /dev/null @@ -1 +0,0 @@ -./cloud/google/gcp_healthcheck.py \ No newline at end of file diff --git a/plugins/modules/gcp_target_proxy.py b/plugins/modules/gcp_target_proxy.py deleted file mode 120000 index 6c33fbe542..0000000000 --- a/plugins/modules/gcp_target_proxy.py +++ /dev/null @@ -1 +0,0 @@ -./cloud/google/gcp_target_proxy.py \ No newline at end of file diff --git a/plugins/modules/gcp_url_map.py b/plugins/modules/gcp_url_map.py deleted file mode 120000 index d976834425..0000000000 --- a/plugins/modules/gcp_url_map.py +++ /dev/null @@ -1 +0,0 @@ -./cloud/google/gcp_url_map.py \ No newline at end of file diff --git a/plugins/modules/gcspanner.py b/plugins/modules/gcspanner.py deleted file mode 120000 index f909c65cfb..0000000000 --- a/plugins/modules/gcspanner.py +++ /dev/null @@ -1 +0,0 @@ -./cloud/google/gcspanner.py \ No newline at end of file diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt index 38a0fce74d..7f5ee77015 100644 --- a/tests/sanity/ignore-2.10.txt +++ b/tests/sanity/ignore-2.10.txt @@ -14,10 +14,6 @@ plugins/modules/cloud/centurylink/clc_modify_server.py validate-modules:paramete plugins/modules/cloud/centurylink/clc_publicip.py validate-modules:parameter-list-no-elements plugins/modules/cloud/centurylink/clc_server.py validate-modules:parameter-list-no-elements plugins/modules/cloud/centurylink/clc_server_snapshot.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gcdns_record.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gce.py pylint:blacklisted-name -plugins/modules/cloud/google/gce.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gce.py yamllint:unparsable-with-libyaml plugins/modules/cloud/google/gce_eip.py pylint:blacklisted-name plugins/modules/cloud/google/gce_eip.py validate-modules:parameter-list-no-elements plugins/modules/cloud/google/gce_img.py pylint:blacklisted-name @@ -37,13 +33,6 @@ plugins/modules/cloud/google/gce_snapshot.py pylint:blacklisted-name plugins/modules/cloud/google/gce_snapshot.py validate-modules:parameter-list-no-elements plugins/modules/cloud/google/gce_tag.py pylint:blacklisted-name plugins/modules/cloud/google/gce_tag.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gcp_backend_service.py pylint:blacklisted-name -plugins/modules/cloud/google/gcp_backend_service.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gcp_forwarding_rule.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gcp_healthcheck.py pylint:blacklisted-name -plugins/modules/cloud/google/gcp_healthcheck.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gcp_target_proxy.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gcp_url_map.py validate-modules:parameter-list-no-elements plugins/modules/cloud/google/gcpubsub.py validate-modules:parameter-list-no-elements plugins/modules/cloud/google/gcpubsub_info.py validate-modules:parameter-state-invalid-choice plugins/modules/cloud/heroku/heroku_collaborator.py validate-modules:parameter-list-no-elements @@ -682,7 +671,5 @@ plugins/modules/web_infrastructure/rundeck_acl_policy.py pylint:blacklisted-name plugins/modules/web_infrastructure/sophos_utm/utm_network_interface_address.py validate-modules:parameter-type-not-in-doc plugins/modules/web_infrastructure/sophos_utm/utm_proxy_exception.py validate-modules:doc-elements-mismatch scripts/inventory/gce.py pylint:blacklisted-name -tests/unit/plugins/modules/cloud/google/test_gcp_forwarding_rule.py future-import-boilerplate -tests/unit/plugins/modules/cloud/google/test_gcp_forwarding_rule.py metaclass-boilerplate tests/utils/shippable/check_matrix.py replace-urlopen tests/utils/shippable/timing.py shebang diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt index 38a0fce74d..7f5ee77015 100644 --- a/tests/sanity/ignore-2.11.txt +++ b/tests/sanity/ignore-2.11.txt @@ -14,10 +14,6 @@ plugins/modules/cloud/centurylink/clc_modify_server.py validate-modules:paramete plugins/modules/cloud/centurylink/clc_publicip.py validate-modules:parameter-list-no-elements plugins/modules/cloud/centurylink/clc_server.py validate-modules:parameter-list-no-elements plugins/modules/cloud/centurylink/clc_server_snapshot.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gcdns_record.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gce.py pylint:blacklisted-name -plugins/modules/cloud/google/gce.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gce.py yamllint:unparsable-with-libyaml plugins/modules/cloud/google/gce_eip.py pylint:blacklisted-name plugins/modules/cloud/google/gce_eip.py validate-modules:parameter-list-no-elements plugins/modules/cloud/google/gce_img.py pylint:blacklisted-name @@ -37,13 +33,6 @@ plugins/modules/cloud/google/gce_snapshot.py pylint:blacklisted-name plugins/modules/cloud/google/gce_snapshot.py validate-modules:parameter-list-no-elements plugins/modules/cloud/google/gce_tag.py pylint:blacklisted-name plugins/modules/cloud/google/gce_tag.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gcp_backend_service.py pylint:blacklisted-name -plugins/modules/cloud/google/gcp_backend_service.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gcp_forwarding_rule.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gcp_healthcheck.py pylint:blacklisted-name -plugins/modules/cloud/google/gcp_healthcheck.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gcp_target_proxy.py validate-modules:parameter-list-no-elements -plugins/modules/cloud/google/gcp_url_map.py validate-modules:parameter-list-no-elements plugins/modules/cloud/google/gcpubsub.py validate-modules:parameter-list-no-elements plugins/modules/cloud/google/gcpubsub_info.py validate-modules:parameter-state-invalid-choice plugins/modules/cloud/heroku/heroku_collaborator.py validate-modules:parameter-list-no-elements @@ -682,7 +671,5 @@ plugins/modules/web_infrastructure/rundeck_acl_policy.py pylint:blacklisted-name plugins/modules/web_infrastructure/sophos_utm/utm_network_interface_address.py validate-modules:parameter-type-not-in-doc plugins/modules/web_infrastructure/sophos_utm/utm_proxy_exception.py validate-modules:doc-elements-mismatch scripts/inventory/gce.py pylint:blacklisted-name -tests/unit/plugins/modules/cloud/google/test_gcp_forwarding_rule.py future-import-boilerplate -tests/unit/plugins/modules/cloud/google/test_gcp_forwarding_rule.py metaclass-boilerplate tests/utils/shippable/check_matrix.py replace-urlopen tests/utils/shippable/timing.py shebang diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt index e8e9ea5a7e..b91d2e4fce 100644 --- a/tests/sanity/ignore-2.9.txt +++ b/tests/sanity/ignore-2.9.txt @@ -6,16 +6,6 @@ plugins/module_utils/_mount.py future-import-boilerplate plugins/module_utils/_mount.py metaclass-boilerplate plugins/modules/cloud/centurylink/clc_alert_policy.py validate-modules:no-default-for-required-parameter plugins/modules/cloud/centurylink/clc_firewall_policy.py validate-modules:no-default-for-required-parameter -plugins/modules/cloud/google/gcdns_record.py validate-modules:deprecation-mismatch -plugins/modules/cloud/google/gcdns_record.py validate-modules:invalid-documentation -plugins/modules/cloud/google/gcdns_record.py validate-modules:missing-main-call -plugins/modules/cloud/google/gcdns_zone.py validate-modules:deprecation-mismatch -plugins/modules/cloud/google/gcdns_zone.py validate-modules:invalid-documentation -plugins/modules/cloud/google/gcdns_zone.py validate-modules:missing-main-call -plugins/modules/cloud/google/gce.py pylint:blacklisted-name -plugins/modules/cloud/google/gce.py validate-modules:deprecation-mismatch -plugins/modules/cloud/google/gce.py validate-modules:invalid-documentation -plugins/modules/cloud/google/gce.py validate-modules:missing-main-call plugins/modules/cloud/google/gce_eip.py pylint:blacklisted-name plugins/modules/cloud/google/gce_img.py pylint:blacklisted-name plugins/modules/cloud/google/gce_instance_template.py pylint:blacklisted-name @@ -26,26 +16,6 @@ plugins/modules/cloud/google/gce_net.py pylint:blacklisted-name plugins/modules/cloud/google/gce_pd.py pylint:blacklisted-name plugins/modules/cloud/google/gce_snapshot.py pylint:blacklisted-name plugins/modules/cloud/google/gce_tag.py pylint:blacklisted-name -plugins/modules/cloud/google/gcp_backend_service.py pylint:blacklisted-name -plugins/modules/cloud/google/gcp_backend_service.py validate-modules:deprecation-mismatch -plugins/modules/cloud/google/gcp_backend_service.py validate-modules:invalid-documentation -plugins/modules/cloud/google/gcp_backend_service.py validate-modules:missing-main-call -plugins/modules/cloud/google/gcp_forwarding_rule.py validate-modules:deprecation-mismatch -plugins/modules/cloud/google/gcp_forwarding_rule.py validate-modules:invalid-documentation -plugins/modules/cloud/google/gcp_forwarding_rule.py validate-modules:missing-main-call -plugins/modules/cloud/google/gcp_healthcheck.py pylint:blacklisted-name -plugins/modules/cloud/google/gcp_healthcheck.py validate-modules:deprecation-mismatch -plugins/modules/cloud/google/gcp_healthcheck.py validate-modules:invalid-documentation -plugins/modules/cloud/google/gcp_healthcheck.py validate-modules:missing-main-call -plugins/modules/cloud/google/gcp_target_proxy.py validate-modules:deprecation-mismatch -plugins/modules/cloud/google/gcp_target_proxy.py validate-modules:invalid-documentation -plugins/modules/cloud/google/gcp_target_proxy.py validate-modules:missing-main-call -plugins/modules/cloud/google/gcp_url_map.py validate-modules:deprecation-mismatch -plugins/modules/cloud/google/gcp_url_map.py validate-modules:invalid-documentation -plugins/modules/cloud/google/gcp_url_map.py validate-modules:missing-main-call -plugins/modules/cloud/google/gcspanner.py validate-modules:deprecation-mismatch -plugins/modules/cloud/google/gcspanner.py validate-modules:invalid-documentation -plugins/modules/cloud/google/gcspanner.py validate-modules:missing-main-call plugins/modules/cloud/kubevirt/kubevirt_cdi_upload.py validate-modules:doc-missing-type plugins/modules/cloud/kubevirt/kubevirt_preset.py validate-modules:parameter-type-not-in-doc plugins/modules/cloud/kubevirt/kubevirt_pvc.py validate-modules:parameter-type-not-in-doc @@ -530,7 +500,5 @@ plugins/modules/web_infrastructure/nginx_status_facts.py validate-modules:invali plugins/modules/web_infrastructure/rundeck_acl_policy.py pylint:blacklisted-name plugins/modules/web_infrastructure/sophos_utm/utm_network_interface_address.py validate-modules:parameter-type-not-in-doc scripts/inventory/gce.py pylint:blacklisted-name -tests/unit/plugins/modules/cloud/google/test_gcp_forwarding_rule.py future-import-boilerplate -tests/unit/plugins/modules/cloud/google/test_gcp_forwarding_rule.py metaclass-boilerplate tests/utils/shippable/check_matrix.py replace-urlopen tests/utils/shippable/timing.py shebang diff --git a/tests/unit/plugins/modules/cloud/google/test_gcp_forwarding_rule.py b/tests/unit/plugins/modules/cloud/google/test_gcp_forwarding_rule.py deleted file mode 100644 index ebaf6bb51f..0000000000 --- a/tests/unit/plugins/modules/cloud/google/test_gcp_forwarding_rule.py +++ /dev/null @@ -1,30 +0,0 @@ -import unittest - -from ansible_collections.community.general.plugins.modules.cloud.google.gcp_forwarding_rule import _build_global_forwarding_rule_dict - - -class TestGCPFowardingRule(unittest.TestCase): - """Unit tests for gcp_fowarding_rule module.""" - params_dict = { - 'forwarding_rule_name': 'foo_fowarding_rule_name', - 'address': 'foo_external_address', - 'target': 'foo_targetproxy', - 'region': 'global', - 'port_range': 80, - 'protocol': 'TCP', - 'state': 'present', - } - - def test__build_global_forwarding_rule_dict(self): - - expected = { - 'name': 'foo_fowarding_rule_name', - 'IPAddress': 'https://www.googleapis.com/compute/v1/projects/my-project/global/addresses/foo_external_address', - 'target': 'https://www.googleapis.com/compute/v1/projects/my-project/global/targetHttpProxies/foo_targetproxy', - 'region': 'global', - 'portRange': 80, - 'IPProtocol': 'TCP', - } - actual = _build_global_forwarding_rule_dict( - self.params_dict, 'my-project') - self.assertEqual(expected, actual) diff --git a/tests/unit/plugins/modules/cloud/google/test_gcp_url_map.py b/tests/unit/plugins/modules/cloud/google/test_gcp_url_map.py deleted file mode 100644 index 84b46357db..0000000000 --- a/tests/unit/plugins/modules/cloud/google/test_gcp_url_map.py +++ /dev/null @@ -1,169 +0,0 @@ -# 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 - -import unittest - -from ansible_collections.community.general.plugins.modules.cloud.google.gcp_url_map import _build_path_matchers, _build_url_map_dict - - -class TestGCPUrlMap(unittest.TestCase): - """Unit tests for gcp_url_map module.""" - params_dict = { - 'url_map_name': 'foo_url_map_name', - 'description': 'foo_url_map description', - 'host_rules': [ - { - 'description': 'host rules description', - 'hosts': [ - 'www.example.com', - 'www2.example.com' - ], - 'path_matcher': 'host_rules_path_matcher' - } - ], - 'path_matchers': [ - { - 'name': 'path_matcher_one', - 'description': 'path matcher one', - 'defaultService': 'bes-pathmatcher-one-default', - 'pathRules': [ - { - 'service': 'my-one-bes', - 'paths': [ - '/', - '/aboutus' - ] - } - ] - }, - { - 'name': 'path_matcher_two', - 'description': 'path matcher two', - 'defaultService': 'bes-pathmatcher-two-default', - 'pathRules': [ - { - 'service': 'my-two-bes', - 'paths': [ - '/webapp', - '/graphs' - ] - } - ] - } - ] - } - - def test__build_path_matchers(self): - input_list = [ - { - 'defaultService': 'bes-pathmatcher-one-default', - 'description': 'path matcher one', - 'name': 'path_matcher_one', - 'pathRules': [ - { - 'paths': [ - '/', - '/aboutus' - ], - 'service': 'my-one-bes' - } - ] - }, - { - 'defaultService': 'bes-pathmatcher-two-default', - 'description': 'path matcher two', - 'name': 'path_matcher_two', - 'pathRules': [ - { - 'paths': [ - '/webapp', - '/graphs' - ], - 'service': 'my-two-bes' - } - ] - } - ] - expected = [ - { - 'defaultService': 'https://www.googleapis.com/compute/v1/projects/my-project/global/backendServices/bes-pathmatcher-one-default', - 'description': 'path matcher one', - 'name': 'path_matcher_one', - 'pathRules': [ - { - 'paths': [ - '/', - '/aboutus' - ], - 'service': 'https://www.googleapis.com/compute/v1/projects/my-project/global/backendServices/my-one-bes' - } - ] - }, - { - 'defaultService': 'https://www.googleapis.com/compute/v1/projects/my-project/global/backendServices/bes-pathmatcher-two-default', - 'description': 'path matcher two', - 'name': 'path_matcher_two', - 'pathRules': [ - { - 'paths': [ - '/webapp', - '/graphs' - ], - 'service': 'https://www.googleapis.com/compute/v1/projects/my-project/global/backendServices/my-two-bes' - } - ] - } - ] - actual = _build_path_matchers(input_list, 'my-project') - self.assertEqual(expected, actual) - - def test__build_url_map_dict(self): - - expected = { - 'description': 'foo_url_map description', - 'hostRules': [ - { - 'description': 'host rules description', - 'hosts': [ - 'www.example.com', - 'www2.example.com' - ], - 'pathMatcher': 'host_rules_path_matcher' - } - ], - 'name': 'foo_url_map_name', - 'pathMatchers': [ - { - 'defaultService': 'https://www.googleapis.com/compute/v1/projects/my-project/global/backendServices/bes-pathmatcher-one-default', - 'description': 'path matcher one', - 'name': 'path_matcher_one', - 'pathRules': [ - { - 'paths': [ - '/', - '/aboutus' - ], - 'service': 'https://www.googleapis.com/compute/v1/projects/my-project/global/backendServices/my-one-bes' - } - ] - }, - { - 'defaultService': 'https://www.googleapis.com/compute/v1/projects/my-project/global/backendServices/bes-pathmatcher-two-default', - 'description': 'path matcher two', - 'name': 'path_matcher_two', - 'pathRules': [ - { - 'paths': [ - '/webapp', - '/graphs' - ], - 'service': 'https://www.googleapis.com/compute/v1/projects/my-project/global/backendServices/my-two-bes' - } - ] - } - ] - } - actual = _build_url_map_dict(self.params_dict, 'my-project') - self.assertEqual(expected, actual)