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))
450 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
452 # Partition table format
453 ptable_format = self.ks.handler.bootloader.ptable
454 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
456 # The kerned boot parameters
457 kernel_opts = self.ks.handler.bootloader.appendLine
458 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
460 # Name of the distribution
461 env[self.installerfw_prefix + "DISTRO_NAME"] = self.distro_name
463 # Name of the image creation tool
464 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
466 # These are historical variables which lack the common name prefix
468 env["INSTALL_ROOT"] = self._instroot
469 env["IMG_NAME"] = self._name
473 def __get_imgname(self):
475 _name = property(__get_imgname)
476 """The name of the image file.
480 def _get_kernel_versions(self):
481 """Return a dict detailing the available kernel types/versions.
483 This is the hook where subclasses may override what kernel types and
484 versions should be available for e.g. creating the booloader
487 A dict should be returned mapping the available kernel types to a list
488 of the available versions for those kernels.
490 The default implementation uses rpm to iterate over everything
491 providing 'kernel', finds /boot/vmlinuz-* and returns the version
492 obtained from the vmlinuz filename. (This can differ from the kernel
493 RPM's n-v-r in the case of e.g. xen)
496 def get_kernel_versions(instroot):
499 files = glob.glob(instroot + "/boot/vmlinuz-*")
501 version = os.path.basename(file)[8:]
504 versions.add(version)
505 ret["kernel"] = list(versions)
508 def get_version(header):
510 for f in header['filenames']:
511 if f.startswith('/boot/vmlinuz-'):
516 return get_kernel_versions(self._instroot)
518 ts = rpm.TransactionSet(self._instroot)
521 for header in ts.dbMatch('provides', 'kernel'):
522 version = get_version(header)
526 name = header['name']
528 ret[name] = [version]
529 elif not version in ret[name]:
530 ret[name].append(version)
536 # Helpers for subclasses
538 def _do_bindmounts(self):
539 """Mount various system directories onto _instroot.
541 This method is called by mount(), but may also be used by subclasses
542 in order to re-mount the bindmounts after modifying the underlying
546 for b in self.__bindmounts:
549 def _undo_bindmounts(self):
550 """Unmount the bind-mounted system directories from _instroot.
552 This method is usually only called by unmount(), but may also be used
553 by subclasses in order to gain access to the filesystem obscured by
554 the bindmounts - e.g. in order to create device nodes on the image
558 self.__bindmounts.reverse()
559 for b in self.__bindmounts:
563 """Chroot into the install root.
565 This method may be used by subclasses when executing programs inside
566 the install root e.g.
568 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
571 os.chroot(self._instroot)
574 def _mkdtemp(self, prefix = "tmp-"):
575 """Create a temporary directory.
577 This method may be used by subclasses to create a temporary directory
578 for use in building the final image - e.g. a subclass might create
579 a temporary directory in order to bundle a set of files into a package.
581 The subclass may delete this directory if it wishes, but it will be
582 automatically deleted by cleanup().
584 The absolute path to the temporary directory is returned.
586 Note, this method should only be called after mount() has been called.
588 prefix -- a prefix which should be used when creating the directory;
592 self.__ensure_builddir()
593 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
595 def _mkstemp(self, prefix = "tmp-"):
596 """Create a temporary file.
598 This method may be used by subclasses to create a temporary file
599 for use in building the final image - e.g. a subclass might need
600 a temporary location to unpack a compressed file.
602 The subclass may delete this file if it wishes, but it will be
603 automatically deleted by cleanup().
605 A tuple containing a file descriptor (returned from os.open() and the
606 absolute path to the temporary directory is returned.
608 Note, this method should only be called after mount() has been called.
610 prefix -- a prefix which should be used when creating the file;
614 self.__ensure_builddir()
615 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
617 def _mktemp(self, prefix = "tmp-"):
618 """Create a temporary file.
620 This method simply calls _mkstemp() and closes the returned file
623 The absolute path to the temporary file 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;
632 (f, path) = self._mkstemp(prefix)
638 # Actual implementation
640 def __ensure_builddir(self):
641 if not self.__builddir is None:
645 self.workdir = os.path.join(self.tmpdir, "build")
646 if not os.path.exists(self.workdir):
647 os.makedirs(self.workdir)
648 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
649 prefix = "imgcreate-")
650 except OSError, (err, msg):
651 raise CreatorError("Failed create build directory in %s: %s" %
654 def get_cachedir(self, cachedir = None):
658 self.__ensure_builddir()
660 self.cachedir = cachedir
662 self.cachedir = self.__builddir + "/mic-cache"
663 fs.makedirs(self.cachedir)
666 def __sanity_check(self):
667 """Ensure that the config we've been given is sane."""
668 if not (kickstart.get_packages(self.ks) or
669 kickstart.get_groups(self.ks)):
670 raise CreatorError("No packages or groups specified")
672 kickstart.convert_method_to_repo(self.ks)
674 if not kickstart.get_repos(self.ks):
675 raise CreatorError("No repositories specified")
677 def __write_fstab(self):
678 fstab = open(self._instroot + "/etc/fstab", "w")
679 fstab.write(self._get_fstab())
682 def __create_minimal_dev(self):
683 """Create a minimal /dev so that we don't corrupt the host /dev"""
684 origumask = os.umask(0000)
685 devices = (('null', 1, 3, 0666),
686 ('urandom',1, 9, 0666),
687 ('random', 1, 8, 0666),
688 ('full', 1, 7, 0666),
689 ('ptmx', 5, 2, 0666),
691 ('zero', 1, 5, 0666))
693 links = (("/proc/self/fd", "/dev/fd"),
694 ("/proc/self/fd/0", "/dev/stdin"),
695 ("/proc/self/fd/1", "/dev/stdout"),
696 ("/proc/self/fd/2", "/dev/stderr"))
698 for (node, major, minor, perm) in devices:
699 if not os.path.exists(self._instroot + "/dev/" + node):
700 os.mknod(self._instroot + "/dev/" + node,
702 os.makedev(major,minor))
704 for (src, dest) in links:
705 if not os.path.exists(self._instroot + dest):
706 os.symlink(src, self._instroot + dest)
710 def __setup_tmpdir(self):
711 if not self.enabletmpfs:
714 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
716 def __clean_tmpdir(self):
717 if not self.enabletmpfs:
720 runner.show('umount -l %s' % self.workdir)
722 def mount(self, base_on = None, cachedir = None):
723 """Setup the target filesystem in preparation for an install.
725 This function sets up the filesystem which the ImageCreator will
726 install into and configure. The ImageCreator class merely creates an
727 install root directory, bind mounts some system directories (e.g. /dev)
728 and writes out /etc/fstab. Other subclasses may also e.g. create a
729 sparse file, format it and loopback mount it to the install root.
731 base_on -- a previous install on which to base this install; defaults
732 to None, causing a new image to be created
734 cachedir -- a directory in which to store the Yum cache; defaults to
735 None, causing a new cache to be created; by setting this
736 to another directory, the same cache can be reused across
740 self.__setup_tmpdir()
741 self.__ensure_builddir()
743 # prevent popup dialog in Ubuntu(s)
744 misc.hide_loopdev_presentation()
746 fs.makedirs(self._instroot)
747 fs.makedirs(self._outdir)
749 self._mount_instroot(base_on)
751 for d in ("/dev/pts",
758 fs.makedirs(self._instroot + d)
760 if self.target_arch and self.target_arch.startswith("arm"):
761 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
765 self.get_cachedir(cachedir)
767 # bind mount system directories into _instroot
768 for (f, dest) in [("/sys", None),
770 ("/proc/sys/fs/binfmt_misc", None),
772 self.__bindmounts.append(
774 f, self._instroot, dest))
776 self._do_bindmounts()
778 self.__create_minimal_dev()
780 if os.path.exists(self._instroot + "/etc/mtab"):
781 os.unlink(self._instroot + "/etc/mtab")
782 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
786 # get size of available space in 'instroot' fs
787 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
790 """Unmounts the target filesystem.
792 The ImageCreator class detaches the system from the install root, but
793 other subclasses may also detach the loopback mounted filesystem image
794 from the install root.
798 mtab = self._instroot + "/etc/mtab"
799 if not os.path.islink(mtab):
800 os.unlink(self._instroot + "/etc/mtab")
802 if self.qemu_emulator:
803 os.unlink(self._instroot + self.qemu_emulator)
807 self._undo_bindmounts()
809 """ Clean up yum garbage """
811 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
812 if os.path.exists(instroot_pdir):
813 shutil.rmtree(instroot_pdir, ignore_errors = True)
814 yumlibdir = self._instroot + "/var/lib/yum"
815 if os.path.exists(yumlibdir):
816 shutil.rmtree(yumlibdir, ignore_errors = True)
820 self._unmount_instroot()
822 # reset settings of popup dialog in Ubuntu(s)
823 misc.unhide_loopdev_presentation()
827 """Unmounts the target filesystem and deletes temporary files.
829 This method calls unmount() and then deletes any temporary files and
830 directories that were created on the host system while building the
833 Note, make sure to call this method once finished with the creator
834 instance in order to ensure no stale files are left on the host e.g.:
836 creator = ImageCreator(ks, name)
843 if not self.__builddir:
848 shutil.rmtree(self.__builddir, ignore_errors = True)
849 self.__builddir = None
851 self.__clean_tmpdir()
853 def __is_excluded_pkg(self, pkg):
854 if pkg in self._excluded_pkgs:
855 self._excluded_pkgs.remove(pkg)
858 for xpkg in self._excluded_pkgs:
859 if xpkg.endswith('*'):
860 if pkg.startswith(xpkg[:-1]):
862 elif xpkg.startswith('*'):
863 if pkg.endswith(xpkg[1:]):
868 def __select_packages(self, pkg_manager):
870 for pkg in self._required_pkgs:
871 e = pkg_manager.selectPackage(pkg)
873 if kickstart.ignore_missing(self.ks):
874 skipped_pkgs.append(pkg)
875 elif self.__is_excluded_pkg(pkg):
876 skipped_pkgs.append(pkg)
878 raise CreatorError("Failed to find package '%s' : %s" %
881 for pkg in skipped_pkgs:
882 msger.warning("Skipping missing package '%s'" % (pkg,))
884 def __select_groups(self, pkg_manager):
886 for group in self._required_groups:
887 e = pkg_manager.selectGroup(group.name, group.include)
889 if kickstart.ignore_missing(self.ks):
890 skipped_groups.append(group)
892 raise CreatorError("Failed to find group '%s' : %s" %
895 for group in skipped_groups:
896 msger.warning("Skipping missing group '%s'" % (group.name,))
898 def __deselect_packages(self, pkg_manager):
899 for pkg in self._excluded_pkgs:
900 pkg_manager.deselectPackage(pkg)
902 def __localinst_packages(self, pkg_manager):
903 for rpm_path in self._get_local_packages():
904 pkg_manager.installLocal(rpm_path)
906 def __preinstall_packages(self, pkg_manager):
910 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
911 for pkg in self._preinstall_pkgs:
912 pkg_manager.preInstall(pkg)
914 def __attachment_packages(self, pkg_manager):
918 self._attachment = []
919 for item in kickstart.get_attachment(self.ks):
920 if item.startswith('/'):
921 fpaths = os.path.join(self._instroot, item.lstrip('/'))
922 for fpath in glob.glob(fpaths):
923 self._attachment.append(fpath)
926 filelist = pkg_manager.getFilelist(item)
928 # found rpm in rootfs
929 for pfile in pkg_manager.getFilelist(item):
930 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
931 self._attachment.append(fpath)
934 # try to retrieve rpm file
935 (url, proxies) = pkg_manager.package_url(item)
937 msger.warning("Can't get url from repo for %s" % item)
939 fpath = os.path.join(self.cachedir, os.path.basename(url))
940 if not os.path.exists(fpath):
943 fpath = grabber.myurlgrab(url, fpath, proxies, None)
947 tmpdir = self._mkdtemp()
948 misc.extract_rpm(fpath, tmpdir)
949 for (root, dirs, files) in os.walk(tmpdir):
951 fpath = os.path.join(root, fname)
952 self._attachment.append(fpath)
954 def install(self, repo_urls=None):
955 """Install packages into the install root.
957 This function installs the packages listed in the supplied kickstart
958 into the install root. By default, the packages are installed from the
959 repository URLs specified in the kickstart.
961 repo_urls -- a dict which maps a repository name to a repository URL;
962 if supplied, this causes any repository URLs specified in
963 the kickstart to be overridden.
967 # initialize pkg list to install
969 self.__sanity_check()
971 self._required_pkgs = \
972 kickstart.get_packages(self.ks, self._get_required_packages())
973 self._excluded_pkgs = \
974 kickstart.get_excluded(self.ks, self._get_excluded_packages())
975 self._required_groups = kickstart.get_groups(self.ks)
977 self._required_pkgs = None
978 self._excluded_pkgs = None
979 self._required_groups = None
981 pkg_manager = self.get_pkg_manager()
984 if hasattr(self, 'install_pkgs') and self.install_pkgs:
985 if 'debuginfo' in self.install_pkgs:
986 pkg_manager.install_debuginfo = True
988 for repo in kickstart.get_repos(self.ks, repo_urls):
989 (name, baseurl, mirrorlist, inc, exc,
990 proxy, proxy_username, proxy_password, debuginfo,
991 source, gpgkey, disable, ssl_verify, nocache,
992 cost, priority) = repo
994 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
995 proxy_username, proxy_password, inc, exc, ssl_verify,
996 nocache, cost, priority)
998 if kickstart.exclude_docs(self.ks):
999 rpm.addMacro("_excludedocs", "1")
1000 rpm.addMacro("_dbpath", "/var/lib/rpm")
1001 rpm.addMacro("__file_context_path", "%{nil}")
1002 if kickstart.inst_langs(self.ks) != None:
1003 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1006 self.__preinstall_packages(pkg_manager)
1007 self.__select_packages(pkg_manager)
1008 self.__select_groups(pkg_manager)
1009 self.__deselect_packages(pkg_manager)
1010 self.__localinst_packages(pkg_manager)
1012 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1013 checksize = self._root_fs_avail
1015 checksize -= BOOT_SAFEGUARD
1016 if self.target_arch:
1017 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1018 pkg_manager.runInstall(checksize)
1019 except CreatorError, e:
1021 except KeyboardInterrupt:
1024 self._pkgs_content = pkg_manager.getAllContent()
1025 self._pkgs_license = pkg_manager.getPkgsLicense()
1026 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1027 self.__attachment_packages(pkg_manager)
1034 # do some clean up to avoid lvm info leakage. this sucks.
1035 for subdir in ("cache", "backup", "archive"):
1036 lvmdir = self._instroot + "/etc/lvm/" + subdir
1038 for f in os.listdir(lvmdir):
1039 os.unlink(lvmdir + "/" + f)
1043 def postinstall(self):
1044 self.copy_attachment()
1046 def __run_post_scripts(self):
1047 msger.info("Running scripts ...")
1048 if os.path.exists(self._instroot + "/tmp"):
1049 shutil.rmtree(self._instroot + "/tmp")
1050 os.mkdir (self._instroot + "/tmp", 0755)
1051 for s in kickstart.get_post_scripts(self.ks):
1052 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1053 dir = self._instroot + "/tmp")
1055 s.script = s.script.replace("\r", "")
1056 os.write(fd, s.script)
1058 os.chmod(path, 0700)
1060 env = self._get_post_scripts_env(s.inChroot)
1066 preexec = self._chroot
1067 script = "/tmp/" + os.path.basename(path)
1071 p = subprocess.Popen([s.interp, script],
1072 preexec_fn = preexec,
1074 stdout = subprocess.PIPE,
1075 stderr = subprocess.STDOUT)
1076 for entry in p.communicate()[0].splitlines():
1078 except OSError, (err, msg):
1079 raise CreatorError("Failed to execute %%post script "
1080 "with '%s' : %s" % (s.interp, msg))
1084 def __save_repo_keys(self, repodata):
1088 gpgkeydir = "/etc/pki/rpm-gpg"
1089 fs.makedirs(self._instroot + gpgkeydir)
1090 for repo in repodata:
1092 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1093 shutil.copy(repo["repokey"], self._instroot + repokey)
1095 def configure(self, repodata = None):
1096 """Configure the system image according to the kickstart.
1098 This method applies the (e.g. keyboard or network) configuration
1099 specified in the kickstart and executes the kickstart %post scripts.
1101 If necessary, it also prepares the image to be bootable by e.g.
1102 creating an initrd and bootloader configuration.
1105 ksh = self.ks.handler
1107 msger.info('Applying configurations ...')
1109 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1110 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1111 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1112 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1113 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1114 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1115 kickstart.UserConfig(self._instroot).apply(ksh.user)
1116 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1117 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1118 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1119 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1120 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1121 self.__save_repo_keys(repodata)
1122 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1124 msger.warning("Failed to apply configuration to image")
1127 self._create_bootconfig()
1128 self.__run_post_scripts()
1130 def launch_shell(self, launch):
1131 """Launch a shell in the install root.
1133 This method is launches a bash shell chroot()ed in the install root;
1134 this can be useful for debugging.
1138 msger.info("Launching shell. Exit to continue.")
1139 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1141 def do_genchecksum(self, image_name):
1142 if not self._genchecksum:
1145 md5sum = misc.get_md5sum(image_name)
1146 with open(image_name + ".md5sum", "w") as f:
1147 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1148 self.outimage.append(image_name+".md5sum")
1150 def package(self, destdir = "."):
1151 """Prepares the created image for final delivery.
1153 In its simplest form, this method merely copies the install root to the
1154 supplied destination directory; other subclasses may choose to package
1155 the image by e.g. creating a bootable ISO containing the image and
1156 bootloader configuration.
1158 destdir -- the directory into which the final image should be moved;
1159 this defaults to the current directory.
1162 self._stage_final_image()
1164 if not os.path.exists(destdir):
1165 fs.makedirs(destdir)
1167 if self._recording_pkgs:
1168 self._save_recording_pkgs(destdir)
1170 # For image formats with two or multiple image files, it will be
1171 # better to put them under a directory
1172 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1173 destdir = os.path.join(destdir, "%s-%s" \
1174 % (self.name, self.image_format))
1175 msger.debug("creating destination dir: %s" % destdir)
1176 fs.makedirs(destdir)
1178 # Ensure all data is flushed to _outdir
1179 runner.quiet('sync')
1181 misc.check_space_pre_cp(self._outdir, destdir)
1182 for f in os.listdir(self._outdir):
1183 shutil.move(os.path.join(self._outdir, f),
1184 os.path.join(destdir, f))
1185 self.outimage.append(os.path.join(destdir, f))
1186 self.do_genchecksum(os.path.join(destdir, f))
1188 def print_outimage_info(self):
1189 msg = "The new image can be found here:\n"
1190 self.outimage.sort()
1191 for file in self.outimage:
1192 msg += ' %s\n' % os.path.abspath(file)
1196 def check_depend_tools(self):
1197 for tool in self._dep_checks:
1198 fs.find_binary_path(tool)
1200 def package_output(self, image_format, destdir = ".", package="none"):
1201 if not package or package == "none":
1204 destdir = os.path.abspath(os.path.expanduser(destdir))
1205 (pkg, comp) = os.path.splitext(package)
1207 comp=comp.lstrip(".")
1211 dst = "%s/%s-%s.tar.%s" %\
1212 (destdir, self.name, image_format, comp)
1214 dst = "%s/%s-%s.tar" %\
1215 (destdir, self.name, image_format)
1217 msger.info("creating %s" % dst)
1218 tar = tarfile.open(dst, "w:" + comp)
1220 for file in self.outimage:
1221 msger.info("adding %s to %s" % (file, dst))
1223 arcname=os.path.join("%s-%s" \
1224 % (self.name, image_format),
1225 os.path.basename(file)))
1226 if os.path.isdir(file):
1227 shutil.rmtree(file, ignore_errors = True)
1233 '''All the file in outimage has been packaged into tar.* file'''
1234 self.outimage = [dst]
1236 def release_output(self, config, destdir, release):
1237 """ Create release directory and files
1241 """ release path """
1242 return os.path.join(destdir, fn)
1244 outimages = self.outimage
1247 new_kspath = _rpath(self.name+'.ks')
1248 with open(config) as fr:
1249 with open(new_kspath, "w") as wf:
1250 # When building a release we want to make sure the .ks
1251 # file generates the same build even when --release not used.
1252 wf.write(fr.read().replace("@BUILD_ID@", release))
1253 outimages.append(new_kspath)
1255 # save log file, logfile is only available in creator attrs
1256 if hasattr(self, 'logfile') and not self.logfile:
1257 log_path = _rpath(self.name + ".log")
1258 # touch the log file, else outimages will filter it out
1259 with open(log_path, 'w') as wf:
1261 msger.set_logfile(log_path)
1262 outimages.append(_rpath(self.name + ".log"))
1264 # rename iso and usbimg
1265 for f in os.listdir(destdir):
1266 if f.endswith(".iso"):
1267 newf = f[:-4] + '.img'
1268 elif f.endswith(".usbimg"):
1269 newf = f[:-7] + '.img'
1272 os.rename(_rpath(f), _rpath(newf))
1273 outimages.append(_rpath(newf))
1276 with open(_rpath("MD5SUMS"), "w") as wf:
1277 for f in os.listdir(destdir):
1281 if os.path.isdir(os.path.join(destdir, f)):
1284 md5sum = misc.get_md5sum(_rpath(f))
1285 # There needs to be two spaces between the sum and
1286 # filepath to match the syntax with md5sum.
1287 # This way also md5sum -c MD5SUMS can be used by users
1288 wf.write("%s *%s\n" % (md5sum, f))
1290 outimages.append("%s/MD5SUMS" % destdir)
1292 # Filter out the nonexist file
1293 for fp in outimages[:]:
1294 if not os.path.exists("%s" % fp):
1295 outimages.remove(fp)
1297 def copy_kernel(self):
1298 """ Copy kernel files to the outimage directory.
1299 NOTE: This needs to be called before unmounting the instroot.
1302 if not self._need_copy_kernel:
1305 if not os.path.exists(self.destdir):
1306 os.makedirs(self.destdir)
1308 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1309 kernelfilename = "%s/%s-%s" % (self.destdir,
1311 os.path.basename(kernel))
1312 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1314 shutil.copy(kernel, kernelfilename)
1315 self.outimage.append(kernelfilename)
1317 def copy_attachment(self):
1318 """ Subclass implement it to handle attachment files
1319 NOTE: This needs to be called before unmounting the instroot.
1323 def get_pkg_manager(self):
1324 return self.pkgmgr(target_arch = self.target_arch,
1325 instroot = self._instroot,
1326 cachedir = self.cachedir)