checking in
This commit is contained in:
parent
a315468ff8
commit
7819b5edc4
3
TODO
3
TODO
@ -1,7 +1,8 @@
|
|||||||
- write classes/functions
|
- write classes/functions
|
||||||
- XML-based config
|
- XML-based config
|
||||||
- ensure we use docstrings in a Sphinx-compatible manner.
|
- ensure we use docstrings in a Sphinx-compatible manner?
|
||||||
https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html
|
https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html
|
||||||
|
at the very least document all the functions and such so pydoc's happy.
|
||||||
|
|
||||||
- package for PyPI:
|
- package for PyPI:
|
||||||
# https://packaging.python.org/tutorials/distributing-packages/
|
# https://packaging.python.org/tutorials/distributing-packages/
|
||||||
|
@ -2,6 +2,9 @@ import os
|
|||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
"""
|
||||||
|
BDisk - An easy liveCD creator built in python.
|
||||||
|
"""
|
||||||
|
|
||||||
# BDisk is only supported on Python 3.4 and up.
|
# BDisk is only supported on Python 3.4 and up.
|
||||||
if sys.version_info.major != 3:
|
if sys.version_info.major != 3:
|
||||||
|
0
bdisk/bdisk.xsd
Normal file
0
bdisk/bdisk.xsd
Normal file
@ -1,29 +1,158 @@
|
|||||||
|
import _io
|
||||||
|
import copy
|
||||||
|
import os
|
||||||
import validators
|
import validators
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
try:
|
import lxml.etree
|
||||||
from lxml import etree
|
import lxml.objectify as objectify
|
||||||
has_lxml = True
|
|
||||||
except ImportError:
|
|
||||||
import xml.etree.ElementTree as etree
|
|
||||||
has_lxml = False
|
|
||||||
|
|
||||||
"""Read a configuration file, parse it, and make it available to the rest of
|
etree = lxml.etree
|
||||||
BDisk."""
|
|
||||||
|
_profile_specifiers = ('id', 'name', 'uuid')
|
||||||
|
|
||||||
|
def _detect_cfg(cfg):
|
||||||
|
if isinstance(cfg, str):
|
||||||
|
# check for path or string
|
||||||
|
try:
|
||||||
|
etree.fromstring(cfg)
|
||||||
|
except lxml.etree.XMLSyntaxError:
|
||||||
|
path = os.path.abspath(os.path.expanduser(cfg))
|
||||||
|
try:
|
||||||
|
with open(path, 'r') as f:
|
||||||
|
cfg = f.read()
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise ValueError('Could not open {0}'.format(path))
|
||||||
|
elif isinstance(cfg, _io.TextIOWrapper):
|
||||||
|
_cfg = cfg.read()
|
||||||
|
cfg.close()
|
||||||
|
cfg = _cfg
|
||||||
|
elif isinstance(self.cfg, _io.BufferedReader):
|
||||||
|
_cfg = cfg.read().decode('utf-8')
|
||||||
|
cfg.close()
|
||||||
|
cfg = _cfg
|
||||||
|
elif isinstance(cfg, bytes):
|
||||||
|
cfg = cfg.decode('utf-8')
|
||||||
|
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):
|
class Conf(object):
|
||||||
def __init__(self, cfg, profile = None, id_type = 'name'):
|
def __init__(self, cfg, profile = None):
|
||||||
"""Conf classes accept the following parameters:
|
"""
|
||||||
cfg - The configuration. Can be a filesystem path, a string, bytes,
|
A configuration object.
|
||||||
or a stream
|
|
||||||
|
|
||||||
profile (optional) - A sub-profile in the configuration. If None is
|
Read a configuration file, parse it, and make it available to the rest
|
||||||
provided, we'll first look for a profile named
|
of BDisk.
|
||||||
'default'. If one isn't found, then the first
|
|
||||||
profile found will be used
|
|
||||||
id_type (optional) - The type of identifer to use for profile=.
|
|
||||||
Valid values are:
|
|
||||||
|
|
||||||
id
|
Args:
|
||||||
name
|
|
||||||
uuid"""
|
cfg The configuration. Can be a filesystem path, a string,
|
||||||
|
bytes, or a stream. If bytes or a bytestream, it must be
|
||||||
|
in UTF-8 format.
|
||||||
|
|
||||||
|
profile (optional) A sub-profile in the configuration. If None
|
||||||
|
is provided, we'll first look for the first profile
|
||||||
|
named 'default' (case-insensitive). If one isn't found,
|
||||||
|
then the first profile found will be used. Can be a
|
||||||
|
string (in which we'll automatically search for the
|
||||||
|
given value in the "name" attribute) or a dict for more
|
||||||
|
fine-grained profile identification, such as:
|
||||||
|
|
||||||
|
{'name': 'PROFILE_NAME',
|
||||||
|
'id': 1,
|
||||||
|
'uuid': '00000000-0000-0000-0000-000000000000'}
|
||||||
|
|
||||||
|
You can provide any combination of these
|
||||||
|
(e.g. "profile={'id': 2, 'name' = 'some_profile'}").
|
||||||
|
"""
|
||||||
|
self.raw = _detect_cfg(cfg)
|
||||||
|
self.profile = profile
|
||||||
|
self.xml = None
|
||||||
|
self.profile = None
|
||||||
|
self.xml = etree.from_string(self.cfg)
|
||||||
|
self.xsd = None
|
||||||
|
#if not self.validate(): # Need to write the XSD
|
||||||
|
# raise ValueError('The configuration did not pass XSD/schema '
|
||||||
|
# 'validation')
|
||||||
|
self.get_profile()
|
||||||
|
self.max_recurse = int(self.profile.xpath('//meta/'
|
||||||
|
'max_recurse')[0].text)
|
||||||
|
|
||||||
|
def get_xsd(self):
|
||||||
|
path = os.path.join(os.path.dirname(__file__),
|
||||||
|
'bdisk.xsd')
|
||||||
|
with open(path, 'r') as f:
|
||||||
|
xsd = f.read()
|
||||||
|
return(xsd)
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
self.xsd = etree.XMLSchema(self.get_xsd())
|
||||||
|
return(self.xsd.validate(self.xml))
|
||||||
|
|
||||||
|
def get_profile(self):
|
||||||
|
"""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 self.profile:
|
||||||
|
# A profile identifier was provided
|
||||||
|
if isinstance(self.profile, str):
|
||||||
|
_profile_name = self.profile
|
||||||
|
self.profile = {}
|
||||||
|
for i in _profile_specifiers:
|
||||||
|
self.profile[i] = None
|
||||||
|
self.profile['name'] = _profile_name
|
||||||
|
elif isinstance(self.profile, dict):
|
||||||
|
for k in _profile_specifiers:
|
||||||
|
if k not in self.profile.keys():
|
||||||
|
self.profile[k] = None
|
||||||
|
else:
|
||||||
|
raise TypeError('profile must be a string (name of profile), '
|
||||||
|
'a dictionary, or None')
|
||||||
|
xpath = ('/bdisk/'
|
||||||
|
'profile{0}').format(_profile_xpath_gen(self.profile))
|
||||||
|
self.profile = self.xml.xpath(xpath)
|
||||||
|
if not 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])
|
||||||
|
break
|
||||||
|
# We couldn't find a profile with a default name. Try to grab the
|
||||||
|
# first profile.
|
||||||
|
if not self.profile:
|
||||||
|
# Grab the first profile.
|
||||||
|
if profiles:
|
||||||
|
self.profile = profile[0]
|
||||||
|
else:
|
||||||
|
# No profiles found.
|
||||||
|
raise RuntimeError('Could not find any usable '
|
||||||
|
'configuration profiles')
|
||||||
|
return()
|
||||||
|
|
||||||
|
def parse_profile(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _xpath_ref(self, element):
|
||||||
|
data = None
|
||||||
|
# This is incremented each recursive call until we reach
|
||||||
|
# self.max_recurse
|
||||||
|
recurse_cnt = 1
|
||||||
|
return(data)
|
||||||
|
@ -1,15 +1,30 @@
|
|||||||
#!/usr/bin/env python3.6
|
#!/usr/bin/env python3.6
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import confparse
|
||||||
|
|
||||||
"""The primary user interface for BDisk. If we are running interactively,
|
"""The primary user interface for BDisk. If we are running interactively,
|
||||||
parse arguments first, then initiate a BDisk session."""
|
parse arguments first, then initiate a BDisk session."""
|
||||||
|
|
||||||
def parseArgs():
|
def parseArgs():
|
||||||
pass
|
args = argparse.ArgumentParser(description = ('An easy liveCD creator '
|
||||||
|
'built in python. Supports '
|
||||||
|
'hybrid ISOs/USB, iPXE, and '
|
||||||
|
'UEFI.'),
|
||||||
|
epilog = ('https://git.square-r00t.net'))
|
||||||
|
return(args)
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def run_interactive():
|
def run_interactive():
|
||||||
pass
|
args = vars(parseArgs().parse_args())
|
||||||
|
args['profile'] = {}
|
||||||
|
for i in ('name', 'id', 'uuid'):
|
||||||
|
args['profile'][i] = args[i]
|
||||||
|
del(args[i])
|
||||||
|
run(args)
|
||||||
|
return()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
@ -110,4 +110,100 @@
|
|||||||
</sync>
|
</sync>
|
||||||
</build>
|
</build>
|
||||||
</profile>
|
</profile>
|
||||||
|
<profile name="alternate" id="2" uuid="4f09e014-0827-41f8-b7bd-4faf9fdcf58f">
|
||||||
|
<meta>
|
||||||
|
<names>
|
||||||
|
<name>Another Disk</name>
|
||||||
|
<uxname>livecd2</uxname>
|
||||||
|
<pname><xpath-ref select="../name" /></pname>
|
||||||
|
</names>
|
||||||
|
<desc>Some other rescue/restore live environment.</desc>
|
||||||
|
<dev>
|
||||||
|
<author>Another Dev Eloper</author>
|
||||||
|
<email>dev2@domain.tld</email>
|
||||||
|
<website>https://domain.tld/~dev2</website>
|
||||||
|
</dev>
|
||||||
|
<uri>https://domain.tld/projname</uri>
|
||||||
|
<ver>0.0.1</ver>
|
||||||
|
<max_recurse>3</max_recurse>
|
||||||
|
</meta>
|
||||||
|
<accounts>
|
||||||
|
<!-- Yep, you guessed it; "test" -->
|
||||||
|
<rootpass hashed="yes"
|
||||||
|
salt="sha512">
|
||||||
|
$6$yR0lsi68GZ.8oAuV$juLOanZ6IGD6caxJFo5knnXwFZRi65Q58a1XfSWBX7R97EpHrVgpzdXfA3ysAfAg4bs1d6wBv7su2rURkg2rn.
|
||||||
|
</rootpass>
|
||||||
|
</accounts>
|
||||||
|
<sources>
|
||||||
|
<source arch="x86_64">
|
||||||
|
<mirror>http://archlinux.mirror.domain.tld</mirror>
|
||||||
|
<webroot>/iso/latest</webroot>
|
||||||
|
<tarball flags="glob,latest">
|
||||||
|
<xpath-ref select="../mirror" />/<xpath-ref select="../webroot" />/archlinux-bootstrap-*-x86_64.tar.gz
|
||||||
|
</tarball>
|
||||||
|
<checksum hash="sha1">
|
||||||
|
<xpath-ref select="../mirror" />/<xpath-ref select="../webroot" />/sha1sums.txt
|
||||||
|
</checksum>
|
||||||
|
<sig keys="7F2D434B9741E8AC"
|
||||||
|
keyserver="hkp://pool.sks-keyservers.net">
|
||||||
|
<xpath-ref select="../tarball" />.sig
|
||||||
|
</sig>
|
||||||
|
</source>
|
||||||
|
<source arch="i686">
|
||||||
|
<mirror>http://archlinux32.mirror.domain.tld</mirror>
|
||||||
|
<webroot>/iso/latest</webroot>
|
||||||
|
<tarball flags="glob,latest">
|
||||||
|
<xpath-ref select="../mirror" />/<xpath-ref select="../webroot" />/archlinux-bootstrap-*-i686.tar.gz
|
||||||
|
</tarball>
|
||||||
|
<checksum hash="sha512">
|
||||||
|
<xpath-ref select="../mirror" />/<xpath-ref select="../webroot" />/sha512sums.txt
|
||||||
|
</checksum>
|
||||||
|
<sig keys="248BF41F9BDD61D41D060AE774EDA3C6B06D0506"
|
||||||
|
keyserver="hkp://pool.sks-keyservers.net">
|
||||||
|
<xpath-ref select="../tarball" />.sig
|
||||||
|
</sig>
|
||||||
|
</source>
|
||||||
|
</sources>
|
||||||
|
<build its_full_of_stars="yes">
|
||||||
|
<paths>
|
||||||
|
<cache>/var/tmp/<xpath-ref select="//meta/names/uxname" /></cache>
|
||||||
|
<chroot>/var/tmp/chroots/<xpath-ref select="//meta/names/uxname" /></chroot>
|
||||||
|
<templates>~/<xpath-ref select="//meta/names/uxname" />/templates</templates>
|
||||||
|
<mount>/mnt/<xpath-ref select="//meta/names/uxname" /></mount>
|
||||||
|
<distros>~/<xpath-ref select="//meta/names/uxname" />/distros</distros>
|
||||||
|
<dest>~/<xpath-ref select="//meta/names/uxname" />/results</dest>
|
||||||
|
<iso><xpath-ref select="../dest" />/iso</iso>
|
||||||
|
<http><xpath-ref select="../dest" />/http</http>
|
||||||
|
<tftp><xpath-ref select="../dest" />/tftp</tftp>
|
||||||
|
<ssl><xpath-ref select="../dest" />/pki</ssl>
|
||||||
|
</paths>
|
||||||
|
<basedistro>archlinux</basedistro>
|
||||||
|
<iso sign="yes" sync="yes" multiarch="yes" rsync="yes"/>
|
||||||
|
<ipxe sign="yes" sync="yes" iso="yes" rsync="yes">
|
||||||
|
<ssl>
|
||||||
|
<ca><xpath-ref select="build/paths/ssl" />/ca.crt</ca>
|
||||||
|
<ca_key><xpath-ref select="build/paths/ssl" />/ca.key</ca_key>
|
||||||
|
<crt>
|
||||||
|
<xpath-ref select="build/paths/ssl" />/<xpath-ref select="//meta/names/uxname" />.crt
|
||||||
|
</crt>
|
||||||
|
<key>
|
||||||
|
<xpath-ref select="build/paths/ssl" />/<xpath-ref select="//meta/names/uxname" />.key
|
||||||
|
</key>
|
||||||
|
</ssl>
|
||||||
|
<uri><xpath-ref select="meta/dev/website" />/ipxe</uri>
|
||||||
|
</ipxe>
|
||||||
|
<gpg keyid="none" gnupghome="none" publish="no" sync="yes" />
|
||||||
|
<sync>
|
||||||
|
<http enabled="yes" rsync="yes" />
|
||||||
|
<tftp enabled="yes" rsync="yes" />
|
||||||
|
<rsync enabled="yes">
|
||||||
|
<user>root</user>
|
||||||
|
<path>/srv/http/<xpath-ref select="//meta/names/uxname" /></path>
|
||||||
|
<host>mirror.domain.tld</host>
|
||||||
|
<port>22</port>
|
||||||
|
<pubkey>~/.ssh/id_ed25519</pubkey>
|
||||||
|
</rsync>
|
||||||
|
</sync>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
</bdisk>
|
</bdisk>
|
||||||
|
Loading…
Reference in New Issue
Block a user