4 # Copyright (c) 2007 Red Hat Inc.
5 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
7 # This program is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by the Free
9 # Software Foundation; version 2 of the License
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc., 59
18 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 from __future__ import with_statement
32 from mic import kickstart
34 from mic.utils.errors import CreatorError, Abort
35 from mic.utils import misc, grabber, runner, fs_related as fs
36 from mic.chroot import kill_proc_inchroot
38 class BaseImageCreator(object):
39 """Installs a system to a chroot directory.
41 ImageCreator is the simplest creator class available; it will install and
42 configure a system image according to the supplied kickstart file.
46 import mic.imgcreate as imgcreate
47 ks = imgcreate.read_kickstart("foo.ks")
48 imgcreate.ImageCreator(ks, "foo").create()
55 def __init__(self, createopts = None, pkgmgr = None):
56 """Initialize an ImageCreator instance.
58 ks -- a pykickstart.KickstartParser instance; this instance will be
59 used to drive the install by e.g. providing the list of packages
60 to be installed, the system configuration and %post scripts
62 name -- a name for the image; used for e.g. image filenames or
69 self.__builddir = None
70 self.__bindmounts = []
74 self.tmpdir = "/var/tmp/mic"
75 self.cachedir = "/var/tmp/mic/cache"
76 self.workdir = "/var/tmp/mic/build"
78 self.installerfw_prefix = "INSTALLERFW_"
79 self.target_arch = "noarch"
80 self._local_pkgs_path = None
84 # If the kernel is save to the destdir when copy_kernel cmd is called.
85 self._need_copy_kernel = False
86 # setup tmpfs tmpdir when enabletmpfs is True
87 self.enabletmpfs = False
90 # Mapping table for variables that have different names.
91 optmap = {"pkgmgr" : "pkgmgr_name",
93 "arch" : "target_arch",
94 "local_pkgs_path" : "_local_pkgs_path",
95 "copy_kernel" : "_need_copy_kernel",
98 # update setting from createopts
99 for key in createopts.keys():
104 setattr(self, option, createopts[key])
106 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
108 if 'release' in createopts and createopts['release']:
109 self.name = createopts['release'] + '_' + self.name
112 if '@NAME@' in self.pack_to:
113 self.pack_to = self.pack_to.replace('@NAME@', self.name)
114 (tar, ext) = os.path.splitext(self.pack_to)
115 if ext in (".gz", ".bz2") and tar.endswith(".tar"):
117 if ext not in misc.pack_formats:
118 self.pack_to += ".tar"
120 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
122 # Output image file names
125 # A flag to generate checksum
126 self._genchecksum = False
128 self._alt_initrd_name = None
130 self._recording_pkgs = []
132 # available size in root fs, init to 0
133 self._root_fs_avail = 0
135 # Name of the disk image file that is created.
136 self._img_name = None
138 self.image_format = None
140 # Save qemu emulator file name in order to clean up it finally
141 self.qemu_emulator = None
143 # No ks provided when called by convertor, so skip the dependency check
145 # If we have btrfs partition we need to check necessary tools
146 for part in self.ks.handler.partition.partitions:
147 if part.fstype and part.fstype == "btrfs":
148 self._dep_checks.append("mkfs.btrfs")
151 if self.target_arch and self.target_arch.startswith("arm"):
152 for dep in self._dep_checks:
153 if dep == "extlinux":
154 self._dep_checks.remove(dep)
156 if not os.path.exists("/usr/bin/qemu-arm") or \
157 not misc.is_statically_linked("/usr/bin/qemu-arm"):
158 self._dep_checks.append("qemu-arm-static")
160 if os.path.exists("/proc/sys/vm/vdso_enabled"):
161 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
162 vdso_value = vdso_fh.read().strip()
164 if (int)(vdso_value) == 1:
165 msger.warning("vdso is enabled on your host, which might "
166 "cause problems with arm emulations.\n"
167 "\tYou can disable vdso with following command before "
168 "starting image build:\n"
169 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
171 # make sure the specified tmpdir and cachedir exist
172 if not os.path.exists(self.tmpdir):
173 os.makedirs(self.tmpdir)
174 if not os.path.exists(self.cachedir):
175 os.makedirs(self.cachedir)
181 def __get_instroot(self):
182 if self.__builddir is None:
183 raise CreatorError("_instroot is not valid before calling mount()")
184 return self.__builddir + "/install_root"
185 _instroot = property(__get_instroot)
186 """The location of the install root directory.
188 This is the directory into which the system is installed. Subclasses may
189 mount a filesystem image here or copy files to/from here.
191 Note, this directory does not exist before ImageCreator.mount() is called.
193 Note also, this is a read-only attribute.
197 def __get_outdir(self):
198 if self.__builddir is None:
199 raise CreatorError("_outdir is not valid before calling mount()")
200 return self.__builddir + "/out"
201 _outdir = property(__get_outdir)
202 """The staging location for the final image.
204 This is where subclasses should stage any files that are part of the final
205 image. ImageCreator.package() will copy any files found here into the
206 requested destination directory.
208 Note, this directory does not exist before ImageCreator.mount() is called.
210 Note also, this is a read-only attribute.
216 # Hooks for subclasses
218 def _mount_instroot(self, base_on = None):
219 """Mount or prepare the install root directory.
221 This is the hook where subclasses may prepare the install root by e.g.
222 mounting creating and loopback mounting a filesystem image to
225 There is no default implementation.
227 base_on -- this is the value passed to mount() and can be interpreted
228 as the subclass wishes; it might e.g. be the location of
229 a previously created ISO containing a system image.
234 def _unmount_instroot(self):
235 """Undo anything performed in _mount_instroot().
237 This is the hook where subclasses must undo anything which was done
238 in _mount_instroot(). For example, if a filesystem image was mounted
239 onto _instroot, it should be unmounted here.
241 There is no default implementation.
246 def _create_bootconfig(self):
247 """Configure the image so that it's bootable.
249 This is the hook where subclasses may prepare the image for booting by
250 e.g. creating an initramfs and bootloader configuration.
252 This hook is called while the install root is still mounted, after the
253 packages have been installed and the kickstart configuration has been
254 applied, but before the %post scripts have been executed.
256 There is no default implementation.
261 def _stage_final_image(self):
262 """Stage the final system image in _outdir.
264 This is the hook where subclasses should place the image in _outdir
265 so that package() can copy it to the requested destination directory.
267 By default, this moves the install root into _outdir.
270 shutil.move(self._instroot, self._outdir + "/" + self.name)
272 def get_installed_packages(self):
273 return self._pkgs_content.keys()
275 def _save_recording_pkgs(self, destdir):
276 """Save the list or content of installed packages to file.
278 pkgs = self._pkgs_content.keys()
279 pkgs.sort() # inplace op
281 if not os.path.exists(destdir):
285 if 'vcs' in self._recording_pkgs:
286 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
287 content = '\n'.join(sorted(vcslst))
288 elif 'name' in self._recording_pkgs:
289 content = '\n'.join(pkgs)
291 namefile = os.path.join(destdir, self.name + '.packages')
292 f = open(namefile, "w")
295 self.outimage.append(namefile);
297 # if 'content', save more details
298 if 'content' in self._recording_pkgs:
299 contfile = os.path.join(destdir, self.name + '.files')
300 f = open(contfile, "w")
305 pkgcont = self._pkgs_content[pkg]
307 content += '\n '.join(pkgcont)
313 self.outimage.append(contfile)
315 if 'license' in self._recording_pkgs:
316 licensefile = os.path.join(destdir, self.name + '.license')
317 f = open(licensefile, "w")
319 f.write('Summary:\n')
320 for license in reversed(sorted(self._pkgs_license, key=\
321 lambda license: len(self._pkgs_license[license]))):
322 f.write(" - %s: %s\n" \
323 % (license, len(self._pkgs_license[license])))
325 f.write('\nDetails:\n')
326 for license in reversed(sorted(self._pkgs_license, key=\
327 lambda license: len(self._pkgs_license[license]))):
328 f.write(" - %s:\n" % (license))
329 for pkg in sorted(self._pkgs_license[license]):
330 f.write(" - %s\n" % (pkg))
334 self.outimage.append(licensefile)
336 def _get_required_packages(self):
337 """Return a list of required packages.
339 This is the hook where subclasses may specify a set of packages which
340 it requires to be installed.
342 This returns an empty list by default.
344 Note, subclasses should usually chain up to the base class
345 implementation of this hook.
350 def _get_excluded_packages(self):
351 """Return a list of excluded packages.
353 This is the hook where subclasses may specify a set of packages which
354 it requires _not_ to be installed.
356 This returns an empty list by default.
358 Note, subclasses should usually chain up to the base class
359 implementation of this hook.
364 def _get_local_packages(self):
365 """Return a list of rpm path to be local installed.
367 This is the hook where subclasses may specify a set of rpms which
368 it requires to be installed locally.
370 This returns an empty list by default.
372 Note, subclasses should usually chain up to the base class
373 implementation of this hook.
376 if self._local_pkgs_path:
377 if os.path.isdir(self._local_pkgs_path):
379 os.path.join(self._local_pkgs_path, '*.rpm'))
380 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
381 return [self._local_pkgs_path]
385 def _get_fstab(self):
386 """Return the desired contents of /etc/fstab.
388 This is the hook where subclasses may specify the contents of
389 /etc/fstab by returning a string containing the desired contents.
391 A sensible default implementation is provided.
394 s = "/dev/root / %s %s 0 0\n" \
396 "defaults,noatime" if not self._fsopts else self._fsopts)
397 s += self._get_fstab_special()
400 def _get_fstab_special(self):
401 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
402 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
403 s += "proc /proc proc defaults 0 0\n"
404 s += "sysfs /sys sysfs defaults 0 0\n"
407 def _set_part_env(self, pnum, prop, value):
408 """ This is a helper function which generates an environment variable
409 for a property "prop" with value "value" of a partition number "pnum".
411 The naming convention is:
412 * Variables start with INSTALLERFW_PART
413 * Then goes the partition number, the order is the same as
414 specified in the KS file
415 * Then goes the property name
423 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
424 return { name : value }
426 def _get_post_scripts_env(self, in_chroot):
427 """Return an environment dict for %post scripts.
429 This is the hook where subclasses may specify some environment
430 variables for %post scripts by return a dict containing the desired
433 in_chroot -- whether this %post script is to be executed chroot()ed
440 for p in kickstart.get_partitions(self.ks):
441 env.update(self._set_part_env(pnum, "SIZE", p.size))
442 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
443 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
444 env.update(self._set_part_env(pnum, "LABEL", p.label))
445 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
446 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
447 env.update(self._set_part_env(pnum, "ALIGN", p.align))
448 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
449 env.update(self._set_part_env(pnum, "DEVNODE",
450 "/dev/%s%d" % (p.disk, pnum + 1)))
454 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
456 # Partition table format
457 ptable_format = self.ks.handler.bootloader.ptable
458 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
460 # The kerned boot parameters
461 kernel_opts = self.ks.handler.bootloader.appendLine
462 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
464 # Name of the distribution
465 env[self.installerfw_prefix + "DISTRO_NAME"] = self.distro_name
467 # Name of the image creation tool
468 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
470 # The real current location of the mounted file-systems
474 mount_prefix = self._instroot
475 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
477 # These are historical variables which lack the common name prefix
479 env["INSTALL_ROOT"] = self._instroot
480 env["IMG_NAME"] = self._name
484 def __get_imgname(self):
486 _name = property(__get_imgname)
487 """The name of the image file.
491 def _get_kernel_versions(self):
492 """Return a dict detailing the available kernel types/versions.
494 This is the hook where subclasses may override what kernel types and
495 versions should be available for e.g. creating the booloader
498 A dict should be returned mapping the available kernel types to a list
499 of the available versions for those kernels.
501 The default implementation uses rpm to iterate over everything
502 providing 'kernel', finds /boot/vmlinuz-* and returns the version
503 obtained from the vmlinuz filename. (This can differ from the kernel
504 RPM's n-v-r in the case of e.g. xen)
507 def get_kernel_versions(instroot):
510 files = glob.glob(instroot + "/boot/vmlinuz-*")
512 version = os.path.basename(file)[8:]
515 versions.add(version)
516 ret["kernel"] = list(versions)
519 def get_version(header):
521 for f in header['filenames']:
522 if f.startswith('/boot/vmlinuz-'):
527 return get_kernel_versions(self._instroot)
529 ts = rpm.TransactionSet(self._instroot)
532 for header in ts.dbMatch('provides', 'kernel'):
533 version = get_version(header)
537 name = header['name']
539 ret[name] = [version]
540 elif not version in ret[name]:
541 ret[name].append(version)
547 # Helpers for subclasses
549 def _do_bindmounts(self):
550 """Mount various system directories onto _instroot.
552 This method is called by mount(), but may also be used by subclasses
553 in order to re-mount the bindmounts after modifying the underlying
557 for b in self.__bindmounts:
560 def _undo_bindmounts(self):
561 """Unmount the bind-mounted system directories from _instroot.
563 This method is usually only called by unmount(), but may also be used
564 by subclasses in order to gain access to the filesystem obscured by
565 the bindmounts - e.g. in order to create device nodes on the image
569 self.__bindmounts.reverse()
570 for b in self.__bindmounts:
574 """Chroot into the install root.
576 This method may be used by subclasses when executing programs inside
577 the install root e.g.
579 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
582 os.chroot(self._instroot)
585 def _mkdtemp(self, prefix = "tmp-"):
586 """Create a temporary directory.
588 This method may be used by subclasses to create a temporary directory
589 for use in building the final image - e.g. a subclass might create
590 a temporary directory in order to bundle a set of files into a package.
592 The subclass may delete this directory if it wishes, but it will be
593 automatically deleted by cleanup().
595 The absolute path to the temporary directory is returned.
597 Note, this method should only be called after mount() has been called.
599 prefix -- a prefix which should be used when creating the directory;
603 self.__ensure_builddir()
604 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
606 def _mkstemp(self, prefix = "tmp-"):
607 """Create a temporary file.
609 This method may be used by subclasses to create a temporary file
610 for use in building the final image - e.g. a subclass might need
611 a temporary location to unpack a compressed file.
613 The subclass may delete this file if it wishes, but it will be
614 automatically deleted by cleanup().
616 A tuple containing a file descriptor (returned from os.open() and the
617 absolute path to the temporary directory is returned.
619 Note, this method should only be called after mount() has been called.
621 prefix -- a prefix which should be used when creating the file;
625 self.__ensure_builddir()
626 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
628 def _mktemp(self, prefix = "tmp-"):
629 """Create a temporary file.
631 This method simply calls _mkstemp() and closes the returned file
634 The absolute path to the temporary file is returned.
636 Note, this method should only be called after mount() has been called.
638 prefix -- a prefix which should be used when creating the file;
643 (f, path) = self._mkstemp(prefix)
649 # Actual implementation
651 def __ensure_builddir(self):
652 if not self.__builddir is None:
656 self.workdir = os.path.join(self.tmpdir, "build")
657 if not os.path.exists(self.workdir):
658 os.makedirs(self.workdir)
659 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
660 prefix = "imgcreate-")
661 except OSError, (err, msg):
662 raise CreatorError("Failed create build directory in %s: %s" %
665 def get_cachedir(self, cachedir = None):
669 self.__ensure_builddir()
671 self.cachedir = cachedir
673 self.cachedir = self.__builddir + "/mic-cache"
674 fs.makedirs(self.cachedir)
677 def __sanity_check(self):
678 """Ensure that the config we've been given is sane."""
679 if not (kickstart.get_packages(self.ks) or
680 kickstart.get_groups(self.ks)):
681 raise CreatorError("No packages or groups specified")
683 kickstart.convert_method_to_repo(self.ks)
685 if not kickstart.get_repos(self.ks):
686 raise CreatorError("No repositories specified")
688 def __write_fstab(self):
689 fstab_contents = self._get_fstab()
691 fstab = open(self._instroot + "/etc/fstab", "w")
692 fstab.write(fstab_contents)
695 def __create_minimal_dev(self):
696 """Create a minimal /dev so that we don't corrupt the host /dev"""
697 origumask = os.umask(0000)
698 devices = (('null', 1, 3, 0666),
699 ('urandom',1, 9, 0666),
700 ('random', 1, 8, 0666),
701 ('full', 1, 7, 0666),
702 ('ptmx', 5, 2, 0666),
704 ('zero', 1, 5, 0666))
706 links = (("/proc/self/fd", "/dev/fd"),
707 ("/proc/self/fd/0", "/dev/stdin"),
708 ("/proc/self/fd/1", "/dev/stdout"),
709 ("/proc/self/fd/2", "/dev/stderr"))
711 for (node, major, minor, perm) in devices:
712 if not os.path.exists(self._instroot + "/dev/" + node):
713 os.mknod(self._instroot + "/dev/" + node,
715 os.makedev(major,minor))
717 for (src, dest) in links:
718 if not os.path.exists(self._instroot + dest):
719 os.symlink(src, self._instroot + dest)
723 def __setup_tmpdir(self):
724 if not self.enabletmpfs:
727 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
729 def __clean_tmpdir(self):
730 if not self.enabletmpfs:
733 runner.show('umount -l %s' % self.workdir)
735 def mount(self, base_on = None, cachedir = None):
736 """Setup the target filesystem in preparation for an install.
738 This function sets up the filesystem which the ImageCreator will
739 install into and configure. The ImageCreator class merely creates an
740 install root directory, bind mounts some system directories (e.g. /dev)
741 and writes out /etc/fstab. Other subclasses may also e.g. create a
742 sparse file, format it and loopback mount it to the install root.
744 base_on -- a previous install on which to base this install; defaults
745 to None, causing a new image to be created
747 cachedir -- a directory in which to store the Yum cache; defaults to
748 None, causing a new cache to be created; by setting this
749 to another directory, the same cache can be reused across
753 self.__setup_tmpdir()
754 self.__ensure_builddir()
756 # prevent popup dialog in Ubuntu(s)
757 misc.hide_loopdev_presentation()
759 fs.makedirs(self._instroot)
760 fs.makedirs(self._outdir)
762 self._mount_instroot(base_on)
764 for d in ("/dev/pts",
771 fs.makedirs(self._instroot + d)
773 if self.target_arch and self.target_arch.startswith("arm"):
774 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
778 self.get_cachedir(cachedir)
780 # bind mount system directories into _instroot
781 for (f, dest) in [("/sys", None),
783 ("/proc/sys/fs/binfmt_misc", None),
785 self.__bindmounts.append(
787 f, self._instroot, dest))
789 self._do_bindmounts()
791 self.__create_minimal_dev()
793 if os.path.exists(self._instroot + "/etc/mtab"):
794 os.unlink(self._instroot + "/etc/mtab")
795 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
799 # get size of available space in 'instroot' fs
800 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
803 """Unmounts the target filesystem.
805 The ImageCreator class detaches the system from the install root, but
806 other subclasses may also detach the loopback mounted filesystem image
807 from the install root.
811 mtab = self._instroot + "/etc/mtab"
812 if not os.path.islink(mtab):
813 os.unlink(self._instroot + "/etc/mtab")
815 if self.qemu_emulator:
816 os.unlink(self._instroot + self.qemu_emulator)
820 self._undo_bindmounts()
822 """ Clean up yum garbage """
824 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
825 if os.path.exists(instroot_pdir):
826 shutil.rmtree(instroot_pdir, ignore_errors = True)
827 yumlibdir = self._instroot + "/var/lib/yum"
828 if os.path.exists(yumlibdir):
829 shutil.rmtree(yumlibdir, ignore_errors = True)
833 self._unmount_instroot()
835 # reset settings of popup dialog in Ubuntu(s)
836 misc.unhide_loopdev_presentation()
840 """Unmounts the target filesystem and deletes temporary files.
842 This method calls unmount() and then deletes any temporary files and
843 directories that were created on the host system while building the
846 Note, make sure to call this method once finished with the creator
847 instance in order to ensure no stale files are left on the host e.g.:
849 creator = ImageCreator(ks, name)
856 if not self.__builddir:
859 kill_proc_inchroot(self._instroot)
863 shutil.rmtree(self.__builddir, ignore_errors = True)
864 self.__builddir = None
866 self.__clean_tmpdir()
868 def __is_excluded_pkg(self, pkg):
869 if pkg in self._excluded_pkgs:
870 self._excluded_pkgs.remove(pkg)
873 for xpkg in self._excluded_pkgs:
874 if xpkg.endswith('*'):
875 if pkg.startswith(xpkg[:-1]):
877 elif xpkg.startswith('*'):
878 if pkg.endswith(xpkg[1:]):
883 def __select_packages(self, pkg_manager):
885 for pkg in self._required_pkgs:
886 e = pkg_manager.selectPackage(pkg)
888 if kickstart.ignore_missing(self.ks):
889 skipped_pkgs.append(pkg)
890 elif self.__is_excluded_pkg(pkg):
891 skipped_pkgs.append(pkg)
893 raise CreatorError("Failed to find package '%s' : %s" %
896 for pkg in skipped_pkgs:
897 msger.warning("Skipping missing package '%s'" % (pkg,))
899 def __select_groups(self, pkg_manager):
901 for group in self._required_groups:
902 e = pkg_manager.selectGroup(group.name, group.include)
904 if kickstart.ignore_missing(self.ks):
905 skipped_groups.append(group)
907 raise CreatorError("Failed to find group '%s' : %s" %
910 for group in skipped_groups:
911 msger.warning("Skipping missing group '%s'" % (group.name,))
913 def __deselect_packages(self, pkg_manager):
914 for pkg in self._excluded_pkgs:
915 pkg_manager.deselectPackage(pkg)
917 def __localinst_packages(self, pkg_manager):
918 for rpm_path in self._get_local_packages():
919 pkg_manager.installLocal(rpm_path)
921 def __preinstall_packages(self, pkg_manager):
925 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
926 for pkg in self._preinstall_pkgs:
927 pkg_manager.preInstall(pkg)
929 def __check_packages(self, pkg_manager):
930 for pkg in self.check_pkgs:
931 pkg_manager.checkPackage(pkg)
933 def __attachment_packages(self, pkg_manager):
937 self._attachment = []
938 for item in kickstart.get_attachment(self.ks):
939 if item.startswith('/'):
940 fpaths = os.path.join(self._instroot, item.lstrip('/'))
941 for fpath in glob.glob(fpaths):
942 self._attachment.append(fpath)
945 filelist = pkg_manager.getFilelist(item)
947 # found rpm in rootfs
948 for pfile in pkg_manager.getFilelist(item):
949 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
950 self._attachment.append(fpath)
953 # try to retrieve rpm file
954 (url, proxies) = pkg_manager.package_url(item)
956 msger.warning("Can't get url from repo for %s" % item)
958 fpath = os.path.join(self.cachedir, os.path.basename(url))
959 if not os.path.exists(fpath):
962 fpath = grabber.myurlgrab(url, fpath, proxies, None)
966 tmpdir = self._mkdtemp()
967 misc.extract_rpm(fpath, tmpdir)
968 for (root, dirs, files) in os.walk(tmpdir):
970 fpath = os.path.join(root, fname)
971 self._attachment.append(fpath)
973 def install(self, repo_urls=None):
974 """Install packages into the install root.
976 This function installs the packages listed in the supplied kickstart
977 into the install root. By default, the packages are installed from the
978 repository URLs specified in the kickstart.
980 repo_urls -- a dict which maps a repository name to a repository URL;
981 if supplied, this causes any repository URLs specified in
982 the kickstart to be overridden.
986 # initialize pkg list to install
988 self.__sanity_check()
990 self._required_pkgs = \
991 kickstart.get_packages(self.ks, self._get_required_packages())
992 self._excluded_pkgs = \
993 kickstart.get_excluded(self.ks, self._get_excluded_packages())
994 self._required_groups = kickstart.get_groups(self.ks)
996 self._required_pkgs = None
997 self._excluded_pkgs = None
998 self._required_groups = None
1000 pkg_manager = self.get_pkg_manager()
1003 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1004 if 'debuginfo' in self.install_pkgs:
1005 pkg_manager.install_debuginfo = True
1007 for repo in kickstart.get_repos(self.ks, repo_urls):
1008 (name, baseurl, mirrorlist, inc, exc,
1009 proxy, proxy_username, proxy_password, debuginfo,
1010 source, gpgkey, disable, ssl_verify, nocache,
1011 cost, priority) = repo
1013 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1014 proxy_username, proxy_password, inc, exc, ssl_verify,
1015 nocache, cost, priority)
1017 if kickstart.exclude_docs(self.ks):
1018 rpm.addMacro("_excludedocs", "1")
1019 rpm.addMacro("_dbpath", "/var/lib/rpm")
1020 rpm.addMacro("__file_context_path", "%{nil}")
1021 if kickstart.inst_langs(self.ks) != None:
1022 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1025 self.__preinstall_packages(pkg_manager)
1026 self.__select_packages(pkg_manager)
1027 self.__select_groups(pkg_manager)
1028 self.__deselect_packages(pkg_manager)
1029 self.__localinst_packages(pkg_manager)
1030 self.__check_packages(pkg_manager)
1032 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1033 checksize = self._root_fs_avail
1035 checksize -= BOOT_SAFEGUARD
1036 if self.target_arch:
1037 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1038 pkg_manager.runInstall(checksize)
1039 except CreatorError, e:
1041 except KeyboardInterrupt:
1044 self._pkgs_content = pkg_manager.getAllContent()
1045 self._pkgs_license = pkg_manager.getPkgsLicense()
1046 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1047 self.__attachment_packages(pkg_manager)
1054 # do some clean up to avoid lvm info leakage. this sucks.
1055 for subdir in ("cache", "backup", "archive"):
1056 lvmdir = self._instroot + "/etc/lvm/" + subdir
1058 for f in os.listdir(lvmdir):
1059 os.unlink(lvmdir + "/" + f)
1063 def postinstall(self):
1064 self.copy_attachment()
1066 def __run_post_scripts(self):
1067 msger.info("Running scripts ...")
1068 if os.path.exists(self._instroot + "/tmp"):
1069 shutil.rmtree(self._instroot + "/tmp")
1070 os.mkdir (self._instroot + "/tmp", 0755)
1071 for s in kickstart.get_post_scripts(self.ks):
1072 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1073 dir = self._instroot + "/tmp")
1075 s.script = s.script.replace("\r", "")
1076 os.write(fd, s.script)
1078 os.chmod(path, 0700)
1080 env = self._get_post_scripts_env(s.inChroot)
1086 preexec = self._chroot
1087 script = "/tmp/" + os.path.basename(path)
1091 p = subprocess.Popen([s.interp, script],
1092 preexec_fn = preexec,
1094 stdout = subprocess.PIPE,
1095 stderr = subprocess.STDOUT)
1096 for entry in p.communicate()[0].splitlines():
1098 except OSError, (err, msg):
1099 raise CreatorError("Failed to execute %%post script "
1100 "with '%s' : %s" % (s.interp, msg))
1104 def __save_repo_keys(self, repodata):
1108 gpgkeydir = "/etc/pki/rpm-gpg"
1109 fs.makedirs(self._instroot + gpgkeydir)
1110 for repo in repodata:
1112 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1113 shutil.copy(repo["repokey"], self._instroot + repokey)
1115 def configure(self, repodata = None):
1116 """Configure the system image according to the kickstart.
1118 This method applies the (e.g. keyboard or network) configuration
1119 specified in the kickstart and executes the kickstart %post scripts.
1121 If necessary, it also prepares the image to be bootable by e.g.
1122 creating an initrd and bootloader configuration.
1125 ksh = self.ks.handler
1127 msger.info('Applying configurations ...')
1129 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1130 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1131 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1132 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1133 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1134 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1135 kickstart.UserConfig(self._instroot).apply(ksh.user)
1136 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1137 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1138 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1139 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1140 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1141 self.__save_repo_keys(repodata)
1142 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1144 msger.warning("Failed to apply configuration to image")
1147 self._create_bootconfig()
1148 self.__run_post_scripts()
1150 def launch_shell(self, launch):
1151 """Launch a shell in the install root.
1153 This method is launches a bash shell chroot()ed in the install root;
1154 this can be useful for debugging.
1158 msger.info("Launching shell. Exit to continue.")
1159 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1161 def do_genchecksum(self, image_name):
1162 if not self._genchecksum:
1165 md5sum = misc.get_md5sum(image_name)
1166 with open(image_name + ".md5sum", "w") as f:
1167 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1168 self.outimage.append(image_name+".md5sum")
1170 def package(self, destdir = "."):
1171 """Prepares the created image for final delivery.
1173 In its simplest form, this method merely copies the install root to the
1174 supplied destination directory; other subclasses may choose to package
1175 the image by e.g. creating a bootable ISO containing the image and
1176 bootloader configuration.
1178 destdir -- the directory into which the final image should be moved;
1179 this defaults to the current directory.
1182 self._stage_final_image()
1184 if not os.path.exists(destdir):
1185 fs.makedirs(destdir)
1187 if self._recording_pkgs:
1188 self._save_recording_pkgs(destdir)
1190 # For image formats with two or multiple image files, it will be
1191 # better to put them under a directory
1192 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1193 destdir = os.path.join(destdir, "%s-%s" \
1194 % (self.name, self.image_format))
1195 msger.debug("creating destination dir: %s" % destdir)
1196 fs.makedirs(destdir)
1198 # Ensure all data is flushed to _outdir
1199 runner.quiet('sync')
1201 misc.check_space_pre_cp(self._outdir, destdir)
1202 for f in os.listdir(self._outdir):
1203 shutil.move(os.path.join(self._outdir, f),
1204 os.path.join(destdir, f))
1205 self.outimage.append(os.path.join(destdir, f))
1206 self.do_genchecksum(os.path.join(destdir, f))
1208 def print_outimage_info(self):
1209 msg = "The new image can be found here:\n"
1210 self.outimage.sort()
1211 for file in self.outimage:
1212 msg += ' %s\n' % os.path.abspath(file)
1216 def check_depend_tools(self):
1217 for tool in self._dep_checks:
1218 fs.find_binary_path(tool)
1220 def package_output(self, image_format, destdir = ".", package="none"):
1221 if not package or package == "none":
1224 destdir = os.path.abspath(os.path.expanduser(destdir))
1225 (pkg, comp) = os.path.splitext(package)
1227 comp=comp.lstrip(".")
1231 dst = "%s/%s-%s.tar.%s" %\
1232 (destdir, self.name, image_format, comp)
1234 dst = "%s/%s-%s.tar" %\
1235 (destdir, self.name, image_format)
1237 msger.info("creating %s" % dst)
1238 tar = tarfile.open(dst, "w:" + comp)
1240 for file in self.outimage:
1241 msger.info("adding %s to %s" % (file, dst))
1243 arcname=os.path.join("%s-%s" \
1244 % (self.name, image_format),
1245 os.path.basename(file)))
1246 if os.path.isdir(file):
1247 shutil.rmtree(file, ignore_errors = True)
1253 '''All the file in outimage has been packaged into tar.* file'''
1254 self.outimage = [dst]
1256 def release_output(self, config, destdir, release):
1257 """ Create release directory and files
1261 """ release path """
1262 return os.path.join(destdir, fn)
1264 outimages = self.outimage
1267 new_kspath = _rpath(self.name+'.ks')
1268 with open(config) as fr:
1269 with open(new_kspath, "w") as wf:
1270 # When building a release we want to make sure the .ks
1271 # file generates the same build even when --release not used.
1272 wf.write(fr.read().replace("@BUILD_ID@", release))
1273 outimages.append(new_kspath)
1275 # save log file, logfile is only available in creator attrs
1276 if hasattr(self, 'logfile') and not self.logfile:
1277 log_path = _rpath(self.name + ".log")
1278 # touch the log file, else outimages will filter it out
1279 with open(log_path, 'w') as wf:
1281 msger.set_logfile(log_path)
1282 outimages.append(_rpath(self.name + ".log"))
1284 # rename iso and usbimg
1285 for f in os.listdir(destdir):
1286 if f.endswith(".iso"):
1287 newf = f[:-4] + '.img'
1288 elif f.endswith(".usbimg"):
1289 newf = f[:-7] + '.img'
1292 os.rename(_rpath(f), _rpath(newf))
1293 outimages.append(_rpath(newf))
1296 with open(_rpath("MD5SUMS"), "w") as wf:
1297 for f in os.listdir(destdir):
1301 if os.path.isdir(os.path.join(destdir, f)):
1304 md5sum = misc.get_md5sum(_rpath(f))
1305 # There needs to be two spaces between the sum and
1306 # filepath to match the syntax with md5sum.
1307 # This way also md5sum -c MD5SUMS can be used by users
1308 wf.write("%s *%s\n" % (md5sum, f))
1310 outimages.append("%s/MD5SUMS" % destdir)
1312 # Filter out the nonexist file
1313 for fp in outimages[:]:
1314 if not os.path.exists("%s" % fp):
1315 outimages.remove(fp)
1317 def copy_kernel(self):
1318 """ Copy kernel files to the outimage directory.
1319 NOTE: This needs to be called before unmounting the instroot.
1322 if not self._need_copy_kernel:
1325 if not os.path.exists(self.destdir):
1326 os.makedirs(self.destdir)
1328 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1329 kernelfilename = "%s/%s-%s" % (self.destdir,
1331 os.path.basename(kernel))
1332 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1334 shutil.copy(kernel, kernelfilename)
1335 self.outimage.append(kernelfilename)
1337 def copy_attachment(self):
1338 """ Subclass implement it to handle attachment files
1339 NOTE: This needs to be called before unmounting the instroot.
1343 def get_pkg_manager(self):
1344 return self.pkgmgr(target_arch = self.target_arch,
1345 instroot = self._instroot,
1346 cachedir = self.cachedir)