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))
109 if '@NAME@' in self.pack_to:
110 self.pack_to = self.pack_to.replace('@NAME@', self.name)
111 (tar, ext) = os.path.splitext(self.pack_to)
112 if ext in (".gz", ".bz2") and tar.endswith(".tar"):
114 if ext not in misc.pack_formats:
115 self.pack_to += ".tar"
117 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
119 # Output image file names
122 # A flag to generate checksum
123 self._genchecksum = False
125 self._alt_initrd_name = None
127 self._recording_pkgs = []
129 # available size in root fs, init to 0
130 self._root_fs_avail = 0
132 # Name of the disk image file that is created.
133 self._img_name = None
135 self.image_format = None
137 # Save qemu emulator file name in order to clean up it finally
138 self.qemu_emulator = None
140 # No ks provided when called by convertor, so skip the dependency check
142 # If we have btrfs partition we need to check necessary tools
143 for part in self.ks.handler.partition.partitions:
144 if part.fstype and part.fstype == "btrfs":
145 self._dep_checks.append("mkfs.btrfs")
148 if self.target_arch and self.target_arch.startswith("arm"):
149 for dep in self._dep_checks:
150 if dep == "extlinux":
151 self._dep_checks.remove(dep)
153 if not os.path.exists("/usr/bin/qemu-arm") or \
154 not misc.is_statically_linked("/usr/bin/qemu-arm"):
155 self._dep_checks.append("qemu-arm-static")
157 if os.path.exists("/proc/sys/vm/vdso_enabled"):
158 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
159 vdso_value = vdso_fh.read().strip()
161 if (int)(vdso_value) == 1:
162 msger.warning("vdso is enabled on your host, which might "
163 "cause problems with arm emulations.\n"
164 "\tYou can disable vdso with following command before "
165 "starting image build:\n"
166 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
168 # make sure the specified tmpdir and cachedir exist
169 if not os.path.exists(self.tmpdir):
170 os.makedirs(self.tmpdir)
171 if not os.path.exists(self.cachedir):
172 os.makedirs(self.cachedir)
178 def __get_instroot(self):
179 if self.__builddir is None:
180 raise CreatorError("_instroot is not valid before calling mount()")
181 return self.__builddir + "/install_root"
182 _instroot = property(__get_instroot)
183 """The location of the install root directory.
185 This is the directory into which the system is installed. Subclasses may
186 mount a filesystem image here or copy files to/from here.
188 Note, this directory does not exist before ImageCreator.mount() is called.
190 Note also, this is a read-only attribute.
194 def __get_outdir(self):
195 if self.__builddir is None:
196 raise CreatorError("_outdir is not valid before calling mount()")
197 return self.__builddir + "/out"
198 _outdir = property(__get_outdir)
199 """The staging location for the final image.
201 This is where subclasses should stage any files that are part of the final
202 image. ImageCreator.package() will copy any files found here into the
203 requested destination directory.
205 Note, this directory does not exist before ImageCreator.mount() is called.
207 Note also, this is a read-only attribute.
213 # Hooks for subclasses
215 def _mount_instroot(self, base_on = None):
216 """Mount or prepare the install root directory.
218 This is the hook where subclasses may prepare the install root by e.g.
219 mounting creating and loopback mounting a filesystem image to
222 There is no default implementation.
224 base_on -- this is the value passed to mount() and can be interpreted
225 as the subclass wishes; it might e.g. be the location of
226 a previously created ISO containing a system image.
231 def _unmount_instroot(self):
232 """Undo anything performed in _mount_instroot().
234 This is the hook where subclasses must undo anything which was done
235 in _mount_instroot(). For example, if a filesystem image was mounted
236 onto _instroot, it should be unmounted here.
238 There is no default implementation.
243 def _create_bootconfig(self):
244 """Configure the image so that it's bootable.
246 This is the hook where subclasses may prepare the image for booting by
247 e.g. creating an initramfs and bootloader configuration.
249 This hook is called while the install root is still mounted, after the
250 packages have been installed and the kickstart configuration has been
251 applied, but before the %post scripts have been executed.
253 There is no default implementation.
258 def _stage_final_image(self):
259 """Stage the final system image in _outdir.
261 This is the hook where subclasses should place the image in _outdir
262 so that package() can copy it to the requested destination directory.
264 By default, this moves the install root into _outdir.
267 shutil.move(self._instroot, self._outdir + "/" + self.name)
269 def get_installed_packages(self):
270 return self._pkgs_content.keys()
272 def _save_recording_pkgs(self, destdir):
273 """Save the list or content of installed packages to file.
275 pkgs = self._pkgs_content.keys()
276 pkgs.sort() # inplace op
278 if not os.path.exists(destdir):
282 if 'vcs' in self._recording_pkgs:
283 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
284 content = '\n'.join(sorted(vcslst))
285 elif 'name' in self._recording_pkgs:
286 content = '\n'.join(pkgs)
288 namefile = os.path.join(destdir, self.name + '.packages')
289 f = open(namefile, "w")
292 self.outimage.append(namefile);
294 # if 'content', save more details
295 if 'content' in self._recording_pkgs:
296 contfile = os.path.join(destdir, self.name + '.files')
297 f = open(contfile, "w")
302 pkgcont = self._pkgs_content[pkg]
304 content += '\n '.join(pkgcont)
310 self.outimage.append(contfile)
312 if 'license' in self._recording_pkgs:
313 licensefile = os.path.join(destdir, self.name + '.license')
314 f = open(licensefile, "w")
316 f.write('Summary:\n')
317 for license in reversed(sorted(self._pkgs_license, key=\
318 lambda license: len(self._pkgs_license[license]))):
319 f.write(" - %s: %s\n" \
320 % (license, len(self._pkgs_license[license])))
322 f.write('\nDetails:\n')
323 for license in reversed(sorted(self._pkgs_license, key=\
324 lambda license: len(self._pkgs_license[license]))):
325 f.write(" - %s:\n" % (license))
326 for pkg in sorted(self._pkgs_license[license]):
327 f.write(" - %s\n" % (pkg))
331 self.outimage.append(licensefile)
333 def _get_required_packages(self):
334 """Return a list of required packages.
336 This is the hook where subclasses may specify a set of packages which
337 it requires to be installed.
339 This returns an empty list by default.
341 Note, subclasses should usually chain up to the base class
342 implementation of this hook.
347 def _get_excluded_packages(self):
348 """Return a list of excluded packages.
350 This is the hook where subclasses may specify a set of packages which
351 it requires _not_ to be installed.
353 This returns an empty list by default.
355 Note, subclasses should usually chain up to the base class
356 implementation of this hook.
361 def _get_local_packages(self):
362 """Return a list of rpm path to be local installed.
364 This is the hook where subclasses may specify a set of rpms which
365 it requires to be installed locally.
367 This returns an empty list by default.
369 Note, subclasses should usually chain up to the base class
370 implementation of this hook.
373 if self._local_pkgs_path:
374 if os.path.isdir(self._local_pkgs_path):
376 os.path.join(self._local_pkgs_path, '*.rpm'))
377 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
378 return [self._local_pkgs_path]
382 def _get_fstab(self):
383 """Return the desired contents of /etc/fstab.
385 This is the hook where subclasses may specify the contents of
386 /etc/fstab by returning a string containing the desired contents.
388 A sensible default implementation is provided.
391 s = "/dev/root / %s %s 0 0\n" \
393 "defaults,noatime" if not self._fsopts else self._fsopts)
394 s += self._get_fstab_special()
397 def _get_fstab_special(self):
398 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
399 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
400 s += "proc /proc proc defaults 0 0\n"
401 s += "sysfs /sys sysfs defaults 0 0\n"
404 def _set_part_env(self, pnum, prop, value):
405 """ This is a helper function which generates an environment variable
406 for a property "prop" with value "value" of a partition number "pnum".
408 The naming convention is:
409 * Variables start with INSTALLERFW_PART
410 * Then goes the partition number, the order is the same as
411 specified in the KS file
412 * Then goes the property name
420 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
421 return { name : value }
423 def _get_post_scripts_env(self, in_chroot):
424 """Return an environment dict for %post scripts.
426 This is the hook where subclasses may specify some environment
427 variables for %post scripts by return a dict containing the desired
430 in_chroot -- whether this %post script is to be executed chroot()ed
437 for p in kickstart.get_partitions(self.ks):
438 env.update(self._set_part_env(pnum, "SIZE", p.size))
439 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
440 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
441 env.update(self._set_part_env(pnum, "LABEL", p.label))
442 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
443 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
444 env.update(self._set_part_env(pnum, "ALIGN", p.align))
445 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
446 env.update(self._set_part_env(pnum, "UUID", p.uuid))
447 env.update(self._set_part_env(pnum, "DEVNODE",
448 "/dev/%s%d" % (p.disk, pnum + 1)))
449 env.update(self._set_part_env(pnum, "DISK_DEVNODE",
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 image creation tool
465 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
467 # The real current location of the mounted file-systems
471 mount_prefix = self._instroot
472 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
474 # These are historical variables which lack the common name prefix
476 env["INSTALL_ROOT"] = self._instroot
477 env["IMG_NAME"] = self._name
481 def __get_imgname(self):
483 _name = property(__get_imgname)
484 """The name of the image file.
488 def _get_kernel_versions(self):
489 """Return a dict detailing the available kernel types/versions.
491 This is the hook where subclasses may override what kernel types and
492 versions should be available for e.g. creating the booloader
495 A dict should be returned mapping the available kernel types to a list
496 of the available versions for those kernels.
498 The default implementation uses rpm to iterate over everything
499 providing 'kernel', finds /boot/vmlinuz-* and returns the version
500 obtained from the vmlinuz filename. (This can differ from the kernel
501 RPM's n-v-r in the case of e.g. xen)
504 def get_kernel_versions(instroot):
507 files = glob.glob(instroot + "/boot/vmlinuz-*")
509 version = os.path.basename(file)[8:]
512 versions.add(version)
513 ret["kernel"] = list(versions)
516 def get_version(header):
518 for f in header['filenames']:
519 if f.startswith('/boot/vmlinuz-'):
524 return get_kernel_versions(self._instroot)
526 ts = rpm.TransactionSet(self._instroot)
529 for header in ts.dbMatch('provides', 'kernel'):
530 version = get_version(header)
534 name = header['name']
536 ret[name] = [version]
537 elif not version in ret[name]:
538 ret[name].append(version)
544 # Helpers for subclasses
546 def _do_bindmounts(self):
547 """Mount various system directories onto _instroot.
549 This method is called by mount(), but may also be used by subclasses
550 in order to re-mount the bindmounts after modifying the underlying
554 for b in self.__bindmounts:
557 def _undo_bindmounts(self):
558 """Unmount the bind-mounted system directories from _instroot.
560 This method is usually only called by unmount(), but may also be used
561 by subclasses in order to gain access to the filesystem obscured by
562 the bindmounts - e.g. in order to create device nodes on the image
566 self.__bindmounts.reverse()
567 for b in self.__bindmounts:
571 """Chroot into the install root.
573 This method may be used by subclasses when executing programs inside
574 the install root e.g.
576 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
579 os.chroot(self._instroot)
582 def _mkdtemp(self, prefix = "tmp-"):
583 """Create a temporary directory.
585 This method may be used by subclasses to create a temporary directory
586 for use in building the final image - e.g. a subclass might create
587 a temporary directory in order to bundle a set of files into a package.
589 The subclass may delete this directory if it wishes, but it will be
590 automatically deleted by cleanup().
592 The absolute path to the temporary directory is returned.
594 Note, this method should only be called after mount() has been called.
596 prefix -- a prefix which should be used when creating the directory;
600 self.__ensure_builddir()
601 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
603 def _mkstemp(self, prefix = "tmp-"):
604 """Create a temporary file.
606 This method may be used by subclasses to create a temporary file
607 for use in building the final image - e.g. a subclass might need
608 a temporary location to unpack a compressed file.
610 The subclass may delete this file if it wishes, but it will be
611 automatically deleted by cleanup().
613 A tuple containing a file descriptor (returned from os.open() and the
614 absolute path to the temporary directory is returned.
616 Note, this method should only be called after mount() has been called.
618 prefix -- a prefix which should be used when creating the file;
622 self.__ensure_builddir()
623 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
625 def _mktemp(self, prefix = "tmp-"):
626 """Create a temporary file.
628 This method simply calls _mkstemp() and closes the returned file
631 The absolute path to the temporary file is returned.
633 Note, this method should only be called after mount() has been called.
635 prefix -- a prefix which should be used when creating the file;
640 (f, path) = self._mkstemp(prefix)
646 # Actual implementation
648 def __ensure_builddir(self):
649 if not self.__builddir is None:
653 self.workdir = os.path.join(self.tmpdir, "build")
654 if not os.path.exists(self.workdir):
655 os.makedirs(self.workdir)
656 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
657 prefix = "imgcreate-")
658 except OSError, (err, msg):
659 raise CreatorError("Failed create build directory in %s: %s" %
662 def get_cachedir(self, cachedir = None):
666 self.__ensure_builddir()
668 self.cachedir = cachedir
670 self.cachedir = self.__builddir + "/mic-cache"
671 fs.makedirs(self.cachedir)
674 def __sanity_check(self):
675 """Ensure that the config we've been given is sane."""
676 if not (kickstart.get_packages(self.ks) or
677 kickstart.get_groups(self.ks)):
678 raise CreatorError("No packages or groups specified")
680 kickstart.convert_method_to_repo(self.ks)
682 if not kickstart.get_repos(self.ks):
683 raise CreatorError("No repositories specified")
685 def __write_fstab(self):
686 if kickstart.use_installerfw(self.ks, "fstab"):
687 # The fstab file will be generated by installer framework scripts
690 fstab_contents = self._get_fstab()
692 fstab = open(self._instroot + "/etc/fstab", "w")
693 fstab.write(fstab_contents)
696 def __create_minimal_dev(self):
697 """Create a minimal /dev so that we don't corrupt the host /dev"""
698 origumask = os.umask(0000)
699 devices = (('null', 1, 3, 0666),
700 ('urandom',1, 9, 0666),
701 ('random', 1, 8, 0666),
702 ('full', 1, 7, 0666),
703 ('ptmx', 5, 2, 0666),
705 ('zero', 1, 5, 0666))
707 links = (("/proc/self/fd", "/dev/fd"),
708 ("/proc/self/fd/0", "/dev/stdin"),
709 ("/proc/self/fd/1", "/dev/stdout"),
710 ("/proc/self/fd/2", "/dev/stderr"))
712 for (node, major, minor, perm) in devices:
713 if not os.path.exists(self._instroot + "/dev/" + node):
714 os.mknod(self._instroot + "/dev/" + node,
716 os.makedev(major,minor))
718 for (src, dest) in links:
719 if not os.path.exists(self._instroot + dest):
720 os.symlink(src, self._instroot + dest)
724 def __setup_tmpdir(self):
725 if not self.enabletmpfs:
728 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
730 def __clean_tmpdir(self):
731 if not self.enabletmpfs:
734 runner.show('umount -l %s' % self.workdir)
736 def mount(self, base_on = None, cachedir = None):
737 """Setup the target filesystem in preparation for an install.
739 This function sets up the filesystem which the ImageCreator will
740 install into and configure. The ImageCreator class merely creates an
741 install root directory, bind mounts some system directories (e.g. /dev)
742 and writes out /etc/fstab. Other subclasses may also e.g. create a
743 sparse file, format it and loopback mount it to the install root.
745 base_on -- a previous install on which to base this install; defaults
746 to None, causing a new image to be created
748 cachedir -- a directory in which to store the Yum cache; defaults to
749 None, causing a new cache to be created; by setting this
750 to another directory, the same cache can be reused across
754 self.__setup_tmpdir()
755 self.__ensure_builddir()
757 # prevent popup dialog in Ubuntu(s)
758 misc.hide_loopdev_presentation()
760 fs.makedirs(self._instroot)
761 fs.makedirs(self._outdir)
763 self._mount_instroot(base_on)
765 for d in ("/dev/pts",
772 fs.makedirs(self._instroot + d)
774 if self.target_arch and self.target_arch.startswith("arm") or \
775 self.target_arch == "aarch64":
776 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
779 self.get_cachedir(cachedir)
781 # bind mount system directories into _instroot
782 for (f, dest) in [("/sys", None),
784 ("/proc/sys/fs/binfmt_misc", None),
786 self.__bindmounts.append(
788 f, self._instroot, dest))
790 self._do_bindmounts()
792 self.__create_minimal_dev()
794 if os.path.exists(self._instroot + "/etc/mtab"):
795 os.unlink(self._instroot + "/etc/mtab")
796 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
800 # get size of available space in 'instroot' fs
801 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
804 """Unmounts the target filesystem.
806 The ImageCreator class detaches the system from the install root, but
807 other subclasses may also detach the loopback mounted filesystem image
808 from the install root.
812 mtab = self._instroot + "/etc/mtab"
813 if not os.path.islink(mtab):
814 os.unlink(self._instroot + "/etc/mtab")
816 if self.qemu_emulator:
817 os.unlink(self._instroot + self.qemu_emulator)
821 self._undo_bindmounts()
823 """ Clean up yum garbage """
825 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
826 if os.path.exists(instroot_pdir):
827 shutil.rmtree(instroot_pdir, ignore_errors = True)
828 yumlibdir = self._instroot + "/var/lib/yum"
829 if os.path.exists(yumlibdir):
830 shutil.rmtree(yumlibdir, ignore_errors = True)
834 self._unmount_instroot()
836 # reset settings of popup dialog in Ubuntu(s)
837 misc.unhide_loopdev_presentation()
841 """Unmounts the target filesystem and deletes temporary files.
843 This method calls unmount() and then deletes any temporary files and
844 directories that were created on the host system while building the
847 Note, make sure to call this method once finished with the creator
848 instance in order to ensure no stale files are left on the host e.g.:
850 creator = ImageCreator(ks, name)
857 if not self.__builddir:
860 kill_proc_inchroot(self._instroot)
864 shutil.rmtree(self.__builddir, ignore_errors = True)
865 self.__builddir = None
867 self.__clean_tmpdir()
869 def __is_excluded_pkg(self, pkg):
870 if pkg in self._excluded_pkgs:
871 self._excluded_pkgs.remove(pkg)
874 for xpkg in self._excluded_pkgs:
875 if xpkg.endswith('*'):
876 if pkg.startswith(xpkg[:-1]):
878 elif xpkg.startswith('*'):
879 if pkg.endswith(xpkg[1:]):
884 def __select_packages(self, pkg_manager):
886 for pkg in self._required_pkgs:
887 e = pkg_manager.selectPackage(pkg)
889 if kickstart.ignore_missing(self.ks):
890 skipped_pkgs.append(pkg)
891 elif self.__is_excluded_pkg(pkg):
892 skipped_pkgs.append(pkg)
894 raise CreatorError("Failed to find package '%s' : %s" %
897 for pkg in skipped_pkgs:
898 msger.warning("Skipping missing package '%s'" % (pkg,))
900 def __select_groups(self, pkg_manager):
902 for group in self._required_groups:
903 e = pkg_manager.selectGroup(group.name, group.include)
905 if kickstart.ignore_missing(self.ks):
906 skipped_groups.append(group)
908 raise CreatorError("Failed to find group '%s' : %s" %
911 for group in skipped_groups:
912 msger.warning("Skipping missing group '%s'" % (group.name,))
914 def __deselect_packages(self, pkg_manager):
915 for pkg in self._excluded_pkgs:
916 pkg_manager.deselectPackage(pkg)
918 def __localinst_packages(self, pkg_manager):
919 for rpm_path in self._get_local_packages():
920 pkg_manager.installLocal(rpm_path)
922 def __preinstall_packages(self, pkg_manager):
926 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
927 for pkg in self._preinstall_pkgs:
928 pkg_manager.preInstall(pkg)
930 def __check_packages(self, pkg_manager):
931 for pkg in self.check_pkgs:
932 pkg_manager.checkPackage(pkg)
934 def __attachment_packages(self, pkg_manager):
938 self._attachment = []
939 for item in kickstart.get_attachment(self.ks):
940 if item.startswith('/'):
941 fpaths = os.path.join(self._instroot, item.lstrip('/'))
942 for fpath in glob.glob(fpaths):
943 self._attachment.append(fpath)
946 filelist = pkg_manager.getFilelist(item)
948 # found rpm in rootfs
949 for pfile in pkg_manager.getFilelist(item):
950 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
951 self._attachment.append(fpath)
954 # try to retrieve rpm file
955 (url, proxies) = pkg_manager.package_url(item)
957 msger.warning("Can't get url from repo for %s" % item)
959 fpath = os.path.join(self.cachedir, os.path.basename(url))
960 if not os.path.exists(fpath):
963 fpath = grabber.myurlgrab(url.full, fpath, proxies, None)
967 tmpdir = self._mkdtemp()
968 misc.extract_rpm(fpath, tmpdir)
969 for (root, dirs, files) in os.walk(tmpdir):
971 fpath = os.path.join(root, fname)
972 self._attachment.append(fpath)
974 def install(self, repo_urls=None):
975 """Install packages into the install root.
977 This function installs the packages listed in the supplied kickstart
978 into the install root. By default, the packages are installed from the
979 repository URLs specified in the kickstart.
981 repo_urls -- a dict which maps a repository name to a repository;
982 if supplied, this causes any repository URLs specified in
983 the kickstart to be overridden.
987 # initialize pkg list to install
989 self.__sanity_check()
991 self._required_pkgs = \
992 kickstart.get_packages(self.ks, self._get_required_packages())
993 self._excluded_pkgs = \
994 kickstart.get_excluded(self.ks, self._get_excluded_packages())
995 self._required_groups = kickstart.get_groups(self.ks)
997 self._required_pkgs = None
998 self._excluded_pkgs = None
999 self._required_groups = None
1002 repo_urls = self.extrarepos
1004 pkg_manager = self.get_pkg_manager()
1007 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1008 if 'debuginfo' in self.install_pkgs:
1009 pkg_manager.install_debuginfo = True
1011 for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1012 (name, baseurl, mirrorlist, inc, exc,
1013 proxy, proxy_username, proxy_password, debuginfo,
1014 source, gpgkey, disable, ssl_verify, nocache,
1015 cost, priority) = repo
1017 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1018 proxy_username, proxy_password, inc, exc, ssl_verify,
1019 nocache, cost, priority)
1021 if kickstart.exclude_docs(self.ks):
1022 rpm.addMacro("_excludedocs", "1")
1023 rpm.addMacro("_dbpath", "/var/lib/rpm")
1024 rpm.addMacro("__file_context_path", "%{nil}")
1025 if kickstart.inst_langs(self.ks) != None:
1026 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1029 self.__preinstall_packages(pkg_manager)
1030 self.__select_packages(pkg_manager)
1031 self.__select_groups(pkg_manager)
1032 self.__deselect_packages(pkg_manager)
1033 self.__localinst_packages(pkg_manager)
1034 self.__check_packages(pkg_manager)
1036 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1037 checksize = self._root_fs_avail
1039 checksize -= BOOT_SAFEGUARD
1040 if self.target_arch:
1041 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1042 pkg_manager.runInstall(checksize)
1043 except CreatorError, e:
1045 except KeyboardInterrupt:
1048 self._pkgs_content = pkg_manager.getAllContent()
1049 self._pkgs_license = pkg_manager.getPkgsLicense()
1050 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1051 self.__attachment_packages(pkg_manager)
1058 # do some clean up to avoid lvm info leakage. this sucks.
1059 for subdir in ("cache", "backup", "archive"):
1060 lvmdir = self._instroot + "/etc/lvm/" + subdir
1062 for f in os.listdir(lvmdir):
1063 os.unlink(lvmdir + "/" + f)
1067 def postinstall(self):
1068 self.copy_attachment()
1070 def __run_post_scripts(self):
1071 msger.info("Running scripts ...")
1072 if os.path.exists(self._instroot + "/tmp"):
1073 shutil.rmtree(self._instroot + "/tmp")
1074 os.mkdir (self._instroot + "/tmp", 0755)
1075 for s in kickstart.get_post_scripts(self.ks):
1076 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1077 dir = self._instroot + "/tmp")
1079 s.script = s.script.replace("\r", "")
1080 os.write(fd, s.script)
1082 os.chmod(path, 0700)
1084 env = self._get_post_scripts_env(s.inChroot)
1085 if 'PATH' not in env:
1086 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1092 preexec = self._chroot
1093 script = "/tmp/" + os.path.basename(path)
1097 p = subprocess.Popen([s.interp, script],
1098 preexec_fn = preexec,
1100 stdout = subprocess.PIPE,
1101 stderr = subprocess.STDOUT)
1102 for entry in p.communicate()[0].splitlines():
1104 except OSError, (err, msg):
1105 raise CreatorError("Failed to execute %%post script "
1106 "with '%s' : %s" % (s.interp, msg))
1110 def __save_repo_keys(self, repodata):
1114 gpgkeydir = "/etc/pki/rpm-gpg"
1115 fs.makedirs(self._instroot + gpgkeydir)
1116 for repo in repodata:
1118 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1119 shutil.copy(repo["repokey"], self._instroot + repokey)
1121 def configure(self, repodata = None):
1122 """Configure the system image according to the kickstart.
1124 This method applies the (e.g. keyboard or network) configuration
1125 specified in the kickstart and executes the kickstart %post scripts.
1127 If necessary, it also prepares the image to be bootable by e.g.
1128 creating an initrd and bootloader configuration.
1131 ksh = self.ks.handler
1133 msger.info('Applying configurations ...')
1135 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1136 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1137 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1138 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1139 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1140 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1141 kickstart.UserConfig(self._instroot).apply(ksh.user)
1142 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1143 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1144 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1145 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1146 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1147 self.__save_repo_keys(repodata)
1148 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1150 msger.warning("Failed to apply configuration to image")
1153 self._create_bootconfig()
1154 self.__run_post_scripts()
1156 def launch_shell(self, launch):
1157 """Launch a shell in the install root.
1159 This method is launches a bash shell chroot()ed in the install root;
1160 this can be useful for debugging.
1164 msger.info("Launching shell. Exit to continue.")
1165 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1167 def do_genchecksum(self, image_name):
1168 if not self._genchecksum:
1171 md5sum = misc.get_md5sum(image_name)
1172 with open(image_name + ".md5sum", "w") as f:
1173 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1174 self.outimage.append(image_name+".md5sum")
1176 def package(self, destdir = "."):
1177 """Prepares the created image for final delivery.
1179 In its simplest form, this method merely copies the install root to the
1180 supplied destination directory; other subclasses may choose to package
1181 the image by e.g. creating a bootable ISO containing the image and
1182 bootloader configuration.
1184 destdir -- the directory into which the final image should be moved;
1185 this defaults to the current directory.
1188 self._stage_final_image()
1190 if not os.path.exists(destdir):
1191 fs.makedirs(destdir)
1193 if self._recording_pkgs:
1194 self._save_recording_pkgs(destdir)
1196 # For image formats with two or multiple image files, it will be
1197 # better to put them under a directory
1198 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1199 destdir = os.path.join(destdir, "%s-%s" \
1200 % (self.name, self.image_format))
1201 msger.debug("creating destination dir: %s" % destdir)
1202 fs.makedirs(destdir)
1204 # Ensure all data is flushed to _outdir
1205 runner.quiet('sync')
1207 misc.check_space_pre_cp(self._outdir, destdir)
1208 for f in os.listdir(self._outdir):
1209 shutil.move(os.path.join(self._outdir, f),
1210 os.path.join(destdir, f))
1211 self.outimage.append(os.path.join(destdir, f))
1212 self.do_genchecksum(os.path.join(destdir, f))
1214 def print_outimage_info(self):
1215 msg = "The new image can be found here:\n"
1216 self.outimage.sort()
1217 for file in self.outimage:
1218 msg += ' %s\n' % os.path.abspath(file)
1222 def check_depend_tools(self):
1223 for tool in self._dep_checks:
1224 fs.find_binary_path(tool)
1226 def package_output(self, image_format, destdir = ".", package="none"):
1227 if not package or package == "none":
1230 destdir = os.path.abspath(os.path.expanduser(destdir))
1231 (pkg, comp) = os.path.splitext(package)
1233 comp=comp.lstrip(".")
1237 dst = "%s/%s-%s.tar.%s" %\
1238 (destdir, self.name, image_format, comp)
1240 dst = "%s/%s-%s.tar" %\
1241 (destdir, self.name, image_format)
1243 msger.info("creating %s" % dst)
1244 tar = tarfile.open(dst, "w:" + comp)
1246 for file in self.outimage:
1247 msger.info("adding %s to %s" % (file, dst))
1249 arcname=os.path.join("%s-%s" \
1250 % (self.name, image_format),
1251 os.path.basename(file)))
1252 if os.path.isdir(file):
1253 shutil.rmtree(file, ignore_errors = True)
1259 '''All the file in outimage has been packaged into tar.* file'''
1260 self.outimage = [dst]
1262 def release_output(self, config, destdir, release):
1263 """ Create release directory and files
1267 """ release path """
1268 return os.path.join(destdir, fn)
1270 outimages = self.outimage
1273 new_kspath = _rpath(self.name+'.ks')
1274 with open(config) as fr:
1275 with open(new_kspath, "w") as wf:
1276 # When building a release we want to make sure the .ks
1277 # file generates the same build even when --release not used.
1278 wf.write(fr.read().replace("@BUILD_ID@", release))
1279 outimages.append(new_kspath)
1281 # save log file, logfile is only available in creator attrs
1282 if hasattr(self, 'logfile') and not self.logfile:
1283 log_path = _rpath(self.name + ".log")
1284 # touch the log file, else outimages will filter it out
1285 with open(log_path, 'w') as wf:
1287 msger.set_logfile(log_path)
1288 outimages.append(_rpath(self.name + ".log"))
1290 # rename iso and usbimg
1291 for f in os.listdir(destdir):
1292 if f.endswith(".iso"):
1293 newf = f[:-4] + '.img'
1294 elif f.endswith(".usbimg"):
1295 newf = f[:-7] + '.img'
1298 os.rename(_rpath(f), _rpath(newf))
1299 outimages.append(_rpath(newf))
1302 with open(_rpath("MD5SUMS"), "w") as wf:
1303 for f in os.listdir(destdir):
1307 if os.path.isdir(os.path.join(destdir, f)):
1310 md5sum = misc.get_md5sum(_rpath(f))
1311 # There needs to be two spaces between the sum and
1312 # filepath to match the syntax with md5sum.
1313 # This way also md5sum -c MD5SUMS can be used by users
1314 wf.write("%s *%s\n" % (md5sum, f))
1316 outimages.append("%s/MD5SUMS" % destdir)
1318 # Filter out the nonexist file
1319 for fp in outimages[:]:
1320 if not os.path.exists("%s" % fp):
1321 outimages.remove(fp)
1323 def copy_kernel(self):
1324 """ Copy kernel files to the outimage directory.
1325 NOTE: This needs to be called before unmounting the instroot.
1328 if not self._need_copy_kernel:
1331 if not os.path.exists(self.destdir):
1332 os.makedirs(self.destdir)
1334 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1335 kernelfilename = "%s/%s-%s" % (self.destdir,
1337 os.path.basename(kernel))
1338 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1340 shutil.copy(kernel, kernelfilename)
1341 self.outimage.append(kernelfilename)
1343 def copy_attachment(self):
1344 """ Subclass implement it to handle attachment files
1345 NOTE: This needs to be called before unmounting the instroot.
1349 def get_pkg_manager(self):
1350 return self.pkgmgr(target_arch = self.target_arch,
1351 instroot = self._instroot,
1352 cachedir = self.cachedir)