2020-03-28 18:34:53 +00:00
|
|
|
#!/usr/bin/env python3
|
2020-03-28 00:01:56 +00:00
|
|
|
|
2020-03-28 18:34:53 +00:00
|
|
|
import json
|
2020-03-29 07:07:36 +00:00
|
|
|
import os
|
2020-03-28 16:55:36 +00:00
|
|
|
import traceback
|
2020-03-28 18:34:53 +00:00
|
|
|
from subprocess import CalledProcessError, check_output
|
2020-03-28 16:55:36 +00:00
|
|
|
|
2020-03-28 18:34:53 +00:00
|
|
|
from ansible.module_utils.basic import (
|
|
|
|
AnsibleModule,
|
|
|
|
env_fallback,
|
|
|
|
missing_required_lib,
|
|
|
|
)
|
2020-03-28 15:49:11 +00:00
|
|
|
|
2020-03-28 00:01:56 +00:00
|
|
|
DOCUMENTATION = """
|
|
|
|
---
|
|
|
|
module: gandi_dns
|
2020-03-28 16:55:36 +00:00
|
|
|
short_description: Manage Gandi DNS entries.
|
|
|
|
requirements:
|
|
|
|
- python >= 3.8
|
2020-03-28 18:34:53 +00:00
|
|
|
- dns-lexicon >= 3.3.19
|
2020-03-28 16:55:36 +00:00
|
|
|
author:
|
|
|
|
- Luke Murphy (@decentral1se)
|
|
|
|
options:
|
|
|
|
domain:
|
|
|
|
description: The domain name you're working with.
|
|
|
|
type: str
|
|
|
|
required: true
|
|
|
|
ipv4:
|
|
|
|
description: The IP v4 address that the domain refers to.
|
|
|
|
type: str
|
|
|
|
required: true
|
2020-03-29 07:07:36 +00:00
|
|
|
gandi_rest_token:
|
2020-03-28 18:34:53 +00:00
|
|
|
description:
|
|
|
|
- The Gandi REST API key. It may also be specified by the C(LEXICON_GANDI_AUTH_TOKEN)
|
|
|
|
environment variable. See U(https://github.com/AnalogJ/lexicon/blob/ce168132880558415c8c755e65f8e2f9b46cff62/lexicon/providers/gandi.py).
|
|
|
|
for more.
|
|
|
|
type: str
|
2020-03-29 07:07:36 +00:00
|
|
|
required: false
|
2020-03-28 16:55:36 +00:00
|
|
|
state:
|
|
|
|
description:
|
|
|
|
- The desired instance state.
|
|
|
|
type: str
|
|
|
|
choices:
|
|
|
|
- present
|
|
|
|
- absent
|
|
|
|
required: true
|
2020-03-28 17:06:00 +00:00
|
|
|
""" # noqa
|
2020-03-28 00:01:56 +00:00
|
|
|
|
|
|
|
EXAMPLES = """
|
|
|
|
- name: Create a new Gandi DNS entry
|
|
|
|
gandi_dns:
|
|
|
|
domain: foobar.autonomic.zone
|
|
|
|
ipv4: 192.168.1.2
|
2020-03-28 16:55:36 +00:00
|
|
|
state: present
|
|
|
|
"""
|
|
|
|
|
2020-03-28 18:34:53 +00:00
|
|
|
DNS_LEXICON_IMP_ERR = None
|
2020-03-28 16:55:36 +00:00
|
|
|
try:
|
2020-03-28 18:34:53 +00:00
|
|
|
from lexicon.providers import gandi # noqa
|
2020-03-28 16:55:36 +00:00
|
|
|
|
2020-03-28 18:34:53 +00:00
|
|
|
HAS_DNS_LEXICON_DEPENDENCY = True
|
2020-03-28 16:55:36 +00:00
|
|
|
except ImportError:
|
2020-03-28 18:34:53 +00:00
|
|
|
DNS_LEXICON_IMP_ERR = traceback.format_exc()
|
|
|
|
HAS_DNS_LEXICON_DEPENDENCY = False
|
|
|
|
|
|
|
|
|
2020-03-29 07:07:36 +00:00
|
|
|
def error_msg(domain):
|
|
|
|
"""Better error message since check_output output is not helpful."""
|
|
|
|
return (
|
|
|
|
"Unable to retrieve domain info. Did you export "
|
|
|
|
"the LEXICON_GANDI_AUTH_TOKEN environment variable? "
|
|
|
|
"Does running the following command work? lexicon "
|
|
|
|
"gandi list {domain} A"
|
|
|
|
).format(domain=domain)
|
|
|
|
|
|
|
|
|
|
|
|
def get_env():
|
|
|
|
"""Build environment for running command-line commands."""
|
|
|
|
env = os.environ.copy()
|
|
|
|
env["PROVIDER"] = "gandi"
|
|
|
|
env["LEXICON_GANDI_API_PROTOCOL"] = "rest"
|
|
|
|
return env
|
|
|
|
|
|
|
|
|
2020-03-28 18:34:53 +00:00
|
|
|
def retrieve_domain_info(module):
|
|
|
|
"""Retrieve all information about a specific domain."""
|
2020-03-29 07:07:36 +00:00
|
|
|
domain = module.params["domain"]
|
|
|
|
env = get_env()
|
|
|
|
|
2020-03-28 18:34:53 +00:00
|
|
|
try:
|
|
|
|
return json.loads(
|
|
|
|
check_output(
|
2020-03-29 07:07:36 +00:00
|
|
|
["lexicon", "gandi", "list", domain, "A", "--output", "JSON"],
|
|
|
|
env=env,
|
2020-03-28 18:34:53 +00:00
|
|
|
)
|
|
|
|
)
|
2020-03-29 07:07:36 +00:00
|
|
|
except CalledProcessError:
|
|
|
|
module.fail_json(msg=error_msg(domain))
|
2020-03-28 18:34:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
def create_domain(module):
|
|
|
|
"""Create a new DNS entry."""
|
2020-03-29 07:07:36 +00:00
|
|
|
domain = module.params["domain"]
|
|
|
|
ipv4 = module.params["ipv4"]
|
|
|
|
env = get_env()
|
|
|
|
|
2020-03-28 18:34:53 +00:00
|
|
|
try:
|
|
|
|
return json.loads(
|
|
|
|
check_output(
|
|
|
|
[
|
|
|
|
"lexicon",
|
|
|
|
"gandi",
|
|
|
|
"create",
|
2020-03-29 07:07:36 +00:00
|
|
|
domain,
|
2020-03-28 18:34:53 +00:00
|
|
|
"A",
|
|
|
|
"--name",
|
2020-03-29 07:07:36 +00:00
|
|
|
domain,
|
2020-03-28 18:34:53 +00:00
|
|
|
"--content",
|
2020-03-29 07:07:36 +00:00
|
|
|
ipv4,
|
2020-03-28 18:34:53 +00:00
|
|
|
"--output",
|
|
|
|
"JSON",
|
2020-03-29 07:07:36 +00:00
|
|
|
],
|
|
|
|
env=env,
|
2020-03-28 18:34:53 +00:00
|
|
|
)
|
|
|
|
)
|
2020-03-29 07:07:36 +00:00
|
|
|
except Exception:
|
|
|
|
module.fail_json(msg=error_msg(domain))
|
2020-03-28 18:34:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
def delete_domain(module):
|
|
|
|
"""Delete an existing DNS entry."""
|
2020-03-29 07:07:36 +00:00
|
|
|
domain = module.params["domain"]
|
|
|
|
ipv4 = module.params["ipv4"]
|
|
|
|
env = get_env()
|
|
|
|
|
2020-03-28 18:34:53 +00:00
|
|
|
try:
|
|
|
|
return json.loads(
|
|
|
|
check_output(
|
|
|
|
[
|
|
|
|
"lexicon",
|
|
|
|
"gandi",
|
|
|
|
"delete",
|
2020-03-29 07:07:36 +00:00
|
|
|
domain,
|
2020-03-28 18:34:53 +00:00
|
|
|
"A",
|
|
|
|
"--name",
|
2020-03-29 07:07:36 +00:00
|
|
|
domain,
|
2020-03-28 18:34:53 +00:00
|
|
|
"--content",
|
2020-03-29 07:07:36 +00:00
|
|
|
ipv4,
|
2020-03-28 18:34:53 +00:00
|
|
|
"--output",
|
|
|
|
"JSON",
|
2020-03-29 07:07:36 +00:00
|
|
|
],
|
|
|
|
env=env,
|
2020-03-28 18:34:53 +00:00
|
|
|
)
|
|
|
|
)
|
2020-03-29 07:07:36 +00:00
|
|
|
except Exception:
|
|
|
|
module.fail_json(msg=error_msg(domain))
|
2020-03-28 16:55:36 +00:00
|
|
|
|
2020-03-28 00:01:56 +00:00
|
|
|
|
|
|
|
def main():
|
2020-03-28 16:55:36 +00:00
|
|
|
"""Module entrypoint."""
|
2020-03-28 18:34:53 +00:00
|
|
|
module = AnsibleModule(
|
|
|
|
argument_spec=dict(
|
2020-03-29 07:07:36 +00:00
|
|
|
domain=dict(type="str", required=True),
|
|
|
|
ipv4=dict(type="str", required=True),
|
2020-03-28 18:34:53 +00:00
|
|
|
state=dict(
|
2020-03-29 07:07:36 +00:00
|
|
|
type="str", required=True, choices=["present", "absent"]
|
2020-03-28 18:34:53 +00:00
|
|
|
),
|
2020-03-29 07:07:36 +00:00
|
|
|
gandi_rest_token=dict(
|
|
|
|
type="str",
|
|
|
|
required=False,
|
2020-03-28 18:34:53 +00:00
|
|
|
no_log=True,
|
2020-03-29 07:07:36 +00:00
|
|
|
fallback=(env_fallback, ["LEXICON_GANDI_AUTH_TOKEN"]),
|
2020-03-28 18:34:53 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
supports_check_mode=False,
|
2020-03-29 07:07:36 +00:00
|
|
|
required_together=(["domain", "ipv4"]),
|
2020-03-28 18:34:53 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
if not HAS_DNS_LEXICON_DEPENDENCY:
|
2020-03-29 07:07:36 +00:00
|
|
|
msg = missing_required_lib("lexicon")
|
2020-03-28 18:34:53 +00:00
|
|
|
module.fail_json(msg=msg, exception=DNS_LEXICON_IMP_ERR)
|
|
|
|
|
|
|
|
domains = retrieve_domain_info(module)
|
2020-03-29 07:07:36 +00:00
|
|
|
existing_domain = module.params["domain"] in [
|
|
|
|
domain["name"] for domain in domains
|
2020-03-28 18:34:53 +00:00
|
|
|
]
|
2020-03-28 16:55:36 +00:00
|
|
|
|
2020-03-29 07:07:36 +00:00
|
|
|
if module.params["state"] == "present":
|
2020-03-28 18:34:53 +00:00
|
|
|
if existing_domain:
|
|
|
|
module.exit_json(changed=False)
|
|
|
|
create_domain(module)
|
|
|
|
module.exit_json(changed=True)
|
2020-03-28 16:55:36 +00:00
|
|
|
|
2020-03-29 07:07:36 +00:00
|
|
|
if module.params["state"] == "absent":
|
2020-03-28 18:34:53 +00:00
|
|
|
if not existing_domain:
|
|
|
|
module.exit_json(changed=False)
|
|
|
|
delete_domain(module)
|
|
|
|
module.exit_json(changed=True)
|
2020-03-28 00:01:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|