Merge branch 'roth_install_hook' of github.com:carlroth/OpenNetworkLinux

This commit is contained in:
Jeffrey Townsend
2016-11-28 10:09:18 -08:00
10 changed files with 386 additions and 22 deletions

View File

@@ -10,8 +10,20 @@ endif
include $(ONL)/make/versions/version-onl.mk
INSTALLER_NAME=$(FNAME_PRODUCT_VERSION)_ONL-OS_$(FNAME_BUILD_ID)_$(UARCH)_$(BOOTMODE)_INSTALLER
MKINSTALLER_OPTS = \
--arch $(ARCH) \
--boot-config boot-config \
--add-dir config \
--initrd onl-loader-initrd:$(ARCH) onl-loader-initrd-$(ARCH).cpio.gz \
--swi onl-swi:$(ARCH) \
--preinstall-script $(ONL)/builds/any/installer/sample-preinstall.sh \
--postinstall-script $(ONL)/builds/any/installer/sample-postinstall.sh \
--preinstall-plugin $(ONL)/builds/any/installer/sample-preinstall.py \
--postinstall-plugin $(ONL)/builds/any/installer/sample-postinstall.py \
# THIS LINE INTENTIONALLY LEFT BLANK
__installer:
$(ONL)/tools/mkinstaller.py --arch $(ARCH) --boot-config boot-config --add-dir config --initrd onl-loader-initrd:$(ARCH) onl-loader-initrd-$(ARCH).cpio.gz --swi onl-swi:$(ARCH) --out $(INSTALLER_NAME)
$(ONL)/tools/mkinstaller.py $(MKINSTALLER_OPTS) --out $(INSTALLER_NAME)
md5sum "$(INSTALLER_NAME)" | awk '{ print $$1 }' > "$(INSTALLER_NAME).md5sum"

View File

@@ -361,16 +361,44 @@ else
installer_list=$initrd_archive
fi
installer_unzip() {
local zip tmp dummy
zip=$1; shift
installer_say "Extracting from $zip: $@ ..."
tmp=$(mktemp -d -t "unzip-XXXXXX")
if test "$SFX_PAD"; then
# ha ha, busybox cannot exclude multiple files
unzip -o $zip "$@" -x $SFX_PAD -d $tmp
elif test "$SFX_UNZIP"; then
unzip -o $zip "$@" -x $installer_script -d $tmp
else
dd if=$zip bs=$SFX_BLOCKSIZE skip=$SFX_BLOCKS \
| unzip -o - "$@" -x $installer_script -d $tmp
fi
rm -f $tmp/$installer_script
if test "$SFX_PAD"; then
rm -f $tmp/$SFX_PAD
fi
set dummy $tmp/*
if test -e "$2"; then
shift
while test $# -gt 0; do
mv "$1" .
shift
done
else
installer_say "Extracting from $zip: no files extracted"
fi
return 0
}
installer_say "Unpacking ONL installer files..."
if test "$SFX_PAD"; then
# ha ha, busybox cannot exclude multiple files
unzip -o $installer_zip $installer_list -x $SFX_PAD
elif test "$SFX_UNZIP"; then
unzip -o $installer_zip $installer_list -x $installer_script
else
dd if=$installer_zip bs=$SFX_BLOCKSIZE skip=$SFX_BLOCKS \
| unzip -o - $installer_list -x $installer_script
fi
installer_unzip $installer_zip $installer_list
# Developer debugging
if has_boot_env onl_installer_unpack_only; then installer_unpack_only=1; fi
@@ -513,6 +541,13 @@ else
installer_say "*** watch out for lingering mount-points"
fi
installer_unzip $installer_zip preinstall.sh || :
if test -f preinstall.sh; then
installer_say "Invoking pre-install actions"
chmod +x preinstall.sh
./preinstall.sh $rootdir
fi
chroot "${rootdir}" $installer_shell
if test -f "$postinst"; then
@@ -522,6 +557,12 @@ if test -f "$postinst"; then
set +x
fi
installer_unzip $installer_zip postinstall.sh || :
if test -f preinstall.sh; then
chmod +x postinstall.sh
./postinstall.sh $rootdir
fi
trap - 0 1
installer_umount

View File

@@ -0,0 +1,59 @@
"""sample-postinstall.py
Example Python script for post-install hooks.
Add this as a postinstall hook to your installer via
the 'mkinstaller.py' command line:
$ mkinstaller.py ... --postinstall-plugin sample-postinstall.py ...
At install time, this script will
1. be extracted into a temporary working directory
2. be imported as a module, in the same process as the installer
script
Importing the module should not trigger any side-effects.
At the appropriate time during the install (a chrooted invocation
of the installer Python script) will
1. scrape the top-level plugin's namespace for subclasses of
onl.install.Plugin.Plugin.
Implementors should declare classes here
(inheriting from onl.install.Plugin.Plugin) to embed the plugin
functionality.
2. instantiate an instance of each class, with the installer
object initialized as the 'installer' attribute
3. invoke the 'run' method (which must be overridden by implementors)
4. invoke the 'shutdown' method (by default, a no-op)
The 'run' method should return zero on success. In any other case, the
installer terminates.
The post-install plugins are invoked after the installer is complete
and after the boot loader is updated.
An exception to this is for proxy GRUB configurations. In that case, the
post-install plugins are invoked after the install is finished, but before
the boot loader has been updated.
At the time the post-install plugin is invoked, none of the
filesystems are mounted. If the implementor needs to manipulate the
disk, the filesystems should be re-mounted temporarily with
e.g. MountContext. The OnlMountContextReadWrite object and their
siblings won't work here because the mtab.yml file is not populated
within the loader environment.
When using MountContxt, the system state in the installer object can help
(self.installer.blkidParts in particular).
"""
import onl.install.Plugin
class Plugin(onl.install.Plugin.Plugin):
def run(self):
self.log.info("hello from postinstall plugin")
return 0

View File

@@ -0,0 +1,42 @@
#!/bin/sh
#
######################################################################
#
# sample-postinstall.sh
#
# Example script for post-install hooks.
#
# Add this as a postinstall hook to your installer via
# the 'mkinstaller.py' command line:
#
# $ mkinstaller.py ... --postinstall-script sample-postinstall.sh ...
#
# At install time, this script will
#
# 1. be extracted into the working directory with the other installer
# collateral
# 2. have the execute bit set
# 3. run in-place with the installer chroot directory passed
# as the first command line parameter
#
# If the script fails (returns a non-zero exit code) then
# the install is aborted.
#
# This script is executed using the ONIE runtime (outside the chroot),
# after the actual installer (chrooted Python script) has finished.
#
# This script is run after the postinstall actions (e.g. proxy GRUB
# commands)
#
# At the time the script is run, the installer environment (chroot)
# is fully prepared, including filesystem mount-points.
# That is, the chroot mount points have not been unmounted yet.
#
######################################################################
rootdir=$1; shift
echo "Hello from postinstall"
echo "Chroot is $rootdir"
exit 0

View File

@@ -0,0 +1,49 @@
"""sample-preinstall.py
Example Python script for pre-install hooks.
Add this as a preinstall hook to your installer via
the 'mkinstaller.py' command line:
$ mkinstaller.py ... --preinstall-plugin sample-preinstall.py ...
At install time, this script will
1. be extracted into a temporary working directory
2. be imported as a module, in the same process as the installer
script
Importing the module should not trigger any side-effects.
At the appropriate time during the install (a chrooted invocation
of the installer Python script) will
1. scrape the top-level plugin's namespace for subclasses of
onl.install.Plugin.Plugin.
Implementors should declare classes here
(inheriting from onl.install.Plugin.Plugin) to embed the plugin
functionality.
2. instantiate an instance of each class, with the installer
object initialized as the 'installer' attribute
3. invoke the 'run' method (which must be overridden by implementors)
4. invoke the 'shutdown' method (by default, a no-op)
The 'run' method should return zero on success. In any other case, the
installer terminates.
The 'installer' object has a handle onto the installer ZIP archive
(self.installer.zf) but otherwise the install has not been
started. That is, the install disk has not been
prepped/initialized/scanned yet. As per the ONL installer API, the
installer starts with *no* filesystems mounted, not even the ones from
a prior install.
"""
import onl.install.Plugin
class Plugin(onl.install.Plugin.Plugin):
def run(self):
self.log.info("hello from preinstall plugin")
return 0

View File

@@ -0,0 +1,38 @@
#!/bin/sh
#
######################################################################
#
# sample-preinstall.sh
#
# Example script for pre-install hooks.
#
# Add this as a preinstall hook to your installer via
# the 'mkinstaller.py' command line:
#
# $ mkinstaller.py ... --preinstall-script sample-preinstall.sh ...
#
# At install time, this script will
#
# 1. be extracted into the working directory with the other installer
# collateral
# 2. have the execute bit set
# 3. run in-place with the installer chroot directory passed
# as the first command line parameter
#
# If the script fails (returns a non-zero exit code) then
# the install is aborted.
#
# This script is executed using the ONIE runtime (outside the chroot),
# before the actual installer (chrooted Python script)
#
# At the time the script is run, the installer environment (chroot)
# has been fully prepared, including filesystem mount-points.
#
######################################################################
rootdir=$1; shift
echo "Hello from preinstall"
echo "Chroot is $rootdir"
exit 0

View File

@@ -10,8 +10,20 @@ endif
include $(ONL)/make/versions/version-onl.mk
INSTALLER_NAME=$(FNAME_PRODUCT_VERSION)_ONL-OS_$(FNAME_BUILD_ID)_$(UARCH)_$(BOOTMODE)_INSTALLER
MKINSTALLER_OPTS = \
--arch $(ARCH) \
--boot-config boot-config \
--add-dir config \
--fit onl-loader-fit:$(ARCH) onl-loader-fit.itb \
--swi onl-swi:$(ARCH) \
--preinstall-script $(ONL)/builds/any/installer/sample-preinstall.sh \
--postinstall-script $(ONL)/builds/any/installer/sample-postinstall.sh \
--preinstall-plugin $(ONL)/builds/any/installer/sample-preinstall.py \
--postinstall-plugin $(ONL)/builds/any/installer/sample-postinstall.py \
# THIS LINE INTENTIONALLY LEFT BLANK
__installer:
$(ONL)/tools/mkinstaller.py --arch $(ARCH) --boot-config boot-config --add-dir config --fit onl-loader-fit:$(ARCH) onl-loader-fit.itb --swi onl-swi:$(ARCH) --out $(INSTALLER_NAME)
$(ONL)/tools/mkinstaller.py $(MKINSTALLER_OPTS) --out $(INSTALLER_NAME)
md5sum "$(INSTALLER_NAME)" | awk '{ print $$1 }' > "$(INSTALLER_NAME).md5sum"

View File

@@ -3,7 +3,7 @@
Base classes for installers.
"""
import os, stat
import os, sys, stat
import subprocess
import re
import tempfile
@@ -13,10 +13,12 @@ import parted
import yaml
import zipfile
import shutil
import imp
from InstallUtils import SubprocessMixin
from InstallUtils import MountContext, BlkidParser, PartedParser
from InstallUtils import ProcMountsParser
from Plugin import Plugin
import onl.YamlUtils
from onl.sysconfig import sysconfig
@@ -413,6 +415,50 @@ class Base:
return 0
def preinstall(self):
return self.runPlugin("preinstall.py")
def postinstall(self):
return self.runPlugin("postinstall.py")
def runPluginFile(self, pyPath):
with open(pyPath) as fd:
sfx = ('.py', 'U', imp.PY_SOURCE,)
mod = imp.load_module("plugin", fd, pyPath, sfx)
for attr in dir(mod):
klass = getattr(mod, attr)
if isinstance(klass, type) and issubclass(klass, Plugin):
self.log.info("%s: running plugin %s", pyPath, attr)
plugin = klass(self)
try:
code = plugin.run()
except:
self.log.exception("plugin failed")
code = 1
plugin.shutdown()
if code: return code
return 0
def runPlugin(self, basename):
src = os.path.join(self.im.installerConf.installer_dir, basename)
if os.path.exists(src):
return self.runPluginFile(src)
if basename in self.zf.namelist():
try:
src = None
with self.zf.open(basename, "r") as rfd:
wfno, src = tempfile.mkstemp(prefix="plugin-",
suffix=".py")
with os.fdopen(wfno, "w") as wfd:
shutil.copyfileobj(rfd, wfd)
return self.runPluginFile(src)
finally:
if src and os.path.exists(src):
os.unlink(src)
GRUB_TPL = """\
serial %(serial)s
terminal_input serial
@@ -603,6 +649,14 @@ class GrubInstaller(SubprocessMixin, Base):
def installGpt(self):
# get a handle to the installer zip
p = os.path.join(self.im.installerConf.installer_dir,
self.im.installerConf.installer_zip)
self.zf = zipfile.ZipFile(p)
code = self.preinstall()
if code: return code
code = self.findGpt()
if code: return code
@@ -640,11 +694,6 @@ class GrubInstaller(SubprocessMixin, Base):
self.im.grubEnv.__dict__['bootPart'] = dev.device
self.im.grubEnv.__dict__['bootDir'] = None
# get a handle to the installer zip
p = os.path.join(self.im.installerConf.installer_dir,
self.im.installerConf.installer_zip)
self.zf = zipfile.ZipFile(p)
code = self.installSwi()
if code: return code
@@ -663,6 +712,9 @@ class GrubInstaller(SubprocessMixin, Base):
code = self.installGrub()
if code: return code
code = self.postinstall()
if code: return code
self.log.info("ONL loader install successful.")
self.log.info("GRUB installation is required next.")
@@ -839,6 +891,14 @@ class UbootInstaller(SubprocessMixin, Base):
self.log.error("not a block device: %s", self.device)
return 1
# get a handle to the installer zip
p = os.path.join(self.im.installerConf.installer_dir,
self.im.installerConf.installer_zip)
self.zf = zipfile.ZipFile(p)
code = self.preinstall()
if code: return code
code = self.assertUnmounted()
if code: return code
@@ -884,11 +944,6 @@ class UbootInstaller(SubprocessMixin, Base):
self.rawLoaderDevice = self.device + str(partIdx+1)
break
# get a handle to the installer zip
p = os.path.join(self.im.installerConf.installer_dir,
self.im.installerConf.installer_zip)
self.zf = zipfile.ZipFile(p)
code = self.installSwi()
if code: return code
@@ -913,6 +968,9 @@ class UbootInstaller(SubprocessMixin, Base):
code = self.installUbootEnv()
if code: return code
code = self.postinstall()
if code: return code
return 0
def run(self):

View File

@@ -0,0 +1,17 @@
"""Plugin.py
Base class for installer plugins.
"""
class Plugin(object):
def __init__(self, installer):
self.installer = installer
self.log = self.installer.log.getChild("plugin")
def run(self):
self.log.warn("not implemented")
return 0
def shutdown(self):
pass

View File

@@ -106,6 +106,18 @@ class InstallerShar(object):
self.files.append(filename)
self.files = list(set(self.files))
def add_file_as(self, source, basename):
if not os.path.exists(source):
self.abort("File %s does not exist." % source)
tmpdir = os.path.join(self.work_dir, "tmp")
if not os.path.exists(tmpdir):
os.mkdir(tmpdir)
dst = os.path.join(tmpdir, basename)
shutil.copy(source, dst)
self.add_file(dst)
def add_dir(self, dir_):
if not os.path.isdir(dir_):
self.abort("Directory %s does not exist." % dir_)
@@ -174,6 +186,16 @@ if __name__ == '__main__':
ap.add_argument("--verbose", '-v', help="Verbose output.", action='store_true')
ap.add_argument("--out", help="Destination Filename")
ap.add_argument("--preinstall-script",
help="Specify a preinstall script (runs before installer)")
ap.add_argument("--postinstall-script",
help="Specify a preinstall script (runs after installer)")
ap.add_argument("--preinstall-plugin",
help="Specify a preinstall plugin (runs from within the installer chroot)")
ap.add_argument("--postinstall-plugin",
help="Specify a postinstall plugin (runs from within the installer chroot)")
ops = ap.parse_args()
installer = InstallerShar(ops.arch, ops.work_dir)
@@ -209,6 +231,20 @@ if __name__ == '__main__':
if ops.swi:
installer.add_swi(ops.swi)
hookdir = os.path.join(installer.work_dir, "tmp")
if not os.path.exists(hookdir):
os.makedirs(hookdir)
if ops.preinstall_script:
installer.add_file_as(ops.preinstall_script, "preinstall.sh")
if ops.postinstall_script:
installer.add_file_as(ops.postinstall_script, "postinstall.sh")
if ops.preinstall_plugin:
installer.add_file_as(ops.preinstall_plugin, "preinstall.py")
if ops.postinstall_plugin:
installer.add_file_as(ops.postinstall_plugin, "postinstall.py")
iname = os.path.abspath(ops.out)
installer.build(iname)
logger.info("installer: %s" % iname)