diff options
Diffstat (limited to 'lib/ansible/modules/cloud/google')
75 files changed, 0 insertions, 9015 deletions
diff --git a/lib/ansible/modules/cloud/google/_gcdns_record.py b/lib/ansible/modules/cloud/google/_gcdns_record.py deleted file mode 100644 index e6e2d7049c..0000000000 --- a/lib/ansible/modules/cloud/google/_gcdns_record.py +++ /dev/null @@ -1,775 +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 -################################################################################ - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'community'} - - -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. -version_added: "2.2" -author: "William Albert (@walbert947)" -requirements: - - "python >= 2.6" - - "apache-libcloud >= 0.19.0" -deprecated: - removed_in: "2.12" - why: Updated modules released with increased functionality - alternative: Use M(gcp_dns_resource_record_set) instead. -options: - state: - description: - - Whether the given resource record should or should not be present. - choices: ["present", "absent"] - default: "present" - record: - description: - - The fully-qualified domain name of the resource record. - required: true - aliases: ['name'] - zone: - 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: - 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: - description: - - The type of resource record to add. - required: true - choices: [ 'A', 'AAAA', 'CNAME', 'SRV', 'TXT', 'SOA', 'NS', 'MX', 'SPF', 'PTR' ] - record_data: - 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: - 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: - description: - - The e-mail address for a service account with access to Google - Cloud DNS. - pem_file: - 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: - description: - - The path to the JSON file associated with the service account - email. - project_id: - description: - - The Google Cloud Platform project ID to use. -notes: - - See also M(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 = ''' -# Create an A record. -- gcdns_record: - record: 'www1.example.com' - zone: 'example.com' - type: A - value: '1.2.3.4' - -# Update an existing record. -- gcdns_record: - record: 'www1.example.com' - zone: 'example.com' - type: A - overwrite: true - value: '5.6.7.8' - -# Remove an A record. -- gcdns_record: - record: 'www1.example.com' - zone_id: 'example-com' - state: absent - type: A - value: '5.6.7.8' - -# Create a CNAME record. -- gcdns_record: - record: 'www.example.com' - zone_id: 'example-com' - type: CNAME - value: 'www.example.com.' # Note the trailing dot - -# Create an MX record with a custom TTL. -- gcdns_record: - record: 'example.com' - zone: 'example.com' - type: MX - ttl: 3600 - value: '10 mail.example.com.' # Note the trailing dot - -# Create multiple A records with the same name. -- 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' - -# Change the value of an existing record with multiple record_data. -- 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' - -# Safely remove a multi-line record. -- 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' - -# Unconditionally remove a record. -- gcdns_record: - record: 'api.example.com' - zone_id: 'example-com' - state: absent - overwrite: true # overwrite is true, so no values are needed - type: A - -# Create an AAAA record -- gcdns_record: - record: 'www1.example.com' - zone: 'example.com' - type: AAAA - value: 'fd00:db8::1' - -# Create a PTR record -- 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. - -# Create an NS record -- 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.' - -# Create a TXT record -- 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.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() diff --git a/lib/ansible/modules/cloud/google/_gcdns_zone.py b/lib/ansible/modules/cloud/google/_gcdns_zone.py deleted file mode 100644 index 57eaf944ef..0000000000 --- a/lib/ansible/modules/cloud/google/_gcdns_zone.py +++ /dev/null @@ -1,371 +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 -################################################################################ - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gcdns_zone -short_description: Creates or removes zones in Google Cloud DNS -description: - - Creates or removes managed zones in Google Cloud DNS. -version_added: "2.2" -author: "William Albert (@walbert947)" -requirements: - - "apache-libcloud >= 0.19.0" -deprecated: - removed_in: "2.12" - why: Updated modules released with increased functionality - alternative: Use M(gcp_dns_managed_zone) instead. -options: - state: - description: - - Whether the given zone should or should not be present. - choices: ["present", "absent"] - default: "present" - zone: - 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: - description: - - An arbitrary text string to use for the zone description. - default: "" - service_account_email: - description: - - The e-mail address for a service account with access to Google - Cloud DNS. - pem_file: - 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: - description: - - The path to the JSON file associated with the service account - email. - project_id: - description: - - The Google Cloud Platform project ID to use. -notes: - - See also M(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. - gcdns_zone: zone=example.com - -# Zone removal example. -- name: Remove a zone. - gcdns_zone: zone=example.com state=absent - -# Zone creation with description -- name: Creating a zone with a description - 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.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() diff --git a/lib/ansible/modules/cloud/google/_gce.py b/lib/ansible/modules/cloud/google/_gce.py deleted file mode 100644 index d5bd6abf28..0000000000 --- a/lib/ansible/modules/cloud/google/_gce.py +++ /dev/null @@ -1,754 +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 - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'community'} - -DOCUMENTATION = ''' ---- -module: gce -version_added: "1.4" -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.12" - why: Updated modules released with increased functionality - alternative: Use M(gcp_compute_instance) instead. -options: - image: - description: - - image string to use for the instance (default will follow latest - stable debian image) - default: "debian-8" - image_family: - description: - - image family from which to select the image. The most recent - non-deprecated image in the family will be used. - version_added: "2.4" - external_projects: - description: - - A list of other projects (accessible with the provisioning credentials) - to be searched for the image. - version_added: "2.4" - instance_names: - description: - - a comma-separated list of instance names to create or destroy - machine_type: - description: - - machine type to use for the instance, use 'n1-standard-1' by default - default: "n1-standard-1" - metadata: - description: - - a hash/dictionary of custom data for the instance; - '{"key":"value", ...}' - service_account_email: - version_added: "1.5.1" - description: - - service account email - service_account_permissions: - version_added: "2.0" - description: - - service account permissions (see - U(https://cloud.google.com/sdk/gcloud/reference/compute/instances/create), - --scopes section for detailed information) - choices: [ - "bigquery", "cloud-platform", "compute-ro", "compute-rw", - "useraccounts-ro", "useraccounts-rw", "datastore", "logging-write", - "monitoring", "sql-admin", "storage-full", "storage-ro", - "storage-rw", "taskqueue", "userinfo-email" - ] - pem_file: - version_added: "1.5.1" - description: - - path to the pem file associated with the service account email - This option is deprecated. Use 'credentials_file'. - credentials_file: - version_added: "2.1.0" - description: - - path to the JSON file associated with the service account email - project_id: - version_added: "1.5.1" - description: - - your GCE project ID - name: - 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: - description: - - can be used with 'name', specifies - the number of nodes to provision using 'name' - as a base name - version_added: "2.3" - network: - description: - - name of the network, 'default' will be used if not specified - default: "default" - subnetwork: - description: - - name of the subnetwork in which the instance should be created - version_added: "2.2" - persistent_boot_disk: - description: - - if set, create the instance with a persistent boot disk - type: bool - default: 'no' - disks: - 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). - version_added: "1.7" - state: - description: - - desired state of the resource - default: "present" - choices: ["active", "present", "absent", "deleted", "started", "stopped", "terminated"] - tags: - description: - - a comma-separated list of tags to associate with the instance - zone: - 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). - required: true - default: "us-central1-a" - ip_forward: - version_added: "1.9" - description: - - set to C(yes) if the instance can forward ip packets (useful for - gateways) - type: bool - default: 'no' - external_ip: - version_added: "1.9" - 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: - version_added: "1.9" - description: - - if set boot disk will be removed after instance destruction - type: bool - default: 'yes' - preemptible: - version_added: "2.1" - description: - - if set to C(yes), instances will be preemptible and time-limited. - (requires libcloud >= 0.20.0) - type: bool - default: 'no' - disk_size: - description: - - The size of the boot disk created for this instance (in GB) - default: 10 - version_added: "2.3" - -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) - - 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. - - 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. - - 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. - 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 - add_host: - hostname: "{{ item.public_ip }}" - groupname: gce_instances_ips - with_items: "{{ gce.instance_data }}" - - - name: Wait for SSH for instances - 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. - 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.module_utils.gce import gce_connect, unexpected_error_msg -from ansible.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 - 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 - gce: authenticated GCE connection object - instance_names: a list of instance names to terminate - zone: GCEZone object where the instances reside prior to termination - state: 'state' parameter passed into module as argument - - Returns a dictionary of instance names that were changed. - - """ - changed = False - nodes = [] - state_instance_names = [] - - if isinstance(instance_names, str) and number: - node_names = ['%s-%03d' % (instance_names, i) for i in range(number)] - elif isinstance(instance_names, str) and not number: - node_names = [instance_names] - else: - node_names = instance_names - - for name in node_names: - inst = None - try: - inst = gce.ex_get_node(name, zone) - except ResourceNotFoundError: - state_instance_names.append(name) - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - else: - nodes.append(inst) - state_instance_names.append(name) - - if state in ['absent', 'deleted'] and number: - changed_nodes = gce.ex_destroy_multiple_nodes(nodes) or [False] - changed = reduce(lambda x, y: x or y, changed_nodes) - else: - for node in nodes: - if state in ['absent', 'deleted']: - gce.destroy_node(node) - changed = True - elif state == 'started' and node.state == libcloud.compute.types.NodeState.STOPPED: - gce.ex_start_node(node) - changed = True - elif state in ['stopped', 'terminated'] and node.state == libcloud.compute.types.NodeState.RUNNING: - gce.ex_stop_node(node) - changed = True - - return (changed, state_instance_names) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - image=dict(default='debian-8'), - image_family=dict(), - external_projects=dict(type='list'), - instance_names=dict(), - machine_type=dict(default='n1-standard-1'), - metadata=dict(), - name=dict(aliases=['base_name']), - num_instances=dict(type='int'), - network=dict(default='default'), - subnetwork=dict(), - persistent_boot_disk=dict(type='bool', default=False), - disks=dict(type='list'), - state=dict(choices=['active', 'present', 'absent', 'deleted', - 'started', 'stopped', 'terminated'], - default='present'), - tags=dict(type='list'), - zone=dict(default='us-central1-a'), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - pem_file=dict(type='path'), - credentials_file=dict(type='path'), - project_id=dict(), - ip_forward=dict(type='bool', default=False), - external_ip=dict(default='ephemeral'), - disk_auto_delete=dict(type='bool', default=True), - disk_size=dict(type='int', default=10), - preemptible=dict(type='bool', default=None), - ), - mutually_exclusive=[('instance_names', 'name')] - ) - - if not HAS_PYTHON26: - module.fail_json(msg="GCE module requires python's 'ast' module, python v2.6+") - if not HAS_LIBCLOUD: - module.fail_json(msg='libcloud with GCE support (0.17.0+) required for this module') - - gce = gce_connect(module) - - image = module.params.get('image') - image_family = module.params.get('image_family') - external_projects = module.params.get('external_projects') - instance_names = module.params.get('instance_names') - name = module.params.get('name') - number = module.params.get('num_instances') - subnetwork = module.params.get('subnetwork') - state = module.params.get('state') - zone = module.params.get('zone') - preemptible = module.params.get('preemptible') - changed = False - - inames = None - if isinstance(instance_names, list): - inames = instance_names - elif isinstance(instance_names, str): - inames = instance_names.split(',') - if name: - inames = name - if not inames: - module.fail_json(msg='Must specify a "name" or "instance_names"', - changed=False) - if not zone: - module.fail_json(msg='Must specify a "zone"', changed=False) - - lc_zone = get_valid_location(module, gce, zone) - if preemptible is not None and hasattr(libcloud, '__version__') and libcloud.__version__ < '0.20': - module.fail_json(msg="Apache Libcloud 0.20.0+ is required to use 'preemptible' option", - changed=False) - - if subnetwork is not None and not hasattr(gce, 'ex_get_subnetwork'): - module.fail_json(msg="Apache Libcloud 1.0.0+ is required to use 'subnetwork' option", - changed=False) - - json_output = {'zone': zone} - if state in ['absent', 'deleted', 'started', 'stopped', 'terminated']: - json_output['state'] = state - (changed, state_instance_names) = change_instance_state( - module, gce, inames, number, lc_zone, state) - - # based on what user specified, return the same variable, although - # value could be different if an instance could not be destroyed - if instance_names or name and number: - json_output['instance_names'] = state_instance_names - elif name: - json_output['name'] = name - - elif state in ['active', 'present']: - json_output['state'] = 'present' - (changed, instance_data, instance_name_list) = create_instances( - module, gce, inames, number, lc_zone) - json_output['instance_data'] = instance_data - if instance_names: - json_output['instance_names'] = instance_name_list - elif name: - json_output['name'] = name - - json_output['changed'] = changed - module.exit_json(**json_output) - - -class LazyDiskImage: - """ - Object for lazy instantiation of disk image - gce.ex_get_image is a very expensive call, so we want to avoid calling it as much as possible. - """ - - def __init__(self, module, gce, name, has_pd, family=None, projects=None): - self.image = None - self.was_called = False - self.gce = gce - self.name = name - self.has_pd = has_pd - self.module = module - self.family = family - self.projects = projects - - def __call__(self): - if not self.was_called: - self.was_called = True - if not self.has_pd: - if self.family: - self.image = self.gce.ex_get_image_from_family(self.family, ex_project_list=self.projects) - else: - self.image = self.gce.ex_get_image(self.name, ex_project_list=self.projects) - if not self.image: - self.module.fail_json(msg='image or disks missing for create instance', changed=False) - return self.image - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/_gcp_backend_service.py b/lib/ansible/modules/cloud/google/_gcp_backend_service.py deleted file mode 100644 index 809395a104..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_backend_service.py +++ /dev/null @@ -1,404 +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 - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'community'} -DOCUMENTATION = ''' -module: gcp_backend_service -version_added: "2.4" -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.12" - why: Updated modules released with increased functionality - alternative: Use M(gcp_compute_backend_service) instead. -author: - - "Tom Melendez (@supertom) <tom@supertom.com>" -options: - backend_service_name: - description: - - Name of the Backend Service. - required: true - backends: - 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: - 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: - description: - - Name of the port on the managed instance group (MIG) that backend - services can forward data to. Required for external load balancing. - protocol: - description: - - The protocol this Backend Service uses to communicate with backends. - Possible values are HTTP, HTTPS, TCP, and SSL. The default is HTTP. - required: false - timeout: - 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: - description: - - Service account email - credentials_file: - description: - - Path to the JSON file associated with the service account email. - project_id: - description: - - GCE project ID. - state: - description: - - Desired state of the resource - default: "present" - choices: ["absent", "present"] -''' - -EXAMPLES = ''' -- name: Create Minimum Backend Service - 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 - 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.module_utils.gce import gce_connect -from ansible.module_utils.gcp import check_params - - -def _validate_params(params): - """ - Validate backend_service params. - - This function calls _validate_backend_params to verify - the backend-specific parameters. - - :param params: Ansible dictionary containing configuration. - :type params: ``dict`` - - :return: True or raises ValueError - :rtype: ``bool`` or `class:ValueError` - """ - fields = [ - {'name': 'timeout', 'type': int, 'min': 1, 'max': 86400}, - ] - try: - check_params(params, fields) - _validate_backend_params(params['backends']) - except Exception: - raise - - return (True, '') - - -def _validate_backend_params(backends): - """ - Validate configuration for backends. - - :param backends: Ansible dictionary containing backends configuration (only). - :type backends: ``dict`` - - :return: True or raises ValueError - :rtype: ``bool`` or `class:ValueError` - """ - fields = [ - {'name': 'balancing_mode', 'type': str, 'values': ['UTILIZATION', 'RATE', 'CONNECTION']}, - {'name': 'max_utilization', 'type': float}, - {'name': 'max_connections', 'type': int}, - {'name': 'max_rate', 'type': int}, - {'name': 'max_rate_per_instance', 'type': float}, - ] - - if not backends: - raise ValueError('backends should be a list.') - - for backend in backends: - try: - check_params(backend, fields) - except Exception: - raise - - if 'max_rate' in backend and 'max_rate_per_instance' in backend: - raise ValueError('Both maxRate or maxRatePerInstance cannot be set.') - - return (True, '') - - -def get_backend_service(gce, name): - """ - Get a Backend Service from GCE. - - :param gce: An initialized GCE driver object. - :type gce: :class: `GCENodeDriver` - - :param name: Name of the Backend Service. - :type name: ``str`` - - :return: A GCEBackendService object or None. - :rtype: :class: `GCEBackendService` or None - """ - try: - # Does the Backend Service already exist? - return gce.ex_get_backendservice(name=name) - - except ResourceNotFoundError: - return None - - -def get_healthcheck(gce, name): - return gce.ex_get_healthcheck(name) - - -def get_instancegroup(gce, name, zone=None): - return gce.ex_get_instancegroup(name=name, zone=zone) - - -def create_backend_service(gce, params): - """ - Create a new Backend Service. - - :param gce: An initialized GCE driver object. - :type gce: :class: `GCENodeDriver` - - :param params: Dictionary of parameters needed by the module. - :type params: ``dict`` - - :return: Tuple with changed stats - :rtype: tuple in the format of (bool, bool) - """ - from copy import deepcopy - - changed = False - return_data = False - # only one healthcheck is currently supported - hc_name = params['healthchecks'][0] - hc = get_healthcheck(gce, hc_name) - backends = [] - for backend in params['backends']: - ig = get_instancegroup(gce, backend['instance_group'], - backend.get('zone', None)) - kwargs = deepcopy(backend) - kwargs['instance_group'] = ig - backends.append(gce.ex_create_backend( - **kwargs)) - - bes = gce.ex_create_backendservice( - name=params['backend_service_name'], healthchecks=[hc], backends=backends, - enable_cdn=params['enable_cdn'], port_name=params['port_name'], - timeout_sec=params['timeout'], protocol=params['protocol']) - - if bes: - changed = True - return_data = True - - return (changed, return_data) - - -def delete_backend_service(bes): - """ - Delete a Backend Service. The Instance Groups are NOT destroyed. - """ - changed = False - return_data = False - if bes.destroy(): - changed = True - return_data = True - return (changed, return_data) - - -def main(): - module = AnsibleModule(argument_spec=dict( - backends=dict(type='list', required=True), - backend_service_name=dict(required=True), - healthchecks=dict(type='list', required=True), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - enable_cdn=dict(type='bool'), - port_name=dict(type='str'), - protocol=dict(type='str', default='TCP', - choices=['HTTP', 'HTTPS', 'SSL', 'TCP']), - timeout=dict(type='int'), - state=dict(choices=['absent', 'present'], default='present'), - pem_file=dict(), - credentials_file=dict(), - project_id=dict(), ), ) - - if not HAS_PYTHON26: - module.fail_json( - msg="GCE module requires python's 'ast' module, python v2.6+") - if not HAS_LIBCLOUD: - module.fail_json( - msg='libcloud with GCE Backend Service support (1.3+) required for this module.') - - gce = gce_connect(module) - if not hasattr(gce, 'ex_create_instancegroupmanager'): - module.fail_json( - msg='libcloud with GCE Backend Service support (1.3+) required for this module.', - changed=False) - - params = {} - params['state'] = module.params.get('state') - params['backend_service_name'] = module.params.get('backend_service_name') - params['backends'] = module.params.get('backends') - params['healthchecks'] = module.params.get('healthchecks') - params['enable_cdn'] = module.params.get('enable_cdn', None) - params['port_name'] = module.params.get('port_name', None) - params['protocol'] = module.params.get('protocol', None) - params['timeout'] = module.params.get('timeout', None) - - try: - _validate_params(params) - except Exception as e: - module.fail_json(msg=e.message, changed=False) - - changed = False - json_output = {'state': params['state']} - bes = get_backend_service(gce, params['backend_service_name']) - - if not bes: - if params['state'] == 'absent': - # Doesn't exist and state==absent. - changed = False - module.fail_json( - msg="Cannot delete unknown backend service: %s" % - (params['backend_service_name'])) - else: - # Create - (changed, json_output['backend_service_created']) = create_backend_service(gce, - params) - elif params['state'] == 'absent': - # Delete - (changed, json_output['backend_service_deleted']) = delete_backend_service(bes) - else: - # TODO(supertom): Add update support when it is available in libcloud. - changed = False - - json_output['changed'] = changed - json_output.update(params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/_gcp_bigquery_dataset_facts.py b/lib/ansible/modules/cloud/google/_gcp_bigquery_dataset_facts.py deleted file mode 120000 index 048b511508..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_bigquery_dataset_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_bigquery_dataset_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_bigquery_table_facts.py b/lib/ansible/modules/cloud/google/_gcp_bigquery_table_facts.py deleted file mode 120000 index 289c79bcfe..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_bigquery_table_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_bigquery_table_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_cloudbuild_trigger_facts.py b/lib/ansible/modules/cloud/google/_gcp_cloudbuild_trigger_facts.py deleted file mode 120000 index 66c2c93f2c..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_cloudbuild_trigger_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_cloudbuild_trigger_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_address_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_address_facts.py deleted file mode 120000 index d12b7e4d49..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_address_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_address_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_backend_bucket_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_backend_bucket_facts.py deleted file mode 120000 index d80cf8b0ae..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_backend_bucket_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_backend_bucket_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_backend_service_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_backend_service_facts.py deleted file mode 120000 index def0ed0e6f..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_backend_service_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_backend_service_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_disk_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_disk_facts.py deleted file mode 120000 index 52aabea81a..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_disk_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_disk_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_firewall_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_firewall_facts.py deleted file mode 120000 index 7a8ccaa415..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_firewall_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_firewall_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_forwarding_rule_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_forwarding_rule_facts.py deleted file mode 120000 index 4f09197451..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_forwarding_rule_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_forwarding_rule_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_global_address_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_global_address_facts.py deleted file mode 120000 index 497372674a..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_global_address_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_global_address_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_global_forwarding_rule_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_global_forwarding_rule_facts.py deleted file mode 120000 index 18d0b3b5db..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_global_forwarding_rule_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_global_forwarding_rule_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_health_check_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_health_check_facts.py deleted file mode 120000 index a2646a6c8d..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_health_check_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_health_check_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_http_health_check_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_http_health_check_facts.py deleted file mode 120000 index dbf679c115..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_http_health_check_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_http_health_check_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_https_health_check_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_https_health_check_facts.py deleted file mode 120000 index 887a5ffe68..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_https_health_check_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_https_health_check_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_image_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_image_facts.py deleted file mode 120000 index f4ca4647ec..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_image_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_image_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_instance_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_instance_facts.py deleted file mode 120000 index b886b91c69..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_instance_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_instance_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_instance_group_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_instance_group_facts.py deleted file mode 120000 index 8703aff9e8..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_instance_group_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_instance_group_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_instance_group_manager_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_instance_group_manager_facts.py deleted file mode 120000 index 2b9ec76192..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_instance_group_manager_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_instance_group_manager_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_instance_template_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_instance_template_facts.py deleted file mode 120000 index a9826ba8f1..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_instance_template_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_instance_template_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_interconnect_attachment_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_interconnect_attachment_facts.py deleted file mode 120000 index 479308d6e7..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_interconnect_attachment_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_interconnect_attachment_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_network_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_network_facts.py deleted file mode 120000 index c2e964a21d..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_network_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_network_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_region_disk_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_region_disk_facts.py deleted file mode 120000 index 1dbc112907..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_region_disk_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_region_disk_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_route_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_route_facts.py deleted file mode 120000 index 33dccb6671..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_route_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_route_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_router_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_router_facts.py deleted file mode 120000 index 00498a1b87..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_router_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_router_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_ssl_certificate_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_ssl_certificate_facts.py deleted file mode 120000 index 188f2878a9..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_ssl_certificate_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_ssl_certificate_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_ssl_policy_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_ssl_policy_facts.py deleted file mode 120000 index 2e64eb7d47..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_ssl_policy_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_ssl_policy_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_subnetwork_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_subnetwork_facts.py deleted file mode 120000 index dc4a73ad54..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_subnetwork_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_subnetwork_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_target_http_proxy_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_target_http_proxy_facts.py deleted file mode 120000 index 628457e100..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_target_http_proxy_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_target_http_proxy_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_target_https_proxy_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_target_https_proxy_facts.py deleted file mode 120000 index 9b6beebab5..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_target_https_proxy_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_target_https_proxy_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_target_pool_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_target_pool_facts.py deleted file mode 120000 index e3583a7239..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_target_pool_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_target_pool_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_target_ssl_proxy_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_target_ssl_proxy_facts.py deleted file mode 120000 index 6f82c12ab5..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_target_ssl_proxy_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_target_ssl_proxy_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_target_tcp_proxy_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_target_tcp_proxy_facts.py deleted file mode 120000 index 551871b74e..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_target_tcp_proxy_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_target_tcp_proxy_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_target_vpn_gateway_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_target_vpn_gateway_facts.py deleted file mode 120000 index 72c072765a..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_target_vpn_gateway_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_target_vpn_gateway_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_url_map_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_url_map_facts.py deleted file mode 120000 index e8046ebc54..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_url_map_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_url_map_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_compute_vpn_tunnel_facts.py b/lib/ansible/modules/cloud/google/_gcp_compute_vpn_tunnel_facts.py deleted file mode 120000 index 26de5596b8..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_compute_vpn_tunnel_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_compute_vpn_tunnel_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_container_cluster_facts.py b/lib/ansible/modules/cloud/google/_gcp_container_cluster_facts.py deleted file mode 120000 index 50b4ee8253..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_container_cluster_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_container_cluster_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_container_node_pool_facts.py b/lib/ansible/modules/cloud/google/_gcp_container_node_pool_facts.py deleted file mode 120000 index 2b73f3c477..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_container_node_pool_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_container_node_pool_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_dns_managed_zone_facts.py b/lib/ansible/modules/cloud/google/_gcp_dns_managed_zone_facts.py deleted file mode 120000 index 08fc673ed2..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_dns_managed_zone_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_dns_managed_zone_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_dns_resource_record_set_facts.py b/lib/ansible/modules/cloud/google/_gcp_dns_resource_record_set_facts.py deleted file mode 120000 index 879f4b6344..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_dns_resource_record_set_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_dns_resource_record_set_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_forwarding_rule.py b/lib/ansible/modules/cloud/google/_gcp_forwarding_rule.py deleted file mode 100644 index a9c835578a..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_forwarding_rule.py +++ /dev/null @@ -1,355 +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 - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gcp_forwarding_rule -version_added: "2.4" -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.12" - why: Updated modules released with increased functionality - alternative: Use M(gcp_compute_forwarding_rule) or M(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: - 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: - description: - - Name of the Forwarding_Rule. - required: true - port_range: - description: - - For global forwarding rules, must be set to 80 or 8080 for TargetHttpProxy, and - 443 for TargetHttpsProxy or TargetSslProxy. - required: false - protocol: - description: - - For global forwarding rules, TCP, UDP, ESP, AH, SCTP or ICMP. Default is TCP. - required: false - region: - description: - - The region for this forwarding rule. Currently, only 'global' is supported. - required: false - state: - description: - - The state of the Forwarding Rule. 'present' or 'absent' - required: true - choices: ["present", "absent"] - target: - 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 -''' - -EXAMPLES = ''' -- name: Create Minimum GLOBAL Forwarding_Rule - 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 - 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.module_utils.gcp import get_google_api_client, GCPUtils - - -USER_AGENT_PRODUCT = 'ansible-forwarding_rule' -USER_AGENT_VERSION = '0.0.1' - - -def _build_global_forwarding_rule_dict(params, project_id=None): - """ - Reformat services in Ansible Params. - - :param params: Params from AnsibleModule object - :type params: ``dict`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: dictionary suitable for submission to GCP API. - :rtype ``dict`` - """ - url = '' - if project_id: - url = GCPUtils.build_googleapi_url(project_id) - gcp_dict = GCPUtils.params_to_gcp_dict(params, 'forwarding_rule_name') - if 'target' in gcp_dict: - gcp_dict['target'] = '%s/global/targetHttpProxies/%s' % (url, - gcp_dict['target']) - if 'address' in gcp_dict: - gcp_dict['IPAddress'] = '%s/global/addresses/%s' % (url, - gcp_dict['address']) - del gcp_dict['address'] - if 'protocol' in gcp_dict: - gcp_dict['IPProtocol'] = gcp_dict['protocol'] - del gcp_dict['protocol'] - return gcp_dict - - -def get_global_forwarding_rule(client, name, project_id=None): - """ - Get a Global Forwarding Rule from GCP. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Global Forwarding Rule. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: A dict resp from the respective GCP 'get' request. - :rtype: ``dict`` - """ - try: - req = client.globalForwardingRules().get( - project=project_id, forwardingRule=name) - return GCPUtils.execute_api_client_req(req, raise_404=False) - except Exception: - raise - - -def create_global_forwarding_rule(client, params, project_id): - """ - Create a new Global Forwarding Rule. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_global_forwarding_rule_dict(params, project_id) - try: - req = client.globalForwardingRules().insert(project=project_id, body=gcp_dict) - return_data = GCPUtils.execute_api_client_req(req, client, raw=False) - if not return_data: - return_data = get_global_forwarding_rule(client, - name=params['forwarding_rule_name'], - project_id=project_id) - return (True, return_data) - except Exception: - raise - - -def delete_global_forwarding_rule(client, name, project_id): - """ - Delete a Global Forwarding Rule. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Target Proxy. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - try: - req = client.globalForwardingRules().delete( - project=project_id, forwardingRule=name) - return_data = GCPUtils.execute_api_client_req(req, client) - return (True, return_data) - except Exception: - raise - - -def update_global_forwarding_rule(client, forwarding_rule, params, name, project_id): - """ - Update a Global Forwarding_Rule. Currently, only a target can be updated. - - If the forwarding_rule has not changed, the update will not occur. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param forwarding_rule: Name of the Target Proxy. - :type forwarding_rule: ``dict`` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :param name: Name of the Global Forwarding Rule. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_global_forwarding_rule_dict(params, project_id) - - GCPUtils.are_params_equal(forwarding_rule, gcp_dict) - if forwarding_rule['target'] == gcp_dict['target']: - return (False, 'no update necessary') - - try: - req = client.globalForwardingRules().setTarget(project=project_id, - forwardingRule=name, - body={'target': gcp_dict['target']}) - return_data = GCPUtils.execute_api_client_req( - req, client=client, raw=False) - return (True, return_data) - except Exception: - raise - - -def main(): - module = AnsibleModule(argument_spec=dict( - forwarding_rule_name=dict(required=True), - region=dict(required=True), - target=dict(required=False), - address=dict(type='str', required=False), - protocol=dict(required=False, default='TCP', choices=['TCP']), - port_range=dict(required=False), - load_balancing_scheme=dict( - required=False, default='EXTERNAL', choices=['EXTERNAL']), - state=dict(required=True, choices=['absent', 'present']), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - pem_file=dict(), - credentials_file=dict(), - project_id=dict(), ), ) - - client, conn_params = get_google_api_client(module, 'compute', user_agent_product=USER_AGENT_PRODUCT, - user_agent_version=USER_AGENT_VERSION) - - params = {} - params['state'] = module.params.get('state') - params['forwarding_rule_name'] = module.params.get('forwarding_rule_name') - params['region'] = module.params.get('region') - params['target'] = module.params.get('target', None) - params['protocol'] = module.params.get('protocol', None) - params['port_range'] = module.params.get('port_range') - if module.params.get('address', None): - params['address'] = module.params.get('address', None) - - if params['region'] != 'global': - # This module currently doesn't support regional rules. - module.fail_json( - msg=("%s - Only global forwarding rules currently supported. " - "Be sure to specify 'global' for the region option.") % - (params['forwarding_rule_name'])) - - changed = False - json_output = {'state': params['state']} - forwarding_rule = None - if params['region'] == 'global': - forwarding_rule = get_global_forwarding_rule(client, - name=params['forwarding_rule_name'], - project_id=conn_params['project_id']) - if not forwarding_rule: - if params['state'] == 'absent': - # Doesn't exist in GCE, and state==absent. - changed = False - module.fail_json( - msg="Cannot delete unknown forwarding_rule: %s" % - (params['forwarding_rule_name'])) - else: - # Create - changed, json_output['forwarding_rule'] = create_global_forwarding_rule(client, - params=params, - project_id=conn_params['project_id']) - elif params['state'] == 'absent': - # Delete - changed, json_output['forwarding_rule'] = delete_global_forwarding_rule(client, - name=params['forwarding_rule_name'], - project_id=conn_params['project_id']) - else: - changed, json_output['forwarding_rule'] = update_global_forwarding_rule(client, - forwarding_rule=forwarding_rule, - params=params, - name=params['forwarding_rule_name'], - project_id=conn_params['project_id']) - - json_output['changed'] = changed - json_output.update(params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/_gcp_healthcheck.py b/lib/ansible/modules/cloud/google/_gcp_healthcheck.py deleted file mode 100644 index 805e12db1b..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_healthcheck.py +++ /dev/null @@ -1,451 +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 - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gcp_healthcheck -version_added: "2.4" -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.12" - why: Updated modules released with increased functionality - alternative: > - Use M(gcp_compute_health_check), M(gcp_compute_http_health_check) or - M(gcp_compute_https_health_check) instead. -author: - - "Tom Melendez (@supertom) <tom@supertom.com>" -options: - check_interval: - description: - - How often (in seconds) to send a health check. - default: 5 - healthcheck_name: - description: - - Name of the Healthcheck. - required: true - healthcheck_type: - description: - - Type of Healthcheck. - required: true - choices: ["HTTP", "HTTPS"] - host_header: - 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. - required: true - default: "" - port: - description: - - The TCP port number for the health check request. The default value is - 443 for HTTPS and 80 for HTTP. - request_path: - description: - - The request path of the HTTPS health check request. - required: false - default: "/" - state: - description: State of the Healthcheck. - required: true - choices: ["present", "absent"] - timeout: - 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: - description: - - A so-far healthy instance will be marked unhealthy after this - many consecutive failures. - default: 2 - healthy_threshold: - description: - - A so-far unhealthy instance will be marked healthy after this - many consecutive successes. - default: 2 - service_account_email: - description: - - service account email - service_account_permissions: - version_added: "2.0" - description: - - service account permissions (see - U(https://cloud.google.com/sdk/gcloud/reference/compute/instances/create), - --scopes section for detailed information) - choices: [ - "bigquery", "cloud-platform", "compute-ro", "compute-rw", - "useraccounts-ro", "useraccounts-rw", "datastore", "logging-write", - "monitoring", "sql-admin", "storage-full", "storage-ro", - "storage-rw", "taskqueue", "userinfo-email" - ] - credentials_file: - description: - - Path to the JSON file associated with the service account email - project_id: - description: - - Your GCP project ID -''' - -EXAMPLES = ''' -- name: Create Minimum HealthCheck - 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 - 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 - 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.module_utils.gcp import get_google_api_client, GCPUtils - - -USER_AGENT_PRODUCT = 'ansible-healthcheck' -USER_AGENT_VERSION = '0.0.1' - - -def _validate_healthcheck_params(params): - """ - Validate healthcheck params. - - Simple validation has already assumed by AnsibleModule. - - :param params: Ansible dictionary containing configuration. - :type params: ``dict`` - - :return: True or raises ValueError - :rtype: ``bool`` or `class:ValueError` - """ - if params['timeout'] > params['check_interval']: - raise ValueError("timeout (%s) is greater than check_interval (%s)" % ( - params['timeout'], params['check_interval'])) - - return (True, '') - - -def _build_healthcheck_dict(params): - """ - Reformat services in Ansible Params for GCP. - - :param params: Params from AnsibleModule object - :type params: ``dict`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: dictionary suitable for submission to GCP - HealthCheck (HTTP/HTTPS) API. - :rtype ``dict`` - """ - gcp_dict = GCPUtils.params_to_gcp_dict(params, 'healthcheck_name') - if 'timeout' in gcp_dict: - gcp_dict['timeoutSec'] = gcp_dict['timeout'] - del gcp_dict['timeout'] - - if 'checkInterval' in gcp_dict: - gcp_dict['checkIntervalSec'] = gcp_dict['checkInterval'] - del gcp_dict['checkInterval'] - - if 'hostHeader' in gcp_dict: - gcp_dict['host'] = gcp_dict['hostHeader'] - del gcp_dict['hostHeader'] - - if 'healthcheckType' in gcp_dict: - del gcp_dict['healthcheckType'] - return gcp_dict - - -def _get_req_resource(client, resource_type): - if resource_type == 'HTTPS': - return (client.httpsHealthChecks(), 'httpsHealthCheck') - else: - return (client.httpHealthChecks(), 'httpHealthCheck') - - -def get_healthcheck(client, name, project_id=None, resource_type='HTTP'): - """ - Get a Healthcheck from GCP. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Url Map. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: A dict resp from the respective GCP 'get' request. - :rtype: ``dict`` - """ - try: - resource, entity_name = _get_req_resource(client, resource_type) - args = {'project': project_id, entity_name: name} - req = resource.get(**args) - return GCPUtils.execute_api_client_req(req, raise_404=False) - except Exception: - raise - - -def create_healthcheck(client, params, project_id, resource_type='HTTP'): - """ - Create a new Healthcheck. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_healthcheck_dict(params) - try: - resource, _ = _get_req_resource(client, resource_type) - args = {'project': project_id, 'body': gcp_dict} - req = resource.insert(**args) - return_data = GCPUtils.execute_api_client_req(req, client, raw=False) - if not return_data: - return_data = get_healthcheck(client, - name=params['healthcheck_name'], - project_id=project_id) - return (True, return_data) - except Exception: - raise - - -def delete_healthcheck(client, name, project_id, resource_type='HTTP'): - """ - Delete a Healthcheck. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Url Map. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - try: - resource, entity_name = _get_req_resource(client, resource_type) - args = {'project': project_id, entity_name: name} - req = resource.delete(**args) - return_data = GCPUtils.execute_api_client_req(req, client) - return (True, return_data) - except Exception: - raise - - -def update_healthcheck(client, healthcheck, params, name, project_id, - resource_type='HTTP'): - """ - Update a Healthcheck. - - If the healthcheck has not changed, the update will not occur. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param healthcheck: Name of the Url Map. - :type healthcheck: ``dict`` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :param name: Name of the Url Map. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_healthcheck_dict(params) - ans = GCPUtils.are_params_equal(healthcheck, gcp_dict) - if ans: - return (False, 'no update necessary') - - try: - resource, entity_name = _get_req_resource(client, resource_type) - args = {'project': project_id, entity_name: name, 'body': gcp_dict} - req = resource.update(**args) - return_data = GCPUtils.execute_api_client_req( - req, client=client, raw=False) - return (True, return_data) - except Exception: - raise - - -def main(): - module = AnsibleModule(argument_spec=dict( - healthcheck_name=dict(required=True), - healthcheck_type=dict(required=True, - choices=['HTTP', 'HTTPS']), - request_path=dict(required=False, default='/'), - check_interval=dict(required=False, type='int', default=5), - healthy_threshold=dict(required=False, type='int', default=2), - unhealthy_threshold=dict(required=False, type='int', default=2), - host_header=dict(required=False, type='str', default=''), - timeout=dict(required=False, type='int', default=5), - port=dict(required=False, type='int'), - state=dict(choices=['absent', 'present'], default='present'), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - credentials_file=dict(), - project_id=dict(), ), ) - - client, conn_params = get_google_api_client(module, 'compute', user_agent_product=USER_AGENT_PRODUCT, - user_agent_version=USER_AGENT_VERSION) - - params = {} - - params['healthcheck_name'] = module.params.get('healthcheck_name') - params['healthcheck_type'] = module.params.get('healthcheck_type') - params['request_path'] = module.params.get('request_path') - params['check_interval'] = module.params.get('check_interval') - params['healthy_threshold'] = module.params.get('healthy_threshold') - params['unhealthy_threshold'] = module.params.get('unhealthy_threshold') - params['host_header'] = module.params.get('host_header') - params['timeout'] = module.params.get('timeout') - params['port'] = module.params.get('port', None) - params['state'] = module.params.get('state') - - if not params['port']: - params['port'] = 80 - if params['healthcheck_type'] == 'HTTPS': - params['port'] = 443 - try: - _validate_healthcheck_params(params) - except Exception as e: - module.fail_json(msg=e.message, changed=False) - - changed = False - json_output = {'state': params['state']} - healthcheck = get_healthcheck(client, - name=params['healthcheck_name'], - project_id=conn_params['project_id'], - resource_type=params['healthcheck_type']) - - if not healthcheck: - if params['state'] == 'absent': - # Doesn't exist in GCE, and state==absent. - changed = False - module.fail_json( - msg="Cannot delete unknown healthcheck: %s" % - (params['healthcheck_name'])) - else: - # Create - changed, json_output['healthcheck'] = create_healthcheck(client, - params=params, - project_id=conn_params['project_id'], - resource_type=params['healthcheck_type']) - elif params['state'] == 'absent': - # Delete - changed, json_output['healthcheck'] = delete_healthcheck(client, - name=params['healthcheck_name'], - project_id=conn_params['project_id'], - resource_type=params['healthcheck_type']) - else: - changed, json_output['healthcheck'] = update_healthcheck(client, - healthcheck=healthcheck, - params=params, - name=params['healthcheck_name'], - project_id=conn_params['project_id'], - resource_type=params['healthcheck_type']) - json_output['changed'] = changed - json_output.update(params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/_gcp_iam_role_facts.py b/lib/ansible/modules/cloud/google/_gcp_iam_role_facts.py deleted file mode 120000 index f46a8e3ec7..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_iam_role_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_iam_role_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_iam_service_account_facts.py b/lib/ansible/modules/cloud/google/_gcp_iam_service_account_facts.py deleted file mode 120000 index 3f03024049..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_iam_service_account_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_iam_service_account_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_pubsub_subscription_facts.py b/lib/ansible/modules/cloud/google/_gcp_pubsub_subscription_facts.py deleted file mode 120000 index 3196ae083b..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_pubsub_subscription_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_pubsub_subscription_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_pubsub_topic_facts.py b/lib/ansible/modules/cloud/google/_gcp_pubsub_topic_facts.py deleted file mode 120000 index 388ebfc1d1..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_pubsub_topic_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_pubsub_topic_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_redis_instance_facts.py b/lib/ansible/modules/cloud/google/_gcp_redis_instance_facts.py deleted file mode 120000 index e6d6fa6db2..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_redis_instance_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_redis_instance_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_resourcemanager_project_facts.py b/lib/ansible/modules/cloud/google/_gcp_resourcemanager_project_facts.py deleted file mode 120000 index 5766332a37..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_resourcemanager_project_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_resourcemanager_project_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_sourcerepo_repository_facts.py b/lib/ansible/modules/cloud/google/_gcp_sourcerepo_repository_facts.py deleted file mode 120000 index b6dc6a8d75..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_sourcerepo_repository_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_sourcerepo_repository_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_spanner_database_facts.py b/lib/ansible/modules/cloud/google/_gcp_spanner_database_facts.py deleted file mode 120000 index abadc34b30..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_spanner_database_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_spanner_database_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_spanner_instance_facts.py b/lib/ansible/modules/cloud/google/_gcp_spanner_instance_facts.py deleted file mode 120000 index 1c1e47220a..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_spanner_instance_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_spanner_instance_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_sql_database_facts.py b/lib/ansible/modules/cloud/google/_gcp_sql_database_facts.py deleted file mode 120000 index de080a71eb..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_sql_database_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_sql_database_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_sql_instance_facts.py b/lib/ansible/modules/cloud/google/_gcp_sql_instance_facts.py deleted file mode 120000 index c6c2c5f386..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_sql_instance_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_sql_instance_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_sql_user_facts.py b/lib/ansible/modules/cloud/google/_gcp_sql_user_facts.py deleted file mode 120000 index 44488004a4..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_sql_user_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_sql_user_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_target_proxy.py b/lib/ansible/modules/cloud/google/_gcp_target_proxy.py deleted file mode 100644 index 57078a8fe1..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_target_proxy.py +++ /dev/null @@ -1,296 +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 - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gcp_target_proxy -version_added: "2.4" -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.12" - why: Updated modules released with increased functionality - alternative: Use M(gcp_compute_target_http_proxy) instead. -notes: - - Currently only supports global HTTP proxy. -author: - - "Tom Melendez (@supertom) <tom@supertom.com>" -options: - target_proxy_name: - description: - - Name of the Target_Proxy. - required: true - target_proxy_type: - description: - - Type of Target_Proxy. HTTP, HTTPS or SSL. Only HTTP is currently supported. - required: true - url_map_name: - description: - - Name of the Url Map. Required if type is HTTP or HTTPS proxy. - required: false -''' - -EXAMPLES = ''' -- name: Create Minimum HTTP Target_Proxy - 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.module_utils.gcp import get_google_api_client, GCPUtils - - -USER_AGENT_PRODUCT = 'ansible-target_proxy' -USER_AGENT_VERSION = '0.0.1' - - -def _build_target_proxy_dict(params, project_id=None): - """ - Reformat services in Ansible Params. - - :param params: Params from AnsibleModule object - :type params: ``dict`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: dictionary suitable for submission to GCP UrlMap API. - :rtype ``dict`` - """ - url = '' - if project_id: - url = GCPUtils.build_googleapi_url(project_id) - gcp_dict = GCPUtils.params_to_gcp_dict(params, 'target_proxy_name') - if 'urlMap' in gcp_dict: - gcp_dict['urlMap'] = '%s/global/urlMaps/%s' % (url, - gcp_dict['urlMap']) - return gcp_dict - - -def get_target_http_proxy(client, name, project_id=None): - """ - Get a Target HTTP Proxy from GCP. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Target Proxy. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: A dict resp from the respective GCP 'get' request. - :rtype: ``dict`` - """ - req = client.targetHttpProxies().get(project=project_id, - targetHttpProxy=name) - return GCPUtils.execute_api_client_req(req, raise_404=False) - - -def create_target_http_proxy(client, params, project_id): - """ - Create a new Target_Proxy. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_target_proxy_dict(params, project_id) - try: - req = client.targetHttpProxies().insert(project=project_id, - body=gcp_dict) - return_data = GCPUtils.execute_api_client_req(req, client, raw=False) - if not return_data: - return_data = get_target_http_proxy(client, - name=params['target_proxy_name'], - project_id=project_id) - return (True, return_data) - except Exception: - raise - - -def delete_target_http_proxy(client, name, project_id): - """ - Delete a Target_Proxy. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Target Proxy. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - try: - req = client.targetHttpProxies().delete( - project=project_id, targetHttpProxy=name) - return_data = GCPUtils.execute_api_client_req(req, client) - return (True, return_data) - except Exception: - raise - - -def update_target_http_proxy(client, target_proxy, params, name, project_id): - """ - Update a HTTP Target_Proxy. Currently only the Url Map can be updated. - - If the target_proxy has not changed, the update will not occur. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param target_proxy: Name of the Target Proxy. - :type target_proxy: ``dict`` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :param name: Name of the Target Proxy. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_target_proxy_dict(params, project_id) - - GCPUtils.are_params_equal(target_proxy, gcp_dict) - if target_proxy['urlMap'] == gcp_dict['urlMap']: - return (False, 'no update necessary') - - try: - req = client.targetHttpProxies().setUrlMap(project=project_id, - targetHttpProxy=name, - body={"urlMap": gcp_dict['urlMap']}) - return_data = GCPUtils.execute_api_client_req( - req, client=client, raw=False) - return (True, return_data) - except Exception: - raise - - -def main(): - module = AnsibleModule(argument_spec=dict( - target_proxy_name=dict(required=True), - target_proxy_type=dict(required=True, choices=['HTTP']), - url_map_name=dict(required=False), - state=dict(required=True, choices=['absent', 'present']), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - pem_file=dict(), - credentials_file=dict(), - project_id=dict(), ), ) - - client, conn_params = get_google_api_client(module, 'compute', user_agent_product=USER_AGENT_PRODUCT, - user_agent_version=USER_AGENT_VERSION) - - params = {} - params['state'] = module.params.get('state') - params['target_proxy_name'] = module.params.get('target_proxy_name') - params['target_proxy_type'] = module.params.get('target_proxy_type') - params['url_map'] = module.params.get('url_map_name', None) - - changed = False - json_output = {'state': params['state']} - target_proxy = get_target_http_proxy(client, - name=params['target_proxy_name'], - project_id=conn_params['project_id']) - - if not target_proxy: - if params['state'] == 'absent': - # Doesn't exist in GCE, and state==absent. - changed = False - module.fail_json( - msg="Cannot delete unknown target_proxy: %s" % - (params['target_proxy_name'])) - else: - # Create - changed, json_output['target_proxy'] = create_target_http_proxy(client, - params=params, - project_id=conn_params['project_id']) - elif params['state'] == 'absent': - # Delete - changed, json_output['target_proxy'] = delete_target_http_proxy(client, - name=params['target_proxy_name'], - project_id=conn_params['project_id']) - else: - changed, json_output['target_proxy'] = update_target_http_proxy(client, - target_proxy=target_proxy, - params=params, - name=params['target_proxy_name'], - project_id=conn_params['project_id']) - json_output['updated_target_proxy'] = changed - - json_output['changed'] = changed - json_output.update(params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/_gcp_tpu_node_facts.py b/lib/ansible/modules/cloud/google/_gcp_tpu_node_facts.py deleted file mode 120000 index 5e0ba332eb..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_tpu_node_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcp_tpu_node_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcp_url_map.py b/lib/ansible/modules/cloud/google/_gcp_url_map.py deleted file mode 100644 index 34bfcf583c..0000000000 --- a/lib/ansible/modules/cloud/google/_gcp_url_map.py +++ /dev/null @@ -1,510 +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 - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gcp_url_map -version_added: "2.4" -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.12" - why: Updated modules released with increased functionality - alternative: Use M(gcp_compute_url_map) instead. -options: - url_map_name: - description: - - Name of the Url_Map. - required: true - default_service: - description: - - Default Backend Service if no host rules match. - required: true - host_rules: - 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: - 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 -''' - -EXAMPLES = ''' -- name: Create Minimal Url_Map - 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 - 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.module_utils.gcp import check_params, get_google_api_client, GCPUtils -from ansible.module_utils.six import string_types - - -USER_AGENT_PRODUCT = 'ansible-url_map' -USER_AGENT_VERSION = '0.0.1' - - -def _validate_params(params): - """ - Validate url_map params. - - This function calls _validate_host_rules_params to verify - the host_rules-specific parameters. - - This function calls _validate_path_matchers_params to verify - the path_matchers-specific parameters. - - :param params: Ansible dictionary containing configuration. - :type params: ``dict`` - - :return: True or raises ValueError - :rtype: ``bool`` or `class:ValueError` - """ - fields = [ - {'name': 'default_service', 'type': str, 'required': True}, - {'name': 'host_rules', 'type': list}, - {'name': 'path_matchers', 'type': list}, - ] - try: - check_params(params, fields) - if 'path_matchers' in params and params['path_matchers'] is not None: - _validate_path_matcher_params(params['path_matchers']) - if 'host_rules' in params and params['host_rules'] is not None: - _validate_host_rules_params(params['host_rules']) - except Exception: - raise - - return (True, '') - - -def _validate_path_matcher_params(path_matchers): - """ - Validate configuration for path_matchers. - - :param path_matchers: Ansible dictionary containing path_matchers - configuration (only). - :type path_matchers: ``dict`` - - :return: True or raises ValueError - :rtype: ``bool`` or `class:ValueError` - """ - fields = [ - {'name': 'name', 'type': str, 'required': True}, - {'name': 'default_service', 'type': str, 'required': True}, - {'name': 'path_rules', 'type': list, 'required': True}, - {'name': 'max_rate', 'type': int}, - {'name': 'max_rate_per_instance', 'type': float}, - ] - pr_fields = [ - {'name': 'service', 'type': str, 'required': True}, - {'name': 'paths', 'type': list, 'required': True}, - ] - - if not path_matchers: - raise ValueError(('path_matchers should be a list. %s (%s) provided' - % (path_matchers, type(path_matchers)))) - - for pm in path_matchers: - try: - check_params(pm, fields) - for pr in pm['path_rules']: - check_params(pr, pr_fields) - for path in pr['paths']: - if not path.startswith('/'): - raise ValueError("path for %s must start with /" % ( - pm['name'])) - except Exception: - raise - - return (True, '') - - -def _validate_host_rules_params(host_rules): - """ - Validate configuration for host_rules. - - :param host_rules: Ansible dictionary containing host_rules - configuration (only). - :type host_rules ``dict`` - - :return: True or raises ValueError - :rtype: ``bool`` or `class:ValueError` - """ - fields = [ - {'name': 'path_matcher', 'type': str, 'required': True}, - ] - - if not host_rules: - raise ValueError('host_rules should be a list.') - - for hr in host_rules: - try: - check_params(hr, fields) - for host in hr['hosts']: - if not isinstance(host, string_types): - raise ValueError("host in hostrules must be a string") - elif '*' in host: - if host.index('*') != 0: - raise ValueError("wildcard must be first char in host, %s" % ( - host)) - else: - if host[1] not in ['.', '-', ]: - raise ValueError("wildcard be followed by a '.' or '-', %s" % ( - host)) - - except Exception: - raise - - return (True, '') - - -def _build_path_matchers(path_matcher_list, project_id): - """ - Reformat services in path matchers list. - - Specifically, builds out URLs. - - :param path_matcher_list: The GCP project ID. - :type path_matcher_list: ``list`` of ``dict`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: list suitable for submission to GCP - UrlMap API Path Matchers list. - :rtype ``list`` of ``dict`` - """ - url = '' - if project_id: - url = GCPUtils.build_googleapi_url(project_id) - for pm in path_matcher_list: - if 'defaultService' in pm: - pm['defaultService'] = '%s/global/backendServices/%s' % (url, - pm['defaultService']) - if 'pathRules' in pm: - for rule in pm['pathRules']: - if 'service' in rule: - rule['service'] = '%s/global/backendServices/%s' % (url, - rule['service']) - return path_matcher_list - - -def _build_url_map_dict(params, project_id=None): - """ - Reformat services in Ansible Params. - - :param params: Params from AnsibleModule object - :type params: ``dict`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: dictionary suitable for submission to GCP UrlMap API. - :rtype ``dict`` - """ - url = '' - if project_id: - url = GCPUtils.build_googleapi_url(project_id) - gcp_dict = GCPUtils.params_to_gcp_dict(params, 'url_map_name') - if 'defaultService' in gcp_dict: - gcp_dict['defaultService'] = '%s/global/backendServices/%s' % (url, - gcp_dict['defaultService']) - if 'pathMatchers' in gcp_dict: - gcp_dict['pathMatchers'] = _build_path_matchers(gcp_dict['pathMatchers'], project_id) - - return gcp_dict - - -def get_url_map(client, name, project_id=None): - """ - Get a Url_Map from GCP. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Url Map. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: A dict resp from the respective GCP 'get' request. - :rtype: ``dict`` - """ - try: - req = client.urlMaps().get(project=project_id, urlMap=name) - return GCPUtils.execute_api_client_req(req, raise_404=False) - except Exception: - raise - - -def create_url_map(client, params, project_id): - """ - Create a new Url_Map. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_url_map_dict(params, project_id) - try: - req = client.urlMaps().insert(project=project_id, body=gcp_dict) - return_data = GCPUtils.execute_api_client_req(req, client, raw=False) - if not return_data: - return_data = get_url_map(client, - name=params['url_map_name'], - project_id=project_id) - return (True, return_data) - except Exception: - raise - - -def delete_url_map(client, name, project_id): - """ - Delete a Url_Map. - - :param client: An initialized GCE Compute Discover resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param name: Name of the Url Map. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - try: - req = client.urlMaps().delete(project=project_id, urlMap=name) - return_data = GCPUtils.execute_api_client_req(req, client) - return (True, return_data) - except Exception: - raise - - -def update_url_map(client, url_map, params, name, project_id): - """ - Update a Url_Map. - - If the url_map has not changed, the update will not occur. - - :param client: An initialized GCE Compute Discovery resource. - :type client: :class: `googleapiclient.discovery.Resource` - - :param url_map: Name of the Url Map. - :type url_map: ``dict`` - - :param params: Dictionary of arguments from AnsibleModule. - :type params: ``dict`` - - :param name: Name of the Url Map. - :type name: ``str`` - - :param project_id: The GCP project ID. - :type project_id: ``str`` - - :return: Tuple with changed status and response dict - :rtype: ``tuple`` in the format of (bool, dict) - """ - gcp_dict = _build_url_map_dict(params, project_id) - - ans = GCPUtils.are_params_equal(url_map, gcp_dict) - if ans: - return (False, 'no update necessary') - - gcp_dict['fingerprint'] = url_map['fingerprint'] - try: - req = client.urlMaps().update(project=project_id, - urlMap=name, body=gcp_dict) - return_data = GCPUtils.execute_api_client_req(req, client=client, raw=False) - return (True, return_data) - except Exception: - raise - - -def main(): - module = AnsibleModule(argument_spec=dict( - url_map_name=dict(required=True), - state=dict(choices=['absent', 'present'], default='present'), - default_service=dict(required=True), - path_matchers=dict(type='list', required=False), - host_rules=dict(type='list', required=False), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - pem_file=dict(), - credentials_file=dict(), - project_id=dict(), ), required_together=[ - ['path_matchers', 'host_rules'], ]) - - client, conn_params = get_google_api_client(module, 'compute', user_agent_product=USER_AGENT_PRODUCT, - user_agent_version=USER_AGENT_VERSION) - - params = {} - params['state'] = module.params.get('state') - params['url_map_name'] = module.params.get('url_map_name') - params['default_service'] = module.params.get('default_service') - if module.params.get('path_matchers'): - params['path_matchers'] = module.params.get('path_matchers') - if module.params.get('host_rules'): - params['host_rules'] = module.params.get('host_rules') - - try: - _validate_params(params) - except Exception as e: - module.fail_json(msg=e.message, changed=False) - - changed = False - json_output = {'state': params['state']} - url_map = get_url_map(client, - name=params['url_map_name'], - project_id=conn_params['project_id']) - - if not url_map: - if params['state'] == 'absent': - # Doesn't exist in GCE, and state==absent. - changed = False - module.fail_json( - msg="Cannot delete unknown url_map: %s" % - (params['url_map_name'])) - else: - # Create - changed, json_output['url_map'] = create_url_map(client, - params=params, - project_id=conn_params['project_id']) - elif params['state'] == 'absent': - # Delete - changed, json_output['url_map'] = delete_url_map(client, - name=params['url_map_name'], - project_id=conn_params['project_id']) - else: - changed, json_output['url_map'] = update_url_map(client, - url_map=url_map, - params=params, - name=params['url_map_name'], - project_id=conn_params['project_id']) - json_output['updated_url_map'] = changed - - json_output['changed'] = changed - json_output.update(params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/_gcpubsub_facts.py b/lib/ansible/modules/cloud/google/_gcpubsub_facts.py deleted file mode 120000 index 3feb35c3ee..0000000000 --- a/lib/ansible/modules/cloud/google/_gcpubsub_facts.py +++ /dev/null @@ -1 +0,0 @@ -gcpubsub_info.py
\ No newline at end of file diff --git a/lib/ansible/modules/cloud/google/_gcspanner.py b/lib/ansible/modules/cloud/google/_gcspanner.py deleted file mode 100644 index fdf81cafca..0000000000 --- a/lib/ansible/modules/cloud/google/_gcspanner.py +++ /dev/null @@ -1,291 +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 - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['deprecated'], - 'supported_by': 'community'} - -DOCUMENTATION = ''' ---- -module: gcspanner -version_added: "2.3" -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.12" - why: Updated modules released with increased functionality - alternative: Use M(gcp_spanner_database) and/or M(gcp_spanner_instance) instead. -author: - - Tom Melendez (@supertom) <tom@supertom.com> -options: - configuration: - description: - - Configuration the instance should use. - - Examples are us-central1, asia-east1 and europe-west1. - required: yes - instance_id: - description: - - GCP spanner instance name. - required: yes - database_name: - 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: - description: - - Name of Instance to display. - - If not specified, instance_id will be used instead. - node_count: - description: - - Number of nodes in the instance. - default: 1 - state: - 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 -''' - -EXAMPLES = ''' -- name: Create instance - gcspanner: - instance_id: '{{ instance_id }}' - configuration: '{{ configuration }}' - state: present - node_count: 1 - -- name: Create database - gcspanner: - instance_id: '{{ instance_id }}' - configuration: '{{ configuration }}' - database_name: '{{ database_name }}' - state: present - -- name: Delete instance (and all databases) -- 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.module_utils.gcp import check_min_pkg_version, get_google_cloud_credentials -from ansible.module_utils.six import string_types - - -CLOUD_CLIENT = 'google-cloud-spanner' -CLOUD_CLIENT_MINIMUM_VERSION = '0.23.0' -CLOUD_CLIENT_USER_AGENT = 'ansible-spanner-0.1' - - -def get_spanner_configuration_name(config_name, project_name): - config_name = 'projects/%s/instanceConfigs/regional-%s' % (project_name, - config_name) - return config_name - - -def instance_update(instance): - """ - Call update method on spanner client. - - Note: A ValueError exception is thrown despite the client succeeding. - So, we validate the node_count and instance_display_name parameters and then - ignore the ValueError exception. - - :param instance: a Spanner instance object - :type instance: class `google.cloud.spanner.Instance` - - :returns True on success, raises ValueError on type error. - :rtype ``bool`` - """ - errmsg = '' - if not isinstance(instance.node_count, int): - errmsg = 'node_count must be an integer %s (%s)' % ( - instance.node_count, type(instance.node_count)) - if instance.display_name and not isinstance(instance.display_name, - string_types): - errmsg = 'instance_display_name must be an string %s (%s)' % ( - instance.display_name, type(instance.display_name)) - if errmsg: - raise ValueError(errmsg) - - try: - instance.update() - except ValueError: - # The ValueError here is the one we 'expect'. - pass - - return True - - -def main(): - module = AnsibleModule( - argument_spec=dict( - instance_id=dict(type='str', required=True), - state=dict(type='str', default='present', choices=['absent', 'present']), - database_name=dict(type='str'), - configuration=dict(type='str', required=True), - node_count=dict(type='int', default=1), - instance_display_name=dict(type='str'), - force_instance_delete=dict(type='bool', default=False), - service_account_email=dict(type='str'), - credentials_file=dict(type='str'), - project_id=dict(type='str'), - ), - ) - - if not HAS_PYTHON26: - module.fail_json( - msg="GCE module requires python's 'ast' module, python v2.6+") - - if not HAS_GOOGLE_CLOUD_SPANNER: - module.fail_json(msg="Please install google-cloud-spanner.") - - if not check_min_pkg_version(CLOUD_CLIENT, CLOUD_CLIENT_MINIMUM_VERSION): - module.fail_json(msg="Please install %s client version %s" % - (CLOUD_CLIENT, CLOUD_CLIENT_MINIMUM_VERSION)) - - mod_params = {} - mod_params['state'] = module.params.get('state') - mod_params['instance_id'] = module.params.get('instance_id') - mod_params['database_name'] = module.params.get('database_name') - mod_params['configuration'] = module.params.get('configuration') - mod_params['node_count'] = module.params.get('node_count', None) - mod_params['instance_display_name'] = module.params.get('instance_display_name') - mod_params['force_instance_delete'] = module.params.get('force_instance_delete') - - creds, params = get_google_cloud_credentials(module) - spanner_client = spanner.Client(project=params['project_id'], - credentials=creds, - user_agent=CLOUD_CLIENT_USER_AGENT) - changed = False - json_output = {} - - i = None - if mod_params['instance_id']: - config_name = get_spanner_configuration_name( - mod_params['configuration'], params['project_id']) - i = spanner_client.instance(mod_params['instance_id'], - configuration_name=config_name) - d = None - if mod_params['database_name']: - # TODO(supertom): support DDL - ddl_statements = '' - d = i.database(mod_params['database_name'], ddl_statements) - - if mod_params['state'] == 'absent': - # Remove the most granular resource. If database is specified - # we remove it. If only instance is specified, that is what is removed. - if d is not None and d.exists(): - d.drop() - changed = True - else: - if i.exists(): - if mod_params['force_instance_delete']: - i.delete() - else: - module.fail_json( - msg=(("Cannot delete Spanner instance: " - "'force_instance_delete' argument not specified"))) - changed = True - elif mod_params['state'] == 'present': - if not i.exists(): - i = spanner_client.instance(mod_params['instance_id'], - configuration_name=config_name, - display_name=mod_params['instance_display_name'], - node_count=mod_params['node_count'] or 1) - i.create() - changed = True - else: - # update instance - i.reload() - inst_prev_vals = {} - if i.display_name != mod_params['instance_display_name']: - inst_prev_vals['instance_display_name'] = i.display_name - i.display_name = mod_params['instance_display_name'] - if mod_params['node_count']: - if i.node_count != mod_params['node_count']: - inst_prev_vals['node_count'] = i.node_count - i.node_count = mod_params['node_count'] - if inst_prev_vals: - changed = instance_update(i) - json_output['updated'] = changed - json_output['previous_values'] = {'instance': inst_prev_vals} - if d: - if not d.exists(): - d.create() - d.reload() - changed = True - - json_output['changed'] = changed - json_output.update(mod_params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/gc_storage.py b/lib/ansible/modules/cloud/google/gc_storage.py deleted file mode 100644 index 4fe29eb681..0000000000 --- a/lib/ansible/modules/cloud/google/gc_storage.py +++ /dev/null @@ -1,491 +0,0 @@ -#!/usr/bin/python -# -# Copyright: Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gc_storage -version_added: "1.4" -short_description: This module manages objects/buckets in Google Cloud Storage. -description: - - This module allows users to manage their objects/buckets in Google Cloud Storage. It allows upload and download operations and can set some - canned permissions. It also allows retrieval of URLs for objects for use in playbooks, and retrieval of string contents of objects. This module - requires setting the default project in GCS prior to playbook usage. See U(https://developers.google.com/storage/docs/reference/v1/apiversion1) for - information about setting the default project. - -options: - bucket: - description: - - Bucket name. - required: true - object: - description: - - Keyname of the object inside the bucket. Can be also be used to create "virtual directories" (see examples). - src: - description: - - The source file path when performing a PUT operation. - dest: - description: - - The destination file path when downloading an object/key with a GET operation. - force: - description: - - Forces an overwrite either locally on the filesystem or remotely with the object/key. Used with PUT and GET operations. - type: bool - default: 'yes' - aliases: [ 'overwrite' ] - permission: - description: - - This option let's the user set the canned permissions on the object/bucket that are created. The permissions that can be set are 'private', - 'public-read', 'authenticated-read'. - default: private - headers: - version_added: "2.0" - description: - - Headers to attach to object. - default: {} - expiration: - description: - - Time limit (in seconds) for the URL generated and returned by GCA when performing a mode=put or mode=get_url operation. This url is only - available when public-read is the acl for the object. - mode: - description: - - Switches the module behaviour between upload, download, get_url (return download url) , get_str (download object as string), create (bucket) and - delete (bucket). - required: true - choices: [ 'get', 'put', 'get_url', 'get_str', 'delete', 'create' ] - gs_secret_key: - description: - - GS secret key. If not set then the value of the GS_SECRET_ACCESS_KEY environment variable is used. - required: true - gs_access_key: - description: - - GS access key. If not set then the value of the GS_ACCESS_KEY_ID environment variable is used. - required: true - region: - version_added: "2.4" - description: - - The gs region to use. If not defined then the value 'US' will be used. See U(https://cloud.google.com/storage/docs/bucket-locations) - default: 'US' - versioning: - version_added: "2.4" - description: - - Whether versioning is enabled or disabled (note that once versioning is enabled, it can only be suspended) - type: bool - -requirements: - - "python >= 2.6" - - "boto >= 2.9" - -author: -- Benno Joy (@bennojoy) -- Lukas Beumer (@Nitaco) - -''' - -EXAMPLES = ''' -- name: Upload some content - gc_storage: - bucket: mybucket - object: key.txt - src: /usr/local/myfile.txt - mode: put - permission: public-read - -- name: Upload some headers - gc_storage: - bucket: mybucket - object: key.txt - src: /usr/local/myfile.txt - headers: '{"Content-Encoding": "gzip"}' - -- name: Download some content - gc_storage: - bucket: mybucket - object: key.txt - dest: /usr/local/myfile.txt - mode: get - -- name: Download an object as a string to use else where in your playbook - gc_storage: - bucket: mybucket - object: key.txt - mode: get_str - -- name: Create an empty bucket - gc_storage: - bucket: mybucket - mode: create - -- name: Create a bucket with key as directory - gc_storage: - bucket: mybucket - object: /my/directory/path - mode: create - -- name: Delete a bucket and all contents - gc_storage: - bucket: mybucket - mode: delete - -- name: Create a bucket with versioning enabled - gc_storage: - bucket: "mybucket" - versioning: yes - mode: create - -- name: Create a bucket located in the eu - gc_storage: - bucket: "mybucket" - region: "europe-west3" - mode: create - -''' - -import os - -try: - import boto - HAS_BOTO = True -except ImportError: - HAS_BOTO = False - -from ansible.module_utils.basic import AnsibleModule - - -def grant_check(module, gs, obj): - try: - acp = obj.get_acl() - if module.params.get('permission') == 'public-read': - grant = [x for x in acp.entries.entry_list if x.scope.type == 'AllUsers'] - if not grant: - obj.set_acl('public-read') - module.exit_json(changed=True, result="The objects permission as been set to public-read") - if module.params.get('permission') == 'authenticated-read': - grant = [x for x in acp.entries.entry_list if x.scope.type == 'AllAuthenticatedUsers'] - if not grant: - obj.set_acl('authenticated-read') - module.exit_json(changed=True, result="The objects permission as been set to authenticated-read") - except gs.provider.storage_response_error as e: - module.fail_json(msg=str(e)) - return True - - -def key_check(module, gs, bucket, obj): - try: - bucket = gs.lookup(bucket) - key_check = bucket.get_key(obj) - except gs.provider.storage_response_error as e: - module.fail_json(msg=str(e)) - if key_check: - grant_check(module, gs, key_check) - return True - else: - return False - - -def keysum(module, gs, bucket, obj): - bucket = gs.lookup(bucket) - key_check = bucket.get_key(obj) - if not key_check: - return None - md5_remote = key_check.etag[1:-1] - etag_multipart = '-' in md5_remote # Check for multipart, etag is not md5 - if etag_multipart is True: - module.fail_json(msg="Files uploaded with multipart of gs are not supported with checksum, unable to compute checksum.") - return md5_remote - - -def bucket_check(module, gs, bucket): - try: - result = gs.lookup(bucket) - except gs.provider.storage_response_error as e: - module.fail_json(msg=str(e)) - if result: - grant_check(module, gs, result) - return True - else: - return False - - -def create_bucket(module, gs, bucket): - try: - bucket = gs.create_bucket(bucket, transform_headers(module.params.get('headers')), module.params.get('region')) - bucket.set_acl(module.params.get('permission')) - bucket.configure_versioning(module.params.get('versioning')) - except gs.provider.storage_response_error as e: - module.fail_json(msg=str(e)) - if bucket: - return True - - -def delete_bucket(module, gs, bucket): - try: - bucket = gs.lookup(bucket) - bucket_contents = bucket.list() - for key in bucket_contents: - bucket.delete_key(key.name) - bucket.delete() - return True - except gs.provider.storage_response_error as e: - module.fail_json(msg=str(e)) - - -def delete_key(module, gs, bucket, obj): - try: - bucket = gs.lookup(bucket) - bucket.delete_key(obj) - module.exit_json(msg="Object deleted from bucket ", changed=True) - except gs.provider.storage_response_error as e: - module.fail_json(msg=str(e)) - - -def create_dirkey(module, gs, bucket, obj): - try: - bucket = gs.lookup(bucket) - key = bucket.new_key(obj) - key.set_contents_from_string('') - module.exit_json(msg="Virtual directory %s created in bucket %s" % (obj, bucket.name), changed=True) - except gs.provider.storage_response_error as e: - module.fail_json(msg=str(e)) - - -def path_check(path): - if os.path.exists(path): - return True - else: - return False - - -def transform_headers(headers): - """ - Boto url-encodes values unless we convert the value to `str`, so doing - this prevents 'max-age=100000' from being converted to "max-age%3D100000". - - :param headers: Headers to convert - :type headers: dict - :rtype: dict - - """ - - for key, value in headers.items(): - headers[key] = str(value) - return headers - - -def upload_gsfile(module, gs, bucket, obj, src, expiry): - try: - bucket = gs.lookup(bucket) - key = bucket.new_key(obj) - key.set_contents_from_filename( - filename=src, - headers=transform_headers(module.params.get('headers')) - ) - key.set_acl(module.params.get('permission')) - url = key.generate_url(expiry) - module.exit_json(msg="PUT operation complete", url=url, changed=True) - except gs.provider.storage_copy_error as e: - module.fail_json(msg=str(e)) - - -def download_gsfile(module, gs, bucket, obj, dest): - try: - bucket = gs.lookup(bucket) - key = bucket.lookup(obj) - key.get_contents_to_filename(dest) - module.exit_json(msg="GET operation complete", changed=True) - except gs.provider.storage_copy_error as e: - module.fail_json(msg=str(e)) - - -def download_gsstr(module, gs, bucket, obj): - try: - bucket = gs.lookup(bucket) - key = bucket.lookup(obj) - contents = key.get_contents_as_string() - module.exit_json(msg="GET operation complete", contents=contents, changed=True) - except gs.provider.storage_copy_error as e: - module.fail_json(msg=str(e)) - - -def get_download_url(module, gs, bucket, obj, expiry): - try: - bucket = gs.lookup(bucket) - key = bucket.lookup(obj) - url = key.generate_url(expiry) - module.exit_json(msg="Download url:", url=url, expiration=expiry, changed=True) - except gs.provider.storage_response_error as e: - module.fail_json(msg=str(e)) - - -def handle_get(module, gs, bucket, obj, overwrite, dest): - md5_remote = keysum(module, gs, bucket, obj) - md5_local = module.md5(dest) - if md5_local == md5_remote: - module.exit_json(changed=False) - if md5_local != md5_remote and not overwrite: - module.exit_json(msg="WARNING: Checksums do not match. Use overwrite parameter to force download.", failed=True) - else: - download_gsfile(module, gs, bucket, obj, dest) - - -def handle_put(module, gs, bucket, obj, overwrite, src, expiration): - # Lets check to see if bucket exists to get ground truth. - bucket_rc = bucket_check(module, gs, bucket) - key_rc = key_check(module, gs, bucket, obj) - - # Lets check key state. Does it exist and if it does, compute the etag md5sum. - if bucket_rc and key_rc: - md5_remote = keysum(module, gs, bucket, obj) - md5_local = module.md5(src) - if md5_local == md5_remote: - module.exit_json(msg="Local and remote object are identical", changed=False) - if md5_local != md5_remote and not overwrite: - module.exit_json(msg="WARNING: Checksums do not match. Use overwrite parameter to force upload.", failed=True) - else: - upload_gsfile(module, gs, bucket, obj, src, expiration) - - if not bucket_rc: - create_bucket(module, gs, bucket) - upload_gsfile(module, gs, bucket, obj, src, expiration) - - # If bucket exists but key doesn't, just upload. - if bucket_rc and not key_rc: - upload_gsfile(module, gs, bucket, obj, src, expiration) - - -def handle_delete(module, gs, bucket, obj): - if bucket and not obj: - if bucket_check(module, gs, bucket): - module.exit_json(msg="Bucket %s and all keys have been deleted." % bucket, changed=delete_bucket(module, gs, bucket)) - else: - module.exit_json(msg="Bucket does not exist.", changed=False) - if bucket and obj: - if bucket_check(module, gs, bucket): - if key_check(module, gs, bucket, obj): - module.exit_json(msg="Object has been deleted.", changed=delete_key(module, gs, bucket, obj)) - else: - module.exit_json(msg="Object does not exist.", changed=False) - else: - module.exit_json(msg="Bucket does not exist.", changed=False) - else: - module.fail_json(msg="Bucket or Bucket & object parameter is required.", failed=True) - - -def handle_create(module, gs, bucket, obj): - if bucket and not obj: - if bucket_check(module, gs, bucket): - module.exit_json(msg="Bucket already exists.", changed=False) - else: - module.exit_json(msg="Bucket created successfully", changed=create_bucket(module, gs, bucket)) - if bucket and obj: - if obj.endswith('/'): - dirobj = obj - else: - dirobj = obj + "/" - - if bucket_check(module, gs, bucket): - if key_check(module, gs, bucket, dirobj): - module.exit_json(msg="Bucket %s and key %s already exists." % (bucket, obj), changed=False) - else: - create_dirkey(module, gs, bucket, dirobj) - else: - create_bucket(module, gs, bucket) - create_dirkey(module, gs, bucket, dirobj) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - bucket=dict(required=True), - object=dict(default=None, type='path'), - src=dict(default=None), - dest=dict(default=None, type='path'), - expiration=dict(type='int', default=600, aliases=['expiry']), - mode=dict(choices=['get', 'put', 'delete', 'create', 'get_url', 'get_str'], required=True), - permission=dict(choices=['private', 'public-read', 'authenticated-read'], default='private'), - headers=dict(type='dict', default={}), - gs_secret_key=dict(no_log=True, required=True), - gs_access_key=dict(required=True), - overwrite=dict(default=True, type='bool', aliases=['force']), - region=dict(default='US', type='str'), - versioning=dict(default='no', type='bool') - ), - ) - - if not HAS_BOTO: - module.fail_json(msg='`boto` 2.9+ is required for this module. Try: pip install `boto` --upgrade') - - bucket = module.params.get('bucket') - obj = module.params.get('object') - src = module.params.get('src') - dest = module.params.get('dest') - mode = module.params.get('mode') - expiry = module.params.get('expiration') - gs_secret_key = module.params.get('gs_secret_key') - gs_access_key = module.params.get('gs_access_key') - overwrite = module.params.get('overwrite') - - if mode == 'put': - if not src or not object: - module.fail_json(msg="When using PUT, src, bucket, object are mandatory parameters") - if mode == 'get': - if not dest or not object: - module.fail_json(msg="When using GET, dest, bucket, object are mandatory parameters") - - try: - gs = boto.connect_gs(gs_access_key, gs_secret_key) - except boto.exception.NoAuthHandlerFound as e: - module.fail_json(msg=str(e)) - - if mode == 'get': - if not bucket_check(module, gs, bucket) or not key_check(module, gs, bucket, obj): - module.fail_json(msg="Target bucket/key cannot be found", failed=True) - if not path_check(dest): - download_gsfile(module, gs, bucket, obj, dest) - else: - handle_get(module, gs, bucket, obj, overwrite, dest) - - if mode == 'put': - if not path_check(src): - module.fail_json(msg="Local object for PUT does not exist", failed=True) - handle_put(module, gs, bucket, obj, overwrite, src, expiry) - - # Support for deleting an object if we have both params. - if mode == 'delete': - handle_delete(module, gs, bucket, obj) - - if mode == 'create': - handle_create(module, gs, bucket, obj) - - if mode == 'get_url': - if bucket and obj: - if bucket_check(module, gs, bucket) and key_check(module, gs, bucket, obj): - get_download_url(module, gs, bucket, obj, expiry) - else: - module.fail_json(msg="Key/Bucket does not exist", failed=True) - else: - module.fail_json(msg="Bucket and Object parameters must be set", failed=True) - - # --------------------------- Get the String contents of an Object ------------------------- - if mode == 'get_str': - if bucket and obj: - if bucket_check(module, gs, bucket) and key_check(module, gs, bucket, obj): - download_gsstr(module, gs, bucket, obj) - else: - module.fail_json(msg="Key/Bucket does not exist", failed=True) - else: - module.fail_json(msg="Bucket and Object parameters must be set", failed=True) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/gce_eip.py b/lib/ansible/modules/cloud/google/gce_eip.py deleted file mode 100644 index 40f8822e91..0000000000 --- a/lib/ansible/modules/cloud/google/gce_eip.py +++ /dev/null @@ -1,228 +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 - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - -DOCUMENTATION = ''' -module: gce_eip -version_added: "2.3" -short_description: Create or Destroy Global or Regional External IP addresses. -description: - - Create (reserve) or Destroy (release) Regional or Global IP Addresses. See - U(https://cloud.google.com/compute/docs/configure-instance-ip-addresses#reserve_new_static) for more on reserving static addresses. -requirements: - - "python >= 2.6" - - "apache-libcloud >= 0.19.0" -notes: - - Global addresses can only be used with Global Forwarding Rules. -author: - - "Tom Melendez (@supertom) <tom@supertom.com>" -options: - name: - description: - - Name of Address. - required: true - region: - description: - - Region to create the address in. Set to 'global' to create a global address. - required: true - state: - description: The state the address should be in. C(present) or C(absent) are the only valid options. - default: present - required: false - choices: [present, absent] -''' - -EXAMPLES = ''' -# Create a Global external IP address -- gce_eip: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - name: my-global-ip - region: global - state: present - -# Create a Regional external IP address -- gce_eip: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - name: my-global-ip - region: us-east1 - state: present -''' - -RETURN = ''' -address: - description: IP address being operated on - returned: always - type: str - sample: "35.186.222.233" -name: - description: name of the address being operated on - returned: always - type: str - sample: "my-address" -region: - description: Which region an address belongs. - returned: always - type: str - sample: "global" -''' - -USER_AGENT_VERSION = 'v1' -USER_AGENT_PRODUCT = 'Ansible-gce_eip' - -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.module_utils.gcp import gcp_connect - - -def get_address(gce, name, region): - """ - Get an Address from GCE. - - :param gce: An initialized GCE driver object. - :type gce: :class: `GCENodeDriver` - - :param name: Name of the Address. - :type name: ``str`` - - :return: A GCEAddress object or None. - :rtype: :class: `GCEAddress` or None - """ - try: - return gce.ex_get_address(name=name, region=region) - - except ResourceNotFoundError: - return None - - -def create_address(gce, params): - """ - Create a new Address. - - :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 status and address. - :rtype: tuple in the format of (bool, str) - """ - changed = False - return_data = [] - - address = gce.ex_create_address( - name=params['name'], region=params['region']) - - if address: - changed = True - return_data = address.address - - return (changed, return_data) - - -def delete_address(address): - """ - Delete an Address. - - :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 status and address. - :rtype: tuple in the format of (bool, str) - """ - changed = False - return_data = [] - if address.destroy(): - changed = True - return_data = address.address - return (changed, return_data) - - -def main(): - module = AnsibleModule(argument_spec=dict( - name=dict(required=True), - state=dict(choices=['absent', 'present'], default='present'), - region=dict(required=True), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - pem_file=dict(type='path'), - credentials_file=dict(type='path'), - 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 support (+0.19) required for this module.') - - gce = gcp_connect(module, Provider.GCE, get_driver, - USER_AGENT_PRODUCT, USER_AGENT_VERSION) - - params = {} - params['state'] = module.params.get('state') - params['name'] = module.params.get('name') - params['region'] = module.params.get('region') - - changed = False - json_output = {'state': params['state']} - address = get_address(gce, params['name'], region=params['region']) - - if params['state'] == 'absent': - if not address: - # Doesn't exist in GCE, and state==absent. - changed = False - module.fail_json( - msg="Cannot delete unknown address: %s" % - (params['name'])) - else: - # Delete - (changed, json_output['address']) = delete_address(address) - else: - if not address: - # Create - (changed, json_output['address']) = create_address(gce, - params) - else: - changed = False - json_output['address'] = address.address - - json_output['changed'] = changed - json_output.update(params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/gce_img.py b/lib/ansible/modules/cloud/google/gce_img.py deleted file mode 100644 index e5abbbbcbb..0000000000 --- a/lib/ansible/modules/cloud/google/gce_img.py +++ /dev/null @@ -1,209 +0,0 @@ -#!/usr/bin/python -# Copyright 2015 Google Inc. All Rights Reserved. -# 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 - - -"""An Ansible module to utilize GCE image resources.""" - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gce_img -version_added: "1.9" -short_description: utilize GCE image resources -description: - - This module can create and delete GCE private images from gzipped - compressed tarball containing raw disk data or from existing detached - disks in any zone. U(https://cloud.google.com/compute/docs/images) -options: - name: - description: - - the name of the image to create or delete - required: true - description: - description: - - an optional description - family: - description: - - an optional family name - version_added: "2.2" - source: - description: - - the source disk or the Google Cloud Storage URI to create the image from - state: - description: - - desired state of the image - default: "present" - choices: ["present", "absent"] - zone: - description: - - the zone of the disk specified by source - default: "us-central1-a" - timeout: - description: - - timeout for the operation - default: 180 - version_added: "2.0" - service_account_email: - description: - - service account email - pem_file: - description: - - path to the pem file associated with the service account email - project_id: - description: - - your GCE project ID -requirements: - - "python >= 2.6" - - "apache-libcloud" -author: "Tom Melendez (@supertom)" -''' - -EXAMPLES = ''' -# Create an image named test-image from the disk 'test-disk' in zone us-central1-a. -- gce_img: - name: test-image - source: test-disk - zone: us-central1-a - state: present - -# Create an image named test-image from a tarball in Google Cloud Storage. -- gce_img: - name: test-image - source: https://storage.googleapis.com/bucket/path/to/image.tgz - -# Alternatively use the gs scheme -- gce_img: - name: test-image - source: gs://bucket/path/to/image.tgz - -# Delete an image named test-image. -- gce_img: - name: test-image - state: absent -''' - - -try: - import libcloud - from libcloud.compute.types import Provider - from libcloud.compute.providers import get_driver - from libcloud.common.google import GoogleBaseError - from libcloud.common.google import ResourceExistsError - from libcloud.common.google import ResourceNotFoundError - _ = Provider.GCE - has_libcloud = True -except ImportError: - has_libcloud = False - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.gce import gce_connect - - -GCS_URI = 'https://storage.googleapis.com/' - - -def create_image(gce, name, module): - """Create an image with the specified name.""" - source = module.params.get('source') - zone = module.params.get('zone') - desc = module.params.get('description') - timeout = module.params.get('timeout') - family = module.params.get('family') - - if not source: - module.fail_json(msg='Must supply a source', changed=False) - - if source.startswith(GCS_URI): - # source is a Google Cloud Storage URI - volume = source - elif source.startswith('gs://'): - # libcloud only accepts https URI. - volume = source.replace('gs://', GCS_URI) - else: - try: - volume = gce.ex_get_volume(source, zone) - except ResourceNotFoundError: - module.fail_json(msg='Disk %s not found in zone %s' % (source, zone), - changed=False) - except GoogleBaseError as e: - module.fail_json(msg=str(e), changed=False) - - gce_extra_args = {} - if family is not None: - gce_extra_args['family'] = family - - old_timeout = gce.connection.timeout - try: - gce.connection.timeout = timeout - gce.ex_create_image(name, volume, desc, use_existing=False, **gce_extra_args) - return True - except ResourceExistsError: - return False - except GoogleBaseError as e: - module.fail_json(msg=str(e), changed=False) - finally: - gce.connection.timeout = old_timeout - - -def delete_image(gce, name, module): - """Delete a specific image resource by name.""" - try: - gce.ex_delete_image(name) - return True - except ResourceNotFoundError: - return False - except GoogleBaseError as e: - module.fail_json(msg=str(e), changed=False) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - name=dict(required=True), - family=dict(), - description=dict(), - source=dict(), - state=dict(default='present', choices=['present', 'absent']), - zone=dict(default='us-central1-a'), - service_account_email=dict(), - pem_file=dict(type='path'), - project_id=dict(), - timeout=dict(type='int', default=180) - ) - ) - - if not has_libcloud: - module.fail_json(msg='libcloud with GCE support is required.') - - gce = gce_connect(module) - - name = module.params.get('name') - state = module.params.get('state') - family = module.params.get('family') - changed = False - - if family is not None and hasattr(libcloud, '__version__') and libcloud.__version__ <= '0.20.1': - module.fail_json(msg="Apache Libcloud 1.0.0+ is required to use 'family' option", - changed=False) - - # user wants to create an image. - if state == 'present': - changed = create_image(gce, name, module) - - # user wants to delete the image. - if state == 'absent': - changed = delete_image(gce, name, module) - - module.exit_json(changed=changed, name=name) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/gce_instance_template.py b/lib/ansible/modules/cloud/google/gce_instance_template.py deleted file mode 100644 index 1116ac32f5..0000000000 --- a/lib/ansible/modules/cloud/google/gce_instance_template.py +++ /dev/null @@ -1,588 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright: Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gce_instance_template -version_added: "2.3" -short_description: create or destroy instance templates of Compute Engine of GCP. -description: - - Creates or destroy Google instance templates - of Compute Engine of Google Cloud Platform. -options: - state: - description: - - The desired state for the instance template. - default: "present" - choices: ["present", "absent"] - name: - description: - - The name of the GCE instance template. - required: True - size: - description: - - The desired machine type for the instance template. - default: "f1-micro" - source: - description: - - A source disk to attach to the instance. - Cannot specify both I(image) and I(source). - image: - description: - - The image to use to create the instance. - Cannot specify both both I(image) and I(source). - image_family: - description: - - The image family to use to create the instance. - If I(image) has been used I(image_family) is ignored. - Cannot specify both I(image) and I(source). - disk_type: - description: - - Specify a C(pd-standard) disk or C(pd-ssd) - for an SSD disk. - default: pd-standard - disk_auto_delete: - description: - - Indicate that the boot disk should be - deleted when the Node is deleted. - default: true - type: bool - network: - description: - - The network to associate with the instance. - default: "default" - subnetwork: - description: - - The Subnetwork resource name for this instance. - can_ip_forward: - description: - - Set to C(yes) to allow instance to - send/receive non-matching src/dst packets. - type: bool - default: 'no' - external_ip: - description: - - The external IP address to use. - If C(ephemeral), a new non-static address will be - used. If C(None), then no external address will - be used. To use an existing static IP address - specify address name. - default: "ephemeral" - service_account_email: - description: - - service account email - service_account_permissions: - description: - - service account permissions (see - U(https://cloud.google.com/sdk/gcloud/reference/compute/instances/create), - --scopes section for detailed information) - choices: [ - "bigquery", "cloud-platform", "compute-ro", "compute-rw", - "useraccounts-ro", "useraccounts-rw", "datastore", "logging-write", - "monitoring", "sql-admin", "storage-full", "storage-ro", - "storage-rw", "taskqueue", "userinfo-email" - ] - automatic_restart: - description: - - Defines whether the instance should be - automatically restarted when it is - terminated by Compute Engine. - type: bool - preemptible: - description: - - Defines whether the instance is preemptible. - type: bool - tags: - description: - - a comma-separated list of tags to associate with the instance - metadata: - description: - - a hash/dictionary of custom data for the instance; - '{"key":"value", ...}' - description: - description: - - description of instance template - disks: - 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). - nic_gce_struct: - description: - - Support passing in the GCE-specific - formatted networkInterfaces[] structure. - disks_gce_struct: - description: - - Support passing in the GCE-specific - formatted formatted disks[] structure. Case sensitive. - see U(https://cloud.google.com/compute/docs/reference/latest/instanceTemplates#resource) for detailed information - version_added: "2.4" - project_id: - description: - - your GCE project ID - pem_file: - description: - - path to the pem file associated with the service account email - This option is deprecated. Use 'credentials_file'. - credentials_file: - description: - - path to the JSON file associated with the service account email - subnetwork_region: - version_added: "2.4" - description: - - Region that subnetwork resides in. (Required for subnetwork to successfully complete) -requirements: - - "python >= 2.6" - - "apache-libcloud >= 0.13.3, >= 0.17.0 if using JSON credentials, - >= 0.20.0 if using preemptible option" -notes: - - JSON credentials strongly preferred. -author: "Gwenael Pellen (@GwenaelPellenArkeup) <gwenael.pellen@arkeup.com>" -''' - -EXAMPLES = ''' -# Usage -- name: create instance template named foo - gce_instance_template: - name: foo - size: n1-standard-1 - image_family: ubuntu-1604-lts - state: present - project_id: "your-project-name" - credentials_file: "/path/to/your-key.json" - service_account_email: "your-sa@your-project-name.iam.gserviceaccount.com" - -# Example Playbook -- name: Compute Engine Instance Template 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 instance template - gce_instance_template: - name: my-test-instance-template - size: n1-standard-1 - image_family: ubuntu-1604-lts - state: present - project_id: "{{ project_id }}" - credentials_file: "{{ credentials_file }}" - service_account_email: "{{ service_account_email }}" - - name: delete instance template - gce_instance_template: - name: my-test-instance-template - size: n1-standard-1 - image_family: ubuntu-1604-lts - state: absent - project_id: "{{ project_id }}" - credentials_file: "{{ credentials_file }}" - service_account_email: "{{ service_account_email }}" - -# Example playbook using disks_gce_struct -- name: Compute Engine Instance Template 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 instance template - gce_instance_template: - name: foo - size: n1-standard-1 - state: present - project_id: "{{ project_id }}" - credentials_file: "{{ credentials_file }}" - service_account_email: "{{ service_account_email }}" - disks_gce_struct: - - device_name: /dev/sda - boot: true - autoDelete: true - initializeParams: - diskSizeGb: 30 - diskType: pd-ssd - sourceImage: projects/debian-cloud/global/images/family/debian-8 - -''' - -RETURN = ''' -''' - -import traceback -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.module_utils.gce import gce_connect -from ansible.module_utils._text import to_native - - -def get_info(inst): - """Retrieves instance template information - """ - return({ - 'name': inst.name, - 'extra': inst.extra, - }) - - -def create_instance_template(module, gce): - """Create an instance template - module : AnsibleModule object - gce: authenticated GCE libcloud driver - Returns: - instance template information - """ - # get info from module - name = module.params.get('name') - size = module.params.get('size') - source = module.params.get('source') - image = module.params.get('image') - image_family = module.params.get('image_family') - disk_type = module.params.get('disk_type') - disk_auto_delete = module.params.get('disk_auto_delete') - network = module.params.get('network') - subnetwork = module.params.get('subnetwork') - subnetwork_region = module.params.get('subnetwork_region') - can_ip_forward = module.params.get('can_ip_forward') - external_ip = module.params.get('external_ip') - service_account_permissions = module.params.get( - 'service_account_permissions') - service_account_email = module.params.get('service_account_email') - on_host_maintenance = module.params.get('on_host_maintenance') - automatic_restart = module.params.get('automatic_restart') - preemptible = module.params.get('preemptible') - tags = module.params.get('tags') - metadata = module.params.get('metadata') - description = module.params.get('description') - disks_gce_struct = module.params.get('disks_gce_struct') - changed = False - - # args of ex_create_instancetemplate - gce_args = dict( - name="instance", - size="f1-micro", - source=None, - image=None, - disk_type='pd-standard', - disk_auto_delete=True, - network='default', - subnetwork=None, - can_ip_forward=None, - external_ip='ephemeral', - service_accounts=None, - on_host_maintenance=None, - automatic_restart=None, - preemptible=None, - tags=None, - metadata=None, - description=None, - disks_gce_struct=None, - nic_gce_struct=None - ) - - gce_args['name'] = name - gce_args['size'] = size - - if source is not None: - gce_args['source'] = source - - if image: - gce_args['image'] = image - else: - if image_family: - image = gce.ex_get_image_from_family(image_family) - gce_args['image'] = image - else: - gce_args['image'] = "debian-8" - - gce_args['disk_type'] = disk_type - gce_args['disk_auto_delete'] = disk_auto_delete - - gce_network = gce.ex_get_network(network) - gce_args['network'] = gce_network - - if subnetwork is not None: - gce_args['subnetwork'] = gce.ex_get_subnetwork(subnetwork, region=subnetwork_region) - - if can_ip_forward is not None: - gce_args['can_ip_forward'] = can_ip_forward - - if external_ip == "ephemeral": - instance_external_ip = external_ip - elif external_ip == "none": - instance_external_ip = None - else: - try: - instance_external_ip = gce.ex_get_address(external_ip) - except GoogleBaseError as err: - # external_ip is name ? - instance_external_ip = external_ip - gce_args['external_ip'] = instance_external_ip - - ex_sa_perms = [] - bad_perms = [] - if service_account_permissions: - for perm in service_account_permissions: - if perm not in gce.SA_SCOPES_MAP: - bad_perms.append(perm) - if len(bad_perms) > 0: - module.fail_json(msg='bad permissions: %s' % str(bad_perms)) - if service_account_email is not None: - ex_sa_perms.append({'email': str(service_account_email)}) - else: - ex_sa_perms.append({'email': "default"}) - ex_sa_perms[0]['scopes'] = service_account_permissions - gce_args['service_accounts'] = ex_sa_perms - - if on_host_maintenance is not None: - gce_args['on_host_maintenance'] = on_host_maintenance - - if automatic_restart is not None: - gce_args['automatic_restart'] = automatic_restart - - if preemptible is not None: - gce_args['preemptible'] = preemptible - - if tags is not None: - gce_args['tags'] = tags - - if disks_gce_struct is not None: - gce_args['disks_gce_struct'] = disks_gce_struct - - # 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 - gce_args['metadata'] = metadata - - if description is not None: - gce_args['description'] = description - - instance = None - try: - instance = gce.ex_get_instancetemplate(name) - except ResourceNotFoundError: - try: - instance = gce.ex_create_instancetemplate(**gce_args) - changed = True - except GoogleBaseError as err: - module.fail_json( - msg='Unexpected error attempting to create instance {0}, error: {1}' - .format( - instance, - err.value - ) - ) - - if instance: - json_data = get_info(instance) - else: - module.fail_json(msg="no instance template!") - - return (changed, json_data, name) - - -def delete_instance_template(module, gce): - """ Delete instance template. - module : AnsibleModule object - gce: authenticated GCE libcloud driver - Returns: - instance template information - """ - name = module.params.get('name') - current_state = "absent" - changed = False - - # get instance template - instance = None - try: - instance = gce.ex_get_instancetemplate(name) - current_state = "present" - except GoogleBaseError as e: - json_data = dict(msg='instance template not exists: %s' % to_native(e), - exception=traceback.format_exc()) - - if current_state == "present": - rc = instance.destroy() - if rc: - changed = True - else: - module.fail_json( - msg='instance template destroy failed' - ) - - json_data = {} - return (changed, json_data, name) - - -def module_controller(module, gce): - ''' Control module state parameter. - module : AnsibleModule object - gce: authenticated GCE libcloud driver - Returns: - nothing - Exit: - AnsibleModule object exit with json data. - ''' - json_output = dict() - state = module.params.get("state") - if state == "present": - (changed, output, name) = create_instance_template(module, gce) - json_output['changed'] = changed - json_output['msg'] = output - elif state == "absent": - (changed, output, name) = delete_instance_template(module, gce) - json_output['changed'] = changed - json_output['msg'] = output - - module.exit_json(**json_output) - - -def check_if_system_state_would_be_changed(module, gce): - ''' check_if_system_state_would_be_changed ! - module : AnsibleModule object - gce: authenticated GCE libcloud driver - Returns: - system_state changed - ''' - changed = False - current_state = "absent" - - state = module.params.get("state") - name = module.params.get("name") - - try: - gce.ex_get_instancetemplate(name) - current_state = "present" - except GoogleBaseError as e: - module.fail_json(msg='GCE get instancetemplate problem: %s' % to_native(e), - exception=traceback.format_exc()) - - if current_state != state: - changed = True - - if current_state == "absent": - if changed: - output = 'instance template {0} will be created'.format(name) - else: - output = 'nothing to do for instance template {0} '.format(name) - if current_state == "present": - if changed: - output = 'instance template {0} will be destroyed'.format(name) - else: - output = 'nothing to do for instance template {0} '.format(name) - - return (changed, output) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - state=dict(choices=['present', 'absent'], default='present'), - name=dict(required=True, aliases=['base_name']), - size=dict(default='f1-micro'), - source=dict(), - image=dict(), - image_family=dict(default='debian-8'), - disk_type=dict(choices=['pd-standard', 'pd-ssd'], default='pd-standard', type='str'), - disk_auto_delete=dict(type='bool', default=True), - network=dict(default='default'), - subnetwork=dict(), - can_ip_forward=dict(type='bool', default=False), - external_ip=dict(default='ephemeral'), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - automatic_restart=dict(type='bool', default=None), - preemptible=dict(type='bool', default=None), - tags=dict(type='list'), - metadata=dict(), - description=dict(), - disks=dict(type='list'), - nic_gce_struct=dict(type='list'), - project_id=dict(), - pem_file=dict(type='path'), - credentials_file=dict(type='path'), - subnetwork_region=dict(), - disks_gce_struct=dict(type='list') - ), - mutually_exclusive=[['source', 'image']], - required_one_of=[['image', 'image_family']], - supports_check_mode=True - ) - - 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') - - try: - gce = gce_connect(module) - except GoogleBaseError as e: - module.fail_json(msg='GCE Connection failed %s' % to_native(e), exception=traceback.format_exc()) - - if module.check_mode: - (changed, output) = check_if_system_state_would_be_changed(module, gce) - module.exit_json( - changed=changed, - msg=output - ) - else: - module_controller(module, gce) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/gce_labels.py b/lib/ansible/modules/cloud/google/gce_labels.py deleted file mode 100644 index 004da50c09..0000000000 --- a/lib/ansible/modules/cloud/google/gce_labels.py +++ /dev/null @@ -1,324 +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 - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gce_labels -version_added: '2.4' -short_description: Create, Update or Destroy GCE Labels. -description: - - Create, Update or Destroy GCE Labels on instances, disks, snapshots, etc. - When specifying the GCE resource, users may specify the full URL for - the resource (its 'self_link'), or the individual parameters of the - resource (type, location, name). Examples for the two options can be - seen in the documentation. - See U(https://cloud.google.com/compute/docs/label-or-tag-resources) for - more information about GCE Labels. Labels are gradually being added to - more GCE resources, so this module will need to be updated as new - resources are added to the GCE (v1) API. -requirements: - - 'python >= 2.6' - - 'google-api-python-client >= 1.6.2' - - 'google-auth >= 1.0.0' - - 'google-auth-httplib2 >= 0.0.2' -notes: - - Labels support resources such as instances, disks, images, etc. See - U(https://cloud.google.com/compute/docs/labeling-resources) for the list - of resources available in the GCE v1 API (not alpha or beta). -author: - - 'Eric Johnson (@erjohnso) <erjohnso@google.com>' -options: - labels: - description: - - A list of labels (key/value pairs) to add or remove for the resource. - required: false - resource_url: - description: - - The 'self_link' for the resource (instance, disk, snapshot, etc) - required: false - resource_type: - description: - - The type of resource (instances, disks, snapshots, images) - required: false - resource_location: - description: - - The location of resource (global, us-central1-f, etc.) - required: false - resource_name: - description: - - The name of resource. - required: false -''' - -EXAMPLES = ''' -- name: Add labels on an existing instance (using resource_url) - gce_labels: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - labels: - webserver-frontend: homepage - environment: test - experiment-name: kennedy - resource_url: https://www.googleapis.com/compute/beta/projects/myproject/zones/us-central1-f/instances/example-instance - state: present -- name: Add labels on an image (using resource params) - gce_labels: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - labels: - webserver-frontend: homepage - environment: test - experiment-name: kennedy - resource_type: images - resource_location: global - resource_name: my-custom-image - state: present -- name: Remove specified labels from the GCE instance - gce_labels: - service_account_email: "{{ service_account_email }}" - credentials_file: "{{ credentials_file }}" - project_id: "{{ project_id }}" - labels: - environment: prod - experiment-name: kennedy - resource_url: https://www.googleapis.com/compute/beta/projects/myproject/zones/us-central1-f/instances/example-instance - state: absent -''' - -RETURN = ''' -labels: - description: List of labels that exist on the resource. - returned: Always. - type: dict - sample: [ { 'webserver-frontend': 'homepage', 'environment': 'test', 'environment-name': 'kennedy' } ] -resource_url: - description: The 'self_link' of the GCE resource. - returned: Always. - type: str - sample: 'https://www.googleapis.com/compute/beta/projects/myproject/zones/us-central1-f/instances/example-instance' -resource_type: - description: The type of the GCE resource. - returned: Always. - type: str - sample: instances -resource_location: - description: The location of the GCE resource. - returned: Always. - type: str - sample: us-central1-f -resource_name: - description: The name of the GCE resource. - returned: Always. - type: str - sample: my-happy-little-instance -state: - description: state of the labels - returned: Always. - type: str - sample: present -''' - -try: - from ast import literal_eval - HAS_PYTHON26 = True -except ImportError: - HAS_PYTHON26 = False - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.gcp import check_params, get_google_api_client, GCPUtils - - -UA_PRODUCT = 'ansible-gce_labels' -UA_VERSION = '0.0.1' -GCE_API_VERSION = 'v1' - -# TODO(all): As Labels are added to more GCE resources, this list will need to -# be updated (along with some code changes below). The list can *only* include -# resources from the 'v1' GCE API and will *not* work with 'beta' or 'alpha'. -KNOWN_RESOURCES = ['instances', 'disks', 'snapshots', 'images'] - - -def _fetch_resource(client, module): - params = module.params - if params['resource_url']: - if not params['resource_url'].startswith('https://www.googleapis.com/compute'): - module.fail_json( - msg='Invalid self_link url: %s' % params['resource_url']) - else: - parts = params['resource_url'].split('/')[8:] - if len(parts) == 2: - resource_type, resource_name = parts - resource_location = 'global' - else: - resource_location, resource_type, resource_name = parts - else: - if not params['resource_type'] or not params['resource_location'] \ - or not params['resource_name']: - module.fail_json(msg='Missing required resource params.') - resource_type = params['resource_type'].lower() - resource_name = params['resource_name'].lower() - resource_location = params['resource_location'].lower() - - if resource_type not in KNOWN_RESOURCES: - module.fail_json(msg='Unsupported resource_type: %s' % resource_type) - - # TODO(all): See the comment above for KNOWN_RESOURCES. As labels are - # added to the v1 GCE API for more resources, some minor code work will - # need to be added here. - if resource_type == 'instances': - resource = client.instances().get(project=params['project_id'], - zone=resource_location, - instance=resource_name).execute() - elif resource_type == 'disks': - resource = client.disks().get(project=params['project_id'], - zone=resource_location, - disk=resource_name).execute() - elif resource_type == 'snapshots': - resource = client.snapshots().get(project=params['project_id'], - snapshot=resource_name).execute() - elif resource_type == 'images': - resource = client.images().get(project=params['project_id'], - image=resource_name).execute() - else: - module.fail_json(msg='Unsupported resource type: %s' % resource_type) - - return resource.get('labelFingerprint', ''), { - 'resource_name': resource.get('name'), - 'resource_url': resource.get('selfLink'), - 'resource_type': resource_type, - 'resource_location': resource_location, - 'labels': resource.get('labels', {}) - } - - -def _set_labels(client, new_labels, module, ri, fingerprint): - params = module.params - result = err = None - labels = { - 'labels': new_labels, - 'labelFingerprint': fingerprint - } - - # TODO(all): See the comment above for KNOWN_RESOURCES. As labels are - # added to the v1 GCE API for more resources, some minor code work will - # need to be added here. - if ri['resource_type'] == 'instances': - req = client.instances().setLabels(project=params['project_id'], - instance=ri['resource_name'], - zone=ri['resource_location'], - body=labels) - elif ri['resource_type'] == 'disks': - req = client.disks().setLabels(project=params['project_id'], - zone=ri['resource_location'], - resource=ri['resource_name'], - body=labels) - elif ri['resource_type'] == 'snapshots': - req = client.snapshots().setLabels(project=params['project_id'], - resource=ri['resource_name'], - body=labels) - elif ri['resource_type'] == 'images': - req = client.images().setLabels(project=params['project_id'], - resource=ri['resource_name'], - body=labels) - else: - module.fail_json(msg='Unsupported resource type: %s' % ri['resource_type']) - - # TODO(erjohnso): Once Labels goes GA, we'll be able to use the GCPUtils - # method to poll for the async request/operation to complete before - # returning. However, during 'beta', we are in an odd state where - # API requests must be sent to the 'compute/beta' API, but the python - # client library only allows for *Operations.get() requests to be - # sent to 'compute/v1' API. The response operation is in the 'beta' - # API-scope, but the client library cannot find the operation (404). - # result = GCPUtils.execute_api_client_req(req, client=client, raw=False) - # return result, err - result = req.execute() - return True, err - - -def main(): - module = AnsibleModule( - argument_spec=dict( - state=dict(choices=['absent', 'present'], default='present'), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - pem_file=dict(), - credentials_file=dict(), - labels=dict(required=False, type='dict', default={}), - resource_url=dict(required=False, type='str'), - resource_name=dict(required=False, type='str'), - resource_location=dict(required=False, type='str'), - resource_type=dict(required=False, type='str'), - project_id=dict() - ), - required_together=[ - ['resource_name', 'resource_location', 'resource_type'] - ], - mutually_exclusive=[ - ['resource_url', 'resource_name'], - ['resource_url', 'resource_location'], - ['resource_url', 'resource_type'] - ] - ) - - if not HAS_PYTHON26: - module.fail_json( - msg="GCE module requires python's 'ast' module, python v2.6+") - - client, cparams = get_google_api_client(module, 'compute', - user_agent_product=UA_PRODUCT, - user_agent_version=UA_VERSION, - api_version=GCE_API_VERSION) - - # Get current resource info including labelFingerprint - fingerprint, resource_info = _fetch_resource(client, module) - new_labels = resource_info['labels'].copy() - - update_needed = False - if module.params['state'] == 'absent': - for k, v in module.params['labels'].items(): - if k in new_labels: - if new_labels[k] == v: - update_needed = True - new_labels.pop(k, None) - else: - module.fail_json(msg="Could not remove unmatched label pair '%s':'%s'" % (k, v)) - else: - for k, v in module.params['labels'].items(): - if k not in new_labels: - update_needed = True - new_labels[k] = v - - changed = False - json_output = {'state': module.params['state']} - if update_needed: - changed, err = _set_labels(client, new_labels, module, resource_info, - fingerprint) - json_output['changed'] = changed - - # TODO(erjohnso): probably want to re-fetch the resource to return the - # new labelFingerprint, check that desired labels match updated labels. - # BUT! Will need to wait for setLabels() to hit v1 API so we can use the - # GCPUtils feature to poll for the operation to be complete. For now, - # we'll just update the output with what we have from the original - # state of the resource. - json_output.update(resource_info) - json_output.update(module.params) - - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/gce_lb.py b/lib/ansible/modules/cloud/google/gce_lb.py deleted file mode 100644 index 1bfa0c58bf..0000000000 --- a/lib/ansible/modules/cloud/google/gce_lb.py +++ /dev/null @@ -1,302 +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 - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gce_lb -version_added: "1.5" -short_description: create/destroy GCE load-balancer resources -description: - - This module can create and destroy Google Compute Engine C(loadbalancer) - and C(httphealthcheck) resources. The primary LB resource is the - C(load_balancer) resource and the health check parameters are all - prefixed with I(httphealthcheck). - The full documentation for Google Compute Engine load balancing is at - U(https://developers.google.com/compute/docs/load-balancing/). However, - the ansible module simplifies the configuration by following the - libcloud model. - Full install/configuration instructions for the gce* modules can - be found in the comments of ansible/test/gce_tests.py. -options: - httphealthcheck_name: - description: - - the name identifier for the HTTP health check - httphealthcheck_port: - description: - - the TCP port to use for HTTP health checking - default: 80 - httphealthcheck_path: - description: - - the url path to use for HTTP health checking - default: "/" - httphealthcheck_interval: - description: - - the duration in seconds between each health check request - default: 5 - httphealthcheck_timeout: - description: - - the timeout in seconds before a request is considered a failed check - default: 5 - httphealthcheck_unhealthy_count: - description: - - number of consecutive failed checks before marking a node unhealthy - default: 2 - httphealthcheck_healthy_count: - description: - - number of consecutive successful checks before marking a node healthy - default: 2 - httphealthcheck_host: - description: - - host header to pass through on HTTP check requests - name: - description: - - name of the load-balancer resource - protocol: - description: - - the protocol used for the load-balancer packet forwarding, tcp or udp - default: "tcp" - choices: ['tcp', 'udp'] - region: - description: - - the GCE region where the load-balancer is defined - external_ip: - description: - - the external static IPv4 (or auto-assigned) address for the LB - port_range: - description: - - the port (range) to forward, e.g. 80 or 8000-8888 defaults to all ports - members: - description: - - a list of zone/nodename pairs, e.g ['us-central1-a/www-a', ...] - aliases: ['nodes'] - state: - description: - - desired state of the LB - default: "present" - choices: ["active", "present", "absent", "deleted"] - service_account_email: - version_added: "1.6" - description: - - service account email - pem_file: - version_added: "1.6" - description: - - path to the pem file associated with the service account email - This option is deprecated. Use 'credentials_file'. - credentials_file: - version_added: "2.1.0" - description: - - path to the JSON file associated with the service account email - project_id: - version_added: "1.6" - description: - - your GCE project ID - -requirements: - - "python >= 2.6" - - "apache-libcloud >= 0.13.3, >= 0.17.0 if using JSON credentials" -author: "Eric Johnson (@erjohnso) <erjohnso@google.com>" -''' - -EXAMPLES = ''' -# Simple example of creating a new LB, adding members, and a health check -- local_action: - module: gce_lb - name: testlb - region: us-central1 - members: ["us-central1-a/www-a", "us-central1-b/www-b"] - httphealthcheck_name: hc - httphealthcheck_port: 80 - httphealthcheck_path: "/up" -''' - -try: - from libcloud.compute.types import Provider - from libcloud.compute.providers import get_driver - from libcloud.loadbalancer.types import Provider as Provider_lb - from libcloud.loadbalancer.providers import get_driver as get_driver_lb - from libcloud.common.google import GoogleBaseError, QuotaExceededError, ResourceExistsError, ResourceNotFoundError - - _ = Provider.GCE - HAS_LIBCLOUD = True -except ImportError: - HAS_LIBCLOUD = False - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.gce import USER_AGENT_PRODUCT, USER_AGENT_VERSION, gce_connect, unexpected_error_msg - - -def main(): - module = AnsibleModule( - argument_spec=dict( - httphealthcheck_name=dict(), - httphealthcheck_port=dict(default=80, type='int'), - httphealthcheck_path=dict(default='/'), - httphealthcheck_interval=dict(default=5, type='int'), - httphealthcheck_timeout=dict(default=5, type='int'), - httphealthcheck_unhealthy_count=dict(default=2, type='int'), - httphealthcheck_healthy_count=dict(default=2, type='int'), - httphealthcheck_host=dict(), - name=dict(), - protocol=dict(default='tcp'), - region=dict(), - external_ip=dict(), - port_range=dict(), - members=dict(type='list'), - state=dict(default='present'), - service_account_email=dict(), - pem_file=dict(type='path'), - credentials_file=dict(type='path'), - project_id=dict(), - ) - ) - - if not HAS_LIBCLOUD: - module.fail_json(msg='libcloud with GCE support (0.13.3+) required for this module.') - - gce = gce_connect(module) - - httphealthcheck_name = module.params.get('httphealthcheck_name') - httphealthcheck_port = module.params.get('httphealthcheck_port') - httphealthcheck_path = module.params.get('httphealthcheck_path') - httphealthcheck_interval = module.params.get('httphealthcheck_interval') - httphealthcheck_timeout = module.params.get('httphealthcheck_timeout') - httphealthcheck_unhealthy_count = module.params.get('httphealthcheck_unhealthy_count') - httphealthcheck_healthy_count = module.params.get('httphealthcheck_healthy_count') - httphealthcheck_host = module.params.get('httphealthcheck_host') - name = module.params.get('name') - protocol = module.params.get('protocol') - region = module.params.get('region') - external_ip = module.params.get('external_ip') - port_range = module.params.get('port_range') - members = module.params.get('members') - state = module.params.get('state') - - try: - gcelb = get_driver_lb(Provider_lb.GCE)(gce_driver=gce) - gcelb.connection.user_agent_append("%s/%s" % ( - USER_AGENT_PRODUCT, USER_AGENT_VERSION)) - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - - changed = False - json_output = {'name': name, 'state': state} - - if not name and not httphealthcheck_name: - module.fail_json(msg='Nothing to do, please specify a "name" ' + 'or "httphealthcheck_name" parameter', changed=False) - - if state in ['active', 'present']: - # first, create the httphealthcheck if requested - hc = None - if httphealthcheck_name: - json_output['httphealthcheck_name'] = httphealthcheck_name - try: - hc = gcelb.ex_create_healthcheck(httphealthcheck_name, - host=httphealthcheck_host, path=httphealthcheck_path, - port=httphealthcheck_port, - interval=httphealthcheck_interval, - timeout=httphealthcheck_timeout, - unhealthy_threshold=httphealthcheck_unhealthy_count, - healthy_threshold=httphealthcheck_healthy_count) - changed = True - except ResourceExistsError: - hc = gce.ex_get_healthcheck(httphealthcheck_name) - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - - if hc is not None: - json_output['httphealthcheck_host'] = hc.extra['host'] - json_output['httphealthcheck_path'] = hc.path - json_output['httphealthcheck_port'] = hc.port - json_output['httphealthcheck_interval'] = hc.interval - json_output['httphealthcheck_timeout'] = hc.timeout - json_output['httphealthcheck_unhealthy_count'] = hc.unhealthy_threshold - json_output['httphealthcheck_healthy_count'] = hc.healthy_threshold - - # create the forwarding rule (and target pool under the hood) - lb = None - if name: - if not region: - module.fail_json(msg='Missing required region name', - changed=False) - nodes = [] - output_nodes = [] - json_output['name'] = name - # members is a python list of 'zone/inst' strings - if members: - for node in members: - try: - zone, node_name = node.split('/') - nodes.append(gce.ex_get_node(node_name, zone)) - output_nodes.append(node) - except Exception: - # skip nodes that are badly formatted or don't exist - pass - try: - if hc is not None: - lb = gcelb.create_balancer(name, port_range, protocol, - None, nodes, ex_region=region, ex_healthchecks=[hc], - ex_address=external_ip) - else: - lb = gcelb.create_balancer(name, port_range, protocol, - None, nodes, ex_region=region, ex_address=external_ip) - changed = True - except ResourceExistsError: - lb = gcelb.get_balancer(name) - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - - if lb is not None: - json_output['members'] = output_nodes - json_output['protocol'] = protocol - json_output['region'] = region - json_output['external_ip'] = lb.ip - json_output['port_range'] = lb.port - hc_names = [] - if 'healthchecks' in lb.extra: - for hc in lb.extra['healthchecks']: - hc_names.append(hc.name) - json_output['httphealthchecks'] = hc_names - - if state in ['absent', 'deleted']: - # first, delete the load balancer (forwarding rule and target pool) - # if specified. - if name: - json_output['name'] = name - try: - lb = gcelb.get_balancer(name) - gcelb.destroy_balancer(lb) - changed = True - except ResourceNotFoundError: - pass - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - - # destroy the health check if specified - if httphealthcheck_name: - json_output['httphealthcheck_name'] = httphealthcheck_name - try: - hc = gce.ex_get_healthcheck(httphealthcheck_name) - gce.ex_destroy_healthcheck(hc) - changed = True - except ResourceNotFoundError: - pass - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - - json_output['changed'] = changed - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/gce_mig.py b/lib/ansible/modules/cloud/google/gce_mig.py deleted file mode 100644 index 0a251517b7..0000000000 --- a/lib/ansible/modules/cloud/google/gce_mig.py +++ /dev/null @@ -1,887 +0,0 @@ -#!/usr/bin/python -# Copyright 2016 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 - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gce_mig -version_added: "2.2" -short_description: Create, Update or Destroy a Managed Instance Group (MIG). -description: - - Create, Update or Destroy a Managed Instance Group (MIG). See - U(https://cloud.google.com/compute/docs/instance-groups) for an overview. - Full install/configuration instructions for the gce* modules can - be found in the comments of ansible/test/gce_tests.py. -requirements: - - "python >= 2.6" - - "apache-libcloud >= 1.2.0" -notes: - - Resizing and Recreating VM are also supported. - - An existing instance template is required in order to create a - Managed Instance Group. -author: - - "Tom Melendez (@supertom) <tom@supertom.com>" -options: - name: - description: - - Name of the Managed Instance Group. - required: true - template: - description: - - Instance Template to be used in creating the VMs. See - U(https://cloud.google.com/compute/docs/instance-templates) to learn more - about Instance Templates. Required for creating MIGs. - size: - description: - - Size of Managed Instance Group. If MIG already exists, it will be - resized to the number provided here. Required for creating MIGs. - service_account_email: - description: - - service account email - credentials_file: - description: - - Path to the JSON file associated with the service account email - project_id: - description: - - GCE project ID - state: - description: - - desired state of the resource - default: "present" - choices: ["absent", "present"] - zone: - description: - - The GCE zone to use for this Managed Instance Group. - required: true - autoscaling: - description: - - A dictionary of configuration for the autoscaler. 'enabled (bool)', 'name (str)' - and policy.max_instances (int) are required fields if autoscaling is used. See - U(https://cloud.google.com/compute/docs/reference/beta/autoscalers) for more information - on Autoscaling. - named_ports: - version_added: "2.3" - description: - - Define named ports that backend services can forward data to. Format is a a list of - name:port dictionaries. -''' - -EXAMPLES = ''' -# Following playbook creates, rebuilds instances, resizes and then deletes a MIG. -# Notes: -# - Two valid Instance Templates must exist in your GCE project in order to run -# this playbook. Change the fields to match the templates used in your -# project. -# - The use of the 'pause' module is not required, it is just for convenience. -- name: Managed Instance Group Example - hosts: localhost - gather_facts: False - tasks: - - name: Create MIG - gce_mig: - name: ansible-mig-example - zone: us-central1-c - state: present - size: 1 - template: my-instance-template-1 - named_ports: - - name: http - port: 80 - - name: foobar - port: 82 - - - name: Pause for 30 seconds - pause: - seconds: 30 - - - name: Recreate MIG Instances with Instance Template change. - gce_mig: - name: ansible-mig-example - zone: us-central1-c - state: present - template: my-instance-template-2-small - recreate_instances: yes - - - name: Pause for 30 seconds - pause: - seconds: 30 - - - name: Resize MIG - gce_mig: - name: ansible-mig-example - zone: us-central1-c - state: present - size: 3 - - - name: Update MIG with Autoscaler - gce_mig: - name: ansible-mig-example - zone: us-central1-c - state: present - size: 3 - template: my-instance-template-2-small - recreate_instances: yes - autoscaling: - enabled: yes - name: my-autoscaler - policy: - min_instances: 2 - max_instances: 5 - cool_down_period: 37 - cpu_utilization: - target: .39 - load_balancing_utilization: - target: 0.4 - - - name: Pause for 30 seconds - pause: - seconds: 30 - - - name: Delete MIG - gce_mig: - name: ansible-mig-example - zone: us-central1-c - state: absent - autoscaling: - enabled: no - name: my-autoscaler -''' -RETURN = ''' -zone: - description: Zone in which to launch MIG. - returned: always - type: str - sample: "us-central1-b" - -template: - description: Instance Template to use for VMs. Must exist prior to using with MIG. - returned: changed - type: str - sample: "my-instance-template" - -name: - description: Name of the Managed Instance Group. - returned: changed - type: str - sample: "my-managed-instance-group" - -named_ports: - description: list of named ports acted upon - returned: when named_ports are initially set or updated - type: list - sample: [{ "name": "http", "port": 80 }, { "name": "foo", "port": 82 }] - -size: - description: Number of VMs in Managed Instance Group. - returned: changed - type: int - sample: 4 - -created_instances: - description: Names of instances created. - returned: When instances are created. - type: list - sample: ["ansible-mig-new-0k4y", "ansible-mig-new-0zk5", "ansible-mig-new-kp68"] - -deleted_instances: - description: Names of instances deleted. - returned: When instances are deleted. - type: list - sample: ["ansible-mig-new-0k4y", "ansible-mig-new-0zk5", "ansible-mig-new-kp68"] - -resize_created_instances: - description: Names of instances created during resizing. - returned: When a resize results in the creation of instances. - type: list - sample: ["ansible-mig-new-0k4y", "ansible-mig-new-0zk5", "ansible-mig-new-kp68"] - -resize_deleted_instances: - description: Names of instances deleted during resizing. - returned: When a resize results in the deletion of instances. - type: list - sample: ["ansible-mig-new-0k4y", "ansible-mig-new-0zk5", "ansible-mig-new-kp68"] - -recreated_instances: - description: Names of instances recreated. - returned: When instances are recreated. - type: list - sample: ["ansible-mig-new-0k4y", "ansible-mig-new-0zk5", "ansible-mig-new-kp68"] - -created_autoscaler: - description: True if Autoscaler was attempted and created. False otherwise. - returned: When the creation of an Autoscaler was attempted. - type: bool - sample: true - -updated_autoscaler: - description: True if an Autoscaler update was attempted and succeeded. - False returned if update failed. - returned: When the update of an Autoscaler was attempted. - type: bool - sample: true - -deleted_autoscaler: - description: True if an Autoscaler delete attempted and succeeded. - False returned if delete failed. - returned: When the delete of an Autoscaler was attempted. - type: bool - sample: true - -set_named_ports: - description: True if the named_ports have been set - returned: named_ports have been set - type: bool - sample: true - -updated_named_ports: - description: True if the named_ports have been updated - returned: named_ports have been updated - type: bool - sample: true -''' - -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.module_utils.gce import gce_connect - - -def _check_params(params, field_list): - """ - Helper to validate params. - - Use this in function definitions if they require specific fields - to be present. - - :param params: structure that contains the fields - :type params: ``dict`` - - :param field_list: list of dict representing the fields - [{'name': str, 'required': True/False', 'type': cls}] - :type field_list: ``list`` of ``dict`` - - :return True, exits otherwise - :rtype: ``bool`` - """ - for d in field_list: - if not d['name'] in params: - if d['required'] is True: - return (False, "%s is required and must be of type: %s" % - (d['name'], str(d['type']))) - else: - if not isinstance(params[d['name']], d['type']): - return (False, - "%s must be of type: %s" % (d['name'], str(d['type']))) - - return (True, '') - - -def _validate_autoscaling_params(params): - """ - Validate that the minimum configuration is present for autoscaling. - - :param params: Ansible dictionary containing autoscaling configuration - It is expected that autoscaling config will be found at the - key 'autoscaling'. - :type params: ``dict`` - - :return: Tuple containing a boolean and a string. True if autoscaler - is valid, False otherwise, plus str for message. - :rtype: ``(``bool``, ``str``)`` - """ - if not params['autoscaling']: - # It's optional, so if not set at all, it's valid. - return (True, '') - if not isinstance(params['autoscaling'], dict): - return (False, - 'autoscaling: configuration expected to be a dictionary.') - - # check first-level required fields - as_req_fields = [ - {'name': 'name', 'required': True, 'type': str}, - {'name': 'enabled', 'required': True, 'type': bool}, - {'name': 'policy', 'required': True, 'type': dict} - ] # yapf: disable - - (as_req_valid, as_req_msg) = _check_params(params['autoscaling'], - as_req_fields) - if not as_req_valid: - return (False, as_req_msg) - - # check policy configuration - as_policy_fields = [ - {'name': 'max_instances', 'required': True, 'type': int}, - {'name': 'min_instances', 'required': False, 'type': int}, - {'name': 'cool_down_period', 'required': False, 'type': int} - ] # yapf: disable - - (as_policy_valid, as_policy_msg) = _check_params( - params['autoscaling']['policy'], as_policy_fields) - if not as_policy_valid: - return (False, as_policy_msg) - - # TODO(supertom): check utilization fields - - return (True, '') - - -def _validate_named_port_params(params): - """ - Validate the named ports parameters - - :param params: Ansible dictionary containing named_ports configuration - It is expected that autoscaling config will be found at the - key 'named_ports'. That key should contain a list of - {name : port} dictionaries. - :type params: ``dict`` - - :return: Tuple containing a boolean and a string. True if params - are valid, False otherwise, plus str for message. - :rtype: ``(``bool``, ``str``)`` - """ - if not params['named_ports']: - # It's optional, so if not set at all, it's valid. - return (True, '') - if not isinstance(params['named_ports'], list): - return (False, 'named_ports: expected list of name:port dictionaries.') - req_fields = [ - {'name': 'name', 'required': True, 'type': str}, - {'name': 'port', 'required': True, 'type': int} - ] # yapf: disable - - for np in params['named_ports']: - (valid_named_ports, np_msg) = _check_params(np, req_fields) - if not valid_named_ports: - return (False, np_msg) - - return (True, '') - - -def _get_instance_list(mig, field='name', filter_list=None): - """ - Helper to grab field from instances response. - - :param mig: Managed Instance Group Object from libcloud. - :type mig: :class: `GCEInstanceGroupManager` - - :param field: Field name in list_managed_instances response. Defaults - to 'name'. - :type field: ``str`` - - :param filter_list: list of 'currentAction' strings to filter on. Only - items that match a currentAction in this list will - be returned. Default is "['NONE']". - :type filter_list: ``list`` of ``str`` - - :return: List of strings from list_managed_instances response. - :rtype: ``list`` - """ - filter_list = ['NONE'] if filter_list is None else filter_list - - return [x[field] for x in mig.list_managed_instances() - if x['currentAction'] in filter_list] - - -def _gen_gce_as_policy(as_params): - """ - Take Autoscaler params and generate GCE-compatible policy. - - :param as_params: Dictionary in Ansible-playbook format - containing policy arguments. - :type as_params: ``dict`` - - :return: GCE-compatible policy dictionary - :rtype: ``dict`` - """ - asp_data = {} - asp_data['maxNumReplicas'] = as_params['max_instances'] - if 'min_instances' in as_params: - asp_data['minNumReplicas'] = as_params['min_instances'] - if 'cool_down_period' in as_params: - asp_data['coolDownPeriodSec'] = as_params['cool_down_period'] - if 'cpu_utilization' in as_params and 'target' in as_params[ - 'cpu_utilization']: - asp_data['cpuUtilization'] = {'utilizationTarget': - as_params['cpu_utilization']['target']} - if 'load_balancing_utilization' in as_params and 'target' in as_params[ - 'load_balancing_utilization']: - asp_data['loadBalancingUtilization'] = { - 'utilizationTarget': - as_params['load_balancing_utilization']['target'] - } - - return asp_data - - -def create_autoscaler(gce, mig, params): - """ - Create a new Autoscaler for a MIG. - - :param gce: An initialized GCE driver object. - :type gce: :class: `GCENodeDriver` - - :param mig: An initialized GCEInstanceGroupManager. - :type mig: :class: `GCEInstanceGroupManager` - - :param params: Dictionary of autoscaling parameters. - :type params: ``dict`` - - :return: Tuple with changed stats. - :rtype: tuple in the format of (bool, list) - """ - changed = False - as_policy = _gen_gce_as_policy(params['policy']) - autoscaler = gce.ex_create_autoscaler(name=params['name'], zone=mig.zone, - instance_group=mig, policy=as_policy) - if autoscaler: - changed = True - return changed - - -def update_autoscaler(gce, autoscaler, params): - """ - Update an Autoscaler. - - Takes an existing Autoscaler object, and updates it with - the supplied params before calling libcloud's update method. - - :param gce: An initialized GCE driver object. - :type gce: :class: `GCENodeDriver` - - :param autoscaler: An initialized GCEAutoscaler. - :type autoscaler: :class: `GCEAutoscaler` - - :param params: Dictionary of autoscaling parameters. - :type params: ``dict`` - - :return: True if changes, False otherwise. - :rtype: ``bool`` - """ - as_policy = _gen_gce_as_policy(params['policy']) - if autoscaler.policy != as_policy: - autoscaler.policy = as_policy - autoscaler = gce.ex_update_autoscaler(autoscaler) - if autoscaler: - return True - return False - - -def delete_autoscaler(autoscaler): - """ - Delete an Autoscaler. Does not affect MIG. - - :param mig: Managed Instance Group Object from Libcloud. - :type mig: :class: `GCEInstanceGroupManager` - - :return: Tuple with changed stats and a list of affected instances. - :rtype: tuple in the format of (bool, list) - """ - changed = False - if autoscaler.destroy(): - changed = True - return changed - - -def get_autoscaler(gce, name, zone): - """ - Get an Autoscaler from GCE. - - If the Autoscaler is not found, None is found. - - :param gce: An initialized GCE driver object. - :type gce: :class: `GCENodeDriver` - - :param name: Name of the Autoscaler. - :type name: ``str`` - - :param zone: Zone that the Autoscaler is located in. - :type zone: ``str`` - - :return: A GCEAutoscaler object or None. - :rtype: :class: `GCEAutoscaler` or None - """ - try: - # Does the Autoscaler already exist? - return gce.ex_get_autoscaler(name, zone) - - except ResourceNotFoundError: - return None - - -def create_mig(gce, params): - """ - Create a new Managed Instance Group. - - :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 and a list of affected instances. - :rtype: tuple in the format of (bool, list) - """ - - changed = False - return_data = [] - actions_filter = ['CREATING'] - - mig = gce.ex_create_instancegroupmanager( - name=params['name'], size=params['size'], template=params['template'], - zone=params['zone']) - - if mig: - changed = True - return_data = _get_instance_list(mig, filter_list=actions_filter) - - return (changed, return_data) - - -def delete_mig(mig): - """ - Delete a Managed Instance Group. All VMs in that MIG are also deleted." - - :param mig: Managed Instance Group Object from Libcloud. - :type mig: :class: `GCEInstanceGroupManager` - - :return: Tuple with changed stats and a list of affected instances. - :rtype: tuple in the format of (bool, list) - """ - changed = False - return_data = [] - actions_filter = ['NONE', 'CREATING', 'RECREATING', 'DELETING', - 'ABANDONING', 'RESTARTING', 'REFRESHING'] - instance_names = _get_instance_list(mig, filter_list=actions_filter) - if mig.destroy(): - changed = True - return_data = instance_names - - return (changed, return_data) - - -def recreate_instances_in_mig(mig): - """ - Recreate the instances for a Managed Instance Group. - - :param mig: Managed Instance Group Object from libcloud. - :type mig: :class: `GCEInstanceGroupManager` - - :return: Tuple with changed stats and a list of affected instances. - :rtype: tuple in the format of (bool, list) - """ - changed = False - return_data = [] - actions_filter = ['RECREATING'] - - if mig.recreate_instances(): - changed = True - return_data = _get_instance_list(mig, filter_list=actions_filter) - - return (changed, return_data) - - -def resize_mig(mig, size): - """ - Resize a Managed Instance Group. - - Based on the size provided, GCE will automatically create and delete - VMs as needed. - - :param mig: Managed Instance Group Object from libcloud. - :type mig: :class: `GCEInstanceGroupManager` - - :return: Tuple with changed stats and a list of affected instances. - :rtype: tuple in the format of (bool, list) - """ - changed = False - return_data = [] - actions_filter = ['CREATING', 'DELETING'] - - if mig.resize(size): - changed = True - return_data = _get_instance_list(mig, filter_list=actions_filter) - - return (changed, return_data) - - -def get_mig(gce, name, zone): - """ - Get a Managed Instance Group from GCE. - - If the MIG is not found, None is found. - - :param gce: An initialized GCE driver object. - :type gce: :class: `GCENodeDriver` - - :param name: Name of the Managed Instance Group. - :type name: ``str`` - - :param zone: Zone that the Managed Instance Group is located in. - :type zone: ``str`` - - :return: A GCEInstanceGroupManager object or None. - :rtype: :class: `GCEInstanceGroupManager` or None - """ - try: - # Does the MIG already exist? - return gce.ex_get_instancegroupmanager(name=name, zone=zone) - - except ResourceNotFoundError: - return None - - -def update_named_ports(mig, named_ports): - """ - Set the named ports on a Managed Instance Group. - - Sort the existing named ports and new. If different, update. - This also implicitly allows for the removal of named_por - - :param mig: Managed Instance Group Object from libcloud. - :type mig: :class: `GCEInstanceGroupManager` - - :param named_ports: list of dictionaries in the format of {'name': port} - :type named_ports: ``list`` of ``dict`` - - :return: True if successful - :rtype: ``bool`` - """ - changed = False - existing_ports = [] - new_ports = [] - if hasattr(mig.instance_group, 'named_ports'): - existing_ports = sorted(mig.instance_group.named_ports, - key=lambda x: x['name']) - if named_ports is not None: - new_ports = sorted(named_ports, key=lambda x: x['name']) - - if existing_ports != new_ports: - if mig.instance_group.set_named_ports(named_ports): - changed = True - - return changed - - -def main(): - module = AnsibleModule(argument_spec=dict( - name=dict(required=True), - template=dict(), - recreate_instances=dict(type='bool', default=False), - # Do not set a default size here. For Create and some update - # operations, it is required and should be explicitly set. - # Below, we set it to the existing value if it has not been set. - size=dict(type='int'), - state=dict(choices=['absent', 'present'], default='present'), - zone=dict(required=True), - autoscaling=dict(type='dict', default=None), - named_ports=dict(type='list', default=None), - service_account_email=dict(), - service_account_permissions=dict(type='list'), - pem_file=dict(type='path'), - credentials_file=dict(type='path'), - 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 Managed Instance Group support (1.2+) required for this module.') - - gce = gce_connect(module) - if not hasattr(gce, 'ex_create_instancegroupmanager'): - module.fail_json( - msg='libcloud with GCE Managed Instance Group support (1.2+) required for this module.', - changed=False) - - params = {} - params['state'] = module.params.get('state') - params['zone'] = module.params.get('zone') - params['name'] = module.params.get('name') - params['size'] = module.params.get('size') - params['template'] = module.params.get('template') - params['recreate_instances'] = module.params.get('recreate_instances') - params['autoscaling'] = module.params.get('autoscaling', None) - params['named_ports'] = module.params.get('named_ports', None) - - (valid_autoscaling, as_msg) = _validate_autoscaling_params(params) - if not valid_autoscaling: - module.fail_json(msg=as_msg, changed=False) - - if params['named_ports'] is not None and not hasattr( - gce, 'ex_instancegroup_set_named_ports'): - module.fail_json( - msg="Apache Libcloud 1.3.0+ is required to use 'named_ports' option", - changed=False) - - (valid_named_ports, np_msg) = _validate_named_port_params(params) - if not valid_named_ports: - module.fail_json(msg=np_msg, changed=False) - - changed = False - json_output = {'state': params['state'], 'zone': params['zone']} - mig = get_mig(gce, params['name'], params['zone']) - - if not mig: - if params['state'] == 'absent': - # Doesn't exist in GCE, and state==absent. - changed = False - module.fail_json( - msg="Cannot delete unknown managed instance group: %s" % - (params['name'])) - else: - # Create MIG - req_create_fields = [ - {'name': 'template', 'required': True, 'type': str}, - {'name': 'size', 'required': True, 'type': int} - ] # yapf: disable - - (valid_create_fields, valid_create_msg) = _check_params( - params, req_create_fields) - if not valid_create_fields: - module.fail_json(msg=valid_create_msg, changed=False) - - (changed, json_output['created_instances']) = create_mig(gce, - params) - if params['autoscaling'] and params['autoscaling'][ - 'enabled'] is True: - # Fetch newly-created MIG and create Autoscaler for it. - mig = get_mig(gce, params['name'], params['zone']) - if not mig: - module.fail_json( - msg='Unable to fetch created MIG %s to create \ - autoscaler in zone: %s' % ( - params['name'], params['zone']), changed=False) - - if not create_autoscaler(gce, mig, params['autoscaling']): - module.fail_json( - msg='Unable to fetch MIG %s to create autoscaler \ - in zone: %s' % (params['name'], params['zone']), - changed=False) - - json_output['created_autoscaler'] = True - # Add named ports if available - if params['named_ports']: - mig = get_mig(gce, params['name'], params['zone']) - if not mig: - module.fail_json( - msg='Unable to fetch created MIG %s to create \ - autoscaler in zone: %s' % ( - params['name'], params['zone']), changed=False) - json_output['set_named_ports'] = update_named_ports( - mig, params['named_ports']) - if json_output['set_named_ports']: - json_output['named_ports'] = params['named_ports'] - - elif params['state'] == 'absent': - # Delete MIG - - # First, check and remove the autoscaler, if present. - # Note: multiple autoscalers can be associated to a single MIG. We - # only handle the one that is named, but we might want to think about this. - if params['autoscaling']: - autoscaler = get_autoscaler(gce, params['autoscaling']['name'], - params['zone']) - if not autoscaler: - module.fail_json(msg='Unable to fetch autoscaler %s to delete \ - in zone: %s' % (params['autoscaling']['name'], params['zone']), - changed=False) - - changed = delete_autoscaler(autoscaler) - json_output['deleted_autoscaler'] = changed - - # Now, delete the MIG. - (changed, json_output['deleted_instances']) = delete_mig(mig) - - else: - # Update MIG - - # If we're going to update a MIG, we need a size and template values. - # If not specified, we use the values from the existing MIG. - if not params['size']: - params['size'] = mig.size - - if not params['template']: - params['template'] = mig.template.name - - if params['template'] != mig.template.name: - # Update Instance Template. - new_template = gce.ex_get_instancetemplate(params['template']) - mig.set_instancetemplate(new_template) - json_output['updated_instancetemplate'] = True - changed = True - if params['recreate_instances'] is True: - # Recreate Instances. - (changed, json_output['recreated_instances'] - ) = recreate_instances_in_mig(mig) - - if params['size'] != mig.size: - # Resize MIG. - keystr = 'created' if params['size'] > mig.size else 'deleted' - (changed, json_output['resize_%s_instances' % - (keystr)]) = resize_mig(mig, params['size']) - - # Update Autoscaler - if params['autoscaling']: - autoscaler = get_autoscaler(gce, params['autoscaling']['name'], - params['zone']) - if not autoscaler: - # Try to create autoscaler. - # Note: this isn't perfect, if the autoscaler name has changed - # we wouldn't know that here. - if not create_autoscaler(gce, mig, params['autoscaling']): - module.fail_json( - msg='Unable to create autoscaler %s for existing MIG %s\ - in zone: %s' % (params['autoscaling']['name'], - params['name'], params['zone']), - changed=False) - json_output['created_autoscaler'] = True - changed = True - else: - if params['autoscaling']['enabled'] is False: - # Delete autoscaler - changed = delete_autoscaler(autoscaler) - json_output['delete_autoscaler'] = changed - else: - # Update policy, etc. - changed = update_autoscaler(gce, autoscaler, - params['autoscaling']) - json_output['updated_autoscaler'] = changed - named_ports = params['named_ports'] or [] - json_output['updated_named_ports'] = update_named_ports(mig, - named_ports) - if json_output['updated_named_ports']: - json_output['named_ports'] = named_ports - - json_output['changed'] = changed - json_output.update(params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/gce_net.py b/lib/ansible/modules/cloud/google/gce_net.py deleted file mode 100644 index a1a418eb61..0000000000 --- a/lib/ansible/modules/cloud/google/gce_net.py +++ /dev/null @@ -1,513 +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 - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gce_net -version_added: "1.5" -short_description: create/destroy GCE networks and firewall rules -description: - - This module can create and destroy Google Compute Engine networks and - firewall rules U(https://cloud.google.com/compute/docs/networking). - The I(name) parameter is reserved for referencing a network while the - I(fwname) parameter is used to reference firewall rules. - IPv4 Address ranges must be specified using the CIDR - U(http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) format. - Full install/configuration instructions for the gce* modules can - be found in the comments of ansible/test/gce_tests.py. -options: - allowed: - description: - - the protocol:ports to allow (I(tcp:80) or I(tcp:80,443) or I(tcp:80-800;udp:1-25)) - this parameter is mandatory when creating or updating a firewall rule - ipv4_range: - description: - - the IPv4 address range in CIDR notation for the network - this parameter is not mandatory when you specified existing network in name parameter, - but when you create new network, this parameter is mandatory - aliases: ['cidr'] - fwname: - description: - - name of the firewall rule - aliases: ['fwrule'] - name: - description: - - name of the network - src_range: - description: - - the source IPv4 address range in CIDR notation - default: [] - aliases: ['src_cidr'] - src_tags: - description: - - the source instance tags for creating a firewall rule - default: [] - target_tags: - version_added: "1.9" - description: - - the target instance tags for creating a firewall rule - default: [] - state: - description: - - desired state of the network or firewall - default: "present" - choices: ["active", "present", "absent", "deleted"] - service_account_email: - version_added: "1.6" - description: - - service account email - pem_file: - version_added: "1.6" - description: - - path to the pem file associated with the service account email - This option is deprecated. Use C(credentials_file). - credentials_file: - version_added: "2.1.0" - description: - - path to the JSON file associated with the service account email - project_id: - version_added: "1.6" - description: - - your GCE project ID - mode: - version_added: "2.2" - description: - - network mode for Google Cloud - C(legacy) indicates a network with an IP address range; - C(auto) automatically generates subnetworks in different regions; - C(custom) uses networks to group subnets of user specified IP address ranges - https://cloud.google.com/compute/docs/networking#network_types - default: "legacy" - choices: ["legacy", "auto", "custom"] - subnet_name: - version_added: "2.2" - description: - - name of subnet to create - subnet_region: - version_added: "2.2" - description: - - region of subnet to create - subnet_desc: - version_added: "2.2" - description: - - description of subnet to create - -requirements: - - "python >= 2.6" - - "apache-libcloud >= 0.13.3, >= 0.17.0 if using JSON credentials" -author: "Eric Johnson (@erjohnso) <erjohnso@google.com>, Tom Melendez (@supertom) <supertom@google.com>" -''' - -EXAMPLES = ''' -# Create a 'legacy' Network -- name: Create Legacy Network - gce_net: - name: legacynet - ipv4_range: '10.24.17.0/24' - mode: legacy - state: present - -# Create an 'auto' Network -- name: Create Auto Network - gce_net: - name: autonet - mode: auto - state: present - -# Create a 'custom' Network -- name: Create Custom Network - gce_net: - name: customnet - mode: custom - subnet_name: "customsubnet" - subnet_region: us-east1 - ipv4_range: '10.240.16.0/24' - state: "present" - -# Create Firewall Rule with Source Tags -- name: Create Firewall Rule w/Source Tags - gce_net: - name: default - fwname: "my-firewall-rule" - allowed: tcp:80 - state: "present" - src_tags: "foo,bar" - -# Create Firewall Rule with Source Range -- name: Create Firewall Rule w/Source Range - gce_net: - name: default - fwname: "my-firewall-rule" - allowed: tcp:80 - state: "present" - src_range: ['10.1.1.1/32'] - -# Create Custom Subnetwork -- name: Create Custom Subnetwork - gce_net: - name: privatenet - mode: custom - subnet_name: subnet_example - subnet_region: us-central1 - ipv4_range: '10.0.0.0/16' -''' - -RETURN = ''' -allowed: - description: Rules (ports and protocols) specified by this firewall rule. - returned: When specified - type: str - sample: "tcp:80;icmp" - -fwname: - description: Name of the firewall rule. - returned: When specified - type: str - sample: "my-fwname" - -ipv4_range: - description: IPv4 range of the specified network or subnetwork. - returned: when specified or when a subnetwork is created - type: str - sample: "10.0.0.0/16" - -name: - description: Name of the network. - returned: always - type: str - sample: "my-network" - -src_range: - description: IP address blocks a firewall rule applies to. - returned: when specified - type: list - sample: [ '10.1.1.12/8' ] - -src_tags: - description: Instance Tags firewall rule applies to. - returned: when specified while creating a firewall rule - type: list - sample: [ 'foo', 'bar' ] - -state: - description: State of the item operated on. - returned: always - type: str - sample: "present" - -subnet_name: - description: Name of the subnetwork. - returned: when specified or when a subnetwork is created - type: str - sample: "my-subnetwork" - -subnet_region: - description: Region of the specified subnet. - returned: when specified or when a subnetwork is created - type: str - sample: "us-east1" - -target_tags: - description: Instance Tags with these tags receive traffic allowed by firewall rule. - returned: when specified while creating a firewall rule - type: list - sample: [ 'foo', 'bar' ] -''' -try: - from libcloud.compute.types import Provider - from libcloud.compute.providers import get_driver - from libcloud.common.google import GoogleBaseError, QuotaExceededError, ResourceExistsError, ResourceNotFoundError - _ = Provider.GCE - HAS_LIBCLOUD = True -except ImportError: - HAS_LIBCLOUD = False - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.gce import gce_connect, unexpected_error_msg - - -def format_allowed_section(allowed): - """Format each section of the allowed list""" - if allowed.count(":") == 0: - protocol = allowed - ports = [] - elif allowed.count(":") == 1: - protocol, ports = allowed.split(":") - else: - return [] - if ports.count(","): - ports = ports.split(",") - elif ports: - ports = [ports] - return_val = {"IPProtocol": protocol} - if ports: - return_val["ports"] = ports - return return_val - - -def format_allowed(allowed): - """Format the 'allowed' value so that it is GCE compatible.""" - return_value = [] - if allowed.count(";") == 0: - return [format_allowed_section(allowed)] - else: - sections = allowed.split(";") - for section in sections: - return_value.append(format_allowed_section(section)) - return return_value - - -def sorted_allowed_list(allowed_list): - """Sort allowed_list (output of format_allowed) by protocol and port.""" - # sort by protocol - allowed_by_protocol = sorted(allowed_list, key=lambda x: x['IPProtocol']) - # sort the ports list - return sorted(allowed_by_protocol, key=lambda y: sorted(y.get('ports', []))) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - allowed=dict(), - ipv4_range=dict(), - fwname=dict(), - name=dict(), - src_range=dict(default=[], type='list'), - src_tags=dict(default=[], type='list'), - target_tags=dict(default=[], type='list'), - state=dict(default='present'), - service_account_email=dict(), - pem_file=dict(type='path'), - credentials_file=dict(type='path'), - project_id=dict(), - mode=dict(default='legacy', choices=['legacy', 'auto', 'custom']), - subnet_name=dict(), - subnet_region=dict(), - subnet_desc=dict(), - ) - ) - - if not HAS_LIBCLOUD: - module.fail_json(msg='libcloud with GCE support (0.17.0+) required for this module') - - gce = gce_connect(module) - - allowed = module.params.get('allowed') - ipv4_range = module.params.get('ipv4_range') - fwname = module.params.get('fwname') - name = module.params.get('name') - src_range = module.params.get('src_range') - src_tags = module.params.get('src_tags') - target_tags = module.params.get('target_tags') - state = module.params.get('state') - mode = module.params.get('mode') - subnet_name = module.params.get('subnet_name') - subnet_region = module.params.get('subnet_region') - subnet_desc = module.params.get('subnet_desc') - - changed = False - json_output = {'state': state} - - if state in ['active', 'present']: - network = None - subnet = None - try: - network = gce.ex_get_network(name) - json_output['name'] = name - if mode == 'legacy': - json_output['ipv4_range'] = network.cidr - if network and mode == 'custom' and subnet_name: - if not hasattr(gce, 'ex_get_subnetwork'): - module.fail_json(msg="Update libcloud to a more recent version (>1.0) that supports network 'mode' parameter", changed=False) - - subnet = gce.ex_get_subnetwork(subnet_name, region=subnet_region) - json_output['subnet_name'] = subnet_name - json_output['ipv4_range'] = subnet.cidr - except ResourceNotFoundError: - pass - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - - # user wants to create a new network that doesn't yet exist - if name and not network: - if not ipv4_range and mode != 'auto': - module.fail_json(msg="Network '" + name + "' is not found. To create network in legacy or custom mode, 'ipv4_range' parameter is required", - changed=False) - args = [ipv4_range if mode == 'legacy' else None] - kwargs = {} - if mode != 'legacy': - kwargs['mode'] = mode - - try: - network = gce.ex_create_network(name, *args, **kwargs) - json_output['name'] = name - json_output['ipv4_range'] = ipv4_range - changed = True - except TypeError: - module.fail_json(msg="Update libcloud to a more recent version (>1.0) that supports network 'mode' parameter", changed=False) - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - - if (subnet_name or ipv4_range) and not subnet and mode == 'custom': - if not hasattr(gce, 'ex_create_subnetwork'): - module.fail_json(msg='Update libcloud to a more recent version (>1.0) that supports subnetwork creation', changed=changed) - if not subnet_name or not ipv4_range or not subnet_region: - module.fail_json(msg="subnet_name, ipv4_range, and subnet_region required for custom mode", changed=changed) - - try: - subnet = gce.ex_create_subnetwork(subnet_name, cidr=ipv4_range, network=name, region=subnet_region, description=subnet_desc) - json_output['subnet_name'] = subnet_name - json_output['ipv4_range'] = ipv4_range - changed = True - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=changed) - - if fwname: - # user creating a firewall rule - if not allowed and not src_range and not src_tags: - if changed and network: - module.fail_json( - msg="Network created, but missing required " + "firewall rule parameter(s)", changed=True) - module.fail_json( - msg="Missing required firewall rule parameter(s)", - changed=False) - - allowed_list = format_allowed(allowed) - - # Fetch existing rule and if it exists, compare attributes - # update if attributes changed. Create if doesn't exist. - try: - fw_changed = False - fw = gce.ex_get_firewall(fwname) - - # If old and new attributes are different, we update the firewall rule. - # This implicitly lets us clear out attributes as well. - # allowed_list is required and must not be None for firewall rules. - if allowed_list and (sorted_allowed_list(allowed_list) != sorted_allowed_list(fw.allowed)): - fw.allowed = allowed_list - fw_changed = True - - # source_ranges might not be set in the project; cast it to an empty list - fw.source_ranges = fw.source_ranges or [] - - # If these attributes are lists, we sort them first, then compare. - # Otherwise, we update if they differ. - if fw.source_ranges != src_range: - if isinstance(src_range, list): - if sorted(fw.source_ranges) != sorted(src_range): - fw.source_ranges = src_range - fw_changed = True - else: - fw.source_ranges = src_range - fw_changed = True - - # source_tags might not be set in the project; cast it to an empty list - fw.source_tags = fw.source_tags or [] - - if fw.source_tags != src_tags: - if isinstance(src_tags, list): - if sorted(fw.source_tags) != sorted(src_tags): - fw.source_tags = src_tags - fw_changed = True - else: - fw.source_tags = src_tags - fw_changed = True - - # target_tags might not be set in the project; cast it to an empty list - fw.target_tags = fw.target_tags or [] - - if fw.target_tags != target_tags: - if isinstance(target_tags, list): - if sorted(fw.target_tags) != sorted(target_tags): - fw.target_tags = target_tags - fw_changed = True - else: - fw.target_tags = target_tags - fw_changed = True - - if fw_changed is True: - try: - gce.ex_update_firewall(fw) - changed = True - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - - # Firewall rule not found so we try to create it. - except ResourceNotFoundError: - try: - gce.ex_create_firewall(fwname, allowed_list, network=name, - source_ranges=src_range, source_tags=src_tags, target_tags=target_tags) - changed = True - - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - - json_output['fwname'] = fwname - json_output['allowed'] = allowed - json_output['src_range'] = src_range - json_output['src_tags'] = src_tags - json_output['target_tags'] = target_tags - - if state in ['absent', 'deleted']: - if fwname: - json_output['fwname'] = fwname - fw = None - try: - fw = gce.ex_get_firewall(fwname) - except ResourceNotFoundError: - pass - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - if fw: - gce.ex_destroy_firewall(fw) - changed = True - elif subnet_name: - if not hasattr(gce, 'ex_get_subnetwork') or not hasattr(gce, 'ex_destroy_subnetwork'): - module.fail_json(msg='Update libcloud to a more recent version (>1.0) that supports subnetwork creation', changed=changed) - json_output['name'] = subnet_name - subnet = None - try: - subnet = gce.ex_get_subnetwork(subnet_name, region=subnet_region) - except ResourceNotFoundError: - pass - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - if subnet: - gce.ex_destroy_subnetwork(subnet) - changed = True - elif name: - json_output['name'] = name - network = None - try: - network = gce.ex_get_network(name) - - except ResourceNotFoundError: - pass - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - if network: - try: - gce.ex_destroy_network(network) - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - changed = True - - json_output['changed'] = changed - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/gce_pd.py b/lib/ansible/modules/cloud/google/gce_pd.py deleted file mode 100644 index d26bf5c177..0000000000 --- a/lib/ansible/modules/cloud/google/gce_pd.py +++ /dev/null @@ -1,286 +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 - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gce_pd -version_added: "1.4" -short_description: utilize GCE persistent disk resources -description: - - This module can create and destroy unformatted GCE persistent disks - U(https://developers.google.com/compute/docs/disks#persistentdisks). - It also supports attaching and detaching disks from running instances. - Full install/configuration instructions for the gce* modules can - be found in the comments of ansible/test/gce_tests.py. -options: - detach_only: - description: - - do not destroy the disk, merely detach it from an instance - type: bool - default: 'no' - instance_name: - description: - - instance name if you wish to attach or detach the disk - mode: - description: - - GCE mount mode of disk, READ_ONLY (default) or READ_WRITE - default: "READ_ONLY" - choices: ["READ_WRITE", "READ_ONLY"] - name: - description: - - name of the disk - required: true - size_gb: - description: - - whole integer size of disk (in GB) to create, default is 10 GB - default: 10 - image: - description: - - the source image to use for the disk - version_added: "1.7" - snapshot: - description: - - the source snapshot to use for the disk - version_added: "1.7" - state: - description: - - desired state of the persistent disk - default: "present" - choices: ["active", "present", "absent", "deleted"] - zone: - description: - - zone in which to create the disk - default: "us-central1-b" - service_account_email: - version_added: "1.6" - description: - - service account email - pem_file: - version_added: "1.6" - description: - - path to the pem file associated with the service account email - This option is deprecated. Use 'credentials_file'. - credentials_file: - version_added: "2.1.0" - description: - - path to the JSON file associated with the service account email - project_id: - version_added: "1.6" - description: - - your GCE project ID - disk_type: - version_added: "1.9" - description: - - type of disk provisioned - default: "pd-standard" - choices: ["pd-standard", "pd-ssd"] - delete_on_termination: - version_added: "2.3" - description: - - If C(yes), deletes the volume when instance is terminated - type: bool - default: 'no' - -requirements: - - "python >= 2.6" - - "apache-libcloud >= 0.13.3, >= 0.17.0 if using JSON credentials" -author: "Eric Johnson (@erjohnso) <erjohnso@google.com>" -''' - -EXAMPLES = ''' -# Simple attachment action to an existing instance -- local_action: - module: gce_pd - instance_name: notlocalhost - size_gb: 5 - name: pd -''' - -try: - from libcloud.compute.types import Provider - from libcloud.compute.providers import get_driver - from libcloud.common.google import GoogleBaseError, QuotaExceededError, ResourceExistsError, ResourceNotFoundError, ResourceInUseError - _ = Provider.GCE - HAS_LIBCLOUD = True -except ImportError: - HAS_LIBCLOUD = False - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.gce import gce_connect, unexpected_error_msg - - -def main(): - module = AnsibleModule( - argument_spec=dict( - delete_on_termination=dict(type='bool'), - detach_only=dict(type='bool'), - instance_name=dict(), - mode=dict(default='READ_ONLY', choices=['READ_WRITE', 'READ_ONLY']), - name=dict(required=True), - size_gb=dict(default=10), - disk_type=dict(default='pd-standard'), - image=dict(), - image_family=dict(), - external_projects=dict(type='list'), - snapshot=dict(), - state=dict(default='present'), - zone=dict(default='us-central1-b'), - service_account_email=dict(), - pem_file=dict(type='path'), - credentials_file=dict(type='path'), - project_id=dict(), - ) - ) - if not HAS_LIBCLOUD: - module.fail_json(msg='libcloud with GCE support (0.17.0+) is required for this module') - - gce = gce_connect(module) - - delete_on_termination = module.params.get('delete_on_termination') - detach_only = module.params.get('detach_only') - instance_name = module.params.get('instance_name') - mode = module.params.get('mode') - name = module.params.get('name') - size_gb = module.params.get('size_gb') - disk_type = module.params.get('disk_type') - image = module.params.get('image') - image_family = module.params.get('image_family') - external_projects = module.params.get('external_projects') - snapshot = module.params.get('snapshot') - state = module.params.get('state') - zone = module.params.get('zone') - - if delete_on_termination and not instance_name: - module.fail_json( - msg='Must specify an instance name when requesting delete on termination', - changed=False) - - if detach_only and not instance_name: - module.fail_json( - msg='Must specify an instance name when detaching a disk', - changed=False) - - disk = inst = None - changed = is_attached = False - - json_output = {'name': name, 'zone': zone, 'state': state, 'disk_type': disk_type} - if detach_only: - json_output['detach_only'] = True - json_output['detached_from_instance'] = instance_name - - if instance_name: - # user wants to attach/detach from an existing instance - try: - inst = gce.ex_get_node(instance_name, zone) - # is the disk attached? - for d in inst.extra['disks']: - if d['deviceName'] == name: - is_attached = True - json_output['attached_mode'] = d['mode'] - json_output['attached_to_instance'] = inst.name - except Exception: - pass - - # find disk if it already exists - try: - disk = gce.ex_get_volume(name) - json_output['size_gb'] = int(disk.size) - except ResourceNotFoundError: - pass - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - - # user wants a disk to exist. If "instance_name" is supplied the user - # also wants it attached - if state in ['active', 'present']: - - if not size_gb: - module.fail_json(msg="Must supply a size_gb", changed=False) - try: - size_gb = int(round(float(size_gb))) - if size_gb < 1: - raise Exception - except Exception: - module.fail_json(msg="Must supply a size_gb larger than 1 GB", - changed=False) - - if instance_name and inst is None: - module.fail_json(msg='Instance %s does not exist in zone %s' % ( - instance_name, zone), changed=False) - - if not disk: - if image is not None and snapshot is not None: - module.fail_json( - msg='Cannot give both image (%s) and snapshot (%s)' % ( - image, snapshot), changed=False) - lc_image = None - lc_snapshot = None - if image_family is not None: - lc_image = gce.ex_get_image_from_family(image_family, ex_project_list=external_projects) - elif image is not None: - lc_image = gce.ex_get_image(image, ex_project_list=external_projects) - elif snapshot is not None: - lc_snapshot = gce.ex_get_snapshot(snapshot) - try: - disk = gce.create_volume( - size_gb, name, location=zone, image=lc_image, - snapshot=lc_snapshot, ex_disk_type=disk_type) - except ResourceExistsError: - pass - except QuotaExceededError: - module.fail_json(msg='Requested disk size exceeds quota', - changed=False) - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - json_output['size_gb'] = size_gb - if image is not None: - json_output['image'] = image - if snapshot is not None: - json_output['snapshot'] = snapshot - changed = True - if inst and not is_attached: - try: - gce.attach_volume(inst, disk, device=name, ex_mode=mode, - ex_auto_delete=delete_on_termination) - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - json_output['attached_to_instance'] = inst.name - json_output['attached_mode'] = mode - if delete_on_termination: - json_output['delete_on_termination'] = True - changed = True - - # user wants to delete a disk (or perhaps just detach it). - if state in ['absent', 'deleted'] and disk: - - if inst and is_attached: - try: - gce.detach_volume(disk, ex_node=inst) - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - changed = True - if not detach_only: - try: - gce.destroy_volume(disk) - except ResourceInUseError as e: - module.fail_json(msg=str(e.value), changed=False) - except Exception as e: - module.fail_json(msg=unexpected_error_msg(e), changed=False) - changed = True - - json_output['changed'] = changed - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/gce_snapshot.py b/lib/ansible/modules/cloud/google/gce_snapshot.py deleted file mode 100644 index d6bb430e92..0000000000 --- a/lib/ansible/modules/cloud/google/gce_snapshot.py +++ /dev/null @@ -1,227 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright: Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gce_snapshot -version_added: "2.3" -short_description: Create or destroy snapshots for GCE storage volumes -description: - - Manages snapshots for GCE instances. This module manages snapshots for - the storage volumes of a GCE compute instance. If there are multiple - volumes, each snapshot will be prepended with the disk name -options: - instance_name: - description: - - The GCE instance to snapshot - required: True - snapshot_name: - description: - - The name of the snapshot to manage - disks: - description: - - A list of disks to create snapshots for. If none is provided, - all of the volumes will be snapshotted - default: all - required: False - state: - description: - - Whether a snapshot should be C(present) or C(absent) - required: false - default: present - choices: [present, absent] - service_account_email: - description: - - GCP service account email for the project where the instance resides - required: true - credentials_file: - description: - - The path to the credentials file associated with the service account - required: true - project_id: - description: - - The GCP project ID to use - required: true -requirements: - - "python >= 2.6" - - "apache-libcloud >= 0.19.0" -author: Rob Wagner (@robwagner33) -''' - -EXAMPLES = ''' -- name: Create gce snapshot - gce_snapshot: - instance_name: example-instance - snapshot_name: example-snapshot - state: present - service_account_email: project_name@appspot.gserviceaccount.com - credentials_file: /path/to/credentials - project_id: project_name - delegate_to: localhost - -- name: Delete gce snapshot - gce_snapshot: - instance_name: example-instance - snapshot_name: example-snapshot - state: absent - service_account_email: project_name@appspot.gserviceaccount.com - credentials_file: /path/to/credentials - project_id: project_name - delegate_to: localhost - -# This example creates snapshots for only two of the available disks as -# disk0-example-snapshot and disk1-example-snapshot -- name: Create snapshots of specific disks - gce_snapshot: - instance_name: example-instance - snapshot_name: example-snapshot - state: present - disks: - - disk0 - - disk1 - service_account_email: project_name@appspot.gserviceaccount.com - credentials_file: /path/to/credentials - project_id: project_name - delegate_to: localhost -''' - -RETURN = ''' -snapshots_created: - description: List of newly created snapshots - returned: When snapshots are created - type: list - sample: "[disk0-example-snapshot, disk1-example-snapshot]" - -snapshots_deleted: - description: List of destroyed snapshots - returned: When snapshots are deleted - type: list - sample: "[disk0-example-snapshot, disk1-example-snapshot]" - -snapshots_existing: - description: List of snapshots that already existed (no-op) - returned: When snapshots were already present - type: list - sample: "[disk0-example-snapshot, disk1-example-snapshot]" - -snapshots_absent: - description: List of snapshots that were already absent (no-op) - returned: When snapshots were already absent - type: list - sample: "[disk0-example-snapshot, disk1-example-snapshot]" -''' - -try: - from libcloud.compute.types import Provider - _ = Provider.GCE - HAS_LIBCLOUD = True -except ImportError: - HAS_LIBCLOUD = False - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.gce import gce_connect - - -def find_snapshot(volume, name): - ''' - Check if there is a snapshot already created with the given name for - the passed in volume. - - Args: - volume: A gce StorageVolume object to manage - name: The name of the snapshot to look for - - Returns: - The VolumeSnapshot object if one is found - ''' - found_snapshot = None - snapshots = volume.list_snapshots() - for snapshot in snapshots: - if name == snapshot.name: - found_snapshot = snapshot - return found_snapshot - - -def main(): - module = AnsibleModule( - argument_spec=dict( - instance_name=dict(required=True), - snapshot_name=dict(required=True), - state=dict(choices=['present', 'absent'], default='present'), - disks=dict(default=None, type='list'), - service_account_email=dict(type='str'), - credentials_file=dict(type='path'), - project_id=dict(type='str') - ) - ) - - if not HAS_LIBCLOUD: - module.fail_json(msg='libcloud with GCE support (0.19.0+) is required for this module') - - gce = gce_connect(module) - - instance_name = module.params.get('instance_name') - snapshot_name = module.params.get('snapshot_name') - disks = module.params.get('disks') - state = module.params.get('state') - - json_output = dict( - changed=False, - snapshots_created=[], - snapshots_deleted=[], - snapshots_existing=[], - snapshots_absent=[] - ) - - snapshot = None - - instance = gce.ex_get_node(instance_name, 'all') - instance_disks = instance.extra['disks'] - - for instance_disk in instance_disks: - disk_snapshot_name = snapshot_name - disk_info = gce._get_components_from_path(instance_disk['source']) - device_name = disk_info['name'] - device_zone = disk_info['zone'] - if disks is None or device_name in disks: - volume_obj = gce.ex_get_volume(device_name, device_zone) - - # If we have more than one disk to snapshot, prepend the disk name - if len(instance_disks) > 1: - disk_snapshot_name = device_name + "-" + disk_snapshot_name - - snapshot = find_snapshot(volume_obj, disk_snapshot_name) - - if snapshot and state == 'present': - json_output['snapshots_existing'].append(disk_snapshot_name) - - elif snapshot and state == 'absent': - snapshot.destroy() - json_output['changed'] = True - json_output['snapshots_deleted'].append(disk_snapshot_name) - - elif not snapshot and state == 'present': - volume_obj.snapshot(disk_snapshot_name) - json_output['changed'] = True - json_output['snapshots_created'].append(disk_snapshot_name) - - elif not snapshot and state == 'absent': - json_output['snapshots_absent'].append(disk_snapshot_name) - - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/gce_tag.py b/lib/ansible/modules/cloud/google/gce_tag.py deleted file mode 100644 index e3adebe56e..0000000000 --- a/lib/ansible/modules/cloud/google/gce_tag.py +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright: (c) 2017, Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - -DOCUMENTATION = ''' ---- -module: gce_tag -version_added: "2.0" -short_description: add or remove tag(s) to/from GCE instances -description: - - This module can add or remove tags U(https://cloud.google.com/compute/docs/label-or-tag-resources#tags) - to/from GCE instances. Use 'instance_pattern' to update multiple instances in a specify zone. -options: - instance_name: - description: - - The name of the GCE instance to add/remove tags. - - Required if C(instance_pattern) is not specified. - instance_pattern: - description: - - The pattern of GCE instance names to match for adding/removing tags. Full-Python regex is supported. - See U(https://docs.python.org/2/library/re.html) for details. - - If C(instance_name) is not specified, this field is required. - version_added: "2.3" - tags: - description: - - Comma-separated list of tags to add or remove. - required: yes - state: - description: - - Desired state of the tags. - choices: [ absent, present ] - default: present - zone: - description: - - The zone of the disk specified by source. - default: us-central1-a - service_account_email: - description: - - Service account email. - pem_file: - description: - - Path to the PEM file associated with the service account email. - project_id: - description: - - Your GCE project ID. -requirements: - - python >= 2.6 - - apache-libcloud >= 0.17.0 -notes: - - Either I(instance_name) or I(instance_pattern) is required. -author: - - Do Hoang Khiem (@dohoangkhiem) <(dohoangkhiem@gmail.com> - - Tom Melendez (@supertom) -''' - -EXAMPLES = ''' -- name: Add tags to instance - gce_tag: - instance_name: staging-server - tags: http-server,https-server,staging - zone: us-central1-a - state: present - -- name: Remove tags from instance in default zone (us-central1-a) - gce_tag: - instance_name: test-server - tags: foo,bar - state: absent - -- name: Add tags to instances in zone that match pattern - gce_tag: - instance_pattern: test-server-* - tags: foo,bar - zone: us-central1-a - state: present -''' - -import re -import traceback - -try: - from libcloud.compute.types import Provider - from libcloud.compute.providers import get_driver - from libcloud.common.google import GoogleBaseError, QuotaExceededError, \ - ResourceExistsError, ResourceNotFoundError, InvalidRequestError - - _ = Provider.GCE - HAS_LIBCLOUD = True -except ImportError: - HAS_LIBCLOUD = False - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.gce import gce_connect - - -def _union_items(baselist, comparelist): - """Combine two lists, removing duplicates.""" - return list(set(baselist) | set(comparelist)) - - -def _intersect_items(baselist, comparelist): - """Return matching items in both lists.""" - return list(set(baselist) & set(comparelist)) - - -def _get_changed_items(baselist, comparelist): - """Return changed items as they relate to baselist.""" - return list(set(baselist) & set(set(baselist) ^ set(comparelist))) - - -def modify_tags(gce, module, node, tags, state='present'): - """Modify tags on an instance.""" - - existing_tags = node.extra['tags'] - tags = [x.lower() for x in tags] - tags_changed = [] - - if state == 'absent': - # tags changed are any that intersect - tags_changed = _intersect_items(existing_tags, tags) - if not tags_changed: - return False, None - # update instance with tags in existing tags that weren't specified - node_tags = _get_changed_items(existing_tags, tags) - else: - # tags changed are any that in the new list that weren't in existing - tags_changed = _get_changed_items(tags, existing_tags) - if not tags_changed: - return False, None - # update instance with the combined list - node_tags = _union_items(existing_tags, tags) - - try: - gce.ex_set_node_tags(node, node_tags) - return True, tags_changed - except (GoogleBaseError, InvalidRequestError) as e: - module.fail_json(msg=str(e), changed=False) - - -def main(): - module = AnsibleModule( - argument_spec=dict( - instance_name=dict(type='str'), - instance_pattern=dict(type='str'), - tags=dict(type='list', required=True), - state=dict(type='str', default='present', choices=['absent', 'present']), - zone=dict(type='str', default='us-central1-a'), - service_account_email=dict(type='str'), - pem_file=dict(type='path'), - project_id=dict(type='str'), - ), - mutually_exclusive=[ - ['instance_name', 'instance_pattern'] - ], - required_one_of=[ - ['instance_name', 'instance_pattern'] - ], - ) - - instance_name = module.params.get('instance_name') - instance_pattern = module.params.get('instance_pattern') - state = module.params.get('state') - tags = module.params.get('tags') - zone = module.params.get('zone') - changed = False - - if not HAS_LIBCLOUD: - module.fail_json(msg='libcloud with GCE support (0.17.0+) required for this module') - - gce = gce_connect(module) - - # Create list of nodes to operate on - matching_nodes = [] - try: - if instance_pattern: - instances = gce.list_nodes(ex_zone=zone) - # no instances in zone - if not instances: - module.exit_json(changed=False, tags=tags, zone=zone, instances_updated=[]) - try: - # Python regex fully supported: https://docs.python.org/2/library/re.html - p = re.compile(instance_pattern) - matching_nodes = [i for i in instances if p.search(i.name) is not None] - except re.error as e: - module.fail_json(msg='Regex error for pattern %s: %s' % (instance_pattern, e), changed=False) - else: - matching_nodes = [gce.ex_get_node(instance_name, zone=zone)] - except ResourceNotFoundError: - module.fail_json(msg='Instance %s not found in zone %s' % (instance_name, zone), changed=False) - except GoogleBaseError as e: - module.fail_json(msg=str(e), changed=False, exception=traceback.format_exc()) - - # Tag nodes - instance_pattern_matches = [] - tags_changed = [] - for node in matching_nodes: - changed, tags_changed = modify_tags(gce, module, node, tags, state) - if changed: - instance_pattern_matches.append({'instance_name': node.name, 'tags_changed': tags_changed}) - if instance_pattern: - module.exit_json(changed=changed, instance_pattern=instance_pattern, tags=tags_changed, zone=zone, instances_updated=instance_pattern_matches) - else: - module.exit_json(changed=changed, instance_name=instance_name, tags=tags_changed, zone=zone) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/gcpubsub.py b/lib/ansible/modules/cloud/google/gcpubsub.py deleted file mode 100644 index 4d0add65e3..0000000000 --- a/lib/ansible/modules/cloud/google/gcpubsub.py +++ /dev/null @@ -1,333 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright: (c) 2016, 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 - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - -DOCUMENTATION = ''' ---- -module: gcpubsub -version_added: "2.3" -short_description: Create and Delete Topics/Subscriptions, Publish and pull messages on PubSub -description: - - Create and Delete Topics/Subscriptions, Publish and pull messages on PubSub. - See U(https://cloud.google.com/pubsub/docs) for an overview. -requirements: - - google-auth >= 0.5.0 - - google-cloud-pubsub >= 0.22.0 -notes: - - Subscription pull happens before publish. You cannot publish and pull in the same task. -author: - - Tom Melendez (@supertom) <tom@supertom.com> -options: - topic: - description: - - GCP pubsub topic name. - - Only the name, not the full path, is required. - required: yes - subscription: - description: - - Dictionary containing a subscription name associated with a topic (required), along with optional ack_deadline, push_endpoint and pull. - For pulling from a subscription, message_ack (bool), max_messages (int) and return_immediate are available as subfields. - See subfields name, push_endpoint and ack_deadline for more information. - name: - description: Subfield of subscription. Required if subscription is specified. See examples. - ack_deadline: - description: Subfield of subscription. Not required. Default deadline for subscriptions to ACK the message before it is resent. See examples. - pull: - description: - - Subfield of subscription. Not required. If specified, messages will be retrieved from topic via the provided subscription name. - max_messages (int; default None; max number of messages to pull), message_ack (bool; default False; acknowledge the message) and return_immediately - (bool; default True, don't wait for messages to appear). If the messages are acknowledged, changed is set to True, otherwise, changed is False. - push_endpoint: - description: - - Subfield of subscription. Not required. If specified, message will be sent to an endpoint. - See U(https://cloud.google.com/pubsub/docs/advanced#push_endpoints) for more information. - publish: - description: - - List of dictionaries describing messages and attributes to be published. Dictionary is in message(str):attributes(dict) format. - Only message is required. - state: - description: - - State of the topic or queue. - - Applies to the most granular resource. - - If subscription isspecified we remove it. - - If only topic is specified, that is what is removed. - - NOTE - A topic can be removed without first removing the subscription. - choices: [ absent, present ] - default: present -''' - -EXAMPLES = ''' -# (Message will be pushed; there is no check to see if the message was pushed before -- name: Create a topic and publish a message to it - gcpubsub: - topic: ansible-topic-example - state: present - -# Subscriptions associated with topic are not deleted. -- name: Delete Topic - gcpubsub: - topic: ansible-topic-example - state: absent - -# Setting absent will keep the messages from being sent -- name: Publish multiple messages, with attributes (key:value available with the message) - gcpubsub: - topic: '{{ topic_name }}' - state: present - publish: - - message: this is message 1 - attributes: - mykey1: myvalue - mykey2: myvalu2 - mykey3: myvalue3 - - message: this is message 2 - attributes: - server: prod - sla: "99.9999" - owner: fred - -- name: Create Subscription (pull) - gcpubsub: - topic: ansible-topic-example - subscription: - - name: mysub - state: present - -# pull is default, ack_deadline is not required -- name: Create Subscription with ack_deadline and push endpoint - gcpubsub: - topic: ansible-topic-example - subscription: - - name: mysub - ack_deadline: "60" - push_endpoint: http://pushendpoint.example.com - state: present - -# Setting push_endpoint to "None" converts subscription to pull. -- name: Subscription change from push to pull - gcpubsub: - topic: ansible-topic-example - subscription: - name: mysub - push_endpoint: "None" - -### Topic will not be deleted -- name: Delete subscription - gcpubsub: - topic: ansible-topic-example - subscription: - - name: mysub - state: absent - -# only pull keyword is required. -- name: Pull messages from subscription - gcpubsub: - topic: ansible-topic-example - subscription: - name: ansible-topic-example-sub - pull: - message_ack: yes - max_messages: "100" -''' - -RETURN = ''' -publish: - description: List of dictionaries describing messages and attributes to be published. Dictionary is in message(str):attributes(dict) format. - Only message is required. - returned: Only when specified - type: list - sample: "publish: ['message': 'my message', attributes: {'key1': 'value1'}]" - -pulled_messages: - description: list of dictionaries containing message info. Fields are ack_id, attributes, data, message_id. - returned: Only when subscription.pull is specified - type: list - sample: [{ "ack_id": "XkASTCcYREl...","attributes": {"key1": "val1",...}, "data": "this is message 1", "message_id": "49107464153705"},..] - -state: - description: The state of the topic or subscription. Value will be either 'absent' or 'present'. - returned: Always - type: str - sample: "present" - -subscription: - description: Name of subscription. - returned: When subscription fields are specified - type: str - sample: "mysubscription" - -topic: - description: Name of topic. - returned: Always - type: str - sample: "mytopic" -''' - -try: - from ast import literal_eval - HAS_PYTHON26 = True -except ImportError: - HAS_PYTHON26 = False - -try: - from google.cloud import pubsub - HAS_GOOGLE_CLOUD_PUBSUB = True -except ImportError as e: - HAS_GOOGLE_CLOUD_PUBSUB = False - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.gcp import check_min_pkg_version, get_google_cloud_credentials - - -CLOUD_CLIENT = 'google-cloud-pubsub' -CLOUD_CLIENT_MINIMUM_VERSION = '0.22.0' -CLOUD_CLIENT_USER_AGENT = 'ansible-pubsub-0.1' - - -def publish_messages(message_list, topic): - with topic.batch() as batch: - for message in message_list: - msg = message['message'] - attrs = {} - if 'attributes' in message: - attrs = message['attributes'] - batch.publish(bytes(msg), **attrs) - return True - - -def pull_messages(pull_params, sub): - """ - :rtype: tuple (output, changed) - """ - changed = False - max_messages = pull_params.get('max_messages', None) - message_ack = pull_params.get('message_ack', 'no') - return_immediately = pull_params.get('return_immediately', False) - - output = [] - pulled = sub.pull(return_immediately=return_immediately, max_messages=max_messages) - - for ack_id, msg in pulled: - msg_dict = {'message_id': msg.message_id, - 'attributes': msg.attributes, - 'data': msg.data, - 'ack_id': ack_id} - output.append(msg_dict) - - if message_ack: - ack_ids = [m['ack_id'] for m in output] - if ack_ids: - sub.acknowledge(ack_ids) - changed = True - return (output, changed) - - -def main(): - - module = AnsibleModule( - argument_spec=dict( - topic=dict(type='str', required=True), - state=dict(type='str', default='present', choices=['absent', 'present']), - publish=dict(type='list'), - subscription=dict(type='dict'), - 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_PUBSUB: - module.fail_json(msg="Please install google-cloud-pubsub library.") - - 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['publish'] = module.params.get('publish') - mod_params['state'] = module.params.get('state') - mod_params['topic'] = module.params.get('topic') - mod_params['subscription'] = module.params.get('subscription') - - creds, params = get_google_cloud_credentials(module) - pubsub_client = pubsub.Client(project=params['project_id'], credentials=creds, use_gax=False) - pubsub_client.user_agent = CLOUD_CLIENT_USER_AGENT - - changed = False - json_output = {} - - t = None - if mod_params['topic']: - t = pubsub_client.topic(mod_params['topic']) - s = None - if mod_params['subscription']: - # Note: default ack deadline cannot be changed without deleting/recreating subscription - s = t.subscription(mod_params['subscription']['name'], - ack_deadline=mod_params['subscription'].get('ack_deadline', None), - push_endpoint=mod_params['subscription'].get('push_endpoint', None)) - - if mod_params['state'] == 'absent': - # Remove the most granular resource. If subscription is specified - # we remove it. If only topic is specified, that is what is removed. - # Note that a topic can be removed without first removing the subscription. - # TODO(supertom): Enhancement: Provide an option to only delete a topic - # if there are no subscriptions associated with it (which the API does not support). - if s is not None: - if s.exists(): - s.delete() - changed = True - else: - if t.exists(): - t.delete() - changed = True - elif mod_params['state'] == 'present': - if not t.exists(): - t.create() - changed = True - if s: - if not s.exists(): - s.create() - s.reload() - changed = True - else: - # Subscription operations - # TODO(supertom): if more 'update' operations arise, turn this into a function. - s.reload() - push_endpoint = mod_params['subscription'].get('push_endpoint', None) - if push_endpoint is not None: - if push_endpoint != s.push_endpoint: - if push_endpoint == 'None': - push_endpoint = None - s.modify_push_configuration(push_endpoint=push_endpoint) - s.reload() - changed = push_endpoint == s.push_endpoint - - if 'pull' in mod_params['subscription']: - if s.push_endpoint is not None: - module.fail_json(msg="Cannot pull messages, push_endpoint is configured.") - (json_output['pulled_messages'], changed) = pull_messages( - mod_params['subscription']['pull'], s) - - # publish messages to the topic - if mod_params['publish'] and len(mod_params['publish']) > 0: - changed = publish_messages(mod_params['publish'], t) - - json_output['changed'] = changed - json_output.update(mod_params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/cloud/google/gcpubsub_info.py b/lib/ansible/modules/cloud/google/gcpubsub_info.py deleted file mode 100644 index 11b1f75b62..0000000000 --- a/lib/ansible/modules/cloud/google/gcpubsub_info.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/python -# Copyright 2016 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 - - -ANSIBLE_METADATA = {'metadata_version': '1.1', - 'status': ['preview'], - 'supported_by': 'community'} - - -DOCUMENTATION = ''' ---- -module: gcpubsub_info -version_added: "2.3" -short_description: List Topics/Subscriptions and Messages from Google PubSub. -description: - - List Topics/Subscriptions from Google PubSub. Use the gcpubsub module for - topic/subscription management. - See U(https://cloud.google.com/pubsub/docs) for an overview. - - This module was called C(gcpubsub_facts) before Ansible 2.9. The usage did not change. -requirements: - - "python >= 2.6" - - "google-auth >= 0.5.0" - - "google-cloud-pubsub >= 0.22.0" -notes: - - list state enables user to list topics or subscriptions in the project. See examples for details. -author: - - "Tom Melendez (@supertom) <tom@supertom.com>" -options: - topic: - description: - - GCP pubsub topic name. Only the name, not the full path, is required. - required: False - view: - description: - - Choices are 'topics' or 'subscriptions' - required: True - state: - description: - - list is the only valid option. - required: False -''' - -EXAMPLES = ''' -## List all Topics in a project -- gcpubsub_info: - view: topics - state: list - -## List all Subscriptions in a project -- gcpubsub_info: - view: subscriptions - state: list - -## List all Subscriptions for a Topic in a project -- gcpubsub_info: - view: subscriptions - topic: my-topic - state: list -''' - -RETURN = ''' -subscriptions: - description: List of subscriptions. - returned: When view is set to subscriptions. - type: list - sample: ["mysubscription", "mysubscription2"] -topic: - description: Name of topic. Used to filter subscriptions. - returned: Always - type: str - sample: "mytopic" -topics: - description: List of topics. - returned: When view is set to topics. - type: list - sample: ["mytopic", "mytopic2"] -''' - -try: - from ast import literal_eval - HAS_PYTHON26 = True -except ImportError: - HAS_PYTHON26 = False - -try: - from google.cloud import pubsub - HAS_GOOGLE_CLOUD_PUBSUB = True -except ImportError as e: - HAS_GOOGLE_CLOUD_PUBSUB = False - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.gcp import check_min_pkg_version, get_google_cloud_credentials - - -def list_func(data, member='name'): - """Used for state=list.""" - return [getattr(x, member) for x in data] - - -def main(): - module = AnsibleModule(argument_spec=dict( - view=dict(choices=['topics', 'subscriptions'], default='topics'), - topic=dict(required=False), - state=dict(choices=['list'], default='list'), - service_account_email=dict(), - credentials_file=dict(), - project_id=dict(), ),) - if module._name == 'gcpubsub_facts': - module.deprecate("The 'gcpubsub_facts' module has been renamed to 'gcpubsub_info'", version='2.13') - - if not HAS_PYTHON26: - module.fail_json( - msg="GCE module requires python's 'ast' module, python v2.6+") - - if not HAS_GOOGLE_CLOUD_PUBSUB: - module.fail_json(msg="Please install google-cloud-pubsub library.") - - CLIENT_MINIMUM_VERSION = '0.22.0' - if not check_min_pkg_version('google-cloud-pubsub', CLIENT_MINIMUM_VERSION): - module.fail_json(msg="Please install google-cloud-pubsub library version %s" % CLIENT_MINIMUM_VERSION) - - mod_params = {} - mod_params['state'] = module.params.get('state') - mod_params['topic'] = module.params.get('topic') - mod_params['view'] = module.params.get('view') - - creds, params = get_google_cloud_credentials(module) - pubsub_client = pubsub.Client(project=params['project_id'], credentials=creds, use_gax=False) - pubsub_client.user_agent = 'ansible-pubsub-0.1' - - json_output = {} - if mod_params['view'] == 'topics': - json_output['topics'] = list_func(pubsub_client.list_topics()) - elif mod_params['view'] == 'subscriptions': - if mod_params['topic']: - t = pubsub_client.topic(mod_params['topic']) - json_output['subscriptions'] = list_func(t.list_subscriptions()) - else: - json_output['subscriptions'] = list_func(pubsub_client.list_subscriptions()) - - json_output['changed'] = False - json_output.update(mod_params) - module.exit_json(**json_output) - - -if __name__ == '__main__': - main() |