4 # Copyright (c) 2007 Red Hat Inc.
5 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
7 # This program is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by the Free
9 # Software Foundation; version 2 of the License
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc., 59
18 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 from __future__ import with_statement
32 from mic import kickstart
34 from mic.utils.errors import CreatorError, Abort
35 from mic.utils import misc, rpmmisc, runner, proxy, fs_related as fs
37 class BaseImageCreator(object):
38 """Installs a system to a chroot directory.
40 ImageCreator is the simplest creator class available; it will install and
41 configure a system image according to the supplied kickstart file.
45 import mic.imgcreate as imgcreate
46 ks = imgcreate.read_kickstart("foo.ks")
47 imgcreate.ImageCreator(ks, "foo").create()
54 def __init__(self, createopts = None, pkgmgr = None):
55 """Initialize an ImageCreator instance.
57 ks -- a pykickstart.KickstartParser instance; this instance will be
58 used to drive the install by e.g. providing the list of packages
59 to be installed, the system configuration and %post scripts
61 name -- a name for the image; used for e.g. image filenames or
67 self.__builddir = None
68 self.__bindmounts = []
72 self.tmpdir = "/var/tmp/mic"
73 self.cachedir = "/var/tmp/mic/cache"
75 self.target_arch = "noarch"
76 self._local_pkgs_path = None
78 # If the kernel is save to the destdir when copy_kernel cmd is called.
79 self._need_copy_kernel = False
82 # Mapping table for variables that have different names.
83 optmap = {"pkgmgr" : "pkgmgr_name",
85 "arch" : "target_arch",
86 "local_pkgs_path" : "_local_pkgs_path",
87 "copy_kernel" : "_need_copy_kernel",
90 # update setting from createopts
91 for key in createopts.keys():
96 setattr(self, option, createopts[key])
98 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
100 if 'release' in createopts and createopts['release']:
101 self.name += '-' + createopts['release']
103 # pending FEA: save log by default for --release
106 if '@NAME@' in self.pack_to:
107 self.pack_to = self.pack_to.replace('@NAME@', self.name)
108 (tar, ext) = os.path.splitext(self.pack_to)
109 if ext in (".gz", ".bz2") and tar.endswith(".tar"):
111 if ext not in misc.pack_formats:
112 self.pack_to += ".tar"
114 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe", "passwd"]
116 # Output image file names
119 # A flag to generate checksum
120 self._genchecksum = False
122 self._alt_initrd_name = None
124 self._recording_pkgs = []
126 # available size in root fs, init to 0
127 self._root_fs_avail = 0
129 # Name of the disk image file that is created.
130 self._img_name = None
132 self.image_format = None
134 # Save qemu emulator file name in order to clean up it finally
135 self.qemu_emulator = None
137 # No ks provided when called by convertor, so skip the dependency check
139 # If we have btrfs partition we need to check that we have toosl for those
140 for part in self.ks.handler.partition.partitions:
141 if part.fstype and part.fstype == "btrfs":
142 self._dep_checks.append("mkfs.btrfs")
145 if self.target_arch and self.target_arch.startswith("arm"):
146 for dep in self._dep_checks:
147 if dep == "extlinux":
148 self._dep_checks.remove(dep)
150 if not os.path.exists("/usr/bin/qemu-arm") or \
151 not misc.is_statically_linked("/usr/bin/qemu-arm"):
152 self._dep_checks.append("qemu-arm-static")
154 if os.path.exists("/proc/sys/vm/vdso_enabled"):
155 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
156 vdso_value = vdso_fh.read().strip()
158 if (int)(vdso_value) == 1:
159 msger.warning("vdso is enabled on your host, which might "
160 "cause problems with arm emulations.\n"
161 "\tYou can disable vdso with following command before "
162 "starting image build:\n"
163 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
165 # make sure the specified tmpdir and cachedir exist
166 if not os.path.exists(self.tmpdir):
167 os.makedirs(self.tmpdir)
168 if not os.path.exists(self.cachedir):
169 os.makedirs(self.cachedir)
175 def __get_instroot(self):
176 if self.__builddir is None:
177 raise CreatorError("_instroot is not valid before calling mount()")
178 return self.__builddir + "/install_root"
179 _instroot = property(__get_instroot)
180 """The location of the install root directory.
182 This is the directory into which the system is installed. Subclasses may
183 mount a filesystem image here or copy files to/from here.
185 Note, this directory does not exist before ImageCreator.mount() is called.
187 Note also, this is a read-only attribute.
191 def __get_outdir(self):
192 if self.__builddir is None:
193 raise CreatorError("_outdir is not valid before calling mount()")
194 return self.__builddir + "/out"
195 _outdir = property(__get_outdir)
196 """The staging location for the final image.
198 This is where subclasses should stage any files that are part of the final
199 image. ImageCreator.package() will copy any files found here into the
200 requested destination directory.
202 Note, this directory does not exist before ImageCreator.mount() is called.
204 Note also, this is a read-only attribute.
210 # Hooks for subclasses
212 def _mount_instroot(self, base_on = None):
213 """Mount or prepare the install root directory.
215 This is the hook where subclasses may prepare the install root by e.g.
216 mounting creating and loopback mounting a filesystem image to
219 There is no default implementation.
221 base_on -- this is the value passed to mount() and can be interpreted
222 as the subclass wishes; it might e.g. be the location of
223 a previously created ISO containing a system image.
228 def _unmount_instroot(self):
229 """Undo anything performed in _mount_instroot().
231 This is the hook where subclasses must undo anything which was done
232 in _mount_instroot(). For example, if a filesystem image was mounted
233 onto _instroot, it should be unmounted here.
235 There is no default implementation.
240 def _create_bootconfig(self):
241 """Configure the image so that it's bootable.
243 This is the hook where subclasses may prepare the image for booting by
244 e.g. creating an initramfs and bootloader configuration.
246 This hook is called while the install root is still mounted, after the
247 packages have been installed and the kickstart configuration has been
248 applied, but before the %post scripts have been executed.
250 There is no default implementation.
255 def _stage_final_image(self):
256 """Stage the final system image in _outdir.
258 This is the hook where subclasses should place the image in _outdir
259 so that package() can copy it to the requested destination directory.
261 By default, this moves the install root into _outdir.
264 shutil.move(self._instroot, self._outdir + "/" + self.name)
266 def get_installed_packages(self):
267 return self._pkgs_content.keys()
269 def _save_recording_pkgs(self, destdir):
270 """Save the list or content of installed packages to file.
272 pkgs = self._pkgs_content.keys()
273 pkgs.sort() # inplace op
275 if not os.path.exists(destdir):
277 if 'name' in self._recording_pkgs :
278 namefile = os.path.join(destdir, self.name + '.packages')
279 f = open(namefile, "w")
280 content = '\n'.join(pkgs)
283 self.outimage.append(namefile);
285 # if 'content', save more details
286 if 'content' in self._recording_pkgs :
287 contfile = os.path.join(destdir, self.name + '.files')
288 f = open(contfile, "w")
293 pkgcont = self._pkgs_content[pkg]
295 if pkgcont.has_key('dir'):
296 items = map(lambda x:x+'/', pkgcont['dir'])
297 if pkgcont.has_key('file'):
298 items.extend(pkgcont['file'])
302 content += '\n '.join(items)
308 self.outimage.append(contfile)
310 if 'license' in self._recording_pkgs:
311 licensefile = os.path.join(destdir, self.name + '.license')
312 f = open(licensefile, "w")
314 f.write('Summary:\n')
315 for license in reversed(sorted(self._pkgs_license, key=\
316 lambda license: len(self._pkgs_license[license]))):
317 f.write(" - %s: %s\n" \
318 % (license, len(self._pkgs_license[license])))
320 f.write('\nDetails:\n')
321 for license in reversed(sorted(self._pkgs_license, key=\
322 lambda license: len(self._pkgs_license[license]))):
323 f.write(" - %s:\n" % (license))
324 for pkg in sorted(self._pkgs_license[license]):
325 f.write(" - %s\n" % (pkg))
329 self.outimage.append(licensefile);
331 def _get_required_packages(self):
332 """Return a list of required packages.
334 This is the hook where subclasses may specify a set of packages which
335 it requires to be installed.
337 This returns an empty list by default.
339 Note, subclasses should usually chain up to the base class
340 implementation of this hook.
345 def _get_excluded_packages(self):
346 """Return a list of excluded packages.
348 This is the hook where subclasses may specify a set of packages which
349 it requires _not_ to be installed.
351 This returns an empty list by default.
353 Note, subclasses should usually chain up to the base class
354 implementation of this hook.
359 def _get_local_packages(self):
360 """Return a list of rpm path to be local installed.
362 This is the hook where subclasses may specify a set of rpms which
363 it requires to be installed locally.
365 This returns an empty list by default.
367 Note, subclasses should usually chain up to the base class
368 implementation of this hook.
371 if self._local_pkgs_path:
372 if os.path.isdir(self._local_pkgs_path):
374 os.path.join(self._local_pkgs_path, '*.rpm'))
375 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
376 return [self._local_pkgs_path]
380 def _get_fstab(self):
381 """Return the desired contents of /etc/fstab.
383 This is the hook where subclasses may specify the contents of
384 /etc/fstab by returning a string containing the desired contents.
386 A sensible default implementation is provided.
389 s = "/dev/root / %s %s 0 0\n" \
391 "defaults,noatime" if not self._fsopts else self._fsopts)
392 s += self._get_fstab_special()
395 def _get_fstab_special(self):
396 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
397 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
398 s += "proc /proc proc defaults 0 0\n"
399 s += "sysfs /sys sysfs defaults 0 0\n"
402 def _get_post_scripts_env(self, in_chroot):
403 """Return an environment dict for %post scripts.
405 This is the hook where subclasses may specify some environment
406 variables for %post scripts by return a dict containing the desired
409 By default, this returns an empty dict.
411 in_chroot -- whether this %post script is to be executed chroot()ed
417 def __get_imgname(self):
419 _name = property(__get_imgname)
420 """The name of the image file.
424 def _get_kernel_versions(self):
425 """Return a dict detailing the available kernel types/versions.
427 This is the hook where subclasses may override what kernel types and
428 versions should be available for e.g. creating the booloader
431 A dict should be returned mapping the available kernel types to a list
432 of the available versions for those kernels.
434 The default implementation uses rpm to iterate over everything
435 providing 'kernel', finds /boot/vmlinuz-* and returns the version
436 obtained from the vmlinuz filename. (This can differ from the kernel
437 RPM's n-v-r in the case of e.g. xen)
440 def get_kernel_versions(instroot):
443 files = glob.glob(instroot + "/boot/vmlinuz-*")
445 version = os.path.basename(file)[8:]
448 versions.add(version)
449 ret["kernel"] = list(versions)
452 def get_version(header):
454 for f in header['filenames']:
455 if f.startswith('/boot/vmlinuz-'):
460 return get_kernel_versions(self._instroot)
462 ts = rpm.TransactionSet(self._instroot)
465 for header in ts.dbMatch('provides', 'kernel'):
466 version = get_version(header)
470 name = header['name']
472 ret[name] = [version]
473 elif not version in ret[name]:
474 ret[name].append(version)
480 # Helpers for subclasses
482 def _do_bindmounts(self):
483 """Mount various system directories onto _instroot.
485 This method is called by mount(), but may also be used by subclasses
486 in order to re-mount the bindmounts after modifying the underlying
490 for b in self.__bindmounts:
493 def _undo_bindmounts(self):
494 """Unmount the bind-mounted system directories from _instroot.
496 This method is usually only called by unmount(), but may also be used
497 by subclasses in order to gain access to the filesystem obscured by
498 the bindmounts - e.g. in order to create device nodes on the image
502 self.__bindmounts.reverse()
503 for b in self.__bindmounts:
507 """Chroot into the install root.
509 This method may be used by subclasses when executing programs inside
510 the install root e.g.
512 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
515 os.chroot(self._instroot)
518 def _mkdtemp(self, prefix = "tmp-"):
519 """Create a temporary directory.
521 This method may be used by subclasses to create a temporary directory
522 for use in building the final image - e.g. a subclass might create
523 a temporary directory in order to bundle a set of files into a package.
525 The subclass may delete this directory if it wishes, but it will be
526 automatically deleted by cleanup().
528 The absolute path to the temporary directory is returned.
530 Note, this method should only be called after mount() has been called.
532 prefix -- a prefix which should be used when creating the directory;
536 self.__ensure_builddir()
537 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
539 def _mkstemp(self, prefix = "tmp-"):
540 """Create a temporary file.
542 This method may be used by subclasses to create a temporary file
543 for use in building the final image - e.g. a subclass might need
544 a temporary location to unpack a compressed file.
546 The subclass may delete this file if it wishes, but it will be
547 automatically deleted by cleanup().
549 A tuple containing a file descriptor (returned from os.open() and the
550 absolute path to the temporary directory is returned.
552 Note, this method should only be called after mount() has been called.
554 prefix -- a prefix which should be used when creating the file;
558 self.__ensure_builddir()
559 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
561 def _mktemp(self, prefix = "tmp-"):
562 """Create a temporary file.
564 This method simply calls _mkstemp() and closes the returned file
567 The absolute path to the temporary file is returned.
569 Note, this method should only be called after mount() has been called.
571 prefix -- a prefix which should be used when creating the file;
576 (f, path) = self._mkstemp(prefix)
582 # Actual implementation
584 def __ensure_builddir(self):
585 if not self.__builddir is None:
589 self.__builddir = tempfile.mkdtemp(dir = self.tmpdir,
590 prefix = "imgcreate-")
591 except OSError, (err, msg):
592 raise CreatorError("Failed create build directory in %s: %s" %
595 def get_cachedir(self, cachedir = None):
599 self.__ensure_builddir()
601 self.cachedir = cachedir
603 self.cachedir = self.__builddir + "/mic-cache"
604 fs.makedirs(self.cachedir)
607 def __sanity_check(self):
608 """Ensure that the config we've been given is sane."""
609 if not (kickstart.get_packages(self.ks) or
610 kickstart.get_groups(self.ks)):
611 raise CreatorError("No packages or groups specified")
613 kickstart.convert_method_to_repo(self.ks)
615 if not kickstart.get_repos(self.ks):
616 raise CreatorError("No repositories specified")
618 def __write_fstab(self):
619 fstab = open(self._instroot + "/etc/fstab", "w")
620 fstab.write(self._get_fstab())
623 def __create_minimal_dev(self):
624 """Create a minimal /dev so that we don't corrupt the host /dev"""
625 origumask = os.umask(0000)
626 devices = (('null', 1, 3, 0666),
627 ('urandom',1, 9, 0666),
628 ('random', 1, 8, 0666),
629 ('full', 1, 7, 0666),
630 ('ptmx', 5, 2, 0666),
632 ('zero', 1, 5, 0666))
634 links = (("/proc/self/fd", "/dev/fd"),
635 ("/proc/self/fd/0", "/dev/stdin"),
636 ("/proc/self/fd/1", "/dev/stdout"),
637 ("/proc/self/fd/2", "/dev/stderr"))
639 for (node, major, minor, perm) in devices:
640 if not os.path.exists(self._instroot + "/dev/" + node):
641 os.mknod(self._instroot + "/dev/" + node,
643 os.makedev(major,minor))
645 for (src, dest) in links:
646 if not os.path.exists(self._instroot + dest):
647 os.symlink(src, self._instroot + dest)
651 def mount(self, base_on = None, cachedir = None):
652 """Setup the target filesystem in preparation for an install.
654 This function sets up the filesystem which the ImageCreator will
655 install into and configure. The ImageCreator class merely creates an
656 install root directory, bind mounts some system directories (e.g. /dev)
657 and writes out /etc/fstab. Other subclasses may also e.g. create a
658 sparse file, format it and loopback mount it to the install root.
660 base_on -- a previous install on which to base this install; defaults
661 to None, causing a new image to be created
663 cachedir -- a directory in which to store the Yum cache; defaults to
664 None, causing a new cache to be created; by setting this
665 to another directory, the same cache can be reused across
669 self.__ensure_builddir()
671 fs.makedirs(self._instroot)
672 fs.makedirs(self._outdir)
674 self._mount_instroot(base_on)
676 for d in ("/dev/pts",
683 fs.makedirs(self._instroot + d)
685 if self.target_arch and self.target_arch.startswith("arm"):
686 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
689 self.get_cachedir(cachedir)
691 # bind mount system directories into _instroot
692 for (f, dest) in [("/sys", None),
694 ("/proc/sys/fs/binfmt_misc", None),
696 self.__bindmounts.append(fs.BindChrootMount(f, self._instroot, dest))
698 self._do_bindmounts()
700 self.__create_minimal_dev()
702 if os.path.exists(self._instroot + "/etc/mtab"):
703 os.unlink(self._instroot + "/etc/mtab")
704 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
708 # get size of available space in 'instroot' fs
709 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
712 """Unmounts the target filesystem.
714 The ImageCreator class detaches the system from the install root, but
715 other subclasses may also detach the loopback mounted filesystem image
716 from the install root.
720 mtab = self._instroot + "/etc/mtab"
721 if not os.path.islink(mtab):
722 os.unlink(self._instroot + "/etc/mtab")
724 if self.qemu_emulator:
725 os.unlink(self._instroot + self.qemu_emulator)
729 self._undo_bindmounts()
731 """ Clean up yum garbage """
733 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
734 if os.path.exists(instroot_pdir):
735 shutil.rmtree(instroot_pdir, ignore_errors = True)
736 yumlibdir = self._instroot + "/var/lib/yum"
737 if os.path.exists(yumlibdir):
738 shutil.rmtree(yumlibdir, ignore_errors = True)
742 self._unmount_instroot()
745 """Unmounts the target filesystem and deletes temporary files.
747 This method calls unmount() and then deletes any temporary files and
748 directories that were created on the host system while building the
751 Note, make sure to call this method once finished with the creator
752 instance in order to ensure no stale files are left on the host e.g.:
754 creator = ImageCreator(ks, name)
761 if not self.__builddir:
766 shutil.rmtree(self.__builddir, ignore_errors = True)
767 self.__builddir = None
769 def __is_excluded_pkg(self, pkg):
770 if pkg in self._excluded_pkgs:
771 self._excluded_pkgs.remove(pkg)
774 for xpkg in self._excluded_pkgs:
775 if xpkg.endswith('*'):
776 if pkg.startswith(xpkg[:-1]):
778 elif xpkg.startswith('*'):
779 if pkg.endswith(xpkg[1:]):
784 def __select_packages(self, pkg_manager):
786 for pkg in self._required_pkgs:
787 e = pkg_manager.selectPackage(pkg)
789 if kickstart.ignore_missing(self.ks):
790 skipped_pkgs.append(pkg)
791 elif self.__is_excluded_pkg(pkg):
792 skipped_pkgs.append(pkg)
794 raise CreatorError("Failed to find package '%s' : %s" %
797 for pkg in skipped_pkgs:
798 msger.warning("Skipping missing package '%s'" % (pkg,))
800 def __select_groups(self, pkg_manager):
802 for group in self._required_groups:
803 e = pkg_manager.selectGroup(group.name, group.include)
805 if kickstart.ignore_missing(self.ks):
806 skipped_groups.append(group)
808 raise CreatorError("Failed to find group '%s' : %s" %
811 for group in skipped_groups:
812 msger.warning("Skipping missing group '%s'" % (group.name,))
814 def __deselect_packages(self, pkg_manager):
815 for pkg in self._excluded_pkgs:
816 pkg_manager.deselectPackage(pkg)
818 def __localinst_packages(self, pkg_manager):
819 for rpm_path in self._get_local_packages():
820 pkg_manager.installLocal(rpm_path)
822 def __preinstall_packages(self, pkg_manager):
826 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
827 for pkg in self._preinstall_pkgs:
828 pkg_manager.preInstall(pkg)
830 def __attachment_packages(self, pkg_manager):
834 self._attachment = []
835 for item in kickstart.get_attachment(self.ks):
836 if item.startswith('/'):
837 fpaths = os.path.join(self._instroot, item.lstrip('/'))
838 for fpath in glob.glob(fpaths):
839 self._attachment.append(fpath)
841 filelist = pkg_manager.getFilelist(item)
843 # found rpm in rootfs
844 for pfile in pkg_manager.getFilelist(item):
845 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
846 self._attachment.append(fpath)
849 # try to retrieve rpm file
850 url = pkg_manager.package_url(item)
852 msger.warning("Can't get url from repo for %s" % item)
855 aproxy = proxy.get_proxy_for(url)
857 proxies = {url.split(':')[0]: aproxy}
858 fpath = os.path.join(self.cachedir, os.path.basename(url))
859 if not os.path.exists(fpath):
862 fpath = rpmmisc.myurlgrab(url, fpath, proxies, None)
866 tmpdir = self._mkdtemp()
867 misc.extract_rpm(fpath, tmpdir)
868 for (root, dirs, files) in os.walk(tmpdir):
870 fpath = os.path.join(root, fname)
871 self._attachment.append(fpath)
873 def install(self, repo_urls = {}):
874 """Install packages into the install root.
876 This function installs the packages listed in the supplied kickstart
877 into the install root. By default, the packages are installed from the
878 repository URLs specified in the kickstart.
880 repo_urls -- a dict which maps a repository name to a repository URL;
881 if supplied, this causes any repository URLs specified in
882 the kickstart to be overridden.
886 # initialize pkg list to install
888 self.__sanity_check()
890 self._required_pkgs = \
891 kickstart.get_packages(self.ks, self._get_required_packages())
892 self._excluded_pkgs = \
893 kickstart.get_excluded(self.ks, self._get_excluded_packages())
894 self._required_groups = kickstart.get_groups(self.ks)
896 self._required_pkgs = None
897 self._excluded_pkgs = None
898 self._required_groups = None
900 pkg_manager = self.get_pkg_manager()
903 for repo in kickstart.get_repos(self.ks, repo_urls):
904 (name, baseurl, mirrorlist, inc, exc,
905 proxy, proxy_username, proxy_password, debuginfo,
906 source, gpgkey, disable, ssl_verify, cost, priority) = repo
908 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
909 proxy_username, proxy_password, inc, exc, ssl_verify,
912 if kickstart.exclude_docs(self.ks):
913 rpm.addMacro("_excludedocs", "1")
914 rpm.addMacro("_dbpath", "/var/lib/rpm")
915 rpm.addMacro("__file_context_path", "%{nil}")
916 if kickstart.inst_langs(self.ks) != None:
917 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
921 self.__preinstall_packages(pkg_manager)
922 self.__select_packages(pkg_manager)
923 self.__select_groups(pkg_manager)
924 self.__deselect_packages(pkg_manager)
925 self.__localinst_packages(pkg_manager)
927 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
928 checksize = self._root_fs_avail
930 checksize -= BOOT_SAFEGUARD
932 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
933 pkg_manager.runInstall(checksize)
934 except CreatorError, e:
937 self._pkgs_content = pkg_manager.getAllContent()
938 self._pkgs_license = pkg_manager.getPkgsLicense()
939 self.__attachment_packages(pkg_manager)
946 # do some clean up to avoid lvm info leakage. this sucks.
947 for subdir in ("cache", "backup", "archive"):
948 lvmdir = self._instroot + "/etc/lvm/" + subdir
950 for f in os.listdir(lvmdir):
951 os.unlink(lvmdir + "/" + f)
955 def postinstall(self):
956 self.copy_attachment()
958 def __run_post_scripts(self):
959 msger.info("Running scripts ...")
960 if os.path.exists(self._instroot + "/tmp"):
961 shutil.rmtree(self._instroot + "/tmp")
962 os.mkdir (self._instroot + "/tmp", 0755)
963 for s in kickstart.get_post_scripts(self.ks):
964 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
965 dir = self._instroot + "/tmp")
967 s.script = s.script.replace("\r", "")
968 os.write(fd, s.script)
972 env = self._get_post_scripts_env(s.inChroot)
975 env["INSTALL_ROOT"] = self._instroot
976 env["IMG_NAME"] = self._name
980 preexec = self._chroot
981 script = "/tmp/" + os.path.basename(path)
985 subprocess.call([s.interp, script],
986 preexec_fn = preexec,
990 except OSError, (err, msg):
991 raise CreatorError("Failed to execute %%post script "
992 "with '%s' : %s" % (s.interp, msg))
996 def __save_repo_keys(self, repodata):
1000 gpgkeydir = "/etc/pki/rpm-gpg"
1001 fs.makedirs(self._instroot + gpgkeydir)
1002 for repo in repodata:
1004 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1005 shutil.copy(repo["repokey"], self._instroot + repokey)
1007 def configure(self, repodata = None):
1008 """Configure the system image according to the kickstart.
1010 This method applies the (e.g. keyboard or network) configuration
1011 specified in the kickstart and executes the kickstart %post scripts.
1013 If necessary, it also prepares the image to be bootable by e.g.
1014 creating an initrd and bootloader configuration.
1017 ksh = self.ks.handler
1019 msger.info('Applying configurations ...')
1021 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1022 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1023 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1024 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1025 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1026 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1027 kickstart.UserConfig(self._instroot).apply(ksh.user)
1028 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1029 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1030 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1031 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1032 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1033 self.__save_repo_keys(repodata)
1034 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata)
1036 msger.warning("Failed to apply configuration to image")
1039 self._create_bootconfig()
1040 self.__run_post_scripts()
1042 def launch_shell(self, launch):
1043 """Launch a shell in the install root.
1045 This method is launches a bash shell chroot()ed in the install root;
1046 this can be useful for debugging.
1050 msger.info("Launching shell. Exit to continue.")
1051 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1053 def do_genchecksum(self, image_name):
1054 if not self._genchecksum:
1057 md5sum = misc.get_md5sum(image_name)
1058 with open(image_name + ".md5sum", "w") as f:
1059 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1060 self.outimage.append(image_name+".md5sum")
1062 def package(self, destdir = "."):
1063 """Prepares the created image for final delivery.
1065 In its simplest form, this method merely copies the install root to the
1066 supplied destination directory; other subclasses may choose to package
1067 the image by e.g. creating a bootable ISO containing the image and
1068 bootloader configuration.
1070 destdir -- the directory into which the final image should be moved;
1071 this defaults to the current directory.
1074 self._stage_final_image()
1076 if not os.path.exists(destdir):
1077 fs.makedirs(destdir)
1079 if self._recording_pkgs:
1080 self._save_recording_pkgs(destdir)
1082 # For image formats with two or multiple image files, it will be
1083 # better to put them under a directory
1084 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1085 destdir = os.path.join(destdir, "%s-%s" \
1086 % (self.name, self.image_format))
1087 msger.debug("creating destination dir: %s" % destdir)
1088 fs.makedirs(destdir)
1090 # Ensure all data is flushed to _outdir
1091 runner.quiet('sync')
1093 misc.check_space_pre_cp(self._outdir, destdir)
1094 for f in os.listdir(self._outdir):
1095 shutil.move(os.path.join(self._outdir, f),
1096 os.path.join(destdir, f))
1097 self.outimage.append(os.path.join(destdir, f))
1098 self.do_genchecksum(os.path.join(destdir, f))
1100 def print_outimage_info(self):
1101 msg = "The new image can be found here:\n"
1102 self.outimage.sort()
1103 for file in self.outimage:
1104 msg += ' %s\n' % os.path.abspath(file)
1108 def check_depend_tools(self):
1109 for tool in self._dep_checks:
1110 fs.find_binary_path(tool)
1112 def package_output(self, image_format, destdir = ".", package="none"):
1113 if not package or package == "none":
1116 destdir = os.path.abspath(os.path.expanduser(destdir))
1117 (pkg, comp) = os.path.splitext(package)
1119 comp=comp.lstrip(".")
1123 dst = "%s/%s-%s.tar.%s" % (destdir, self.name, image_format, comp)
1125 dst = "%s/%s-%s.tar" % (destdir, self.name, image_format)
1126 msger.info("creating %s" % dst)
1127 tar = tarfile.open(dst, "w:" + comp)
1129 for file in self.outimage:
1130 msger.info("adding %s to %s" % (file, dst))
1132 arcname=os.path.join("%s-%s" \
1133 % (self.name, image_format),
1134 os.path.basename(file)))
1135 if os.path.isdir(file):
1136 shutil.rmtree(file, ignore_errors = True)
1142 '''All the file in outimage has been packaged into tar.* file'''
1143 self.outimage = [dst]
1145 def release_output(self, config, destdir, release):
1146 """ Create release directory and files
1150 """ release path """
1151 return os.path.join(destdir, fn)
1153 outimages = self.outimage
1156 new_kspath = _rpath(self.name+'.ks')
1157 with open(config) as fr:
1158 with open(new_kspath, "w") as wf:
1159 # When building a release we want to make sure the .ks
1160 # file generates the same build even when --release= is not used.
1161 wf.write(fr.read().replace("@BUILD_ID@", release))
1162 outimages.append(new_kspath)
1164 # rename iso and usbimg
1165 for f in os.listdir(destdir):
1166 if f.endswith(".iso"):
1167 newf = f[:-4] + '.img'
1168 elif f.endswith(".usbimg"):
1169 newf = f[:-7] + '.img'
1172 os.rename(_rpath(f), _rpath(newf))
1173 outimages.append(_rpath(newf))
1176 with open(_rpath("MANIFEST"), "w") as wf:
1177 for f in os.listdir(destdir):
1181 if os.path.isdir(os.path.join(destdir, f)):
1184 md5sum = misc.get_md5sum(_rpath(f))
1185 # There needs to be two spaces between the sum and
1186 # filepath to match the syntax with md5sum.
1187 # This way also md5sum -c MANIFEST can be used by users
1188 wf.write("%s *%s\n" % (md5sum, f))
1190 outimages.append("%s/MANIFEST" % destdir)
1192 # Filter out the nonexist file
1193 for fp in outimages[:]:
1194 if not os.path.exists("%s" % fp):
1195 outimages.remove(fp)
1197 def copy_kernel(self):
1198 """ Copy kernel files to the outimage directory.
1200 NOTE: This needs to be called before unmounting the instroot.
1203 if not self._need_copy_kernel:
1206 if not os.path.exists(self.destdir):
1207 os.makedirs(self.destdir)
1209 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1210 kernelfilename = "%s/%s-%s" % (self.destdir, self.name, os.path.basename(kernel))
1211 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel), kernelfilename))
1212 shutil.copy(kernel, kernelfilename)
1213 self.outimage.append(kernelfilename)
1216 def copy_attachment(self):
1217 """ Subclass implement it to handle attachment files
1219 NOTE: This needs to be called before unmounting the instroot.
1223 def get_pkg_manager(self):
1224 return self.pkgmgr(target_arch = self.target_arch, instroot = self._instroot, cachedir = self.cachedir)