Move the run part of LoraxTemplateRunner into new TemplateRunner class

This will make it easier to add a new subclass that only handles
installpkg for use with livemedia-creator and lorax-composer iso
creation.

(cherry picked from commit 8eaad3bc5e)
This commit is contained in:
Brian C. Lane 2019-02-15 16:31:08 -08:00
parent 883bc07fc8
commit 54fe00d16e

View File

@ -103,8 +103,83 @@ def rexists(pathname, root=""):
return True return True
return False return False
class TemplateRunner(object):
'''
This class parses and executes Lorax templates. Sample usage:
# install a bunch of packages
runner = LoraxTemplateRunner(inroot=rundir, outroot=rundir, dbo=dnf_obj)
runner.run("install-packages.ltmpl")
NOTES:
* Parsing procedure is roughly:
1. Mako template expansion (on the whole file)
2. For each line of the result,
a. Whitespace splitting (using shlex.split())
b. Brace expansion (using brace_expand())
c. If the first token is the name of a function, call that function
with the rest of the line as arguments
* Parsing and execution are *separate* passes - so you can't use the result
of a command in an %if statement (or any other control statements)!
'''
def __init__(self, fatalerrors=True, templatedir=None, defaults=None, builtins=None):
self.fatalerrors = fatalerrors
self.templatedir = templatedir or "/usr/share/lorax"
self.templatefile = None
self.builtins = builtins or {}
self.defaults = defaults or {}
def run(self, templatefile, **variables):
for k,v in list(self.defaults.items()) + list(self.builtins.items()):
variables.setdefault(k,v)
logger.debug("executing %s with variables=%s", templatefile, variables)
self.templatefile = templatefile
t = LoraxTemplate(directories=[self.templatedir])
commands = t.parse(templatefile, variables)
self._run(commands)
def _run(self, parsed_template):
logger.info("running %s", self.templatefile)
for (num, line) in enumerate(parsed_template,1):
logger.debug("template line %i: %s", num, " ".join(line))
skiperror = False
(cmd, args) = (line[0], line[1:])
# Following Makefile convention, if the command is prefixed with
# a dash ('-'), we'll ignore any errors on that line.
if cmd.startswith('-'):
cmd = cmd[1:]
skiperror = True
try:
# grab the method named in cmd and pass it the given arguments
f = getattr(self, cmd, None)
if cmd[0] == '_' or cmd == 'run' or not isinstance(f, collections.Callable):
raise ValueError("unknown command %s" % cmd)
f(*args)
except Exception: # pylint: disable=broad-except
if skiperror:
logger.debug("ignoring error")
continue
logger.error("template command error in %s:", self.templatefile)
logger.error(" %s", " ".join(line))
# format the exception traceback
exclines = traceback.format_exception(*sys.exc_info())
# skip the bit about "ltmpl.py, in _run()" - we know that
exclines.pop(1)
# log the "ErrorType: this is what happened" line
logger.error(" %s", exclines[-1].strip())
# and log the entire traceback to the debug log
for _line in ''.join(exclines).splitlines():
logger.debug(" %s", _line)
if self.fatalerrors:
raise
# TODO: operate inside an actual chroot for safety? Not that RPM bothers.. # TODO: operate inside an actual chroot for safety? Not that RPM bothers..
class LoraxTemplateRunner(object): class LoraxTemplateRunner(TemplateRunner):
''' '''
This class parses and executes Lorax templates. Sample usage: This class parses and executes Lorax templates. Sample usage:
@ -118,18 +193,6 @@ class LoraxTemplateRunner(object):
NOTES: NOTES:
* Parsing procedure is roughly:
1. Mako template expansion (on the whole file)
2. For each line of the result,
a. Whitespace splitting (using shlex.split())
b. Brace expansion (using brace_expand())
c. If the first token is the name of a function, call that function
with the rest of the line as arguments
* Parsing and execution are *separate* passes - so you can't use the result
of a command in an %if statement (or any other control statements)!
* Commands that run external programs (e.g. systemctl) currently use * Commands that run external programs (e.g. systemctl) currently use
the *host*'s copy of that program, which may cause problems if there's a the *host*'s copy of that program, which may cause problems if there's a
big enough difference between the host and the image you're modifying. big enough difference between the host and the image you're modifying.
@ -152,14 +215,11 @@ class LoraxTemplateRunner(object):
self.inroot = inroot self.inroot = inroot
self.outroot = outroot self.outroot = outroot
self.dbo = dbo self.dbo = dbo
self.fatalerrors = fatalerrors builtins = DataHolder(exists=lambda p: rexists(p, root=inroot),
self.templatedir = templatedir or "/usr/share/lorax" glob=lambda g: list(rglob(g, root=inroot)))
self.templatefile = None
# some builtin methods
self.builtins = DataHolder(exists=lambda p: rexists(p, root=inroot),
glob=lambda g: list(rglob(g, root=inroot)))
self.defaults = defaults or {}
self.results = DataHolder(treeinfo=dict()) # just treeinfo for now self.results = DataHolder(treeinfo=dict()) # just treeinfo for now
super(LoraxTemplateRunner, self).__init__(fatalerrors, templatedir, defaults, builtins)
# TODO: set up custom logger with a filter to add line info # TODO: set up custom logger with a filter to add line info
def _out(self, path): def _out(self, path):
@ -210,51 +270,6 @@ class LoraxTemplateRunner(object):
for pkg in debug_pkgs: for pkg in debug_pkgs:
f.write("%s\n" % pkg) f.write("%s\n" % pkg)
def run(self, templatefile, **variables):
for k,v in list(self.defaults.items()) + list(self.builtins.items()):
variables.setdefault(k,v)
logger.debug("executing %s with variables=%s", templatefile, variables)
self.templatefile = templatefile
t = LoraxTemplate(directories=[self.templatedir])
commands = t.parse(templatefile, variables)
self._run(commands)
def _run(self, parsed_template):
logger.info("running %s", self.templatefile)
for (num, line) in enumerate(parsed_template,1):
logger.debug("template line %i: %s", num, " ".join(line))
skiperror = False
(cmd, args) = (line[0], line[1:])
# Following Makefile convention, if the command is prefixed with
# a dash ('-'), we'll ignore any errors on that line.
if cmd.startswith('-'):
cmd = cmd[1:]
skiperror = True
try:
# grab the method named in cmd and pass it the given arguments
f = getattr(self, cmd, None)
if cmd[0] == '_' or cmd == 'run' or not isinstance(f, collections.Callable):
raise ValueError("unknown command %s" % cmd)
f(*args)
except Exception: # pylint: disable=broad-except
if skiperror:
logger.debug("ignoring error")
continue
logger.error("template command error in %s:", self.templatefile)
logger.error(" %s", " ".join(line))
# format the exception traceback
exclines = traceback.format_exception(*sys.exc_info())
# skip the bit about "ltmpl.py, in _run()" - we know that
exclines.pop(1)
# log the "ErrorType: this is what happened" line
logger.error(" %s", exclines[-1].strip())
# and log the entire traceback to the debug log
for _line in ''.join(exclines).splitlines():
logger.debug(" %s", _line)
if self.fatalerrors:
raise
def install(self, srcglob, dest): def install(self, srcglob, dest):
''' '''
install SRC DEST install SRC DEST
@ -265,7 +280,7 @@ class LoraxTemplateRunner(object):
If DEST doesn't exist, SRC will be copied to a file with that name, If DEST doesn't exist, SRC will be copied to a file with that name,
assuming the rest of the path exists. assuming the rest of the path exists.
This is pretty much like how the 'cp' command works. This is pretty much like how the 'cp' command works.
Examples: Examples:
install usr/share/myconfig/grub.conf /boot install usr/share/myconfig/grub.conf /boot
install /usr/share/myconfig/grub.conf.in /boot/grub.conf install /usr/share/myconfig/grub.conf.in /boot/grub.conf
@ -321,7 +336,7 @@ class LoraxTemplateRunner(object):
''' '''
mkdir DIR [DIR ...] mkdir DIR [DIR ...]
Create the named DIR(s). Will create leading directories as needed. Create the named DIR(s). Will create leading directories as needed.
Example: Example:
mkdir /images mkdir /images
''' '''
@ -335,7 +350,7 @@ class LoraxTemplateRunner(object):
replace PATTERN REPLACEMENT FILEGLOB [FILEGLOB ...] replace PATTERN REPLACEMENT FILEGLOB [FILEGLOB ...]
Find-and-replace the given PATTERN (Python-style regex) with the given Find-and-replace the given PATTERN (Python-style regex) with the given
REPLACEMENT string for each of the files listed. REPLACEMENT string for each of the files listed.
Example: Example:
replace @VERSION@ ${product.version} /boot/grub.conf /boot/isolinux.cfg replace @VERSION@ ${product.version} /boot/grub.conf /boot/isolinux.cfg
''' '''
@ -353,9 +368,9 @@ class LoraxTemplateRunner(object):
Append STRING (followed by a newline character) to FILE. Append STRING (followed by a newline character) to FILE.
Python character escape sequences ('\\n', '\\t', etc.) will be Python character escape sequences ('\\n', '\\t', etc.) will be
converted to the appropriate characters. converted to the appropriate characters.
Examples: Examples:
append /etc/depmod.d/dd.conf "search updates built-in" append /etc/depmod.d/dd.conf "search updates built-in"
append /etc/resolv.conf "" append /etc/resolv.conf ""
''' '''
@ -368,7 +383,7 @@ class LoraxTemplateRunner(object):
Add an item to the treeinfo data store. Add an item to the treeinfo data store.
The given SECTION will have a new item added where The given SECTION will have a new item added where
KEY = ARG ARG ... KEY = ARG ARG ...
Example: Example:
treeinfo images-${kernel.arch} boot.iso images/boot.iso treeinfo images-${kernel.arch} boot.iso images/boot.iso
''' '''
@ -469,7 +484,7 @@ class LoraxTemplateRunner(object):
''' '''
log MESSAGE log MESSAGE
Emit the given log message. Be sure to put it in quotes! Emit the given log message. Be sure to put it in quotes!
Example: Example:
log "Reticulating splines, please wait..." log "Reticulating splines, please wait..."
''' '''
@ -588,7 +603,7 @@ class LoraxTemplateRunner(object):
''' '''
removepkg PKGGLOB [PKGGLOB...] removepkg PKGGLOB [PKGGLOB...]
Delete the named package(s). Delete the named package(s).
IMPLEMENTATION NOTES: IMPLEMENTATION NOTES:
RPM scriptlets (%preun/%postun) are *not* run. RPM scriptlets (%preun/%postun) are *not* run.
Files are deleted, but directories are left behind. Files are deleted, but directories are left behind.
@ -653,7 +668,7 @@ class LoraxTemplateRunner(object):
(or packages) named. (or packages) named.
If '--allbut' is used, all the files from the given package(s) will If '--allbut' is used, all the files from the given package(s) will
be removed *except* the ones which match the file globs. be removed *except* the ones which match the file globs.
Examples: Examples:
removefrom usbutils /usr/bin/* removefrom usbutils /usr/bin/*
removefrom xfsprogs --allbut /sbin/* removefrom xfsprogs --allbut /sbin/*
@ -748,7 +763,7 @@ class LoraxTemplateRunner(object):
''' '''
createaddrsize INITRD_ADDRESS INITRD ADDRSIZE createaddrsize INITRD_ADDRESS INITRD ADDRSIZE
Create the initrd.addrsize file required in LPAR boot process. Create the initrd.addrsize file required in LPAR boot process.
Examples: Examples:
createaddrsize ${INITRD_ADDRESS} ${outroot}/${BOOTDIR}/initrd.img ${outroot}/${BOOTDIR}/initrd.addrsize createaddrsize ${INITRD_ADDRESS} ${outroot}/${BOOTDIR}/initrd.img ${outroot}/${BOOTDIR}/initrd.addrsize
''' '''
@ -761,7 +776,7 @@ class LoraxTemplateRunner(object):
''' '''
systemctl [enable|disable|mask] UNIT [UNIT...] systemctl [enable|disable|mask] UNIT [UNIT...]
Enable, disable, or mask the given systemd units. Enable, disable, or mask the given systemd units.
Examples: Examples:
systemctl disable lvm2-monitor.service systemctl disable lvm2-monitor.service
systemctl mask fedora-storage-init.service fedora-configure.service systemctl mask fedora-storage-init.service fedora-configure.service