4 # Copyright (c) 2007 Red Hat Inc.
5 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
7 # This program is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by the Free
9 # Software Foundation; version 2 of the License
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc., 59
18 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 from __future__ import with_statement
30 from datetime import datetime
34 from mic import kickstart
35 from mic import msger, __version__ as VERSION
36 from mic.utils.errors import CreatorError, Abort
37 from mic.utils import misc, grabber, runner, fs_related as fs
38 from mic.chroot import kill_proc_inchroot
39 from mic.archive import get_archive_suffixes
41 class BaseImageCreator(object):
42 """Installs a system to a chroot directory.
44 ImageCreator is the simplest creator class available; it will install and
45 configure a system image according to the supplied kickstart file.
49 import mic.imgcreate as imgcreate
50 ks = imgcreate.read_kickstart("foo.ks")
51 imgcreate.ImageCreator(ks, "foo").create()
60 def __init__(self, createopts = None, pkgmgr = None):
61 """Initialize an ImageCreator instance.
63 ks -- a pykickstart.KickstartParser instance; this instance will be
64 used to drive the install by e.g. providing the list of packages
65 to be installed, the system configuration and %post scripts
67 name -- a name for the image; used for e.g. image filenames or
74 self.__builddir = None
75 self.__bindmounts = []
79 self.tmpdir = "/var/tmp/mic"
80 self.cachedir = "/var/tmp/mic/cache"
81 self.workdir = "/var/tmp/mic/build"
83 self.installerfw_prefix = "INSTALLERFW_"
84 self.target_arch = "noarch"
85 self._local_pkgs_path = None
89 # If the kernel is save to the destdir when copy_kernel cmd is called.
90 self._need_copy_kernel = False
91 # setup tmpfs tmpdir when enabletmpfs is True
92 self.enabletmpfs = False
95 # Mapping table for variables that have different names.
96 optmap = {"pkgmgr" : "pkgmgr_name",
97 "arch" : "target_arch",
98 "local_pkgs_path" : "_local_pkgs_path",
99 "copy_kernel" : "_need_copy_kernel",
102 # update setting from createopts
103 for key in createopts.keys():
108 setattr(self, option, createopts[key])
110 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
113 if '@NAME@' in self.pack_to:
114 self.pack_to = self.pack_to.replace('@NAME@', self.name)
115 (tar, ext) = os.path.splitext(self.pack_to)
116 if ext in (".gz", ".bz2", ".lzo", ".bz") and tar.endswith(".tar"):
118 if ext not in get_archive_suffixes():
119 self.pack_to += ".tar"
121 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
123 # Output image file names
125 # Output info related with manifest
126 self.image_files = {}
127 # A flag to generate checksum
128 self._genchecksum = False
130 self._alt_initrd_name = None
132 self._recording_pkgs = []
134 # available size in root fs, init to 0
135 self._root_fs_avail = 0
137 # Name of the disk image file that is created.
138 self._img_name = None
140 self.image_format = None
142 # Save qemu emulator file name in order to clean up it finally
143 self.qemu_emulator = None
145 # No ks provided when called by convertor, so skip the dependency check
147 # If we have btrfs partition we need to check necessary tools
148 for part in self.ks.handler.partition.partitions:
149 if part.fstype and part.fstype == "btrfs":
150 self._dep_checks.append("mkfs.btrfs")
153 if self.target_arch and self.target_arch.startswith("arm"):
154 for dep in self._dep_checks:
155 if dep == "extlinux":
156 self._dep_checks.remove(dep)
158 if not os.path.exists("/usr/bin/qemu-arm") or \
159 not misc.is_statically_linked("/usr/bin/qemu-arm"):
160 self._dep_checks.append("qemu-arm-static")
162 if os.path.exists("/proc/sys/vm/vdso_enabled"):
163 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
164 vdso_value = vdso_fh.read().strip()
166 if (int)(vdso_value) == 1:
167 msger.warning("vdso is enabled on your host, which might "
168 "cause problems with arm emulations.\n"
169 "\tYou can disable vdso with following command before "
170 "starting image build:\n"
171 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
173 # make sure the specified tmpdir and cachedir exist
174 if not os.path.exists(self.tmpdir):
175 os.makedirs(self.tmpdir)
176 if not os.path.exists(self.cachedir):
177 os.makedirs(self.cachedir)
183 def __get_instroot(self):
184 if self.__builddir is None:
185 raise CreatorError("_instroot is not valid before calling mount()")
186 return self.__builddir + "/install_root"
187 _instroot = property(__get_instroot)
188 """The location of the install root directory.
190 This is the directory into which the system is installed. Subclasses may
191 mount a filesystem image here or copy files to/from here.
193 Note, this directory does not exist before ImageCreator.mount() is called.
195 Note also, this is a read-only attribute.
199 def __get_outdir(self):
200 if self.__builddir is None:
201 raise CreatorError("_outdir is not valid before calling mount()")
202 return self.__builddir + "/out"
203 _outdir = property(__get_outdir)
204 """The staging location for the final image.
206 This is where subclasses should stage any files that are part of the final
207 image. ImageCreator.package() will copy any files found here into the
208 requested destination directory.
210 Note, this directory does not exist before ImageCreator.mount() is called.
212 Note also, this is a read-only attribute.
218 # Hooks for subclasses
220 def _mount_instroot(self, base_on = None):
221 """Mount or prepare the install root directory.
223 This is the hook where subclasses may prepare the install root by e.g.
224 mounting creating and loopback mounting a filesystem image to
227 There is no default implementation.
229 base_on -- this is the value passed to mount() and can be interpreted
230 as the subclass wishes; it might e.g. be the location of
231 a previously created ISO containing a system image.
236 def _unmount_instroot(self):
237 """Undo anything performed in _mount_instroot().
239 This is the hook where subclasses must undo anything which was done
240 in _mount_instroot(). For example, if a filesystem image was mounted
241 onto _instroot, it should be unmounted here.
243 There is no default implementation.
248 def _create_bootconfig(self):
249 """Configure the image so that it's bootable.
251 This is the hook where subclasses may prepare the image for booting by
252 e.g. creating an initramfs and bootloader configuration.
254 This hook is called while the install root is still mounted, after the
255 packages have been installed and the kickstart configuration has been
256 applied, but before the %post scripts have been executed.
258 There is no default implementation.
263 def _stage_final_image(self):
264 """Stage the final system image in _outdir.
266 This is the hook where subclasses should place the image in _outdir
267 so that package() can copy it to the requested destination directory.
269 By default, this moves the install root into _outdir.
272 shutil.move(self._instroot, self._outdir + "/" + self.name)
274 def get_installed_packages(self):
275 return self._pkgs_content.keys()
277 def _save_recording_pkgs(self, destdir):
278 """Save the list or content of installed packages to file.
280 pkgs = self._pkgs_content.keys()
281 pkgs.sort() # inplace op
283 if not os.path.exists(destdir):
287 if 'vcs' in self._recording_pkgs:
288 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
289 content = '\n'.join(sorted(vcslst))
290 elif 'name' in self._recording_pkgs:
291 content = '\n'.join(pkgs)
293 namefile = os.path.join(destdir, self.name + '.packages')
294 f = open(namefile, "w")
297 self.outimage.append(namefile);
299 # if 'content', save more details
300 if 'content' in self._recording_pkgs:
301 contfile = os.path.join(destdir, self.name + '.files')
302 f = open(contfile, "w")
307 pkgcont = self._pkgs_content[pkg]
309 content += '\n '.join(pkgcont)
315 self.outimage.append(contfile)
317 if 'license' in self._recording_pkgs:
318 licensefile = os.path.join(destdir, self.name + '.license')
319 f = open(licensefile, "w")
321 f.write('Summary:\n')
322 for license in reversed(sorted(self._pkgs_license, key=\
323 lambda license: len(self._pkgs_license[license]))):
324 f.write(" - %s: %s\n" \
325 % (license, len(self._pkgs_license[license])))
327 f.write('\nDetails:\n')
328 for license in reversed(sorted(self._pkgs_license, key=\
329 lambda license: len(self._pkgs_license[license]))):
330 f.write(" - %s:\n" % (license))
331 for pkg in sorted(self._pkgs_license[license]):
332 f.write(" - %s\n" % (pkg))
336 self.outimage.append(licensefile)
338 def _get_required_packages(self):
339 """Return a list of required packages.
341 This is the hook where subclasses may specify a set of packages which
342 it requires to be installed.
344 This returns an empty list by default.
346 Note, subclasses should usually chain up to the base class
347 implementation of this hook.
352 def _get_excluded_packages(self):
353 """Return a list of excluded packages.
355 This is the hook where subclasses may specify a set of packages which
356 it requires _not_ to be installed.
358 This returns an empty list by default.
360 Note, subclasses should usually chain up to the base class
361 implementation of this hook.
366 def _get_local_packages(self):
367 """Return a list of rpm path to be local installed.
369 This is the hook where subclasses may specify a set of rpms which
370 it requires to be installed locally.
372 This returns an empty list by default.
374 Note, subclasses should usually chain up to the base class
375 implementation of this hook.
378 if self._local_pkgs_path:
379 if os.path.isdir(self._local_pkgs_path):
381 os.path.join(self._local_pkgs_path, '*.rpm'))
382 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
383 return [self._local_pkgs_path]
387 def _get_fstab(self):
388 """Return the desired contents of /etc/fstab.
390 This is the hook where subclasses may specify the contents of
391 /etc/fstab by returning a string containing the desired contents.
393 A sensible default implementation is provided.
396 s = "/dev/root / %s %s 0 0\n" \
398 "defaults,noatime" if not self._fsopts else self._fsopts)
399 s += self._get_fstab_special()
402 def _get_fstab_special(self):
403 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
404 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
405 s += "proc /proc proc defaults 0 0\n"
406 s += "sysfs /sys sysfs defaults 0 0\n"
409 def _set_part_env(self, pnum, prop, value):
410 """ This is a helper function which generates an environment variable
411 for a property "prop" with value "value" of a partition number "pnum".
413 The naming convention is:
414 * Variables start with INSTALLERFW_PART
415 * Then goes the partition number, the order is the same as
416 specified in the KS file
417 * Then goes the property name
425 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
426 return { name : value }
428 def _get_post_scripts_env(self, in_chroot):
429 """Return an environment dict for %post scripts.
431 This is the hook where subclasses may specify some environment
432 variables for %post scripts by return a dict containing the desired
435 in_chroot -- whether this %post script is to be executed chroot()ed
442 for p in kickstart.get_partitions(self.ks):
443 env.update(self._set_part_env(pnum, "SIZE", p.size))
444 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
445 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
446 env.update(self._set_part_env(pnum, "LABEL", p.label))
447 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
448 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
449 env.update(self._set_part_env(pnum, "ALIGN", p.align))
450 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
451 env.update(self._set_part_env(pnum, "UUID", p.uuid))
452 env.update(self._set_part_env(pnum, "DEVNODE",
453 "/dev/%s%d" % (p.disk, pnum + 1)))
454 env.update(self._set_part_env(pnum, "DISK_DEVNODE",
459 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
461 # Partition table format
462 ptable_format = self.ks.handler.bootloader.ptable
463 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
465 # The kerned boot parameters
466 kernel_opts = self.ks.handler.bootloader.appendLine
467 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
469 # Name of the image creation tool
470 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
472 # The real current location of the mounted file-systems
476 mount_prefix = self._instroot
477 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
479 # These are historical variables which lack the common name prefix
481 env["INSTALL_ROOT"] = self._instroot
482 env["IMG_NAME"] = self._name
486 def __get_imgname(self):
488 _name = property(__get_imgname)
489 """The name of the image file.
493 def _get_kernel_versions(self):
494 """Return a dict detailing the available kernel types/versions.
496 This is the hook where subclasses may override what kernel types and
497 versions should be available for e.g. creating the booloader
500 A dict should be returned mapping the available kernel types to a list
501 of the available versions for those kernels.
503 The default implementation uses rpm to iterate over everything
504 providing 'kernel', finds /boot/vmlinuz-* and returns the version
505 obtained from the vmlinuz filename. (This can differ from the kernel
506 RPM's n-v-r in the case of e.g. xen)
509 def get_kernel_versions(instroot):
512 files = glob.glob(instroot + "/boot/vmlinuz-*")
514 version = os.path.basename(file)[8:]
517 versions.add(version)
518 ret["kernel"] = list(versions)
521 def get_version(header):
523 for f in header['filenames']:
524 if f.startswith('/boot/vmlinuz-'):
529 return get_kernel_versions(self._instroot)
531 ts = rpm.TransactionSet(self._instroot)
534 for header in ts.dbMatch('provides', 'kernel'):
535 version = get_version(header)
539 name = header['name']
541 ret[name] = [version]
542 elif not version in ret[name]:
543 ret[name].append(version)
549 # Helpers for subclasses
551 def _do_bindmounts(self):
552 """Mount various system directories onto _instroot.
554 This method is called by mount(), but may also be used by subclasses
555 in order to re-mount the bindmounts after modifying the underlying
559 for b in self.__bindmounts:
562 def _undo_bindmounts(self):
563 """Unmount the bind-mounted system directories from _instroot.
565 This method is usually only called by unmount(), but may also be used
566 by subclasses in order to gain access to the filesystem obscured by
567 the bindmounts - e.g. in order to create device nodes on the image
571 self.__bindmounts.reverse()
572 for b in self.__bindmounts:
576 """Chroot into the install root.
578 This method may be used by subclasses when executing programs inside
579 the install root e.g.
581 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
584 os.chroot(self._instroot)
587 def _mkdtemp(self, prefix = "tmp-"):
588 """Create a temporary directory.
590 This method may be used by subclasses to create a temporary directory
591 for use in building the final image - e.g. a subclass might create
592 a temporary directory in order to bundle a set of files into a package.
594 The subclass may delete this directory if it wishes, but it will be
595 automatically deleted by cleanup().
597 The absolute path to the temporary directory is returned.
599 Note, this method should only be called after mount() has been called.
601 prefix -- a prefix which should be used when creating the directory;
605 self.__ensure_builddir()
606 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
608 def _mkstemp(self, prefix = "tmp-"):
609 """Create a temporary file.
611 This method may be used by subclasses to create a temporary file
612 for use in building the final image - e.g. a subclass might need
613 a temporary location to unpack a compressed file.
615 The subclass may delete this file if it wishes, but it will be
616 automatically deleted by cleanup().
618 A tuple containing a file descriptor (returned from os.open() and the
619 absolute path to the temporary directory is returned.
621 Note, this method should only be called after mount() has been called.
623 prefix -- a prefix which should be used when creating the file;
627 self.__ensure_builddir()
628 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
630 def _mktemp(self, prefix = "tmp-"):
631 """Create a temporary file.
633 This method simply calls _mkstemp() and closes the returned file
636 The absolute path to the temporary file is returned.
638 Note, this method should only be called after mount() has been called.
640 prefix -- a prefix which should be used when creating the file;
645 (f, path) = self._mkstemp(prefix)
651 # Actual implementation
653 def __ensure_builddir(self):
654 if not self.__builddir is None:
658 self.workdir = os.path.join(self.tmpdir, "build")
659 if not os.path.exists(self.workdir):
660 os.makedirs(self.workdir)
661 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
662 prefix = "imgcreate-")
663 except OSError, (err, msg):
664 raise CreatorError("Failed create build directory in %s: %s" %
667 def get_cachedir(self, cachedir = None):
671 self.__ensure_builddir()
673 self.cachedir = cachedir
675 self.cachedir = self.__builddir + "/mic-cache"
676 fs.makedirs(self.cachedir)
679 def __sanity_check(self):
680 """Ensure that the config we've been given is sane."""
681 if not (kickstart.get_packages(self.ks) or
682 kickstart.get_groups(self.ks)):
683 raise CreatorError("No packages or groups specified")
685 kickstart.convert_method_to_repo(self.ks)
687 if not kickstart.get_repos(self.ks):
688 raise CreatorError("No repositories specified")
690 def __write_fstab(self):
691 if kickstart.use_installerfw(self.ks, "fstab"):
692 # The fstab file will be generated by installer framework scripts
695 fstab_contents = self._get_fstab()
697 fstab = open(self._instroot + "/etc/fstab", "w")
698 fstab.write(fstab_contents)
701 def __create_minimal_dev(self):
702 """Create a minimal /dev so that we don't corrupt the host /dev"""
703 origumask = os.umask(0000)
704 devices = (('null', 1, 3, 0666),
705 ('urandom',1, 9, 0666),
706 ('random', 1, 8, 0666),
707 ('full', 1, 7, 0666),
708 ('ptmx', 5, 2, 0666),
710 ('zero', 1, 5, 0666))
712 links = (("/proc/self/fd", "/dev/fd"),
713 ("/proc/self/fd/0", "/dev/stdin"),
714 ("/proc/self/fd/1", "/dev/stdout"),
715 ("/proc/self/fd/2", "/dev/stderr"))
717 for (node, major, minor, perm) in devices:
718 if not os.path.exists(self._instroot + "/dev/" + node):
719 os.mknod(self._instroot + "/dev/" + node,
721 os.makedev(major,minor))
723 for (src, dest) in links:
724 if not os.path.exists(self._instroot + dest):
725 os.symlink(src, self._instroot + dest)
729 def __setup_tmpdir(self):
730 if not self.enabletmpfs:
733 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
735 def __clean_tmpdir(self):
736 if not self.enabletmpfs:
739 runner.show('umount -l %s' % self.workdir)
741 def mount(self, base_on = None, cachedir = None):
742 """Setup the target filesystem in preparation for an install.
744 This function sets up the filesystem which the ImageCreator will
745 install into and configure. The ImageCreator class merely creates an
746 install root directory, bind mounts some system directories (e.g. /dev)
747 and writes out /etc/fstab. Other subclasses may also e.g. create a
748 sparse file, format it and loopback mount it to the install root.
750 base_on -- a previous install on which to base this install; defaults
751 to None, causing a new image to be created
753 cachedir -- a directory in which to store the Yum cache; defaults to
754 None, causing a new cache to be created; by setting this
755 to another directory, the same cache can be reused across
759 self.__setup_tmpdir()
760 self.__ensure_builddir()
762 # prevent popup dialog in Ubuntu(s)
763 misc.hide_loopdev_presentation()
765 fs.makedirs(self._instroot)
766 fs.makedirs(self._outdir)
768 self._mount_instroot(base_on)
770 for d in ("/dev/pts",
777 fs.makedirs(self._instroot + d)
779 if self.target_arch and self.target_arch.startswith("arm") or \
780 self.target_arch == "aarch64":
781 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
784 self.get_cachedir(cachedir)
786 # bind mount system directories into _instroot
787 for (f, dest) in [("/sys", None),
789 ("/proc/sys/fs/binfmt_misc", None),
791 self.__bindmounts.append(
793 f, self._instroot, dest))
795 self._do_bindmounts()
797 self.__create_minimal_dev()
799 if os.path.exists(self._instroot + "/etc/mtab"):
800 os.unlink(self._instroot + "/etc/mtab")
801 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
805 # get size of available space in 'instroot' fs
806 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
809 """Unmounts the target filesystem.
811 The ImageCreator class detaches the system from the install root, but
812 other subclasses may also detach the loopback mounted filesystem image
813 from the install root.
817 mtab = self._instroot + "/etc/mtab"
818 if not os.path.islink(mtab):
819 os.unlink(self._instroot + "/etc/mtab")
821 if self.qemu_emulator:
822 os.unlink(self._instroot + self.qemu_emulator)
826 self._undo_bindmounts()
828 """ Clean up yum garbage """
830 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
831 if os.path.exists(instroot_pdir):
832 shutil.rmtree(instroot_pdir, ignore_errors = True)
833 yumlibdir = self._instroot + "/var/lib/yum"
834 if os.path.exists(yumlibdir):
835 shutil.rmtree(yumlibdir, ignore_errors = True)
839 self._unmount_instroot()
841 # reset settings of popup dialog in Ubuntu(s)
842 misc.unhide_loopdev_presentation()
846 """Unmounts the target filesystem and deletes temporary files.
848 This method calls unmount() and then deletes any temporary files and
849 directories that were created on the host system while building the
852 Note, make sure to call this method once finished with the creator
853 instance in order to ensure no stale files are left on the host e.g.:
855 creator = ImageCreator(ks, name)
862 if not self.__builddir:
865 kill_proc_inchroot(self._instroot)
869 shutil.rmtree(self.__builddir, ignore_errors = True)
870 self.__builddir = None
872 self.__clean_tmpdir()
874 def __is_excluded_pkg(self, pkg):
875 if pkg in self._excluded_pkgs:
876 self._excluded_pkgs.remove(pkg)
879 for xpkg in self._excluded_pkgs:
880 if xpkg.endswith('*'):
881 if pkg.startswith(xpkg[:-1]):
883 elif xpkg.startswith('*'):
884 if pkg.endswith(xpkg[1:]):
889 def __select_packages(self, pkg_manager):
891 for pkg in self._required_pkgs:
892 e = pkg_manager.selectPackage(pkg)
894 if kickstart.ignore_missing(self.ks):
895 skipped_pkgs.append(pkg)
896 elif self.__is_excluded_pkg(pkg):
897 skipped_pkgs.append(pkg)
899 raise CreatorError("Failed to find package '%s' : %s" %
902 for pkg in skipped_pkgs:
903 msger.warning("Skipping missing package '%s'" % (pkg,))
905 def __select_groups(self, pkg_manager):
907 for group in self._required_groups:
908 e = pkg_manager.selectGroup(group.name, group.include)
910 if kickstart.ignore_missing(self.ks):
911 skipped_groups.append(group)
913 raise CreatorError("Failed to find group '%s' : %s" %
916 for group in skipped_groups:
917 msger.warning("Skipping missing group '%s'" % (group.name,))
919 def __deselect_packages(self, pkg_manager):
920 for pkg in self._excluded_pkgs:
921 pkg_manager.deselectPackage(pkg)
923 def __localinst_packages(self, pkg_manager):
924 for rpm_path in self._get_local_packages():
925 pkg_manager.installLocal(rpm_path)
927 def __preinstall_packages(self, pkg_manager):
931 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
932 for pkg in self._preinstall_pkgs:
933 pkg_manager.preInstall(pkg)
935 def __check_packages(self, pkg_manager):
936 for pkg in self.check_pkgs:
937 pkg_manager.checkPackage(pkg)
939 def __attachment_packages(self, pkg_manager):
943 self._attachment = []
944 for item in kickstart.get_attachment(self.ks):
945 if item.startswith('/'):
946 fpaths = os.path.join(self._instroot, item.lstrip('/'))
947 for fpath in glob.glob(fpaths):
948 self._attachment.append(fpath)
951 filelist = pkg_manager.getFilelist(item)
953 # found rpm in rootfs
954 for pfile in pkg_manager.getFilelist(item):
955 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
956 self._attachment.append(fpath)
959 # try to retrieve rpm file
960 (url, proxies) = pkg_manager.package_url(item)
962 msger.warning("Can't get url from repo for %s" % item)
964 fpath = os.path.join(self.cachedir, os.path.basename(url))
965 if not os.path.exists(fpath):
968 fpath = grabber.myurlgrab(url.full, fpath, proxies, None)
972 tmpdir = self._mkdtemp()
973 misc.extract_rpm(fpath, tmpdir)
974 for (root, dirs, files) in os.walk(tmpdir):
976 fpath = os.path.join(root, fname)
977 self._attachment.append(fpath)
979 def install(self, repo_urls=None):
980 """Install packages into the install root.
982 This function installs the packages listed in the supplied kickstart
983 into the install root. By default, the packages are installed from the
984 repository URLs specified in the kickstart.
986 repo_urls -- a dict which maps a repository name to a repository;
987 if supplied, this causes any repository URLs specified in
988 the kickstart to be overridden.
992 # initialize pkg list to install
994 self.__sanity_check()
996 self._required_pkgs = \
997 kickstart.get_packages(self.ks, self._get_required_packages())
998 self._excluded_pkgs = \
999 kickstart.get_excluded(self.ks, self._get_excluded_packages())
1000 self._required_groups = kickstart.get_groups(self.ks)
1002 self._required_pkgs = None
1003 self._excluded_pkgs = None
1004 self._required_groups = None
1007 repo_urls = self.extrarepos
1009 pkg_manager = self.get_pkg_manager()
1012 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1013 if 'debuginfo' in self.install_pkgs:
1014 pkg_manager.install_debuginfo = True
1016 for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1017 (name, baseurl, mirrorlist, inc, exc,
1018 proxy, proxy_username, proxy_password, debuginfo,
1019 source, gpgkey, disable, ssl_verify, nocache,
1020 cost, priority) = repo
1022 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1023 proxy_username, proxy_password, inc, exc, ssl_verify,
1024 nocache, cost, priority)
1026 if kickstart.exclude_docs(self.ks):
1027 rpm.addMacro("_excludedocs", "1")
1028 rpm.addMacro("_dbpath", "/var/lib/rpm")
1029 rpm.addMacro("__file_context_path", "%{nil}")
1030 if kickstart.inst_langs(self.ks) != None:
1031 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1034 self.__preinstall_packages(pkg_manager)
1035 self.__select_packages(pkg_manager)
1036 self.__select_groups(pkg_manager)
1037 self.__deselect_packages(pkg_manager)
1038 self.__localinst_packages(pkg_manager)
1039 self.__check_packages(pkg_manager)
1041 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1042 checksize = self._root_fs_avail
1044 checksize -= BOOT_SAFEGUARD
1045 if self.target_arch:
1046 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1047 pkg_manager.runInstall(checksize)
1048 except CreatorError, e:
1050 except KeyboardInterrupt:
1053 self._pkgs_content = pkg_manager.getAllContent()
1054 self._pkgs_license = pkg_manager.getPkgsLicense()
1055 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1056 self.__attachment_packages(pkg_manager)
1063 # do some clean up to avoid lvm info leakage. this sucks.
1064 for subdir in ("cache", "backup", "archive"):
1065 lvmdir = self._instroot + "/etc/lvm/" + subdir
1067 for f in os.listdir(lvmdir):
1068 os.unlink(lvmdir + "/" + f)
1072 def postinstall(self):
1073 self.copy_attachment()
1075 def __run_post_scripts(self):
1076 msger.info("Running scripts ...")
1077 if os.path.exists(self._instroot + "/tmp"):
1078 shutil.rmtree(self._instroot + "/tmp")
1079 os.mkdir (self._instroot + "/tmp", 0755)
1080 for s in kickstart.get_post_scripts(self.ks):
1081 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1082 dir = self._instroot + "/tmp")
1084 s.script = s.script.replace("\r", "")
1085 os.write(fd, s.script)
1087 os.chmod(path, 0700)
1089 env = self._get_post_scripts_env(s.inChroot)
1090 if 'PATH' not in env:
1091 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1097 preexec = self._chroot
1098 script = "/tmp/" + os.path.basename(path)
1102 p = subprocess.Popen([s.interp, script],
1103 preexec_fn = preexec,
1105 stdout = subprocess.PIPE,
1106 stderr = subprocess.STDOUT)
1107 for entry in p.communicate()[0].splitlines():
1109 except OSError, (err, msg):
1110 raise CreatorError("Failed to execute %%post script "
1111 "with '%s' : %s" % (s.interp, msg))
1115 def __save_repo_keys(self, repodata):
1119 gpgkeydir = "/etc/pki/rpm-gpg"
1120 fs.makedirs(self._instroot + gpgkeydir)
1121 for repo in repodata:
1123 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1124 shutil.copy(repo["repokey"], self._instroot + repokey)
1126 def configure(self, repodata = None):
1127 """Configure the system image according to the kickstart.
1129 This method applies the (e.g. keyboard or network) configuration
1130 specified in the kickstart and executes the kickstart %post scripts.
1132 If necessary, it also prepares the image to be bootable by e.g.
1133 creating an initrd and bootloader configuration.
1136 ksh = self.ks.handler
1138 msger.info('Applying configurations ...')
1140 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1141 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1142 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1143 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1144 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1145 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1146 kickstart.UserConfig(self._instroot).apply(ksh.user)
1147 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1148 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1149 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1150 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1151 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1152 self.__save_repo_keys(repodata)
1153 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1155 msger.warning("Failed to apply configuration to image")
1158 self._create_bootconfig()
1159 self.__run_post_scripts()
1161 def launch_shell(self, launch):
1162 """Launch a shell in the install root.
1164 This method is launches a bash shell chroot()ed in the install root;
1165 this can be useful for debugging.
1169 msger.info("Launching shell. Exit to continue.")
1170 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1172 def do_genchecksum(self, image_name):
1173 if not self._genchecksum:
1176 md5sum = misc.get_md5sum(image_name)
1177 with open(image_name + ".md5sum", "w") as f:
1178 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1179 self.outimage.append(image_name+".md5sum")
1181 def package(self, destdir = "."):
1182 """Prepares the created image for final delivery.
1184 In its simplest form, this method merely copies the install root to the
1185 supplied destination directory; other subclasses may choose to package
1186 the image by e.g. creating a bootable ISO containing the image and
1187 bootloader configuration.
1189 destdir -- the directory into which the final image should be moved;
1190 this defaults to the current directory.
1193 self._stage_final_image()
1195 if not os.path.exists(destdir):
1196 fs.makedirs(destdir)
1198 if self._recording_pkgs:
1199 self._save_recording_pkgs(destdir)
1201 # For image formats with two or multiple image files, it will be
1202 # better to put them under a directory
1203 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1204 destdir = os.path.join(destdir, "%s-%s" \
1205 % (self.name, self.image_format))
1206 msger.debug("creating destination dir: %s" % destdir)
1207 fs.makedirs(destdir)
1209 # Ensure all data is flushed to _outdir
1210 runner.quiet('sync')
1212 misc.check_space_pre_cp(self._outdir, destdir)
1213 for f in os.listdir(self._outdir):
1214 shutil.move(os.path.join(self._outdir, f),
1215 os.path.join(destdir, f))
1216 self.outimage.append(os.path.join(destdir, f))
1217 self.do_genchecksum(os.path.join(destdir, f))
1219 def print_outimage_info(self):
1220 msg = "The new image can be found here:\n"
1221 self.outimage.sort()
1222 for file in self.outimage:
1223 msg += ' %s\n' % os.path.abspath(file)
1227 def check_depend_tools(self):
1228 for tool in self._dep_checks:
1229 fs.find_binary_path(tool)
1231 def package_output(self, image_format, destdir = ".", package="none"):
1232 if not package or package == "none":
1235 destdir = os.path.abspath(os.path.expanduser(destdir))
1236 (pkg, comp) = os.path.splitext(package)
1238 comp=comp.lstrip(".")
1242 dst = "%s/%s-%s.tar.%s" %\
1243 (destdir, self.name, image_format, comp)
1245 dst = "%s/%s-%s.tar" %\
1246 (destdir, self.name, image_format)
1248 msger.info("creating %s" % dst)
1249 tar = tarfile.open(dst, "w:" + comp)
1251 for file in self.outimage:
1252 msger.info("adding %s to %s" % (file, dst))
1254 arcname=os.path.join("%s-%s" \
1255 % (self.name, image_format),
1256 os.path.basename(file)))
1257 if os.path.isdir(file):
1258 shutil.rmtree(file, ignore_errors = True)
1264 '''All the file in outimage has been packaged into tar.* file'''
1265 self.outimage = [dst]
1267 def release_output(self, config, destdir, release):
1268 """ Create release directory and files
1272 """ release path """
1273 return os.path.join(destdir, fn)
1275 outimages = self.outimage
1278 new_kspath = _rpath(self.name+'.ks')
1279 with open(config) as fr:
1280 with open(new_kspath, "w") as wf:
1281 # When building a release we want to make sure the .ks
1282 # file generates the same build even when --release not used.
1283 wf.write(fr.read().replace("@BUILD_ID@", release))
1284 outimages.append(new_kspath)
1286 # save log file, logfile is only available in creator attrs
1287 if hasattr(self, 'releaselog') and self.releaselog:
1288 final_logfile = _rpath(self.name+'.log')
1289 shutil.move(self.logfile, final_logfile)
1290 self.logfile = final_logfile
1291 outimages.append(self.logfile)
1293 # rename iso and usbimg
1294 for f in os.listdir(destdir):
1295 if f.endswith(".iso"):
1296 newf = f[:-4] + '.img'
1297 elif f.endswith(".usbimg"):
1298 newf = f[:-7] + '.img'
1301 os.rename(_rpath(f), _rpath(newf))
1302 outimages.append(_rpath(newf))
1305 with open(_rpath("MD5SUMS"), "w") as wf:
1306 for f in os.listdir(destdir):
1310 if os.path.isdir(os.path.join(destdir, f)):
1313 md5sum = misc.get_md5sum(_rpath(f))
1314 # There needs to be two spaces between the sum and
1315 # filepath to match the syntax with md5sum.
1316 # This way also md5sum -c MD5SUMS can be used by users
1317 wf.write("%s %s\n" % (md5sum, f))
1319 outimages.append("%s/MD5SUMS" % destdir)
1321 # Filter out the nonexist file
1322 for fp in outimages[:]:
1323 if not os.path.exists("%s" % fp):
1324 outimages.remove(fp)
1326 def copy_kernel(self):
1327 """ Copy kernel files to the outimage directory.
1328 NOTE: This needs to be called before unmounting the instroot.
1331 if not self._need_copy_kernel:
1334 if not os.path.exists(self.destdir):
1335 os.makedirs(self.destdir)
1337 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1338 kernelfilename = "%s/%s-%s" % (self.destdir,
1340 os.path.basename(kernel))
1341 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1343 shutil.copy(kernel, kernelfilename)
1344 self.outimage.append(kernelfilename)
1346 def copy_attachment(self):
1347 """ Subclass implement it to handle attachment files
1348 NOTE: This needs to be called before unmounting the instroot.
1352 def get_pkg_manager(self):
1353 return self.pkgmgr(target_arch = self.target_arch,
1354 instroot = self._instroot,
1355 cachedir = self.cachedir)
1357 def create_manifest(self):
1358 def get_pack_suffix():
1359 return '.' + self.pack_to.split('.', 1)[1]
1361 if not os.path.exists(self.destdir):
1362 os.makedirs(self.destdir)
1364 now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1365 manifest_dict = {'version': VERSION,
1368 manifest_dict.update({'format': self.img_format})
1370 if hasattr(self, 'logfile') and self.logfile:
1371 manifest_dict.update({'log_file': self.logfile})
1373 if self.image_files:
1375 self.image_files.update({'pack': get_pack_suffix()})
1376 manifest_dict.update({self.img_format: self.image_files})
1378 msger.info('Creating manifest file...')
1379 manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1380 with open(manifest_file_path, 'w') as fest_file:
1381 json.dump(manifest_dict, fest_file, indent=4)
1382 self.outimage.append(manifest_file_path)