[Lorax] move image-minimizer to lorax (#1082642)

Brian C. Lane bcl at redhat.com
Tue Apr 1 00:08:03 UTC 2014


This is used as a kickstart %post interpreter to streamline
modifications to images.

Also adds an example kickstart.

This obsoletes the old appliance-tools-minimizer and includes a Provide
so that the transition will be seamless.
---
 docs/fedora-minimized.ks | 104 +++++++++++++++++++++++++
 lorax.spec               |   4 +
 setup.py                 |   1 +
 src/bin/image-minimizer  | 198 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 307 insertions(+)
 create mode 100644 docs/fedora-minimized.ks
 create mode 100755 src/bin/image-minimizer

diff --git a/docs/fedora-minimized.ks b/docs/fedora-minimized.ks
new file mode 100644
index 0000000..ac97017
--- /dev/null
+++ b/docs/fedora-minimized.ks
@@ -0,0 +1,104 @@
+# Minimal Disk Image -- Example of image-minimizer usage in %post
+#
+sshpw --username=root --plaintext randOmStrinGhERE
+# Firewall configuration
+firewall --enabled
+# Use network installation
+url --url="http://dl.fedoraproject.org/pub/fedora/linux/development/20/x86_64/os/"
+
+# Root password
+rootpw --plaintext removethispw
+# Network information
+network  --bootproto=dhcp --onboot=on --activate
+# System authorization information
+auth --useshadow --enablemd5
+# System keyboard
+keyboard --xlayouts=us --vckeymap=us
+# System language
+lang en_US.UTF-8
+# SELinux configuration
+selinux --enforcing
+# Installation logging level
+logging --level=info
+# Shutdown after installation
+shutdown
+# System timezone
+timezone  US/Eastern
+# System bootloader configuration
+bootloader --location=mbr
+# Clear the Master Boot Record
+zerombr
+# Partition clearing information
+clearpart --all
+# Disk partitioning information
+part / --fstype="ext4" --size=4000
+part swap --size=1000
+
+%post
+# Remove root password
+passwd -d root > /dev/null
+%end
+
+%packages
+ at core
+kernel
+memtest86+
+grub2-efi
+grub2
+shim
+syslinux
+-dracut-config-rescue
+%end
+
+#
+# Use the image-minimizer to remove some packages and dirs
+#
+%post --interpreter=image-minimizer --nochroot
+
+
+# Kernel modules minimization
+# Drop many filesystems
+drop /lib/modules/*/kernel/fs
+keep /lib/modules/*/kernel/fs/ext*
+keep /lib/modules/*/kernel/fs/mbcache*
+keep /lib/modules/*/kernel/fs/squashfs
+keep /lib/modules/*/kernel/fs/jbd*
+keep /lib/modules/*/kernel/fs/btrfs
+keep /lib/modules/*/kernel/fs/cifs*
+keep /lib/modules/*/kernel/fs/fat
+keep /lib/modules/*/kernel/fs/nfs
+keep /lib/modules/*/kernel/fs/nfs_common
+keep /lib/modules/*/kernel/fs/fscache
+keep /lib/modules/*/kernel/fs/lockd
+keep /lib/modules/*/kernel/fs/nls/nls_utf8.ko
+keep /lib/modules/*/kernel/fs/configfs/configfs.ko
+keep /lib/modules/*/kernel/fs/fuse
+keep /lib/modules/*/kernel/fs/isofs
+# No sound
+drop /lib/modules/*/kernel/sound
+
+
+# Drop some unused rpms, without dropping dependencies
+droprpm checkpolicy
+droprpm dmraid-events
+droprpm gamin
+droprpm gnupg2
+droprpm linux-atm-libs
+droprpm make
+droprpm mtools
+droprpm mysql-libs
+droprpm perl
+droprpm perl-Module-Pluggable
+droprpm perl-Net-Telnet
+droprpm perl-PathTools
+droprpm perl-Pod-Escapes
+droprpm perl-Pod-Simple
+droprpm perl-Scalar-List-Utils
+droprpm perl-hivex
+droprpm perl-macros
+droprpm sgpio
+droprpm syslinux
+droprpm system-config-firewall-base
+droprpm usermode
+
+%end
diff --git a/lorax.spec b/lorax.spec
index 1adfd63..7b7e665 100644
--- a/lorax.spec
+++ b/lorax.spec
@@ -60,6 +60,9 @@ Requires:       grub2-tools
 Requires:       openssh
 %endif
 
+# Moved image-minimizer tool to lorax
+Provides:       appliance-tools-minimizer
+
 %description
 Lorax is a tool for creating the anaconda install images.
 
@@ -85,6 +88,7 @@ make DESTDIR=$RPM_BUILD_ROOT mandir=%{_mandir} install
 %{_sbindir}/lorax
 %{_sbindir}/mkefiboot
 %{_sbindir}/livemedia-creator
+%{_bindir}/image-minimizer
 %dir %{_sysconfdir}/lorax
 %config(noreplace) %{_sysconfdir}/lorax/lorax.conf
 %dir %{_datadir}/lorax
diff --git a/setup.py b/setup.py
index 4d5b543..5d38b5b 100644
--- a/setup.py
+++ b/setup.py
@@ -18,6 +18,7 @@ for root, dnames, fnames in os.walk("share"):
 # executable
 data_files.append(("/usr/sbin", ["src/sbin/lorax", "src/sbin/mkefiboot",
                                  "src/sbin/livemedia-creator"]))
+data_files.append(("/usr/bin",  ["src/bin/image-minimizer"]))
 
 # get the version
 sys.path.insert(0, "src")
diff --git a/src/bin/image-minimizer b/src/bin/image-minimizer
new file mode 100755
index 0000000..e613556
--- /dev/null
+++ b/src/bin/image-minimizer
@@ -0,0 +1,198 @@
+#!/usr/bin/python
+#
+# image-minimizer: removes files and packages on the filesystem
+#
+# Copyright 2007-2010 Red Hat  Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+import glob
+import optparse
+import os
+import shutil
+import sys
+import rpm
+
+class ImageMinimizer:
+    filename = ''
+    dryrun = False
+    verbose = False
+    prefix = None
+    drops = set()
+    visited = set()
+    drops_rpm = set()
+    ts = None
+
+    def __init__(self, filename, root, dryrun, verbose):
+        self.filename = filename
+        self.prefix = root
+        self.dryrun = dryrun
+        self.verbose = verbose
+        self.ts = None
+
+    # Recursively adds all files and directories.
+    # This is done becuase globbing does not allow
+    # ** for arbitrary nesting.
+    def add_directory(self, files, dirname):
+        self.visited.add(dirname)
+        for root, dirs, items in os.walk(dirname):
+            for dir in dirs:
+                self.visited.add(os.path.join(root, dir))
+            for name in items:
+                files.add(os.path.join(root, name))
+
+    def add_pattern(self, files, pattern):
+        globs = glob.glob(pattern)
+        if self.verbose and len(globs) == 0:
+            print "%s file not found" % pattern
+        for g in globs:
+            if os.path.isdir(g):
+                self.add_directory(files, g)
+            else:
+                files.add(g)
+
+    def add_pattern_rpm(self, rpms, pattern):
+        if self.ts is None:
+            if self.prefix is None:
+                raise Exception ('Must specify installation root for droprpm/keeprpm')
+            self.ts = rpm.TransactionSet(self.prefix)
+        mi = self.ts.dbMatch()
+        mi.pattern('name', rpm.RPMMIRE_GLOB, pattern)
+        not_found = True
+        for hdr in mi:
+            not_found = False
+            rpms.add(hdr['name'])
+        if self.verbose and not_found:
+            print "%s package not found" % pattern
+
+    # Parses each line in the ifle
+    def parse_line(self, line):
+        command = ""
+        pattern = ""
+        tok = line.split(None,1)
+        if len(tok) > 0:
+            command = tok[0].lower()
+            if len(tok) > 1:
+                pattern = tok[1].strip()
+
+        # Strip out all the comments and blank lines
+        if not (command.startswith('#') or command==''):
+            if command == 'keep':
+                if self.prefix is not None :
+                    pattern = pattern.lstrip('/')
+                    pattern = os.path.join(self.prefix, pattern)
+                keeps = set()
+                self.add_pattern(keeps, pattern)
+                self.drops.difference_update(keeps)
+                keeps = None
+            elif command == 'drop':
+                if self.prefix is not None :
+                    pattern = pattern.lstrip('/')
+                    pattern = os.path.join(self.prefix, pattern)
+                self.add_pattern(self.drops, pattern)
+            elif command == 'keeprpm':
+                keeps_rpm = set()
+                self.add_pattern_rpm(keeps_rpm, pattern)
+                self.drops_rpm.difference_update(keeps_rpm)
+                keeps_rpm = None
+            elif command == 'droprpm':
+                self.add_pattern_rpm(self.drops_rpm, pattern)
+            else:
+                raise Exception ('Unknown Command: ' + command)
+
+    def remove(self):
+        for tag in sorted(self.drops, reverse=True):
+            self.visited.add(os.path.split(tag)[0])
+            if os.path.isdir(tag):
+                self.visited.add(tag)
+            else:
+                if self.dryrun:
+                    print 'rm ' + tag
+                else:
+                    if self.verbose:
+                        print 'rm ' + tag
+                    os.remove(tag)
+
+        #remove all empty directory. Every 8k counts!
+        for dir in sorted(self.visited, reverse=True):
+            if len(os.listdir(dir)) == 0:
+                if self.dryrun:
+                    print 'rm -rf ' + dir
+                else:
+                    if self.verbose:
+                        print 'rm -rf ' + dir
+                    os.rmdir(dir)
+
+    def remove_rpm(self):
+
+        def runCallback(reason, amount, total, key, client_data):
+            if self.verbose and reason == rpm.RPMCALLBACK_UNINST_STOP:
+                print key, "erased"
+
+        if len(self.drops_rpm) == 0:
+            return
+
+        for pkg in self.drops_rpm:
+            if self.dryrun:
+                print "erasing ", pkg
+            else:
+                self.ts.addErase(pkg)
+        if not self.dryrun:
+            # skip ts.check(), equivalent to --nodeps
+            self.ts.run(runCallback, "erase")
+
+    def filter(self):
+        for line in (open(self.filename).readlines()):
+            self.parse_line(line.strip())
+        self.remove()
+        self.remove_rpm()
+
+
+def parse_options():
+    usage = "usage: %prog [options] filename"
+    parser = optparse.OptionParser(usage=usage)
+
+    parser.set_defaults(root=os.environ.get('INSTALL_ROOT', '/mnt/sysimage/'), dry_run=False)
+
+    parser.add_option("-i", "--installroot", type="string", dest="root",
+        help="Root path to prepend to all file patterns and installation root for RPM "
+             "operations.  Defaults to INSTALL_ROOT or /mnt/sysimage/")
+
+    parser.add_option("--dryrun", action="store_true", dest="dryrun",
+        help="If set, no filesystem changes are made.")
+
+    parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
+        help="Display every action as it is performed.")
+
+    (options, args) = parser.parse_args()
+    if len(args) == 0:
+        parser.print_help()
+        sys.exit(1)
+
+    return (options, args)
+
+
+if __name__ == "__main__":
+    try:
+        (options, args) = parse_options()
+        filename = args[0]
+        minimizer = ImageMinimizer(filename, options.root, options.dryrun,
+                                    options.verbose)
+        minimizer.filter()
+    except SystemExit, e:
+        sys.exit(e.code)
+    except KeyboardInterrupt, e:
+        print >> sys.stderr, _("Aborted at user request")
+    except Exception, e:
+        print e
+        sys.exit(1)
-- 
1.9.0



More information about the anaconda-patches mailing list