From 4de9d1a26c03df046d918ec28a61b2ad004c1b82 Mon Sep 17 00:00:00 2001 From: r00t Date: Tue, 22 May 2018 06:01:18 -0400 Subject: [PATCH] checking in... --- TODO | 25 ++- bdisk/SSL.py | 9 +- bdisk/confgen.py | 4 +- bdisk/confparse.py | 254 +++++++++++++++++++++---------- bdisk/utils.py | 245 ++++++++++++++++++++++++----- docs/examples/multi_profile.xml | 20 +-- docs/examples/single_profile.xml | 12 +- docs/manual/TODO | 5 +- 8 files changed, 426 insertions(+), 148 deletions(-) diff --git a/TODO b/TODO index ec0678d..334588a 100644 --- a/TODO +++ b/TODO @@ -1,24 +1,23 @@ - write classes/functions - XML-based config +-x XML syntax +--- regex btags - case-insensitive? this can be represented in-pattern: + https://stackoverflow.com/a/9655186/733214 +-x configuration generator +--- print end result xml config to stderr for easier redirection? or print prompts to stderr and xml to stdout? +-- XSD for validation +-- Flask app for generating config? +-- TKinter (or pygame?) GUI? +--- https://docs.python.org/3/faq/gui.html +--- https://www.pygame.org/wiki/gui - ensure we use docstrings in a Sphinx-compatible manner? https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html at the very least document all the functions and such so pydoc's happy. -- better prompt display. i might include them as constants in a separate file - and then import it for e.g. confgen. or maybe a Flask website/app? + - locking - for docs, 3.x (as of 3.10) was 2.4M. -- GUI? at least for generating config... - Need ability to write/parse mtree specs (or a similar equivalent) for applying ownerships/permissions to overlay files -- SSL key gen: -import OpenSSL -k = OpenSSL.crypto.PKey() -k.generate_key(OpenSSL.crypto.TYPE_RSA, 4096) -x = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, - k, - cipher = 'aes256', - passphrase = 'test') - - need to package: python-hashid (https://psypanda.github.io/hashID/, https://github.com/psypanda/hashID, @@ -41,4 +40,4 @@ BUGS.SQUARE-R00T.NET bugs/tasks: #36: Allow parsing pkg lists with inline comments #39: Fix UEFI #40: ISO overlay (to add e.g. memtest86+ to final ISO) -#43: Support resuming partial tarball downloads (Accet-Ranges: bytes) \ No newline at end of file +#43: Support resuming partial tarball downloads (Accept-Ranges: bytes) \ No newline at end of file diff --git a/bdisk/SSL.py b/bdisk/SSL.py index a991649..0c91e27 100644 --- a/bdisk/SSL.py +++ b/bdisk/SSL.py @@ -2,4 +2,11 @@ import OpenSSL # https://cryptography.io/en/latest/x509/reference/#cryptography.x509.CertificateBuilder.sign # migrate old functions of bSSL to use cryptography # but still waiting on their recpipes. -# https://cryptography.io/en/latest/x509/tutorial/ \ No newline at end of file +# https://cryptography.io/en/latest/x509/tutorial/ +#import OpenSSL +#k = OpenSSL.crypto.PKey() +#k.generate_key(OpenSSL.crypto.TYPE_RSA, 4096) +#x = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, +# k, +# cipher = 'aes256', +# passphrase = 'test') \ No newline at end of file diff --git a/bdisk/confgen.py b/bdisk/confgen.py index 2af06d3..5b10e2e 100755 --- a/bdisk/confgen.py +++ b/bdisk/confgen.py @@ -442,6 +442,8 @@ class ConfGenerator(object): tarball_elem.attrib['flags'] = 'latest' tarball_elem.text = tarball['full_url'] print('\n++ SOURCES || {0} || CHECKSUM ++'.format(arch.upper())) + # TODO: explicit not being set for explicitly-set sums, + # and checksum string not actually added to those. investigate. chksum = lxml.etree.SubElement(source, 'checksum') _chksum_chk = prompt.confirm_or_no(prompt = ( '\nWould you like to add a checksum for the tarball? (BDisk ' @@ -502,7 +504,7 @@ class ConfGenerator(object): print('Invalid selection. Starting over.') continue else: - checksum_type == checksum_type[0] + checksum_type = checksum_type[0] chksum.attrib['explicit'] = "yes" chksum.text = checksum chksum.attrib['hash_algo'] = checksum_type diff --git a/bdisk/confparse.py b/bdisk/confparse.py index 2cc271e..e4102dd 100644 --- a/bdisk/confparse.py +++ b/bdisk/confparse.py @@ -1,15 +1,18 @@ -import copy -import re import os +import pprint +import re import utils -import validators import lxml.etree from urllib.parse import urlparse etree = lxml.etree +detect = utils.detect() +generate = utils.generate() +transform = utils.transform() +valid = utils.valid() class Conf(object): - def __init__(self, cfg, profile = None): + def __init__(self, cfg, profile = None, validate = False): """ A configuration object. @@ -37,27 +40,70 @@ class Conf(object): You can provide any combination of these (e.g. "profile={'id': 2, 'name' = 'some_profile'}"). """ - #self.raw = _detect_cfg(cfg) # no longer needed; in utils self.xml_suppl = utils.xml_supplicant(cfg, profile = profile) - self.profile = self.xml_suppl - self.xml = None - self.profile = None - # Mad props to https://stackoverflow.com/a/12728199/733214 - self.xpath_re = re.compile('(?<=(?= 1: + for item in ptrn_id: + try: + btag, expr = item.split('%', 1) + if btag not in self.btags: + continue + if item not in self.btags[btag]: + self.btags[btag][item] = None + #self.btags[btag][item] = expr # remove me? + if btag == 'xpath': + d[item] = (btag, expr) + elif btag == 'variable': + d[item] = (btag, self.btags['variable'][item]) + except ValueError: + return(d) + return(d) + + def get_profile(self, profile = None): + """Get a configuration profile. + + Get a configuration profile from the XML object and set that as a + profile object. If a profile is specified, attempt to find it. If not, + follow the default rules as specified in __init__. + """ + if profile: + # A profile identifier was provided + if isinstance(profile, str): + _profile_name = profile + profile = {} + for i in self.selector_ids: + profile[i] = None + profile['name'] = _profile_name + elif isinstance(profile, dict): + for k in self.selector_ids: + if k not in profile.keys(): + profile[k] = None + else: + raise TypeError('profile must be a string (name of profile), ' + 'a dictionary, or None') + xpath = '/bdisk/profile{0}'.format(self.xpath_selector(profile)) + self.profile = self.xml.xpath(xpath) + if len(self.profile) != 1: + raise ValueError('Could not determine a valid, unique ' + 'profile; please check your profile ' + 'specifier(s)') + else: + # We need the actual *profile*, not a list of matching + # profile(s). + self.profile = self.profile[0] + if not len(self.profile): + raise RuntimeError('Could not find the profile specified in ' + 'the given configuration') + else: + # We need to find the default. + profiles = [] + for p in self.xml.xpath('/bdisk/profile'): + profiles.append(p) + # Look for one named "default" or "DEFAULT" etc. + for idx, value in enumerate([e.attrib['name'].lower() \ + for e in profiles]): + if value == 'default': + #self.profile = copy.deepcopy(profiles[idx]) + self.profile = profiles[idx] + break + # We couldn't find a profile with a default name. Try to grab the + # first profile. + if self.profile is None: + # Grab the first profile. + if profiles: + self.profile = profiles[0] + else: + # No profiles found. + raise RuntimeError('Could not find any usable ' + 'configuration profiles') + return() + def get_path(self, element): path = element try: @@ -815,33 +1005,10 @@ class xml_supplicant(object): _dictmap = self.btags_to_dict(element.text) return(element) - def xpath_selector(self, selectors, - selector_ids = ('id', 'name', 'uuid')): + def xpath_selector(self, selectors): # selectors is a dict of {attrib:value} xpath = '' for i in selectors.items(): - if i[1] and i[0] in selector_ids: + if i[1] and i[0] in self.selector_ids: xpath += '[@{0}="{1}"]'.format(*i) - return(xpath) - - def btags_to_dict(self, text_in): - d = {} - ptrn_id = self.ptrn.findall(text_in) - if len(ptrn_id) >= 1: - for item in ptrn_id: - try: - btag, expr = item.split('%', 1) - if btag not in self.btags: - continue - if item not in self.btags[btag]: - self.btags[btag][item] = None - #self.btags[btag][item] = expr # remove me? - if btag == 'xpath': - d[item] = (btag, expr) - elif btag == 'variable': - d[item] = (btag, self.btags['variable'][item]) - except ValueError: - return(d) - return(d) - - + return(xpath) \ No newline at end of file diff --git a/docs/examples/multi_profile.xml b/docs/examples/multi_profile.xml index 1b10071..054661e 100644 --- a/docs/examples/multi_profile.xml +++ b/docs/examples/multi_profile.xml @@ -9,7 +9,7 @@ but now with the neat benefits of XPath! Everything you could do in build.ini's and more. See https://www.w3schools.com/xml/xpath_syntax.asp If you need a literal curly brace, double them (e.g. for "{foo}", use "{{foo}}"), - UNLESS it's in a {regex%...} placeholder/filter (as part of the expression). --> + UNLESS it's in a as part of the expression. Those are taken as literal strings. --> {xpath%../name/text()} A rescue/restore live environment. @@ -24,7 +24,7 @@ 5 + items. See the manual for more information. NO btags within the patterns is allowed. --> archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-x86_64\.tar\.gz$ archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-x86_64\.tar\.gz\.sig$ @@ -139,10 +139,10 @@ blank passphrase for all operations. --> - - {xpath%../../../../meta/dev/author/text()} - {xpath%../../../../meta/dev/email/text()} - for {xpath%../../../../meta/names/pname/text()} [autogenerated] | {xpath%../../../../meta/uri/text()} | {xpath%../../../../meta/desc/text()} + + {xpath%../../../meta/dev/author/text()} + {xpath%../../../meta/dev/email/text()} + for {xpath%../../../meta/names/pname/text()} [autogenerated] | {xpath%../../../meta/uri/text()} | {xpath%../../../meta/desc/text()} @@ -263,10 +263,10 @@ - - {xpath%../../../../meta/dev/author/text()} - {xpath%../../../../meta/dev/email/text()} - for {xpath%../../../../meta/names/pname/text()} [autogenerated] | {xpath%../../../../meta/uri/text()} | {xpath%../../../../meta/desc/text()} + + {xpath%../../../meta/dev/author/text()} + {xpath%../../../meta/dev/email/text()} + for {xpath%../../../meta/names/pname/text()} [autogenerated] | {xpath%../../../meta/uri/text()} | {xpath%../../../meta/desc/text()} diff --git a/docs/examples/single_profile.xml b/docs/examples/single_profile.xml index e267446..ae4b535 100644 --- a/docs/examples/single_profile.xml +++ b/docs/examples/single_profile.xml @@ -9,7 +9,7 @@ but now with the neat benefits of XPath! Everything you could do in build.ini's and more. See https://www.w3schools.com/xml/xpath_syntax.asp If you need a literal curly brace, double them (e.g. for "{foo}", use "{{foo}}"), - UNLESS it's in a {regex%...} placeholder/filter (as part of the expression). --> + UNLESS it's in a as part of the expression. Those are taken as literal strings. --> {xpath%../name/text()} A rescue/restore live environment. @@ -24,7 +24,7 @@ 5 + items. See the manual for more information. NO btags within the patterns is allowed. --> archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-x86_64\.tar\.gz$ archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-x86_64\.tar\.gz\.sig$ @@ -62,7 +62,7 @@ /iso/latest {regex%tarball_x86_64} sha1sums.txt + explicit="no">sha1sums.txt {regex%sig_x86_64} @@ -157,9 +157,9 @@ prompt_passphrase="no"> - {xpath%../../../../meta/dev/author/text()} - {xpath%../../../../meta/dev/email/text()} - for {xpath%../../../../meta/names/pname/text()} [autogenerated] | {xpath%../../../../meta/uri/text()} | {xpath%../../../../meta/desc/text()} + {xpath%../../../meta/dev/author/text()} + {xpath%../../../meta/dev/email/text()} + for {xpath%../../../meta/names/pname/text()} [autogenerated] | {xpath%../../../meta/uri/text()} | {xpath%../../../meta/desc/text()} diff --git a/docs/manual/TODO b/docs/manual/TODO index 46f2589..e1f733f 100644 --- a/docs/manual/TODO +++ b/docs/manual/TODO @@ -7,4 +7,7 @@ - in faq/ISOBIG.adoc and the doc section it references, make sure we reference that the package lists are now in the environment plugin! -- change all references to build.ini to something like "BDisk configuration file" \ No newline at end of file +- change all references to build.ini to something like "BDisk configuration file" + +- reminder: users can specify a local file source for items by using "file:///absolute/path/to/file" +-- todo: add http auth, ftp, ftps \ No newline at end of file