193 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			193 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
#!/usr/bin/python3
 | 
						|
#
 | 
						|
# image-minimizer: removes files and packages on the filesystem
 | 
						|
#
 | 
						|
# Copyright 2007-2015 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 argparse
 | 
						|
import os
 | 
						|
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 d in dirs:
 | 
						|
                self.visited.add(os.path.join(root, d))
 | 
						|
            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 d in sorted(self.visited, reverse=True):
 | 
						|
            if len(os.listdir(d)) == 0:
 | 
						|
                if self.dryrun:
 | 
						|
                    print('rm -rf ' + d)
 | 
						|
                else:
 | 
						|
                    if self.verbose:
 | 
						|
                        print('rm -rf ' + d)
 | 
						|
                    os.rmdir(d)
 | 
						|
 | 
						|
    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():
 | 
						|
    parser = argparse.ArgumentParser(description="Image Minimizer")
 | 
						|
 | 
						|
    parser.set_defaults(root=os.environ.get('INSTALL_ROOT', '/mnt/sysimage/'), dry_run=False)
 | 
						|
 | 
						|
    parser.add_argument("-i", "--installroot", metavar="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_argument("--dryrun", metavar="BOOL", action="store_true", dest="dryrun",
 | 
						|
        help="If set, no filesystem changes are made.")
 | 
						|
 | 
						|
    parser.add_argument("-v", "--verbose", metavar="BOOL", action="store_true", dest="verbose",
 | 
						|
        help="Display every action as it is performed.")
 | 
						|
 | 
						|
    parser.add_argument("filename", metavar="STRING", help="Filename to process")
 | 
						|
 | 
						|
    return parser.parse_args()
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    try:
 | 
						|
        args = parse_options()
 | 
						|
        minimizer = ImageMinimizer(args.filename, args.root, args.dryrun,
 | 
						|
                                   args.verbose)
 | 
						|
        minimizer.filter()
 | 
						|
    except SystemExit as e:
 | 
						|
        sys.exit(e.code)
 | 
						|
    except KeyboardInterrupt as e:
 | 
						|
        print("Aborted at user request", file=sys.stderr)
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    main()
 |