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
36 from mic.chroot import kill_proc_inchroot
38 class BaseImageCreator(object):
39 """Installs a system to a chroot directory.
41 ImageCreator is the simplest creator class available; it will install and
42 configure a system image according to the supplied kickstart file.
46 import mic.imgcreate as imgcreate
47 ks = imgcreate.read_kickstart("foo.ks")
48 imgcreate.ImageCreator(ks, "foo").create()
55 def __init__(self, createopts = None, pkgmgr = None):
56 """Initialize an ImageCreator instance.
58 ks -- a pykickstart.KickstartParser instance; this instance will be
59 used to drive the install by e.g. providing the list of packages
60 to be installed, the system configuration and %post scripts
62 name -- a name for the image; used for e.g. image filenames or
69 self.__builddir = None
70 self.__bindmounts = []
74 self.tmpdir = "/var/tmp/mic"
75 self.cachedir = "/var/tmp/mic/cache"
76 self.workdir = "/var/tmp/mic/build"
78 self.installerfw_prefix = "INSTALLERFW_"
79 self.target_arch = "noarch"
80 self._local_pkgs_path = None
84 # If the kernel is save to the destdir when copy_kernel cmd is called.
85 self._need_copy_kernel = False
86 # setup tmpfs tmpdir when enabletmpfs is True
87 self.enabletmpfs = False
90 # Mapping table for variables that have different names.
91 optmap = {"pkgmgr" : "pkgmgr_name",
93 "arch" : "target_arch",
94 "local_pkgs_path" : "_local_pkgs_path",
95 "copy_kernel" : "_need_copy_kernel",
98 # update setting from createopts
99 for key in createopts.keys():
104 setattr(self, option, createopts[key])
106 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
108 if 'release' in createopts and createopts['release']:
109 self.name = createopts['release'] + '_' + self.name
112 if '@NAME@' in self.pack_to:
113 self.pack_to = self.pack_to.replace('@NAME@', self.name)
114 (tar, ext) = os.path.splitext(self.pack_to)
115 if ext in (".gz", ".bz2") and tar.endswith(".tar"):
117 if ext not in misc.pack_formats:
118 self.pack_to += ".tar"
120 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
122 # Output image file names
125 # A flag to generate checksum
126 self._genchecksum = False
128 self._alt_initrd_name = None
130 self._recording_pkgs = []
132 # available size in root fs, init to 0
133 self._root_fs_avail = 0
135 # Name of the disk image file that is created.
136 self._img_name = None
138 self.image_format = None
140 # Save qemu emulator file name in order to clean up it finally
141 self.qemu_emulator = None
143 # No ks provided when called by convertor, so skip the dependency check
145 # If we have btrfs partition we need to check necessary tools
146 for part in self.ks.handler.partition.partitions:
147 if part.fstype and part.fstype == "btrfs":
148 self._dep_checks.append("mkfs.btrfs")
151 if self.target_arch and self.target_arch.startswith("arm"):
152 for dep in self._dep_checks:
153 if dep == "extlinux":
154 self._dep_checks.remove(dep)
156 if not os.path.exists("/usr/bin/qemu-arm") or \
157 not misc.is_statically_linked("/usr/bin/qemu-arm"):
158 self._dep_checks.append("qemu-arm-static")
160 if os.path.exists("/proc/sys/vm/vdso_enabled"):
161 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
162 vdso_value = vdso_fh.read().strip()
164 if (int)(vdso_value) == 1:
165 msger.warning("vdso is enabled on your host, which might "
166 "cause problems with arm emulations.\n"
167 "\tYou can disable vdso with following command before "
168 "starting image build:\n"
169 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
171 # make sure the specified tmpdir and cachedir exist
172 if not os.path.exists(self.tmpdir):
173 os.makedirs(self.tmpdir)
174 if not os.path.exists(self.cachedir):
175 os.makedirs(self.cachedir)
181 def __get_instroot(self):
182 if self.__builddir is None:
183 raise CreatorError("_instroot is not valid before calling mount()")
184 return self.__builddir + "/install_root"
185 _instroot = property(__get_instroot)
186 """The location of the install root directory.
188 This is the directory into which the system is installed. Subclasses may
189 mount a filesystem image here or copy files to/from here.
191 Note, this directory does not exist before ImageCreator.mount() is called.
193 Note also, this is a read-only attribute.
197 def __get_outdir(self):
198 if self.__builddir is None:
199 raise CreatorError("_outdir is not valid before calling mount()")
200 return self.__builddir + "/out"
201 _outdir = property(__get_outdir)
202 """The staging location for the final image.
204 This is where subclasses should stage any files that are part of the final
205 image. ImageCreator.package() will copy any files found here into the
206 requested destination directory.
208 Note, this directory does not exist before ImageCreator.mount() is called.
210 Note also, this is a read-only attribute.
216 # Hooks for subclasses
218 def _mount_instroot(self, base_on = None):
219 """Mount or prepare the install root directory.
221 This is the hook where subclasses may prepare the install root by e.g.
222 mounting creating and loopback mounting a filesystem image to
225 There is no default implementation.
227 base_on -- this is the value passed to mount() and can be interpreted
228 as the subclass wishes; it might e.g. be the location of
229 a previously created ISO containing a system image.
234 def _unmount_instroot(self):
235 """Undo anything performed in _mount_instroot().
237 This is the hook where subclasses must undo anything which was done
238 in _mount_instroot(). For example, if a filesystem image was mounted
239 onto _instroot, it should be unmounted here.
241 There is no default implementation.
246 def _create_bootconfig(self):
247 """Configure the image so that it's bootable.
249 This is the hook where subclasses may prepare the image for booting by
250 e.g. creating an initramfs and bootloader configuration.
252 This hook is called while the install root is still mounted, after the
253 packages have been installed and the kickstart configuration has been
254 applied, but before the %post scripts have been executed.
256 There is no default implementation.
261 def _stage_final_image(self):
262 """Stage the final system image in _outdir.
264 This is the hook where subclasses should place the image in _outdir
265 so that package() can copy it to the requested destination directory.
267 By default, this moves the install root into _outdir.
270 shutil.move(self._instroot, self._outdir + "/" + self.name)
272 def get_installed_packages(self):
273 return self._pkgs_content.keys()
275 def _save_recording_pkgs(self, destdir):
276 """Save the list or content of installed packages to file.
278 pkgs = self._pkgs_content.keys()
279 pkgs.sort() # inplace op
281 if not os.path.exists(destdir):
285 if 'vcs' in self._recording_pkgs:
286 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
287 content = '\n'.join(sorted(vcslst))
288 elif 'name' in self._recording_pkgs:
289 content = '\n'.join(pkgs)
291 namefile = os.path.join(destdir, self.name + '.packages')
292 f = open(namefile, "w")
295 self.outimage.append(namefile);
297 # if 'content', save more details
298 if 'content' in self._recording_pkgs:
299 contfile = os.path.join(destdir, self.name + '.files')
300 f = open(contfile, "w")
305 pkgcont = self._pkgs_content[pkg]
307 content += '\n '.join(pkgcont)
313 self.outimage.append(contfile)
315 if 'license' in self._recording_pkgs:
316 licensefile = os.path.join(destdir, self.name + '.license')
317 f = open(licensefile, "w")
319 f.write('Summary:\n')
320 for license in reversed(sorted(self._pkgs_license, key=\
321 lambda license: len(self._pkgs_license[license]))):
322 f.write(" - %s: %s\n" \
323 % (license, len(self._pkgs_license[license])))
325 f.write('\nDetails:\n')
326 for license in reversed(sorted(self._pkgs_license, key=\
327 lambda license: len(self._pkgs_license[license]))):
328 f.write(" - %s:\n" % (license))
329 for pkg in sorted(self._pkgs_license[license]):
330 f.write(" - %s\n" % (pkg))
334 self.outimage.append(licensefile)
336 def _get_required_packages(self):
337 """Return a list of required packages.
339 This is the hook where subclasses may specify a set of packages which
340 it requires to be installed.
342 This returns an empty list by default.
344 Note, subclasses should usually chain up to the base class
345 implementation of this hook.
350 def _get_excluded_packages(self):
351 """Return a list of excluded packages.
353 This is the hook where subclasses may specify a set of packages which
354 it requires _not_ to be installed.
356 This returns an empty list by default.
358 Note, subclasses should usually chain up to the base class
359 implementation of this hook.
364 def _get_local_packages(self):
365 """Return a list of rpm path to be local installed.
367 This is the hook where subclasses may specify a set of rpms which
368 it requires to be installed locally.
370 This returns an empty list by default.
372 Note, subclasses should usually chain up to the base class
373 implementation of this hook.
376 if self._local_pkgs_path:
377 if os.path.isdir(self._local_pkgs_path):
379 os.path.join(self._local_pkgs_path, '*.rpm'))
380 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
381 return [self._local_pkgs_path]
385 def _get_fstab(self):
386 """Return the desired contents of /etc/fstab.
388 This is the hook where subclasses may specify the contents of
389 /etc/fstab by returning a string containing the desired contents.
391 A sensible default implementation is provided.
394 s = "/dev/root / %s %s 0 0\n" \
396 "defaults,noatime" if not self._fsopts else self._fsopts)
397 s += self._get_fstab_special()
400 def _get_fstab_special(self):
401 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
402 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
403 s += "proc /proc proc defaults 0 0\n"
404 s += "sysfs /sys sysfs defaults 0 0\n"
407 def _set_part_env(self, pnum, prop, value):
408 """ This is a helper function which generates an environment variable
409 for a property "prop" with value "value" of a partition number "pnum".
411 The naming convention is:
412 * Variables start with INSTALLERFW_PART
413 * Then goes the partition number, the order is the same as
414 specified in the KS file
415 * Then goes the property name
423 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
424 return { name : value }
426 def _get_post_scripts_env(self, in_chroot):
427 """Return an environment dict for %post scripts.
429 This is the hook where subclasses may specify some environment
430 variables for %post scripts by return a dict containing the desired
433 in_chroot -- whether this %post script is to be executed chroot()ed
440 for p in kickstart.get_partitions(self.ks):
441 env.update(self._set_part_env(pnum, "SIZE", p.size))
442 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
443 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
444 env.update(self._set_part_env(pnum, "LABEL", p.label))
445 env.update(self._set_part_env(pnum, "FSOPTS",
446 "defaults,noatime" if not p.fsopts
448 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
449 env.update(self._set_part_env(pnum, "ALIGN", p.align))
450 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
451 env.update(self._set_part_env(pnum, "UUID", p.uuid))
452 env.update(self._set_part_env(pnum, "DEVNODE",
453 "/dev/%s%d" % (p.disk, pnum + 1)))
454 env.update(self._set_part_env(pnum, "DISK_DEVNODE",
459 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
461 # Partition table format
462 ptable_format = self.ks.handler.bootloader.ptable
463 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
465 # The kerned boot parameters
466 kernel_opts = self.ks.handler.bootloader.appendLine
467 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
469 # Name of the distribution
470 env[self.installerfw_prefix + "DISTRO_NAME"] = self.distro_name
472 # Name of the image creation tool
473 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
475 # The real current location of the mounted file-systems
479 mount_prefix = self._instroot
480 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
482 # These are historical variables which lack the common name prefix
484 env["INSTALL_ROOT"] = self._instroot
485 env["IMG_NAME"] = self._name
489 def __get_imgname(self):
491 _name = property(__get_imgname)
492 """The name of the image file.
496 def _get_kernel_versions(self):
497 """Return a dict detailing the available kernel types/versions.
499 This is the hook where subclasses may override what kernel types and
500 versions should be available for e.g. creating the booloader
503 A dict should be returned mapping the available kernel types to a list
504 of the available versions for those kernels.
506 The default implementation uses rpm to iterate over everything
507 providing 'kernel', finds /boot/vmlinuz-* and returns the version
508 obtained from the vmlinuz filename. (This can differ from the kernel
509 RPM's n-v-r in the case of e.g. xen)
512 def get_kernel_versions(instroot):
515 files = glob.glob(instroot + "/boot/vmlinuz-*")
517 version = os.path.basename(file)[8:]
520 versions.add(version)
521 ret["kernel"] = list(versions)
524 def get_version(header):
526 for f in header['filenames']:
527 if f.startswith('/boot/vmlinuz-'):
532 return get_kernel_versions(self._instroot)
534 ts = rpm.TransactionSet(self._instroot)
537 for header in ts.dbMatch('provides', 'kernel'):
538 version = get_version(header)
542 name = header['name']
544 ret[name] = [version]
545 elif not version in ret[name]:
546 ret[name].append(version)
552 # Helpers for subclasses
554 def _do_bindmounts(self):
555 """Mount various system directories onto _instroot.
557 This method is called by mount(), but may also be used by subclasses
558 in order to re-mount the bindmounts after modifying the underlying
562 for b in self.__bindmounts:
565 def _undo_bindmounts(self):
566 """Unmount the bind-mounted system directories from _instroot.
568 This method is usually only called by unmount(), but may also be used
569 by subclasses in order to gain access to the filesystem obscured by
570 the bindmounts - e.g. in order to create device nodes on the image
574 self.__bindmounts.reverse()
575 for b in self.__bindmounts:
579 """Chroot into the install root.
581 This method may be used by subclasses when executing programs inside
582 the install root e.g.
584 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
587 os.chroot(self._instroot)
590 def _mkdtemp(self, prefix = "tmp-"):
591 """Create a temporary directory.
593 This method may be used by subclasses to create a temporary directory
594 for use in building the final image - e.g. a subclass might create
595 a temporary directory in order to bundle a set of files into a package.
597 The subclass may delete this directory if it wishes, but it will be
598 automatically deleted by cleanup().
600 The absolute path to the temporary directory is returned.
602 Note, this method should only be called after mount() has been called.
604 prefix -- a prefix which should be used when creating the directory;
608 self.__ensure_builddir()
609 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
611 def _mkstemp(self, prefix = "tmp-"):
612 """Create a temporary file.
614 This method may be used by subclasses to create a temporary file
615 for use in building the final image - e.g. a subclass might need
616 a temporary location to unpack a compressed file.
618 The subclass may delete this file if it wishes, but it will be
619 automatically deleted by cleanup().
621 A tuple containing a file descriptor (returned from os.open() and the
622 absolute path to the temporary directory is returned.
624 Note, this method should only be called after mount() has been called.
626 prefix -- a prefix which should be used when creating the file;
630 self.__ensure_builddir()
631 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
633 def _mktemp(self, prefix = "tmp-"):
634 """Create a temporary file.
636 This method simply calls _mkstemp() and closes the returned file
639 The absolute path to the temporary file is returned.
641 Note, this method should only be called after mount() has been called.
643 prefix -- a prefix which should be used when creating the file;
648 (f, path) = self._mkstemp(prefix)
654 # Actual implementation
656 def __ensure_builddir(self):
657 if not self.__builddir is None:
661 self.workdir = os.path.join(self.tmpdir, "build")
662 if not os.path.exists(self.workdir):
663 os.makedirs(self.workdir)
664 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
665 prefix = "imgcreate-")
666 except OSError, (err, msg):
667 raise CreatorError("Failed create build directory in %s: %s" %
670 def get_cachedir(self, cachedir = None):
674 self.__ensure_builddir()
676 self.cachedir = cachedir
678 self.cachedir = self.__builddir + "/mic-cache"
679 fs.makedirs(self.cachedir)
682 def __sanity_check(self):
683 """Ensure that the config we've been given is sane."""
684 if not (kickstart.get_packages(self.ks) or
685 kickstart.get_groups(self.ks)):
686 raise CreatorError("No packages or groups specified")
688 kickstart.convert_method_to_repo(self.ks)
690 if not kickstart.get_repos(self.ks):
691 raise CreatorError("No repositories specified")
693 def __write_fstab(self):
694 if kickstart.use_installerfw(self.ks, "fstab"):
695 # The fstab file will be generated by installer framework scripts
698 fstab_contents = self._get_fstab()
700 fstab = open(self._instroot + "/etc/fstab", "w")
701 fstab.write(fstab_contents)
704 def __create_minimal_dev(self):
705 """Create a minimal /dev so that we don't corrupt the host /dev"""
706 origumask = os.umask(0000)
707 devices = (('null', 1, 3, 0666),
708 ('urandom',1, 9, 0666),
709 ('random', 1, 8, 0666),
710 ('full', 1, 7, 0666),
711 ('ptmx', 5, 2, 0666),
713 ('zero', 1, 5, 0666))
715 links = (("/proc/self/fd", "/dev/fd"),
716 ("/proc/self/fd/0", "/dev/stdin"),
717 ("/proc/self/fd/1", "/dev/stdout"),
718 ("/proc/self/fd/2", "/dev/stderr"))
720 for (node, major, minor, perm) in devices:
721 if not os.path.exists(self._instroot + "/dev/" + node):
722 os.mknod(self._instroot + "/dev/" + node,
724 os.makedev(major,minor))
726 for (src, dest) in links:
727 if not os.path.exists(self._instroot + dest):
728 os.symlink(src, self._instroot + dest)
732 def __setup_tmpdir(self):
733 if not self.enabletmpfs:
736 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
738 def __clean_tmpdir(self):
739 if not self.enabletmpfs:
742 runner.show('umount -l %s' % self.workdir)
744 def mount(self, base_on = None, cachedir = None):
745 """Setup the target filesystem in preparation for an install.
747 This function sets up the filesystem which the ImageCreator will
748 install into and configure. The ImageCreator class merely creates an
749 install root directory, bind mounts some system directories (e.g. /dev)
750 and writes out /etc/fstab. Other subclasses may also e.g. create a
751 sparse file, format it and loopback mount it to the install root.
753 base_on -- a previous install on which to base this install; defaults
754 to None, causing a new image to be created
756 cachedir -- a directory in which to store the Yum cache; defaults to
757 None, causing a new cache to be created; by setting this
758 to another directory, the same cache can be reused across
762 self.__setup_tmpdir()
763 self.__ensure_builddir()
765 # prevent popup dialog in Ubuntu(s)
766 misc.hide_loopdev_presentation()
768 fs.makedirs(self._instroot)
769 fs.makedirs(self._outdir)
771 self._mount_instroot(base_on)
773 for d in ("/dev/pts",
780 fs.makedirs(self._instroot + d)
782 if self.target_arch and self.target_arch.startswith("arm") or \
783 self.target_arch == "aarch64":
784 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
787 self.get_cachedir(cachedir)
789 # bind mount system directories into _instroot
790 for (f, dest) in [("/sys", None),
792 ("/proc/sys/fs/binfmt_misc", None),
794 self.__bindmounts.append(
796 f, self._instroot, dest))
798 self._do_bindmounts()
800 self.__create_minimal_dev()
802 if os.path.exists(self._instroot + "/etc/mtab"):
803 os.unlink(self._instroot + "/etc/mtab")
804 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
808 # get size of available space in 'instroot' fs
809 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
812 """Unmounts the target filesystem.
814 The ImageCreator class detaches the system from the install root, but
815 other subclasses may also detach the loopback mounted filesystem image
816 from the install root.
820 mtab = self._instroot + "/etc/mtab"
821 if not os.path.islink(mtab):
822 os.unlink(self._instroot + "/etc/mtab")
824 if self.qemu_emulator:
825 os.unlink(self._instroot + self.qemu_emulator)
829 self._undo_bindmounts()
831 """ Clean up yum garbage """
833 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
834 if os.path.exists(instroot_pdir):
835 shutil.rmtree(instroot_pdir, ignore_errors = True)
836 yumlibdir = self._instroot + "/var/lib/yum"
837 if os.path.exists(yumlibdir):
838 shutil.rmtree(yumlibdir, ignore_errors = True)
842 self._unmount_instroot()
844 # reset settings of popup dialog in Ubuntu(s)
845 misc.unhide_loopdev_presentation()
849 """Unmounts the target filesystem and deletes temporary files.
851 This method calls unmount() and then deletes any temporary files and
852 directories that were created on the host system while building the
855 Note, make sure to call this method once finished with the creator
856 instance in order to ensure no stale files are left on the host e.g.:
858 creator = ImageCreator(ks, name)
865 if not self.__builddir:
868 kill_proc_inchroot(self._instroot)
872 shutil.rmtree(self.__builddir, ignore_errors = True)
873 self.__builddir = None
875 self.__clean_tmpdir()
877 def __is_excluded_pkg(self, pkg):
878 if pkg in self._excluded_pkgs:
879 self._excluded_pkgs.remove(pkg)
882 for xpkg in self._excluded_pkgs:
883 if xpkg.endswith('*'):
884 if pkg.startswith(xpkg[:-1]):
886 elif xpkg.startswith('*'):
887 if pkg.endswith(xpkg[1:]):
892 def __select_packages(self, pkg_manager):
894 for pkg in self._required_pkgs:
895 e = pkg_manager.selectPackage(pkg)
897 if kickstart.ignore_missing(self.ks):
898 skipped_pkgs.append(pkg)
899 elif self.__is_excluded_pkg(pkg):
900 skipped_pkgs.append(pkg)
902 raise CreatorError("Failed to find package '%s' : %s" %
905 for pkg in skipped_pkgs:
906 msger.warning("Skipping missing package '%s'" % (pkg,))
908 def __select_groups(self, pkg_manager):
910 for group in self._required_groups:
911 e = pkg_manager.selectGroup(group.name, group.include)
913 if kickstart.ignore_missing(self.ks):
914 skipped_groups.append(group)
916 raise CreatorError("Failed to find group '%s' : %s" %
919 for group in skipped_groups:
920 msger.warning("Skipping missing group '%s'" % (group.name,))
922 def __deselect_packages(self, pkg_manager):
923 for pkg in self._excluded_pkgs:
924 pkg_manager.deselectPackage(pkg)
926 def __localinst_packages(self, pkg_manager):
927 for rpm_path in self._get_local_packages():
928 pkg_manager.installLocal(rpm_path)
930 def __preinstall_packages(self, pkg_manager):
934 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
935 for pkg in self._preinstall_pkgs:
936 pkg_manager.preInstall(pkg)
938 def __check_packages(self, pkg_manager):
939 for pkg in self.check_pkgs:
940 pkg_manager.checkPackage(pkg)
942 def __attachment_packages(self, pkg_manager):
946 self._attachment = []
947 for item in kickstart.get_attachment(self.ks):
948 if item.startswith('/'):
949 fpaths = os.path.join(self._instroot, item.lstrip('/'))
950 for fpath in glob.glob(fpaths):
951 self._attachment.append(fpath)
954 filelist = pkg_manager.getFilelist(item)
956 # found rpm in rootfs
957 for pfile in pkg_manager.getFilelist(item):
958 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
959 self._attachment.append(fpath)
962 # try to retrieve rpm file
963 (url, proxies) = pkg_manager.package_url(item)
965 msger.warning("Can't get url from repo for %s" % item)
967 fpath = os.path.join(self.cachedir, os.path.basename(url))
968 if not os.path.exists(fpath):
971 fpath = grabber.myurlgrab(url, fpath, proxies, None)
975 tmpdir = self._mkdtemp()
976 misc.extract_rpm(fpath, tmpdir)
977 for (root, dirs, files) in os.walk(tmpdir):
979 fpath = os.path.join(root, fname)
980 self._attachment.append(fpath)
982 def install(self, repo_urls=None):
983 """Install packages into the install root.
985 This function installs the packages listed in the supplied kickstart
986 into the install root. By default, the packages are installed from the
987 repository URLs specified in the kickstart.
989 repo_urls -- a dict which maps a repository name to a repository URL;
990 if supplied, this causes any repository URLs specified in
991 the kickstart to be overridden.
995 # initialize pkg list to install
997 self.__sanity_check()
999 self._required_pkgs = \
1000 kickstart.get_packages(self.ks, self._get_required_packages())
1001 self._excluded_pkgs = \
1002 kickstart.get_excluded(self.ks, self._get_excluded_packages())
1003 self._required_groups = kickstart.get_groups(self.ks)
1005 self._required_pkgs = None
1006 self._excluded_pkgs = None
1007 self._required_groups = None
1009 pkg_manager = self.get_pkg_manager()
1012 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1013 if 'debuginfo' in self.install_pkgs:
1014 pkg_manager.install_debuginfo = True
1016 for repo in kickstart.get_repos(self.ks, repo_urls):
1017 (name, baseurl, mirrorlist, inc, exc,
1018 proxy, proxy_username, proxy_password, debuginfo,
1019 source, gpgkey, disable, ssl_verify, nocache,
1020 cost, priority) = repo
1022 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1023 proxy_username, proxy_password, inc, exc, ssl_verify,
1024 nocache, cost, priority)
1026 if kickstart.exclude_docs(self.ks):
1027 rpm.addMacro("_excludedocs", "1")
1028 rpm.addMacro("_dbpath", "/var/lib/rpm")
1029 rpm.addMacro("__file_context_path", "%{nil}")
1030 if kickstart.inst_langs(self.ks) != None:
1031 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1034 self.__preinstall_packages(pkg_manager)
1035 self.__select_packages(pkg_manager)
1036 self.__select_groups(pkg_manager)
1037 self.__deselect_packages(pkg_manager)
1038 self.__localinst_packages(pkg_manager)
1039 self.__check_packages(pkg_manager)
1041 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1042 checksize = self._root_fs_avail
1044 checksize -= BOOT_SAFEGUARD
1045 if self.target_arch:
1046 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1047 pkg_manager.runInstall(checksize)
1048 except CreatorError, e:
1050 except KeyboardInterrupt:
1053 self._pkgs_content = pkg_manager.getAllContent()
1054 self._pkgs_license = pkg_manager.getPkgsLicense()
1055 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1056 self.__attachment_packages(pkg_manager)
1063 # do some clean up to avoid lvm info leakage. this sucks.
1064 for subdir in ("cache", "backup", "archive"):
1065 lvmdir = self._instroot + "/etc/lvm/" + subdir
1067 for f in os.listdir(lvmdir):
1068 os.unlink(lvmdir + "/" + f)
1072 def postinstall(self):
1073 self.copy_attachment()
1075 def __run_post_scripts(self):
1076 msger.info("Running scripts ...")
1077 if os.path.exists(self._instroot + "/tmp"):
1078 shutil.rmtree(self._instroot + "/tmp")
1079 os.mkdir (self._instroot + "/tmp", 0755)
1080 for s in kickstart.get_post_scripts(self.ks):
1081 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1082 dir = self._instroot + "/tmp")
1084 s.script = s.script.replace("\r", "")
1085 os.write(fd, s.script)
1087 os.chmod(path, 0700)
1089 env = self._get_post_scripts_env(s.inChroot)
1090 if 'PATH' not in env:
1091 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1097 preexec = self._chroot
1098 script = "/tmp/" + os.path.basename(path)
1102 p = subprocess.Popen([s.interp, script],
1103 preexec_fn = preexec,
1105 stdout = subprocess.PIPE,
1106 stderr = subprocess.STDOUT)
1107 for entry in p.communicate()[0].splitlines():
1109 except OSError, (err, msg):
1110 raise CreatorError("Failed to execute %%post script "
1111 "with '%s' : %s" % (s.interp, msg))
1115 def __save_repo_keys(self, repodata):
1119 gpgkeydir = "/etc/pki/rpm-gpg"
1120 fs.makedirs(self._instroot + gpgkeydir)
1121 for repo in repodata:
1123 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1124 shutil.copy(repo["repokey"], self._instroot + repokey)
1126 def configure(self, repodata = None):
1127 """Configure the system image according to the kickstart.
1129 This method applies the (e.g. keyboard or network) configuration
1130 specified in the kickstart and executes the kickstart %post scripts.
1132 If necessary, it also prepares the image to be bootable by e.g.
1133 creating an initrd and bootloader configuration.
1136 ksh = self.ks.handler
1138 msger.info('Applying configurations ...')
1140 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1141 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1142 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1143 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1144 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1145 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1146 kickstart.UserConfig(self._instroot).apply(ksh.user)
1147 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1148 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1149 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1150 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1151 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1152 self.__save_repo_keys(repodata)
1153 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1155 msger.warning("Failed to apply configuration to image")
1158 self._create_bootconfig()
1159 self.__run_post_scripts()
1161 def launch_shell(self, launch):
1162 """Launch a shell in the install root.
1164 This method is launches a bash shell chroot()ed in the install root;
1165 this can be useful for debugging.
1169 msger.info("Launching shell. Exit to continue.")
1170 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1172 def do_genchecksum(self, image_name):
1173 if not self._genchecksum:
1176 md5sum = misc.get_md5sum(image_name)
1177 with open(image_name + ".md5sum", "w") as f:
1178 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1179 self.outimage.append(image_name+".md5sum")
1181 def package(self, destdir = "."):
1182 """Prepares the created image for final delivery.
1184 In its simplest form, this method merely copies the install root to the
1185 supplied destination directory; other subclasses may choose to package
1186 the image by e.g. creating a bootable ISO containing the image and
1187 bootloader configuration.
1189 destdir -- the directory into which the final image should be moved;
1190 this defaults to the current directory.
1193 self._stage_final_image()
1195 if not os.path.exists(destdir):
1196 fs.makedirs(destdir)
1198 if self._recording_pkgs:
1199 self._save_recording_pkgs(destdir)
1201 # For image formats with two or multiple image files, it will be
1202 # better to put them under a directory
1203 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1204 destdir = os.path.join(destdir, "%s-%s" \
1205 % (self.name, self.image_format))
1206 msger.debug("creating destination dir: %s" % destdir)
1207 fs.makedirs(destdir)
1209 # Ensure all data is flushed to _outdir
1210 runner.quiet('sync')
1212 misc.check_space_pre_cp(self._outdir, destdir)
1213 for f in os.listdir(self._outdir):
1214 shutil.move(os.path.join(self._outdir, f),
1215 os.path.join(destdir, f))
1216 self.outimage.append(os.path.join(destdir, f))
1217 self.do_genchecksum(os.path.join(destdir, f))
1219 def print_outimage_info(self):
1220 msg = "The new image can be found here:\n"
1221 self.outimage.sort()
1222 for file in self.outimage:
1223 msg += ' %s\n' % os.path.abspath(file)
1227 def check_depend_tools(self):
1228 for tool in self._dep_checks:
1229 fs.find_binary_path(tool)
1231 def package_output(self, image_format, destdir = ".", package="none"):
1232 if not package or package == "none":
1235 destdir = os.path.abspath(os.path.expanduser(destdir))
1236 (pkg, comp) = os.path.splitext(package)
1238 comp=comp.lstrip(".")
1242 dst = "%s/%s-%s.tar.%s" %\
1243 (destdir, self.name, image_format, comp)
1245 dst = "%s/%s-%s.tar" %\
1246 (destdir, self.name, image_format)
1248 msger.info("creating %s" % dst)
1249 tar = tarfile.open(dst, "w:" + comp)
1251 for file in self.outimage:
1252 msger.info("adding %s to %s" % (file, dst))
1254 arcname=os.path.join("%s-%s" \
1255 % (self.name, image_format),
1256 os.path.basename(file)))
1257 if os.path.isdir(file):
1258 shutil.rmtree(file, ignore_errors = True)
1264 '''All the file in outimage has been packaged into tar.* file'''
1265 self.outimage = [dst]
1267 def release_output(self, config, destdir, release):
1268 """ Create release directory and files
1272 """ release path """
1273 return os.path.join(destdir, fn)
1275 outimages = self.outimage
1278 new_kspath = _rpath(self.name+'.ks')
1279 with open(config) as fr:
1280 with open(new_kspath, "w") as wf:
1281 # When building a release we want to make sure the .ks
1282 # file generates the same build even when --release not used.
1283 wf.write(fr.read().replace("@BUILD_ID@", release))
1284 outimages.append(new_kspath)
1286 # save log file, logfile is only available in creator attrs
1287 if hasattr(self, 'logfile') and not self.logfile:
1288 log_path = _rpath(self.name + ".log")
1289 # touch the log file, else outimages will filter it out
1290 with open(log_path, 'w') as wf:
1292 msger.set_logfile(log_path)
1293 outimages.append(_rpath(self.name + ".log"))
1295 # rename iso and usbimg
1296 for f in os.listdir(destdir):
1297 if f.endswith(".iso"):
1298 newf = f[:-4] + '.img'
1299 elif f.endswith(".usbimg"):
1300 newf = f[:-7] + '.img'
1303 os.rename(_rpath(f), _rpath(newf))
1304 outimages.append(_rpath(newf))
1307 with open(_rpath("MD5SUMS"), "w") as wf:
1308 for f in os.listdir(destdir):
1312 if os.path.isdir(os.path.join(destdir, f)):
1315 md5sum = misc.get_md5sum(_rpath(f))
1316 # There needs to be two spaces between the sum and
1317 # filepath to match the syntax with md5sum.
1318 # This way also md5sum -c MD5SUMS can be used by users
1319 wf.write("%s *%s\n" % (md5sum, f))
1321 outimages.append("%s/MD5SUMS" % destdir)
1323 # Filter out the nonexist file
1324 for fp in outimages[:]:
1325 if not os.path.exists("%s" % fp):
1326 outimages.remove(fp)
1328 def copy_kernel(self):
1329 """ Copy kernel files to the outimage directory.
1330 NOTE: This needs to be called before unmounting the instroot.
1333 if not self._need_copy_kernel:
1336 if not os.path.exists(self.destdir):
1337 os.makedirs(self.destdir)
1339 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1340 kernelfilename = "%s/%s-%s" % (self.destdir,
1342 os.path.basename(kernel))
1343 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1345 shutil.copy(kernel, kernelfilename)
1346 self.outimage.append(kernelfilename)
1348 def copy_attachment(self):
1349 """ Subclass implement it to handle attachment files
1350 NOTE: This needs to be called before unmounting the instroot.
1354 def get_pkg_manager(self):
1355 return self.pkgmgr(target_arch = self.target_arch,
1356 instroot = self._instroot,
1357 cachedir = self.cachedir)