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._local_pkgs_path = None
89 # If the kernel is save to the destdir when copy_kernel cmd is called.
90 self._need_copy_kernel = False
91 # setup tmpfs tmpdir when enabletmpfs is True
92 self.enabletmpfs = False
95 # Mapping table for variables that have different names.
96 optmap = {"pkgmgr" : "pkgmgr_name",
98 "arch" : "target_arch",
99 "local_pkgs_path" : "_local_pkgs_path",
100 "copy_kernel" : "_need_copy_kernel",
103 # update setting from createopts
104 for key in createopts.keys():
109 setattr(self, option, createopts[key])
111 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
113 if 'release' in createopts and createopts['release']:
114 self.name = createopts['release'] + '_' + self.name
117 if '@NAME@' in self.pack_to:
118 self.pack_to = self.pack_to.replace('@NAME@', self.name)
119 (tar, ext) = os.path.splitext(self.pack_to)
120 if ext in (".gz", ".bz2", ".lzo", ".bz") and tar.endswith(".tar"):
122 if ext not in get_archive_suffixes():
123 self.pack_to += ".tar"
125 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
127 # Output image file names
129 # Output info related with manifest
130 self.image_files = {}
131 # A flag to generate checksum
132 self._genchecksum = False
134 self._alt_initrd_name = None
136 self._recording_pkgs = []
138 # available size in root fs, init to 0
139 self._root_fs_avail = 0
141 # Name of the disk image file that is created.
142 self._img_name = None
144 self.image_format = None
146 # Save qemu emulator file name in order to clean up it finally
147 self.qemu_emulator = None
149 # No ks provided when called by convertor, so skip the dependency check
151 # If we have btrfs partition we need to check necessary tools
152 for part in self.ks.handler.partition.partitions:
153 if part.fstype and part.fstype == "btrfs":
154 self._dep_checks.append("mkfs.btrfs")
157 if self.target_arch and self.target_arch.startswith("arm"):
158 for dep in self._dep_checks:
159 if dep == "extlinux":
160 self._dep_checks.remove(dep)
162 if not os.path.exists("/usr/bin/qemu-arm") or \
163 not misc.is_statically_linked("/usr/bin/qemu-arm"):
164 self._dep_checks.append("qemu-arm-static")
166 if os.path.exists("/proc/sys/vm/vdso_enabled"):
167 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
168 vdso_value = vdso_fh.read().strip()
170 if (int)(vdso_value) == 1:
171 msger.warning("vdso is enabled on your host, which might "
172 "cause problems with arm emulations.\n"
173 "\tYou can disable vdso with following command before "
174 "starting image build:\n"
175 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
177 # make sure the specified tmpdir and cachedir exist
178 if not os.path.exists(self.tmpdir):
179 os.makedirs(self.tmpdir)
180 if not os.path.exists(self.cachedir):
181 os.makedirs(self.cachedir)
187 def __get_instroot(self):
188 if self.__builddir is None:
189 raise CreatorError("_instroot is not valid before calling mount()")
190 return self.__builddir + "/install_root"
191 _instroot = property(__get_instroot)
192 """The location of the install root directory.
194 This is the directory into which the system is installed. Subclasses may
195 mount a filesystem image here or copy files to/from here.
197 Note, this directory does not exist before ImageCreator.mount() is called.
199 Note also, this is a read-only attribute.
203 def __get_outdir(self):
204 if self.__builddir is None:
205 raise CreatorError("_outdir is not valid before calling mount()")
206 return self.__builddir + "/out"
207 _outdir = property(__get_outdir)
208 """The staging location for the final image.
210 This is where subclasses should stage any files that are part of the final
211 image. ImageCreator.package() will copy any files found here into the
212 requested destination directory.
214 Note, this directory does not exist before ImageCreator.mount() is called.
216 Note also, this is a read-only attribute.
222 # Hooks for subclasses
224 def _mount_instroot(self, base_on = None):
225 """Mount or prepare the install root directory.
227 This is the hook where subclasses may prepare the install root by e.g.
228 mounting creating and loopback mounting a filesystem image to
231 There is no default implementation.
233 base_on -- this is the value passed to mount() and can be interpreted
234 as the subclass wishes; it might e.g. be the location of
235 a previously created ISO containing a system image.
240 def _unmount_instroot(self):
241 """Undo anything performed in _mount_instroot().
243 This is the hook where subclasses must undo anything which was done
244 in _mount_instroot(). For example, if a filesystem image was mounted
245 onto _instroot, it should be unmounted here.
247 There is no default implementation.
252 def _create_bootconfig(self):
253 """Configure the image so that it's bootable.
255 This is the hook where subclasses may prepare the image for booting by
256 e.g. creating an initramfs and bootloader configuration.
258 This hook is called while the install root is still mounted, after the
259 packages have been installed and the kickstart configuration has been
260 applied, but before the %post scripts have been executed.
262 There is no default implementation.
267 def _stage_final_image(self):
268 """Stage the final system image in _outdir.
270 This is the hook where subclasses should place the image in _outdir
271 so that package() can copy it to the requested destination directory.
273 By default, this moves the install root into _outdir.
276 shutil.move(self._instroot, self._outdir + "/" + self.name)
278 def get_installed_packages(self):
279 return self._pkgs_content.keys()
281 def _save_recording_pkgs(self, destdir):
282 """Save the list or content of installed packages to file.
284 pkgs = self._pkgs_content.keys()
285 pkgs.sort() # inplace op
287 if not os.path.exists(destdir):
291 if 'vcs' in self._recording_pkgs:
292 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
293 content = '\n'.join(sorted(vcslst))
294 elif 'name' in self._recording_pkgs:
295 content = '\n'.join(pkgs)
297 namefile = os.path.join(destdir, self.name + '.packages')
298 f = open(namefile, "w")
301 self.outimage.append(namefile);
303 # if 'content', save more details
304 if 'content' in self._recording_pkgs:
305 contfile = os.path.join(destdir, self.name + '.files')
306 f = open(contfile, "w")
311 pkgcont = self._pkgs_content[pkg]
313 content += '\n '.join(pkgcont)
319 self.outimage.append(contfile)
321 if 'license' in self._recording_pkgs:
322 licensefile = os.path.join(destdir, self.name + '.license')
323 f = open(licensefile, "w")
325 f.write('Summary:\n')
326 for license in reversed(sorted(self._pkgs_license, key=\
327 lambda license: len(self._pkgs_license[license]))):
328 f.write(" - %s: %s\n" \
329 % (license, len(self._pkgs_license[license])))
331 f.write('\nDetails:\n')
332 for license in reversed(sorted(self._pkgs_license, key=\
333 lambda license: len(self._pkgs_license[license]))):
334 f.write(" - %s:\n" % (license))
335 for pkg in sorted(self._pkgs_license[license]):
336 f.write(" - %s\n" % (pkg))
340 self.outimage.append(licensefile)
342 def _get_required_packages(self):
343 """Return a list of required packages.
345 This is the hook where subclasses may specify a set of packages which
346 it requires 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_excluded_packages(self):
357 """Return a list of excluded packages.
359 This is the hook where subclasses may specify a set of packages which
360 it requires _not_ to be installed.
362 This returns an empty list by default.
364 Note, subclasses should usually chain up to the base class
365 implementation of this hook.
370 def _get_local_packages(self):
371 """Return a list of rpm path to be local installed.
373 This is the hook where subclasses may specify a set of rpms which
374 it requires to be installed locally.
376 This returns an empty list by default.
378 Note, subclasses should usually chain up to the base class
379 implementation of this hook.
382 if self._local_pkgs_path:
383 if os.path.isdir(self._local_pkgs_path):
385 os.path.join(self._local_pkgs_path, '*.rpm'))
386 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
387 return [self._local_pkgs_path]
391 def _get_fstab(self):
392 """Return the desired contents of /etc/fstab.
394 This is the hook where subclasses may specify the contents of
395 /etc/fstab by returning a string containing the desired contents.
397 A sensible default implementation is provided.
400 s = "/dev/root / %s %s 0 0\n" \
402 "defaults,noatime" if not self._fsopts else self._fsopts)
403 s += self._get_fstab_special()
406 def _get_fstab_special(self):
407 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
408 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
409 s += "proc /proc proc defaults 0 0\n"
410 s += "sysfs /sys sysfs defaults 0 0\n"
413 def _set_part_env(self, pnum, prop, value):
414 """ This is a helper function which generates an environment variable
415 for a property "prop" with value "value" of a partition number "pnum".
417 The naming convention is:
418 * Variables start with INSTALLERFW_PART
419 * Then goes the partition number, the order is the same as
420 specified in the KS file
421 * Then goes the property name
429 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
430 return { name : value }
432 def _get_post_scripts_env(self, in_chroot):
433 """Return an environment dict for %post scripts.
435 This is the hook where subclasses may specify some environment
436 variables for %post scripts by return a dict containing the desired
439 in_chroot -- whether this %post script is to be executed chroot()ed
446 for p in kickstart.get_partitions(self.ks):
447 env.update(self._set_part_env(pnum, "SIZE", p.size))
448 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
449 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
450 env.update(self._set_part_env(pnum, "LABEL", p.label))
451 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
452 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
453 env.update(self._set_part_env(pnum, "ALIGN", p.align))
454 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
455 env.update(self._set_part_env(pnum, "UUID", p.uuid))
456 env.update(self._set_part_env(pnum, "DEVNODE",
457 "/dev/%s%d" % (p.disk, pnum + 1)))
458 env.update(self._set_part_env(pnum, "DISK_DEVNODE",
463 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
465 # Partition table format
466 ptable_format = self.ks.handler.bootloader.ptable
467 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
469 # The kerned boot parameters
470 kernel_opts = self.ks.handler.bootloader.appendLine
471 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
473 # Name of the image creation tool
474 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
476 # The real current location of the mounted file-systems
480 mount_prefix = self._instroot
481 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
483 # These are historical variables which lack the common name prefix
485 env["INSTALL_ROOT"] = self._instroot
486 env["IMG_NAME"] = self._name
490 def __get_imgname(self):
492 _name = property(__get_imgname)
493 """The name of the image file.
497 def _get_kernel_versions(self):
498 """Return a dict detailing the available kernel types/versions.
500 This is the hook where subclasses may override what kernel types and
501 versions should be available for e.g. creating the booloader
504 A dict should be returned mapping the available kernel types to a list
505 of the available versions for those kernels.
507 The default implementation uses rpm to iterate over everything
508 providing 'kernel', finds /boot/vmlinuz-* and returns the version
509 obtained from the vmlinuz filename. (This can differ from the kernel
510 RPM's n-v-r in the case of e.g. xen)
513 def get_kernel_versions(instroot):
516 files = glob.glob(instroot + "/boot/vmlinuz-*")
518 version = os.path.basename(file)[8:]
521 versions.add(version)
522 ret["kernel"] = list(versions)
525 def get_version(header):
527 for f in header['filenames']:
528 if f.startswith('/boot/vmlinuz-'):
533 return get_kernel_versions(self._instroot)
535 ts = rpm.TransactionSet(self._instroot)
538 for header in ts.dbMatch('provides', 'kernel'):
539 version = get_version(header)
543 name = header['name']
545 ret[name] = [version]
546 elif not version in ret[name]:
547 ret[name].append(version)
553 # Helpers for subclasses
555 def _do_bindmounts(self):
556 """Mount various system directories onto _instroot.
558 This method is called by mount(), but may also be used by subclasses
559 in order to re-mount the bindmounts after modifying the underlying
563 for b in self.__bindmounts:
566 def _undo_bindmounts(self):
567 """Unmount the bind-mounted system directories from _instroot.
569 This method is usually only called by unmount(), but may also be used
570 by subclasses in order to gain access to the filesystem obscured by
571 the bindmounts - e.g. in order to create device nodes on the image
575 self.__bindmounts.reverse()
576 for b in self.__bindmounts:
580 """Chroot into the install root.
582 This method may be used by subclasses when executing programs inside
583 the install root e.g.
585 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
588 os.chroot(self._instroot)
591 def _mkdtemp(self, prefix = "tmp-"):
592 """Create a temporary directory.
594 This method may be used by subclasses to create a temporary directory
595 for use in building the final image - e.g. a subclass might create
596 a temporary directory in order to bundle a set of files into a package.
598 The subclass may delete this directory if it wishes, but it will be
599 automatically deleted by cleanup().
601 The absolute path to the temporary directory is returned.
603 Note, this method should only be called after mount() has been called.
605 prefix -- a prefix which should be used when creating the directory;
609 self.__ensure_builddir()
610 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
612 def _mkstemp(self, prefix = "tmp-"):
613 """Create a temporary file.
615 This method may be used by subclasses to create a temporary file
616 for use in building the final image - e.g. a subclass might need
617 a temporary location to unpack a compressed file.
619 The subclass may delete this file if it wishes, but it will be
620 automatically deleted by cleanup().
622 A tuple containing a file descriptor (returned from os.open() and the
623 absolute path to the temporary directory is returned.
625 Note, this method should only be called after mount() has been called.
627 prefix -- a prefix which should be used when creating the file;
631 self.__ensure_builddir()
632 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
634 def _mktemp(self, prefix = "tmp-"):
635 """Create a temporary file.
637 This method simply calls _mkstemp() and closes the returned file
640 The absolute path to the temporary file is returned.
642 Note, this method should only be called after mount() has been called.
644 prefix -- a prefix which should be used when creating the file;
649 (f, path) = self._mkstemp(prefix)
655 # Actual implementation
657 def __ensure_builddir(self):
658 if not self.__builddir is None:
662 self.workdir = os.path.join(self.tmpdir, "build")
663 if not os.path.exists(self.workdir):
664 os.makedirs(self.workdir)
665 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
666 prefix = "imgcreate-")
667 except OSError, (err, msg):
668 raise CreatorError("Failed create build directory in %s: %s" %
671 def get_cachedir(self, cachedir = None):
675 self.__ensure_builddir()
677 self.cachedir = cachedir
679 self.cachedir = self.__builddir + "/mic-cache"
680 fs.makedirs(self.cachedir)
683 def __sanity_check(self):
684 """Ensure that the config we've been given is sane."""
685 if not (kickstart.get_packages(self.ks) or
686 kickstart.get_groups(self.ks)):
687 raise CreatorError("No packages or groups specified")
689 kickstart.convert_method_to_repo(self.ks)
691 if not kickstart.get_repos(self.ks):
692 raise CreatorError("No repositories specified")
694 def __write_fstab(self):
695 if kickstart.use_installerfw(self.ks, "fstab"):
696 # The fstab file will be generated by installer framework scripts
699 fstab_contents = self._get_fstab()
701 fstab = open(self._instroot + "/etc/fstab", "w")
702 fstab.write(fstab_contents)
705 def __create_minimal_dev(self):
706 """Create a minimal /dev so that we don't corrupt the host /dev"""
707 origumask = os.umask(0000)
708 devices = (('null', 1, 3, 0666),
709 ('urandom',1, 9, 0666),
710 ('random', 1, 8, 0666),
711 ('full', 1, 7, 0666),
712 ('ptmx', 5, 2, 0666),
714 ('zero', 1, 5, 0666))
716 links = (("/proc/self/fd", "/dev/fd"),
717 ("/proc/self/fd/0", "/dev/stdin"),
718 ("/proc/self/fd/1", "/dev/stdout"),
719 ("/proc/self/fd/2", "/dev/stderr"))
721 for (node, major, minor, perm) in devices:
722 if not os.path.exists(self._instroot + "/dev/" + node):
723 os.mknod(self._instroot + "/dev/" + node,
725 os.makedev(major,minor))
727 for (src, dest) in links:
728 if not os.path.exists(self._instroot + dest):
729 os.symlink(src, self._instroot + dest)
733 def __setup_tmpdir(self):
734 if not self.enabletmpfs:
737 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
739 def __clean_tmpdir(self):
740 if not self.enabletmpfs:
743 runner.show('umount -l %s' % self.workdir)
745 def mount(self, base_on = None, cachedir = None):
746 """Setup the target filesystem in preparation for an install.
748 This function sets up the filesystem which the ImageCreator will
749 install into and configure. The ImageCreator class merely creates an
750 install root directory, bind mounts some system directories (e.g. /dev)
751 and writes out /etc/fstab. Other subclasses may also e.g. create a
752 sparse file, format it and loopback mount it to the install root.
754 base_on -- a previous install on which to base this install; defaults
755 to None, causing a new image to be created
757 cachedir -- a directory in which to store the Yum cache; defaults to
758 None, causing a new cache to be created; by setting this
759 to another directory, the same cache can be reused across
763 self.__setup_tmpdir()
764 self.__ensure_builddir()
766 # prevent popup dialog in Ubuntu(s)
767 misc.hide_loopdev_presentation()
769 fs.makedirs(self._instroot)
770 fs.makedirs(self._outdir)
772 self._mount_instroot(base_on)
774 for d in ("/dev/pts",
781 fs.makedirs(self._instroot + d)
783 if self.target_arch and self.target_arch.startswith("arm") or \
784 self.target_arch == "aarch64":
785 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
788 self.get_cachedir(cachedir)
790 # bind mount system directories into _instroot
791 for (f, dest) in [("/sys", None),
793 ("/proc/sys/fs/binfmt_misc", None),
795 self.__bindmounts.append(
797 f, self._instroot, dest))
799 self._do_bindmounts()
801 self.__create_minimal_dev()
803 if os.path.exists(self._instroot + "/etc/mtab"):
804 os.unlink(self._instroot + "/etc/mtab")
805 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
809 # get size of available space in 'instroot' fs
810 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
813 """Unmounts the target filesystem.
815 The ImageCreator class detaches the system from the install root, but
816 other subclasses may also detach the loopback mounted filesystem image
817 from the install root.
821 mtab = self._instroot + "/etc/mtab"
822 if not os.path.islink(mtab):
823 os.unlink(self._instroot + "/etc/mtab")
825 if self.qemu_emulator:
826 os.unlink(self._instroot + self.qemu_emulator)
830 self._undo_bindmounts()
832 """ Clean up yum garbage """
834 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
835 if os.path.exists(instroot_pdir):
836 shutil.rmtree(instroot_pdir, ignore_errors = True)
837 yumlibdir = self._instroot + "/var/lib/yum"
838 if os.path.exists(yumlibdir):
839 shutil.rmtree(yumlibdir, ignore_errors = True)
843 self._unmount_instroot()
845 # reset settings of popup dialog in Ubuntu(s)
846 misc.unhide_loopdev_presentation()
850 """Unmounts the target filesystem and deletes temporary files.
852 This method calls unmount() and then deletes any temporary files and
853 directories that were created on the host system while building the
856 Note, make sure to call this method once finished with the creator
857 instance in order to ensure no stale files are left on the host e.g.:
859 creator = ImageCreator(ks, name)
866 if not self.__builddir:
869 kill_proc_inchroot(self._instroot)
873 shutil.rmtree(self.__builddir, ignore_errors = True)
874 self.__builddir = None
876 self.__clean_tmpdir()
878 def __is_excluded_pkg(self, pkg):
879 if pkg in self._excluded_pkgs:
880 self._excluded_pkgs.remove(pkg)
883 for xpkg in self._excluded_pkgs:
884 if xpkg.endswith('*'):
885 if pkg.startswith(xpkg[:-1]):
887 elif xpkg.startswith('*'):
888 if pkg.endswith(xpkg[1:]):
893 def __select_packages(self, pkg_manager):
895 for pkg in self._required_pkgs:
896 e = pkg_manager.selectPackage(pkg)
898 if kickstart.ignore_missing(self.ks):
899 skipped_pkgs.append(pkg)
900 elif self.__is_excluded_pkg(pkg):
901 skipped_pkgs.append(pkg)
903 raise CreatorError("Failed to find package '%s' : %s" %
906 for pkg in skipped_pkgs:
907 msger.warning("Skipping missing package '%s'" % (pkg,))
909 def __select_groups(self, pkg_manager):
911 for group in self._required_groups:
912 e = pkg_manager.selectGroup(group.name, group.include)
914 if kickstart.ignore_missing(self.ks):
915 skipped_groups.append(group)
917 raise CreatorError("Failed to find group '%s' : %s" %
920 for group in skipped_groups:
921 msger.warning("Skipping missing group '%s'" % (group.name,))
923 def __deselect_packages(self, pkg_manager):
924 for pkg in self._excluded_pkgs:
925 pkg_manager.deselectPackage(pkg)
927 def __localinst_packages(self, pkg_manager):
928 for rpm_path in self._get_local_packages():
929 pkg_manager.installLocal(rpm_path)
931 def __preinstall_packages(self, pkg_manager):
935 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
936 for pkg in self._preinstall_pkgs:
937 pkg_manager.preInstall(pkg)
939 def __check_packages(self, pkg_manager):
940 for pkg in self.check_pkgs:
941 pkg_manager.checkPackage(pkg)
943 def __attachment_packages(self, pkg_manager):
947 self._attachment = []
948 for item in kickstart.get_attachment(self.ks):
949 if item.startswith('/'):
950 fpaths = os.path.join(self._instroot, item.lstrip('/'))
951 for fpath in glob.glob(fpaths):
952 self._attachment.append(fpath)
955 filelist = pkg_manager.getFilelist(item)
957 # found rpm in rootfs
958 for pfile in pkg_manager.getFilelist(item):
959 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
960 self._attachment.append(fpath)
963 # try to retrieve rpm file
964 (url, proxies) = pkg_manager.package_url(item)
966 msger.warning("Can't get url from repo for %s" % item)
968 fpath = os.path.join(self.cachedir, os.path.basename(url))
969 if not os.path.exists(fpath):
972 fpath = grabber.myurlgrab(url.full, fpath, proxies, None)
976 tmpdir = self._mkdtemp()
977 misc.extract_rpm(fpath, tmpdir)
978 for (root, dirs, files) in os.walk(tmpdir):
980 fpath = os.path.join(root, fname)
981 self._attachment.append(fpath)
983 def install(self, repo_urls=None):
984 """Install packages into the install root.
986 This function installs the packages listed in the supplied kickstart
987 into the install root. By default, the packages are installed from the
988 repository URLs specified in the kickstart.
990 repo_urls -- a dict which maps a repository name to a repository;
991 if supplied, this causes any repository URLs specified in
992 the kickstart to be overridden.
996 # initialize pkg list to install
998 self.__sanity_check()
1000 self._required_pkgs = \
1001 kickstart.get_packages(self.ks, self._get_required_packages())
1002 self._excluded_pkgs = \
1003 kickstart.get_excluded(self.ks, self._get_excluded_packages())
1004 self._required_groups = kickstart.get_groups(self.ks)
1006 self._required_pkgs = None
1007 self._excluded_pkgs = None
1008 self._required_groups = None
1011 repo_urls = self.extrarepos
1013 pkg_manager = self.get_pkg_manager()
1016 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1017 if 'debuginfo' in self.install_pkgs:
1018 pkg_manager.install_debuginfo = True
1020 for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1021 (name, baseurl, mirrorlist, inc, exc,
1022 proxy, proxy_username, proxy_password, debuginfo,
1023 source, gpgkey, disable, ssl_verify, nocache,
1024 cost, priority) = repo
1026 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1027 proxy_username, proxy_password, inc, exc, ssl_verify,
1028 nocache, cost, priority)
1030 if kickstart.exclude_docs(self.ks):
1031 rpm.addMacro("_excludedocs", "1")
1032 rpm.addMacro("_dbpath", "/var/lib/rpm")
1033 rpm.addMacro("__file_context_path", "%{nil}")
1034 if kickstart.inst_langs(self.ks) != None:
1035 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1038 self.__preinstall_packages(pkg_manager)
1039 self.__select_packages(pkg_manager)
1040 self.__select_groups(pkg_manager)
1041 self.__deselect_packages(pkg_manager)
1042 self.__localinst_packages(pkg_manager)
1043 self.__check_packages(pkg_manager)
1045 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1046 checksize = self._root_fs_avail
1048 checksize -= BOOT_SAFEGUARD
1049 if self.target_arch:
1050 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1051 pkg_manager.runInstall(checksize)
1052 except CreatorError, e:
1054 except KeyboardInterrupt:
1057 self._pkgs_content = pkg_manager.getAllContent()
1058 self._pkgs_license = pkg_manager.getPkgsLicense()
1059 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1060 self.__attachment_packages(pkg_manager)
1067 # do some clean up to avoid lvm info leakage. this sucks.
1068 for subdir in ("cache", "backup", "archive"):
1069 lvmdir = self._instroot + "/etc/lvm/" + subdir
1071 for f in os.listdir(lvmdir):
1072 os.unlink(lvmdir + "/" + f)
1076 def postinstall(self):
1077 self.copy_attachment()
1079 def __run_post_scripts(self):
1080 msger.info("Running scripts ...")
1081 if os.path.exists(self._instroot + "/tmp"):
1082 shutil.rmtree(self._instroot + "/tmp")
1083 os.mkdir (self._instroot + "/tmp", 0755)
1084 for s in kickstart.get_post_scripts(self.ks):
1085 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1086 dir = self._instroot + "/tmp")
1088 s.script = s.script.replace("\r", "")
1089 os.write(fd, s.script)
1091 os.chmod(path, 0700)
1093 env = self._get_post_scripts_env(s.inChroot)
1094 if 'PATH' not in env:
1095 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1101 preexec = self._chroot
1102 script = "/tmp/" + os.path.basename(path)
1106 p = subprocess.Popen([s.interp, script],
1107 preexec_fn = preexec,
1109 stdout = subprocess.PIPE,
1110 stderr = subprocess.STDOUT)
1111 for entry in p.communicate()[0].splitlines():
1113 except OSError, (err, msg):
1114 raise CreatorError("Failed to execute %%post script "
1115 "with '%s' : %s" % (s.interp, msg))
1119 def __save_repo_keys(self, repodata):
1123 gpgkeydir = "/etc/pki/rpm-gpg"
1124 fs.makedirs(self._instroot + gpgkeydir)
1125 for repo in repodata:
1127 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1128 shutil.copy(repo["repokey"], self._instroot + repokey)
1130 def configure(self, repodata = None):
1131 """Configure the system image according to the kickstart.
1133 This method applies the (e.g. keyboard or network) configuration
1134 specified in the kickstart and executes the kickstart %post scripts.
1136 If necessary, it also prepares the image to be bootable by e.g.
1137 creating an initrd and bootloader configuration.
1140 ksh = self.ks.handler
1142 msger.info('Applying configurations ...')
1144 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1145 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1146 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1147 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1148 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1149 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1150 kickstart.UserConfig(self._instroot).apply(ksh.user)
1151 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1152 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1153 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1154 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1155 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1156 self.__save_repo_keys(repodata)
1157 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1159 msger.warning("Failed to apply configuration to image")
1162 self._create_bootconfig()
1163 self.__run_post_scripts()
1165 def launch_shell(self, launch):
1166 """Launch a shell in the install root.
1168 This method is launches a bash shell chroot()ed in the install root;
1169 this can be useful for debugging.
1173 msger.info("Launching shell. Exit to continue.")
1174 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1176 def do_genchecksum(self, image_name):
1177 if not self._genchecksum:
1180 md5sum = misc.get_md5sum(image_name)
1181 with open(image_name + ".md5sum", "w") as f:
1182 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1183 self.outimage.append(image_name+".md5sum")
1185 def package(self, destdir = "."):
1186 """Prepares the created image for final delivery.
1188 In its simplest form, this method merely copies the install root to the
1189 supplied destination directory; other subclasses may choose to package
1190 the image by e.g. creating a bootable ISO containing the image and
1191 bootloader configuration.
1193 destdir -- the directory into which the final image should be moved;
1194 this defaults to the current directory.
1197 self._stage_final_image()
1199 if not os.path.exists(destdir):
1200 fs.makedirs(destdir)
1202 if self._recording_pkgs:
1203 self._save_recording_pkgs(destdir)
1205 # For image formats with two or multiple image files, it will be
1206 # better to put them under a directory
1207 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1208 destdir = os.path.join(destdir, "%s-%s" \
1209 % (self.name, self.image_format))
1210 msger.debug("creating destination dir: %s" % destdir)
1211 fs.makedirs(destdir)
1213 # Ensure all data is flushed to _outdir
1214 runner.quiet('sync')
1216 misc.check_space_pre_cp(self._outdir, destdir)
1217 for f in os.listdir(self._outdir):
1218 shutil.move(os.path.join(self._outdir, f),
1219 os.path.join(destdir, f))
1220 self.outimage.append(os.path.join(destdir, f))
1221 self.do_genchecksum(os.path.join(destdir, f))
1223 def print_outimage_info(self):
1224 msg = "The new image can be found here:\n"
1225 self.outimage.sort()
1226 for file in self.outimage:
1227 msg += ' %s\n' % os.path.abspath(file)
1231 def check_depend_tools(self):
1232 for tool in self._dep_checks:
1233 fs.find_binary_path(tool)
1235 def package_output(self, image_format, destdir = ".", package="none"):
1236 if not package or package == "none":
1239 destdir = os.path.abspath(os.path.expanduser(destdir))
1240 (pkg, comp) = os.path.splitext(package)
1242 comp=comp.lstrip(".")
1246 dst = "%s/%s-%s.tar.%s" %\
1247 (destdir, self.name, image_format, comp)
1249 dst = "%s/%s-%s.tar" %\
1250 (destdir, self.name, image_format)
1252 msger.info("creating %s" % dst)
1253 tar = tarfile.open(dst, "w:" + comp)
1255 for file in self.outimage:
1256 msger.info("adding %s to %s" % (file, dst))
1258 arcname=os.path.join("%s-%s" \
1259 % (self.name, image_format),
1260 os.path.basename(file)))
1261 if os.path.isdir(file):
1262 shutil.rmtree(file, ignore_errors = True)
1268 '''All the file in outimage has been packaged into tar.* file'''
1269 self.outimage = [dst]
1271 def release_output(self, config, destdir, release):
1272 """ Create release directory and files
1276 """ release path """
1277 return os.path.join(destdir, fn)
1279 outimages = self.outimage
1282 new_kspath = _rpath(self.name+'.ks')
1283 with open(config) as fr:
1284 with open(new_kspath, "w") as wf:
1285 # When building a release we want to make sure the .ks
1286 # file generates the same build even when --release not used.
1287 wf.write(fr.read().replace("@BUILD_ID@", release))
1288 outimages.append(new_kspath)
1290 # save log file, logfile is only available in creator attrs
1291 if hasattr(self, 'logfile') and not self.logfile:
1292 log_path = _rpath(self.name + ".log")
1293 # touch the log file, else outimages will filter it out
1294 with open(log_path, 'w') as wf:
1296 msger.set_logfile(log_path)
1297 outimages.append(_rpath(self.name + ".log"))
1299 # rename iso and usbimg
1300 for f in os.listdir(destdir):
1301 if f.endswith(".iso"):
1302 newf = f[:-4] + '.img'
1303 elif f.endswith(".usbimg"):
1304 newf = f[:-7] + '.img'
1307 os.rename(_rpath(f), _rpath(newf))
1308 outimages.append(_rpath(newf))
1311 with open(_rpath("MD5SUMS"), "w") as wf:
1312 for f in os.listdir(destdir):
1316 if os.path.isdir(os.path.join(destdir, f)):
1319 md5sum = misc.get_md5sum(_rpath(f))
1320 # There needs to be two spaces between the sum and
1321 # filepath to match the syntax with md5sum.
1322 # This way also md5sum -c MD5SUMS can be used by users
1323 wf.write("%s *%s\n" % (md5sum, f))
1325 outimages.append("%s/MD5SUMS" % destdir)
1327 # Filter out the nonexist file
1328 for fp in outimages[:]:
1329 if not os.path.exists("%s" % fp):
1330 outimages.remove(fp)
1332 def copy_kernel(self):
1333 """ Copy kernel files to the outimage directory.
1334 NOTE: This needs to be called before unmounting the instroot.
1337 if not self._need_copy_kernel:
1340 if not os.path.exists(self.destdir):
1341 os.makedirs(self.destdir)
1343 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1344 kernelfilename = "%s/%s-%s" % (self.destdir,
1346 os.path.basename(kernel))
1347 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1349 shutil.copy(kernel, kernelfilename)
1350 self.outimage.append(kernelfilename)
1352 def copy_attachment(self):
1353 """ Subclass implement it to handle attachment files
1354 NOTE: This needs to be called before unmounting the instroot.
1358 def get_pkg_manager(self):
1359 return self.pkgmgr(target_arch = self.target_arch,
1360 instroot = self._instroot,
1361 cachedir = self.cachedir)
1363 def create_manifest(self):
1364 def get_pack_suffix():
1365 return '.' + self.pack_to.split('.', 1)[1]
1367 if not os.path.exists(self.destdir):
1368 os.makedirs(self.destdir)
1370 now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1371 manifest_dict = {'version': VERSION,
1374 manifest_dict.update({'format': self.img_format})
1376 if hasattr(self, 'logfile') and self.logfile:
1377 manifest_dict.update({'log_file': self.logfile})
1379 if self.image_files:
1381 self.image_files.update({'pack': get_pack_suffix()})
1382 manifest_dict.update({self.img_format: self.image_files})
1384 msger.info('Creating manifest file...')
1385 manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1386 with open(manifest_file_path, 'w') as fest_file:
1387 json.dump(manifest_dict, fest_file, indent=4)
1388 self.outimage.append(manifest_file_path)