-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsynchronize.py
executable file
·122 lines (98 loc) · 3.5 KB
/
synchronize.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
#!/usr/bin/env python
"""
Utilities for parsing basic text-based zonefiles.
"""
import re
import pyflare
import os
import argparse
import sys
def parse(buffer):
"""
Parse a string into an enumeration of (name, type, target) tuples; for
example:
# a comment
a CNAME example.com owner-a@uchicago.edu
b A example.com owner-b@uchicago.edu
results in:
[
("a", "CNAME", "example.com"),
("b", "A", "example.com")
]
"""
for line in buffer.split("\n"):
line = line.split("#")[0].strip()
if not line:
continue
subdomain, record_type, content, owner = line.split(" ")
validation = {
r'^(CNAME|A|AAAA)$': record_type,
r'^(@|[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*)$': subdomain,
r'^.+$': content,
r'^([^@]+)@uchicago\.edu$': owner,
}
for pattern, value in validation.items():
if not re.match(pattern, value):
raise ValueError("in {line!r}, {value!r} must match {pattern!r}"
.format(**locals()))
yield (subdomain, record_type, content)
def apply(zone_name, parsed_zone, cloudflare, live=True):
"""
Applies DNS records in the format returned from +parse+ to a CloudFlare
zone.
"""
# Extract values currently in DNS.
present = set()
record_ids = {}
for record in cloudflare.rec_load_all(zone_name):
name = record["display_name"] if record["name"] != zone_name else "@"
present.add((name, record["type"], record["content"]))
record_ids[name] = record["rec_id"]
# Compute steps needed to update the live zone file.
future = set(parsed_zone)
to_add = future - present
to_remove = present - future
# Remove outdated DNS records.
for subdomain, record_type, content in to_remove:
print("- {subdomain} {record_type} {content}".format(**locals()))
if live:
cloudflare.rec_delete(zone_name, record_ids[subdomain])
# Add new DNS records.
for subdomain, record_type, content in to_add:
print("+ {subdomain} {record_type} {content}".format(**locals()))
if live:
cloudflare.rec_new(
zone_name,
record_type,
subdomain,
content,
1, # ttl (1=automatic)
None, # prio
None, # service
None, # service name
None, # protocol
None, # weight
None, # port
None # target
)
def main(argv):
parser = argparse.ArgumentParser(description='Push Settings to CloudFlare.')
parser.add_argument("--dry-run", dest="dry_run", action="store_true",
help="do everything but actually change DNS")
parser.add_argument("zone_name", help="where to apply the settings")
parser.add_argument("file_path", help="source of settings (default: stdin)")
opts = parser.parse_args(argv)
# Configure a CloudFlare API client.
email, key = os.environ["CF_EMAIL"], os.environ["CF_KEY"]
cloudflare = pyflare.PyflareClient(email, key)
# Extract records from the zone file.
try:
records = parse(open(opts.file_path).read())
except ValueError, e:
print(str(e))
return 1
# Apply the changes to DNS.
apply(opts.zone_name, records, cloudflare, live=(not opts.dry_run))
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))