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, grabber, runner, 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
80 # If the kernel is save to the destdir when copy_kernel cmd is called.
81 self._need_copy_kernel = False
84 # Mapping table for variables that have different names.
85 optmap = {"pkgmgr" : "pkgmgr_name",
87 "arch" : "target_arch",
88 "local_pkgs_path" : "_local_pkgs_path",
89 "copy_kernel" : "_need_copy_kernel",
92 # update setting from createopts
93 for key in createopts.keys():
98 setattr(self, option, createopts[key])
100 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
103 if '@NAME@' in self.pack_to:
104 self.pack_to = self.pack_to.replace('@NAME@', self.name)
105 (tar, ext) = os.path.splitext(self.pack_to)
106 if ext in (".gz", ".bz2") and tar.endswith(".tar"):
108 if ext not in misc.pack_formats:
109 self.pack_to += ".tar"
111 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
113 # Output image file names
116 # A flag to generate checksum
117 self._genchecksum = False
119 self._alt_initrd_name = None
121 self._recording_pkgs = []
123 # available size in root fs, init to 0
124 self._root_fs_avail = 0
126 # Name of the disk image file that is created.
127 self._img_name = None
129 self.image_format = None
131 # Save qemu emulator file name in order to clean up it finally
132 self.qemu_emulator = None
134 # No ks provided when called by convertor, so skip the dependency check
136 # If we have btrfs partition we need to check necessary tools
137 for part in self.ks.handler.partition.partitions:
138 if part.fstype and part.fstype == "btrfs":
139 self._dep_checks.append("mkfs.btrfs")
142 if self.target_arch and self.target_arch.startswith("arm"):
143 for dep in self._dep_checks:
144 if dep == "extlinux":
145 self._dep_checks.remove(dep)
147 if not os.path.exists("/usr/bin/qemu-arm") or \
148 not misc.is_statically_linked("/usr/bin/qemu-arm"):
149 self._dep_checks.append("qemu-arm-static")
151 if os.path.exists("/proc/sys/vm/vdso_enabled"):
152 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
153 vdso_value = vdso_fh.read().strip()
155 if (int)(vdso_value) == 1:
156 msger.warning("vdso is enabled on your host, which might "
157 "cause problems with arm emulations.\n"
158 "\tYou can disable vdso with following command before "
159 "starting image build:\n"
160 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
162 # make sure the specified tmpdir and cachedir exist
163 if not os.path.exists(self.tmpdir):
164 os.makedirs(self.tmpdir)
165 if not os.path.exists(self.cachedir):
166 os.makedirs(self.cachedir)
172 def __get_instroot(self):
173 if self.__builddir is None:
174 raise CreatorError("_instroot is not valid before calling mount()")
175 return self.__builddir + "/install_root"
176 _instroot = property(__get_instroot)
177 """The location of the install root directory.
179 This is the directory into which the system is installed. Subclasses may
180 mount a filesystem image here or copy files to/from here.
182 Note, this directory does not exist before ImageCreator.mount() is called.
184 Note also, this is a read-only attribute.
188 def __get_outdir(self):
189 if self.__builddir is None:
190 raise CreatorError("_outdir is not valid before calling mount()")
191 return self.__builddir + "/out"
192 _outdir = property(__get_outdir)
193 """The staging location for the final image.
195 This is where subclasses should stage any files that are part of the final
196 image. ImageCreator.package() will copy any files found here into the
197 requested destination directory.
199 Note, this directory does not exist before ImageCreator.mount() is called.
201 Note also, this is a read-only attribute.
207 # Hooks for subclasses
209 def _mount_instroot(self, base_on = None):
210 """Mount or prepare the install root directory.
212 This is the hook where subclasses may prepare the install root by e.g.
213 mounting creating and loopback mounting a filesystem image to
216 There is no default implementation.
218 base_on -- this is the value passed to mount() and can be interpreted
219 as the subclass wishes; it might e.g. be the location of
220 a previously created ISO containing a system image.
225 def _unmount_instroot(self):
226 """Undo anything performed in _mount_instroot().
228 This is the hook where subclasses must undo anything which was done
229 in _mount_instroot(). For example, if a filesystem image was mounted
230 onto _instroot, it should be unmounted here.
232 There is no default implementation.
237 def _create_bootconfig(self):
238 """Configure the image so that it's bootable.
240 This is the hook where subclasses may prepare the image for booting by
241 e.g. creating an initramfs and bootloader configuration.
243 This hook is called while the install root is still mounted, after the
244 packages have been installed and the kickstart configuration has been
245 applied, but before the %post scripts have been executed.
247 There is no default implementation.
252 def _stage_final_image(self):
253 """Stage the final system image in _outdir.
255 This is the hook where subclasses should place the image in _outdir
256 so that package() can copy it to the requested destination directory.
258 By default, this moves the install root into _outdir.
261 shutil.move(self._instroot, self._outdir + "/" + self.name)
263 def get_installed_packages(self):
264 return self._pkgs_content.keys()
266 def _save_recording_pkgs(self, destdir):
267 """Save the list or content of installed packages to file.
269 pkgs = self._pkgs_content.keys()
270 pkgs.sort() # inplace op
272 if not os.path.exists(destdir):
274 if 'name' in self._recording_pkgs :
275 namefile = os.path.join(destdir, self.name + '.packages')
276 f = open(namefile, "w")
277 content = '\n'.join(pkgs)
280 self.outimage.append(namefile);
282 # if 'content', save more details
283 if 'content' in self._recording_pkgs :
284 contfile = os.path.join(destdir, self.name + '.files')
285 f = open(contfile, "w")
290 pkgcont = self._pkgs_content[pkg]
292 content += '\n '.join(pkgcont)
298 self.outimage.append(contfile)
300 if 'license' in self._recording_pkgs:
301 licensefile = os.path.join(destdir, self.name + '.license')
302 f = open(licensefile, "w")
304 f.write('Summary:\n')
305 for license in reversed(sorted(self._pkgs_license, key=\
306 lambda license: len(self._pkgs_license[license]))):
307 f.write(" - %s: %s\n" \
308 % (license, len(self._pkgs_license[license])))
310 f.write('\nDetails:\n')
311 for license in reversed(sorted(self._pkgs_license, key=\
312 lambda license: len(self._pkgs_license[license]))):
313 f.write(" - %s:\n" % (license))
314 for pkg in sorted(self._pkgs_license[license]):
315 f.write(" - %s\n" % (pkg))
319 self.outimage.append(licensefile)
321 if 'vcs' in self._recording_pkgs:
322 vcsfile = os.path.join(destdir, self.name + '.vcs')
323 f = open(vcsfile, "w")
324 f.write('\n'.join(["%s\n %s" % (k, v)
325 for (k, v) in self._pkgs_vcsinfo.items()]))
328 def _get_required_packages(self):
329 """Return a list of required packages.
331 This is the hook where subclasses may specify a set of packages which
332 it requires to be installed.
334 This returns an empty list by default.
336 Note, subclasses should usually chain up to the base class
337 implementation of this hook.
342 def _get_excluded_packages(self):
343 """Return a list of excluded packages.
345 This is the hook where subclasses may specify a set of packages which
346 it requires _not_ to be installed.
348 This returns an empty list by default.
350 Note, subclasses should usually chain up to the base class
351 implementation of this hook.
356 def _get_local_packages(self):
357 """Return a list of rpm path to be local installed.
359 This is the hook where subclasses may specify a set of rpms which
360 it requires to be installed locally.
362 This returns an empty list by default.
364 Note, subclasses should usually chain up to the base class
365 implementation of this hook.
368 if self._local_pkgs_path:
369 if os.path.isdir(self._local_pkgs_path):
371 os.path.join(self._local_pkgs_path, '*.rpm'))
372 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
373 return [self._local_pkgs_path]
377 def _get_fstab(self):
378 """Return the desired contents of /etc/fstab.
380 This is the hook where subclasses may specify the contents of
381 /etc/fstab by returning a string containing the desired contents.
383 A sensible default implementation is provided.
386 s = "/dev/root / %s %s 0 0\n" \
388 "defaults,noatime" if not self._fsopts else self._fsopts)
389 s += self._get_fstab_special()
392 def _get_fstab_special(self):
393 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
394 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
395 s += "proc /proc proc defaults 0 0\n"
396 s += "sysfs /sys sysfs defaults 0 0\n"
399 def _get_post_scripts_env(self, in_chroot):
400 """Return an environment dict for %post scripts.
402 This is the hook where subclasses may specify some environment
403 variables for %post scripts by return a dict containing the desired
406 By default, this returns an empty dict.
408 in_chroot -- whether this %post script is to be executed chroot()ed
414 def __get_imgname(self):
416 _name = property(__get_imgname)
417 """The name of the image file.
421 def _get_kernel_versions(self):
422 """Return a dict detailing the available kernel types/versions.
424 This is the hook where subclasses may override what kernel types and
425 versions should be available for e.g. creating the booloader
428 A dict should be returned mapping the available kernel types to a list
429 of the available versions for those kernels.
431 The default implementation uses rpm to iterate over everything
432 providing 'kernel', finds /boot/vmlinuz-* and returns the version
433 obtained from the vmlinuz filename. (This can differ from the kernel
434 RPM's n-v-r in the case of e.g. xen)
437 def get_kernel_versions(instroot):
440 files = glob.glob(instroot + "/boot/vmlinuz-*")
442 version = os.path.basename(file)[8:]
445 versions.add(version)
446 ret["kernel"] = list(versions)
449 def get_version(header):
451 for f in header['filenames']:
452 if f.startswith('/boot/vmlinuz-'):
457 return get_kernel_versions(self._instroot)
459 ts = rpm.TransactionSet(self._instroot)
462 for header in ts.dbMatch('provides', 'kernel'):
463 version = get_version(header)
467 name = header['name']
469 ret[name] = [version]
470 elif not version in ret[name]:
471 ret[name].append(version)
477 # Helpers for subclasses
479 def _do_bindmounts(self):
480 """Mount various system directories onto _instroot.
482 This method is called by mount(), but may also be used by subclasses
483 in order to re-mount the bindmounts after modifying the underlying
487 for b in self.__bindmounts:
490 def _undo_bindmounts(self):
491 """Unmount the bind-mounted system directories from _instroot.
493 This method is usually only called by unmount(), but may also be used
494 by subclasses in order to gain access to the filesystem obscured by
495 the bindmounts - e.g. in order to create device nodes on the image
499 self.__bindmounts.reverse()
500 for b in self.__bindmounts:
504 """Chroot into the install root.
506 This method may be used by subclasses when executing programs inside
507 the install root e.g.
509 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
512 os.chroot(self._instroot)
515 def _mkdtemp(self, prefix = "tmp-"):
516 """Create a temporary directory.
518 This method may be used by subclasses to create a temporary directory
519 for use in building the final image - e.g. a subclass might create
520 a temporary directory in order to bundle a set of files into a package.
522 The subclass may delete this directory if it wishes, but it will be
523 automatically deleted by cleanup().
525 The absolute path to the temporary directory is returned.
527 Note, this method should only be called after mount() has been called.
529 prefix -- a prefix which should be used when creating the directory;
533 self.__ensure_builddir()
534 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
536 def _mkstemp(self, prefix = "tmp-"):
537 """Create a temporary file.
539 This method may be used by subclasses to create a temporary file
540 for use in building the final image - e.g. a subclass might need
541 a temporary location to unpack a compressed file.
543 The subclass may delete this file if it wishes, but it will be
544 automatically deleted by cleanup().
546 A tuple containing a file descriptor (returned from os.open() and the
547 absolute path to the temporary directory is returned.
549 Note, this method should only be called after mount() has been called.
551 prefix -- a prefix which should be used when creating the file;
555 self.__ensure_builddir()
556 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
558 def _mktemp(self, prefix = "tmp-"):
559 """Create a temporary file.
561 This method simply calls _mkstemp() and closes the returned file
564 The absolute path to the temporary file is returned.
566 Note, this method should only be called after mount() has been called.
568 prefix -- a prefix which should be used when creating the file;
573 (f, path) = self._mkstemp(prefix)
579 # Actual implementation
581 def __ensure_builddir(self):
582 if not self.__builddir is None:
586 self.__builddir = tempfile.mkdtemp(dir = self.tmpdir,
587 prefix = "imgcreate-")
588 except OSError, (err, msg):
589 raise CreatorError("Failed create build directory in %s: %s" %
592 def get_cachedir(self, cachedir = None):
596 self.__ensure_builddir()
598 self.cachedir = cachedir
600 self.cachedir = self.__builddir + "/mic-cache"
601 fs.makedirs(self.cachedir)
604 def __sanity_check(self):
605 """Ensure that the config we've been given is sane."""
606 if not (kickstart.get_packages(self.ks) or
607 kickstart.get_groups(self.ks)):
608 raise CreatorError("No packages or groups specified")
610 kickstart.convert_method_to_repo(self.ks)
612 if not kickstart.get_repos(self.ks):
613 raise CreatorError("No repositories specified")
615 def __write_fstab(self):
616 fstab = open(self._instroot + "/etc/fstab", "w")
617 fstab.write(self._get_fstab())
620 def __create_minimal_dev(self):
621 """Create a minimal /dev so that we don't corrupt the host /dev"""
622 origumask = os.umask(0000)
623 devices = (('null', 1, 3, 0666),
624 ('urandom',1, 9, 0666),
625 ('random', 1, 8, 0666),
626 ('full', 1, 7, 0666),
627 ('ptmx', 5, 2, 0666),
629 ('zero', 1, 5, 0666))
631 links = (("/proc/self/fd", "/dev/fd"),
632 ("/proc/self/fd/0", "/dev/stdin"),
633 ("/proc/self/fd/1", "/dev/stdout"),
634 ("/proc/self/fd/2", "/dev/stderr"))
636 for (node, major, minor, perm) in devices:
637 if not os.path.exists(self._instroot + "/dev/" + node):
638 os.mknod(self._instroot + "/dev/" + node,
640 os.makedev(major,minor))
642 for (src, dest) in links:
643 if not os.path.exists(self._instroot + dest):
644 os.symlink(src, self._instroot + dest)
648 def mount(self, base_on = None, cachedir = None):
649 """Setup the target filesystem in preparation for an install.
651 This function sets up the filesystem which the ImageCreator will
652 install into and configure. The ImageCreator class merely creates an
653 install root directory, bind mounts some system directories (e.g. /dev)
654 and writes out /etc/fstab. Other subclasses may also e.g. create a
655 sparse file, format it and loopback mount it to the install root.
657 base_on -- a previous install on which to base this install; defaults
658 to None, causing a new image to be created
660 cachedir -- a directory in which to store the Yum cache; defaults to
661 None, causing a new cache to be created; by setting this
662 to another directory, the same cache can be reused across
666 self.__ensure_builddir()
668 # prevent popup dialog in Ubuntu(s)
669 misc.hide_loopdev_presentation()
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(
698 f, self._instroot, dest))
700 self._do_bindmounts()
702 self.__create_minimal_dev()
704 if os.path.exists(self._instroot + "/etc/mtab"):
705 os.unlink(self._instroot + "/etc/mtab")
706 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
710 # get size of available space in 'instroot' fs
711 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
714 """Unmounts the target filesystem.
716 The ImageCreator class detaches the system from the install root, but
717 other subclasses may also detach the loopback mounted filesystem image
718 from the install root.
722 mtab = self._instroot + "/etc/mtab"
723 if not os.path.islink(mtab):
724 os.unlink(self._instroot + "/etc/mtab")
726 if self.qemu_emulator:
727 os.unlink(self._instroot + self.qemu_emulator)
731 self._undo_bindmounts()
733 """ Clean up yum garbage """
735 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
736 if os.path.exists(instroot_pdir):
737 shutil.rmtree(instroot_pdir, ignore_errors = True)
738 yumlibdir = self._instroot + "/var/lib/yum"
739 if os.path.exists(yumlibdir):
740 shutil.rmtree(yumlibdir, ignore_errors = True)
744 self._unmount_instroot()
746 # reset settings of popup dialog in Ubuntu(s)
747 misc.unhide_loopdev_presentation()
750 """Unmounts the target filesystem and deletes temporary files.
752 This method calls unmount() and then deletes any temporary files and
753 directories that were created on the host system while building the
756 Note, make sure to call this method once finished with the creator
757 instance in order to ensure no stale files are left on the host e.g.:
759 creator = ImageCreator(ks, name)
766 if not self.__builddir:
771 shutil.rmtree(self.__builddir, ignore_errors = True)
772 self.__builddir = None
774 def __is_excluded_pkg(self, pkg):
775 if pkg in self._excluded_pkgs:
776 self._excluded_pkgs.remove(pkg)
779 for xpkg in self._excluded_pkgs:
780 if xpkg.endswith('*'):
781 if pkg.startswith(xpkg[:-1]):
783 elif xpkg.startswith('*'):
784 if pkg.endswith(xpkg[1:]):
789 def __select_packages(self, pkg_manager):
791 for pkg in self._required_pkgs:
792 e = pkg_manager.selectPackage(pkg)
794 if kickstart.ignore_missing(self.ks):
795 skipped_pkgs.append(pkg)
796 elif self.__is_excluded_pkg(pkg):
797 skipped_pkgs.append(pkg)
799 raise CreatorError("Failed to find package '%s' : %s" %
802 for pkg in skipped_pkgs:
803 msger.warning("Skipping missing package '%s'" % (pkg,))
805 def __select_groups(self, pkg_manager):
807 for group in self._required_groups:
808 e = pkg_manager.selectGroup(group.name, group.include)
810 if kickstart.ignore_missing(self.ks):
811 skipped_groups.append(group)
813 raise CreatorError("Failed to find group '%s' : %s" %
816 for group in skipped_groups:
817 msger.warning("Skipping missing group '%s'" % (group.name,))
819 def __deselect_packages(self, pkg_manager):
820 for pkg in self._excluded_pkgs:
821 pkg_manager.deselectPackage(pkg)
823 def __localinst_packages(self, pkg_manager):
824 for rpm_path in self._get_local_packages():
825 pkg_manager.installLocal(rpm_path)
827 def __preinstall_packages(self, pkg_manager):
831 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
832 for pkg in self._preinstall_pkgs:
833 pkg_manager.preInstall(pkg)
835 def __attachment_packages(self, pkg_manager):
839 self._attachment = []
840 for item in kickstart.get_attachment(self.ks):
841 if item.startswith('/'):
842 fpaths = os.path.join(self._instroot, item.lstrip('/'))
843 for fpath in glob.glob(fpaths):
844 self._attachment.append(fpath)
847 filelist = pkg_manager.getFilelist(item)
849 # found rpm in rootfs
850 for pfile in pkg_manager.getFilelist(item):
851 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
852 self._attachment.append(fpath)
855 # try to retrieve rpm file
856 (url, proxies) = pkg_manager.package_url(item)
858 msger.warning("Can't get url from repo for %s" % item)
860 fpath = os.path.join(self.cachedir, os.path.basename(url))
861 if not os.path.exists(fpath):
864 fpath = grabber.myurlgrab(url, fpath, proxies, None)
868 tmpdir = self._mkdtemp()
869 misc.extract_rpm(fpath, tmpdir)
870 for (root, dirs, files) in os.walk(tmpdir):
872 fpath = os.path.join(root, fname)
873 self._attachment.append(fpath)
875 def install(self, repo_urls = {}):
876 """Install packages into the install root.
878 This function installs the packages listed in the supplied kickstart
879 into the install root. By default, the packages are installed from the
880 repository URLs specified in the kickstart.
882 repo_urls -- a dict which maps a repository name to a repository URL;
883 if supplied, this causes any repository URLs specified in
884 the kickstart to be overridden.
888 # initialize pkg list to install
890 self.__sanity_check()
892 self._required_pkgs = \
893 kickstart.get_packages(self.ks, self._get_required_packages())
894 self._excluded_pkgs = \
895 kickstart.get_excluded(self.ks, self._get_excluded_packages())
896 self._required_groups = kickstart.get_groups(self.ks)
898 self._required_pkgs = None
899 self._excluded_pkgs = None
900 self._required_groups = None
902 pkg_manager = self.get_pkg_manager()
905 if hasattr(self, 'install_pkgs') and self.install_pkgs:
906 if 'debuginfo' in self.install_pkgs:
907 pkg_manager.install_debuginfo = True
909 for repo in kickstart.get_repos(self.ks, repo_urls):
910 (name, baseurl, mirrorlist, inc, exc,
911 proxy, proxy_username, proxy_password, debuginfo,
912 source, gpgkey, disable, ssl_verify, nocache,
913 cost, priority) = repo
915 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
916 proxy_username, proxy_password, inc, exc, ssl_verify,
917 nocache, cost, priority)
919 if kickstart.exclude_docs(self.ks):
920 rpm.addMacro("_excludedocs", "1")
921 rpm.addMacro("_dbpath", "/var/lib/rpm")
922 rpm.addMacro("__file_context_path", "%{nil}")
923 if kickstart.inst_langs(self.ks) != None:
924 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
927 self.__preinstall_packages(pkg_manager)
928 self.__select_packages(pkg_manager)
929 self.__select_groups(pkg_manager)
930 self.__deselect_packages(pkg_manager)
931 self.__localinst_packages(pkg_manager)
933 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
934 checksize = self._root_fs_avail
936 checksize -= BOOT_SAFEGUARD
938 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
939 pkg_manager.runInstall(checksize)
940 except CreatorError, e:
942 except KeyboardInterrupt:
945 self._pkgs_content = pkg_manager.getAllContent()
946 self._pkgs_license = pkg_manager.getPkgsLicense()
947 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
948 self.__attachment_packages(pkg_manager)
955 # do some clean up to avoid lvm info leakage. this sucks.
956 for subdir in ("cache", "backup", "archive"):
957 lvmdir = self._instroot + "/etc/lvm/" + subdir
959 for f in os.listdir(lvmdir):
960 os.unlink(lvmdir + "/" + f)
964 def postinstall(self):
965 self.copy_attachment()
967 def __run_post_scripts(self):
968 msger.info("Running scripts ...")
969 if os.path.exists(self._instroot + "/tmp"):
970 shutil.rmtree(self._instroot + "/tmp")
971 os.mkdir (self._instroot + "/tmp", 0755)
972 for s in kickstart.get_post_scripts(self.ks):
973 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
974 dir = self._instroot + "/tmp")
976 s.script = s.script.replace("\r", "")
977 os.write(fd, s.script)
981 env = self._get_post_scripts_env(s.inChroot)
984 env["INSTALL_ROOT"] = self._instroot
985 env["IMG_NAME"] = self._name
989 preexec = self._chroot
990 script = "/tmp/" + os.path.basename(path)
994 subprocess.call([s.interp, script],
995 preexec_fn = preexec,
999 except OSError, (err, msg):
1000 raise CreatorError("Failed to execute %%post script "
1001 "with '%s' : %s" % (s.interp, msg))
1005 def __save_repo_keys(self, repodata):
1009 gpgkeydir = "/etc/pki/rpm-gpg"
1010 fs.makedirs(self._instroot + gpgkeydir)
1011 for repo in repodata:
1013 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1014 shutil.copy(repo["repokey"], self._instroot + repokey)
1016 def configure(self, repodata = None):
1017 """Configure the system image according to the kickstart.
1019 This method applies the (e.g. keyboard or network) configuration
1020 specified in the kickstart and executes the kickstart %post scripts.
1022 If necessary, it also prepares the image to be bootable by e.g.
1023 creating an initrd and bootloader configuration.
1026 ksh = self.ks.handler
1028 msger.info('Applying configurations ...')
1030 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1031 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1032 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1033 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1034 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1035 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1036 kickstart.UserConfig(self._instroot).apply(ksh.user)
1037 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1038 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1039 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1040 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1041 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1042 self.__save_repo_keys(repodata)
1043 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1045 msger.warning("Failed to apply configuration to image")
1048 self._create_bootconfig()
1049 self.__run_post_scripts()
1051 def launch_shell(self, launch):
1052 """Launch a shell in the install root.
1054 This method is launches a bash shell chroot()ed in the install root;
1055 this can be useful for debugging.
1059 msger.info("Launching shell. Exit to continue.")
1060 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1062 def do_genchecksum(self, image_name):
1063 if not self._genchecksum:
1066 md5sum = misc.get_md5sum(image_name)
1067 with open(image_name + ".md5sum", "w") as f:
1068 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1069 self.outimage.append(image_name+".md5sum")
1071 def package(self, destdir = "."):
1072 """Prepares the created image for final delivery.
1074 In its simplest form, this method merely copies the install root to the
1075 supplied destination directory; other subclasses may choose to package
1076 the image by e.g. creating a bootable ISO containing the image and
1077 bootloader configuration.
1079 destdir -- the directory into which the final image should be moved;
1080 this defaults to the current directory.
1083 self._stage_final_image()
1085 if not os.path.exists(destdir):
1086 fs.makedirs(destdir)
1088 if self._recording_pkgs:
1089 self._save_recording_pkgs(destdir)
1091 # For image formats with two or multiple image files, it will be
1092 # better to put them under a directory
1093 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1094 destdir = os.path.join(destdir, "%s-%s" \
1095 % (self.name, self.image_format))
1096 msger.debug("creating destination dir: %s" % destdir)
1097 fs.makedirs(destdir)
1099 # Ensure all data is flushed to _outdir
1100 runner.quiet('sync')
1102 misc.check_space_pre_cp(self._outdir, destdir)
1103 for f in os.listdir(self._outdir):
1104 shutil.move(os.path.join(self._outdir, f),
1105 os.path.join(destdir, f))
1106 self.outimage.append(os.path.join(destdir, f))
1107 self.do_genchecksum(os.path.join(destdir, f))
1109 def print_outimage_info(self):
1110 msg = "The new image can be found here:\n"
1111 self.outimage.sort()
1112 for file in self.outimage:
1113 msg += ' %s\n' % os.path.abspath(file)
1117 def check_depend_tools(self):
1118 for tool in self._dep_checks:
1119 fs.find_binary_path(tool)
1121 def package_output(self, image_format, destdir = ".", package="none"):
1122 if not package or package == "none":
1125 destdir = os.path.abspath(os.path.expanduser(destdir))
1126 (pkg, comp) = os.path.splitext(package)
1128 comp=comp.lstrip(".")
1132 dst = "%s/%s-%s.tar.%s" %\
1133 (destdir, self.name, image_format, comp)
1135 dst = "%s/%s-%s.tar" %\
1136 (destdir, self.name, image_format)
1138 msger.info("creating %s" % dst)
1139 tar = tarfile.open(dst, "w:" + comp)
1141 for file in self.outimage:
1142 msger.info("adding %s to %s" % (file, dst))
1144 arcname=os.path.join("%s-%s" \
1145 % (self.name, image_format),
1146 os.path.basename(file)))
1147 if os.path.isdir(file):
1148 shutil.rmtree(file, ignore_errors = True)
1154 '''All the file in outimage has been packaged into tar.* file'''
1155 self.outimage = [dst]
1157 def release_output(self, config, destdir, release):
1158 """ Create release directory and files
1162 """ release path """
1163 return os.path.join(destdir, fn)
1165 outimages = self.outimage
1168 new_kspath = _rpath(self.name+'.ks')
1169 with open(config) as fr:
1170 with open(new_kspath, "w") as wf:
1171 # When building a release we want to make sure the .ks
1172 # file generates the same build even when --release not used.
1173 wf.write(fr.read().replace("@BUILD_ID@", release))
1174 outimages.append(new_kspath)
1176 # save log file, logfile is only available in creator attrs
1177 if hasattr(self, 'logfile') and not self.logfile:
1178 log_path = _rpath(self.name + ".log")
1179 # touch the log file, else outimages will filter it out
1180 with open(log_path, 'w') as wf:
1182 msger.set_logfile(log_path)
1183 outimages.append(_rpath(self.name + ".log"))
1185 # rename iso and usbimg
1186 for f in os.listdir(destdir):
1187 if f.endswith(".iso"):
1188 newf = f[:-4] + '.img'
1189 elif f.endswith(".usbimg"):
1190 newf = f[:-7] + '.img'
1193 os.rename(_rpath(f), _rpath(newf))
1194 outimages.append(_rpath(newf))
1197 with open(_rpath("MD5SUMS"), "w") as wf:
1198 for f in os.listdir(destdir):
1202 if os.path.isdir(os.path.join(destdir, f)):
1205 md5sum = misc.get_md5sum(_rpath(f))
1206 # There needs to be two spaces between the sum and
1207 # filepath to match the syntax with md5sum.
1208 # This way also md5sum -c MD5SUMS can be used by users
1209 wf.write("%s *%s\n" % (md5sum, f))
1211 outimages.append("%s/MD5SUMS" % destdir)
1213 # Filter out the nonexist file
1214 for fp in outimages[:]:
1215 if not os.path.exists("%s" % fp):
1216 outimages.remove(fp)
1218 def copy_kernel(self):
1219 """ Copy kernel files to the outimage directory.
1220 NOTE: This needs to be called before unmounting the instroot.
1223 if not self._need_copy_kernel:
1226 if not os.path.exists(self.destdir):
1227 os.makedirs(self.destdir)
1229 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1230 kernelfilename = "%s/%s-%s" % (self.destdir,
1232 os.path.basename(kernel))
1233 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1235 shutil.copy(kernel, kernelfilename)
1236 self.outimage.append(kernelfilename)
1238 def copy_attachment(self):
1239 """ Subclass implement it to handle attachment files
1240 NOTE: This needs to be called before unmounting the instroot.
1244 def get_pkg_manager(self):
1245 return self.pkgmgr(target_arch = self.target_arch,
1246 instroot = self._instroot,
1247 cachedir = self.cachedir)