9fda16070f
This commit adds modules that can manipulate Google Cloud DNS. The modules can create and delete zones, as well as records within zones.
790 lines
27 KiB
Python
790 lines
27 KiB
Python
#!/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
|
|
################################################################################
|
|
|
|
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' ]
|
|
values:
|
|
description:
|
|
- The values to use for the resource record.
|
|
- I(values) must be specified if I(state) is C(present) or
|
|
I(overwrite) is C(True), or the module will fail.
|
|
- Valid values 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 values for TXT records must be enclosed in
|
|
double quotes.
|
|
- For resource records that have the same name but different
|
|
values (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(values). 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 values do not match exactly
|
|
with the existing resource record's values.
|
|
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
|
|
values:
|
|
- '10.1.2.3'
|
|
- '10.4.5.6'
|
|
- '10.7.8.9'
|
|
- '192.168.5.10'
|
|
|
|
# Change the value of an existing record with multiple values.
|
|
- gcdns_record:
|
|
record: 'api.example.com'
|
|
zone: 'example.com'
|
|
type: A
|
|
overwrite: true
|
|
values: # WARNING: All values in a record will be replaced
|
|
- '10.1.2.3'
|
|
- '10.5.5.7' # The changed record
|
|
- '10.7.8.9'
|
|
- '192.168.5.10'
|
|
|
|
# Safely remove a multi-line record.
|
|
- gcdns_record:
|
|
record: 'api.example.com'
|
|
zone_id: 'example-com'
|
|
state: absent
|
|
type: A
|
|
values: # NOTE: All of the values must match exactly
|
|
- '10.1.2.3'
|
|
- '10.5.5.7'
|
|
- '10.7.8.9'
|
|
- '192.168.5.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
|
|
values:
|
|
- '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
|
|
values:
|
|
- '"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
|
|
values:
|
|
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']
|
|
values = module.params['values']
|
|
data = dict(ttl=ttl, rrdatas=values)
|
|
|
|
# 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, values):
|
|
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, values),
|
|
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']
|
|
values = module.params['values']
|
|
|
|
# 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, values):
|
|
module.fail_json(
|
|
msg = 'cannot delete due to non-matching ttl or values: ' +
|
|
"ttl: %d, values: %s " % (ttl, values) +
|
|
"original ttl: %d, original values: %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_values, new_ttl, new_values):
|
|
"""Checks to see if original and new TTL and values match."""
|
|
|
|
matches = True
|
|
|
|
if old_ttl != new_ttl:
|
|
matches = False
|
|
if old_values != new_values:
|
|
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']
|
|
values = module.params['values']
|
|
|
|
# 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 values is not None and (state == 'present' or not overwrite):
|
|
# A records must contain valid IPv4 addresses.
|
|
if record_type == 'A':
|
|
for value in values:
|
|
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 values:
|
|
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(values) > 1:
|
|
module.fail_json(
|
|
msg = 'CNAME or SOA records cannot have more than one value, ' +
|
|
"got: %s" % values,
|
|
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 values:
|
|
if value[0] != '"' and value[-1] != '"':
|
|
module.fail_json(
|
|
msg = 'TXT values 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'),
|
|
values = 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', ['values']),
|
|
('overwrite', False, ['values'])
|
|
],
|
|
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,
|
|
values = module.params['values'],
|
|
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'],
|
|
values = 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,
|
|
values = module.params['values'],
|
|
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()
|