Files
ansible-freeipa/plugins/modules/ipadnsrecord.py
Thomas Woerner fe364cc2db ipadnsrecord: Fix for ansible-test fake execution test
All imports that are only available after installing IPA need to be in a
try exception clause to be able to pass the fake execution test.

If the imports can not be done, all used and needed attributes are
defined with the value None, MODULE_IMPORT_ERROR is set to the import
error and fail_json is called.
2022-09-27 12:54:11 +02:00

1497 lines
46 KiB
Python

# -*- coding: utf-8 -*-
# Authors:
# Rafael Guterres Jeffman <rjeffman@redhat.com>
#
# Copyright (C) 2020 Red Hat
# see file 'COPYING' for use and warranty information
#
# 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/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
"""DNS Record ansible-freeipa module."""
ANSIBLE_METADATA = {
"metadata_version": "1.0",
"supported_by": "community",
"status": ["preview"],
}
DOCUMENTATION = """
---
module: ipadnsrecord
short_description: Manage FreeIPA DNS records
description: Manage FreeIPA DNS records
extends_documentation_fragment:
- ipamodule_base_docs
options:
records:
description: The list of user dns records dicts
required: false
options:
name:
description: The DNS record name to manage.
aliases: ["record_name"]
required: true
zone_name:
description: |
The DNS zone name to which DNS record needs to be managed.
Required if not provided globally.
aliases: ["dnszone"]
required: false
record_type:
description: The type of DNS record.
choices: ["A", "AAAA", "A6", "AFSDB", "CERT", "CNAME", "DLV", "DNAME",
"DS", "KX", "LOC", "MX", "NAPTR", "NS", "PTR", "SRV",
"SSHFP", "TLSA", "TXT", "URI"]
default: "A"
record_value:
description: Manage DNS record name with these values.
required: false
type: list
record_ttl:
description: Set the TTL for the record.
required: false
type: int
del_all:
description: Delete all associated records.
required: false
type: bool
a_rec:
description: Raw A record.
required: false
aliases: ["a_record"]
aaaa_rec:
description: Raw AAAA record.
required: false
aliases: ["aaaa_record"]
a6_rec:
description: Raw A6 record.
required: false
aliases: ["a6_record"]
afsdb_rec:
description: Raw AFSDB record.
required: false
aliases: ["afsdb_record"]
cert_rec:
description: Raw CERT record.
required: false
aliases: ["cert_record"]
cname_rec:
description: Raw CNAME record.
required: false
aliases: ["cname_record"]
dlv_rec:
description: Raw DLV record.
required: false
aliases: ["dlv_record"]
dname_rec:
description: Raw DNAM record.
required: false
aliases: ["dname_record"]
ds_rec:
description: Raw DS record.
required: false
aliases: ["ds_record"]
kx_rec:
description: Raw KX record.
required: false
aliases: ["kx_record"]
loc_rec:
description: Raw LOC record.
required: false
aliases: ["loc_record"]
mx_rec:
description: Raw MX record.
required: false
aliases: ["mx_record"]
naptr_rec:
description: Raw NAPTR record.
required: false
aliases: ["naptr_record"]
ns_rec:
description: Raw NS record.
required: false
aliases: ["ns_record"]
ptr_rec:
description: Raw PTR record.
required: false
aliases: ["ptr_record"]
srv_rec:
description: Raw SRV record.
required: false
aliases: ["srv_record"]
sshfp_rec:
description: Raw SSHFP record.
required: false
aliases: ["sshfp_record"]
tlsa_rec:
description: Raw TLSA record.
required: false
aliases: ["tlsa_record"]
txt_rec:
description: Raw TXT record.
required: false
aliases: ["txt_record"]
uri_rec:
description: Raw URI record.
required: false
aliases: ["uri_record"]
ip_address:
description: IP adresses for A or AAAA records.
required: false
type: string
a_ip_address:
description: IP adresses for A records.
required: false
type: string
a_create_reverse:
description: |
Create reverse record for A records.
There is no equivalent to remove reverse records.
type: bool
required: false
aaaa_ip_address:
description: IP adresses for AAAA records.
required: false
type: string
aaaa_create_reverse:
description: |
Create reverse record for AAAA records.
There is no equivalent to remove reverse records.
type: bool
required: false
create_reverse:
description: |
Create reverse record for A or AAAA record types.
There is no equivalent to remove reverse records.
type: bool
required: false
aliases: ["reverse"]
a6_data:
description: A6 record data.
required: false
afsdb_subtype:
description: AFSDB Subtype
required: false
type: int
afsdb_hostname:
description: AFSDB Hostname
required: false
type: string
cert_type:
description: CERT Certificate Type
required: false
type: int
cert_key_tag:
description: CERT Key Tag
required: false
type: int
cert_algorithm:
description: CERT Algorithm
required: false
type: int
cert_certificate_or_crl:
description: CERT Certificate or Certificate Revocation List (CRL).
required: false
type: string
cname_hostname:
description: A hostname which this alias hostname points to.
required: false
type: string
dlv_key_tag:
description: DS Key Tag
required: false
type: int
dlv_algorithm:
description: DLV Algorithm
required: false
type: int
dlv_digest_type:
description: DLV Digest Type
required: false
type: int
dlv_digest:
description: DLV Digest
required: false
type: string
dname_target:
description: DNAME Target
required: false
type: string
ds_key_tag:
description: DS Key Tag
required: false
type: int
ds_algorithm:
description: DS Algorithm
required: false
type: int
ds_digest_type:
description: DS Digest Type
required: false
type: int
ds_digest:
description: DS Digest
required: false
type: string
kx_preference:
description: |
Preference given to this exchanger. Lower values are more preferred.
required: false
type: int
kx_exchanger:
description: A host willing to act as a key exchanger.
required: false
type: string
loc_lat_deg:
description: LOC Degrees Latitude
required: false
type: int
loc_lat_min:
description: LOC Minutes Latitude
required: false
type: int
loc_lat_sec:
description: LOC Seconds Latitude
required: false
type: float
loc_lat_dir:
description: LOC Direction Latitude
required: false
choices: ["N", "S"]
loc_lon_deg:
description: LOC Degrees Longitude
required: false
type: int
loc_lon_min:
description: LOC Minutes Longitude
required: false
type: int
loc_lon_sec:
description: LOC Seconds Longitude
required: false
type: float
loc_lon_dir:
description: LOC Direction Longitude
required: false
choices: ["E", "W"]
loc_altitude:
description: LOC Altitude
required: false
type: float
loc_size:
description: LOC Size
required: false
type: float
loc_h_precision:
description: LOC Horizontal Precision
required: false
type: float
loc_v_precision:
description: LOC Vertical Precision
required: false
type: float
mx_preference:
description: |
Preference given to this exchanger. Lower values are more preferred.
required: false
type: int
mx_exchanger:
description: A host willing to act as a mail exchanger.
required: false
type: string
naptr_order:
description: NAPTR Order
required: false
type: int
naptr_preference:
description: NAPTR Preference
required: false
type: int
naptr_flags:
description: NAPTR Flags
required: false
type: string
naptr_service:
description: NAPTR Service
required: false
type: string
naptr_regexp:
description: NAPTR Regular Expression
required: false
type: string
naptr_replacement:
description: NAPTR Replacement
required: false
type: string
ns_hostname:
description: NS Hostname
required: false
type: string
ptr_hostname:
description: The hostname this reverse record points to.
required: false
type: string
srv_priority:
description: |
Lower number means higher priority. Clients will attempt to contact
the server with the lowest-numbered priority they can reach.
required: false
type: int
srv_weight:
description: Relative weight for entries with the same priority.
required: false
type: int
srv_port:
description: SRV Port
required: false
type: int
srv_target:
description: |
The domain name of the target host or '.' if the service is decidedly
not available at this domain.
required: false
type: string
sshfp_algorithm:
description: SSHFP Algorithm
required: False
type: int
sshfp_fp_type:
description: SSHFP Fingerprint Type
required: False
type: int
sshfp_fingerprint:
description: SSHFP Fingerprint
required: False
type: string
txt_data:
description: TXT Text Data
required: false
type: string
tlsa_cert_usage:
description: TLSA Certificate Usage
required: false
type: int
tlsa_selector:
description: TLSA Selector
required: false
type: int
tlsa_matching_type:
description: TLSA Matching Type
required: false
type: int
tlsa_cert_association_data:
description: TLSA Certificate Association Data
required: false
type: string
uri_target:
description: Target Uniform Resource Identifier according to RFC 3986.
required: false
type: string
uri_priority:
description: |
Lower number means higher priority. Clients will attempt to contact
the URI with the lowest-numbered priority they can reach.
required: false
type: int
uri_weight:
description: Relative weight for entries with the same priority.
required: false
type: int
zone_name:
description: |
The DNS zone name to which DNS record needs to be managed.
Required if not provided globally.
aliases: ["dnszone"]
required: false
name:
description: The DNS record name to manage.
aliases: ["record_name"]
required: true
record_type:
description: The type of DNS record.
required: false
choices: ["A", "AAAA", "A6", "AFSDB", "CERT", "CNAME", "DLV", "DNAME",
"DS", "KX", "LOC", "MX", "NAPTR", "NS", "PTR", "SRV", "SSHFP",
"TLSA", "TXT", "URI"]
default: "A"
record_value:
description: Manage DNS record name with this values.
required: false
type: list
record_ttl:
description: Set the TTL for the record.
required: false
type: int
del_all:
description: Delete all associated records.
required: false
type: bool
a_rec:
description: Raw A record.
required: false
aliases: ["a_record"]
aaaa_rec:
description: Raw AAAA record.
required: false
aliases: ["aaaa_record"]
a6_rec:
description: Raw A6 record.
required: false
aliases: ["a6_record"]
afsdb_rec:
description: Raw AFSDB record.
required: false
aliases: ["afsdb_record"]
cert_rec:
description: Raw CERT record.
required: false
aliases: ["cert_record"]
cname_rec:
description: Raw CNAME record.
required: false
aliases: ["cname_record"]
dlv_rec:
description: Raw DLV record.
required: false
aliases: ["dlv_record"]
dname_rec:
description: Raw DNAM record.
required: false
aliases: ["dname_record"]
ds_rec:
description: Raw DS record.
required: false
aliases: ["ds_record"]
kx_rec:
description: Raw KX record.
required: false
aliases: ["kx_record"]
loc_rec:
description: Raw LOC record.
required: false
aliases: ["loc_record"]
mx_rec:
description: Raw MX record.
required: false
aliases: ["mx_record"]
naptr_rec:
description: Raw NAPTR record.
required: false
aliases: ["naptr_record"]
ns_rec:
description: Raw NS record.
required: false
aliases: ["ns_record"]
ptr_rec:
description: Raw PTR record.
required: false
aliases: ["ptr_record"]
srv_rec:
description: Raw SRV record.
required: false
aliases: ["srv_record"]
sshfp_rec:
description: Raw SSHFP record.
required: false
aliases: ["sshfp_record"]
tlsa_rec:
description: Raw TLSA record.
required: false
aliases: ["tlsa_record"]
txt_rec:
description: Raw TXT record.
required: false
aliases: ["txt_record"]
uri_rec:
description: Raw URI record.
required: false
aliases: ["uri_record"]
ip_address:
description: IP adresses for A ar AAAA.
required: false
type: string
create_reverse:
description: |
Create reverse record for A or AAAA record types.
There is no equivalent to remove reverse records.
type: bool
required: false
aliases: ["reverse"]
a_ip_address:
description: IP adresses for A records.
required: false
type: string
a_create_reverse:
description: |
Create reverse record for A records.
There is no equivalent to remove reverse records.
type: bool
required: false
aaaa_ip_address:
description: IP adresses for AAAA records.
required: false
type: string
aaaa_create_reverse:
description: |
Create reverse record for AAAA records.
There is no equivalent to remove reverse records.
type: bool
required: false
afsdb_subtype:
description: AFSDB Subtype
required: false
type: int
afsdb_hostname:
description: AFSDB Hostname
required: false
type: string
cert_type:
description: CERT Certificate Type
required: false
type: int
cert_key_tag:
description: CERT Key Tag
required: false
type: int
cert_algorithm:
description: CERT Algorithm
required: false
type: int
cert_certificate_or_crl:
description: CERT Certificate/CRL
required: false
type: string
cname_hostname:
description: A hostname which this alias hostname points to.
required: false
type: string
dlv_key_tag:
description: DS Key Tag
required: false
type: int
dlv_algorithm:
description: DLV Algorithm
required: false
type: int
dlv_digest_type:
description: DLV Digest Type
required: false
type: int
dlv_digest:
description: DLV Digest
required: false
type: string
dname_target:
description: DNAME Target
required: false
type: string
ds_key_tag:
description: DS Key Tag
required: false
type: int
ds_algorithm:
description: DS Algorithm
required: false
type: int
ds_digest_type:
description: DS Digest Type
required: false
type: int
ds_digest:
description: DS Digest
required: false
type: string
kx_preference:
description: |
Preference given to this exchanger. Lower values are more preferred.
required: false
type: int
kx_exchanger:
description: A host willing to act as a key exchanger.
required: false
type: string
loc_lat_deg:
description: LOC Degrees Latitude
required: false
type: int
loc_lat_min:
description: LOC Minutes Latitude
required: false
type: int
loc_lat_sec:
description: LOC Seconds Latitude
required: false
type: float
loc_lat_dir:
description: LOC Direction Latitude
required: false
choices: ["N", "S"]
loc_lon_deg:
description: LOC Degrees Longitude
required: false
type: int
loc_lon_min:
description: LOC Minutes Longitude
required: false
type: int
loc_lon_sec:
description: LOC Seconds Longitude
required: false
type: float
loc_lon_dir:
description: LOC Direction Longitude
required: false
choices: ["E", "W"]
loc_altitude:
description: LOC Altitude
required: false
type: float
loc_size:
description: LOC Size
required: false
type: float
loc_h_precision:
description: LOC Horizontal Precision
required: false
type: float
loc_v_precision:
description: LOC Vertical Precision
required: false
type: float
mx_preference:
description: |
Preference given to this exchanger. Lower values are more preferred.
required: false
type: int
mx_exchanger:
description: A host willing to act as a mail exchanger.
required: false
type: string
naptr_order:
description: NAPTR Order
required: false
type: int
naptr_preference:
description: NAPTR Preference
required: false
type: int
naptr_flags:
description: NAPTR Flags
required: false
type: string
naptr_service:
description: NAPTR Service
required: false
type: string
naptr_regexp:
description: NAPTR Regular Expression
required: false
type: string
naptr_replacement:
description: NAPTR Replacement
required: false
type: string
ns_hostname:
description: NS Hostname
required: false
type: string
ptr_hostname:
description: The hostname this reverse record points to.
required: false
type: string
srv_priority:
description: |
Lower number means higher priority. Clients will attempt to contact the
server with the lowest-numbered priority they can reach.
required: false
type: int
srv_weight:
description: Relative weight for entries with the same priority.
required: false
type: int
srv_port:
description: SRV Port
required: false
type: int
srv_target:
description: |
The domain name of the target host or '.' if the service is decidedly not
available at this domain.
required: false
type: string
sshfp_algorithm:
description: SSHFP Algorithm
required: false
type: int
sshfp_fp_type:
description: SSHFP Fingerprint Type
required: false
type: int
sshfp_fingerprint:
description: SSHFP Fingerprint
required: false
type: string
txt_data:
description: TXT Text Data
required: false
type: string
tlsa_cert_usage:
description: TLSA Certificate Usage
required: false
type: int
tlsa_selector:
description: TLSA Selector
required: false
type: int
tlsa_matching_type:
description: TLSA Matching Type
required: false
type: int
tlsa_cert_association_data:
description: TLSA Certificate Association Data
required: false
type: string
uri_target:
description: Target Uniform Resource Identifier according to RFC 3986.
required: false
type: string
uri_priority:
description: |
Lower number means higher priority. Clients will attempt to contact the
URI with the lowest-numbered priority they can reach.
required: false
type: int
uri_weight:
description: Relative weight for entries with the same priority.
required: false
type: int
state:
description: State to ensure
default: present
choices: ["present", "absent"]
author:
- Rafael Guterres Jeffman
"""
EXAMPLES = """
# Ensure dns record is present
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: vm-001
zone_name: example.com
record_type: 'AAAA'
record_value: '::1'
# Ensure that dns record exists with a TTL
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: example.com
record_type: 'AAAA'
record_value: '::1'
record_ttl: 300
# Ensure that dns record exists with a reverse record
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host02
zone_name: example.com
record_type: 'AAAA'
record_value: 'fd00::0002'
create_reverse: yes
# Ensure a PTR record is present
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: 5
zone_name: 2.168.192.in-addr.arpa
record_type: 'PTR'
record_value: 'internal.ipa.example.com'
# Ensure a TXT record is present
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: _kerberos
zone_name: example.com
record_type: 'TXT'
record_value: 'EXAMPLE.COM'
# Ensure a SRV record is present
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: _kerberos._udp.example.com
zone_name: example.com
record_type: 'SRV'
record_value: '10 50 88 ipa.example.com'
# Ensure an MX record is present
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: '@'
zone_name: example.com
record_type: 'MX'
record_value: '1 mailserver.example.com'
# Ensure that dns record is absent
- ipadnsrecord:
ipaadmin_password: SomeADMINpassword
name: host01
zone_name: example.com
record_type: 'AAAA'
record_value: '::1'
state: absent
"""
RETURN = """
"""
from ansible.module_utils._text import to_text
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, is_ipv4_addr, is_ipv6_addr, ipalib_errors
try:
import dns.reversename
import dns.resolver
except ImportError as _err:
MODULE_IMPORT_ERROR = str(_err)
else:
MODULE_IMPORT_ERROR = None
from ansible.module_utils import six
if six.PY3:
unicode = str
_SUPPORTED_RECORD_TYPES = [
"A", "AAAA", "A6", "AFSDB", "CERT", "CNAME", "DLV", "DNAME", "DS", "KX",
"LOC", "MX", "NAPTR", "NS", "PTR", "SRV", "SSHFP", "TLSA", "TXT", "URI"]
_RECORD_FIELDS = [
"a_rec", "aaaa_rec", "a6_rec", "afsdb_rec", "cert_rec",
"cname_rec", "dlv_rec", "dname_rec", "ds_rec", "kx_rec", "loc_rec",
"mx_rec", "naptr_rec", "ns_rec", "ptr_rec", "srv_rec", "sshfp_rec",
"tlsa_rec", "txt_rec", "uri_rec"
]
# The _PART_MAP structure maps ansible-freeipa attributes to their
# FreeIPA API counterparts. The keys are also used to obtain a list
# of all supported DNS record attributes.
_PART_MAP = {
'a_ip_address': 'a_part_ip_address',
'a_create_reverse': 'a_extra_create_reverse',
'aaaa_ip_address': 'aaaa_part_ip_address',
'aaaa_create_reverse': 'aaaa_extra_create_reverse',
'a6_data': 'a6_part_data',
'afsdb_subtype': 'afsdb_part_subtype',
'afsdb_hostname': 'afsdb_part_hostname',
'cert_type': 'cert_part_type',
'cert_key_tag': 'cert_part_key_tag',
'cert_algorithm': 'cert_part_algorithm',
'cert_certificate_or_crl': 'cert_part_certificate_or_crl',
'cname_hostname': 'cname_part_hostname',
'dlv_algorithm': 'dlv_part_algorithm',
'dlv_digest': 'dlv_part_digest',
'dlv_digest_type': 'dlv_part_digest_type',
'dlv_key_tag': 'dlv_part_key_tag',
'dname_target': 'dname_part_target',
'ds_algorithm': 'ds_part_algorithm',
'ds_digest': 'ds_part_digest',
'ds_digest_type': 'ds_part_digest_type',
'ds_key_tag': 'ds_part_key_tag',
'kx_preference': 'kx_part_preference',
'kx_exchanger': 'kx_part_exchanger',
"loc_lat_deg": "loc_part_lat_deg",
"loc_lat_min": "loc_part_lat_min",
"loc_lat_sec": "loc_part_lat_sec",
"loc_lat_dir": "loc_part_lat_dir",
"loc_lon_deg": "loc_part_lon_deg",
"loc_lon_min": "loc_part_lon_min",
"loc_lon_sec": "loc_part_lon_sec",
"loc_lon_dir": "loc_part_lon_dir",
"loc_altitude": "loc_part_altitude",
"loc_size": "loc_part_size",
"loc_h_precision": "loc_part_h_precision",
"loc_v_precision": "loc_part_v_precision",
"mx_preference": "mx_part_preference",
"mx_exchanger": 'mx_part_exchanger',
"naptr_order": "naptr_part_order",
"naptr_preference": "naptr_part_preference",
"naptr_flags": "naptr_part_flags",
"naptr_service": "naptr_part_service",
"naptr_regexp": "naptr_part_regexp",
"naptr_replacement": "naptr_part_replacement",
'ns_hostname': 'ns_part_hostname',
'ptr_hostname': 'ptr_part_hostname',
"srv_priority": "srv_part_priority",
"srv_weight": "srv_part_weight",
"srv_port": "srv_part_port",
"srv_target": "srv_part_target",
'sshfp_algorithm': 'sshfp_part_algorithm',
'sshfp_fingerprint': 'sshfp_part_fingerprint',
'sshfp_fp_type': 'sshfp_part_fp_type',
"tlsa_cert_usage": "tlsa_part_cert_usage",
"tlsa_cert_association_data": "tlsa_part_cert_association_data",
"tlsa_matching_type": "tlsa_part_matching_type",
"tlsa_selector": "tlsa_part_selector",
'txt_data': 'txt_part_data',
"uri_priority": "uri_part_priority",
"uri_target": "uri_part_target",
"uri_weight": "uri_part_weight"
}
# _RECORD_PARTS is a structure that maps the attributes that store
# the DNS record in FreeIPA API to the parts and options available
# for these records in the API.
_RECORD_PARTS = {
"arecord": ["a_part_ip_address", "a_extra_create_reverse"],
"aaaarecord": [
"aaaa_part_ip_address", "aaaa_extra_create_reverse"
],
"a6record": ["a6_part_data"],
"afsdbrecord": ['afsdb_part_subtype', 'afsdb_part_hostname'],
"certrecord": [
'cert_part_type', 'cert_part_key_tag', 'cert_part_algorithm',
'cert_part_certificate_or_crl'
],
"cnamerecord": ["cname_part_hostname"],
"dlvrecord": [
'dlv_part_key_tag', 'dlv_part_algorithm', 'dlv_part_digest_type',
'dlv_part_digest'
],
"dnamerecord": ["dname_part_target"],
"dsrecord": ['ds_part_key_tag', 'ds_part_algorithm',
'ds_part_digest_type', 'ds_part_digest'],
"kxrecord": ['kx_part_preference', 'kx_part_exchanger'],
"locrecord": [
"loc_part_lat_deg", "loc_part_lat_min", "loc_part_lat_sec",
"loc_part_lat_dir", "loc_part_lon_deg", "loc_part_lon_min",
"loc_part_lon_sec", "loc_part_lon_dir", "loc_part_altitude",
"loc_part_size", "loc_part_h_precision", "loc_part_v_precision"
],
"mxrecord": ['mx_part_preference', 'mx_part_exchanger'],
"naptrrecord": [
"naptr_part_order", "naptr_part_preference", "naptr_part_flags",
"naptr_part_service", "naptr_part_regexp", "naptr_part_replacement"
],
"nsrecord": ["ns_part_hostname"],
"ptrrecord": ["ptr_part_hostname"],
"srvrecord": [
"srv_part_priority", "srv_part_weight", "srv_part_port",
"srv_part_target",
],
"sshfprecord": [
'sshfp_part_algorithm', 'sshfp_part_fingerprint',
'sshfp_part_fp_type'
],
"tlsarecord": [
"tlsa_part_cert_usage", "tlsa_part_cert_association_data",
"tlsa_part_matching_type", "tlsa_part_selector"
],
"txtrecord": ["txt_part_data"],
"urirecord": ["uri_part_priority", "uri_part_target", "uri_part_weight"],
}
def configure_module():
"""Configure ipadnsrecord ansible module variables."""
record_spec = dict(
zone_name=dict(type='str', required=False, aliases=['dnszone']),
record_type=dict(type='str', default="A",
choices=["A", "AAAA", "A6", "AFSDB", "CERT", "CNAME",
"DLV", "DNAME", "DS", "KX", "LOC", "MX",
"NAPTR", "NS", "PTR", "SRV", "SSHFP", "TLSA",
"TXT", "URI"]),
record_value=dict(type='list', required=False),
record_ttl=dict(type='int', required=False),
del_all=dict(type='bool', required=False),
a_rec=dict(type='list', required=False, aliases=['a_record']),
aaaa_rec=dict(type='list', required=False, aliases=['aaaa_record']),
a6_rec=dict(type='list', required=False, aliases=['a6_record']),
afsdb_rec=dict(type='list', required=False, aliases=['afsdb_record']),
cert_rec=dict(type='list', required=False, aliases=['cert_record']),
cname_rec=dict(type='list', required=False, aliases=['cname_record']),
dlv_rec=dict(type='list', required=False, aliases=['dlv_record']),
dname_rec=dict(type='list', required=False, aliases=['dname_record']),
ds_rec=dict(type='list', required=False, aliases=['ds_record']),
kx_rec=dict(type='list', required=False, aliases=['kx_record']),
loc_rec=dict(type='list', required=False, aliases=['loc_record']),
mx_rec=dict(type='list', required=False, aliases=['mx_record']),
naptr_rec=dict(type='list', required=False, aliases=['naptr_record']),
ns_rec=dict(type='list', required=False, aliases=['ns_record']),
ptr_rec=dict(type='list', required=False, aliases=['ptr_record']),
srv_rec=dict(type='list', required=False, aliases=['srv_record']),
sshfp_rec=dict(type='list', required=False, aliases=['sshfp_record']),
tlsa_rec=dict(type='list', required=False, aliases=['tlsa_record']),
txt_rec=dict(type='list', required=False, aliases=['txt_record']),
uri_rec=dict(type='list', required=False, aliases=['uri_record']),
ip_address=dict(type='str', required=False),
create_reverse=dict(type='bool', required=False, aliases=['reverse']),
a_ip_address=dict(type='str', required=False),
a_create_reverse=dict(type='bool', required=False),
aaaa_ip_address=dict(type='str', required=False),
aaaa_create_reverse=dict(type='bool', required=False),
a6_data=dict(type='str', required=False),
afsdb_subtype=dict(type='int', required=False),
afsdb_hostname=dict(type='str', required=False),
cert_type=dict(type='int', required=False),
cert_key_tag=dict(type='int', required=False),
cert_algorithm=dict(type='int', required=False),
cert_certificate_or_crl=dict(type='str', required=False),
cname_hostname=dict(type='str', required=False),
dlv_key_tag=dict(type='int', required=False),
dlv_algorithm=dict(type='int', required=False),
dlv_digest_type=dict(type='int', required=False),
dlv_digest=dict(type='str', required=False),
dname_target=dict(type='str', required=False),
ds_key_tag=dict(type='int', required=False),
ds_algorithm=dict(type='int', required=False),
ds_digest_type=dict(type='int', required=False),
ds_digest=dict(type='str', required=False),
kx_preference=dict(type='int', required=False),
kx_exchanger=dict(type='str', required=False),
loc_lat_deg=dict(type='int', required=False),
loc_lat_min=dict(type='int', required=False),
loc_lat_sec=dict(type='float', required=False),
loc_lat_dir=dict(type='str', required=False),
loc_lon_deg=dict(type='int', required=False),
loc_lon_min=dict(type='int', required=False),
loc_lon_sec=dict(type='float', required=False),
loc_lon_dir=dict(type='str', required=False),
loc_altitude=dict(type='float', required=False),
loc_size=dict(type='float', required=False),
loc_h_precision=dict(type='float', required=False),
loc_v_precision=dict(type='float', required=False),
mx_preference=dict(type='int', required=False),
mx_exchanger=dict(type='str', required=False),
naptr_order=dict(type='int', required=False),
naptr_preference=dict(type='int', required=False),
naptr_flags=dict(type='str', required=False),
naptr_service=dict(type='str', required=False),
naptr_regexp=dict(type='str', required=False),
naptr_replacement=dict(type='str', required=False),
ns_hostname=dict(type='str', required=False),
ptr_hostname=dict(type='str', required=False),
srv_priority=dict(type='int', required=False),
srv_weight=dict(type='int', required=False),
srv_port=dict(type='int', required=False),
srv_target=dict(type='str', required=False),
sshfp_algorithm=dict(type='int', required=False),
sshfp_fingerprint=dict(type='str', required=False),
sshfp_fp_type=dict(type='int', required=False),
tlsa_cert_usage=dict(type='int', required=False),
tlsa_cert_association_data=dict(type='str', required=False),
tlsa_matching_type=dict(type='int', required=False),
tlsa_selector=dict(type='int', required=False),
txt_data=dict(type='str', required=False),
uri_priority=dict(type='int', required=False),
uri_target=dict(type='str', required=False),
uri_weight=dict(type='int', required=False),
)
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# general
name=dict(type="list", aliases=["record_name"], default=None,
required=False),
records=dict(type="list",
default=None,
options=dict(
# Here name is a simple string
name=dict(type='str', required=True,
aliases=['record_name']),
**record_spec),
),
# general
state=dict(type="str", default="present",
choices=["present", "absent", "disabled"]),
# Add record specific parameters for simple use case
**record_spec
),
mutually_exclusive=[["name", "records"], ['record_value', 'del_all']],
required_one_of=[["name", "records"]],
supports_check_mode=True,
)
ansible_module._ansible_debug = True
if MODULE_IMPORT_ERROR is not None:
ansible_module.fail_json(msg=MODULE_IMPORT_ERROR)
return ansible_module
def find_dnsrecord(module, dnszone, name):
"""Find a DNS record based on its name (idnsname)."""
_args = {
"all": True,
"idnsname": to_text(name),
}
try:
_result = module.ipa_command(
"dnsrecord_show", to_text(dnszone), _args)
except ipalib_errors.NotFound:
return None
return _result["result"]
def check_parameters(module, state, zone_name, record):
"""Check if parameters are correct."""
if zone_name is None:
module.fail_json(msg="Msssing required argument: zone_name")
record_type = record.get('record_type', None)
record_value = record.get('record_value', None)
if record_type is not None:
if record_type not in _SUPPORTED_RECORD_TYPES:
module.fail_json(
msg="Record Type '%s' is not supported." % record_type)
# has_record is "True" if the playbook has set any of the full record
# attributes (*record or *_rec).
has_record = any(
(rec in record) or (("%sord" % rec) in record)
for rec in _RECORD_FIELDS
)
# has_part_record is "True" if the playbook has set any of the
# record field attributes.
has_part_record = any(record.get(rec, None) for rec in _PART_MAP)
# some attributes in the playbook may have a special meaning,
# like "ip_address", which is used for either arecord or aaaarecord,
# and has_special is true if any of these attributes is set on
# on the playbook.
special_list = ['ip_address']
has_special = any(record.get(rec, None) for rec in special_list)
invalid = []
if state == 'present':
if has_record or has_part_record or has_special:
if record_value:
module.fail_json(
msg="Cannot use record data with `record_value`.")
elif not record_value:
module.fail_json(msg="No record data provided.")
invalid = ['del_all']
if state == 'absent':
del_all = record.get('del_all', None)
if record_value:
if has_record or has_part_record or del_all:
module.fail_json(
msg="Cannot use record data with `record_value`.")
elif not (has_record or has_part_record or del_all):
module.fail_json(
msg="Either a record description or `del_all` is required.")
invalid = list(_PART_MAP.keys())
invalid.extend(['create_reverse', 'dns_ttl'])
module.params_fail_used_invalid(invalid, state)
def get_entry_from_module(module, name):
"""Create an entry dict from attributes in module."""
attrs = [
'del_all', 'zone_name', 'record_type', 'record_value', 'record_ttl',
"ip_address", "create_reverse"
]
entry = {'name': name}
for key_set in [_RECORD_FIELDS, _PART_MAP, attrs]:
entry.update({
key: module.params_get(key)
for key in key_set
if module.params_get(key) is not None
})
return entry
def create_reverse_ip_record(module, zone_name, name, ips):
"""Create a reverse record for an IP (PTR record)."""
_cmds = []
for address in ips:
reverse_ip = dns.reversename.from_address(address)
reverse_zone = dns.resolver.zone_for_name(reverse_ip)
reverse_host = to_text(reverse_ip).replace(".%s" % reverse_zone, '')
rev_find = find_dnsrecord(module, reverse_zone, reverse_host)
if rev_find is None:
rev_args = {
'idnsname': to_text(reverse_host),
"ptrrecord": "%s.%s" % (name, zone_name)
}
_cmds.append([to_text(reverse_zone), 'dnsrecord_add', rev_args])
return _cmds
def ensure_data_is_list(data):
"""Ensure data is represented as a list."""
return data if isinstance(data, list) else [data]
def gen_args(entry):
"""Generate IPA API arguments for a given `entry`."""
args = {'idnsname': to_text(entry['name'])}
if 'del_all' in entry:
args['del_all'] = entry['del_all']
record_value = entry.get('record_value', None)
if record_value is not None:
record_type = entry['record_type']
rec = "{}record".format(record_type.lower())
args[rec] = ensure_data_is_list(record_value)
else:
for field in _RECORD_FIELDS:
record_value = entry.get(field) or entry.get("%sord" % field)
if record_value is not None:
record_type = field.split('_')[0]
rec = "{}record".format(record_type.lower())
args[rec] = ensure_data_is_list(record_value)
records = {
key: rec for key, rec in _PART_MAP.items() if key in entry
}
for key, rec in records.items():
args[rec] = entry[key]
if 'ip_address' in entry:
ip_address = entry['ip_address']
if is_ipv4_addr(ip_address):
args['a_part_ip_address'] = ip_address
if is_ipv6_addr(ip_address):
args['aaaa_part_ip_address'] = ip_address
if entry.get('create_reverse', False):
if 'a_part_ip_address' in args or 'arecord' in args:
args['a_extra_create_reverse'] = True
if 'aaaa_part_ip_address' in args or 'aaaarecord' in args:
args['aaaa_extra_create_reverse'] = True
if 'record_ttl' in entry:
args['dnsttl'] = entry['record_ttl']
return args
def define_commands_for_present_state(module, zone_name, entry, res_find):
"""Define commnads for `state: present`."""
_commands = []
name = to_text(entry['name'])
args = gen_args(entry)
existing = find_dnsrecord(module, zone_name, name)
for record, fields in _RECORD_PARTS.items():
part_fields = [f for f in fields if f in args]
if part_fields and record in args:
record_change_request = True
break
else:
record_change_request = False
if res_find is None and not record_change_request:
_commands.append([zone_name, 'dnsrecord_add', args])
else:
# Create reverse records for existing records
for ipv in ['a', 'aaaa']:
record = ('%srecord' % ipv)
if record in args and ('%s_extra_create_reverse' % ipv) in args:
cmds = create_reverse_ip_record(
module, zone_name, name, args[record])
_commands.extend(cmds)
del args['%s_extra_create_reverse' % ipv]
for record, fields in _RECORD_PARTS.items():
part_fields = [f for f in fields if f in args]
if part_fields:
if record in args:
# user wants to update record.
if len(args[record]) > 1:
module.fail_json(msg="Cannot modify multiple records "
"of the same type at once.")
mod_record = args[record][0]
if existing is None:
module.fail_json(msg="`%s` not found." % record)
else:
# update DNS record
_args = {k: args[k] for k in part_fields if k in args}
_args["idnsname"] = to_text(args["idnsname"])
_args[record] = mod_record
if 'dns_ttl' in args:
_args['dns_ttl'] = args['dns_ttl']
_commands.append([zone_name, 'dnsrecord_mod', _args])
# remove record from args, as it will not be used again.
del args[record]
else:
_args = {k: args[k] for k in part_fields if k in args}
_args['idnsname'] = name
_commands.append([zone_name, 'dnsrecord_add', _args])
# clean used fields from args
for f in part_fields: # pylint: disable=invalid-name
if f in args:
del args[f]
else:
if record in args:
add_list = []
for value in args[record]:
if (
res_find is None
or record not in res_find
or value not in res_find[record]
):
add_list.append(value)
if add_list:
args[record] = add_list
_commands.append([zone_name, 'dnsrecord_add', args])
return _commands
def define_commands_for_absent_state(module, zone_name, entry, res_find):
"""Define commands for `state: absent`."""
_commands = []
if res_find is None:
return []
args = gen_args(entry)
del_all = args.get('del_all', False)
records_to_delete = {k: v for k, v in args.items() if k.endswith('record')}
if del_all and records_to_delete:
module.fail_json(msg="Cannot use del_all and record together.")
if not del_all:
delete_records = False
for record, values in records_to_delete.items():
del_list = []
if record in res_find:
for value in values:
for rec_found in res_find[record]:
if rec_found == value:
del_list.append(value)
if del_list:
args[record] = del_list
delete_records = True
if delete_records:
_commands.append([zone_name, 'dnsrecord_del', args])
else:
_commands.append([zone_name, 'dnsrecord_del', args])
return _commands
# pylint: disable=unused-argument
def exception_handler(module, ex):
if isinstance(ex, (ipalib_errors.EmptyModlist,
ipalib_errors.DuplicateEntry)):
return True
return False
def main():
"""Execute DNS record playbook."""
ansible_module = configure_module()
global_zone_name = ansible_module.params_get("zone_name")
names = ansible_module.params_get("name")
records = ansible_module.params_get("records")
state = ansible_module.params_get("state")
# Check parameters
if (names is None or len(names) < 1) and \
(records is None or len(records) < 1):
ansible_module.fail_json(msg="One of name and records is required")
if state == "present":
if names is not None and len(names) != 1:
ansible_module.fail_json(
msg="Only one record can be added at a time.")
if records is not None:
names = records
# Init
changed = False
exit_args = {}
# Connect to IPA API
with ansible_module.ipa_connect():
commands = []
for record in names:
if isinstance(record, dict):
# ensure name is a string
zone_name = record.get("zone_name", global_zone_name)
name = record['name'] = str(record['name'])
entry = record
else:
zone_name = global_zone_name
name = record
entry = get_entry_from_module(ansible_module, name)
check_parameters(ansible_module, state, zone_name, entry)
res_find = find_dnsrecord(ansible_module, zone_name, name)
if state == 'present':
cmds = define_commands_for_present_state(
ansible_module, zone_name, entry, res_find)
elif state == 'absent':
cmds = define_commands_for_absent_state(
ansible_module, zone_name, entry, res_find)
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
if cmds:
commands.extend(cmds)
# Execute commands
changed = ansible_module.execute_ipa_commands(
commands, exception_handler=exception_handler)
# Done
ansible_module.exit_json(changed=changed, host=exit_args)
if __name__ == "__main__":
main()