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
|
"""Import zone based off a BIND zone file."""
# :license: MIT, see LICENSE for more details.
import re
import click
import SoftLayer
from SoftLayer.CLI.command import SLCommand as SLCommand
from SoftLayer.CLI import environment
from SoftLayer.CLI import exceptions
from SoftLayer.CLI import helpers
RECORD_REGEX = re.compile(r"""^((?P<domain>(([\w-]+|\*)(\.)?)*|\@)?\s+
(?P<ttl>\d+)?\s+
(?P<class>\w+)?)?\s+
(?P<type>\w+)\s+
(?P<data>.*)""", re.X)
RECORD_FMT = "type={type}, record={record}, data={data}, ttl={ttl}"
@click.command(cls=SLCommand)
@click.argument('zonefile', type=click.Path(exists=True, readable=True, resolve_path=True))
@click.option('--dry-run', is_flag=True, help="Don't actually create records")
@environment.pass_env
def cli(env, zonefile, dry_run):
"""Import zone based off a BIND zone file."""
manager = SoftLayer.DNSManager(env.client)
with open(zonefile, encoding="utf-8") as zone_f:
zone_contents = zone_f.read()
zone, records, bad_lines = parse_zone_details(zone_contents)
click.secho("Parsed: zone=%s" % zone)
for record in records:
click.secho("Parsed: %s" % RECORD_FMT.format(**record), fg="green")
for line in bad_lines:
click.secho("Unparsed: %s" % line, fg="yellow")
if dry_run:
return
# Find zone id or create the zone if it doesn't exist
try:
zone_id = helpers.resolve_id(manager.resolve_ids, zone,
name='zone')
except exceptions.CLIAbort:
zone_id = manager.create_zone(zone)['id']
click.secho(click.style("Created: %s" % zone, fg='green'))
# Attempt to create each record
for record in records:
try:
manager.create_record(zone_id, record['record'], record['type'], record['data'], record['ttl'])
click.secho(click.style("Created: %s" % RECORD_FMT.format(**record), fg='green'))
except SoftLayer.SoftLayerAPIError as ex:
click.secho(click.style("Failed: %s" % RECORD_FMT.format(**record), fg='red'))
click.secho(click.style(str(ex), fg='red'))
click.secho(click.style("Finished", fg='green'))
def parse_zone_details(zone_contents):
"""Parses a zone file into python data-structures."""
records = []
bad_lines = []
zone_lines = [line.strip() for line in zone_contents.split('\n')]
zone_search = re.search(r'^\$ORIGIN (?P<zone>.*)\.', zone_lines[0])
if zone_search:
zone = zone_search.group('zone')
else:
raise exceptions.ArgumentError("Invalid Zone File")
for line in zone_lines[1:]:
record_search = re.search(RECORD_REGEX, line)
if record_search is None:
bad_lines.append(line)
continue
name = record_search.group('domain')
# The API requires we send a host, although bind allows a blank
# entry. @ is the same thing as blank
if name is None:
name = "@"
ttl = record_search.group('ttl')
# we don't do anything with the class
# domain_class = domainSearch.group('class')
record_type = record_search.group('type').upper()
data = record_search.group('data')
# the dns class doesn't support weighted MX records yet, so we chomp
# that part out.
if record_type == "MX":
record_search = re.search(r'(?P<weight>\d+)\s+(?P<data>.*)', data)
data = record_search.group('data')
# This will skip the SOA record bit. And any domain that gets
# parsed oddly.
if record_type == 'IN':
bad_lines.append(line)
continue
records.append({
'record': name,
'type': record_type,
'data': data,
'ttl': ttl,
})
return zone, records, bad_lines
|