2020-08-18 21:44:49 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""Module to control corenetworks DNS API."""
|
|
|
|
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
__metaclass__ = type
|
|
|
|
ANSIBLE_METADATA = {"metadata_version": "1.1", "status": ["preview"], "supported_by": "community"}
|
|
|
|
|
|
|
|
DOCUMENTATION = r"""
|
|
|
|
---
|
|
|
|
module: corenetworks_dns
|
|
|
|
short_description: Interface with the DNS API of core-networks.de
|
|
|
|
description:
|
|
|
|
- "Manages DNS zones and records via the core networks API, see the docs: U(https://beta.api.core-networks.de/doc/)."
|
|
|
|
options:
|
|
|
|
api_user:
|
|
|
|
description:
|
|
|
|
- Account API username. If omitted, the environment variables C(CN_API_USER) and C(CN_API_PASSWORD) will be looked for.
|
|
|
|
- You should prefere to use `api_token` or the `corenetworks_token` module to create one to prevent running into rate limits.
|
|
|
|
type: str
|
|
|
|
api_password:
|
|
|
|
description:
|
|
|
|
- Account API password.
|
|
|
|
type: str
|
|
|
|
api_token:
|
|
|
|
description:
|
|
|
|
- Account API token.
|
|
|
|
type: str
|
|
|
|
zone:
|
|
|
|
description:
|
|
|
|
- The name of the Zone to work with (e.g. "example.com").
|
|
|
|
- The Zone must already exist.
|
|
|
|
zone:
|
|
|
|
type: str
|
|
|
|
required: true
|
|
|
|
aliases: [ domain ]
|
|
|
|
record:
|
|
|
|
description:
|
|
|
|
- Used record relative to the given zone.
|
|
|
|
- Default is C(@) (e.g. the zone name).
|
|
|
|
type: str
|
|
|
|
default: "@"
|
|
|
|
aliases: [ name ]
|
|
|
|
type:
|
|
|
|
description:
|
|
|
|
- The type of DNS record to create.
|
|
|
|
choices: [ "A", "ALIAS", "CNAME", "MX", "SPF", "URL", "TXT", "NS", "SRV", "NAPTR", "PTR", "AAAA", "SSHFP", "HINFO", "POOL" ]
|
|
|
|
type: str
|
|
|
|
ttl:
|
|
|
|
description:
|
|
|
|
- The TTL to give the new record in seconds.
|
|
|
|
default: 3600
|
|
|
|
type: int
|
|
|
|
value:
|
|
|
|
description:
|
|
|
|
- Record value.
|
|
|
|
- Must be specified when trying to ensure a record exists.
|
|
|
|
type: str
|
|
|
|
solo:
|
|
|
|
description:
|
|
|
|
- Whether the record should be the only one for that record type and record name.
|
|
|
|
- Only use with C(state=present).
|
|
|
|
- This will delete all other records with the same record name and type.
|
|
|
|
type: bool
|
2021-02-19 12:38:30 +00:00
|
|
|
throttle:
|
|
|
|
description:
|
|
|
|
- Throttle API calls in seconds.
|
|
|
|
default: 2
|
|
|
|
type: int
|
2020-08-18 21:44:49 +00:00
|
|
|
state:
|
|
|
|
description:
|
|
|
|
- whether the record should exist or not
|
|
|
|
choices: [ "present", "absent" ]
|
|
|
|
default: present
|
|
|
|
type: str
|
|
|
|
requirements:
|
|
|
|
- "corenetworks >= 0.1.4"
|
|
|
|
author: "Robert Kaussow (@xoxys)"
|
|
|
|
""" # noqa
|
|
|
|
|
|
|
|
EXAMPLES = """
|
|
|
|
- name: Create a test.my.com A record to point to 127.0.0.1
|
|
|
|
corenetworks_dns:
|
|
|
|
zone: my.com
|
|
|
|
record: test
|
|
|
|
type: A
|
|
|
|
value: 127.0.0.1
|
|
|
|
delegate_to: localhost
|
|
|
|
register: record
|
|
|
|
|
|
|
|
- name: Create a my.com CNAME record to example.com
|
|
|
|
corenetworks_dns:
|
|
|
|
zone: my.com
|
|
|
|
type: CNAME
|
|
|
|
value: example.com
|
|
|
|
state: present
|
|
|
|
delegate_to: localhost
|
|
|
|
|
|
|
|
- name: Change TTL value for a record
|
|
|
|
corenetworks_dns:
|
|
|
|
zone: my.com
|
|
|
|
type: CNAME
|
|
|
|
value: example.com
|
|
|
|
ttl: 600
|
|
|
|
state: present
|
|
|
|
delegate_to: localhost
|
|
|
|
|
|
|
|
- name: Delete the record
|
|
|
|
corenetworks_dns:
|
|
|
|
zone: my.com
|
|
|
|
type: CNAME
|
|
|
|
value: example.com
|
|
|
|
state: absent
|
|
|
|
delegate_to: localhost
|
|
|
|
"""
|
|
|
|
|
|
|
|
RETURN = r"""# """
|
|
|
|
|
|
|
|
import copy
|
2021-02-19 12:38:30 +00:00
|
|
|
import time
|
2020-08-18 21:44:49 +00:00
|
|
|
import traceback
|
|
|
|
|
|
|
|
CORENETWORKS_IMP_ERR = None
|
|
|
|
try:
|
|
|
|
from corenetworks import CoreNetworks
|
|
|
|
from corenetworks.exceptions import CoreNetworksException
|
|
|
|
HAS_CORENETWORKS = True
|
|
|
|
except ImportError:
|
|
|
|
CORENETWORKS_IMP_ERR = traceback.format_exc()
|
|
|
|
HAS_CORENETWORKS = False
|
|
|
|
|
|
|
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
|
|
|
|
|
|
|
|
|
|
|
def delete_records(client, module, zone, params, is_solo=False):
|
|
|
|
changed = False
|
|
|
|
|
|
|
|
search = copy.deepcopy(params)
|
|
|
|
if is_solo:
|
|
|
|
search.pop("data", None)
|
|
|
|
search.pop("ttl", None)
|
|
|
|
|
|
|
|
records = client.records(zone, params=search)
|
|
|
|
|
|
|
|
for r in records:
|
|
|
|
r["ttl"] = int(r["ttl"])
|
|
|
|
|
|
|
|
if is_solo:
|
|
|
|
if not (r["data"] == params["data"] and r["ttl"] == params["ttl"]):
|
|
|
|
changed = True
|
|
|
|
if not module.check_mode:
|
|
|
|
client.delete_record(zone, r)
|
|
|
|
else:
|
|
|
|
changed = True
|
|
|
|
if not module.check_mode:
|
|
|
|
client.delete_record(zone, r)
|
|
|
|
|
|
|
|
return changed
|
|
|
|
|
|
|
|
|
|
|
|
def add_record(client, module, zone, params):
|
|
|
|
changed = False
|
|
|
|
result = []
|
|
|
|
records = client.records(zone, params=params)
|
|
|
|
|
|
|
|
if len(records) > 1:
|
|
|
|
module.fail_json(
|
|
|
|
msg="More than one record already exists for the given attributes. "
|
|
|
|
"That should be impossible, please open an issue!"
|
|
|
|
)
|
|
|
|
|
|
|
|
if len(records) == 0:
|
|
|
|
changed = True
|
|
|
|
if not module.check_mode:
|
|
|
|
result = client.add_record(zone, params=params)
|
|
|
|
|
|
|
|
return result, changed
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
module = AnsibleModule(
|
|
|
|
argument_spec=dict(
|
|
|
|
api_user=dict(type="str"),
|
|
|
|
api_password=dict(type="str", no_log=True),
|
|
|
|
api_token=dict(type="str", no_log=True),
|
|
|
|
zone=dict(type="str", required=True, aliases=["domain"]),
|
|
|
|
record=dict(type="str", default="@", aliases=["name"]),
|
|
|
|
type=dict(
|
|
|
|
type="str",
|
|
|
|
choices=[
|
|
|
|
"A", "ALIAS", "CNAME", "MX", "SPF", "URL", "TXT", "NS", "SRV", "NAPTR", "PTR",
|
|
|
|
"AAAA", "SSHFP", "HINFO", "POOL"
|
|
|
|
]
|
|
|
|
),
|
|
|
|
ttl=dict(type="int", default=3600),
|
|
|
|
value=dict(type="str"),
|
|
|
|
solo=dict(type="bool", default=False),
|
|
|
|
state=dict(type="str", choices=["present", "absent"], default="present"),
|
|
|
|
),
|
|
|
|
required_together=[["record", "value"]],
|
|
|
|
supports_check_mode=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
if not HAS_CORENETWORKS:
|
|
|
|
module.fail_json(msg=missing_required_lib("corenetworks"), exception=CORENETWORKS_IMP_ERR)
|
|
|
|
|
|
|
|
api_user = module.params.get("api_user")
|
|
|
|
api_password = module.params.get("api_password")
|
|
|
|
api_token = module.params.get("api_token")
|
|
|
|
zone = module.params.get("zone")
|
|
|
|
record = module.params.get("record")
|
|
|
|
record_type = module.params.get("type")
|
|
|
|
ttl = module.params.get("ttl")
|
|
|
|
value = module.params.get("value")
|
|
|
|
state = module.params.get("state")
|
2021-02-19 12:38:30 +00:00
|
|
|
throttle = module.params.get("throttle")
|
2020-08-18 21:44:49 +00:00
|
|
|
is_solo = module.params.get("solo")
|
|
|
|
params = {"name": record, "ttl": ttl}
|
|
|
|
|
|
|
|
# sanity checks
|
|
|
|
if not record_type:
|
|
|
|
if state == "present":
|
|
|
|
module.fail_json(msg="Missing the record type")
|
|
|
|
else:
|
|
|
|
params["type"] = record_type
|
|
|
|
|
|
|
|
if not value:
|
|
|
|
if state == "present":
|
|
|
|
module.fail_json(msg="Missing the record value")
|
|
|
|
else:
|
|
|
|
params["data"] = value
|
|
|
|
|
|
|
|
if is_solo and state == "absent":
|
|
|
|
module.fail_json(msg="solo=true can only be used with state=present")
|
|
|
|
|
|
|
|
# perform actions
|
|
|
|
try:
|
2021-02-19 12:38:30 +00:00
|
|
|
# request throttling to workaround the rate limit
|
|
|
|
time.sleep(throttle)
|
|
|
|
|
2020-08-18 21:44:49 +00:00
|
|
|
changed = False
|
|
|
|
if api_token:
|
|
|
|
client = CoreNetworks(api_token=api_token, auto_commit=True)
|
|
|
|
else:
|
|
|
|
client = CoreNetworks(user=api_user, password=api_password, auto_commit=True)
|
|
|
|
|
|
|
|
if state == "present":
|
|
|
|
changed_solo = False
|
|
|
|
if is_solo:
|
|
|
|
changed_solo = delete_records(client, module, zone, params, is_solo=True)
|
|
|
|
result, changed = add_record(client, module, zone, params)
|
|
|
|
|
|
|
|
module.exit_json(changed=changed_solo + changed, result=result)
|
|
|
|
# state is absent
|
|
|
|
else:
|
|
|
|
changed = delete_records(client, module, zone, params)
|
|
|
|
module.exit_json(changed=changed)
|
|
|
|
|
|
|
|
except CoreNetworksException as e:
|
|
|
|
module.fail_json(msg="Failure in core networks API communication: {}".format(str(e)))
|
|
|
|
|
|
|
|
module.fail_json(msg="Unknown what you wanted me to do")
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|