mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-02-04 16:01:55 +00:00
remove required=false from docs (#11055)
(cherry picked from commit a9a4f89033)
Co-authored-by: Alexei Znamensky <103110+russoz@users.noreply.github.com>
392 lines
13 KiB
Python
392 lines
13 KiB
Python
#!/usr/bin/python
|
|
#
|
|
# Copyright (c) 2018, Simon Weald <ansible@simonweald.com>
|
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
from __future__ import annotations
|
|
|
|
DOCUMENTATION = r"""
|
|
module: memset_zone_record
|
|
author: "Simon Weald (@glitchcrab)"
|
|
short_description: Create and delete records in Memset DNS zones
|
|
notes:
|
|
- Zones can be thought of as a logical group of domains, all of which share the same DNS records (in other words they point
|
|
to the same IP). An API key generated using the Memset customer control panel is needed with the following minimum scope
|
|
- C(dns.zone_create), C(dns.zone_delete), C(dns.zone_list).
|
|
- Currently this module can only create one DNS record at a time. Multiple records should be created using C(loop).
|
|
description:
|
|
- Manage DNS records in a Memset account.
|
|
extends_documentation_fragment:
|
|
- community.general.attributes
|
|
attributes:
|
|
check_mode:
|
|
support: full
|
|
diff_mode:
|
|
support: none
|
|
options:
|
|
state:
|
|
default: present
|
|
description:
|
|
- Indicates desired state of resource.
|
|
type: str
|
|
choices: [absent, present]
|
|
api_key:
|
|
required: true
|
|
description:
|
|
- The API key obtained from the Memset control panel.
|
|
type: str
|
|
address:
|
|
required: true
|
|
description:
|
|
- The address for this record (can be IP or text string depending on record type).
|
|
type: str
|
|
aliases: [ip, data]
|
|
priority:
|
|
description:
|
|
- C(SRV) and C(TXT) record priority, in the range 0 > 999 (inclusive).
|
|
type: int
|
|
default: 0
|
|
record:
|
|
description:
|
|
- The subdomain to create.
|
|
type: str
|
|
default: ''
|
|
type:
|
|
required: true
|
|
description:
|
|
- The type of DNS record to create.
|
|
choices: [A, AAAA, CNAME, MX, NS, SRV, TXT]
|
|
type: str
|
|
relative:
|
|
type: bool
|
|
default: false
|
|
description:
|
|
- If set then the current domain is added onto the address field for C(CNAME), C(MX), C(NS) and C(SRV)record types.
|
|
ttl:
|
|
description:
|
|
- The record's TTL in seconds (will inherit zone's TTL if not explicitly set). This must be a valid int from
|
|
U(https://www.memset.com/apidocs/methods_dns.html#dns.zone_record_create).
|
|
default: 0
|
|
choices: [0, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200, 86400]
|
|
type: int
|
|
zone:
|
|
required: true
|
|
description:
|
|
- The name of the zone to which to add the record to.
|
|
type: str
|
|
"""
|
|
|
|
EXAMPLES = r"""
|
|
# Create DNS record for www.domain.com
|
|
- name: Create DNS record
|
|
community.general.memset_zone_record:
|
|
api_key: dcf089a2896940da9ffefb307ef49ccd
|
|
state: present
|
|
zone: domain.com
|
|
type: A
|
|
record: www
|
|
address: 1.2.3.4
|
|
ttl: 300
|
|
relative: false
|
|
delegate_to: localhost
|
|
|
|
# create an SPF record for domain.com
|
|
- name: Create SPF record for domain.com
|
|
community.general.memset_zone_record:
|
|
api_key: dcf089a2896940da9ffefb307ef49ccd
|
|
state: present
|
|
zone: domain.com
|
|
type: TXT
|
|
address: "v=spf1 +a +mx +ip4:a1.2.3.4 ?all"
|
|
delegate_to: localhost
|
|
|
|
# create multiple DNS records
|
|
- name: Create multiple DNS records
|
|
community.general.memset_zone_record:
|
|
api_key: dcf089a2896940da9ffefb307ef49ccd
|
|
zone: "{{ item.zone }}"
|
|
type: "{{ item.type }}"
|
|
record: "{{ item.record }}"
|
|
address: "{{ item.address }}"
|
|
delegate_to: localhost
|
|
with_items:
|
|
- {'zone': 'domain1.com', 'type': 'A', 'record': 'www', 'address': '1.2.3.4'}
|
|
- {'zone': 'domain2.com', 'type': 'A', 'record': 'mail', 'address': '4.3.2.1'}
|
|
"""
|
|
|
|
RETURN = r"""
|
|
memset_api:
|
|
description: Record info from the Memset API.
|
|
returned: when state == present
|
|
type: complex
|
|
contains:
|
|
address:
|
|
description: Record content (may be an IP, string or blank depending on record type).
|
|
returned: always
|
|
type: str
|
|
sample: 1.1.1.1
|
|
id:
|
|
description: Record ID.
|
|
returned: always
|
|
type: str
|
|
sample: "b0bb1ce851aeea6feeb2dc32fe83bf9c"
|
|
priority:
|
|
description: Priority for C(MX) and C(SRV) records.
|
|
returned: always
|
|
type: int
|
|
sample: 10
|
|
record:
|
|
description: Name of record.
|
|
returned: always
|
|
type: str
|
|
sample: "www"
|
|
relative:
|
|
description: Adds the current domain onto the address field for C(CNAME), C(MX), C(NS) and C(SRV) types.
|
|
returned: always
|
|
type: bool
|
|
sample: false
|
|
ttl:
|
|
description: Record TTL.
|
|
returned: always
|
|
type: int
|
|
sample: 10
|
|
type:
|
|
description: Record type.
|
|
returned: always
|
|
type: str
|
|
sample: AAAA
|
|
zone_id:
|
|
description: Zone ID.
|
|
returned: always
|
|
type: str
|
|
sample: "b0bb1ce851aeea6feeb2dc32fe83bf9c"
|
|
"""
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible_collections.community.general.plugins.module_utils.memset import get_zone_id
|
|
from ansible_collections.community.general.plugins.module_utils.memset import memset_api_call
|
|
|
|
|
|
def api_validation(args=None):
|
|
"""
|
|
Perform some validation which will be enforced by Memset's API (see:
|
|
https://www.memset.com/apidocs/methods_dns.html#dns.zone_record_create)
|
|
"""
|
|
failed_validation = False
|
|
error = None
|
|
|
|
# priority can only be integer 0 > 999
|
|
if not 0 <= args["priority"] <= 999:
|
|
failed_validation = True
|
|
error = "Priority must be in the range 0 > 999 (inclusive)."
|
|
# data value must be max 250 chars
|
|
if len(args["address"]) > 250:
|
|
failed_validation = True
|
|
error = "Address must be less than 250 characters in length."
|
|
# record value must be max 250 chars
|
|
if args["record"]:
|
|
if len(args["record"]) > 63:
|
|
failed_validation = True
|
|
error = "Record must be less than 63 characters in length."
|
|
# relative isn't used for all record types
|
|
if args["relative"]:
|
|
if args["type"] not in ["CNAME", "MX", "NS", "SRV"]:
|
|
failed_validation = True
|
|
error = "Relative is only valid for CNAME, MX, NS and SRV record types."
|
|
# if any of the above failed then fail early
|
|
if failed_validation:
|
|
module.fail_json(failed=True, msg=error)
|
|
|
|
|
|
def create_zone_record(args=None, zone_id=None, records=None, payload=None):
|
|
"""
|
|
Sanity checking has already occurred prior to this function being
|
|
called, so we can go ahead and either create or update the record.
|
|
As defaults are defined for all values in the argument_spec, this
|
|
may cause some changes to occur as the defaults are enforced (if
|
|
the user has only configured required variables).
|
|
"""
|
|
has_changed, has_failed = False, False
|
|
msg, memset_api = None, None
|
|
|
|
# assemble the new record.
|
|
new_record = dict()
|
|
new_record["zone_id"] = zone_id
|
|
for arg in ["priority", "address", "relative", "record", "ttl", "type"]:
|
|
new_record[arg] = args[arg]
|
|
|
|
# if we have any matches, update them.
|
|
if records:
|
|
for zone_record in records:
|
|
# record exists, add ID to payload.
|
|
new_record["id"] = zone_record["id"]
|
|
if zone_record == new_record:
|
|
# nothing to do; record is already correct so we populate
|
|
# the return var with the existing record's details.
|
|
memset_api = zone_record
|
|
return has_changed, has_failed, memset_api, msg
|
|
else:
|
|
# merge dicts ensuring we change any updated values
|
|
payload = zone_record.copy()
|
|
payload.update(new_record)
|
|
api_method = "dns.zone_record_update"
|
|
if args["check_mode"]:
|
|
has_changed = True
|
|
# return the new record to the user in the returned var.
|
|
memset_api = new_record
|
|
return has_changed, has_failed, memset_api, msg
|
|
has_failed, msg, response = memset_api_call(
|
|
api_key=args["api_key"], api_method=api_method, payload=payload
|
|
)
|
|
if not has_failed:
|
|
has_changed = True
|
|
memset_api = new_record
|
|
# empty msg as we don't want to return a boatload of json to the user.
|
|
msg = None
|
|
else:
|
|
# no record found, so we need to create it
|
|
api_method = "dns.zone_record_create"
|
|
payload = new_record
|
|
if args["check_mode"]:
|
|
has_changed = True
|
|
# populate the return var with the new record's details.
|
|
memset_api = new_record
|
|
return has_changed, has_failed, memset_api, msg
|
|
has_failed, msg, response = memset_api_call(api_key=args["api_key"], api_method=api_method, payload=payload)
|
|
if not has_failed:
|
|
has_changed = True
|
|
memset_api = new_record
|
|
# empty msg as we don't want to return a boatload of json to the user.
|
|
msg = None
|
|
|
|
return has_changed, has_failed, memset_api, msg
|
|
|
|
|
|
def delete_zone_record(args=None, records=None, payload=None):
|
|
"""
|
|
Matching records can be cleanly deleted without affecting other
|
|
resource types, so this is pretty simple to achieve.
|
|
"""
|
|
has_changed, has_failed = False, False
|
|
msg, memset_api = None, None
|
|
|
|
# if we have any matches, delete them.
|
|
if records:
|
|
for zone_record in records:
|
|
if args["check_mode"]:
|
|
has_changed = True
|
|
return has_changed, has_failed, memset_api, msg
|
|
payload["id"] = zone_record["id"]
|
|
api_method = "dns.zone_record_delete"
|
|
has_failed, msg, response = memset_api_call(api_key=args["api_key"], api_method=api_method, payload=payload)
|
|
if not has_failed:
|
|
has_changed = True
|
|
memset_api = zone_record
|
|
# empty msg as we don't want to return a boatload of json to the user.
|
|
msg = None
|
|
|
|
return has_changed, has_failed, memset_api, msg
|
|
|
|
|
|
def create_or_delete(args=None):
|
|
"""
|
|
We need to perform some initial sanity checking and also look
|
|
up required info before handing it off to create or delete functions.
|
|
Check mode is integrated into the create or delete functions.
|
|
"""
|
|
has_failed, has_changed = False, False
|
|
msg, memset_api, stderr = None, None, None
|
|
retvals, payload = dict(), dict()
|
|
|
|
# get the zones and check if the relevant zone exists.
|
|
api_method = "dns.zone_list"
|
|
_has_failed, msg, response = memset_api_call(api_key=args["api_key"], api_method=api_method)
|
|
|
|
if _has_failed:
|
|
# this is the first time the API is called; incorrect credentials will
|
|
# manifest themselves at this point so we need to ensure the user is
|
|
# informed of the reason.
|
|
retvals["failed"] = _has_failed
|
|
retvals["msg"] = msg
|
|
if response.status_code is not None:
|
|
retvals["stderr"] = f"API returned an error: {response.status_code}"
|
|
else:
|
|
retvals["stderr"] = response.stderr
|
|
return retvals
|
|
|
|
zone_exists, _msg, counter, zone_id = get_zone_id(zone_name=args["zone"], current_zones=response.json())
|
|
|
|
if not zone_exists:
|
|
has_failed = True
|
|
if counter == 0:
|
|
stderr = f"DNS zone {args['zone']} does not exist."
|
|
elif counter > 1:
|
|
stderr = f"{args['zone']} matches multiple zones."
|
|
retvals["failed"] = has_failed
|
|
retvals["msg"] = stderr
|
|
retvals["stderr"] = stderr
|
|
return retvals
|
|
|
|
# get a list of all records ( as we can't limit records by zone)
|
|
api_method = "dns.zone_record_list"
|
|
_has_failed, _msg, response = memset_api_call(api_key=args["api_key"], api_method=api_method)
|
|
|
|
# find any matching records
|
|
records = [
|
|
record
|
|
for record in response.json()
|
|
if record["zone_id"] == zone_id and record["record"] == args["record"] and record["type"] == args["type"]
|
|
]
|
|
|
|
if args["state"] == "present":
|
|
has_changed, has_failed, memset_api, msg = create_zone_record(
|
|
args=args, zone_id=zone_id, records=records, payload=payload
|
|
)
|
|
|
|
if args["state"] == "absent":
|
|
has_changed, has_failed, memset_api, msg = delete_zone_record(args=args, records=records, payload=payload)
|
|
|
|
retvals["changed"] = has_changed
|
|
retvals["failed"] = has_failed
|
|
for val in ["msg", "stderr", "memset_api"]:
|
|
if val is not None:
|
|
retvals[val] = eval(val)
|
|
|
|
return retvals
|
|
|
|
|
|
def main():
|
|
global module
|
|
module = AnsibleModule(
|
|
argument_spec=dict(
|
|
state=dict(default="present", choices=["present", "absent"], type="str"),
|
|
api_key=dict(required=True, type="str", no_log=True),
|
|
zone=dict(required=True, type="str"),
|
|
type=dict(required=True, choices=["A", "AAAA", "CNAME", "MX", "NS", "SRV", "TXT"], type="str"),
|
|
address=dict(required=True, aliases=["ip", "data"], type="str"),
|
|
record=dict(default="", type="str"),
|
|
ttl=dict(default=0, choices=[0, 300, 600, 900, 1800, 3600, 7200, 10800, 21600, 43200, 86400], type="int"),
|
|
priority=dict(default=0, type="int"),
|
|
relative=dict(default=False, type="bool"),
|
|
),
|
|
supports_check_mode=True,
|
|
)
|
|
|
|
# populate the dict with the user-provided vars.
|
|
args = dict(module.params)
|
|
args["check_mode"] = module.check_mode
|
|
|
|
# perform some Memset API-specific validation
|
|
api_validation(args=args)
|
|
|
|
retvals = create_or_delete(args)
|
|
|
|
if retvals["failed"]:
|
|
module.fail_json(**retvals)
|
|
else:
|
|
module.exit_json(**retvals)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|