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 for repo in kickstart.get_repos(self.ks, repo_urls):
906 (name, baseurl, mirrorlist, inc, exc,
907 proxy, proxy_username, proxy_password, debuginfo,
908 source, gpgkey, disable, ssl_verify, nocache,
909 cost, priority) = repo
911 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
912 proxy_username, proxy_password, inc, exc, ssl_verify,
913 nocache, cost, priority)
915 if kickstart.exclude_docs(self.ks):
916 rpm.addMacro("_excludedocs", "1")
917 rpm.addMacro("_dbpath", "/var/lib/rpm")
918 rpm.addMacro("__file_context_path", "%{nil}")
919 if kickstart.inst_langs(self.ks) != None:
920 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
924 self.__preinstall_packages(pkg_manager)
925 self.__select_packages(pkg_manager)
926 self.__select_groups(pkg_manager)
927 self.__deselect_packages(pkg_manager)
928 self.__localinst_packages(pkg_manager)
930 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
931 checksize = self._root_fs_avail
933 checksize -= BOOT_SAFEGUARD
935 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
936 pkg_manager.runInstall(checksize)
937 except CreatorError, e:
940 self._pkgs_content = pkg_manager.getAllContent()
941 self._pkgs_license = pkg_manager.getPkgsLicense()
942 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
943 self.__attachment_packages(pkg_manager)
950 # do some clean up to avoid lvm info leakage. this sucks.
951 for subdir in ("cache", "backup", "archive"):
952 lvmdir = self._instroot + "/etc/lvm/" + subdir
954 for f in os.listdir(lvmdir):
955 os.unlink(lvmdir + "/" + f)
959 def postinstall(self):
960 self.copy_attachment()
962 def __run_post_scripts(self):
963 msger.info("Running scripts ...")
964 if os.path.exists(self._instroot + "/tmp"):
965 shutil.rmtree(self._instroot + "/tmp")
966 os.mkdir (self._instroot + "/tmp", 0755)
967 for s in kickstart.get_post_scripts(self.ks):
968 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
969 dir = self._instroot + "/tmp")
971 s.script = s.script.replace("\r", "")
972 os.write(fd, s.script)
976 env = self._get_post_scripts_env(s.inChroot)
979 env["INSTALL_ROOT"] = self._instroot
980 env["IMG_NAME"] = self._name
984 preexec = self._chroot
985 script = "/tmp/" + os.path.basename(path)
989 subprocess.call([s.interp, script],
990 preexec_fn = preexec,
994 except OSError, (err, msg):
995 raise CreatorError("Failed to execute %%post script "
996 "with '%s' : %s" % (s.interp, msg))
1000 def __save_repo_keys(self, repodata):
1004 gpgkeydir = "/etc/pki/rpm-gpg"
1005 fs.makedirs(self._instroot + gpgkeydir)
1006 for repo in repodata:
1008 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1009 shutil.copy(repo["repokey"], self._instroot + repokey)
1011 def configure(self, repodata = None):
1012 """Configure the system image according to the kickstart.
1014 This method applies the (e.g. keyboard or network) configuration
1015 specified in the kickstart and executes the kickstart %post scripts.
1017 If necessary, it also prepares the image to be bootable by e.g.
1018 creating an initrd and bootloader configuration.
1021 ksh = self.ks.handler
1023 msger.info('Applying configurations ...')
1025 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1026 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1027 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1028 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1029 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1030 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1031 kickstart.UserConfig(self._instroot).apply(ksh.user)
1032 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1033 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1034 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1035 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1036 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1037 self.__save_repo_keys(repodata)
1038 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1040 msger.warning("Failed to apply configuration to image")
1043 self._create_bootconfig()
1044 self.__run_post_scripts()
1046 def launch_shell(self, launch):
1047 """Launch a shell in the install root.
1049 This method is launches a bash shell chroot()ed in the install root;
1050 this can be useful for debugging.
1054 msger.info("Launching shell. Exit to continue.")
1055 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1057 def do_genchecksum(self, image_name):
1058 if not self._genchecksum:
1061 md5sum = misc.get_md5sum(image_name)
1062 with open(image_name + ".md5sum", "w") as f:
1063 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1064 self.outimage.append(image_name+".md5sum")
1066 def package(self, destdir = "."):
1067 """Prepares the created image for final delivery.
1069 In its simplest form, this method merely copies the install root to the
1070 supplied destination directory; other subclasses may choose to package
1071 the image by e.g. creating a bootable ISO containing the image and
1072 bootloader configuration.
1074 destdir -- the directory into which the final image should be moved;
1075 this defaults to the current directory.
1078 self._stage_final_image()
1080 if not os.path.exists(destdir):
1081 fs.makedirs(destdir)
1083 if self._recording_pkgs:
1084 self._save_recording_pkgs(destdir)
1086 # For image formats with two or multiple image files, it will be
1087 # better to put them under a directory
1088 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1089 destdir = os.path.join(destdir, "%s-%s" \
1090 % (self.name, self.image_format))
1091 msger.debug("creating destination dir: %s" % destdir)
1092 fs.makedirs(destdir)
1094 # Ensure all data is flushed to _outdir
1095 runner.quiet('sync')
1097 misc.check_space_pre_cp(self._outdir, destdir)
1098 for f in os.listdir(self._outdir):
1099 shutil.move(os.path.join(self._outdir, f),
1100 os.path.join(destdir, f))
1101 self.outimage.append(os.path.join(destdir, f))
1102 self.do_genchecksum(os.path.join(destdir, f))
1104 def print_outimage_info(self):
1105 msg = "The new image can be found here:\n"
1106 self.outimage.sort()
1107 for file in self.outimage:
1108 msg += ' %s\n' % os.path.abspath(file)
1112 def check_depend_tools(self):
1113 for tool in self._dep_checks:
1114 fs.find_binary_path(tool)
1116 def package_output(self, image_format, destdir = ".", package="none"):
1117 if not package or package == "none":
1120 destdir = os.path.abspath(os.path.expanduser(destdir))
1121 (pkg, comp) = os.path.splitext(package)
1123 comp=comp.lstrip(".")
1127 dst = "%s/%s-%s.tar.%s" %\
1128 (destdir, self.name, image_format, comp)
1130 dst = "%s/%s-%s.tar" %\
1131 (destdir, self.name, image_format)
1133 msger.info("creating %s" % dst)
1134 tar = tarfile.open(dst, "w:" + comp)
1136 for file in self.outimage:
1137 msger.info("adding %s to %s" % (file, dst))
1139 arcname=os.path.join("%s-%s" \
1140 % (self.name, image_format),
1141 os.path.basename(file)))
1142 if os.path.isdir(file):
1143 shutil.rmtree(file, ignore_errors = True)
1149 '''All the file in outimage has been packaged into tar.* file'''
1150 self.outimage = [dst]
1152 def release_output(self, config, destdir, release):
1153 """ Create release directory and files
1157 """ release path """
1158 return os.path.join(destdir, fn)
1160 outimages = self.outimage
1163 new_kspath = _rpath(self.name+'.ks')
1164 with open(config) as fr:
1165 with open(new_kspath, "w") as wf:
1166 # When building a release we want to make sure the .ks
1167 # file generates the same build even when --release not used.
1168 wf.write(fr.read().replace("@BUILD_ID@", release))
1169 outimages.append(new_kspath)
1171 # save log file, logfile is only available in creator attrs
1172 if hasattr(self, 'logfile') and not self.logfile:
1173 log_path = _rpath(self.name + ".log")
1174 # touch the log file, else outimages will filter it out
1175 with open(log_path, 'w') as wf:
1177 msger.set_logfile(log_path)
1178 outimages.append(_rpath(self.name + ".log"))
1180 # rename iso and usbimg
1181 for f in os.listdir(destdir):
1182 if f.endswith(".iso"):
1183 newf = f[:-4] + '.img'
1184 elif f.endswith(".usbimg"):
1185 newf = f[:-7] + '.img'
1188 os.rename(_rpath(f), _rpath(newf))
1189 outimages.append(_rpath(newf))
1192 with open(_rpath("MD5SUMS"), "w") as wf:
1193 for f in os.listdir(destdir):
1197 if os.path.isdir(os.path.join(destdir, f)):
1200 md5sum = misc.get_md5sum(_rpath(f))
1201 # There needs to be two spaces between the sum and
1202 # filepath to match the syntax with md5sum.
1203 # This way also md5sum -c MD5SUMS can be used by users
1204 wf.write("%s *%s\n" % (md5sum, f))
1206 outimages.append("%s/MD5SUMS" % destdir)
1208 # Filter out the nonexist file
1209 for fp in outimages[:]:
1210 if not os.path.exists("%s" % fp):
1211 outimages.remove(fp)
1213 def copy_kernel(self):
1214 """ Copy kernel files to the outimage directory.
1215 NOTE: This needs to be called before unmounting the instroot.
1218 if not self._need_copy_kernel:
1221 if not os.path.exists(self.destdir):
1222 os.makedirs(self.destdir)
1224 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1225 kernelfilename = "%s/%s-%s" % (self.destdir,
1227 os.path.basename(kernel))
1228 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1230 shutil.copy(kernel, kernelfilename)
1231 self.outimage.append(kernelfilename)
1233 def copy_attachment(self):
1234 """ Subclass implement it to handle attachment files
1235 NOTE: This needs to be called before unmounting the instroot.
1239 def get_pkg_manager(self):
1240 return self.pkgmgr(target_arch = self.target_arch,
1241 instroot = self._instroot,
1242 cachedir = self.cachedir)