-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathdockerDDNS.py
executable file
·148 lines (117 loc) · 5.83 KB
/
dockerDDNS.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#!/usr/bin/env python3
__author__ = 'xtof'
import argparse
import re
import logging
import sys
from subprocess import Popen, PIPE
from docker import Client
from docker.utils import kwargs_from_env
from dns.resolver import Resolver
from dns.exception import DNSException
# Templates for nsupdate
zone_update_start_template = """server {0}
zone {1}.
"""
zone_update_template = """update delete {0}.{1}
update add {0}.{1} 60 A {2}
"""
zone_update_add_alias_template = """update delete {0}.{1}
update add {0}.{1} 600 CNAME {2}.{1}.
update add {2}.{1} 600 TXT dockerDDNS-alias:{0}:
"""
zone_update_delete_record_template = """update delete {0}.{1}
"""
def register_container(container_id):
detail = c.inspect_container(container_id)
container_hostname = detail["Config"]["Hostname"]
container_name = detail["Name"].split('/', 1)[1]
network_mode = detail["HostConfig"]["NetworkMode"]
if network_mode == "default":
container_ip = detail["NetworkSettings"]["IPAddress"]
else :
container_ip = detail["NetworkSettings"]["Networks"][network_mode]["IPAddress"]
logging.info("Updating %s to ip (%s|%s) -> %s", container_id, container_hostname, container_name, container_ip)
if not args.dry_run:
nsupdate = Popen(['nsupdate', '-k', args.key], stdin=PIPE)
nsupdate.stdin.write(bytes(zone_update_start_template.format(args.server, args.zone), "UTF-8"))
nsupdate.stdin.write(bytes(zone_update_template.format(container_hostname, args.domain, container_ip), "UTF-8"))
if container_name != container_hostname:
nsupdate.stdin.write(bytes(zone_update_add_alias_template.format(container_name, args.domain, container_hostname), "UTF-8"))
if re.search("_", container_name):
alternate_name = re.sub('_','-',container_name)
logging.info("Adding alternate name %s to %s", alternate_name, container_name)
nsupdate.stdin.write(bytes(zone_update_add_alias_template.format(alternate_name, args.domain, container_hostname), "UTF-8"))
nsupdate.stdin.write(bytes("send\n", "UTF-8"))
nsupdate.stdin.close()
def remove_container(container_id):
logging.info("Destroying %s", container_id)
short_id = container_id[:12]
record_to_delete = [short_id]
logging.debug("Looking for alias to %s.%s", short_id, args.domain)
try:
answers = resolver.query("{0}.{1}.".format(short_id, args.domain), "TXT", raise_on_no_answer=False).rrset
if answers:
for answer in answers:
logging.debug("Checking TXT record %s for alias", answer)
match = re.search(r"dockerDDNS-alias:([^:]+):", answer.to_text())
if match:
record_to_delete.append(match.group(1))
except DNSException as e:
logging.error("Cannot get TXT record for %s: %s", short_id, e)
except:
logging.error("Unexpected error: %s", sys.exc_info()[0])
raise
if not args.dry_run:
nsupdate = Popen(['nsupdate', '-k', args.key], stdin=PIPE)
nsupdate.stdin.write(bytes(zone_update_start_template.format(args.server, args.zone), "UTF-8"))
for record in record_to_delete:
logging.info("Removing record for %s", record)
nsupdate.stdin.write(bytes(zone_update_delete_record_template.format(record, args.domain), "UTF-8"))
nsupdate.stdin.write(bytes("send\n", "UTF-8"))
nsupdate.stdin.close()
parser = argparse.ArgumentParser()
parser.add_argument("--key", required=True, help="Path to the dynamic dns key")
parser.add_argument("--server", help="IP/Hostname of the server to update", default="127.0.0.1")
parser.add_argument("--domain", help="The domain to be updated", required=True)
parser.add_argument("--zone", help="The zone to be updated (default to the domain)")
parser.add_argument("--dry-run", help="Run in dry run mode without doing any update", default=False, action="store_true")
parser.add_argument("--catchup", help="Register the running containers on startup", default=False, action="store_true")
parser.add_argument("--log-level", help="Log level to display", default="INFO")
parser.add_argument("--log-file", help="Where to put the logs", default="/var/log/docker-ddns.log")
args = parser.parse_args()
logging.basicConfig(level=getattr(logging,args.log_level.upper()),
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
filename=(args.log_file if args.log_file != '-' else None))
if args.zone is None:
args.zone = args.domain
logging.info("Starting with arguments %s", args)
c = Client(**(kwargs_from_env()))
resolver = Resolver()
resolver.nameservers = [args.server]
if args.catchup:
logging.info("Registering existing containers")
containers = c.containers()
for container in containers:
register_container(container["Id"])
# TODO use docker-py streaming API
events_pipe = Popen(['docker', 'events'], stdout=PIPE)
while True:
line = events_pipe.stdout.readline()
if line != '':
text_line = line.decode().rstrip()
logging.debug("Read line %s", text_line)
m = re.search(r"\s+([0-9a-f]{64}):.*\s+([a-z]+)\s*$", text_line)
if m:
event = m.group(2)
container_id = m.group(1)
logging.debug("Got event %s for container %s", event, container_id)
if event == "start":
register_container(container_id)
elif event == "destroy":
remove_container(container_id)
else:
print("Done return code: ", events_pipe.returncode)
break
# 2014-11-28T15:32:04.000000000+01:00 a3d66b00acc9adbdbdbc91cc664d2d94b6a07cc4295c5cf54fcc595e2aa92a43: (from mongo:latest) restart
# 2015-03-05T08:36:14.000000000+01:00 eb75c1a5ad836d008b0fd66bf6b1ea353510175e8caa619e59d9851029b1ceca: (from ggtools/zabbix-server:latest) exec_start: ifconfig eth0