From ed7ccdeeafcded4215edf701f181f4b3ca0476ee Mon Sep 17 00:00:00 2001 From: r00t Date: Tue, 15 May 2018 05:31:20 -0400 Subject: [PATCH] checking in some major progress. STILL hitting a bug with multiple xpath% btags(TM ;P) with a regex% tag in the same line that contains {#}. --- .gitignore | 2 + bdisk/confgen.py | 29 +++- bdisk/confparse.py | 42 +---- bdisk/utils.py | 155 +++++++++++++++-- docs/examples/multi_profile.xml | 281 ++++++++++++++++--------------- docs/examples/regen_multi.py | 6 +- docs/examples/single_profile.xml | 151 ++++++++--------- 7 files changed, 396 insertions(+), 270 deletions(-) diff --git a/.gitignore b/.gitignore index e1f6aec..14c1cb5 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ screenlog* __pycache__/ *.pyc *test*.py +*test*.sh +*test*.exp diff --git a/bdisk/confgen.py b/bdisk/confgen.py index 74cbe92..cdf0e06 100755 --- a/bdisk/confgen.py +++ b/bdisk/confgen.py @@ -30,8 +30,8 @@ def pass_prompt(user): _need_input_type = True while _need_input_type: _input_type = input('\nWill you be entering a password or a salted ' - 'hash? (If using a "special" value per the manual, ' - 'use 1 (password)):\n\n' + 'hash? (If using a "special" value per the ' + 'manual, use password entry):\n\n' '\t\t1: password\n' '\t\t2: salted hash\n\n' 'Choice: ').strip() @@ -576,7 +576,7 @@ class ConfGenerator(object): usage = ('{0} for yes, {1} ' 'for no...\n')) return() - + def get_build(self): print('\n++ BUILD ++') build = lxml.etree.SubElement(self.profile, 'build') @@ -586,6 +586,7 @@ class ConfGenerator(object): '{0} for yes, {1} for no...\n')) if _chk_optimizations: build.attrib['its_full_of_stars'] = 'yes' + print('\n++ BUILD || PATHS ++') # Thankfully, we can simplify a lot of this. _dir_strings = {'cache': ('the caching directory (used for temporary ' 'files, temporary downloads, etc.)'), @@ -625,6 +626,28 @@ class ConfGenerator(object): _paths_elems[_dir].text = path build.append(paths) has_paths = True + print('\n++ BUILD || ENVIRONMENT DISTRO ++') + has_distro = False + while not has_distro: + try: + distro_path = self.profile.xpath('//paths/distros/text()')[0] + except IndexError: + distro_path = 'your "distros" path' + distro = (input('\nWhich distro plugin/distro base are you using? ' + 'See the manual for more information. A matching ' + 'plugin MUST exist in {0} for a build to ' + 'complete successfully! The default (Arch Linux, ' + '"archlinux") will be used if left blank.' + '\nDistro base: ').format( + distro_path)).strip() + if distro == '': + distro = 'archlinux' + if not valid.plugin_name(distro): + print('That is not a valid name. See the manual for examples ' + 'and shipped plugins. Retrying.') + continue + distro_elem = lxml.etree.SubElement(build, 'distro') + distro_elem.text = distro return() def main(): diff --git a/bdisk/confparse.py b/bdisk/confparse.py index 2e74073..def5ab7 100644 --- a/bdisk/confparse.py +++ b/bdisk/confparse.py @@ -1,49 +1,13 @@ -import _io import copy import re import os +import utils import validators -from urllib.parse import urlparse import lxml.etree +from urllib.parse import urlparse etree = lxml.etree -_profile_specifiers = ('id', 'name', 'uuid') - -def _detect_cfg(cfg): - if isinstance(cfg, str): - # check for path or string - try: - etree.fromstring(cfg.encode('utf-8')) - return(cfg.encode('utf-8')) - except lxml.etree.XMLSyntaxError: - path = os.path.abspath(os.path.expanduser(cfg)) - try: - with open(path, 'rb') as f: - cfg = f.read() - except FileNotFoundError: - raise ValueError('Could not open {0}'.format(path)) - elif isinstance(cfg, _io.TextIOWrapper): - _cfg = cfg.read().encode('utf-8') - cfg.close() - cfg = _cfg - elif isinstance(self.cfg, _io.BufferedReader): - _cfg = cfg.read() - cfg.close() - cfg = _cfg - elif isinstance(cfg, bytes): - return(cfg) - else: - raise TypeError('Could not determine the object type.') - return(cfg) - -def _profile_xpath_gen(selector): - xpath = '' - for i in selector.items(): - if i[1] and i[0] in _profile_specifiers: - xpath += '[@{0}="{1}"]'.format(*i) - return(xpath) - class Conf(object): def __init__(self, cfg, profile = None): """ @@ -80,7 +44,7 @@ class Conf(object): # Mad props to https://stackoverflow.com/a/12728199/733214 self.xpath_re = re.compile('(?<=(?= self.max_recurse: + return(None) + if isinstance(element, lxml.etree._Element): + if isinstance(element, lxml.etree._Comment): + return(element) + if element.text: + _dictmap = self.xpath_to_dict(element.text) + while _dictmap: + for elem in _dictmap: + if _dictmap is None: + continue + # I still for the life of me cannot figure out why this + # is not caught by the above. But it isn't. + if elem not in _dictmap: + continue + if isinstance(_dictmap[elem], str): + try: + newpath = element.xpath(_dictmap[elem]) + except (AttributeError, IndexError, TypeError): + newpath = element + try: + self.substitutions[elem] = self.substitute( + newpath, + (recurse_count + 1) + )[0] + except (IndexError, TypeError): + raise ValueError( + ('Encountered an error while trying to ' + 'substitute {0} at {1}').format( + elem, self.get_path(element) + )) + element.text = XPathFmt().vformat( + element.text, + [], + self.substitutions) + _dictmap = self.xpath_to_dict(element.text) + return(element) + + def xpath_selector(self, selectors, + selector_ids = ('id', 'name', 'uuid')): + # selectors is a dict of {attrib:value} + xpath = '' + for i in selectors.items(): + if i[1] and i[0] in selector_ids: + xpath += '[@{0}="{1}"]'.format(*i) + return(xpath) + + def xpath_to_dict(self, text_in): + d = None + ptrn_id = self.ptrn.findall(text_in) + if len(ptrn_id) >= 1: + for item in ptrn_id: + if not isinstance(d, dict): + d = {} + try: + _, xpath_expr = item.split('%', 1) + if not _ == 'xpath': + continue + if item not in self.substitutions: + self.substitutions[item] = None + d[item] = xpath_expr + except ValueError: + return(None) + return(d) + + class valid(object): def __init__(self): pass @@ -423,12 +554,9 @@ class valid(object): pass def email(self, addr): - if isinstance(validators.email(emailparse(addr)[1]), - validators.utils.ValidationFailure): - return(False) - else: - return(True) - return() + return( + isinstance(validators.email(emailparse(addr)[1]), + validators.utils.ValidationFailure)) def gpgkeyID(self, key_id): # Condense fingerprints into normalized 40-char "full" key IDs. @@ -487,14 +615,21 @@ class valid(object): return(False) return(True) + def plugin_name(self, name): + if len(name) == 0: + return(False) + _name_re = re.compile('^[a-z][0-9a-z_]+$', re.IGNORECASE) + if not _name_re.search(name): + return(False) + return(True) + def posix_filename(self, fname): # Note: 2009 spec of POSIX, "3.282 Portable Filename Character Set" if len(fname) == 0: return(False) - _chars = (string.ascii_letters + string.digits + '.-_') - for char in fname: - if char not in _chars: - return(False) + _char_re = re.compile('^[a-z0-9._-]+$', re.IGNORECASE) + if not _char_re.search(fname): + return(False) return(True) def url(self, url): diff --git a/docs/examples/multi_profile.xml b/docs/examples/multi_profile.xml index b46ce87..9a77387 100644 --- a/docs/examples/multi_profile.xml +++ b/docs/examples/multi_profile.xml @@ -8,8 +8,9 @@ - {xpath_ref%../name/text()} + 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). --> + {xpath%../name/text()} A rescue/restore live environment. @@ -19,7 +20,7 @@ https://domain.tld/projname 1.0.0 - + 5 @@ -27,10 +28,10 @@ $6$7KfIdtHTcXwVrZAC$LZGNeMNz7v5o/cYuA48FAxtZynpIwO5B1CPGXnOW5kCTVpXVt4SypRqfM.AoKkFt/O7MZZ8ySXJmxpELKmdlF1 - {xpath_ref%//meta/names/uxname/text()} + {xpath%//meta/names/uxname/text()} - - {xpath_ref%//meta/dev/author/text()} + + {xpath%//meta/dev/author/text()} testpassword @@ -43,100 +44,100 @@ http://archlinux.mirror.domain.tld /iso/latest - {xpath_ref%../mirror/text()}{xpath_ref%../webroot/text()}/{regex%archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-x86_64\.tar\.gz} - {xpath_ref%../mirror/text()}{xpath_ref%../webroot/text()}/sha1sums.txt - {xpath_ref%../tarball/text()}.sig + {xpath%../mirror/text()}{xpath%../webroot/text()}/{regex%archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-x86_64\.tar\.gz} + {xpath%../mirror/text()}{xpath%../webroot/text()}/sha1sums.txt + {xpath%../tarball/text()}.sig http://archlinux32.mirror.domain.tld /iso/latest - {xpath_ref%../mirror/text()}/{xpath_ref%../webroot/text()}/{regex%archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-i686\.tar\.gz} + {xpath%../mirror/text()}/{xpath%../webroot/text()}/{regex%archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-i686\.tar\.gz} cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e - {xpath_ref%../tarball/text()}.sig + {xpath%../tarball/text()}.sig - /var/tmp/{xpath_ref%//meta/names/uxname/text()} - /var/tmp/chroots/{xpath_ref%//meta/names/uxname/text()} - {xpath_ref%../cache/text()}/overlay - ~/{xpath_ref%//meta/names/uxname/text()}/templates - /mnt/{xpath_ref%//meta/names/uxname/text()} - ~/{xpath_ref%//meta/names/uxname/text()}/distros - ~/{xpath_ref%//meta/names/uxname/text()}/results - {xpath_ref%../dest/text()}/iso - {xpath_ref%../dest/text()}/http - {xpath_ref%../dest/text()}/tftp - {xpath_ref%../dest/text()}/pki + /var/tmp/{xpath%//meta/names/uxname/text()} + /var/tmp/chroots/{xpath%//meta/names/uxname/text()} + {xpath%../cache/text()}/overlay + ~/{xpath%//meta/names/uxname/text()}/templates + /mnt/{xpath%//meta/names/uxname/text()} + ~/{xpath%//meta/names/uxname/text()}/distros + ~/{xpath%//meta/names/uxname/text()}/results + {xpath%../dest/text()}/iso + {xpath%../dest/text()}/http + {xpath%../dest/text()}/tftp + {xpath%../dest/text()}/pki archlinux - - - - - - {xpath_ref%//build/paths/ssl/text()}/ca.crt - - - {xpath_ref%//build/paths/ssl/text()}/ca.key - - domain.tld - XX - Some City - Some State - Some Org, Inc. - Department Name - {xpath_ref%//meta/dev/email/text()} - - - - {xpath_ref%//build/paths/ssl/text()}/{xpath_ref%//meta/names/uxname/text()}.crt - - {xpath_ref%//build/paths/ssl/text()}/{xpath_ref%//meta/names/uxname/text()}.key - - domain.tld (client) - XX - Some City - Some State - Some Org, Inc. - Department Name - {xpath_ref%//meta/dev/email/text()} - - - - {xpath_ref%//meta/dev/website/text()}/ipxe - - - - - - - root - /srv/http/{xpath_ref%//meta/names/uxname/text()} - mirror.domain.tld - 22 - ~/.ssh/id_ed25519 - - + + + {xpath%//meta/dev/website/text()}/ipxe + + + + + {xpath%../../../build/paths/pki/text()}/ca.crt + + + {xpath%../../../build/paths/pki/text()}/ca.key + + domain.tld + XX + Some City + Some State + Some Org, Inc. + Department Name + {xpath%../../../../meta/dev/email/text()} + + + + {xpath%../../../build/paths/pki/text()}/{xpath%../../../meta/names/uxname/text()}.crt + + {xpath%//build/paths/pki/text()}/{xpath%../../../meta/names/uxname/text()}.key + + some client name + XX + Some City + Some State + Some Org, Inc. + Department Name + {xpath%../../../../meta/dev/email/text()} + + + + + + /srv/http/{xpath%../../meta/names/uxname/text()} + /tftproot/{xpath%../../meta/names/uxname/text()} + /srv/http/isos/{xpath%../../meta/names/uxname/text()} + + root + mirror.domain.tld + 22 + ~/.ssh/id_ed25519 + + AnotherCD bdisk_alt - {xpath_ref%../name/text()} + {xpath%../name/text()} Another rescue/restore live environment. Another Dev Eloper - {xpath_ref%//profile[@name="default"]/meta/dev/email/text()} - {xpath_ref%//profile[@name="default"]/meta/dev/website/text()} + {xpath%//profile[@name="default"]/meta/dev/email/text()} + {xpath%//profile[@name="default"]/meta/dev/website/text()} https://domain.tld/projname 0.0.1 @@ -154,79 +155,79 @@ http://archlinux.mirror.domain.tld /iso/latest - {xpath_ref%../mirror/text()}{xpath_ref%../webroot/text()}/{regex%archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-x86_64\.tar\.gz} - {xpath_ref%../mirror/text()}{xpath_ref%../webroot/text()}/sha1sums.txt - {xpath_ref%../tarball/text()}.sig + {xpath%../mirror/text()}{xpath%../webroot/text()}/{regex%archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-x86_64\.tar\.gz} + {xpath%../mirror/text()}{xpath%../webroot/text()}/sha1sums.txt + {xpath%../tarball/text()}.sig http://archlinux32.mirror.domain.tld /iso/latest - {xpath_ref%../mirror/text()}/{xpath_ref%../webroot/text()}/{regex%archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-i686\.tar\.gz} + {xpath%../mirror/text()}/{xpath%../webroot/text()}/{regex%archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-i686\.tar\.gz} cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e - {xpath_ref%../tarball/text()}.sig + {xpath%../tarball/text()}.sig - /var/tmp/{xpath_ref%//meta/names/uxname/text()} - /var/tmp/chroots/{xpath_ref%//meta/names/uxname/text()} - {xpath_ref%../cache/text()}/overlay - ~/{xpath_ref%//meta/names/uxname/text()}/templates - /mnt/{xpath_ref%//meta/names/uxname/text()} - ~/{xpath_ref%//meta/names/uxname/text()}/distros - ~/{xpath_ref%//meta/names/uxname/text()}/results - {xpath_ref%../dest/text()}/iso - {xpath_ref%../dest/text()}/http - {xpath_ref%../dest/text()}/tftp - {xpath_ref%../dest/text()}/pki + /var/tmp/{xpath%//meta/names/uxname/text()} + /var/tmp/chroots/{xpath%//meta/names/uxname/text()} + {xpath%../cache/text()}/overlay + ~/{xpath%//meta/names/uxname/text()}/templates + /mnt/{xpath%//meta/names/uxname/text()} + ~/{xpath%//meta/names/uxname/text()}/distros + ~/{xpath%//meta/names/uxname/text()}/results + {xpath%../dest/text()}/iso + {xpath%../dest/text()}/http + {xpath%../dest/text()}/tftp + {xpath%../dest/text()}/pki archlinux - - - - - {xpath_ref%//build/paths/ssl/text()}/ca.crt - - {xpath_ref%//build/paths/ssl/text()}/ca.key - - domain.tld - XX - Some City - Some State - Some Org, Inc. - Department Name - {xpath_ref%//meta/dev/email/text()} - - - - {xpath_ref%//build/paths/ssl/text()}/{xpath_ref%//meta/names/uxname/text()}.crt - - {xpath_ref%//build/paths/ssl/text()}/{xpath_ref%//meta/names/uxname/text()}.key - - domain.tld (client) - XX - Some City - Some State - Some Org, Inc. - Department Name - {xpath_ref%//meta/dev/email/text()} - - - - {xpath_ref%//meta/dev/website/text()}/ipxe - - - - - - - root - /srv/http/{xpath_ref%//meta/names/uxname/text()} - mirror.domain.tld - 22 - ~/.ssh/id_ed25519 - - + + + {xpath%//meta/dev/website/text()}/ipxe + + + + {xpath%../../../build/paths/pki/text()}/ca.crt + + {xpath%../../../build/paths/pki/text()}/ca.key + + domain.tld + XX + Some City + Some State + Some Org, Inc. + Department Name + {xpath%../../../../meta/dev/email/text()} + + + + {xpath%../../../build/paths/pki/text()}/{xpath%../../../meta/names/uxname/text()}.crt + + {xpath%//build/paths/pki/text()}/{xpath%../../../meta/names/uxname/text()}.key + + some client name + XX + Some City + Some State + Some Org, Inc. + Department Name + {xpath%../../../../meta/dev/email/text()} + + + + + + /srv/http/{xpath%../../meta/names/uxname/text()} + /tftproot/{xpath%../../meta/names/uxname/text()} + /srv/http/isos/{xpath%../../meta/names/uxname/text()} + + root + mirror.domain.tld + 22 + ~/.ssh/id_ed25519 + + diff --git a/docs/examples/regen_multi.py b/docs/examples/regen_multi.py index ff3cc50..e2673ca 100755 --- a/docs/examples/regen_multi.py +++ b/docs/examples/regen_multi.py @@ -19,11 +19,11 @@ alt_profile.attrib['uuid'] = '2ed07c19-2071-4d66-8569-da40475ba716' meta_tags = {'name': 'AnotherCD', 'uxname': 'bdisk_alt', - 'pname': '{xpath_ref%../name/text()}', + 'pname': '{xpath%../name/text()}', 'desc': 'Another rescue/restore live environment.', 'author': 'Another Dev Eloper', - 'email': '{xpath_ref%//profile[@name="default"]/meta/dev/email/text()}', - 'website': '{xpath_ref%//profile[@name="default"]/meta/dev/website/text()}', + 'email': '{xpath%//profile[@name="default"]/meta/dev/email/text()}', + 'website': '{xpath%//profile[@name="default"]/meta/dev/website/text()}', 'ver': '0.0.1'} # Change the names meta = alt_profile.xpath('/profile/meta')[0] diff --git a/docs/examples/single_profile.xml b/docs/examples/single_profile.xml index 465a737..f0ce144 100644 --- a/docs/examples/single_profile.xml +++ b/docs/examples/single_profile.xml @@ -8,8 +8,9 @@ - {xpath_ref%../name/text()} + 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). --> + {xpath%../name/text()} A rescue/restore live environment. @@ -19,7 +20,7 @@ https://domain.tld/projname 1.0.0 - + 5 @@ -27,10 +28,10 @@ $6$7KfIdtHTcXwVrZAC$LZGNeMNz7v5o/cYuA48FAxtZynpIwO5B1CPGXnOW5kCTVpXVt4SypRqfM.AoKkFt/O7MZZ8ySXJmxpELKmdlF1 - {xpath_ref%//meta/names/uxname/text()} + {xpath%//meta/names/uxname/text()} - - {xpath_ref%//meta/dev/author/text()} + + {xpath%//meta/dev/author/text()} testpassword @@ -47,89 +48,89 @@ http://archlinux.mirror.domain.tld /iso/latest - {xpath_ref%../mirror/text()}{xpath_ref%../webroot/text()}/{regex%archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-x86_64\.tar\.gz} - {xpath_ref%../mirror/text()}{xpath_ref%../webroot/text()}/sha1sums.txt + {xpath%../mirror/text()}{xpath%../webroot/text()}/{regex%archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-x86_64\.tar\.gz} + {xpath%../mirror/text()}{xpath%../webroot/text()}/sha1sums.txt {xpath_ref%../tarball/text()}.sig + flags="latest">{xpath%../tarball/text()}.sig http://archlinux32.mirror.domain.tld /iso/latest - {xpath_ref%../mirror/text()}/{xpath_ref%../webroot/text()}/{regex%archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-i686\.tar\.gz} + {xpath%../mirror/text()}/{xpath%../webroot/text()}/{regex%archlinux-bootstrap-[0-9]{4}\.[0-9]{2}\.[0-9]{2}-i686\.tar\.gz} cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e {xpath_ref%../tarball/text()}.sig + keyserver="hkp://pool.sks-keyservers.net">{xpath%../tarball/text()}.sig - /var/tmp/{xpath_ref%//meta/names/uxname/text()} - /var/tmp/chroots/{xpath_ref%//meta/names/uxname/text()} - {xpath_ref%../cache/text()}/overlay - ~/{xpath_ref%//meta/names/uxname/text()}/templates - /mnt/{xpath_ref%//meta/names/uxname/text()} - ~/{xpath_ref%//meta/names/uxname/text()}/distros - ~/{xpath_ref%//meta/names/uxname/text()}/results - {xpath_ref%../dest/text()}/iso - {xpath_ref%../dest/text()}/http - {xpath_ref%../dest/text()}/tftp - {xpath_ref%../dest/text()}/pki + /var/tmp/{xpath%//meta/names/uxname/text()} + /var/tmp/chroots/{xpath%//meta/names/uxname/text()} + {xpath%../cache/text()}/overlay + ~/{xpath%//meta/names/uxname/text()}/templates + /mnt/{xpath%//meta/names/uxname/text()} + ~/{xpath%//meta/names/uxname/text()}/distros + ~/{xpath%//meta/names/uxname/text()}/results + {xpath%../dest/text()}/iso + {xpath%../dest/text()}/http + {xpath%../dest/text()}/tftp + {xpath%../dest/text()}/pki archlinux - - - - - - {xpath_ref%//build/paths/ssl/text()}/ca.crt - - - {xpath_ref%//build/paths/ssl/text()}/ca.key - - domain.tld - XX - Some City - Some State - Some Org, Inc. - Department Name - {xpath_ref%//meta/dev/email/text()} - - - - {xpath_ref%//build/paths/ssl/text()}/{xpath_ref%//meta/names/uxname/text()}.crt - - {xpath_ref%//build/paths/ssl/text()}/{xpath_ref%//meta/names/uxname/text()}.key - - domain.tld (client) - XX - Some City - Some State - Some Org, Inc. - Department Name - {xpath_ref%//meta/dev/email/text()} - - - - {xpath_ref%//meta/dev/website/text()}/ipxe - - - - - - - root - /srv/http/{xpath_ref%//meta/names/uxname/text()} - mirror.domain.tld - 22 - ~/.ssh/id_ed25519 - - + + + {xpath%//meta/dev/website/text()}/ipxe + + + + + {xpath%../../../build/paths/pki/text()}/ca.crt + + + {xpath%../../../build/paths/pki/text()}/ca.key + + domain.tld + XX + Some City + Some State + Some Org, Inc. + Department Name + {xpath%../../../../meta/dev/email/text()} + + + + {xpath%../../../build/paths/pki/text()}/{xpath%../../../meta/names/uxname/text()}.crt + + {xpath%//build/paths/pki/text()}/{xpath%../../../meta/names/uxname/text()}.key + + some client name + XX + Some City + Some State + Some Org, Inc. + Department Name + {xpath%../../../../meta/dev/email/text()} + + + + + + /srv/http/{xpath%../../meta/names/uxname/text()} + /tftproot/{xpath%../../meta/names/uxname/text()} + /srv/http/isos/{xpath%../../meta/names/uxname/text()} + + root + mirror.domain.tld + 22 + ~/.ssh/id_ed25519 + +