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.

Related: rhbz#1673744
This commit is contained in:
Brian C. Lane 2019-02-15 16:31:08 -08:00
parent 8c94ee6ba1
commit 51180ad407

View File

@ -103,19 +103,13 @@ def rexists(pathname, root=""):
return True return True
return False return False
# TODO: operate inside an actual chroot for safety? Not that RPM bothers.. class TemplateRunner(object):
class LoraxTemplateRunner(object):
''' '''
This class parses and executes Lorax templates. Sample usage: This class parses and executes Lorax templates. Sample usage:
# install a bunch of packages # install a bunch of packages
runner = LoraxTemplateRunner(inroot=rundir, outroot=rundir, dbo=dnf_obj) runner = LoraxTemplateRunner(inroot=rundir, outroot=rundir, dbo=dnf_obj)
runner.run("install-packages.ltmpl") runner.run("install-packages.ltmpl")
# modify a runtime dir
runner = LoraxTemplateRunner(inroot=rundir, outroot=newrun)
runner.run("runtime-transmogrify.ltmpl")
NOTES: NOTES:
* Parsing procedure is roughly: * Parsing procedure is roughly:
@ -128,6 +122,75 @@ class LoraxTemplateRunner(object):
* Parsing and execution are *separate* passes - so you can't use the result * 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)! 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..
class LoraxTemplateRunner(TemplateRunner):
'''
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")
# modify a runtime dir
runner = LoraxTemplateRunner(inroot=rundir, outroot=newrun)
runner.run("runtime-transmogrify.ltmpl")
NOTES:
* 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
@ -151,14 +214,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):
@ -209,52 +269,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(" " + exclines[-1].strip())
# and log the entire traceback to the debug log
for _line in ''.join(exclines).splitlines():
logger.debug(" " + _line)
if self.fatalerrors:
raise
def install(self, srcglob, dest): def install(self, srcglob, dest):
''' '''
install SRC DEST install SRC DEST
@ -265,6 +279,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
@ -320,6 +335,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
''' '''
@ -333,6 +349,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
''' '''
@ -350,7 +367,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 ""
''' '''
@ -363,6 +382,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
''' '''
@ -463,6 +483,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..."
''' '''
@ -581,6 +602,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.
@ -645,6 +667,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/*
@ -738,6 +761,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
''' '''
@ -750,6 +774,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