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.
991 def get_ssl_verify(ssl_verify=None):
992 if ssl_verify is not None:
993 return not ssl_verify.lower().strip() == 'no'
995 return not self.ssl_verify.lower().strip() == 'no'
997 # initialize pkg list to install
999 self.__sanity_check()
1001 self._required_pkgs = \
1002 kickstart.get_packages(self.ks, self._get_required_packages())
1003 self._excluded_pkgs = \
1004 kickstart.get_excluded(self.ks, self._get_excluded_packages())
1005 self._required_groups = kickstart.get_groups(self.ks)
1007 self._required_pkgs = None
1008 self._excluded_pkgs = None
1009 self._required_groups = None
1012 repo_urls = self.extrarepos
1014 pkg_manager = self.get_pkg_manager()
1017 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1018 if 'debuginfo' in self.install_pkgs:
1019 pkg_manager.install_debuginfo = True
1021 for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1022 (name, baseurl, mirrorlist, inc, exc,
1023 proxy, proxy_username, proxy_password, debuginfo,
1024 source, gpgkey, disable, ssl_verify, nocache,
1025 cost, priority) = repo
1027 ssl_verify = get_ssl_verify(ssl_verify)
1028 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1029 proxy_username, proxy_password, inc, exc, ssl_verify,
1030 nocache, cost, priority)
1032 if kickstart.exclude_docs(self.ks):
1033 rpm.addMacro("_excludedocs", "1")
1034 rpm.addMacro("_dbpath", "/var/lib/rpm")
1035 rpm.addMacro("__file_context_path", "%{nil}")
1036 if kickstart.inst_langs(self.ks) != None:
1037 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1040 self.__preinstall_packages(pkg_manager)
1041 self.__select_packages(pkg_manager)
1042 self.__select_groups(pkg_manager)
1043 self.__deselect_packages(pkg_manager)
1044 self.__localinst_packages(pkg_manager)
1045 self.__check_packages(pkg_manager)
1047 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1048 checksize = self._root_fs_avail
1050 checksize -= BOOT_SAFEGUARD
1051 if self.target_arch:
1052 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1053 pkg_manager.runInstall(checksize)
1054 except CreatorError, e:
1056 except KeyboardInterrupt:
1059 self._pkgs_content = pkg_manager.getAllContent()
1060 self._pkgs_license = pkg_manager.getPkgsLicense()
1061 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1062 self.__attachment_packages(pkg_manager)
1069 # do some clean up to avoid lvm info leakage. this sucks.
1070 for subdir in ("cache", "backup", "archive"):
1071 lvmdir = self._instroot + "/etc/lvm/" + subdir
1073 for f in os.listdir(lvmdir):
1074 os.unlink(lvmdir + "/" + f)
1078 def postinstall(self):
1079 self.copy_attachment()
1081 def __run_post_scripts(self):
1082 msger.info("Running scripts ...")
1083 if os.path.exists(self._instroot + "/tmp"):
1084 shutil.rmtree(self._instroot + "/tmp")
1085 os.mkdir (self._instroot + "/tmp", 0755)
1086 for s in kickstart.get_post_scripts(self.ks):
1087 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1088 dir = self._instroot + "/tmp")
1090 s.script = s.script.replace("\r", "")
1091 os.write(fd, s.script)
1093 os.chmod(path, 0700)
1095 env = self._get_post_scripts_env(s.inChroot)
1096 if 'PATH' not in env:
1097 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1103 preexec = self._chroot
1104 script = "/tmp/" + os.path.basename(path)
1108 p = subprocess.Popen([s.interp, script],
1109 preexec_fn = preexec,
1111 stdout = subprocess.PIPE,
1112 stderr = subprocess.STDOUT)
1113 for entry in p.communicate()[0].splitlines():
1115 except OSError, (err, msg):
1116 raise CreatorError("Failed to execute %%post script "
1117 "with '%s' : %s" % (s.interp, msg))
1121 def __save_repo_keys(self, repodata):
1125 gpgkeydir = "/etc/pki/rpm-gpg"
1126 fs.makedirs(self._instroot + gpgkeydir)
1127 for repo in repodata:
1129 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1130 shutil.copy(repo["repokey"], self._instroot + repokey)
1132 def configure(self, repodata = None):
1133 """Configure the system image according to the kickstart.
1135 This method applies the (e.g. keyboard or network) configuration
1136 specified in the kickstart and executes the kickstart %post scripts.
1138 If necessary, it also prepares the image to be bootable by e.g.
1139 creating an initrd and bootloader configuration.
1142 ksh = self.ks.handler
1144 msger.info('Applying configurations ...')
1146 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1147 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1148 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1149 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1150 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1151 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1152 kickstart.UserConfig(self._instroot).apply(ksh.user)
1153 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1154 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1155 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1156 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1157 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1158 self.__save_repo_keys(repodata)
1159 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1161 msger.warning("Failed to apply configuration to image")
1164 self._create_bootconfig()
1165 self.__run_post_scripts()
1167 def launch_shell(self, launch):
1168 """Launch a shell in the install root.
1170 This method is launches a bash shell chroot()ed in the install root;
1171 this can be useful for debugging.
1175 msger.info("Launching shell. Exit to continue.")
1176 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1178 def do_genchecksum(self, image_name):
1179 if not self._genchecksum:
1182 md5sum = misc.get_md5sum(image_name)
1183 with open(image_name + ".md5sum", "w") as f:
1184 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1185 self.outimage.append(image_name+".md5sum")
1187 def package(self, destdir = "."):
1188 """Prepares the created image for final delivery.
1190 In its simplest form, this method merely copies the install root to the
1191 supplied destination directory; other subclasses may choose to package
1192 the image by e.g. creating a bootable ISO containing the image and
1193 bootloader configuration.
1195 destdir -- the directory into which the final image should be moved;
1196 this defaults to the current directory.
1199 self._stage_final_image()
1201 if not os.path.exists(destdir):
1202 fs.makedirs(destdir)
1204 if self._recording_pkgs:
1205 self._save_recording_pkgs(destdir)
1207 # For image formats with two or multiple image files, it will be
1208 # better to put them under a directory
1209 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1210 destdir = os.path.join(destdir, "%s-%s" \
1211 % (self.name, self.image_format))
1212 msger.debug("creating destination dir: %s" % destdir)
1213 fs.makedirs(destdir)
1215 # Ensure all data is flushed to _outdir
1216 runner.quiet('sync')
1218 misc.check_space_pre_cp(self._outdir, destdir)
1219 for f in os.listdir(self._outdir):
1220 shutil.move(os.path.join(self._outdir, f),
1221 os.path.join(destdir, f))
1222 self.outimage.append(os.path.join(destdir, f))
1223 self.do_genchecksum(os.path.join(destdir, f))
1225 def print_outimage_info(self):
1226 msg = "The new image can be found here:\n"
1227 self.outimage.sort()
1228 for file in self.outimage:
1229 msg += ' %s\n' % os.path.abspath(file)
1233 def check_depend_tools(self):
1234 for tool in self._dep_checks:
1235 fs.find_binary_path(tool)
1237 def package_output(self, image_format, destdir = ".", package="none"):
1238 if not package or package == "none":
1241 destdir = os.path.abspath(os.path.expanduser(destdir))
1242 (pkg, comp) = os.path.splitext(package)
1244 comp=comp.lstrip(".")
1248 dst = "%s/%s-%s.tar.%s" %\
1249 (destdir, self.name, image_format, comp)
1251 dst = "%s/%s-%s.tar" %\
1252 (destdir, self.name, image_format)
1254 msger.info("creating %s" % dst)
1255 tar = tarfile.open(dst, "w:" + comp)
1257 for file in self.outimage:
1258 msger.info("adding %s to %s" % (file, dst))
1260 arcname=os.path.join("%s-%s" \
1261 % (self.name, image_format),
1262 os.path.basename(file)))
1263 if os.path.isdir(file):
1264 shutil.rmtree(file, ignore_errors = True)
1270 '''All the file in outimage has been packaged into tar.* file'''
1271 self.outimage = [dst]
1273 def release_output(self, config, destdir, release):
1274 """ Create release directory and files
1278 """ release path """
1279 return os.path.join(destdir, fn)
1281 outimages = self.outimage
1284 new_kspath = _rpath(self.name+'.ks')
1285 with open(config) as fr:
1286 with open(new_kspath, "w") as wf:
1287 # When building a release we want to make sure the .ks
1288 # file generates the same build even when --release not used.
1289 wf.write(fr.read().replace("@BUILD_ID@", release))
1290 outimages.append(new_kspath)
1292 # save log file, logfile is only available in creator attrs
1293 if hasattr(self, 'releaselog') and self.releaselog:
1294 final_logfile = _rpath(self.name+'.log')
1295 shutil.move(self.logfile, final_logfile)
1296 self.logfile = final_logfile
1297 outimages.append(self.logfile)
1299 # rename iso and usbimg
1300 for f in os.listdir(destdir):
1301 if f.endswith(".iso"):
1302 newf = f[:-4] + '.img'
1303 elif f.endswith(".usbimg"):
1304 newf = f[:-7] + '.img'
1307 os.rename(_rpath(f), _rpath(newf))
1308 outimages.append(_rpath(newf))
1311 with open(_rpath("MD5SUMS"), "w") as wf:
1312 for f in os.listdir(destdir):
1316 if os.path.isdir(os.path.join(destdir, f)):
1319 md5sum = misc.get_md5sum(_rpath(f))
1320 # There needs to be two spaces between the sum and
1321 # filepath to match the syntax with md5sum.
1322 # This way also md5sum -c MD5SUMS can be used by users
1323 wf.write("%s %s\n" % (md5sum, f))
1325 outimages.append("%s/MD5SUMS" % destdir)
1327 # Filter out the nonexist file
1328 for fp in outimages[:]:
1329 if not os.path.exists("%s" % fp):
1330 outimages.remove(fp)
1332 def copy_kernel(self):
1333 """ Copy kernel files to the outimage directory.
1334 NOTE: This needs to be called before unmounting the instroot.
1337 if not self._need_copy_kernel:
1340 if not os.path.exists(self.destdir):
1341 os.makedirs(self.destdir)
1343 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1344 kernelfilename = "%s/%s-%s" % (self.destdir,
1346 os.path.basename(kernel))
1347 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1349 shutil.copy(kernel, kernelfilename)
1350 self.outimage.append(kernelfilename)
1352 def copy_attachment(self):
1353 """ Subclass implement it to handle attachment files
1354 NOTE: This needs to be called before unmounting the instroot.
1358 def get_pkg_manager(self):
1359 return self.pkgmgr(target_arch = self.target_arch,
1360 instroot = self._instroot,
1361 cachedir = self.cachedir)
1363 def create_manifest(self):
1364 def get_pack_suffix():
1365 return '.' + self.pack_to.split('.', 1)[1]
1367 if not os.path.exists(self.destdir):
1368 os.makedirs(self.destdir)
1370 now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1371 manifest_dict = {'version': VERSION,
1374 manifest_dict.update({'format': self.img_format})
1376 if hasattr(self, 'logfile') and self.logfile:
1377 manifest_dict.update({'log_file': self.logfile})
1379 if self.image_files:
1381 self.image_files.update({'pack': get_pack_suffix()})
1382 manifest_dict.update({self.img_format: self.image_files})
1384 msger.info('Creating manifest file...')
1385 manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1386 with open(manifest_file_path, 'w') as fest_file:
1387 json.dump(manifest_dict, fest_file, indent=4)
1388 self.outimage.append(manifest_file_path)