#!/usr/bin/env python3 import argparse import asyncio import ecs_logging import logging import os import signal import socket from kubernetes_asyncio.client.api_client import ApiClient from kubernetes_asyncio import client, config, watch from time import time parser = argparse.ArgumentParser() parser.add_argument("--replica", action="store_true") args = parser.parse_args() # Get the Logger logger = logging.getLogger() logger.setLevel(logging.INFO) # Add an ECS formatter to the Handler handler = logging.StreamHandler() handler.setFormatter(ecs_logging.StdlibFormatter()) logger.addHandler(handler) PATH_CONFIG = "/var/bind/bind.conf" PATH_ZONEFILE = "/var/bind/db.%s" active_zones = set() def update_config(): master = socket.gethostbyname("bind-primary") if args.replica else None with open(PATH_CONFIG + ".part", "w") as fh: for zone in active_zones: path = PATH_ZONEFILE % zone if args.replica: fh.write("zone \"%s\" { type slave; masters { %s key zone-transfer; }; };\n" % (zone, master)) else: fh.write("zone \"%s\" { type master; file \"%s\"; };\n" % (zone, path)) os.rename(PATH_CONFIG + ".part", PATH_CONFIG) logger.debug("File %s updated", PATH_CONFIG) try: with open("/var/bind/named.pid") as fh: os.kill(int(fh.read()), signal.SIGHUP) except FileNotFoundError: logger.warn("File /var/bind/named.pid not found, assuming Bind not running yet") async def update_loop(v1, apps_api, api_instance, revision): flt = "codemowers.io", "v1alpha1", "bindzones" method = api_instance.list_cluster_custom_object w = watch.Watch() latest_version = None logger.info("Subscribing to updates") async for event in w.stream(method, *flt, resource_version=latest_version): latest_version = event["object"]["metadata"]["resourceVersion"] if event["type"] == "ADDED": zone = event["object"]["metadata"]["name"] active_zones.add(zone) if not args.replica: path = PATH_ZONEFILE % zone logger.info("Adding zone: %s", zone) if not os.path.exists(path): with open(path + ".part", "w") as fh: fh.write("@ IN SOA ns1.%(zone)s. hostmaster.%(zone)s. (1 300 300 300 300)\n" % locals()) fh.write(" NS ns1.%(zone)s.\n" % locals()) fh.write("ns1 A 127.0.0.1\n") os.rename(path + ".part", path) update_config() elif event["type"] == "DELETED": logger.info("Removing zone: %s", zone) active_zones.remove(zone) os.unlink(PATH_ZONEFILE % event["object"]["metadata"]["name"]) async def main(): if os.getenv("KUBECONFIG"): await config.load_kube_config() else: config.load_incluster_config() async with ApiClient() as api: v1 = client.CoreV1Api(api) apps_api = client.AppsV1Api() api_instance = client.CustomObjectsApi(api) revision = str(time()) while True: try: await update_loop(v1, apps_api, api_instance, revision) except asyncio.exceptions.TimeoutError: pass if __name__ == "__main__": loop = asyncio.new_event_loop() loop.run_until_complete(main()) loop.close()