4 # Copyright (c) 2007 Red Hat Inc.
5 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
7 # This program is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by the Free
9 # Software Foundation; version 2 of the License
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc., 59
18 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 from __future__ import with_statement
32 from mic import kickstart
34 from mic.utils.errors import CreatorError, Abort
35 from mic.utils import misc, grabber, runner, fs_related as fs
37 class BaseImageCreator(object):
38 """Installs a system to a chroot directory.
40 ImageCreator is the simplest creator class available; it will install and
41 configure a system image according to the supplied kickstart file.
45 import mic.imgcreate as imgcreate
46 ks = imgcreate.read_kickstart("foo.ks")
47 imgcreate.ImageCreator(ks, "foo").create()
54 def __init__(self, createopts = None, pkgmgr = None):
55 """Initialize an ImageCreator instance.
57 ks -- a pykickstart.KickstartParser instance; this instance will be
58 used to drive the install by e.g. providing the list of packages
59 to be installed, the system configuration and %post scripts
61 name -- a name for the image; used for e.g. image filenames or
67 self.__builddir = None
68 self.__bindmounts = []
72 self.tmpdir = "/var/tmp/mic"
73 self.cachedir = "/var/tmp/mic/cache"
74 self.workdir = "/var/tmp/mic/build"
76 self.installerfw_prefix = "INSTALLERFW_"
77 self.target_arch = "noarch"
78 self._local_pkgs_path = None
82 # If the kernel is save to the destdir when copy_kernel cmd is called.
83 self._need_copy_kernel = False
84 # setup tmpfs tmpdir when enabletmpfs is True
85 self.enabletmpfs = False
88 # Mapping table for variables that have different names.
89 optmap = {"pkgmgr" : "pkgmgr_name",
91 "arch" : "target_arch",
92 "local_pkgs_path" : "_local_pkgs_path",
93 "copy_kernel" : "_need_copy_kernel",
96 # update setting from createopts
97 for key in createopts.keys():
102 setattr(self, option, createopts[key])
104 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
106 if 'release' in createopts and createopts['release']:
107 self.name = createopts['release'] + '_' + self.name
110 if '@NAME@' in self.pack_to:
111 self.pack_to = self.pack_to.replace('@NAME@', self.name)
112 (tar, ext) = os.path.splitext(self.pack_to)
113 if ext in (".gz", ".bz2") and tar.endswith(".tar"):
115 if ext not in misc.pack_formats:
116 self.pack_to += ".tar"
118 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
120 # Output image file names
123 # A flag to generate checksum
124 self._genchecksum = False
126 self._alt_initrd_name = None
128 self._recording_pkgs = []
130 # available size in root fs, init to 0
131 self._root_fs_avail = 0
133 # Name of the disk image file that is created.
134 self._img_name = None
136 self.image_format = None
138 # Save qemu emulator file name in order to clean up it finally
139 self.qemu_emulator = None
141 # No ks provided when called by convertor, so skip the dependency check
143 # If we have btrfs partition we need to check necessary tools
144 for part in self.ks.handler.partition.partitions:
145 if part.fstype and part.fstype == "btrfs":
146 self._dep_checks.append("mkfs.btrfs")
149 if self.target_arch and self.target_arch.startswith("arm"):
150 for dep in self._dep_checks:
151 if dep == "extlinux":
152 self._dep_checks.remove(dep)
154 if not os.path.exists("/usr/bin/qemu-arm") or \
155 not misc.is_statically_linked("/usr/bin/qemu-arm"):
156 self._dep_checks.append("qemu-arm-static")
158 if os.path.exists("/proc/sys/vm/vdso_enabled"):
159 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
160 vdso_value = vdso_fh.read().strip()
162 if (int)(vdso_value) == 1:
163 msger.warning("vdso is enabled on your host, which might "
164 "cause problems with arm emulations.\n"
165 "\tYou can disable vdso with following command before "
166 "starting image build:\n"
167 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
169 # make sure the specified tmpdir and cachedir exist
170 if not os.path.exists(self.tmpdir):
171 os.makedirs(self.tmpdir)
172 if not os.path.exists(self.cachedir):
173 os.makedirs(self.cachedir)
179 def __get_instroot(self):
180 if self.__builddir is None:
181 raise CreatorError("_instroot is not valid before calling mount()")
182 return self.__builddir + "/install_root"
183 _instroot = property(__get_instroot)
184 """The location of the install root directory.
186 This is the directory into which the system is installed. Subclasses may
187 mount a filesystem image here or copy files to/from here.
189 Note, this directory does not exist before ImageCreator.mount() is called.
191 Note also, this is a read-only attribute.
195 def __get_outdir(self):
196 if self.__builddir is None:
197 raise CreatorError("_outdir is not valid before calling mount()")
198 return self.__builddir + "/out"
199 _outdir = property(__get_outdir)
200 """The staging location for the final image.
202 This is where subclasses should stage any files that are part of the final
203 image. ImageCreator.package() will copy any files found here into the
204 requested destination directory.
206 Note, this directory does not exist before ImageCreator.mount() is called.
208 Note also, this is a read-only attribute.
214 # Hooks for subclasses
216 def _mount_instroot(self, base_on = None):
217 """Mount or prepare the install root directory.
219 This is the hook where subclasses may prepare the install root by e.g.
220 mounting creating and loopback mounting a filesystem image to
223 There is no default implementation.
225 base_on -- this is the value passed to mount() and can be interpreted
226 as the subclass wishes; it might e.g. be the location of
227 a previously created ISO containing a system image.
232 def _unmount_instroot(self):
233 """Undo anything performed in _mount_instroot().
235 This is the hook where subclasses must undo anything which was done
236 in _mount_instroot(). For example, if a filesystem image was mounted
237 onto _instroot, it should be unmounted here.
239 There is no default implementation.
244 def _create_bootconfig(self):
245 """Configure the image so that it's bootable.
247 This is the hook where subclasses may prepare the image for booting by
248 e.g. creating an initramfs and bootloader configuration.
250 This hook is called while the install root is still mounted, after the
251 packages have been installed and the kickstart configuration has been
252 applied, but before the %post scripts have been executed.
254 There is no default implementation.
259 def _stage_final_image(self):
260 """Stage the final system image in _outdir.
262 This is the hook where subclasses should place the image in _outdir
263 so that package() can copy it to the requested destination directory.
265 By default, this moves the install root into _outdir.
268 shutil.move(self._instroot, self._outdir + "/" + self.name)
270 def get_installed_packages(self):
271 return self._pkgs_content.keys()
273 def _save_recording_pkgs(self, destdir):
274 """Save the list or content of installed packages to file.
276 pkgs = self._pkgs_content.keys()
277 pkgs.sort() # inplace op
279 if not os.path.exists(destdir):
283 if 'vcs' in self._recording_pkgs:
284 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
285 content = '\n'.join(sorted(vcslst))
286 elif 'name' in self._recording_pkgs:
287 content = '\n'.join(pkgs)
289 namefile = os.path.join(destdir, self.name + '.packages')
290 f = open(namefile, "w")
293 self.outimage.append(namefile);
295 # if 'content', save more details
296 if 'content' in self._recording_pkgs:
297 contfile = os.path.join(destdir, self.name + '.files')
298 f = open(contfile, "w")
303 pkgcont = self._pkgs_content[pkg]
305 content += '\n '.join(pkgcont)
311 self.outimage.append(contfile)
313 if 'license' in self._recording_pkgs:
314 licensefile = os.path.join(destdir, self.name + '.license')
315 f = open(licensefile, "w")
317 f.write('Summary:\n')
318 for license in reversed(sorted(self._pkgs_license, key=\
319 lambda license: len(self._pkgs_license[license]))):
320 f.write(" - %s: %s\n" \
321 % (license, len(self._pkgs_license[license])))
323 f.write('\nDetails:\n')
324 for license in reversed(sorted(self._pkgs_license, key=\
325 lambda license: len(self._pkgs_license[license]))):
326 f.write(" - %s:\n" % (license))
327 for pkg in sorted(self._pkgs_license[license]):
328 f.write(" - %s\n" % (pkg))
332 self.outimage.append(licensefile)
334 def _get_required_packages(self):
335 """Return a list of required packages.
337 This is the hook where subclasses may specify a set of packages which
338 it requires to be installed.
340 This returns an empty list by default.
342 Note, subclasses should usually chain up to the base class
343 implementation of this hook.
348 def _get_excluded_packages(self):
349 """Return a list of excluded packages.
351 This is the hook where subclasses may specify a set of packages which
352 it requires _not_ to be installed.
354 This returns an empty list by default.
356 Note, subclasses should usually chain up to the base class
357 implementation of this hook.
362 def _get_local_packages(self):
363 """Return a list of rpm path to be local installed.
365 This is the hook where subclasses may specify a set of rpms which
366 it requires to be installed locally.
368 This returns an empty list by default.
370 Note, subclasses should usually chain up to the base class
371 implementation of this hook.
374 if self._local_pkgs_path:
375 if os.path.isdir(self._local_pkgs_path):
377 os.path.join(self._local_pkgs_path, '*.rpm'))
378 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
379 return [self._local_pkgs_path]
383 def _get_fstab(self):
384 """Return the desired contents of /etc/fstab.
386 This is the hook where subclasses may specify the contents of
387 /etc/fstab by returning a string containing the desired contents.
389 A sensible default implementation is provided.
392 s = "/dev/root / %s %s 0 0\n" \
394 "defaults,noatime" if not self._fsopts else self._fsopts)
395 s += self._get_fstab_special()
398 def _get_fstab_special(self):
399 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
400 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
401 s += "proc /proc proc defaults 0 0\n"
402 s += "sysfs /sys sysfs defaults 0 0\n"
405 def _set_part_env(self, pnum, prop, value):
406 """ This is a helper function which generates an environment variable
407 for a property "prop" with value "value" of a partition number "pnum".
409 The naming convention is:
410 * Variables start with INSTALLERFW_PART
411 * Then goes the partition number, the order is the same as
412 specified in the KS file
413 * Then goes the property name
421 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
422 return { name : value }
424 def _get_post_scripts_env(self, in_chroot):
425 """Return an environment dict for %post scripts.
427 This is the hook where subclasses may specify some environment
428 variables for %post scripts by return a dict containing the desired
431 in_chroot -- whether this %post script is to be executed chroot()ed
438 for p in kickstart.get_partitions(self.ks):
439 env.update(self._set_part_env(pnum, "SIZE", p.size))
440 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
441 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
442 env.update(self._set_part_env(pnum, "LABEL", p.label))
443 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
444 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
445 env.update(self._set_part_env(pnum, "ALIGN", p.align))
446 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
447 env.update(self._set_part_env(pnum, "DEVNODE",
448 "/dev/%s%d" % (p.disk, pnum + 1)))
452 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
454 # Partition table format
455 ptable_format = self.ks.handler.bootloader.ptable
456 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
458 # The kerned boot parameters
459 kernel_opts = self.ks.handler.bootloader.appendLine
460 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
462 # Name of the distribution
463 env[self.installerfw_prefix + "DISTRO_NAME"] = self.distro_name
465 # Name of the image creation tool
466 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
468 # The real current location of the mounted file-systems
472 mount_prefix = self._instroot
473 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
475 # These are historical variables which lack the common name prefix
477 env["INSTALL_ROOT"] = self._instroot
478 env["IMG_NAME"] = self._name
482 def __get_imgname(self):
484 _name = property(__get_imgname)
485 """The name of the image file.
489 def _get_kernel_versions(self):
490 """Return a dict detailing the available kernel types/versions.
492 This is the hook where subclasses may override what kernel types and
493 versions should be available for e.g. creating the booloader
496 A dict should be returned mapping the available kernel types to a list
497 of the available versions for those kernels.
499 The default implementation uses rpm to iterate over everything
500 providing 'kernel', finds /boot/vmlinuz-* and returns the version
501 obtained from the vmlinuz filename. (This can differ from the kernel
502 RPM's n-v-r in the case of e.g. xen)
505 def get_kernel_versions(instroot):
508 files = glob.glob(instroot + "/boot/vmlinuz-*")
510 version = os.path.basename(file)[8:]
513 versions.add(version)
514 ret["kernel"] = list(versions)
517 def get_version(header):
519 for f in header['filenames']:
520 if f.startswith('/boot/vmlinuz-'):
525 return get_kernel_versions(self._instroot)
527 ts = rpm.TransactionSet(self._instroot)
530 for header in ts.dbMatch('provides', 'kernel'):
531 version = get_version(header)
535 name = header['name']
537 ret[name] = [version]
538 elif not version in ret[name]:
539 ret[name].append(version)
545 # Helpers for subclasses
547 def _do_bindmounts(self):
548 """Mount various system directories onto _instroot.
550 This method is called by mount(), but may also be used by subclasses
551 in order to re-mount the bindmounts after modifying the underlying
555 for b in self.__bindmounts:
558 def _undo_bindmounts(self):
559 """Unmount the bind-mounted system directories from _instroot.
561 This method is usually only called by unmount(), but may also be used
562 by subclasses in order to gain access to the filesystem obscured by
563 the bindmounts - e.g. in order to create device nodes on the image
567 self.__bindmounts.reverse()
568 for b in self.__bindmounts:
572 """Chroot into the install root.
574 This method may be used by subclasses when executing programs inside
575 the install root e.g.
577 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
580 os.chroot(self._instroot)
583 def _mkdtemp(self, prefix = "tmp-"):
584 """Create a temporary directory.
586 This method may be used by subclasses to create a temporary directory
587 for use in building the final image - e.g. a subclass might create
588 a temporary directory in order to bundle a set of files into a package.
590 The subclass may delete this directory if it wishes, but it will be
591 automatically deleted by cleanup().
593 The absolute path to the temporary directory is returned.
595 Note, this method should only be called after mount() has been called.
597 prefix -- a prefix which should be used when creating the directory;
601 self.__ensure_builddir()
602 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
604 def _mkstemp(self, prefix = "tmp-"):
605 """Create a temporary file.
607 This method may be used by subclasses to create a temporary file
608 for use in building the final image - e.g. a subclass might need
609 a temporary location to unpack a compressed file.
611 The subclass may delete this file if it wishes, but it will be
612 automatically deleted by cleanup().
614 A tuple containing a file descriptor (returned from os.open() and the
615 absolute path to the temporary directory is returned.
617 Note, this method should only be called after mount() has been called.
619 prefix -- a prefix which should be used when creating the file;
623 self.__ensure_builddir()
624 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
626 def _mktemp(self, prefix = "tmp-"):
627 """Create a temporary file.
629 This method simply calls _mkstemp() and closes the returned file
632 The absolute path to the temporary file is returned.
634 Note, this method should only be called after mount() has been called.
636 prefix -- a prefix which should be used when creating the file;
641 (f, path) = self._mkstemp(prefix)
647 # Actual implementation
649 def __ensure_builddir(self):
650 if not self.__builddir is None:
654 self.workdir = os.path.join(self.tmpdir, "build")
655 if not os.path.exists(self.workdir):
656 os.makedirs(self.workdir)
657 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
658 prefix = "imgcreate-")
659 except OSError, (err, msg):
660 raise CreatorError("Failed create build directory in %s: %s" %
663 def get_cachedir(self, cachedir = None):
667 self.__ensure_builddir()
669 self.cachedir = cachedir
671 self.cachedir = self.__builddir + "/mic-cache"
672 fs.makedirs(self.cachedir)
675 def __sanity_check(self):
676 """Ensure that the config we've been given is sane."""
677 if not (kickstart.get_packages(self.ks) or
678 kickstart.get_groups(self.ks)):
679 raise CreatorError("No packages or groups specified")
681 kickstart.convert_method_to_repo(self.ks)
683 if not kickstart.get_repos(self.ks):
684 raise CreatorError("No repositories specified")
686 def __write_fstab(self):
687 fstab_contents = self._get_fstab()
689 fstab = open(self._instroot + "/etc/fstab", "w")
690 fstab.write(fstab_contents)
693 def __create_minimal_dev(self):
694 """Create a minimal /dev so that we don't corrupt the host /dev"""
695 origumask = os.umask(0000)
696 devices = (('null', 1, 3, 0666),
697 ('urandom',1, 9, 0666),
698 ('random', 1, 8, 0666),
699 ('full', 1, 7, 0666),
700 ('ptmx', 5, 2, 0666),
702 ('zero', 1, 5, 0666))
704 links = (("/proc/self/fd", "/dev/fd"),
705 ("/proc/self/fd/0", "/dev/stdin"),
706 ("/proc/self/fd/1", "/dev/stdout"),
707 ("/proc/self/fd/2", "/dev/stderr"))
709 for (node, major, minor, perm) in devices:
710 if not os.path.exists(self._instroot + "/dev/" + node):
711 os.mknod(self._instroot + "/dev/" + node,
713 os.makedev(major,minor))
715 for (src, dest) in links:
716 if not os.path.exists(self._instroot + dest):
717 os.symlink(src, self._instroot + dest)
721 def __setup_tmpdir(self):
722 if not self.enabletmpfs:
725 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
727 def __clean_tmpdir(self):
728 if not self.enabletmpfs:
731 runner.show('umount -l %s' % self.workdir)
733 def mount(self, base_on = None, cachedir = None):
734 """Setup the target filesystem in preparation for an install.
736 This function sets up the filesystem which the ImageCreator will
737 install into and configure. The ImageCreator class merely creates an
738 install root directory, bind mounts some system directories (e.g. /dev)
739 and writes out /etc/fstab. Other subclasses may also e.g. create a
740 sparse file, format it and loopback mount it to the install root.
742 base_on -- a previous install on which to base this install; defaults
743 to None, causing a new image to be created
745 cachedir -- a directory in which to store the Yum cache; defaults to
746 None, causing a new cache to be created; by setting this
747 to another directory, the same cache can be reused across
751 self.__setup_tmpdir()
752 self.__ensure_builddir()
754 # prevent popup dialog in Ubuntu(s)
755 misc.hide_loopdev_presentation()
757 fs.makedirs(self._instroot)
758 fs.makedirs(self._outdir)
760 self._mount_instroot(base_on)
762 for d in ("/dev/pts",
769 fs.makedirs(self._instroot + d)
771 if self.target_arch and self.target_arch.startswith("arm"):
772 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
776 self.get_cachedir(cachedir)
778 # bind mount system directories into _instroot
779 for (f, dest) in [("/sys", None),
781 ("/proc/sys/fs/binfmt_misc", None),
783 self.__bindmounts.append(
785 f, self._instroot, dest))
787 self._do_bindmounts()
789 self.__create_minimal_dev()
791 if os.path.exists(self._instroot + "/etc/mtab"):
792 os.unlink(self._instroot + "/etc/mtab")
793 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
797 # get size of available space in 'instroot' fs
798 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
801 """Unmounts the target filesystem.
803 The ImageCreator class detaches the system from the install root, but
804 other subclasses may also detach the loopback mounted filesystem image
805 from the install root.
809 mtab = self._instroot + "/etc/mtab"
810 if not os.path.islink(mtab):
811 os.unlink(self._instroot + "/etc/mtab")
813 if self.qemu_emulator:
814 os.unlink(self._instroot + self.qemu_emulator)
818 self._undo_bindmounts()
820 """ Clean up yum garbage """
822 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
823 if os.path.exists(instroot_pdir):
824 shutil.rmtree(instroot_pdir, ignore_errors = True)
825 yumlibdir = self._instroot + "/var/lib/yum"
826 if os.path.exists(yumlibdir):
827 shutil.rmtree(yumlibdir, ignore_errors = True)
831 self._unmount_instroot()
833 # reset settings of popup dialog in Ubuntu(s)
834 misc.unhide_loopdev_presentation()
838 """Unmounts the target filesystem and deletes temporary files.
840 This method calls unmount() and then deletes any temporary files and
841 directories that were created on the host system while building the
844 Note, make sure to call this method once finished with the creator
845 instance in order to ensure no stale files are left on the host e.g.:
847 creator = ImageCreator(ks, name)
854 if not self.__builddir:
859 shutil.rmtree(self.__builddir, ignore_errors = True)
860 self.__builddir = None
862 self.__clean_tmpdir()
864 def __is_excluded_pkg(self, pkg):
865 if pkg in self._excluded_pkgs:
866 self._excluded_pkgs.remove(pkg)
869 for xpkg in self._excluded_pkgs:
870 if xpkg.endswith('*'):
871 if pkg.startswith(xpkg[:-1]):
873 elif xpkg.startswith('*'):
874 if pkg.endswith(xpkg[1:]):
879 def __select_packages(self, pkg_manager):
881 for pkg in self._required_pkgs:
882 e = pkg_manager.selectPackage(pkg)
884 if kickstart.ignore_missing(self.ks):
885 skipped_pkgs.append(pkg)
886 elif self.__is_excluded_pkg(pkg):
887 skipped_pkgs.append(pkg)
889 raise CreatorError("Failed to find package '%s' : %s" %
892 for pkg in skipped_pkgs:
893 msger.warning("Skipping missing package '%s'" % (pkg,))
895 def __select_groups(self, pkg_manager):
897 for group in self._required_groups:
898 e = pkg_manager.selectGroup(group.name, group.include)
900 if kickstart.ignore_missing(self.ks):
901 skipped_groups.append(group)
903 raise CreatorError("Failed to find group '%s' : %s" %
906 for group in skipped_groups:
907 msger.warning("Skipping missing group '%s'" % (group.name,))
909 def __deselect_packages(self, pkg_manager):
910 for pkg in self._excluded_pkgs:
911 pkg_manager.deselectPackage(pkg)
913 def __localinst_packages(self, pkg_manager):
914 for rpm_path in self._get_local_packages():
915 pkg_manager.installLocal(rpm_path)
917 def __preinstall_packages(self, pkg_manager):
921 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
922 for pkg in self._preinstall_pkgs:
923 pkg_manager.preInstall(pkg)
925 def __attachment_packages(self, pkg_manager):
929 self._attachment = []
930 for item in kickstart.get_attachment(self.ks):
931 if item.startswith('/'):
932 fpaths = os.path.join(self._instroot, item.lstrip('/'))
933 for fpath in glob.glob(fpaths):
934 self._attachment.append(fpath)
937 filelist = pkg_manager.getFilelist(item)
939 # found rpm in rootfs
940 for pfile in pkg_manager.getFilelist(item):
941 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
942 self._attachment.append(fpath)
945 # try to retrieve rpm file
946 (url, proxies) = pkg_manager.package_url(item)
948 msger.warning("Can't get url from repo for %s" % item)
950 fpath = os.path.join(self.cachedir, os.path.basename(url))
951 if not os.path.exists(fpath):
954 fpath = grabber.myurlgrab(url, fpath, proxies, None)
958 tmpdir = self._mkdtemp()
959 misc.extract_rpm(fpath, tmpdir)
960 for (root, dirs, files) in os.walk(tmpdir):
962 fpath = os.path.join(root, fname)
963 self._attachment.append(fpath)
965 def install(self, repo_urls=None):
966 """Install packages into the install root.
968 This function installs the packages listed in the supplied kickstart
969 into the install root. By default, the packages are installed from the
970 repository URLs specified in the kickstart.
972 repo_urls -- a dict which maps a repository name to a repository URL;
973 if supplied, this causes any repository URLs specified in
974 the kickstart to be overridden.
978 # initialize pkg list to install
980 self.__sanity_check()
982 self._required_pkgs = \
983 kickstart.get_packages(self.ks, self._get_required_packages())
984 self._excluded_pkgs = \
985 kickstart.get_excluded(self.ks, self._get_excluded_packages())
986 self._required_groups = kickstart.get_groups(self.ks)
988 self._required_pkgs = None
989 self._excluded_pkgs = None
990 self._required_groups = None
992 pkg_manager = self.get_pkg_manager()
995 if hasattr(self, 'install_pkgs') and self.install_pkgs:
996 if 'debuginfo' in self.install_pkgs:
997 pkg_manager.install_debuginfo = True
999 for repo in kickstart.get_repos(self.ks, repo_urls):
1000 (name, baseurl, mirrorlist, inc, exc,
1001 proxy, proxy_username, proxy_password, debuginfo,
1002 source, gpgkey, disable, ssl_verify, nocache,
1003 cost, priority) = repo
1005 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1006 proxy_username, proxy_password, inc, exc, ssl_verify,
1007 nocache, cost, priority)
1009 if kickstart.exclude_docs(self.ks):
1010 rpm.addMacro("_excludedocs", "1")
1011 rpm.addMacro("_dbpath", "/var/lib/rpm")
1012 rpm.addMacro("__file_context_path", "%{nil}")
1013 if kickstart.inst_langs(self.ks) != None:
1014 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1017 self.__preinstall_packages(pkg_manager)
1018 self.__select_packages(pkg_manager)
1019 self.__select_groups(pkg_manager)
1020 self.__deselect_packages(pkg_manager)
1021 self.__localinst_packages(pkg_manager)
1023 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1024 checksize = self._root_fs_avail
1026 checksize -= BOOT_SAFEGUARD
1027 if self.target_arch:
1028 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1029 pkg_manager.runInstall(checksize)
1030 except CreatorError, e:
1032 except KeyboardInterrupt:
1035 self._pkgs_content = pkg_manager.getAllContent()
1036 self._pkgs_license = pkg_manager.getPkgsLicense()
1037 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1038 self.__attachment_packages(pkg_manager)
1045 # do some clean up to avoid lvm info leakage. this sucks.
1046 for subdir in ("cache", "backup", "archive"):
1047 lvmdir = self._instroot + "/etc/lvm/" + subdir
1049 for f in os.listdir(lvmdir):
1050 os.unlink(lvmdir + "/" + f)
1054 def postinstall(self):
1055 self.copy_attachment()
1057 def __run_post_scripts(self):
1058 msger.info("Running scripts ...")
1059 if os.path.exists(self._instroot + "/tmp"):
1060 shutil.rmtree(self._instroot + "/tmp")
1061 os.mkdir (self._instroot + "/tmp", 0755)
1062 for s in kickstart.get_post_scripts(self.ks):
1063 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1064 dir = self._instroot + "/tmp")
1066 s.script = s.script.replace("\r", "")
1067 os.write(fd, s.script)
1069 os.chmod(path, 0700)
1071 env = self._get_post_scripts_env(s.inChroot)
1077 preexec = self._chroot
1078 script = "/tmp/" + os.path.basename(path)
1082 p = subprocess.Popen([s.interp, script],
1083 preexec_fn = preexec,
1085 stdout = subprocess.PIPE,
1086 stderr = subprocess.STDOUT)
1087 for entry in p.communicate()[0].splitlines():
1089 except OSError, (err, msg):
1090 raise CreatorError("Failed to execute %%post script "
1091 "with '%s' : %s" % (s.interp, msg))
1095 def __save_repo_keys(self, repodata):
1099 gpgkeydir = "/etc/pki/rpm-gpg"
1100 fs.makedirs(self._instroot + gpgkeydir)
1101 for repo in repodata:
1103 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1104 shutil.copy(repo["repokey"], self._instroot + repokey)
1106 def configure(self, repodata = None):
1107 """Configure the system image according to the kickstart.
1109 This method applies the (e.g. keyboard or network) configuration
1110 specified in the kickstart and executes the kickstart %post scripts.
1112 If necessary, it also prepares the image to be bootable by e.g.
1113 creating an initrd and bootloader configuration.
1116 ksh = self.ks.handler
1118 msger.info('Applying configurations ...')
1120 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1121 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1122 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1123 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1124 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1125 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1126 kickstart.UserConfig(self._instroot).apply(ksh.user)
1127 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1128 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1129 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1130 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1131 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1132 self.__save_repo_keys(repodata)
1133 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1135 msger.warning("Failed to apply configuration to image")
1138 self._create_bootconfig()
1139 self.__run_post_scripts()
1141 def launch_shell(self, launch):
1142 """Launch a shell in the install root.
1144 This method is launches a bash shell chroot()ed in the install root;
1145 this can be useful for debugging.
1149 msger.info("Launching shell. Exit to continue.")
1150 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1152 def do_genchecksum(self, image_name):
1153 if not self._genchecksum:
1156 md5sum = misc.get_md5sum(image_name)
1157 with open(image_name + ".md5sum", "w") as f:
1158 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1159 self.outimage.append(image_name+".md5sum")
1161 def package(self, destdir = "."):
1162 """Prepares the created image for final delivery.
1164 In its simplest form, this method merely copies the install root to the
1165 supplied destination directory; other subclasses may choose to package
1166 the image by e.g. creating a bootable ISO containing the image and
1167 bootloader configuration.
1169 destdir -- the directory into which the final image should be moved;
1170 this defaults to the current directory.
1173 self._stage_final_image()
1175 if not os.path.exists(destdir):
1176 fs.makedirs(destdir)
1178 if self._recording_pkgs:
1179 self._save_recording_pkgs(destdir)
1181 # For image formats with two or multiple image files, it will be
1182 # better to put them under a directory
1183 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1184 destdir = os.path.join(destdir, "%s-%s" \
1185 % (self.name, self.image_format))
1186 msger.debug("creating destination dir: %s" % destdir)
1187 fs.makedirs(destdir)
1189 # Ensure all data is flushed to _outdir
1190 runner.quiet('sync')
1192 misc.check_space_pre_cp(self._outdir, destdir)
1193 for f in os.listdir(self._outdir):
1194 shutil.move(os.path.join(self._outdir, f),
1195 os.path.join(destdir, f))
1196 self.outimage.append(os.path.join(destdir, f))
1197 self.do_genchecksum(os.path.join(destdir, f))
1199 def print_outimage_info(self):
1200 msg = "The new image can be found here:\n"
1201 self.outimage.sort()
1202 for file in self.outimage:
1203 msg += ' %s\n' % os.path.abspath(file)
1207 def check_depend_tools(self):
1208 for tool in self._dep_checks:
1209 fs.find_binary_path(tool)
1211 def package_output(self, image_format, destdir = ".", package="none"):
1212 if not package or package == "none":
1215 destdir = os.path.abspath(os.path.expanduser(destdir))
1216 (pkg, comp) = os.path.splitext(package)
1218 comp=comp.lstrip(".")
1222 dst = "%s/%s-%s.tar.%s" %\
1223 (destdir, self.name, image_format, comp)
1225 dst = "%s/%s-%s.tar" %\
1226 (destdir, self.name, image_format)
1228 msger.info("creating %s" % dst)
1229 tar = tarfile.open(dst, "w:" + comp)
1231 for file in self.outimage:
1232 msger.info("adding %s to %s" % (file, dst))
1234 arcname=os.path.join("%s-%s" \
1235 % (self.name, image_format),
1236 os.path.basename(file)))
1237 if os.path.isdir(file):
1238 shutil.rmtree(file, ignore_errors = True)
1244 '''All the file in outimage has been packaged into tar.* file'''
1245 self.outimage = [dst]
1247 def release_output(self, config, destdir, release):
1248 """ Create release directory and files
1252 """ release path """
1253 return os.path.join(destdir, fn)
1255 outimages = self.outimage
1258 new_kspath = _rpath(self.name+'.ks')
1259 with open(config) as fr:
1260 with open(new_kspath, "w") as wf:
1261 # When building a release we want to make sure the .ks
1262 # file generates the same build even when --release not used.
1263 wf.write(fr.read().replace("@BUILD_ID@", release))
1264 outimages.append(new_kspath)
1266 # save log file, logfile is only available in creator attrs
1267 if hasattr(self, 'logfile') and not self.logfile:
1268 log_path = _rpath(self.name + ".log")
1269 # touch the log file, else outimages will filter it out
1270 with open(log_path, 'w') as wf:
1272 msger.set_logfile(log_path)
1273 outimages.append(_rpath(self.name + ".log"))
1275 # rename iso and usbimg
1276 for f in os.listdir(destdir):
1277 if f.endswith(".iso"):
1278 newf = f[:-4] + '.img'
1279 elif f.endswith(".usbimg"):
1280 newf = f[:-7] + '.img'
1283 os.rename(_rpath(f), _rpath(newf))
1284 outimages.append(_rpath(newf))
1287 with open(_rpath("MD5SUMS"), "w") as wf:
1288 for f in os.listdir(destdir):
1292 if os.path.isdir(os.path.join(destdir, f)):
1295 md5sum = misc.get_md5sum(_rpath(f))
1296 # There needs to be two spaces between the sum and
1297 # filepath to match the syntax with md5sum.
1298 # This way also md5sum -c MD5SUMS can be used by users
1299 wf.write("%s *%s\n" % (md5sum, f))
1301 outimages.append("%s/MD5SUMS" % destdir)
1303 # Filter out the nonexist file
1304 for fp in outimages[:]:
1305 if not os.path.exists("%s" % fp):
1306 outimages.remove(fp)
1308 def copy_kernel(self):
1309 """ Copy kernel files to the outimage directory.
1310 NOTE: This needs to be called before unmounting the instroot.
1313 if not self._need_copy_kernel:
1316 if not os.path.exists(self.destdir):
1317 os.makedirs(self.destdir)
1319 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1320 kernelfilename = "%s/%s-%s" % (self.destdir,
1322 os.path.basename(kernel))
1323 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1325 shutil.copy(kernel, kernelfilename)
1326 self.outimage.append(kernelfilename)
1328 def copy_attachment(self):
1329 """ Subclass implement it to handle attachment files
1330 NOTE: This needs to be called before unmounting the instroot.
1334 def get_pkg_manager(self):
1335 return self.pkgmgr(target_arch = self.target_arch,
1336 instroot = self._instroot,
1337 cachedir = self.cachedir)