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.strict_mode = False
86 self._local_pkgs_path = None
89 self.multiple_partitions = False
91 # If the kernel is save to the destdir when copy_kernel cmd is called.
92 self._need_copy_kernel = False
93 # setup tmpfs tmpdir when enabletmpfs is True
94 self.enabletmpfs = False
97 # Mapping table for variables that have different names.
98 optmap = {"pkgmgr" : "pkgmgr_name",
99 "arch" : "target_arch",
100 "local_pkgs_path" : "_local_pkgs_path",
101 "copy_kernel" : "_need_copy_kernel",
102 "strict_mode" : "strict_mode",
105 # update setting from createopts
106 for key in createopts.keys():
111 setattr(self, option, createopts[key])
113 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
116 if '@NAME@' in self.pack_to:
117 self.pack_to = self.pack_to.replace('@NAME@', self.name)
118 (tar, ext) = os.path.splitext(self.pack_to)
119 if ext in (".gz", ".bz2", ".lzo", ".bz") and tar.endswith(".tar"):
121 if ext not in get_archive_suffixes():
122 self.pack_to += ".tar"
124 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
126 # Output image file names
128 # Output info related with manifest
129 self.image_files = {}
130 # A flag to generate checksum
131 self._genchecksum = False
133 self._alt_initrd_name = None
135 self._recording_pkgs = []
137 # available size in root fs, init to 0
138 self._root_fs_avail = 0
140 # Name of the disk image file that is created.
141 self._img_name = None
143 self.image_format = None
145 # Save qemu emulator file name in order to clean up it finally
146 self.qemu_emulator = None
148 # No ks provided when called by convertor, so skip the dependency check
150 # If we have btrfs partition we need to check necessary tools
151 for part in self.ks.handler.partition.partitions:
152 if part.fstype and part.fstype == "btrfs":
153 self._dep_checks.append("mkfs.btrfs")
155 if part.fstype == "cpio":
157 if len(self.ks.handler.partition.partitions) > 1:
158 self.multiple_partitions = True
161 if self.target_arch.startswith("arm"):
162 for dep in self._dep_checks:
163 if dep == "extlinux":
164 self._dep_checks.remove(dep)
166 if not os.path.exists("/usr/bin/qemu-arm") or \
167 not misc.is_statically_linked("/usr/bin/qemu-arm"):
168 self._dep_checks.append("qemu-arm-static")
170 if os.path.exists("/proc/sys/vm/vdso_enabled"):
171 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
172 vdso_value = vdso_fh.read().strip()
174 if (int)(vdso_value) == 1:
175 msger.warning("vdso is enabled on your host, which might "
176 "cause problems with arm emulations.\n"
177 "\tYou can disable vdso with following command before "
178 "starting image build:\n"
179 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
180 elif self.target_arch == "mipsel":
181 for dep in self._dep_checks:
182 if dep == "extlinux":
183 self._dep_checks.remove(dep)
185 if not os.path.exists("/usr/bin/qemu-mipsel") or \
186 not misc.is_statically_linked("/usr/bin/qemu-mipsel"):
187 self._dep_checks.append("qemu-mipsel-static")
189 if os.path.exists("/proc/sys/vm/vdso_enabled"):
190 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
191 vdso_value = vdso_fh.read().strip()
193 if (int)(vdso_value) == 1:
194 msger.warning("vdso is enabled on your host, which might "
195 "cause problems with mipsel emulations.\n"
196 "\tYou can disable vdso with following command before "
197 "starting image build:\n"
198 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
200 # make sure the specified tmpdir and cachedir exist
201 if not os.path.exists(self.tmpdir):
202 os.makedirs(self.tmpdir)
203 if not os.path.exists(self.cachedir):
204 os.makedirs(self.cachedir)
210 def __get_instroot(self):
211 if self.__builddir is None:
212 raise CreatorError("_instroot is not valid before calling mount()")
213 return self.__builddir + "/install_root"
214 _instroot = property(__get_instroot)
215 """The location of the install root directory.
217 This is the directory into which the system is installed. Subclasses may
218 mount a filesystem image here or copy files to/from here.
220 Note, this directory does not exist before ImageCreator.mount() is called.
222 Note also, this is a read-only attribute.
226 def __get_outdir(self):
227 if self.__builddir is None:
228 raise CreatorError("_outdir is not valid before calling mount()")
229 return self.__builddir + "/out"
230 _outdir = property(__get_outdir)
231 """The staging location for the final image.
233 This is where subclasses should stage any files that are part of the final
234 image. ImageCreator.package() will copy any files found here into the
235 requested destination directory.
237 Note, this directory does not exist before ImageCreator.mount() is called.
239 Note also, this is a read-only attribute.
245 # Hooks for subclasses
247 def _mount_instroot(self, base_on = None):
248 """Mount or prepare the install root directory.
250 This is the hook where subclasses may prepare the install root by e.g.
251 mounting creating and loopback mounting a filesystem image to
254 There is no default implementation.
256 base_on -- this is the value passed to mount() and can be interpreted
257 as the subclass wishes; it might e.g. be the location of
258 a previously created ISO containing a system image.
263 def _unmount_instroot(self):
264 """Undo anything performed in _mount_instroot().
266 This is the hook where subclasses must undo anything which was done
267 in _mount_instroot(). For example, if a filesystem image was mounted
268 onto _instroot, it should be unmounted here.
270 There is no default implementation.
275 def _create_bootconfig(self):
276 """Configure the image so that it's bootable.
278 This is the hook where subclasses may prepare the image for booting by
279 e.g. creating an initramfs and bootloader configuration.
281 This hook is called while the install root is still mounted, after the
282 packages have been installed and the kickstart configuration has been
283 applied, but before the %post scripts have been executed.
285 There is no default implementation.
290 def _stage_final_image(self):
291 """Stage the final system image in _outdir.
293 This is the hook where subclasses should place the image in _outdir
294 so that package() can copy it to the requested destination directory.
296 By default, this moves the install root into _outdir.
299 shutil.move(self._instroot, self._outdir + "/" + self.name)
301 def get_installed_packages(self):
302 return self._pkgs_content.keys()
304 def _save_recording_pkgs(self, destdir):
305 """Save the list or content of installed packages to file.
307 pkgs = self._pkgs_content.keys()
308 pkgs.sort() # inplace op
310 if not os.path.exists(destdir):
314 if 'vcs' in self._recording_pkgs:
315 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
316 content = '\n'.join(sorted(vcslst))
317 elif 'name' in self._recording_pkgs:
318 content = '\n'.join(pkgs)
320 namefile = os.path.join(destdir, self.name + '.packages')
321 f = open(namefile, "w")
324 self.outimage.append(namefile);
326 # if 'content', save more details
327 if 'content' in self._recording_pkgs:
328 contfile = os.path.join(destdir, self.name + '.files')
329 f = open(contfile, "w")
334 pkgcont = self._pkgs_content[pkg]
336 content += '\n '.join(pkgcont)
342 self.outimage.append(contfile)
344 if 'license' in self._recording_pkgs:
345 licensefile = os.path.join(destdir, self.name + '.license')
346 f = open(licensefile, "w")
348 f.write('Summary:\n')
349 for license in reversed(sorted(self._pkgs_license, key=\
350 lambda license: len(self._pkgs_license[license]))):
351 f.write(" - %s: %s\n" \
352 % (license, len(self._pkgs_license[license])))
354 f.write('\nDetails:\n')
355 for license in reversed(sorted(self._pkgs_license, key=\
356 lambda license: len(self._pkgs_license[license]))):
357 f.write(" - %s:\n" % (license))
358 for pkg in sorted(self._pkgs_license[license]):
359 f.write(" - %s\n" % (pkg))
363 self.outimage.append(licensefile)
365 def _get_required_packages(self):
366 """Return a list of required packages.
368 This is the hook where subclasses may specify a set of packages which
369 it requires to be installed.
371 This returns an empty list by default.
373 Note, subclasses should usually chain up to the base class
374 implementation of this hook.
379 def _get_excluded_packages(self):
380 """Return a list of excluded packages.
382 This is the hook where subclasses may specify a set of packages which
383 it requires _not_ to be installed.
385 This returns an empty list by default.
387 Note, subclasses should usually chain up to the base class
388 implementation of this hook.
393 def _get_local_packages(self):
394 """Return a list of rpm path to be local installed.
396 This is the hook where subclasses may specify a set of rpms which
397 it requires to be installed locally.
399 This returns an empty list by default.
401 Note, subclasses should usually chain up to the base class
402 implementation of this hook.
405 if self._local_pkgs_path:
406 if os.path.isdir(self._local_pkgs_path):
408 os.path.join(self._local_pkgs_path, '*.rpm'))
409 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
410 return [self._local_pkgs_path]
414 def _get_fstab(self):
415 """Return the desired contents of /etc/fstab.
417 This is the hook where subclasses may specify the contents of
418 /etc/fstab by returning a string containing the desired contents.
420 A sensible default implementation is provided.
423 s = "/dev/root / %s %s 0 0\n" \
425 "defaults,noatime" if not self._fsopts else self._fsopts)
426 s += self._get_fstab_special()
429 def _get_fstab_special(self):
430 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
431 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
432 s += "proc /proc proc defaults 0 0\n"
433 s += "sysfs /sys sysfs defaults 0 0\n"
436 def _set_part_env(self, pnum, prop, value):
437 """ This is a helper function which generates an environment variable
438 for a property "prop" with value "value" of a partition number "pnum".
440 The naming convention is:
441 * Variables start with INSTALLERFW_PART
442 * Then goes the partition number, the order is the same as
443 specified in the KS file
444 * Then goes the property name
452 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
453 return { name : value }
455 def _get_post_scripts_env(self, in_chroot):
456 """Return an environment dict for %post scripts.
458 This is the hook where subclasses may specify some environment
459 variables for %post scripts by return a dict containing the desired
462 in_chroot -- whether this %post script is to be executed chroot()ed
469 for p in kickstart.get_partitions(self.ks):
470 env.update(self._set_part_env(pnum, "SIZE", p.size))
471 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
472 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
473 env.update(self._set_part_env(pnum, "LABEL", p.label))
474 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
475 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
476 env.update(self._set_part_env(pnum, "ALIGN", p.align))
477 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
478 env.update(self._set_part_env(pnum, "UUID", p.uuid))
479 env.update(self._set_part_env(pnum, "DEVNODE",
480 "/dev/%s%d" % (p.disk, pnum + 1)))
481 env.update(self._set_part_env(pnum, "DISK_DEVNODE",
486 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
488 # Partition table format
489 ptable_format = self.ks.handler.bootloader.ptable
490 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
492 # The kerned boot parameters
493 kernel_opts = self.ks.handler.bootloader.appendLine
494 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
496 # Name of the image creation tool
497 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
499 # The real current location of the mounted file-systems
503 mount_prefix = self._instroot
504 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
506 # These are historical variables which lack the common name prefix
508 env["INSTALL_ROOT"] = self._instroot
509 env["IMG_NAME"] = self._name
513 def __get_imgname(self):
515 _name = property(__get_imgname)
516 """The name of the image file.
520 def _get_kernel_versions(self):
521 """Return a dict detailing the available kernel types/versions.
523 This is the hook where subclasses may override what kernel types and
524 versions should be available for e.g. creating the booloader
527 A dict should be returned mapping the available kernel types to a list
528 of the available versions for those kernels.
530 The default implementation uses rpm to iterate over everything
531 providing 'kernel', finds /boot/vmlinuz-* and returns the version
532 obtained from the vmlinuz filename. (This can differ from the kernel
533 RPM's n-v-r in the case of e.g. xen)
536 def get_kernel_versions(instroot):
539 files = glob.glob(instroot + "/boot/vmlinuz-*")
541 version = os.path.basename(file)[8:]
544 versions.add(version)
545 ret["kernel"] = list(versions)
548 def get_version(header):
550 for f in header['filenames']:
551 if f.startswith('/boot/vmlinuz-'):
556 return get_kernel_versions(self._instroot)
558 ts = rpm.TransactionSet(self._instroot)
561 for header in ts.dbMatch('provides', 'kernel'):
562 version = get_version(header)
566 name = header['name']
568 ret[name] = [version]
569 elif not version in ret[name]:
570 ret[name].append(version)
576 # Helpers for subclasses
578 def _do_bindmounts(self):
579 """Mount various system directories onto _instroot.
581 This method is called by mount(), but may also be used by subclasses
582 in order to re-mount the bindmounts after modifying the underlying
586 for b in self.__bindmounts:
589 def _undo_bindmounts(self):
590 """Unmount the bind-mounted system directories from _instroot.
592 This method is usually only called by unmount(), but may also be used
593 by subclasses in order to gain access to the filesystem obscured by
594 the bindmounts - e.g. in order to create device nodes on the image
598 self.__bindmounts.reverse()
599 for b in self.__bindmounts:
603 """Chroot into the install root.
605 This method may be used by subclasses when executing programs inside
606 the install root e.g.
608 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
611 os.chroot(self._instroot)
614 def _mkdtemp(self, prefix = "tmp-"):
615 """Create a temporary directory.
617 This method may be used by subclasses to create a temporary directory
618 for use in building the final image - e.g. a subclass might create
619 a temporary directory in order to bundle a set of files into a package.
621 The subclass may delete this directory if it wishes, but it will be
622 automatically deleted by cleanup().
624 The absolute path to the temporary directory is returned.
626 Note, this method should only be called after mount() has been called.
628 prefix -- a prefix which should be used when creating the directory;
632 self.__ensure_builddir()
633 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
635 def _mkstemp(self, prefix = "tmp-"):
636 """Create a temporary file.
638 This method may be used by subclasses to create a temporary file
639 for use in building the final image - e.g. a subclass might need
640 a temporary location to unpack a compressed file.
642 The subclass may delete this file if it wishes, but it will be
643 automatically deleted by cleanup().
645 A tuple containing a file descriptor (returned from os.open() and the
646 absolute path to the temporary directory is returned.
648 Note, this method should only be called after mount() has been called.
650 prefix -- a prefix which should be used when creating the file;
654 self.__ensure_builddir()
655 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
657 def _mktemp(self, prefix = "tmp-"):
658 """Create a temporary file.
660 This method simply calls _mkstemp() and closes the returned file
663 The absolute path to the temporary file is returned.
665 Note, this method should only be called after mount() has been called.
667 prefix -- a prefix which should be used when creating the file;
672 (f, path) = self._mkstemp(prefix)
678 # Actual implementation
680 def __ensure_builddir(self):
681 if not self.__builddir is None:
685 self.workdir = os.path.join(self.tmpdir, "build")
686 if not os.path.exists(self.workdir):
687 os.makedirs(self.workdir)
688 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
689 prefix = "imgcreate-")
690 except OSError, (err, msg):
691 raise CreatorError("Failed create build directory in %s: %s" %
694 def get_cachedir(self, cachedir = None):
698 self.__ensure_builddir()
700 self.cachedir = cachedir
702 self.cachedir = self.__builddir + "/mic-cache"
703 fs.makedirs(self.cachedir)
706 def __sanity_check(self):
707 """Ensure that the config we've been given is sane."""
708 if not (kickstart.get_packages(self.ks) or
709 kickstart.get_groups(self.ks)):
710 raise CreatorError("No packages or groups specified")
712 kickstart.convert_method_to_repo(self.ks)
714 if not kickstart.get_repos(self.ks):
715 raise CreatorError("No repositories specified")
717 def __write_fstab(self):
718 if kickstart.use_installerfw(self.ks, "fstab"):
719 # The fstab file will be generated by installer framework scripts
722 fstab_contents = self._get_fstab()
724 fstab = open(self._instroot + "/etc/fstab", "w")
725 fstab.write(fstab_contents)
728 def __create_minimal_dev(self):
729 """Create a minimal /dev so that we don't corrupt the host /dev"""
730 origumask = os.umask(0000)
731 devices = (('null', 1, 3, 0666),
732 ('urandom',1, 9, 0666),
733 ('random', 1, 8, 0666),
734 ('full', 1, 7, 0666),
735 ('ptmx', 5, 2, 0666),
737 ('zero', 1, 5, 0666))
739 links = (("/proc/self/fd", "/dev/fd"),
740 ("/proc/self/fd/0", "/dev/stdin"),
741 ("/proc/self/fd/1", "/dev/stdout"),
742 ("/proc/self/fd/2", "/dev/stderr"))
744 for (node, major, minor, perm) in devices:
745 if not os.path.exists(self._instroot + "/dev/" + node):
746 os.mknod(self._instroot + "/dev/" + node,
748 os.makedev(major,minor))
750 for (src, dest) in links:
751 if not os.path.exists(self._instroot + dest):
752 os.symlink(src, self._instroot + dest)
756 def __setup_tmpdir(self):
757 if not self.enabletmpfs:
760 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
762 def __clean_tmpdir(self):
763 if not self.enabletmpfs:
766 runner.show('umount -l %s' % self.workdir)
768 def mount(self, base_on = None, cachedir = None):
769 """Setup the target filesystem in preparation for an install.
771 This function sets up the filesystem which the ImageCreator will
772 install into and configure. The ImageCreator class merely creates an
773 install root directory, bind mounts some system directories (e.g. /dev)
774 and writes out /etc/fstab. Other subclasses may also e.g. create a
775 sparse file, format it and loopback mount it to the install root.
777 base_on -- a previous install on which to base this install; defaults
778 to None, causing a new image to be created
780 cachedir -- a directory in which to store the Yum cache; defaults to
781 None, causing a new cache to be created; by setting this
782 to another directory, the same cache can be reused across
786 self.__setup_tmpdir()
787 self.__ensure_builddir()
789 # prevent popup dialog in Ubuntu(s)
790 misc.hide_loopdev_presentation()
792 fs.makedirs(self._instroot)
793 fs.makedirs(self._outdir)
795 self._mount_instroot(base_on)
797 for d in ("/dev/pts",
804 fs.makedirs(self._instroot + d)
806 if self.target_arch and self.target_arch.startswith("arm") or \
807 self.target_arch == "aarch64":
808 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
811 self.get_cachedir(cachedir)
813 # bind mount system directories into _instroot
814 for (f, dest) in [("/sys", None),
816 ("/proc/sys/fs/binfmt_misc", None),
818 self.__bindmounts.append(
820 f, self._instroot, dest))
822 self._do_bindmounts()
824 self.__create_minimal_dev()
826 if os.path.exists(self._instroot + "/etc/mtab"):
827 os.unlink(self._instroot + "/etc/mtab")
828 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
832 # get size of available space in 'instroot' fs
833 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
836 """Unmounts the target filesystem.
838 The ImageCreator class detaches the system from the install root, but
839 other subclasses may also detach the loopback mounted filesystem image
840 from the install root.
844 mtab = self._instroot + "/etc/mtab"
845 if not os.path.islink(mtab):
846 os.unlink(self._instroot + "/etc/mtab")
848 if self.qemu_emulator:
849 os.unlink(self._instroot + self.qemu_emulator)
853 self._undo_bindmounts()
855 """ Clean up yum garbage """
857 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
858 if os.path.exists(instroot_pdir):
859 shutil.rmtree(instroot_pdir, ignore_errors = True)
860 yumlibdir = self._instroot + "/var/lib/yum"
861 if os.path.exists(yumlibdir):
862 shutil.rmtree(yumlibdir, ignore_errors = True)
866 self._unmount_instroot()
868 # reset settings of popup dialog in Ubuntu(s)
869 misc.unhide_loopdev_presentation()
873 """Unmounts the target filesystem and deletes temporary files.
875 This method calls unmount() and then deletes any temporary files and
876 directories that were created on the host system while building the
879 Note, make sure to call this method once finished with the creator
880 instance in order to ensure no stale files are left on the host e.g.:
882 creator = ImageCreator(ks, name)
889 if not self.__builddir:
892 kill_proc_inchroot(self._instroot)
896 shutil.rmtree(self.__builddir, ignore_errors = True)
897 self.__builddir = None
899 self.__clean_tmpdir()
901 def __is_excluded_pkg(self, pkg):
902 if pkg in self._excluded_pkgs:
903 self._excluded_pkgs.remove(pkg)
906 for xpkg in self._excluded_pkgs:
907 if xpkg.endswith('*'):
908 if pkg.startswith(xpkg[:-1]):
910 elif xpkg.startswith('*'):
911 if pkg.endswith(xpkg[1:]):
916 def __select_packages(self, pkg_manager):
918 for pkg in self._required_pkgs:
919 e = pkg_manager.selectPackage(pkg)
921 if kickstart.ignore_missing(self.ks):
922 skipped_pkgs.append(pkg)
923 elif self.__is_excluded_pkg(pkg):
924 skipped_pkgs.append(pkg)
926 raise CreatorError("Failed to find package '%s' : %s" %
929 for pkg in skipped_pkgs:
930 msger.warning("Skipping missing package '%s'" % (pkg,))
932 def __select_groups(self, pkg_manager):
934 for group in self._required_groups:
935 e = pkg_manager.selectGroup(group.name, group.include)
937 if kickstart.ignore_missing(self.ks):
938 skipped_groups.append(group)
940 raise CreatorError("Failed to find group '%s' : %s" %
943 for group in skipped_groups:
944 msger.warning("Skipping missing group '%s'" % (group.name,))
946 def __deselect_packages(self, pkg_manager):
947 for pkg in self._excluded_pkgs:
948 pkg_manager.deselectPackage(pkg)
950 def __localinst_packages(self, pkg_manager):
951 for rpm_path in self._get_local_packages():
952 pkg_manager.installLocal(rpm_path)
954 def __preinstall_packages(self, pkg_manager):
958 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
959 for pkg in self._preinstall_pkgs:
960 pkg_manager.preInstall(pkg)
962 def __check_packages(self, pkg_manager):
963 for pkg in self.check_pkgs:
964 pkg_manager.checkPackage(pkg)
966 def __attachment_packages(self, pkg_manager):
970 self._attachment = []
971 for item in kickstart.get_attachment(self.ks):
972 if item.startswith('/'):
973 fpaths = os.path.join(self._instroot, item.lstrip('/'))
974 for fpath in glob.glob(fpaths):
975 self._attachment.append(fpath)
978 filelist = pkg_manager.getFilelist(item)
980 # found rpm in rootfs
981 for pfile in pkg_manager.getFilelist(item):
982 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
983 self._attachment.append(fpath)
986 # try to retrieve rpm file
987 (url, proxies) = pkg_manager.package_url(item)
989 msger.warning("Can't get url from repo for %s" % item)
991 fpath = os.path.join(self.cachedir, os.path.basename(url))
992 if not os.path.exists(fpath):
995 fpath = grabber.myurlgrab(url.full, fpath, proxies, None)
999 tmpdir = self._mkdtemp()
1000 misc.extract_rpm(fpath, tmpdir)
1001 for (root, dirs, files) in os.walk(tmpdir):
1003 fpath = os.path.join(root, fname)
1004 self._attachment.append(fpath)
1006 def install(self, repo_urls=None):
1007 """Install packages into the install root.
1009 This function installs the packages listed in the supplied kickstart
1010 into the install root. By default, the packages are installed from the
1011 repository URLs specified in the kickstart.
1013 repo_urls -- a dict which maps a repository name to a repository;
1014 if supplied, this causes any repository URLs specified in
1015 the kickstart to be overridden.
1018 def checkScriptletError(dirname, suffix):
1019 if os.path.exists(dirname):
1020 list = os.listdir(dirname)
1022 filepath = os.path.join(dirname, line)
1023 if os.path.isfile(filepath) and 0 < line.find(suffix):
1030 def showErrorInfo(filepath):
1031 if os.path.isfile(filepath):
1032 for line in open(filepath):
1033 msger.info("The error install package info: %s" % line)
1035 msger.info("%s is not found." % filepath)
1037 def get_ssl_verify(ssl_verify=None):
1038 if ssl_verify is not None:
1039 return not ssl_verify.lower().strip() == 'no'
1041 return not self.ssl_verify.lower().strip() == 'no'
1043 # initialize pkg list to install
1045 self.__sanity_check()
1047 self._required_pkgs = \
1048 kickstart.get_packages(self.ks, self._get_required_packages())
1049 self._excluded_pkgs = \
1050 kickstart.get_excluded(self.ks, self._get_excluded_packages())
1051 self._required_groups = kickstart.get_groups(self.ks)
1053 self._required_pkgs = None
1054 self._excluded_pkgs = None
1055 self._required_groups = None
1058 repo_urls = self.extrarepos
1060 pkg_manager = self.get_pkg_manager()
1063 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1064 if 'debuginfo' in self.install_pkgs:
1065 pkg_manager.install_debuginfo = True
1067 for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1068 (name, baseurl, mirrorlist, inc, exc,
1069 proxy, proxy_username, proxy_password, debuginfo,
1070 source, gpgkey, disable, ssl_verify, nocache,
1071 cost, priority) = repo
1073 ssl_verify = get_ssl_verify(ssl_verify)
1074 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1075 proxy_username, proxy_password, inc, exc, ssl_verify,
1076 nocache, cost, priority)
1078 if kickstart.exclude_docs(self.ks):
1079 rpm.addMacro("_excludedocs", "1")
1080 rpm.addMacro("_dbpath", "/var/lib/rpm")
1081 rpm.addMacro("__file_context_path", "%{nil}")
1082 if kickstart.inst_langs(self.ks) != None:
1083 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1086 self.__preinstall_packages(pkg_manager)
1087 self.__select_packages(pkg_manager)
1088 self.__select_groups(pkg_manager)
1089 self.__deselect_packages(pkg_manager)
1090 self.__localinst_packages(pkg_manager)
1091 self.__check_packages(pkg_manager)
1093 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1094 checksize = self._root_fs_avail
1096 checksize -= BOOT_SAFEGUARD
1097 if self.target_arch:
1098 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1100 # If we have multiple partitions, don't check diskspace when rpm run transaction
1101 # because rpm check '/' partition only.
1102 if self.multiple_partitions:
1103 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_DISKSPACE)
1104 pkg_manager.runInstall(checksize)
1105 except CreatorError, e:
1107 except KeyboardInterrupt:
1110 self._pkgs_content = pkg_manager.getAllContent()
1111 self._pkgs_license = pkg_manager.getPkgsLicense()
1112 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1113 self.__attachment_packages(pkg_manager)
1117 if checkScriptletError(self._instroot + "/tmp/.postscript/error/", "_error"):
1118 showErrorInfo(self._instroot + "/tmp/.preload_install_error")
1119 raise CreatorError('scriptlet errors occurred')
1124 # do some clean up to avoid lvm info leakage. this sucks.
1125 for subdir in ("cache", "backup", "archive"):
1126 lvmdir = self._instroot + "/etc/lvm/" + subdir
1128 for f in os.listdir(lvmdir):
1129 os.unlink(lvmdir + "/" + f)
1133 def postinstall(self):
1134 self.copy_attachment()
1136 def __run_post_scripts(self):
1137 msger.info("Running scripts ...")
1138 if os.path.exists(self._instroot + "/tmp"):
1139 shutil.rmtree(self._instroot + "/tmp")
1140 os.mkdir (self._instroot + "/tmp", 0755)
1141 for s in kickstart.get_post_scripts(self.ks):
1142 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1143 dir = self._instroot + "/tmp")
1145 s.script = s.script.replace("\r", "")
1146 os.write(fd, s.script)
1148 os.chmod(path, 0700)
1150 env = self._get_post_scripts_env(s.inChroot)
1151 if 'PATH' not in env:
1152 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1158 preexec = self._chroot
1159 script = "/tmp/" + os.path.basename(path)
1163 p = subprocess.Popen([s.interp, script],
1164 preexec_fn = preexec,
1166 stdout = subprocess.PIPE,
1167 stderr = subprocess.STDOUT)
1168 while p.poll() == None:
1169 msger.info(p.stdout.readline().strip())
1170 except OSError, (err, msg):
1171 raise CreatorError("Failed to execute %%post script "
1172 "with '%s' : %s" % (s.interp, msg))
1176 def __save_repo_keys(self, repodata):
1180 gpgkeydir = "/etc/pki/rpm-gpg"
1181 fs.makedirs(self._instroot + gpgkeydir)
1182 for repo in repodata:
1184 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1185 shutil.copy(repo["repokey"], self._instroot + repokey)
1187 def configure(self, repodata = None):
1188 """Configure the system image according to the kickstart.
1190 This method applies the (e.g. keyboard or network) configuration
1191 specified in the kickstart and executes the kickstart %post scripts.
1193 If necessary, it also prepares the image to be bootable by e.g.
1194 creating an initrd and bootloader configuration.
1197 ksh = self.ks.handler
1199 msger.info('Applying configurations ...')
1201 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1202 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1203 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1204 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1205 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1206 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1207 kickstart.UserConfig(self._instroot).apply(ksh.user)
1208 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1209 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1210 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1211 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1212 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1213 self.__save_repo_keys(repodata)
1214 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1216 msger.warning("Failed to apply configuration to image")
1219 self._create_bootconfig()
1220 self.__run_post_scripts()
1222 def launch_shell(self, launch):
1223 """Launch a shell in the install root.
1225 This method is launches a bash shell chroot()ed in the install root;
1226 this can be useful for debugging.
1230 msger.info("Launching shell. Exit to continue.")
1231 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1233 def do_genchecksum(self, image_name):
1234 if not self._genchecksum:
1237 md5sum = misc.get_md5sum(image_name)
1238 with open(image_name + ".md5sum", "w") as f:
1239 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1240 self.outimage.append(image_name+".md5sum")
1242 def remove_exclude_image(self):
1243 for item in self._instloops[:]:
1244 if item['exclude_image']:
1245 msger.info("Removing %s in image." % item['name'])
1246 imgfile = os.path.join(self._imgdir, item['name'])
1249 except OSError as err:
1250 if err.errno == errno.ENOENT:
1252 self._instloops.remove(item)
1254 def create_cpio_image(self):
1255 for item in self._instloops:
1256 if item['cpioopts']:
1257 msger.info("Create image by cpio.")
1258 imgfile = os.path.join(self._imgdir, item['name'])
1262 cpiocmd = fs.find_binary_path('cpio')
1264 oldoutdir = os.getcwd()
1265 cpiomountdir = os.path.join(self._instroot, item['mountpoint'].lstrip('/'))
1266 os.chdir(cpiomountdir)
1267 # find . | cpio --create --'format=newc' | gzip > ../ramdisk.img
1268 runner.show('find . | cpio --create --format=%s | gzip > %s' % (item['cpioopts'], imgfile))
1269 shutil.rmtree(cpiomountdir, ignore_errors=True)
1270 fs.makedirs(cpiomountdir)
1272 except OSError, (errno, msg):
1273 raise errors.KsError("Create image by cpio error: %s" % msg)
1275 def package(self, destdir = "."):
1276 """Prepares the created image for final delivery.
1278 In its simplest form, this method merely copies the install root to the
1279 supplied destination directory; other subclasses may choose to package
1280 the image by e.g. creating a bootable ISO containing the image and
1281 bootloader configuration.
1283 destdir -- the directory into which the final image should be moved;
1284 this defaults to the current directory.
1287 self.remove_exclude_image()
1289 self._stage_final_image()
1291 if not os.path.exists(destdir):
1292 fs.makedirs(destdir)
1294 if self._recording_pkgs:
1295 self._save_recording_pkgs(destdir)
1297 # For image formats with two or multiple image files, it will be
1298 # better to put them under a directory
1299 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1300 destdir = os.path.join(destdir, "%s-%s" \
1301 % (self.name, self.image_format))
1302 msger.debug("creating destination dir: %s" % destdir)
1303 fs.makedirs(destdir)
1305 # Ensure all data is flushed to _outdir
1306 runner.quiet('sync')
1308 misc.check_space_pre_cp(self._outdir, destdir)
1309 for f in os.listdir(self._outdir):
1310 shutil.move(os.path.join(self._outdir, f),
1311 os.path.join(destdir, f))
1312 self.outimage.append(os.path.join(destdir, f))
1313 self.do_genchecksum(os.path.join(destdir, f))
1315 def print_outimage_info(self):
1316 msg = "The new image can be found here:\n"
1317 self.outimage.sort()
1318 for file in self.outimage:
1319 msg += ' %s\n' % os.path.abspath(file)
1323 def check_depend_tools(self):
1324 for tool in self._dep_checks:
1325 fs.find_binary_path(tool)
1327 def package_output(self, image_format, destdir = ".", package="none"):
1328 if not package or package == "none":
1331 destdir = os.path.abspath(os.path.expanduser(destdir))
1332 (pkg, comp) = os.path.splitext(package)
1334 comp=comp.lstrip(".")
1338 dst = "%s/%s-%s.tar.%s" %\
1339 (destdir, self.name, image_format, comp)
1341 dst = "%s/%s-%s.tar" %\
1342 (destdir, self.name, image_format)
1344 msger.info("creating %s" % dst)
1345 tar = tarfile.open(dst, "w:" + comp)
1347 for file in self.outimage:
1348 msger.info("adding %s to %s" % (file, dst))
1350 arcname=os.path.join("%s-%s" \
1351 % (self.name, image_format),
1352 os.path.basename(file)))
1353 if os.path.isdir(file):
1354 shutil.rmtree(file, ignore_errors = True)
1360 '''All the file in outimage has been packaged into tar.* file'''
1361 self.outimage = [dst]
1363 def release_output(self, config, destdir, release):
1364 """ Create release directory and files
1368 """ release path """
1369 return os.path.join(destdir, fn)
1371 outimages = self.outimage
1374 new_kspath = _rpath(self.name+'.ks')
1375 with open(config) as fr:
1376 with open(new_kspath, "w") as wf:
1377 # When building a release we want to make sure the .ks
1378 # file generates the same build even when --release not used.
1379 wf.write(fr.read().replace("@BUILD_ID@", release))
1380 outimages.append(new_kspath)
1382 # save log file, logfile is only available in creator attrs
1383 if hasattr(self, 'releaselog') and self.releaselog:
1384 outimages.append(self.logfile)
1386 # rename iso and usbimg
1387 for f in os.listdir(destdir):
1388 if f.endswith(".iso"):
1389 newf = f[:-4] + '.img'
1390 elif f.endswith(".usbimg"):
1391 newf = f[:-7] + '.img'
1394 os.rename(_rpath(f), _rpath(newf))
1395 outimages.append(_rpath(newf))
1397 # generate MD5SUMS SHA1SUMS SHA256SUMS
1398 def generate_hashsum(hash_name, hash_method):
1399 with open(_rpath(hash_name), "w") as wf:
1400 for f in os.listdir(destdir):
1401 if f.endswith('SUMS'):
1404 if os.path.isdir(os.path.join(destdir, f)):
1407 hash_value = hash_method(_rpath(f))
1408 # There needs to be two spaces between the sum and
1409 # filepath to match the syntax with md5sum,sha1sum,
1410 # sha256sum. This way also *sum -c *SUMS can be used.
1411 wf.write("%s %s\n" % (hash_value, f))
1413 outimages.append("%s/%s" % (destdir, hash_name))
1416 'MD5SUMS' : misc.get_md5sum,
1417 'SHA1SUMS' : misc.get_sha1sum,
1418 'SHA256SUMS' : misc.get_sha256sum
1421 for k, v in hash_dict.items():
1422 generate_hashsum(k, v)
1424 # Filter out the nonexist file
1425 for fp in outimages[:]:
1426 if not os.path.exists("%s" % fp):
1427 outimages.remove(fp)
1429 def copy_kernel(self):
1430 """ Copy kernel files to the outimage directory.
1431 NOTE: This needs to be called before unmounting the instroot.
1434 if not self._need_copy_kernel:
1437 if not os.path.exists(self.destdir):
1438 os.makedirs(self.destdir)
1440 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1441 kernelfilename = "%s/%s-%s" % (self.destdir,
1443 os.path.basename(kernel))
1444 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1446 shutil.copy(kernel, kernelfilename)
1447 self.outimage.append(kernelfilename)
1449 def copy_attachment(self):
1450 """ Subclass implement it to handle attachment files
1451 NOTE: This needs to be called before unmounting the instroot.
1455 def get_pkg_manager(self):
1456 return self.pkgmgr(target_arch = self.target_arch,
1457 instroot = self._instroot,
1458 cachedir = self.cachedir,
1459 strict_mode = self.strict_mode)
1461 def create_manifest(self):
1462 def get_pack_suffix():
1463 return '.' + self.pack_to.split('.', 1)[1]
1465 if not os.path.exists(self.destdir):
1466 os.makedirs(self.destdir)
1468 now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1469 manifest_dict = {'version': VERSION,
1472 manifest_dict.update({'format': self.img_format})
1474 if hasattr(self, 'logfile') and self.logfile:
1475 manifest_dict.update({'log_file': self.logfile})
1477 if self.image_files:
1479 self.image_files.update({'pack': get_pack_suffix()})
1480 manifest_dict.update({self.img_format: self.image_files})
1482 msger.info('Creating manifest file...')
1483 manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1484 with open(manifest_file_path, 'w') as fest_file:
1485 json.dump(manifest_dict, fest_file, indent=4)
1486 self.outimage.append(manifest_file_path)