--- a/.hgignore Fri May 10 00:05:25 2013 +0300
+++ b/.hgignore Sat Dec 21 22:43:38 2013 +0200
@@ -1,13 +1,9 @@
-# temp
-\.sw[op]$
-\.pyc$
+syntax:glob
-# output
-^zones/
-^var/
+.*.swo
+.*.swp
-# data
-^settings/
+etc/
+opt/
+var/
-# stuff
-^old/
--- a/bin/check-dhcp-hosts Fri May 10 00:05:25 2013 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,201 +0,0 @@
-#!/usr/bin/env python
-
-"""
- Go through a dhcp conf file looking for fixed-address stanzas, and make sure that they are valid.
-"""
-
-__version__ = '0.0.1-dev'
-
-import optparse
-import codecs
-import logging
-
-import socket
-
-log = logging.getLogger('main')
-
-# command-line options, global state
-options = None
-
-def parse_options (argv) :
- """
- Parse command-line arguments.
- """
-
- prog = argv[0]
-
- parser = optparse.OptionParser(
- prog = prog,
- usage = '%prog: [options]',
- version = __version__,
-
- # module docstring
- description = __doc__,
- )
-
- # logging
- general = optparse.OptionGroup(parser, "General Options")
-
- general.add_option('-q', '--quiet', dest='loglevel', action='store_const', const=logging.ERROR, help="Less output")
- general.add_option('-v', '--verbose', dest='loglevel', action='store_const', const=logging.INFO, help="More output")
- general.add_option('-D', '--debug', dest='loglevel', action='store_const', const=logging.DEBUG, help="Even more output")
-
- parser.add_option_group(general)
-
- # input/output
- parser.add_option('-c', '--input-charset', metavar='CHARSET', default='utf-8',
- help="Encoding used for input files")
-
- #
- parser.add_option('--doctest', action='store_true',
- help="Run module doctests")
-
- # defaults
- parser.set_defaults(
- loglevel = logging.WARN,
- )
-
- # parse
- options, args = parser.parse_args(argv[1:])
-
- # configure
- logging.basicConfig(
- format = prog + ': %(name)s: %(levelname)s %(funcName)s : %(message)s',
- level = options.loglevel,
- )
-
- return options, args
-
-def parse_fixedaddrs (file) :
- """
- Go through lines in given .conf file, looking for fixed-address stanzas.
- """
-
- filename = file.name
-
- for lineno, line in enumerate(file) :
- # comments?
- if '#' in line :
- line, comment = line.split('#', 1)
-
- else :
- comment = None
-
- # whitespace
- line = line.strip()
-
- if not line :
- # empty
- continue
-
- # grep
- if 'fixed-address' in line :
- # great parsing :)
- fixedaddr = line.replace('fixed-address', '').replace(';', '').strip()
-
- log.debug("%s:%d: %s: %s", filename, lineno, fixedaddr, line)
-
- yield lineno, fixedaddr
-
-def resolve_addr (addr, af=socket.AF_INET, socktype=socket.SOCK_STREAM) :
- """
- Resolve given address for given AF_INET, returning a list of resolved addresses.
-
- Raises an Exception if failed.
-
- >>> resolve_addr('127.0.0.1')
- ['127.0.0.1']
- """
-
- if not addr :
- raise Exception("Empty addr: %r", addr)
-
- # resolve
- result = socket.getaddrinfo(addr, None, af, socktype)
-
- #log.debug("%s: %s", addr, result)
-
- # addresses
- addrs = list(sorted(set(sockaddr[0] for family, socktype, proto, canonname, sockaddr in result)))
-
- return addrs
-
-def check_file_hosts (file) :
- """
- Check all fixed-address parameters in given file.
- """
-
- filename = file.name
- fail = 0
-
- for lineno, addr in parse_fixedaddrs(file) :
- # lookup
- try :
- resolved = resolve_addr(addr)
-
- except Exception as e:
- log.warning("%s:%d: failed to resolve: %s: %s", filename, lineno, addr, e)
- fail += 1
-
- else :
- log.debug("%s:%d: %s: %r", filename, lineno, addr, resolved)
-
- return fail
-
-def open_file (path, mode, charset) :
- """
- Open unicode-enabled file from path, with - using stdio.
- """
-
- if path == '-' :
- # use stdin/out based on mode
- stream, func = {
- 'r': (sys.stdin, codecs.getreader),
- 'w': (sys.stdout, codecs.getwriter),
- }[mode[0]]
-
- # wrap
- return func(charset)(stream)
-
- else :
- # open
- return codecs.open(path, mode, charset)
-
-def main (argv) :
- global options
-
- options, args = parse_options(argv)
-
- if options.doctest :
- import doctest
- fail, total = doctest.testmod()
- return fail
-
- if args :
- # open files
- input_files = [open_file(path, 'r', options.input_charset) for path in args]
-
- else :
- # default to stdout
- input_files = [open_file('-', 'r', options.input_charset)]
-
- # process zone data
- for file in input_files :
- log.info("Reading zone: %s", file)
-
- fail = check_file_hosts(file)
-
- if fail :
- log.warn("DHCP hosts check failed: %d", fail)
- return 2
-
- else :
- log.info("DHCP hosts check OK")
-
- return 0
-
-if __name__ == '__main__':
- import sys
-
- sys.exit(main(sys.argv))
-
--- a/bin/expand-zone Fri May 10 00:05:25 2013 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,170 +0,0 @@
-#!/usr/bin/env python
-# vim: set ft=python :
-
-"""
- Process zonefiles with template expansions.
-"""
-
-__version__ = '0.0.1-dev'
-
-import optparse
-import codecs
-import os.path
-from datetime import datetime
-import logging
-
-log = logging.getLogger()
-
-# command-line options, global state
-options = None
-
-def parse_options (argv) :
- """
- Parse command-line arguments.
- """
-
- parser = optparse.OptionParser(
- prog = argv[0],
- usage = '%prog: [options]',
- version = __version__,
-
- # module docstring
- description = __doc__,
- )
-
- # logging
- general = optparse.OptionGroup(parser, "General Options")
-
- general.add_option('-q', '--quiet', dest='loglevel', action='store_const', const=logging.ERROR, help="Less output")
- general.add_option('-v', '--verbose', dest='loglevel', action='store_const', const=logging.INFO, help="More output")
- general.add_option('-D', '--debug', dest='loglevel', action='store_const', const=logging.DEBUG, help="Even more output")
-
- parser.add_option_group(general)
-
- parser.add_option('-c', '--input-charset', metavar='CHARSET', default='utf-8',
- help="Encoding used for input files")
-
- parser.add_option('-o', '--output', metavar='FILE', default='-',
- help="Write to output file; default stdout")
-
- parser.add_option('--output-charset', metavar='CHARSET', default='utf-8',
- help="Encoding used for output files")
-
- parser.add_option('--expand', metavar='NAME=VALUE', action='append',
- help="Expand given template variable in zone")
-
- parser.add_option('--serial', metavar='FILE',
- help="Read/expand serial from given .serial file")
-
- # defaults
- parser.set_defaults(
- loglevel = logging.WARN,
- expand = [],
- )
-
- # parse
- options, args = parser.parse_args(argv[1:])
-
- # configure
- logging.basicConfig(
- format = '%(processName)s: %(name)s: %(levelname)s %(funcName)s : %(message)s',
- level = options.loglevel,
- )
-
- return options, args
-
-def process_file (file, expansions) :
- """
- Process file, expanding lines.
- """
-
- for line in file :
- line = line.format(**expansions)
-
- yield line
-
-def write_lines (file, lines, suffix='\n') :
- for line in lines :
- file.write(line + suffix)
-
-def open_file (path, mode, charset) :
- """
- Open unicode-enabled file from path, with - using stdio.
- """
-
- if path == '-' :
- # use stdin/out based on mode
- stream, func = {
- 'r': (sys.stdin, codecs.getreader),
- 'w': (sys.stdout, codecs.getwriter),
- }[mode[0]]
-
- # wrap
- return func(charset)(stream)
-
- else :
- # open
- return codecs.open(path, mode, charset)
-
-def process_serial (path) :
- """
- Use serial number from given file.
-
- Returns the new serial as a string.
- """
-
- if not os.path.exists(path) :
- raise Exception("Given --serial does not exist: %s" % path)
-
- return open(path).read().strip()
-
-def parse_expand (expand) :
- """
- Parse an --expand foo=bar to (key, value)
- """
-
- key, value = expand.split('=', 1)
-
- return key, value
-
-def main (argv) :
- global options
-
- options, args = parse_options(argv)
-
- # expands
- expand = dict(parse_expand(expand) for expand in options.expand)
-
- # serial?
- if options.serial :
- serial = process_serial(options.serial)
-
- expand['serial'] = serial
-
- # input
- if args :
- # open files
- input_files = [open_file(path, 'r', options.input_charset) for path in args]
-
- else :
- # default to stdout
- input_files = [open_file('-', 'r', options.input_charset)]
-
- # process
- lines = []
-
- for file in input_files :
- log.info("Reading zone: %s", file)
-
- lines += list(process_file(file, expand))
-
- # output
- output = open_file(options.output, 'w', options.output_charset)
- write_lines(output, lines, suffix='')
-
- return 0
-
-if __name__ == '__main__':
- import sys
-
- sys.exit(main(sys.argv))
--- a/bin/process-zone Fri May 10 00:05:25 2013 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,833 +0,0 @@
-#!/usr/bin/env python
-
-"""
- Process zonefiles.
-"""
-
-__version__ = '0.0.1-dev'
-
-import optparse
-import codecs
-from datetime import datetime
-import logging
-
-import ipaddr
-
-log = logging.getLogger('main')
-
-# command-line options, global state
-options = None
-
-def parse_options (argv) :
- """
- Parse command-line arguments.
- """
-
- prog = argv[0]
-
- parser = optparse.OptionParser(
- prog = prog,
- usage = '%prog: [options]',
- version = __version__,
-
- # module docstring
- description = __doc__,
- )
-
- # logging
- general = optparse.OptionGroup(parser, "General Options")
-
- general.add_option('-q', '--quiet', dest='loglevel', action='store_const', const=logging.ERROR, help="Less output")
- general.add_option('-v', '--verbose', dest='loglevel', action='store_const', const=logging.INFO, help="More output")
- general.add_option('-D', '--debug', dest='loglevel', action='store_const', const=logging.DEBUG, help="Even more output")
-
- parser.add_option_group(general)
-
- # input/output
- parser.add_option('-c', '--input-charset', metavar='CHARSET', default='utf-8',
- help="Encoding used for input files")
-
- parser.add_option('-o', '--output', metavar='FILE', default='-',
- help="Write to output file; default stdout")
-
- parser.add_option('--output-charset', metavar='CHARSET', default='utf-8',
- help="Encoding used for output files")
-
- # check stage
- parser.add_option('--check-hosts', action='store_true',
- help="Check that host/IPs are unique. Use --quiet to silence warnings, and test exit status")
-
- parser.add_option('--check-exempt', metavar='HOST', action='append',
- help="Allow given names to have multiple records")
-
- # meta stage
- parser.add_option('--meta-zone', action='store_true',
- help="Generate host metadata zone; requires --input-line-date")
-
- parser.add_option('--meta-ignore', metavar='HOST', action='append',
- help="Ignore given hostnames in metadata output")
-
- parser.add_option('--input-line-date', action='store_true',
- help="Parse timestamp prefix from each input line (e.g. `hg blame | ...`)")
-
- # forward stage
- parser.add_option('--forward-zone', action='store_true',
- help="Generate forward zone")
-
- parser.add_option('--forward-txt', action='store_true',
- help="Generate TXT records for forward zone")
-
- parser.add_option('--forward-mx', metavar='MX',
- help="Generate MX records for forward zone")
-
- # reverse stage
- parser.add_option('--reverse-domain', metavar='DOMAIN',
- help="Domain to use for hosts in reverse zone")
-
- parser.add_option('--reverse-zone', metavar='NET',
- help="Generate forward zone for given subnet (x.z.y | a:b:c:d)")
-
- #
- parser.add_option('--doctest', action='store_true',
- help="Run module doctests")
-
- # defaults
- parser.set_defaults(
- loglevel = logging.WARN,
-
- # XXX: combine
- check_exempt = [],
- meta_ignore = [],
- )
-
- # parse
- options, args = parser.parse_args(argv[1:])
-
- # configure
- logging.basicConfig(
- format = prog + ': %(name)s: %(levelname)s %(funcName)s : %(message)s',
- level = options.loglevel,
- )
-
- return options, args
-
-class ZoneError (Exception) :
- pass
-
-class ZoneLineError (ZoneError) :
- """
- ZoneLine-related error
- """
-
- def __init__ (self, line, msg, *args, **kwargs) :
- super(ZoneLineError, self).__init__("%s: %s" % (line, msg.format(*args, **kwargs)))
-
-class ZoneLine (object) :
- """
- A line in a zonefile.
- """
-
- file = None
- lineno = None
-
- # data
- indent = None # was the line indented?
- data = None
- parts = None # split line fields
-
- # optional
- timestamp = None
- comment = None
-
- PARSE_DATETIME_FORMAT = '%Y-%m-%d'
-
- @classmethod
- def parse (cls, file, lineno, line, line_timestamp_prefix=False) :
- """
- Parse out given line and build.
- """
-
- log.debug("parse: %s:%d: %s", file, lineno, line)
-
- ts = None
-
- if line_timestamp_prefix :
- if ': ' not in line :
- raise ZoneError("%s:%d: Missing timestamp prefix: %s" % (file, lineno, line))
-
- # split prefix
- prefix, line = line.split(': ', 1)
-
- # parse it out
- ts = datetime.strptime(prefix, cls.PARSE_DATETIME_FORMAT)
-
- log.debug(" ts=%r", ts)
-
- # was line indented?
- indent = line.startswith(' ') or line.startswith('\t')
-
- # strip
- line = line.strip()
-
- log.debug(" indent=%r, line=%r", indent, line)
-
- # parse comment out?
- if ';' in line :
- line, comment = line.split(';', 1)
-
- line = line.strip()
- comment = comment.strip()
-
- else :
- line = line.strip()
- comment = None
-
- log.debug(" line=%r, comment=%r", line, comment)
-
- # parse fields
- if '"' in line :
- pre, data, post = line.split('"', 2)
- parts = pre.split() + [data] + post.split()
-
- else :
- parts = line.split()
-
- log.debug(" parts=%r", parts)
-
- # build
- return cls(file, lineno, indent, line, parts, timestamp=ts, comment=comment)
-
- def __init__ (self, file, lineno, indent, data, parts, timestamp=None, comment=None) :
- self.file = file
- self.lineno = lineno
-
- self.indent = indent
- self.data = data
- self.parts = parts
-
- self.timestamp = timestamp
- self.comment = comment
-
- def __str__ (self) :
- return "{file}:{lineno}".format(file=self.file, lineno=self.lineno)
-
-class ZoneRecord (object) :
- """
- A record from a zonefile.
- """
-
- # the underlying line
- line = None
-
- # possible $ORIGIN context
- origin = None
-
- # record fields
- name = None
- type = None
-
- # list of data fields
- data = None
-
- # optional
- ttl = None
- cls = None
-
- @classmethod
- def parse (cls, line, parts=None, origin=None) :
- """
- Parse from ZoneLine. Returns None if there is no record on the line..
- """
-
- if parts is None :
- parts = list(line.parts)
-
- if not parts :
- # skip
- return
-
- # XXX: indented lines keep name from previous record
- if line.indent :
- name = None
-
- else :
- name = parts.pop(0)
-
- log.debug(" name=%r, origin=%r", name, origin)
-
- if len(parts) < 2 :
- raise ZoneLineError(line, "Too few parts to parse: {0!r}", line.data)
-
- # parse ttl/cls/type
- ttl = _cls = None
-
- if parts and parts[0][0].isdigit() :
- ttl = parts.pop(0)
-
- if parts and parts[0].upper() in ('IN', 'CH') :
- _cls = parts.pop(0)
-
- # always have type
- type = parts.pop(0)
-
- # remaining parts are data
- data = parts
-
- log.debug(" ttl=%r, cls=%r, type=%r, data=%r", ttl, _cls, type, data)
-
- return cls(name, type, data,
- origin = origin,
- ttl = ttl,
- cls = _cls,
- line = line,
- )
-
- def __init__ (self, name, type, data, origin=None, ttl=None, cls=None, line=None, comment=None) :
- self.name = name
- self.type = type
- self.data = data
-
- self.ttl = ttl
- self.cls = cls
-
- self.origin = origin
- self.line = line
-
- # XXX: within line
- self._comment = comment
-
- def build_line (self) :
- """
- Construct a zonefile-format line..."
- """
-
- # XXX: comment?
- if self._comment :
- comment = '\t; ' + self._comment
- else :
- comment = ''
-
- return u"{name:25} {ttl:4} {cls:2} {type:5} {data}{comment}".format(
- name = self.name or '',
- ttl = self.ttl or '',
- cls = self.cls or '',
- type = self.type,
- data = ' '.join(unicode(data) for data in self.data),
- comment = comment,
- )
-
- def __str__ (self) :
- return ' '.join((self.name or '', self.type, ' '.join(self.data)))
-
-class TXTRecord (ZoneRecord) :
- """
- TXT record.
- """
-
- def __init__ (self, name, text, **opts) :
- return super(TXTRecord, self).__init__(name, 'TXT',
- [u'"{0}"'.format(text.replace('"', '\\"'))],
- **opts
- )
-
-class OffsetValue (object) :
- def __init__ (self, value) :
- self.value = value
-
- def __getitem__ (self, offset) :
- value = self.value + offset
-
- #log.debug("OffsetValue: %d[%d] -> %d", self.value, offset, value)
-
- return value
-
-def parse_generate_field (line, field) :
- """
- Parse a $GENERATE lhs/rhs field:
- $
- ${<offset>[,<width>[,<base>]]}
- \$
- $$
-
- Returns a wrapper that builds the field-value when called with the index.
-
- >>> parse_generate_field(None, "foo")(1)
- 'foo'
- >>> parse_generate_field(None, "foo-$")(1)
- 'foo-1'
- >>> parse_generate_field(None, "foo-$$")(1)
- 'foo-$'
- >>> parse_generate_field(None, "\$")(1)
- '$'
- >>> parse_generate_field(None, "10.0.0.${100}")(1)
- '10.0.0.101'
- >>> parse_generate_field(None, "foo-${0,2,d}")(1)
- 'foo-01'
-
- """
-
- input = field
- expr = []
-
- while '$' in field :
- # defaults
- offset = 0
- width = 0
- base = 'd'
- escape = False
-
- # different forms
- if '${' in field :
- pre, body = field.split('${', 1)
- body, post = body.split('}', 1)
-
- # parse body
- parts = body.split(',')
-
- # offset
- offset = int(parts.pop(0))
-
- # width
- if parts :
- width = int(parts.pop(0))
-
- # base
- if parts :
- base = parts.pop(0)
-
- if parts:
- # fail
- raise ZoneLineError(line, "extra data in ${...} body: {0!r}", parts)
-
- elif '$$' in field :
- pre, post = field.split('$$', 1)
- escape = True
-
- elif '\\$' in field :
- pre, post = field.split('\\$', 1)
- escape = True
-
- else :
- pre, post = field.split('$', 1)
-
- expr.append(pre)
-
- if escape :
- expr.append('$')
-
- else :
- # meta-format
- fmt = '{value[%d]:0%d%s}' % (offset, width, base)
-
- log.debug("field=%r -> pre=%r, fmt=%r, post=%r", field, expr, fmt, post)
-
- expr.append(fmt)
-
- field = post
-
- # final
- if field :
- expr.append(field)
-
- # combine
- expr = ''.join(expr)
-
- log.debug("%s: %s", input, expr)
-
- # processed
- def value_func (value) :
- # magic wrapper to implement offsets
- return expr.format(value=OffsetValue(value))
-
- return value_func
-
-def process_generate (line, origin, parts) :
- """
- Process a
- $GENERATE <start>-<stop>[/<step>] lhs [ttl] [class] type rhs [comment]
- directive into a series of ZoneResource's.
- """
-
- range = parts.pop(0)
-
- # parse range
- if '/' in range :
- range, step = range.split('/')
- step = int(step)
- else :
- step = 1
-
- start, stop = range.split('-')
- start = int(start)
- stop = int(stop)
-
- log.debug(" range: start=%r, stop=%r, step=%r", start, stop, step)
-
- # inclusive
- range = xrange(start, stop + 1, step)
-
- lhs_func = parse_generate_field(line, parts.pop(0))
- rhs_func = parse_generate_field(line, parts.pop(-1))
- body = parts
-
- for i in range :
- # build
- parts = [lhs_func(i)] + body + [rhs_func(i)]
-
- log.debug(" %03d: %r", i, parts)
-
- # parse
- yield ZoneRecord.parse(line, parts=parts, origin=origin)
-
-def parse_zone_records (file, origin=None, **opts) :
- """
- Parse ZoneRecord items from the given zonefile, ignoring non-record lines.
- """
-
- ttl = None
-
- skip_multiline = False
-
- for lineno, raw_line in enumerate(file) :
- # parse comment
- if ';' in raw_line :
- line, comment = raw_line.split(';', 1)
- else :
- line = raw_line
- comment = None
-
- # XXX: handle multi-line statements...
- # start
- if '(' in line :
- skip_multiline = True
-
- log.warn("%s:%d: Start of multi-line statement: %s", file.name, lineno, raw_line)
-
- # end?
- if ')' in line :
- skip_multiline = False
-
- log.warn("%s:%d: End of multi-line statement: %s", file.name, lineno, raw_line)
-
- continue
-
- elif skip_multiline :
- log.warn("%s:%d: Multi-line statement: %s", file.name, lineno, raw_line)
-
- continue
-
- # parse
- line = ZoneLine.parse(file.name, lineno, raw_line, **opts)
-
- if not line.data :
- log.debug("%s: skip empty line: %s", line, raw_line)
-
- continue
-
- elif line.data.startswith('$') :
- # control record
- type = line.parts[0]
-
- if type == '$ORIGIN':
- # update
- origin = line.parts[1]
-
- log.info("%s: origin: %s", line, origin)
-
- elif type == '$GENERATE':
- # process...
- log.info("%s: generate: %s", line, line.parts)
-
- for record in process_generate(line, origin, line.parts[1:]) :
- yield record
-
- else :
- log.warning("%s: skip control record: %s", line, line.data)
-
- # XXX: passthrough!
- continue
-
- # normal record?
- record = ZoneRecord.parse(line, origin=origin)
-
- if record :
- yield record
-
- else :
- # unknown
- log.warning("%s: skip unknown line: %s", line, line.data)
-
-def check_zone_hosts (zone, whitelist=None, whitelist_types=set(['TXT'])) :
- """
- Parse host/IP pairs from the zone, and verify that they are unique.
-
- As an exception, names listed in the given whitelist may have multiple IPs.
- """
-
- by_name = {}
- by_ip = {}
-
- fail = None
-
- last_name = None
-
- for r in zone :
- name = r.name or last_name
-
- name = (r.origin, name)
-
- # name
- if r.type not in whitelist_types :
- if name not in by_name :
- by_name[name] = r
-
- elif r.name in whitelist :
- log.debug("Duplicate whitelist entry: %s", r)
-
- else :
- # fail!
- log.warn("%s: Duplicate name: %s <-> %s", r.line, r, by_name[name])
- fail = True
-
- # ip
- if r.type == 'A' :
- ip, = r.data
-
- if ip not in by_ip :
- by_ip[ip] = r
-
- else :
- # fail!
- log.warn("%s: Duplicate IP: %s <-> %s", r.line, r, by_ip[ip])
- fail = True
-
- return fail
-
-def process_zone_forwards (zone, txt=False, mx=False) :
- """
- Process zone data -> forward zone data.
- """
-
- for r in zone :
- yield r
-
- if r.type == 'A' :
- if txt :
- # comment?
- comment = r.line.comment
-
- if comment :
- yield TXTRecord(None, comment, ttl=r.ttl)
-
-
- # XXX: RP, do we need it?
-
- if mx :
- # XXX: is this a good idea?
- yield ZoneRecord(None, 'MX', [10, mx], ttl=r.ttl)
-
-def process_zone_meta (zone, ignore=None) :
- """
- Process zone metadata -> output.
- """
-
- TIMESTAMP_FORMAT='%Y/%m/%d'
-
- for r in zone :
- if ignore and r.name in ignore :
- # skip
- log.debug("Ignore record: %s", r)
- continue
-
- # for hosts..
- if r.type == 'A' :
- # timestamp?
- timestamp = r.line.timestamp
-
- if timestamp :
- yield TXTRecord(r.name, timestamp.strftime(TIMESTAMP_FORMAT), ttl=r.ttl)
-
-def reverse_ipv4 (ip) :
- """
- Return in-addr.arpa reverse for given IPv4 prefix.
- """
-
- # parse
- octets = tuple(int(part) for part in ip.split('.'))
-
- for octet in octets :
- assert 0 <= octet <= 255
-
- return '.'.join([str(octet) for octet in reversed(octets)] + ['in-addr', 'arpa'])
-
-def reverse_ipv6 (ip6) :
- """
- Return ip6.arpa reverse for given IPv6 prefix.
- """
-
- parts = [int(part, 16) for part in ip6.split(':')]
- parts = ['{0:04x}'.format(part) for part in parts]
- parts = ''.join(parts)
-
- return '.'.join(tuple(reversed(parts)) + ( 'ip6', 'arpa'))
-
-def fqdn (*parts) :
- fqdn = '.'.join(parts)
-
- # we may be given an fqdn in parts
- if not fqdn.endswith('.') :
- fqdn += '.'
-
- return fqdn
-
-def process_zone_reverse (zone, origin, domain) :
- """
- Process zone data -> reverse zone data.
- """
-
- name = None
-
- for r in zone :
- # keep name from previous..
- if r.name :
- name = r.name
-
- if r.type == 'A' :
- ip, = r.data
- ptr = reverse_ipv4(ip)
-
- elif r.type == 'AAAA' :
- ip, = r.data
- ptr = reverse_ipv6(ip)
-
- else :
- continue
-
- # verify
- if zone and ptr.endswith(origin) :
- ptr = ptr[:-(len(origin) + 1)]
-
- else :
- log.warning("Reverse does not match zone origin, skipping: (%s) -> %s <-> %s", ip, ptr, origin)
- continue
-
- # domain to use
- host_domain = r.origin or domain
- host_fqdn = fqdn(name, host_domain)
-
- yield ZoneRecord(ptr, 'PTR', [host_fqdn])
-
-def write_zone_records (file, zone) :
- for r in zone :
- file.write(r.build_line() + u'\n')
-
-def open_file (path, mode, charset) :
- """
- Open unicode-enabled file from path, with - using stdio.
- """
-
- if path == '-' :
- # use stdin/out based on mode
- stream, func = {
- 'r': (sys.stdin, codecs.getreader),
- 'w': (sys.stdout, codecs.getwriter),
- }[mode[0]]
-
- # wrap
- return func(charset)(stream)
-
- else :
- # open
- return codecs.open(path, mode, charset)
-
-def main (argv) :
- global options
-
- options, args = parse_options(argv)
-
- if options.doctest :
- import doctest
- fail, total = doctest.testmod()
- return fail
-
- if args :
- # open files
- input_files = [open_file(path, 'r', options.input_charset) for path in args]
-
- else :
- # default to stdout
- input_files = [open_file('-', 'r', options.input_charset)]
-
- # process zone data
- zone = []
-
- for file in input_files :
- log.info("Reading zone: %s", file)
-
- zone += list(parse_zone_records(file,
- line_timestamp_prefix = options.input_line_date,
- ))
-
- # check?
- if options.check_hosts :
- whitelist = set(options.check_exempt)
-
- log.debug("checking hosts; whitelist=%r", whitelist)
-
- if check_zone_hosts(zone, whitelist=whitelist) :
- log.warn("Hosts check failed")
- return 2
-
- else :
- log.info("Hosts check OK")
-
- # output file
- output = open_file(options.output, 'w', options.output_charset)
-
- if options.forward_zone :
- log.info("Write forward zone: %s", output)
-
- zone = list(process_zone_forwards(zone, txt=options.forward_txt, mx=options.forward_mx))
-
- elif options.meta_zone :
- log.info("Write metadata zone: %s", output)
-
- if not options.input_line_date :
- log.error("--meta-zone requires --input-line-date")
- return 1
-
- zone = list(process_zone_meta(zone, ignore=set(options.meta_ignore)))
-
- elif options.reverse_zone :
- if ':' in options.reverse_zone :
- # IPv6
- origin = reverse_ipv6(options.reverse_zone)
-
- else :
- # IPv4
- origin = reverse_ipv4(options.reverse_zone)
-
- domain = options.reverse_domain
-
- if not domain :
- log.error("--reverse-zone requires --reverse-domain")
- return 1
-
- zone = list(process_zone_reverse(zone, origin=origin, domain=domain))
-
- elif options.check_hosts :
- # we only did that, done
- return 0
-
- else :
- log.warn("Nothing to do")
- return 1
-
- write_zone_records(output, zone)
-
- return 0
-
-if __name__ == '__main__':
- import sys
-
- sys.exit(main(sys.argv))
--- a/bin/update Fri May 10 00:05:25 2013 +0300
+++ b/bin/update Sat Dec 21 22:43:38 2013 +0200
@@ -1,283 +1,131 @@
#!/bin/bash
# vim: set ft=sh :
-set -ue
-
-# resolve $0 -> bin/update
-self=$0
-while [ -L $self ]; do
- tgt=$(readlink $self)
-
- if [ "${tgt:0:1}" == "/" ]; then
- self=$tgt
- else
- self=$(dirname $self)/$tgt
- fi
-done
-
-# root dir
-ROOT=$(dirname $(dirname $self))
-
-BIN=$ROOT/bin
-LIB=$ROOT/lib
-VAR=$ROOT/var
-
-## Data paths
-# absolute path to data files; can be changed using -d
-ROOT=$(pwd)
-
-DATA=settings
-ZONES=$VAR/zones
-SERIALS=$VAR/serials
-
-DHCP=$VAR/dhcp
-DHCP_DATA=$DATA/dhcp
-
-# global DHCP conf to test
-DHCPD=/usr/sbin/dhcpd
-DHCPD_CONF=/etc/dhcp/dhcpd.conf
-DHCPD_INIT=/etc/init.d/isc-dhcp-server
-
-# hg repo to commit
-REPO=$DATA
-
-## Settings used in lib
-# Hide files under repo in commit diff output..
-REPO_HIDE='*.serial'
-
-# data input charsets; arguments to ./bin/... python scripts
-HOSTS_FILE_ARGS='--input-charset utf-8'
-DHCP_FILE_ARGS='--input-charset utf-8'
-
-# External bins
-NAMED_CHECKZONE=/usr/sbin/named-checkzone
-
-HG=/usr/bin/hg
-HG_ARGS=(--config trusted.users=root)
-
-RNDC=/usr/sbin/rndc
-
-# Path to rndc key, must be readable to run..
-RNDC_KEY=/etc/bind/rndc.key
-
-## Library includes
-# Command-line argument handling
-source $LIB/update.args
+# Bootstrap
+if [ $0 == './update' ]; then
+ SRV=$(pwd)
+ OPT=./opt
+else
+ SRV=${SRV:-/srv/dns}
+ OPT=${SRV:-/srv/dns/opt}
+ cd $SRV
+fi
-# Logging
-source $LIB/update.logging
-
-# Utility functions
-source $LIB/update.utils
-
-# Dependency-based updates
-source $LIB/update.updates
-
-# Operations; the functions called from run()
-source $LIB/update.operations
-
-## Flags
-# set by do_reload_zone if zone data has actually been reloaded
-RELOAD_ZONES=
-
-## Site settings, used as arguments to scripts
-# MX record to generate in hosts --forward-zone
-FORWARD_MX=mx0
-
-# IP network to generate reverse records for in --reverse-zone
-REVERSE_ZONE=194.197.235
-
-# Origin domain to generate reverse records for in --reverse-zone
-REVERSE_DOMAIN=paivola.fi
-
-# Views used
-VIEWS=(internal external)
-
-# Base domain zone for domains
-DOMAIN_BASE=paivola
+source lib/update
-# List of actual domains used; will be linked to $DOMAIN_BASE
-DOMAINS=(paivola.fi paivola.net paivola.org paivola.info paivola.mobi xn--pivl-load8j.fi)
-
-# Names of dhcp conf file names
-DHCP_CONFS=( $(list_files $DHCP_DATA *.conf) )
-
-## Operate!
-# these functions are all defined in lib/update.operations
-
-# Update $ZONES/$DHCP host-files from $DATA
-function run_hosts {
- ## Hosts
- # test
+function commit {
+ ## Commit
+ # pre-commit check
log "Testing hosts..."
- # data args...
- check_hosts $DATA/paivola.txt --check-exempt ufc
+ for hosts in $(list_files etc/hosts); do
+ log_warn "TODO: check_hosts $hosts"
+ done
- # update
- log "Generating host zones..."
- # hosts data args...
- update_hosts $ZONES/hosts/paivola:internal $DATA/paivola.txt --forward-zone --forward-txt
- update_hosts $ZONES/hosts/paivola:external $DATA/paivola.txt --forward-zone
- update_hosts $ZONES/hosts/194.197.235 $DATA/paivola.txt --reverse-zone $REVERSE_ZONE --reverse-domain $REVERSE_DOMAIN
-
-
- update_hosts $ZONES/hosts/10 $DATA/pvl.txt --reverse-zone 10 --reverse-domain pvl -q
- update_hosts $ZONES/hosts/10.0 $DATA/test.pvl.txt --reverse-zone 10.0 --reverse-domain test.pvl -q
- update_hosts $ZONES/hosts/fdc4:4cef:395a $DATA/test.pvl.txt --reverse-zone fdc4:4cef:395a --reverse-domain test.pvl -q
- update_hosts $ZONES/hosts/192.168 $DATA/pvl.txt --reverse-zone 192.168 --reverse-domain pvl -q
-
- # XXX: unsupported --forward-zone with pvl.txt
- # update_hosts $ZONES/hosts/pvl $DATA/pvl.txt --forward-zone
- copy_hosts $ZONES/hosts/pvl $DATA/pvl.txt
- copy_hosts $ZONES/hosts/test.pvl $DATA/test.pvl.txt
+ # commit, unless noop'd
+ log "Commit..."
+ update_commit etc
}
-# Update $ZONES files
-function run_zones {
- ## Includes
- log "Copying zone includes..."
- # view zone base
- copy_zone includes paivola:internal paivola.zone.internal
- copy_zone includes paivola:external paivola.zone.external
- copy_zone includes paivola.auto paivola.zone.auto
- copy_zone includes paivola.services paivola.zone.services
- copy_zone includes paivola.aux paivola.zone.aux
-
- ## Serials
- log "Updating serials..."
+function update {
+ if hg_modified etc; then
+ serial=$(unix_time)
+ log_warn "Using local unix time for uncommited changes: $serial"
+ else
+ serial=$(hg_time etc)
+ log_update "Using HG commit timestamp: $serial"
+ fi
- # zone deps...
- # includes...
- update_serial pvl $ZONES/hosts/pvl $DATA/pvl.zone
- update_serial test.pvl $ZONES/hosts/test.pvl $DATA/test.pvl.zone
- update_serial 10 $ZONES/hosts/10 $DATA/10.zone
- update_serial 10.0 $ZONES/hosts/10.0 $DATA/10.0.zone
- update_serial fdc4:4cef:395a $ZONES/hosts/fdc4:4cef:395a $DATA/fdc4:4cef:395a.zone
- update_serial 192.168 $ZONES/hosts/192.168 $DATA/192.168.zone
+ ## Hosts
+ log "Updating forward host zones..."
+ for zone in $(list_dirs etc/hosts/forward); do
+ update_hosts_forward "var/zones/hosts/forward/$zone" "$zone" \
+ etc/hosts/forward/$zone/*
+ done
- update_serial paivola $ZONES/hosts/paivola:* $DATA/paivola.zone \
- $ZONES/includes/paivola:* \
- $ZONES/includes/paivola.*
+ log "Updating DHCP hosts..."
+ for hosts in $(list_files etc/hosts); do
+ update_hosts_dhcp "var/dhcp/hosts/$hosts.conf" \
+ "etc/hosts/$hosts"
+ done
- update_serial 194.197.235 $ZONES/hosts/194.197.235 $DATA/194.197.235.zone
+ log "Updating reverse host zones..."
+ for zone in $(list_dirs etc/hosts/reverse); do
+ update_hosts_reverse "var/zones/hosts/reverse/$zone" "$zone" \
+ etc/hosts/reverse/$zone/*
+ done
## Zones
- log "Updating zones..."
- # view zone base
- update_zone internal pvl
- update_zone internal test.pvl
-
- update_zone internal 10
- update_zone internal 10.0
- update_zone internal fdc4:4cef:395a
- update_zone internal 192.168
-
- update_zone common 194.197.235
- link_zone internal 194.197.235
- link_zone external 194.197.235
+ log "Copying zone includes..."
+ for zone in $(list_files etc/zones/includes); do
+ copy "var/zones/includes/$zone" "etc/zones/includes/$zone"
+ done
- ## Test
- log "Testing zones..."
- # view zone origin
- check_zone internal 10 10.in-addr.arpa
- check_zone internal 10.0 0.10.in-addr.arpa
- check_zone internal fdc4:4cef:395a a.5.9.3.f.e.c.4.4.c.d.f.ip6.arpa
-
- check_zone internal 192.168 192.168.in-addr.arpa
- check_zone common 194.197.235 235.197.194.in-addr.arpa
+ log "Updating zone serials..."
+ for zone in $(list_files etc/zones); do
+ update_serial "var/serials/$zone" $serial \
+ "etc/zones/$zone" $(zone_includes var/include-cache/$zone etc/zones/$zone var/zones/)
+ done
- ## Domains...
- log "Linking domains..."
- for view in "${VIEWS[@]}"; do
- for zone in "${DOMAINS[@]}"; do
- # choose input .zone to use
- base=$(choose_zone $zone $DOMAIN_BASE)
-
- if [ $base != $DOMAIN_BASE ]; then
- # serial
- # XXX: not all zones use all these includes?
- update_serial $base $DATA/$base.zone \
- $ZONES/hosts/paivola:* \
- $ZONES/includes/paivola:* \
- $ZONES/includes/paivola.*
- fi
+ log "Updating zones..."
+ for zone in $(list_files etc/zones); do
+ update_zone "var/zones/$zone" "etc/zones/$zone" "var/serials/$zone" \
+ $(zone_includes var/include-cache/$zone etc/zones/$zone var/zones/)
+ done
- # link
- update_zone $view $zone $base
-
- # test
- check_zone $view $zone $zone
- done
- done
+ log "Updating DHCP confs..."
+ for conf in $(list_files etc/dhcp); do
+ update_dhcp_conf "var/dhcp/$conf" "etc/dhcp/$conf"
+ done
}
-# Update $DHCP files from $DATA/dhcp
-function run_dhcp {
- log_debug "DHCP_CONFS: ${DHCP_CONFS[*]}"
-
- log "Copying DHCP configs..."
- for conf in "${DHCP_CONFS[@]}"; do
- # XXX: ei toimi, koska conf:it riippuu toisistaan include:ien takia
- # check_dhcp_conf $conf
-
- # conf base
- copy_dhcp_conf $conf
- done
+function deploy {
+ ## Check
+ log "Testing zones..."
+ for zone in $(list_files etc/zones); do
+ check_zone "var/zones/$zone" $zone
+ done
- log "Testing dhcp..."
- # checks the whole dhcpd.conf, with all includes..
- check_dhcp
-}
+ log "Testing DHCP confs..."
+ for conf in var/dhcp/*.conf; do
+ check_dhcp $conf
+ done
-# Runs DHCP checks, once DNS hosts have been updated
-function run_dhcp_check {
- log "Testing dhcp hosts..."
- for conf in "${DHCP_CONFS[@]}"; do
- check_dhcp_hosts $DHCP/$conf.conf
- done
-}
-
-function run_deploy {
- ## Reload zones
log "Reload zones..."
reload_zones
- ## DHCP
- run_dhcp_check
-
log "Reload dhcp..."
reload_dhcp
- ## Commit
- log "Commit data..."
- commit_data
}
## Main entry point
function main {
- # test tty
- [ -t 1 ] && IS_TTY=y
-
parse_args "$@"
## Input dirs
- [ -d $ROOT/$DATA ] || die "Missing data: $ROOT/$DATA"
+ for dir in etc etc/zones etc/hosts opt; do
+ [ -d $dir ] || die "Missing directory: $dir"
+ done
## Output dirs
- for dir in $VAR $DHCP $ZONES $SERIALS; do
+ ensure_dir var
+ for dir in var/dhcp var/zones var/include-cache var/serials; do
ensure_dir $dir
done
-
- # sub-$ZONES
- for dir in "common" "hosts" "includes" "${VIEWS[@]}"; do
- ensure_dir $ZONES/$dir
+ for dir in var/dhcp/hosts; do
+ ensure_dir $dir
+ done
+ for dir in var/zones/includes var/zones/hosts; do
+ ensure_dir $dir
+ done
+ for dir in var/zones/hosts/forward var/zones/hosts/reverse; do
+ ensure_dir $dir
done
## Go
- run_hosts
- run_zones
- run_dhcp
- run_deploy
+ commit
+ update
+ deploy
}
main "$@"
--- a/bin/update-serial Fri May 10 00:05:25 2013 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,195 +0,0 @@
-#!/usr/bin/env python
-# vim: set ft=python :
-
-"""
- Update zone serials.
-"""
-
-__version__ = '0.0.1-dev'
-
-import optparse
-import codecs
-import os.path
-from datetime import datetime
-import logging
-
-log = logging.getLogger('main')
-
-# command-line options, global state
-options = None
-
-def parse_options (argv) :
- """
- Parse command-line arguments.
- """
-
- prog = argv[0]
-
- parser = optparse.OptionParser(
- prog = prog,
- usage = '%prog: [options]',
- version = __version__,
-
- # module docstring
- description = __doc__,
- )
-
- # logging
- general = optparse.OptionGroup(parser, "General Options")
-
- general.add_option('-q', '--quiet', dest='loglevel', action='store_const', const=logging.ERROR, help="Less output")
- general.add_option('-v', '--verbose', dest='loglevel', action='store_const', const=logging.INFO, help="More output")
- general.add_option('-D', '--debug', dest='loglevel', action='store_const', const=logging.DEBUG, help="Even more output")
-
- parser.add_option_group(general)
-
- # defaults
- parser.set_defaults(
- loglevel = logging.WARN,
- expand = [],
- )
-
- # parse
- options, args = parser.parse_args(argv[1:])
-
- # configure
- logging.basicConfig(
- format = prog + ': %(name)s: %(levelname)s %(funcName)s : %(message)s',
- level = options.loglevel,
- )
-
- return options, args
-
-# date fmt to use in serial
-DATE_FMT = '%Y%m%d'
-DATE_LEN = 8
-
-SERIAL_FMT = "{date:8}{count:02}"
-SERIAL_LEN = 10
-
-def format_serial (date, count) :
- return SERIAL_FMT.format(date=date.strftime(DATE_FMT), count=count)
-
-def next_count (value, date, count) :
- """
- Return serial with next count.
- """
-
- count += 1
-
- # check
- if count > 99 :
- serial = str(value + 1)
-
- log.warn("Serial count rollover: %s, %s; fallback -> %s", date, count, serial)
-
- return serial
-
- return format_serial(date, count)
-
-def update_serial (serial) :
- """
- Update new serial number into given file, based on date.
- """
-
- today = datetime.now().date()
-
- # handle
- if not serial :
- # fresh
- log.info("Setting initial serial: %s01", today)
-
- return format_serial(today, 1)
-
- elif len(serial) != SERIAL_LEN :
- log.warn("Invalid serial format: %s", serial)
-
- value = int(serial)
- serial = str(value + 1)
-
- log.info("Incrementing serial: %d -> %s", value, serial)
-
- return serial
-
- else :
- # parse
- value = int(serial)
-
- try :
- date = datetime.strptime(serial[:DATE_LEN], DATE_FMT).date()
- count = int(serial[DATE_LEN:])
-
- except ValueError, e :
- # invalid date/count format?
- log.warn("Unable to parse serial: %s: %s", serial, e)
-
- serial = str(value + 1)
-
- log.info("Incrementing serial: %d -> %s", value, serial)
-
- return serial
-
- log.debug("old serial=%s, value=%d, date=%s, count=%s", serial, value, date, count)
-
- # update
- if date < today :
- log.info("Updating to today: %s -> %s", date, today)
-
- # update date
- return format_serial(today, 1)
-
- elif date == today :
- # keep date, update count
- log.info("Updating today's count: %s, %s", date, count)
-
- # handle count rollover
- return next_count(value, date, count)
-
- elif date > today :
- # keep, update count
- serial = next_count(value, date, count)
-
- log.warn("Serial in future; incrementing count: %s, %s -> %s", date, count, serial)
-
- return serial
-
- else :
- raise Exception("Invalid serial: %s:%s", old_date, old_count)
-
-def process_serial (path) :
- """
- Read old serial, generate new one, and update file.
- """
-
- # read
- if os.path.exists(path) :
- serial = open(path).read().strip()
- log.debug("current serial: %s", serial)
-
- else :
- log.warn("Given .serial does not yet exist: %s", path)
- serial = None
-
- # update
- serial = update_serial(serial)
-
- # write
- open(path, 'w').write(serial + '\n')
-
- return serial
-
-def main (argv) :
- global options
-
- options, args = parse_options(argv)
-
- # serial files to update
- for path in args :
- process_serial(path)
-
- return 0
-
-if __name__ == '__main__':
- import sys
-
- sys.exit(main(sys.argv))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/update Sat Dec 21 22:43:38 2013 +0200
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+
+## Strict errors
+set -ue
+
+## Library includes
+for lib in lib/update.*; do
+ source $lib
+done
+
--- a/lib/update.args Fri May 10 00:05:25 2013 +0300
+++ b/lib/update.args Sat Dec 21 22:43:38 2013 +0200
@@ -1,7 +1,7 @@
#!/bin/bash
# vim: set ft=sh :
#
-# Command-line option handling
+# Command-line options
# use color output?
IS_TTY=
@@ -70,6 +70,9 @@
## Parse any command-line arguments, setting the global options vars.
function parse_args {
+ # test tty
+ [ -t 1 ] && IS_TTY=y
+
OPTIND=1
while getopts 'hd:qvDVpFSsnCcm:Rr' opt "$@"; do
@@ -79,7 +82,7 @@
exit 0
;;
- d) ROOT="$OPTARG" ;;
+ d) SRV="$OPTARG" ;;
q)
LOG=
@@ -106,7 +109,7 @@
UPDATE_NOOP=y
# implies -Sp
UPDATE_DIFF=y
- SERIAL_NOUPDATE=y
+ SERIAL_NOOP=y
COMMIT_SKIP=y
RELOAD_NOOP=y
;;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/update.config Sat Dec 21 22:43:38 2013 +0200
@@ -0,0 +1,16 @@
+# charset for files under etc/
+CHARSET='utf-8'
+
+# External bins
+NAMED_CHECKZONE=/usr/sbin/named-checkzone
+
+DHCPD=/usr/sbin/dhcpd
+DHCPD_CONF=/etc/dhcp/dhcpd.conf
+DHCPD_INIT=/etc/init.d/isc-dhcp-server
+
+HG=/usr/bin/hg
+HG_ARGS=(--config trusted.users=root)
+
+RNDC=/usr/sbin/rndc
+RNDC_KEY=/etc/bind/rndc.key
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/update.hg Sat Dec 21 22:43:38 2013 +0200
@@ -0,0 +1,75 @@
+#!/bin/bash
+#
+# HG wrappers
+
+## Run `hg ...` within $REPO.
+function hg {
+ local repo=$1; shift
+ cmd $HG -R "$repo" "${HG_ARGS[@]}" "$@"
+}
+
+## Does the repo have local modifications?
+function hg_modified {
+ hg $1 id -i | grep -q '+'
+}
+
+## Get the date for the current commit as an unix timestamp
+function hg_time {
+ local repo=$1
+ local hg_unix=
+ local hg_tz=
+
+ local hg_date=$(hg $repo log -r . --template '{date|hgdate}')
+ local hg_unix=${hg_date% *}
+ local hg_tz=${hg_date#* }
+
+ [ -n "$hg_unix" ] || fail "failed to read hg time"
+
+ echo "$hg_unix"
+}
+
+## Show changes in repo
+# hg_diff [path ...]
+function hg_diff {
+ local repo=$1; shift
+ hg $repo diff "$@"
+}
+
+## Commit changes in repo, with given message:
+#
+# hg_commit .../etc $msg
+#
+# Automatically determines possible -u to use when running with sudo.
+function hg_commit {
+ local repo="$1"
+ local msg="$2"
+ local user_opt=
+ local msg_opt=
+
+ if [ ${SUDO_USER:-} ]; then
+ user_opt=('-u' "$SUDO_USER")
+
+ elif [ $HOME ] && [ -e $HOME/.hgrc ]; then
+ debug "using .hgrc user"
+ user_opt=( )
+
+ else
+ user_opt=('-u' "$USER")
+ fi
+
+ if [ "$msg" ]; then
+ msg_opt=('-m' "$msg")
+ fi
+
+ # XXX: there's something about bash arrays that I don't like... empty arrays behave badly
+ # mercurial does not like it if you pass it '' as an argument
+ if [ -n "${user_opt:-}" -a -n "${msg_opt:-}" ]; then
+ hg $repo commit "${user_opt[@]}" "${msg_opt[@]}"
+ elif [ -n "${user_opt:-}" ]; then
+ hg $repo commit "${user_opt[@]}"
+ elif [ -n "${msg_opt:-}" ]; then
+ hg $repo commit "${msg_opt[@]}"
+ else
+ hg $repo commit
+ fi
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/update.log Sat Dec 21 22:43:38 2013 +0200
@@ -0,0 +1,107 @@
+#!/bin/bash
+# vim: set ft=sh :
+#
+# Logging output
+
+# Output message to stderr.
+function log_msg {
+ echo "$*" >&2
+}
+
+# Output message to stderr, optionally with given color, if TTY.
+function log_color {
+ local code=$1; shift
+
+ if [ $IS_TTY ]; then
+ echo $'\e['${code}'m'"$*"$'\e[00m' >&2
+ else
+ echo "$*" >&2
+ fi
+}
+
+## Log at various log-levels
+# plain
+function log {
+ [ $LOG ] && log_msg "$*" || true
+}
+
+function log_error {
+ [ $LOG_ERROR ] && log_color '31' "$*" || true
+}
+
+function log_warn {
+ [ $LOG_WARN ] && log_color '33' "$*" || true
+}
+
+function log_force {
+ [ $LOG_FORCE ] && log_color '2;33' " $*" || true
+}
+
+function log_update {
+ [ $LOG_UPDATE ] && log_color '36' " $*" || true
+}
+
+function log_check {
+ [ $LOG_UPDATE ] && log_color '37' " $*" || true
+}
+
+function log_noop {
+ [ $LOG_NOOP ] && log_color '2;34' " $*" || true
+}
+
+function log_skip {
+ [ $LOG_SKIP ] && log_color '1;34' " $*" || true
+}
+
+function log_debug {
+ [ $LOG_DEBUG ] && log_color '32' " $*" || true
+}
+
+function log_cmd {
+ [ $LOG_CMD ] && log_color '35' " \$ $*" || true
+}
+
+# Output stacktrace, broken.
+function log_stack {
+ local level=1
+
+ while info=$(caller $level); do
+ echo $info | read line sub file
+
+ log_msg "$file:$lineno $sub()"
+
+ level=$(($level + 1))
+ done
+}
+
+# Output calling function's name.
+function func_caller {
+ caller 1 | cut -d ' ' -f 2
+}
+
+### High-level logging output
+# Log with func_caller at log_debug
+function debug {
+ printf -v prefix "%s" $(func_caller)
+
+ log_debug "$prefix: $*"
+}
+
+function warn {
+ log_warn "$(func_caller): $*"
+}
+
+# Log with func_caller at log_error and exit, intended for internal errors...
+function fail {
+ log_error "$(func_caller): $*"
+
+ exit 2
+}
+
+# Log at log_error and exit
+function die {
+ log_error "$*"
+ exit 1
+}
+
+
--- a/lib/update.logging Fri May 10 00:05:25 2013 +0300
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-#!/bin/bash
-# vim: set ft=sh :
-#
-# Logging output
-
-# Output message to stderr.
-function log_msg {
- echo "$*" >&2
-}
-
-# Output message to stderr, optionally with given color, if TTY.
-function log_color {
- local code=$1; shift
-
- if [ $IS_TTY ]; then
- echo $'\e['${code}'m'"$*"$'\e[00m' >&2
- else
- echo "$*" >&2
- fi
-}
-
-## Log at various log-levels
-
-function log_error {
- [ $LOG_ERROR ] && log_color '31' "$*"
-}
-
-function log_warn {
- [ $LOG_WARN ] && log_color '33' "$*" || true
-}
-
-# plain
-function log {
- [ $LOG ] && log_msg "$*" || true
-}
-
-function log_force {
- [ $LOG_FORCE ] && log_color '2;33' " $*" || true
-}
-
-function log_update {
- [ $LOG_UPDATE ] && log_color '36' " $*" || true
-}
-
-function log_noop {
- [ $LOG_NOOP ] && log_color '2;34' " $*" || true
-}
-
-function log_skip {
- [ $LOG_SKIP ] && log_color '1;34' " $*" || true
-}
-
-function log_debug {
- [ $LOG_DEBUG ] && log_color 32 " $*" || true
-}
-
-function log_cmd {
- [ $LOG_CMD ] && log_color 35 " \$ $*" || true
-}
-
-# Output stacktrace, broken.
-function log_stack {
- local level=1
-
- while info=$(caller $level); do
- echo $info | read line sub file
-
- log_msg "$file:$lineno $sub()"
-
- level=$(($level + 1))
- done
-}
-
-# Output calling function's name.
-function func_caller {
- caller 1 | cut -d ' ' -f 2
-}
-
-### High-level logging output
-# Log with func_caller at log_debug
-function debug {
- printf -v prefix "%s" $(func_caller)
-
- log_debug "$prefix: $*"
-}
-
-# Log with func_caller at log_error and exit, intended for internal errors...
-function fail {
- log_error "$(func_caller): $*"
-
- exit 2
-}
-
-# Log at log_error and exit
-function die {
- log_error "$*"
- exit 1
-}
-
-
--- a/lib/update.operations Fri May 10 00:05:25 2013 +0300
+++ b/lib/update.operations Sat Dec 21 22:43:38 2013 +0200
@@ -3,29 +3,29 @@
#
# Operations on zonefiles/hosts/whatever
-function link_generic {
- local out=$1
- local tgt=$2
+function link {
+ local out="$1"
+ local tgt="$2"
- if check_link $out $tgt; then
+ if check_link "$out" "$tgt"; then
log_update "Linking $out -> $tgt..."
- do_link $out $tgt
+ do_link "$out" "$tgt"
else
log_skip "Linking $out -> $tgt: not changed"
fi
}
-function copy_generic {
- local out=$1
- local src=$2
+function copy {
+ local out="$1"
+ local src="$2"
- if check_update $out $src; then
+ if check_update "$out" "$src"; then
log_update "Copying $out <- $src..."
- do_update $out \
- cat $ROOT/$src
+ do_update "$out" \
+ cat "$src"
else
log_skip "Copying $out <- $src: not changed"
fi
@@ -33,271 +33,185 @@
## Run check-command on given file, outputting results:
#
-# check_generic $src $cmd $args...
+# check $src $cmd $args...
#
-function check_generic {
- local src=$1; shift
- local cmd=$1; shift
+function check {
+ local src="$1"; shift
+ local cmd="$1"; shift
- if cmd_test $cmd -q "$@"; then
+ if cmd_test "$cmd" -q "$@"; then
log_skip "Check $src: OK"
else
log_error " Check $src: Failed"
- indent " " $cmd "$@"
+ indent " " "$cmd" "$@"
exit 1
fi
}
-## Hosts
-## Update hosts from verbatim from input zone data:
-#
-# copy_hosts $ZONES/$zone $DATA/$base
+## Generate forward zone from hosts hosts using pvl.hosts-dns:
#
-# Writes updated zone to $zone, deps on $base.
-function copy_hosts {
- local zone=$1
- local base=$2
+# update_hosts_forward out/hosts/$hosts $hosts in/hosts/$hosts
+function update_hosts_forward {
+ local out="$1"; shift
+ local domain="$1"; shift
- # XXX: filenames given directly
- local out=$zone
- local src=$base
-
- copy_generic $out $src
+ if check_update "$out" "$@"; then
+ log_update "Generating forward hosts zone $out @ $domain <- $@..."
+
+ do_update "$out" $OPT/bin/pvl.hosts-dns \
+ --hosts-charset=$CHARSET \
+ --forward-zone="$domain" \
+ "$@"
+ else
+ log_skip "Generating forward hosts $out <- $@: not changed"
+ fi
}
-## Generate hosts from input zone data using $BIN/process-zone:
-#
-# update_hosts $ZONES/$zone $DATA/$base
+function update_hosts_dhcp {
+ local out=$1; shift
+ local src=$1; shift
+
+ if check_update $out $src "$@"; then
+ log_update "Generating DHCP hosts $out <- $src..."
+
+ do_update $out $OPT/bin/pvl.hosts-dhcp \
+ --hosts-charset=$CHARSET \
+ $src "$@"
+ else
+ log_skip "Generating DHCP hosts $out <- $src: not changed"
+ fi
+}
+
+## Generate reverse zone from hosts hosts using pvl.hosts-dns:
#
-# Writes process-zone'd data to $zone, deps on $base.
-function update_hosts {
- local zone=$1; shift
- local base=$1; shift
+# update_hosts_reverse out/hosts/$reverse $reverse in/hosts/$hosts
+function update_hosts_reverse {
+ local out="$1"; shift
+ local reverse="$1"; shift
- if check_update $zone $base; then
- log_update "Generating hosts $zone <- $base..."
-
- do_update $zone \
- $BIN/process-zone $HOSTS_FILE_ARGS $ROOT/$base "$@"
+ if check_update "$out" "$@"; then
+ log_update "Generating reverse hosts zone $out <- $@..."
+
+ do_update "$out" $OPT/bin/pvl.hosts-dns \
+ --hosts-charset=$CHARSET \
+ --reverse-zone="$reverse" \
+ "$@"
else
- log_skip "Generating hosts $zone <- $base: not changed"
+ log_skip "Generating reverse hosts $out <- $@: not changed"
fi
}
## Update .serial number:
#
-# do_update_serial $serial
+# do_update_serial .../serials/$zone $serial
#
-# Shows old/new serial on debug.
function do_update_serial {
- local serial=$1
+ local dst="$1"
+ local serial="$2"
- # read
- local old=$(test -e $ROOT/$serial && cat $ROOT/$serial || echo '')
-
-
- cmd $BIN/update-serial $ROOT/$serial
-
- # read
- local new=$(cat $ROOT/$serial)
-
- debug " $old -> $new"
+ echo $serial > $dst
}
-## Generate new serial for zone using $BIN/update-serial, if the zone data has changed:
+## Generate new serial for zone using pvl.dns-serial, if the zone data has changed:
#
-# update_serial $zone $deps...
+# update_serial .../serials/$zone $serial $deps...
#
# Supports SERIAL_FORCE/NOOP.
# Updates $SERIALS/$zone.serial.
function update_serial {
- local zone=$1; shift
+ local dst="$1"; shift
+ local serial="$1"; shift
+
+ local old=$(test -e "$dst" && cat "$dst" || echo '')
- local serial=$SERIALS/$zone.serial
-
# test
if [ $SERIAL_FORCE ]; then
- log_force "Updating $serial: forced"
+ log_force "Updating $dst: $old <- $serial: forced"
- do_update_serial $serial
+ do_update_serial "$dst" "$serial"
- elif ! check_update $serial "$@"; then
- log_skip "Updating $serial: not changed"
+ elif ! check_update "$dst" "$@"; then
+ log_skip "Updating $dst: $old <- $serial: not changed"
elif [ $SERIAL_NOOP ]; then
- log_noop "Updating $serial: skipped"
+ log_noop "Updating $dst: $old <- $serial: skipped"
else
- log_update "Updating $serial..."
+ log_update "Updating $dst: $old <- $serial"
- do_update_serial $serial
+ do_update_serial "$dst" "$serial"
fi
}
-## Link serial for zone from given base-zone:
+## Generate zone file from source using pvl.dns-zone:
#
-# link_serial $zone $base
-function link_serial {
- local zone=$1
- local base=$2
-
- local out=$SERIALS/$zone.serial
- local tgt=$SERIALS/$base.serial
-
- link_generic $out $tgt
-}
-
-## Update zone file verbatim from source:
-#
-# copy_zone $view $zone [$base]
-#
-# Copies changed $DATA/$base zone data to $ZONES/$view/$zone.
-function copy_zone {
- local view=$1
- local zone=$2
- local base=${3:-$zone}
-
- local out=$ZONES/$view/$zone
- local src=$DATA/$base
-
- copy_generic $out $src
-}
+# update_zone out/zones/$zone in/zones/$zone var/serials/$zone
+function update_zone {
+ local out="$1"; shift
+ local src="$1"; shift
+ local serial="$1"; shift
+ local serial_opt=
-## Return the first zone that exists under $DATA/$name.zone
-#
-# base=$(choose_zone $name...)
-function choose_zone {
- # look
- for name in "$@"; do
- if [ $name ] && [ -e $DATA/$name.zone ]; then
- echo $name
- return 0
- fi
- done
-
- # failed to find
- die "Unable to find zone in $DATA/*.zone: $@"
-}
+ if [ -n "$serial" -a -f "$serial" ]; then
+ serial_opt="--serial=$(cat "$serial")"
+ elif [ $SERIAL_NOOP ]; then
+ warn "$out: noop'd serial, omitting"
+ else
+ fail "$out: missing serial: $serial"
+ fi
-## Expand zone file from source using $BIN/expand-zone:
-#
-# update_zone $view $zone [$base]
-#
-# Updates $ZONES/$view/$zone from $DATA/$base.{zone,serial} using $BIN/expand-zone.
-function update_zone {
- local view=$1
- local zone=$2
- local base=${3:-$zone}
-
- # zones <- data+serial file
- local out=$ZONES/$view/$zone
- local src=$DATA/$base.zone
- local serial=$SERIALS/$base.serial
-
- if check_update $out $src $serial; then
+ if check_update "$out" "$src" "$serial" "$@"; then
log_update "Generating $out <- $src..."
- do_update $out \
- $BIN/expand-zone $ROOT/$src \
- --serial $ROOT/$serial \
- --expand zones=$(abspath $ZONES) \
- --expand view=$view
+ do_update "$out" $OPT/bin/pvl.dns-zone "$src" \
+ --include-path=$SRV/var/zones \
+ $serial_opt
else
log_skip "Generating $out <- $src: not changed"
fi
}
-## Link zone file to ues given shared zone.
-#
-# link_zone $view $zone [$base]
-#
-# Looks for shared zone at:
-# $ZONES/$view/$base
-# $ZONES/common/$base
-function link_zone {
- local view=$1
- local zone=$2
- local base=${3:-$zone}
+## Generate dhcp confs from source using pvl.dhcp-conf:
+function update_dhcp_conf {
+ local out="$1"
+ local src="$2"
- local out=$ZONES/$view/$zone
- local tgt=$(choose_link $out $ZONES/$view/$base $ZONES/common/$base)
-
- link_generic $out $tgt
-}
-
-## Link dhcp file directly from data to $DHCP
-function link_dhcp_conf {
- local conf=$1
- local base=${2:-$conf}
-
- local out=$DHCP/$conf.conf
- local tgt=$(choose_link $out $DHCP/$base.conf $DHCP_DATA/$base.conf)
-
- link_generic $out $tgt
+ if check_update "$out" "$src"; then
+ log_update "Generating $out <- $src..."
+
+ do_update "$out" $OPT/bin/pvl.dhcp-conf "$src" \
+ --include-path=$SRV/var/dhcp
+ else
+ log_skip "Generating $out <- $src: not changed"
+ fi
}
-## Copy dhcp conf from data to $DHCP
-function copy_dhcp_conf {
- local conf=$1
- local base=${2:-$conf}
-
- local out=$DHCP/$conf.conf
- local src=$DHCP_DATA/$base.conf
-
- copy_generic $out $src
-}
-
-## Test hosts zone for validity:
+## Test hosts zone for validity using pvl.hosts-check:
#
-# check_hosts $DATA/$hosts --check-exempt ...
-#
-# Fails if the check fails.
+# check_hosts .../hosts
function check_hosts {
local hosts=$1; shift 1
-
- local cmd=($BIN/process-zone $HOSTS_FILE_ARGS $ROOT/$hosts --check-hosts "$@")
-
- if "${cmd[@]}" -q; then
- log_skip "Check $hosts: OK"
- else
- log_error " Check $hosts: Failed"
-
- indent " " "${cmd[@]}"
-
- exit 1
- fi
+
+ # TODO
+ check $hosts \
+ $OPT/bin/pvl.hosts-check $hosts
}
## Test zone file for validity using named-checkzone:
#
-# check_zone $view $zone $origin
-#
-# Uses the zonefile at $ZONES/$view/$zone, loading it with given initial $ORIGIN.
-# Fails if the check fails.
+# check_zone ..../$zone $origin
function check_zone {
- local view=$1
- local zone=$2
- local origin=$3
-
- local src=$ZONES/$view/$zone
+ local zone=$1
+ local origin=$2
- local cmd=($NAMED_CHECKZONE $origin $ROOT/$src)
+ log_check "Checking $zone @ $origin..."
- # test
- # XXX: checkzone is very specific about the order of arguments, -q must be first
- if cmd_test $NAMED_CHECKZONE -q $origin $ROOT/$src; then
- log_skip "Check $src ($origin): OK"
- else
- log_error " Check $src ($origin): Failed:"
-
- indent " " "${cmd[@]}"
-
- exit 1
- fi
+ # checkzone is very specific about the order of arguments, -q must be first
+ check $zone $NAMED_CHECKZONE $origin $zone
}
## Test DHCP configuration for validity using dhcpd -t:
@@ -308,13 +222,15 @@
# Fails if the check fails.
function check_dhcp {
local conf=${1:-$DHCPD_CONF}
+
+ log_check "Checking DHCP $conf..."
if [ ! -e $DHCPD ]; then
log_warn "check_dhcp: dhcpd not installed, skipping: $conf"
return 0
fi
- check_generic $conf \
+ check $conf \
$DHCPD -cf $conf -t
}
@@ -328,26 +244,11 @@
check_dhcp $DHCP_DATA/$conf.conf
}
-## Test DHCP hosts source configuration for invalid fixed-address stanzas:
-#
-# check_dhcp_hosts $hosts_conf
-#
-function check_dhcp_hosts {
- local hosts=$1
-
- # XXX: still too unclear
- local src=$hosts #$DHCP_DATA/$hosts.conf
+### Deploy
+# set by do_reload_zone if zone data has actually been reloaded
+RELOAD_ZONES=
- # set in do_reload_zones below
- if [ $RELOAD_ZONES ]; then
- check_generic $src \
- $BIN/check-dhcp-hosts $DHCP_FILE_ARGS $ROOT/$src
- else
- log_noop "Check $src: skipped; did not reload DNS zones"
- fi
-}
-
-# Run rndc reload
+## Run rndc reload
function do_reload_zones {
# run
indent " rndc: " \
@@ -432,45 +333,39 @@
fi
}
-## Perform `hg commit` for $DATA
-function do_commit {
- local msg=$1
-
- [ $LOG_DIFF ] && indent " " hg_diff
-
- hg_commit "$msg"
-}
-
-
-## Commit changes in $DATA to version control:
+### Commit
+## Commit changes to version control:
#
-# commit_data
+# update_commit .../etc "commit message"
#
-# Invokes `hg commit` in the $REPO, first showing the diff.
-function commit_data {
- local repo=$REPO
+# Invokes `hg commit`, first showing the diff.
+function update_commit {
+ local repo="$1"
local commit_msg="$COMMIT_MSG"
- local msg="Commit changes in $repo"
+ local msg="Commit changes"
# operate?
if [ $COMMIT_FORCE ]; then
- log_force "$msg..."
+ log_force "$msg: $commit_msg"
- do_commit "$commit_msg"
+ [ $LOG_DIFF ] && indent " " hg_diff $repo
- elif ! hg_modified; then
- log_skip "$msg: no changes"
+ hg_commit "$repo" "$commit_msg"
+
+ elif ! hg_modified "$repo"; then
+ log_warn "$msg: no changes"
elif [ $COMMIT_SKIP ]; then
log_noop "$msg: skipped"
# still show diff, though
- [ $LOG_DIFF ] && indent " " hg_diff
+ [ $LOG_DIFF ] && indent " " hg_diff "$repo"
else
- log_update "$msg..."
+ log_update "$msg: $commit_msg"
- do_commit "$commit_msg"
+ [ $LOG_DIFF ] && indent " " hg_diff $repo
+
+ hg_commit "$repo" "$commit_msg"
fi
}
-
--- a/lib/update.updates Fri May 10 00:05:25 2013 +0300
+++ b/lib/update.updates Sat Dec 21 22:43:38 2013 +0200
@@ -10,7 +10,7 @@
# Returns true if the output file needs to be updated.
function check_update {
# target
- local out=$1; shift
+ local out="$1"; shift
debug "$out"
@@ -21,7 +21,7 @@
debug " update: unknown deps"
update=y
- elif [ ! -e $out ]; then
+ elif [ ! -e "$out" ]; then
debug " update: dest missing"
update=y
@@ -36,10 +36,10 @@
[ $update ] && continue
# check
- if [ ! -e $ROOT/$dep ]; then
- fail "$out: Missing source: $dep"
+ if [ ! -e "$dep" ]; then
+ warn "$out: Missing source: $dep"
- elif [ $ROOT/$out -ot $ROOT/$dep ]; then
+ elif [ "$out" -ot "$dep" ]; then
debug " update: $dep"
update=y
else
@@ -60,18 +60,18 @@
# Writes output to a temporary .new file, optionally shows a diff of changes, and commits
# the new version to $out (unless noop'd).
function do_update {
- local out=$1; shift
- local tmp=$out.new
+ local out="$1"; shift
+ local tmp="$out.new"
debug "$out"
- cmd "$@" > $ROOT/$tmp
+ cmd "$@" > "$tmp"
# compare
- if [ -e $ROOT/$out ] && [ $UPDATE_DIFF ]; then
+ if [ -e "$out" ] && [ $UPDATE_DIFF ]; then
debug " changes:"
# terse
- indent " " diff --unified=1 $ROOT/$out $ROOT/$tmp || true
+ indent " " diff --unified=1 "$out" "$tmp" || true
fi
# deploy
@@ -79,33 +79,15 @@
# cleanup
debug " no-op"
- cmd rm $ROOT/$tmp
+ cmd rm "$tmp"
else
# commit
debug " deploy"
- cmd mv $ROOT/$tmp $ROOT/$out
+ cmd mv "$tmp" "$out"
fi
}
-## Look for a link target:
-#
-# find_link $lnk $tgt...
-#
-# Outputs the first given target to exist, skipping any that are the same as the given $lnk.
-# If no $tgt matches, outputs the last one, or '-'.
-function choose_link {
- local lnk=$1; shift
- local tgt=-
-
- for tgt in "$@"; do
- [ $tgt != $out ] && [ -e $ROOT/$tgt ] && break
- done
-
- echo $tgt
-}
-
-
## Compare symlink to target:
#
# check_link $lnk $tgt && do_link $lnk $tgt || ...
@@ -113,12 +95,12 @@
# Tests if the symlink exists, and the target matches.
# Fails if the target does not exist.
function check_link {
- local lnk=$1
- local tgt=$2
+ local lnk="$1"
+ local tgt="$2"
- [ ! -e $ROOT/$tgt ] && fail "$tgt: target does not exist"
+ [ ! -e "$tgt" ] && fail "$tgt: target does not exist"
- [ ! -e $ROOT/$lnk ] || [ $(readlink $ROOT/$lnk) != $ROOT/$tgt ]
+ [ ! -e "$lnk" ] || [ $(readlink "$lnk") != "$tgt" ]
}
## Update symlink to point to target:
@@ -126,9 +108,41 @@
# do_link $lnk $tgt
#
function do_link {
- local lnk=$1
- local tgt=$2
+ local lnk="$1"
+ local tgt="$2"
- cmd ln -sf $ROOT/$tgt $ROOT/$lnk
+ cmd ln -sf "$tgt" "$lnk"
}
+## Read include paths from file
+function read_zone_includes {
+ cmd sed -n -E 's/^\$INCLUDE\s+"(.+)"/\1/p' "$@"
+}
+
+## (cached) include paths for zone file
+function zone_includes {
+ local cache="$1"
+ local src="$2"
+ local prefix="${3:-}"
+
+ if [ ! -e "$cache" -o "$cache" -ot "$src" ]; then
+ read_zone_includes "$src" > "$cache"
+ fi
+
+ while read include; do
+ echo -n "$prefix$include "
+ done < "$cache"
+}
+
+## Search for prefix-matching includes in zone file
+function zone_includes_grep {
+ local cache="$1"
+ local src="$2"
+ local prefix="$3"
+
+ for include in $(zone_includes $cache $src); do
+ if [ "${include#$prefix}" != "$include" ]; then
+ echo -n " ${include#$prefix}"
+ fi
+ done
+}
--- a/lib/update.utils Fri May 10 00:05:25 2013 +0300
+++ b/lib/update.utils Sat Dec 21 22:43:38 2013 +0200
@@ -1,9 +1,7 @@
#!/bin/bash
-# vim: set ft=sh :
#
# Utility functions
-
### Command execution
## Execute command, possibly logging its execution.
#
@@ -13,10 +11,9 @@
function cmd {
log_cmd "$@"
- "$@" || die "Failed"
+ "$@" || die "Failed: $@"
}
-### Command execution
## Execute command as a test, logging its execution at log_cmd
#
# cmd_test $cmd... && ... || ...
@@ -32,11 +29,8 @@
# indent " " $cmd...
#
# Output is kept on stdout, exit status is that of the given command.
-# Also logs the executed command at log_cmd level..
function indent () {
- local indent=$1; shift
-
- log_cmd "$@"
+ local indent="$1"; shift
"$@" | sed "s/^/$indent/"
@@ -45,24 +39,25 @@
### FS utils
-# Create dir in $ROOT if not exists.
+# Create dir if not exists.
function ensure_dir {
- local dir=$1
+ local dir="$1"
- if [ ! -d $ROOT/$dir ]; then
+ if [ ! -d "$dir" ]; then
log_warn "Creating output dir: $dir"
- cmd mkdir $ROOT/$dir
+ cmd mkdir "$dir"
fi
}
-## Output absolute path from $ROOT:
+## Output absolute path
#
# abspath $path
#
+# XXX: improve...?
function abspath () {
- local path=$1
+ local path="$1"
- echo "$ROOT/$path"
+ echo "$SRV/$path"
}
## List names of files in dir:
@@ -70,11 +65,14 @@
# list_files $dir $glob
#
function list_files {
- local dir=$1
- local glob=$2
+ local dir="$1"
+ local glob="${2:-*}"
local name=
for file in $dir/$glob; do
+ # only files
+ [ -f "$file" ] || continue
+
# strip prefix
name=${file#$dir/}
name=${name%$glob}
@@ -83,50 +81,18 @@
done
}
-### HG wrappers
-# Run `hg ...` within $REPO.
-function hg {
- local repo=$REPO
+## List names of dirs in dir:
+function list_dirs {
+ local dir="$1"
- cmd $HG -R $ROOT/$repo "${HG_ARGS[@]}" "$@"
-}
+ for file in $dir/*; do
+ [ -d "$file" ] || continue
-# Does the repo have local modifications?
-function hg_modified {
- hg id | grep -q '+'
+ echo -n "${file#$dir/} "
+ done
}
-# Output possible -u flag for commit.
-function hg_user {
- if [ ${SUDO_USER:-} ]; then
- echo '-u' "$SUDO_USER"
-
- elif [ $HOME ] && [ -e $HOME/.hgrc ]; then
- debug "using .hgrc user"
- echo ''
-
- else
- echo '-u' "$USER"
- fi
+## Get current unix (utc) timestamp
+function unix_time {
+ date +'%s'
}
-
-# Show changes in repo
-function hg_diff {
- # just stat hidden files, but show the rest
- hg diff --stat -I "$REPO/$REPO_HIDE"
- hg diff -X "$REPO/$REPO_HIDE"
-}
-
-## Commit changes in repo, with given message:
-#
-# hg_commit $msg
-#
-function hg_commit {
- local msg=$1
- local user_opt=$(hg_user)
-
- debug "$user_opt: $msg"
- hg commit $user_opt -m "$msg"
-}
-
-