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
30 from datetime import datetime
34 from mic import kickstart
35 from mic import msger, __version__ as VERSION
36 from mic.utils.errors import CreatorError, Abort
37 from mic.utils import misc, grabber, runner, fs_related as fs
38 from mic.chroot import kill_proc_inchroot
39 from mic.archive import get_archive_suffixes
41 class BaseImageCreator(object):
42 """Installs a system to a chroot directory.
44 ImageCreator is the simplest creator class available; it will install and
45 configure a system image according to the supplied kickstart file.
49 import mic.imgcreate as imgcreate
50 ks = imgcreate.read_kickstart("foo.ks")
51 imgcreate.ImageCreator(ks, "foo").create()
60 def __init__(self, createopts = None, pkgmgr = None):
61 """Initialize an ImageCreator instance.
63 ks -- a pykickstart.KickstartParser instance; this instance will be
64 used to drive the install by e.g. providing the list of packages
65 to be installed, the system configuration and %post scripts
67 name -- a name for the image; used for e.g. image filenames or
74 self.__builddir = None
75 self.__bindmounts = []
79 self.tmpdir = "/var/tmp/mic"
80 self.cachedir = "/var/tmp/mic/cache"
81 self.workdir = "/var/tmp/mic/build"
83 self.installerfw_prefix = "INSTALLERFW_"
84 self.target_arch = "noarch"
85 self.strict_mode = False
86 self._local_pkgs_path = None
90 # If the kernel is save to the destdir when copy_kernel cmd is called.
91 self._need_copy_kernel = False
92 # setup tmpfs tmpdir when enabletmpfs is True
93 self.enabletmpfs = False
96 # Mapping table for variables that have different names.
97 optmap = {"pkgmgr" : "pkgmgr_name",
98 "arch" : "target_arch",
99 "local_pkgs_path" : "_local_pkgs_path",
100 "copy_kernel" : "_need_copy_kernel",
101 "strict_mode" : "strict_mode",
104 # update setting from createopts
105 for key in createopts.keys():
110 setattr(self, option, createopts[key])
112 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
115 if '@NAME@' in self.pack_to:
116 self.pack_to = self.pack_to.replace('@NAME@', self.name)
117 (tar, ext) = os.path.splitext(self.pack_to)
118 if ext in (".gz", ".bz2", ".lzo", ".bz") and tar.endswith(".tar"):
120 if ext not in get_archive_suffixes():
121 self.pack_to += ".tar"
123 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
125 # Output image file names
127 # Output info related with manifest
128 self.image_files = {}
129 # A flag to generate checksum
130 self._genchecksum = False
132 self._alt_initrd_name = None
134 self._recording_pkgs = []
136 # available size in root fs, init to 0
137 self._root_fs_avail = 0
139 # Name of the disk image file that is created.
140 self._img_name = None
142 self.image_format = None
144 # Save qemu emulator file name in order to clean up it finally
145 self.qemu_emulator = None
147 # No ks provided when called by convertor, so skip the dependency check
149 # If we have btrfs partition we need to check necessary tools
150 for part in self.ks.handler.partition.partitions:
151 if part.fstype and part.fstype == "btrfs":
152 self._dep_checks.append("mkfs.btrfs")
155 if self.target_arch and self.target_arch.startswith("arm"):
156 for dep in self._dep_checks:
157 if dep == "extlinux":
158 self._dep_checks.remove(dep)
160 if not os.path.exists("/usr/bin/qemu-arm") or \
161 not misc.is_statically_linked("/usr/bin/qemu-arm"):
162 self._dep_checks.append("qemu-arm-static")
164 if os.path.exists("/proc/sys/vm/vdso_enabled"):
165 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
166 vdso_value = vdso_fh.read().strip()
168 if (int)(vdso_value) == 1:
169 msger.warning("vdso is enabled on your host, which might "
170 "cause problems with arm emulations.\n"
171 "\tYou can disable vdso with following command before "
172 "starting image build:\n"
173 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
175 # make sure the specified tmpdir and cachedir exist
176 if not os.path.exists(self.tmpdir):
177 os.makedirs(self.tmpdir)
178 if not os.path.exists(self.cachedir):
179 os.makedirs(self.cachedir)
185 def __get_instroot(self):
186 if self.__builddir is None:
187 raise CreatorError("_instroot is not valid before calling mount()")
188 return self.__builddir + "/install_root"
189 _instroot = property(__get_instroot)
190 """The location of the install root directory.
192 This is the directory into which the system is installed. Subclasses may
193 mount a filesystem image here or copy files to/from here.
195 Note, this directory does not exist before ImageCreator.mount() is called.
197 Note also, this is a read-only attribute.
201 def __get_outdir(self):
202 if self.__builddir is None:
203 raise CreatorError("_outdir is not valid before calling mount()")
204 return self.__builddir + "/out"
205 _outdir = property(__get_outdir)
206 """The staging location for the final image.
208 This is where subclasses should stage any files that are part of the final
209 image. ImageCreator.package() will copy any files found here into the
210 requested destination directory.
212 Note, this directory does not exist before ImageCreator.mount() is called.
214 Note also, this is a read-only attribute.
220 # Hooks for subclasses
222 def _mount_instroot(self, base_on = None):
223 """Mount or prepare the install root directory.
225 This is the hook where subclasses may prepare the install root by e.g.
226 mounting creating and loopback mounting a filesystem image to
229 There is no default implementation.
231 base_on -- this is the value passed to mount() and can be interpreted
232 as the subclass wishes; it might e.g. be the location of
233 a previously created ISO containing a system image.
238 def _unmount_instroot(self):
239 """Undo anything performed in _mount_instroot().
241 This is the hook where subclasses must undo anything which was done
242 in _mount_instroot(). For example, if a filesystem image was mounted
243 onto _instroot, it should be unmounted here.
245 There is no default implementation.
250 def _create_bootconfig(self):
251 """Configure the image so that it's bootable.
253 This is the hook where subclasses may prepare the image for booting by
254 e.g. creating an initramfs and bootloader configuration.
256 This hook is called while the install root is still mounted, after the
257 packages have been installed and the kickstart configuration has been
258 applied, but before the %post scripts have been executed.
260 There is no default implementation.
265 def _stage_final_image(self):
266 """Stage the final system image in _outdir.
268 This is the hook where subclasses should place the image in _outdir
269 so that package() can copy it to the requested destination directory.
271 By default, this moves the install root into _outdir.
274 shutil.move(self._instroot, self._outdir + "/" + self.name)
276 def get_installed_packages(self):
277 return self._pkgs_content.keys()
279 def _save_recording_pkgs(self, destdir):
280 """Save the list or content of installed packages to file.
282 pkgs = self._pkgs_content.keys()
283 pkgs.sort() # inplace op
285 if not os.path.exists(destdir):
289 if 'vcs' in self._recording_pkgs:
290 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
291 content = '\n'.join(sorted(vcslst))
292 elif 'name' in self._recording_pkgs:
293 content = '\n'.join(pkgs)
295 namefile = os.path.join(destdir, self.name + '.packages')
296 f = open(namefile, "w")
299 self.outimage.append(namefile);
301 # if 'content', save more details
302 if 'content' in self._recording_pkgs:
303 contfile = os.path.join(destdir, self.name + '.files')
304 f = open(contfile, "w")
309 pkgcont = self._pkgs_content[pkg]
311 content += '\n '.join(pkgcont)
317 self.outimage.append(contfile)
319 if 'license' in self._recording_pkgs:
320 licensefile = os.path.join(destdir, self.name + '.license')
321 f = open(licensefile, "w")
323 f.write('Summary:\n')
324 for license in reversed(sorted(self._pkgs_license, key=\
325 lambda license: len(self._pkgs_license[license]))):
326 f.write(" - %s: %s\n" \
327 % (license, len(self._pkgs_license[license])))
329 f.write('\nDetails:\n')
330 for license in reversed(sorted(self._pkgs_license, key=\
331 lambda license: len(self._pkgs_license[license]))):
332 f.write(" - %s:\n" % (license))
333 for pkg in sorted(self._pkgs_license[license]):
334 f.write(" - %s\n" % (pkg))
338 self.outimage.append(licensefile)
340 def _get_required_packages(self):
341 """Return a list of required packages.
343 This is the hook where subclasses may specify a set of packages which
344 it requires to be installed.
346 This returns an empty list by default.
348 Note, subclasses should usually chain up to the base class
349 implementation of this hook.
354 def _get_excluded_packages(self):
355 """Return a list of excluded packages.
357 This is the hook where subclasses may specify a set of packages which
358 it requires _not_ to be installed.
360 This returns an empty list by default.
362 Note, subclasses should usually chain up to the base class
363 implementation of this hook.
368 def _get_local_packages(self):
369 """Return a list of rpm path to be local installed.
371 This is the hook where subclasses may specify a set of rpms which
372 it requires to be installed locally.
374 This returns an empty list by default.
376 Note, subclasses should usually chain up to the base class
377 implementation of this hook.
380 if self._local_pkgs_path:
381 if os.path.isdir(self._local_pkgs_path):
383 os.path.join(self._local_pkgs_path, '*.rpm'))
384 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
385 return [self._local_pkgs_path]
389 def _get_fstab(self):
390 """Return the desired contents of /etc/fstab.
392 This is the hook where subclasses may specify the contents of
393 /etc/fstab by returning a string containing the desired contents.
395 A sensible default implementation is provided.
398 s = "/dev/root / %s %s 0 0\n" \
400 "defaults,noatime" if not self._fsopts else self._fsopts)
401 s += self._get_fstab_special()
404 def _get_fstab_special(self):
405 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
406 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
407 s += "proc /proc proc defaults 0 0\n"
408 s += "sysfs /sys sysfs defaults 0 0\n"
411 def _set_part_env(self, pnum, prop, value):
412 """ This is a helper function which generates an environment variable
413 for a property "prop" with value "value" of a partition number "pnum".
415 The naming convention is:
416 * Variables start with INSTALLERFW_PART
417 * Then goes the partition number, the order is the same as
418 specified in the KS file
419 * Then goes the property name
427 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
428 return { name : value }
430 def _get_post_scripts_env(self, in_chroot):
431 """Return an environment dict for %post scripts.
433 This is the hook where subclasses may specify some environment
434 variables for %post scripts by return a dict containing the desired
437 in_chroot -- whether this %post script is to be executed chroot()ed
444 for p in kickstart.get_partitions(self.ks):
445 env.update(self._set_part_env(pnum, "SIZE", p.size))
446 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
447 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
448 env.update(self._set_part_env(pnum, "LABEL", p.label))
449 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
450 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
451 env.update(self._set_part_env(pnum, "ALIGN", p.align))
452 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
453 env.update(self._set_part_env(pnum, "UUID", p.uuid))
454 env.update(self._set_part_env(pnum, "DEVNODE",
455 "/dev/%s%d" % (p.disk, pnum + 1)))
456 env.update(self._set_part_env(pnum, "DISK_DEVNODE",
461 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
463 # Partition table format
464 ptable_format = self.ks.handler.bootloader.ptable
465 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
467 # The kerned boot parameters
468 kernel_opts = self.ks.handler.bootloader.appendLine
469 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
471 # Name of the image creation tool
472 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
474 # The real current location of the mounted file-systems
478 mount_prefix = self._instroot
479 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
481 # These are historical variables which lack the common name prefix
483 env["INSTALL_ROOT"] = self._instroot
484 env["IMG_NAME"] = self._name
488 def __get_imgname(self):
490 _name = property(__get_imgname)
491 """The name of the image file.
495 def _get_kernel_versions(self):
496 """Return a dict detailing the available kernel types/versions.
498 This is the hook where subclasses may override what kernel types and
499 versions should be available for e.g. creating the booloader
502 A dict should be returned mapping the available kernel types to a list
503 of the available versions for those kernels.
505 The default implementation uses rpm to iterate over everything
506 providing 'kernel', finds /boot/vmlinuz-* and returns the version
507 obtained from the vmlinuz filename. (This can differ from the kernel
508 RPM's n-v-r in the case of e.g. xen)
511 def get_kernel_versions(instroot):
514 files = glob.glob(instroot + "/boot/vmlinuz-*")
516 version = os.path.basename(file)[8:]
519 versions.add(version)
520 ret["kernel"] = list(versions)
523 def get_version(header):
525 for f in header['filenames']:
526 if f.startswith('/boot/vmlinuz-'):
531 return get_kernel_versions(self._instroot)
533 ts = rpm.TransactionSet(self._instroot)
536 for header in ts.dbMatch('provides', 'kernel'):
537 version = get_version(header)
541 name = header['name']
543 ret[name] = [version]
544 elif not version in ret[name]:
545 ret[name].append(version)
551 # Helpers for subclasses
553 def _do_bindmounts(self):
554 """Mount various system directories onto _instroot.
556 This method is called by mount(), but may also be used by subclasses
557 in order to re-mount the bindmounts after modifying the underlying
561 for b in self.__bindmounts:
564 def _undo_bindmounts(self):
565 """Unmount the bind-mounted system directories from _instroot.
567 This method is usually only called by unmount(), but may also be used
568 by subclasses in order to gain access to the filesystem obscured by
569 the bindmounts - e.g. in order to create device nodes on the image
573 self.__bindmounts.reverse()
574 for b in self.__bindmounts:
578 """Chroot into the install root.
580 This method may be used by subclasses when executing programs inside
581 the install root e.g.
583 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
586 os.chroot(self._instroot)
589 def _mkdtemp(self, prefix = "tmp-"):
590 """Create a temporary directory.
592 This method may be used by subclasses to create a temporary directory
593 for use in building the final image - e.g. a subclass might create
594 a temporary directory in order to bundle a set of files into a package.
596 The subclass may delete this directory if it wishes, but it will be
597 automatically deleted by cleanup().
599 The absolute path to the temporary directory is returned.
601 Note, this method should only be called after mount() has been called.
603 prefix -- a prefix which should be used when creating the directory;
607 self.__ensure_builddir()
608 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
610 def _mkstemp(self, prefix = "tmp-"):
611 """Create a temporary file.
613 This method may be used by subclasses to create a temporary file
614 for use in building the final image - e.g. a subclass might need
615 a temporary location to unpack a compressed file.
617 The subclass may delete this file if it wishes, but it will be
618 automatically deleted by cleanup().
620 A tuple containing a file descriptor (returned from os.open() and the
621 absolute path to the temporary directory is returned.
623 Note, this method should only be called after mount() has been called.
625 prefix -- a prefix which should be used when creating the file;
629 self.__ensure_builddir()
630 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
632 def _mktemp(self, prefix = "tmp-"):
633 """Create a temporary file.
635 This method simply calls _mkstemp() and closes the returned file
638 The absolute path to the temporary file is returned.
640 Note, this method should only be called after mount() has been called.
642 prefix -- a prefix which should be used when creating the file;
647 (f, path) = self._mkstemp(prefix)
653 # Actual implementation
655 def __ensure_builddir(self):
656 if not self.__builddir is None:
660 self.workdir = os.path.join(self.tmpdir, "build")
661 if not os.path.exists(self.workdir):
662 os.makedirs(self.workdir)
663 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
664 prefix = "imgcreate-")
665 except OSError, (err, msg):
666 raise CreatorError("Failed create build directory in %s: %s" %
669 def get_cachedir(self, cachedir = None):
673 self.__ensure_builddir()
675 self.cachedir = cachedir
677 self.cachedir = self.__builddir + "/mic-cache"
678 fs.makedirs(self.cachedir)
681 def __sanity_check(self):
682 """Ensure that the config we've been given is sane."""
683 if not (kickstart.get_packages(self.ks) or
684 kickstart.get_groups(self.ks)):
685 raise CreatorError("No packages or groups specified")
687 kickstart.convert_method_to_repo(self.ks)
689 if not kickstart.get_repos(self.ks):
690 raise CreatorError("No repositories specified")
692 def __write_fstab(self):
693 if kickstart.use_installerfw(self.ks, "fstab"):
694 # The fstab file will be generated by installer framework scripts
697 fstab_contents = self._get_fstab()
699 fstab = open(self._instroot + "/etc/fstab", "w")
700 fstab.write(fstab_contents)
703 def __create_minimal_dev(self):
704 """Create a minimal /dev so that we don't corrupt the host /dev"""
705 origumask = os.umask(0000)
706 devices = (('null', 1, 3, 0666),
707 ('urandom',1, 9, 0666),
708 ('random', 1, 8, 0666),
709 ('full', 1, 7, 0666),
710 ('ptmx', 5, 2, 0666),
712 ('zero', 1, 5, 0666))
714 links = (("/proc/self/fd", "/dev/fd"),
715 ("/proc/self/fd/0", "/dev/stdin"),
716 ("/proc/self/fd/1", "/dev/stdout"),
717 ("/proc/self/fd/2", "/dev/stderr"))
719 for (node, major, minor, perm) in devices:
720 if not os.path.exists(self._instroot + "/dev/" + node):
721 os.mknod(self._instroot + "/dev/" + node,
723 os.makedev(major,minor))
725 for (src, dest) in links:
726 if not os.path.exists(self._instroot + dest):
727 os.symlink(src, self._instroot + dest)
731 def __setup_tmpdir(self):
732 if not self.enabletmpfs:
735 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
737 def __clean_tmpdir(self):
738 if not self.enabletmpfs:
741 runner.show('umount -l %s' % self.workdir)
743 def mount(self, base_on = None, cachedir = None):
744 """Setup the target filesystem in preparation for an install.
746 This function sets up the filesystem which the ImageCreator will
747 install into and configure. The ImageCreator class merely creates an
748 install root directory, bind mounts some system directories (e.g. /dev)
749 and writes out /etc/fstab. Other subclasses may also e.g. create a
750 sparse file, format it and loopback mount it to the install root.
752 base_on -- a previous install on which to base this install; defaults
753 to None, causing a new image to be created
755 cachedir -- a directory in which to store the Yum cache; defaults to
756 None, causing a new cache to be created; by setting this
757 to another directory, the same cache can be reused across
761 self.__setup_tmpdir()
762 self.__ensure_builddir()
764 # prevent popup dialog in Ubuntu(s)
765 misc.hide_loopdev_presentation()
767 fs.makedirs(self._instroot)
768 fs.makedirs(self._outdir)
770 self._mount_instroot(base_on)
772 for d in ("/dev/pts",
779 fs.makedirs(self._instroot + d)
781 if self.target_arch and self.target_arch.startswith("arm") or \
782 self.target_arch == "aarch64":
783 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
786 self.get_cachedir(cachedir)
788 # bind mount system directories into _instroot
789 for (f, dest) in [("/sys", None),
791 ("/proc/sys/fs/binfmt_misc", None),
793 self.__bindmounts.append(
795 f, self._instroot, dest))
797 self._do_bindmounts()
799 self.__create_minimal_dev()
801 if os.path.exists(self._instroot + "/etc/mtab"):
802 os.unlink(self._instroot + "/etc/mtab")
803 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
807 # get size of available space in 'instroot' fs
808 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
811 """Unmounts the target filesystem.
813 The ImageCreator class detaches the system from the install root, but
814 other subclasses may also detach the loopback mounted filesystem image
815 from the install root.
819 mtab = self._instroot + "/etc/mtab"
820 if not os.path.islink(mtab):
821 os.unlink(self._instroot + "/etc/mtab")
823 if self.qemu_emulator:
824 os.unlink(self._instroot + self.qemu_emulator)
828 self._undo_bindmounts()
830 """ Clean up yum garbage """
832 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
833 if os.path.exists(instroot_pdir):
834 shutil.rmtree(instroot_pdir, ignore_errors = True)
835 yumlibdir = self._instroot + "/var/lib/yum"
836 if os.path.exists(yumlibdir):
837 shutil.rmtree(yumlibdir, ignore_errors = True)
841 self._unmount_instroot()
843 # reset settings of popup dialog in Ubuntu(s)
844 misc.unhide_loopdev_presentation()
848 """Unmounts the target filesystem and deletes temporary files.
850 This method calls unmount() and then deletes any temporary files and
851 directories that were created on the host system while building the
854 Note, make sure to call this method once finished with the creator
855 instance in order to ensure no stale files are left on the host e.g.:
857 creator = ImageCreator(ks, name)
864 if not self.__builddir:
867 kill_proc_inchroot(self._instroot)
871 shutil.rmtree(self.__builddir, ignore_errors = True)
872 self.__builddir = None
874 self.__clean_tmpdir()
876 def __is_excluded_pkg(self, pkg):
877 if pkg in self._excluded_pkgs:
878 self._excluded_pkgs.remove(pkg)
881 for xpkg in self._excluded_pkgs:
882 if xpkg.endswith('*'):
883 if pkg.startswith(xpkg[:-1]):
885 elif xpkg.startswith('*'):
886 if pkg.endswith(xpkg[1:]):
891 def __select_packages(self, pkg_manager):
893 for pkg in self._required_pkgs:
894 e = pkg_manager.selectPackage(pkg)
896 if kickstart.ignore_missing(self.ks):
897 skipped_pkgs.append(pkg)
898 elif self.__is_excluded_pkg(pkg):
899 skipped_pkgs.append(pkg)
901 raise CreatorError("Failed to find package '%s' : %s" %
904 for pkg in skipped_pkgs:
905 msger.warning("Skipping missing package '%s'" % (pkg,))
907 def __select_groups(self, pkg_manager):
909 for group in self._required_groups:
910 e = pkg_manager.selectGroup(group.name, group.include)
912 if kickstart.ignore_missing(self.ks):
913 skipped_groups.append(group)
915 raise CreatorError("Failed to find group '%s' : %s" %
918 for group in skipped_groups:
919 msger.warning("Skipping missing group '%s'" % (group.name,))
921 def __deselect_packages(self, pkg_manager):
922 for pkg in self._excluded_pkgs:
923 pkg_manager.deselectPackage(pkg)
925 def __localinst_packages(self, pkg_manager):
926 for rpm_path in self._get_local_packages():
927 pkg_manager.installLocal(rpm_path)
929 def __preinstall_packages(self, pkg_manager):
933 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
934 for pkg in self._preinstall_pkgs:
935 pkg_manager.preInstall(pkg)
937 def __check_packages(self, pkg_manager):
938 for pkg in self.check_pkgs:
939 pkg_manager.checkPackage(pkg)
941 def __attachment_packages(self, pkg_manager):
945 self._attachment = []
946 for item in kickstart.get_attachment(self.ks):
947 if item.startswith('/'):
948 fpaths = os.path.join(self._instroot, item.lstrip('/'))
949 for fpath in glob.glob(fpaths):
950 self._attachment.append(fpath)
953 filelist = pkg_manager.getFilelist(item)
955 # found rpm in rootfs
956 for pfile in pkg_manager.getFilelist(item):
957 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
958 self._attachment.append(fpath)
961 # try to retrieve rpm file
962 (url, proxies) = pkg_manager.package_url(item)
964 msger.warning("Can't get url from repo for %s" % item)
966 fpath = os.path.join(self.cachedir, os.path.basename(url))
967 if not os.path.exists(fpath):
970 fpath = grabber.myurlgrab(url.full, fpath, proxies, None)
974 tmpdir = self._mkdtemp()
975 misc.extract_rpm(fpath, tmpdir)
976 for (root, dirs, files) in os.walk(tmpdir):
978 fpath = os.path.join(root, fname)
979 self._attachment.append(fpath)
981 def install(self, repo_urls=None):
982 """Install packages into the install root.
984 This function installs the packages listed in the supplied kickstart
985 into the install root. By default, the packages are installed from the
986 repository URLs specified in the kickstart.
988 repo_urls -- a dict which maps a repository name to a repository;
989 if supplied, this causes any repository URLs specified in
990 the kickstart to be overridden.
993 def get_ssl_verify(ssl_verify=None):
994 if ssl_verify is not None:
995 return not ssl_verify.lower().strip() == 'no'
997 return not self.ssl_verify.lower().strip() == 'no'
999 # initialize pkg list to install
1001 self.__sanity_check()
1003 self._required_pkgs = \
1004 kickstart.get_packages(self.ks, self._get_required_packages())
1005 self._excluded_pkgs = \
1006 kickstart.get_excluded(self.ks, self._get_excluded_packages())
1007 self._required_groups = kickstart.get_groups(self.ks)
1009 self._required_pkgs = None
1010 self._excluded_pkgs = None
1011 self._required_groups = None
1014 repo_urls = self.extrarepos
1016 pkg_manager = self.get_pkg_manager()
1019 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1020 if 'debuginfo' in self.install_pkgs:
1021 pkg_manager.install_debuginfo = True
1023 for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1024 (name, baseurl, mirrorlist, inc, exc,
1025 proxy, proxy_username, proxy_password, debuginfo,
1026 source, gpgkey, disable, ssl_verify, nocache,
1027 cost, priority) = repo
1029 ssl_verify = get_ssl_verify(ssl_verify)
1030 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1031 proxy_username, proxy_password, inc, exc, ssl_verify,
1032 nocache, cost, priority)
1034 if kickstart.exclude_docs(self.ks):
1035 rpm.addMacro("_excludedocs", "1")
1036 rpm.addMacro("_dbpath", "/var/lib/rpm")
1037 rpm.addMacro("__file_context_path", "%{nil}")
1038 if kickstart.inst_langs(self.ks) != None:
1039 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1042 self.__preinstall_packages(pkg_manager)
1043 self.__select_packages(pkg_manager)
1044 self.__select_groups(pkg_manager)
1045 self.__deselect_packages(pkg_manager)
1046 self.__localinst_packages(pkg_manager)
1047 self.__check_packages(pkg_manager)
1049 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1050 checksize = self._root_fs_avail
1052 checksize -= BOOT_SAFEGUARD
1053 if self.target_arch:
1054 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1055 pkg_manager.runInstall(checksize)
1056 except CreatorError, e:
1058 except KeyboardInterrupt:
1061 self._pkgs_content = pkg_manager.getAllContent()
1062 self._pkgs_license = pkg_manager.getPkgsLicense()
1063 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1064 self.__attachment_packages(pkg_manager)
1071 # do some clean up to avoid lvm info leakage. this sucks.
1072 for subdir in ("cache", "backup", "archive"):
1073 lvmdir = self._instroot + "/etc/lvm/" + subdir
1075 for f in os.listdir(lvmdir):
1076 os.unlink(lvmdir + "/" + f)
1080 def postinstall(self):
1081 self.copy_attachment()
1083 def __run_post_scripts(self):
1084 msger.info("Running scripts ...")
1085 if os.path.exists(self._instroot + "/tmp"):
1086 shutil.rmtree(self._instroot + "/tmp")
1087 os.mkdir (self._instroot + "/tmp", 0755)
1088 for s in kickstart.get_post_scripts(self.ks):
1089 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1090 dir = self._instroot + "/tmp")
1092 s.script = s.script.replace("\r", "")
1093 os.write(fd, s.script)
1095 os.chmod(path, 0700)
1097 env = self._get_post_scripts_env(s.inChroot)
1098 if 'PATH' not in env:
1099 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1105 preexec = self._chroot
1106 script = "/tmp/" + os.path.basename(path)
1110 p = subprocess.Popen([s.interp, script],
1111 preexec_fn = preexec,
1113 stdout = subprocess.PIPE,
1114 stderr = subprocess.STDOUT)
1115 for entry in p.communicate()[0].splitlines():
1117 except OSError, (err, msg):
1118 raise CreatorError("Failed to execute %%post script "
1119 "with '%s' : %s" % (s.interp, msg))
1123 def __save_repo_keys(self, repodata):
1127 gpgkeydir = "/etc/pki/rpm-gpg"
1128 fs.makedirs(self._instroot + gpgkeydir)
1129 for repo in repodata:
1131 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1132 shutil.copy(repo["repokey"], self._instroot + repokey)
1134 def configure(self, repodata = None):
1135 """Configure the system image according to the kickstart.
1137 This method applies the (e.g. keyboard or network) configuration
1138 specified in the kickstart and executes the kickstart %post scripts.
1140 If necessary, it also prepares the image to be bootable by e.g.
1141 creating an initrd and bootloader configuration.
1144 ksh = self.ks.handler
1146 msger.info('Applying configurations ...')
1148 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1149 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1150 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1151 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1152 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1153 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1154 kickstart.UserConfig(self._instroot).apply(ksh.user)
1155 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1156 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1157 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1158 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1159 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1160 self.__save_repo_keys(repodata)
1161 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1163 msger.warning("Failed to apply configuration to image")
1166 self._create_bootconfig()
1167 self.__run_post_scripts()
1169 def launch_shell(self, launch):
1170 """Launch a shell in the install root.
1172 This method is launches a bash shell chroot()ed in the install root;
1173 this can be useful for debugging.
1177 msger.info("Launching shell. Exit to continue.")
1178 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1180 def do_genchecksum(self, image_name):
1181 if not self._genchecksum:
1184 md5sum = misc.get_md5sum(image_name)
1185 with open(image_name + ".md5sum", "w") as f:
1186 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1187 self.outimage.append(image_name+".md5sum")
1189 def package(self, destdir = "."):
1190 """Prepares the created image for final delivery.
1192 In its simplest form, this method merely copies the install root to the
1193 supplied destination directory; other subclasses may choose to package
1194 the image by e.g. creating a bootable ISO containing the image and
1195 bootloader configuration.
1197 destdir -- the directory into which the final image should be moved;
1198 this defaults to the current directory.
1201 self._stage_final_image()
1203 if not os.path.exists(destdir):
1204 fs.makedirs(destdir)
1206 if self._recording_pkgs:
1207 self._save_recording_pkgs(destdir)
1209 # For image formats with two or multiple image files, it will be
1210 # better to put them under a directory
1211 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1212 destdir = os.path.join(destdir, "%s-%s" \
1213 % (self.name, self.image_format))
1214 msger.debug("creating destination dir: %s" % destdir)
1215 fs.makedirs(destdir)
1217 # Ensure all data is flushed to _outdir
1218 runner.quiet('sync')
1220 misc.check_space_pre_cp(self._outdir, destdir)
1221 for f in os.listdir(self._outdir):
1222 shutil.move(os.path.join(self._outdir, f),
1223 os.path.join(destdir, f))
1224 self.outimage.append(os.path.join(destdir, f))
1225 self.do_genchecksum(os.path.join(destdir, f))
1227 def print_outimage_info(self):
1228 msg = "The new image can be found here:\n"
1229 self.outimage.sort()
1230 for file in self.outimage:
1231 msg += ' %s\n' % os.path.abspath(file)
1235 def check_depend_tools(self):
1236 for tool in self._dep_checks:
1237 fs.find_binary_path(tool)
1239 def package_output(self, image_format, destdir = ".", package="none"):
1240 if not package or package == "none":
1243 destdir = os.path.abspath(os.path.expanduser(destdir))
1244 (pkg, comp) = os.path.splitext(package)
1246 comp=comp.lstrip(".")
1250 dst = "%s/%s-%s.tar.%s" %\
1251 (destdir, self.name, image_format, comp)
1253 dst = "%s/%s-%s.tar" %\
1254 (destdir, self.name, image_format)
1256 msger.info("creating %s" % dst)
1257 tar = tarfile.open(dst, "w:" + comp)
1259 for file in self.outimage:
1260 msger.info("adding %s to %s" % (file, dst))
1262 arcname=os.path.join("%s-%s" \
1263 % (self.name, image_format),
1264 os.path.basename(file)))
1265 if os.path.isdir(file):
1266 shutil.rmtree(file, ignore_errors = True)
1272 '''All the file in outimage has been packaged into tar.* file'''
1273 self.outimage = [dst]
1275 def release_output(self, config, destdir, release):
1276 """ Create release directory and files
1280 """ release path """
1281 return os.path.join(destdir, fn)
1283 outimages = self.outimage
1286 new_kspath = _rpath(self.name+'.ks')
1287 with open(config) as fr:
1288 with open(new_kspath, "w") as wf:
1289 # When building a release we want to make sure the .ks
1290 # file generates the same build even when --release not used.
1291 wf.write(fr.read().replace("@BUILD_ID@", release))
1292 outimages.append(new_kspath)
1294 # save log file, logfile is only available in creator attrs
1295 if hasattr(self, 'releaselog') and self.releaselog:
1296 final_logfile = _rpath(self.name+'.log')
1297 shutil.move(self.logfile, final_logfile)
1298 self.logfile = final_logfile
1299 outimages.append(self.logfile)
1301 # rename iso and usbimg
1302 for f in os.listdir(destdir):
1303 if f.endswith(".iso"):
1304 newf = f[:-4] + '.img'
1305 elif f.endswith(".usbimg"):
1306 newf = f[:-7] + '.img'
1309 os.rename(_rpath(f), _rpath(newf))
1310 outimages.append(_rpath(newf))
1313 with open(_rpath("MD5SUMS"), "w") as wf:
1314 for f in os.listdir(destdir):
1318 if os.path.isdir(os.path.join(destdir, f)):
1321 md5sum = misc.get_md5sum(_rpath(f))
1322 # There needs to be two spaces between the sum and
1323 # filepath to match the syntax with md5sum.
1324 # This way also md5sum -c MD5SUMS can be used by users
1325 wf.write("%s %s\n" % (md5sum, f))
1327 outimages.append("%s/MD5SUMS" % destdir)
1329 # Filter out the nonexist file
1330 for fp in outimages[:]:
1331 if not os.path.exists("%s" % fp):
1332 outimages.remove(fp)
1334 def copy_kernel(self):
1335 """ Copy kernel files to the outimage directory.
1336 NOTE: This needs to be called before unmounting the instroot.
1339 if not self._need_copy_kernel:
1342 if not os.path.exists(self.destdir):
1343 os.makedirs(self.destdir)
1345 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1346 kernelfilename = "%s/%s-%s" % (self.destdir,
1348 os.path.basename(kernel))
1349 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1351 shutil.copy(kernel, kernelfilename)
1352 self.outimage.append(kernelfilename)
1354 def copy_attachment(self):
1355 """ Subclass implement it to handle attachment files
1356 NOTE: This needs to be called before unmounting the instroot.
1360 def get_pkg_manager(self):
1361 return self.pkgmgr(target_arch = self.target_arch,
1362 instroot = self._instroot,
1363 cachedir = self.cachedir,
1364 strict_mode = self.strict_mode)
1366 def create_manifest(self):
1367 def get_pack_suffix():
1368 return '.' + self.pack_to.split('.', 1)[1]
1370 if not os.path.exists(self.destdir):
1371 os.makedirs(self.destdir)
1373 now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1374 manifest_dict = {'version': VERSION,
1377 manifest_dict.update({'format': self.img_format})
1379 if hasattr(self, 'logfile') and self.logfile:
1380 manifest_dict.update({'log_file': self.logfile})
1382 if self.image_files:
1384 self.image_files.update({'pack': get_pack_suffix()})
1385 manifest_dict.update({self.img_format: self.image_files})
1387 msger.info('Creating manifest file...')
1388 manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1389 with open(manifest_file_path, 'w') as fest_file:
1390 json.dump(manifest_dict, fest_file, indent=4)
1391 self.outimage.append(manifest_file_path)