#!/usr/bin/env python # $Id$ # # Copyright (C) 2015-2016 Parsons Government Services ("PARSONS") # Portions copyright (C) 2014 Dragon Research Labs ("DRL") # Portions copyright (C) 2013 Internet Systems Consortium ("ISC") # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notices and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND PARSONS, DRL, AND ISC DISCLAIM # ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL # PARSONS, DRL, OR ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. import os import sys import argparse import base64 import textwrap from lxml.etree import Element, SubElement, ElementTree, Comment space4 = " " * 4 space6 = " " * 6 space8 = " " * 8 star78 = "*" * 78 wiki_wrapper = textwrap.TextWrapper() conf_wrapper = textwrap.TextWrapper(initial_indent = "# ", subsequent_indent = "# ") xml6_wrapper = textwrap.TextWrapper(initial_indent = space6, subsequent_indent = space6) xml8_wrapper = textwrap.TextWrapper(initial_indent = space8, subsequent_indent = space8) class Option(object): def __init__(self, name, value, doc): self.name = name self.value = value self.doc = doc @property def width(self): return len(self.name) def to_xml(self): x = Element("option", name = self.name) if self.value is not None: x.set("value", self.value) for d in self.doc: SubElement(x, "doc").text = "\n" + xml8_wrapper.fill(d) + "\n" + space6 return x def to_wiki(self, f): f.write("\n== {0.name} == #{0.name}\n".format(self)) for d in self.doc: f.write("\n{0}\n".format(wiki_wrapper.fill(d))) if self.value is None: f.write("\n{0}\n".format(wiki_wrapper.fill("No default value."))) else: f.write("\n{{{{{{\n#!ini\n{0.name} = {0.value}\n}}}}}}\n".format(self)) def to_conf(self, f, width): for i, d in enumerate(self.doc): f.write("{}\n{}\n".format("" if i == 0 else "#", conf_wrapper.fill(d))) if self.value is None: f.write("\n#{1.name:{0}} = ???\n".format(width - 1, self)) else: f.write("\n{1.name:{0}} = {1.value}\n".format(width, self)) class Section(object): def __init__(self, name): self.name = name self.doc = [] self.options = [] @property def width(self): return max(o.width for o in self.options) @classmethod def from_xml(cls, elt): self = cls(name = elt.get("name")) for x in elt.iterchildren("doc"): self.doc.append(" ".join(x.text.split())) for x in elt.iterchildren("option"): self.options.append(Option(name = x.get("name"), value = x.get("value"), doc = [" ".join(d.text.split()) for d in x.iterchildren("doc")])) return self def to_xml(self): x = Element("section", name = self.name) for d in self.doc: SubElement(x, "doc").text = "\n" + xml6_wrapper.fill(d) + "\n" + space4 x.extend(o.to_xml() for o in self.options) return x def to_wiki(self, f): f.write("\n= [{0}] section = #{0}\n".format(self.name)) for d in self.doc: f.write("\n{0}\n".format(wiki_wrapper.fill(d))) for o in self.options: o.to_wiki(f) def to_conf(self, f, width): f.write("\n" + "#" * 78 + "\n\n[" + self.name + "]\n") if self.doc: f.write("\n##") for i, d in enumerate(self.doc): f.write("{}\n{}\n".format("" if i == 0 else "#", conf_wrapper.fill(d))) f.write("##\n") for o in self.options: o.to_conf(f, width) def wiki_header(f, ident, toc): f.write(textwrap.dedent('''\ {{{{{{ #!comment {star78} THIS PAGE WAS GENERATED AUTOMATICALLY, DO NOT EDIT. Generated from {ident} by $Id$ {star78} }}}}}} '''.format(star78 = star78, ident = ident))) if toc is not None: f.write("[[TracNav({})]]\n".format(toc)) f.write("[[PageOutline]]\n") def conf_header(f, ident): f.write(textwrap.dedent('''\ # Automatically generated. Edit as needed, but be careful of overwriting. # # Generated from {ident} # by $Id$ '''.format(ident = ident))) # http://stackoverflow.com/questions/9027028/argparse-argument-order class CustomAction(argparse.Action): def __call__(self, parser, namespace, values, option_string = None): if not "ordered_args" in namespace: namespace.ordered_args = [] namespace.ordered_args.append((self.dest, values)) class CustomFlagAction(CustomAction): def __init__(self, option_strings, dest, default = None, required = False, help = None): # pylint: disable=W0622 super(CustomFlagAction, self).__init__( option_strings = option_strings, dest = dest, nargs = 0, const = None, default = default, required = required, help = help) class main(object): def __init__(self): self.sections = [] self.section_map = None self.option_map = None self.ident = None self.toc = None parser = argparse.ArgumentParser(description = __doc__) parser.add_argument("--read-xml", type = argparse.FileType("r"), metavar = "FILE", action = CustomAction, help = "XML input file defining sections and options", required = True) parser.add_argument("--write-xml", type = argparse.FileType("w"), metavar = "FILE", action = CustomAction, help = "XML output file to snapshot configuration") parser.add_argument("--write-conf", type = argparse.FileType("w"), metavar = "FILE", action = CustomAction, help = "rpki.conf configuration file to write") parser.add_argument("--write-wiki", type = argparse.FileType("w"), metavar = "FILE", action = CustomAction, help = "TracWiki file to write (monolithic)") parser.add_argument("--write-wiki-pages", metavar = "PATTERN", action = CustomAction, help = "TracWiki filenames (pattern) to write (one section per page)") parser.add_argument("--set", metavar = "VARVAL", action = CustomAction, help = "variable setting in form \"VAR=VAL\"") parser.add_argument("--pwgen", metavar = "VAR", action = CustomAction, help = "set variable to generated password") parser.add_argument("--toc", metavar = "TOCVAL", action = CustomAction, help = "set TOC value to use with TracNav plugin") parser.add_argument("--autoconf", action = CustomFlagAction, help = "configure [autoconf] section") args = parser.parse_args() for cmd, arg in args.ordered_args: getattr(self, "do_" + cmd)(arg) def do_read_xml(self, arg): self.option_map = None root = ElementTree(file = arg).getroot() self.ident = root.get("ident") self.sections.extend(Section.from_xml(x) for x in root.iterchildren("section")) self.option_map = {} self.section_map = {} for section in self.sections: if section.name in self.section_map: sys.exit("Duplicate section {}".format(section.name)) self.section_map[section.name] = section for option in section.options: name = (section.name, option.name) if name in self.option_map: sys.exit("Duplicate option {}::{}".format(*name)) self.option_map[name] = option def do_set(self, arg): try: name, value = arg.split("=", 1) section, option = name.split("::") except ValueError: sys.exit("Couldn't parse --set specification \"{}\"".format(arg)) name = (section, option) if name not in self.option_map: sys.exit("Couldn't find option {}::{}".format(*name)) self.option_map[name].value = value def do_pwgen(self, arg): try: section, option = arg.split("::") except ValueError: sys.exit("Couldn't parse --pwgen specification \"{}\"".format(arg)) name = (section, option) if name not in self.option_map: sys.exit("Couldn't find option {}::{}".format(name)) self.option_map[name].value = base64.urlsafe_b64encode(os.urandom(66)) def do_autoconf(self, ignored): try: import rpki.autoconf for option in self.section_map["autoconf"].options: try: option.value = getattr(rpki.autoconf, option.name) except AttributeError: pass except ImportError: sys.exit("rpki.autoconf module is not available") except KeyError: sys.exit("Couldn't find autoconf section") def do_write_xml(self, arg): x = Element("configuration", ident = self.ident) x.append(Comment(" Machine-editable configuration snapshot, generated automatically, do not touch ")) x.extend(s.to_xml() for s in self.sections) ElementTree(x).write(arg, pretty_print = True, encoding = "us-ascii") def do_write_wiki(self, arg): for i, section in enumerate(self.sections): if i == 0: wiki_header(arg, self.ident, self.toc) else: arg.write("\f\n") section.to_wiki(arg) def do_write_wiki_pages(self, arg): for section in self.sections: with open(arg % section.name, "w") as f: wiki_header(f, self.ident, self.toc) section.to_wiki(f) def do_write_conf(self, arg): conf_header(arg, self.ident) width = max(s.width for s in self.sections) for section in self.sections: section.to_conf(arg, width) def do_toc(self, arg): self.toc = arg if __name__ == "__main__": main()