Re2o virtual emails.
This commit is contained in:
parent
371b4883dd
commit
4beb209b13
9 changed files with 103 additions and 548 deletions
173
main.py
173
main.py
|
@ -2,16 +2,17 @@ import logging
|
|||
import logging.config
|
||||
import pathlib
|
||||
import traceback
|
||||
import socket
|
||||
|
||||
import click
|
||||
import toml
|
||||
import requests
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
from re2oapi import Re2oAPIClient
|
||||
from gandi import GandiAPIClient, DomainsRecords, Record
|
||||
|
||||
RUN_PATH = pathlib.Path(__file__).parent
|
||||
|
||||
CLIENT_HOSTNAME = socket.gethostname().split('.', 1)[0]
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
|
@ -25,7 +26,7 @@ RUN_PATH = pathlib.Path(__file__).parent
|
|||
)
|
||||
def main(config_dir, dry_run, keep):
|
||||
logging.config.fileConfig(config_dir / "logging.conf")
|
||||
logger = logging.getLogger("dns")
|
||||
logger = logging.getLogger("re2o-mails")
|
||||
logger.debug("Fetching configuration from %s.", config_dir)
|
||||
config = toml.load(config_dir / "config.toml")
|
||||
re2o_client = Re2oAPIClient(
|
||||
|
@ -34,148 +35,48 @@ def main(config_dir, dry_run, keep):
|
|||
config["Re2o"]["password"],
|
||||
use_tls=config["Re2o"]["use_TLS"],
|
||||
)
|
||||
zones = re2o_client.list("dns/zones")
|
||||
default_ttl = re2o_client.view("preferences/optionalmachine").get(
|
||||
"default_dns_ttl", 10800
|
||||
)
|
||||
users_emails = re2o_client.list("localemail/users")
|
||||
users = filter(lambda x: x["local_email_enabled"], re2o_client.list("users/user"))
|
||||
local_email_domain = re2o_client.get("preferences/optionaluser")["local_email_domain"]
|
||||
env = Environment(loader=FileSystemLoader(config_dir))
|
||||
|
||||
default_API_key = config["Gandi"]["API_KEY"]
|
||||
generated_folder = config_dir / "generated"
|
||||
generated_folder.mkdir(exist_ok=True)
|
||||
|
||||
for zone in zones:
|
||||
# Re2o has zones names begining with '.'. It is a bit difficult to translate
|
||||
# that into toml
|
||||
name = zone["name"][1:]
|
||||
logger.debug(zone)
|
||||
try:
|
||||
configured_zone = config["Gandi"]["zone"][name]
|
||||
except KeyError as e:
|
||||
logger.error("Could not find zone named %s in configuration.", e)
|
||||
continue
|
||||
virtual_alias = generated_folder / "virtual_alias"
|
||||
virtual_mailbox = generated_folder / "virtual_mailbox"
|
||||
|
||||
key = configured_zone.get("API_KEY", default_API_key)
|
||||
gandi_client = GandiAPIClient(key)
|
||||
|
||||
logger.info("Fetching last update for zone %s.", name)
|
||||
last_update_file = config_dir / "last_update" / "last_update_{}.toml".format(name)
|
||||
last_update_file.touch(mode=0o644, exist_ok=True)
|
||||
last_update = DomainsRecords(gandi_client, name, fetch=False)
|
||||
try:
|
||||
last_update.from_dict(toml.load(last_update_file))
|
||||
except Exception as e:
|
||||
logger.warning("Could not retrieve last update.")
|
||||
logger.debug(e)
|
||||
logger.info("Fetching current records for zone %s.", name)
|
||||
current_records = DomainsRecords(gandi_client, name)
|
||||
logger.info("Fetching re2o records for zone %s.", name)
|
||||
new_records = set()
|
||||
|
||||
if zone["originv4"]:
|
||||
new_records.add(
|
||||
Record(
|
||||
gandi_client,
|
||||
name,
|
||||
rrset_name="@",
|
||||
rrset_type=Record.Types.A,
|
||||
rrset_values=[zone["originv4"]["ipv4"]],
|
||||
rrset_ttl=zone["soa"].get("ttl", None) or default_ttl,
|
||||
)
|
||||
)
|
||||
|
||||
if zone["originv6"]:
|
||||
new_records.add(
|
||||
Record(
|
||||
gandi_client,
|
||||
name,
|
||||
rrset_name="@",
|
||||
rrset_type=Record.Types.AAAA,
|
||||
rrset_values=[zone["originv6"]],
|
||||
rrset_ttl=zone["soa"].get("ttl", None) or default_ttl,
|
||||
)
|
||||
)
|
||||
|
||||
for record in zone["a_records"]:
|
||||
new_records.add(
|
||||
Record(
|
||||
gandi_client,
|
||||
name,
|
||||
rrset_name=record["hostname"],
|
||||
rrset_type=Record.Types.A,
|
||||
rrset_values=[record["ipv4"]],
|
||||
rrset_ttl=record.get("ttl", None) or default_ttl,
|
||||
)
|
||||
)
|
||||
|
||||
for record in zone["aaaa_records"]:
|
||||
new_records.add(
|
||||
Record(
|
||||
gandi_client,
|
||||
name,
|
||||
rrset_name=record["hostname"],
|
||||
rrset_type=Record.Types.AAAA,
|
||||
rrset_values=[ipv6["ipv6"] for ipv6 in record["ipv6"]],
|
||||
rrset_ttl=record.get("ttl", None) or default_ttl,
|
||||
)
|
||||
)
|
||||
|
||||
for record in zone["cname_records"]:
|
||||
new_records.add(
|
||||
Record(
|
||||
gandi_client,
|
||||
name,
|
||||
rrset_name=record["hostname"],
|
||||
rrset_type=Record.Types.CNAME,
|
||||
# The dot is to conform with Gandi API
|
||||
rrset_values=[record["alias"] + "."],
|
||||
rrset_ttl=record.get("ttl", None) or default_ttl,
|
||||
)
|
||||
)
|
||||
|
||||
# Delete records added by this script that are not in new_records.
|
||||
to_be_deleted = set(last_update.records) & (
|
||||
set(current_records.records) - set(new_records)
|
||||
)
|
||||
# Add records that are in new_records except if they are already there.
|
||||
to_be_added = set(new_records) - set(current_records.records)
|
||||
logger.debug("Re2o records are %r", new_records)
|
||||
logger.debug("I will add : %r", to_be_added)
|
||||
logger.debug("I will delete : %r", to_be_deleted)
|
||||
|
||||
if not dry_run:
|
||||
saved = set()
|
||||
for r in to_be_deleted:
|
||||
logger.info("Deleting record %r for zone %s.", r, name)
|
||||
try:
|
||||
r.delete()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
logger.error("Failed to delete %r for zone %s: %s", r, name, e)
|
||||
saved.add(r)
|
||||
|
||||
for r in to_be_added:
|
||||
logger.info("Adding record %r for zone %s.", r, name)
|
||||
try:
|
||||
r.save()
|
||||
saved.add(r)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
logger.error("Failed to add %r for zone %s: %s", r, name, e)
|
||||
logger.debug("Saving update for zone %s.", name)
|
||||
|
||||
# the new last_update file should contain the old one, plus the ones registered on re2o, minus the ones that should have been removed and were not saved
|
||||
managed = (set(last_update.records) | set(new_records)) - (
|
||||
to_be_deleted - saved
|
||||
)
|
||||
with last_update_file.open("w") as f:
|
||||
toml.dump({"records": [r.as_dict() for r in managed]}, f)
|
||||
template_alias = env.get_template('templates/virtual_alias.j2')
|
||||
template_mailbox = env.get_template('templates/virtual_mailbox.j2')
|
||||
|
||||
with virtual_mailbox.open(mode='w') as f_mailbox:
|
||||
rendered_mailbox = template_mailbox.render(users=users, local_email_domain=local_email_domain)
|
||||
if dry_run:
|
||||
logging.info("New virtual mailboxes would be : %s", rendered_mailbox)
|
||||
else:
|
||||
logger.info("This is a dry run for zone %s.", name)
|
||||
logger.info("Records to be deleted : %r", to_be_deleted)
|
||||
logger.info("Records to be added : %r", to_be_added)
|
||||
logging.info("Writing virtual mailboxes")
|
||||
f_mailbox.write(rendered_mailbox)
|
||||
|
||||
with virtual_alias.open(mode='w') as f_alias:
|
||||
rendered_alias = template_alias.render(users_emails=users_emails, local_email_domain=local_email_domain)
|
||||
if dry_run:
|
||||
logging.info("New virtual aliases would be : %s", rendered_alias)
|
||||
else:
|
||||
logging.info("Writing virtual aliases")
|
||||
f_alias.write(rendered_alias)
|
||||
|
||||
if not dry_run:
|
||||
logging.info("Generating new maps")
|
||||
call(["/usr/sbin/postmap", str(virtual_alias)], stdout=DEVNULL)
|
||||
call(["/usr/sbin/postmap", str(virtual_mailbox)], stdout=DEVNULL)
|
||||
logging.info("Reloading postfix")
|
||||
call(["/usr/sbin/postfix", "reload"])
|
||||
|
||||
if not keep and not dry_run:
|
||||
for service in re2o_client.list("services/regen/"):
|
||||
if (
|
||||
service["hostname"] == client_hostname
|
||||
and service["service_name"] == "dns"
|
||||
service["hostname"] == CLIENT_HOSTNAME
|
||||
and service["service_name"] == "mail-server"
|
||||
and service["need_regen"]
|
||||
):
|
||||
re2o_client.patch(service["api_url"], data={"need_regen": False})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue