mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Remove deprecated google modules (#1370)
* Remove deprecated google modules. * Remove ignore.txt entries.
This commit is contained in:
parent
eab9a43d2e
commit
c52839c601
25 changed files with 28 additions and 4610 deletions
10
changelogs/fragments/remove-deprecated-modules-2.yml
Normal file
10
changelogs/fragments/remove-deprecated-modules-2.yml
Normal file
|
@ -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).
|
|
@ -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
|
||||
|
|
|
@ -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'] = '<absent>'
|
||||
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'] = '<absent>'
|
||||
|
||||
changed = remove_record(module, gcdns, record)
|
||||
|
||||
module.exit_json(changed=changed, diff=diff, **json_output)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -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'] = '<absent>'
|
||||
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'] = '<absent>'
|
||||
|
||||
changed = remove_zone(module, gcdns, zone)
|
||||
|
||||
module.exit_json(changed=changed, diff=diff, **json_output)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -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) <erjohnso@google.com>
|
||||
- Tom Melendez (@supertom) <supertom@google.com>
|
||||
'''
|
||||
|
||||
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()
|
|
@ -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) <tom@supertom.com>"
|
||||
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()
|
|
@ -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) <tom@supertom.com>"
|
||||
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()
|
|
@ -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) <tom@supertom.com>"
|
||||
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()
|
|
@ -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) <tom@supertom.com>"
|
||||
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()
|
|
@ -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) <tom@supertom.com>"
|
||||
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()
|
|
@ -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) <tom@supertom.com>
|
||||
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()
|
|
@ -1 +0,0 @@
|
|||
./cloud/google/gcdns_record.py
|
|
@ -1 +0,0 @@
|
|||
./cloud/google/gcdns_zone.py
|
|
@ -1 +0,0 @@
|
|||
./cloud/google/gce.py
|
|
@ -1 +0,0 @@
|
|||
./cloud/google/gcp_backend_service.py
|
|
@ -1 +0,0 @@
|
|||
./cloud/google/gcp_forwarding_rule.py
|
|
@ -1 +0,0 @@
|
|||
./cloud/google/gcp_healthcheck.py
|
|
@ -1 +0,0 @@
|
|||
./cloud/google/gcp_target_proxy.py
|
|
@ -1 +0,0 @@
|
|||
./cloud/google/gcp_url_map.py
|
|
@ -1 +0,0 @@
|
|||
./cloud/google/gcspanner.py
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
Loading…
Reference in a new issue