Relocating extras into lib/ansible/modules/ after merge

This commit is contained in:
James Cammarata 2016-12-08 00:36:57 -05:00 committed by Matt Clay
commit 011ea55a8f
596 changed files with 0 additions and 266 deletions

View file

@ -0,0 +1,794 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 CallFire Inc.
#
# This file is part of Ansible.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
################################################################################
# Documentation
################################################################################
ANSIBLE_METADATA = {'status': ['preview'],
'supported_by': 'community',
'version': '1.0'}
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"
options:
state:
description:
- Whether the given resource record should or should not be present.
required: false
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 specifed, I(zone_id) will be
used.
required: false
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 specifed, I(zone_id) will be
used.
required: false
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.
required: false
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.
required: false
choices: [True, False]
default: False
service_account_email:
description:
- The e-mail address for a service account with access to Google
Cloud DNS.
required: false
default: null
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.
required: false
default: null
credentials_file:
description:
- The path to the JSON file associated with the service account
email.
required: false
default: null
project_id:
description:
- The Google Cloud Platform project ID to use.
required: false
default: null
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: boolean
sample: True
record:
description: Fully-qualified domain name of the resource record
returned: success
type: string
sample: mail.example.com.
state:
description: Whether the record is present or absent
returned: success
type: string
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: string
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: string
sample: example.com.
zone_id:
description: The Google Cloud DNS ID of the zone
returned: success
type: string
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
except ImportError:
HAS_LIBCLOUD = False
################################################################################
# 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 libcloud Google Cloud DNS provider.
PROVIDER = Provider.GOOGLE
# 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 resore 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)
from ansible.module_utils.basic import *
from ansible.module_utils.gcdns import *
if __name__ == '__main__':
main()

View file

@ -0,0 +1,385 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 CallFire Inc.
#
# This file is part of Ansible.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
################################################################################
# Documentation
################################################################################
ANSIBLE_METADATA = {'status': ['preview'],
'supported_by': 'community',
'version': '1.0'}
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:
- "python >= 2.6"
- "apache-libcloud >= 0.19.0"
options:
state:
description:
- Whether the given zone should or should not be present.
required: false
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.
required: false
default: ""
service_account_email:
description:
- The e-mail address for a service account with access to Google
Cloud DNS.
required: false
default: null
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.
required: false
default: null
credentials_file:
description:
- The path to the JSON file associated with the service account
email.
required: false
default: null
project_id:
description:
- The Google Cloud Platform project ID to use.
required: false
default: null
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: string
sample: This is an awesome zone
state:
description: Whether the zone is present or absent
returned: success
type: string
sample: present
zone:
description: The zone's DNS name
returned: success
type: string
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
HAS_LIBCLOUD = True
except ImportError:
HAS_LIBCLOUD = False
################################################################################
# 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 libcloud Google Cloud DNS provider.
PROVIDER = Provider.GOOGLE
# 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)
from ansible.module_utils.basic import *
from ansible.module_utils.gcdns import *
if __name__ == '__main__':
main()

View file

@ -0,0 +1,233 @@
#!/usr/bin/python
# Copyright 2015 Google Inc. All Rights Reserved.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
"""An Ansible module to utilize GCE image resources."""
ANSIBLE_METADATA = {'status': ['preview'],
'supported_by': 'community',
'version': '1.0'}
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
default: null
description:
description:
- an optional description
required: false
default: null
family:
description:
- an optional family name
required: false
default: null
version_added: "2.2"
source:
description:
- the source disk or the Google Cloud Storage URI to create the image from
required: false
default: null
state:
description:
- desired state of the image
required: false
default: "present"
choices: ["present", "absent"]
zone:
description:
- the zone of the disk specified by source
required: false
default: "us-central1-a"
timeout:
description:
- timeout for the operation
required: false
default: 180
version_added: "2.0"
service_account_email:
description:
- service account email
required: false
default: null
pem_file:
description:
- path to the pem file associated with the service account email
required: false
default: null
project_id:
description:
- your GCE project ID
required: false
default: null
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()

View file

@ -0,0 +1,232 @@
#!/usr/bin/python
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>
ANSIBLE_METADATA = {'status': ['preview'],
'supported_by': 'community',
'version': '1.0'}
DOCUMENTATION = '''
---
module: gce_tag
version_added: "2.0"
short_description: add or remove tag(s) to/from GCE instance
description:
- This module can add or remove tags U(https://cloud.google.com/compute/docs/instances/#tags)
to/from GCE instance.
options:
instance_name:
description:
- the name of the GCE instance to add/remove tags
required: true
default: null
aliases: []
tags:
description:
- comma-separated list of tags to add or remove
required: true
default: null
aliases: []
state:
description:
- desired state of the tags
required: false
default: "present"
choices: ["present", "absent"]
aliases: []
zone:
description:
- the zone of the disk specified by source
required: false
default: "us-central1-a"
aliases: []
service_account_email:
description:
- service account email
required: false
default: null
aliases: []
pem_file:
description:
- path to the pem file associated with the service account email
required: false
default: null
aliases: []
project_id:
description:
- your GCE project ID
required: false
default: null
aliases: []
requirements:
- "python >= 2.6"
- "apache-libcloud"
author: "Do Hoang Khiem (dohoangkhiem@gmail.com)"
'''
EXAMPLES = '''
# Add tags 'http-server', 'https-server', 'staging' to instance name 'staging-server' in zone us-central1-a.
- gce_tag:
instance_name: staging-server
tags: http-server,https-server,staging
zone: us-central1-a
state: present
# Remove tags 'foo', 'bar' from instance 'test-server' in default zone (us-central1-a)
- gce_tag:
instance_name: test-server
tags: foo,bar
state: absent
'''
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 add_tags(gce, module, instance_name, tags):
"""Add tags to instance."""
zone = module.params.get('zone')
if not instance_name:
module.fail_json(msg='Must supply instance_name', changed=False)
if not tags:
module.fail_json(msg='Must supply tags', changed=False)
tags = [x.lower() for x in tags]
try:
node = 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)
node_tags = node.extra['tags']
changed = False
tags_changed = []
for t in tags:
if t not in node_tags:
changed = True
node_tags.append(t)
tags_changed.append(t)
if not changed:
return False, None
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 remove_tags(gce, module, instance_name, tags):
"""Remove tags from instance."""
zone = module.params.get('zone')
if not instance_name:
module.fail_json(msg='Must supply instance_name', changed=False)
if not tags:
module.fail_json(msg='Must supply tags', changed=False)
tags = [x.lower() for x in tags]
try:
node = 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)
node_tags = node.extra['tags']
changed = False
tags_changed = []
for t in tags:
if t in node_tags:
node_tags.remove(t)
changed = True
tags_changed.append(t)
if not changed:
return False, None
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(required=True),
tags=dict(type='list'),
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(),
)
)
if not HAS_LIBCLOUD:
module.fail_json(msg='libcloud with GCE support is required.')
instance_name = module.params.get('instance_name')
state = module.params.get('state')
tags = module.params.get('tags')
zone = module.params.get('zone')
changed = False
if not zone:
module.fail_json(msg='Must specify "zone"', changed=False)
if not tags:
module.fail_json(msg='Must specify "tags"', changed=False)
gce = gce_connect(module)
# add tags to instance.
if state == 'present':
changed, tags_changed = add_tags(gce, module, instance_name, tags)
# remove tags from instance
if state == 'absent':
changed, tags_changed = remove_tags(gce, module, instance_name, tags)
module.exit_json(changed=changed, instance_name=instance_name, tags=tags_changed, zone=zone)
if __name__ == '__main__':
main()