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
92 # If the kernel is save to the destdir when copy_kernel cmd is called.
93 self._need_copy_kernel = False
94 # setup tmpfs tmpdir when enabletmpfs is True
95 self.enabletmpfs = False
98 # Mapping table for variables that have different names.
99 optmap = {"pkgmgr" : "pkgmgr_name",
100 "arch" : "target_arch",
101 "local_pkgs_path" : "_local_pkgs_path",
102 "copy_kernel" : "_need_copy_kernel",
103 "strict_mode" : "strict_mode",
106 # update setting from createopts
107 for key in createopts.keys():
112 setattr(self, option, createopts[key])
114 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
117 if '@NAME@' in self.pack_to:
118 self.pack_to = self.pack_to.replace('@NAME@', self.name)
119 (tar, ext) = os.path.splitext(self.pack_to)
120 if ext in (".gz", ".bz2", ".lzo", ".bz") and tar.endswith(".tar"):
122 if ext not in get_archive_suffixes():
123 self.pack_to += ".tar"
125 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
127 # Output image file names
129 # Output info related with manifest
130 self.image_files = {}
131 # A flag to generate checksum
132 self._genchecksum = False
134 self._alt_initrd_name = None
136 self._recording_pkgs = []
138 # available size in root fs, init to 0
139 self._root_fs_avail = 0
141 # Name of the disk image file that is created.
142 self._img_name = None
144 self.image_format = None
146 # Save qemu emulator file name in order to clean up it finally
147 self.qemu_emulator = None
149 # No ks provided when called by convertor, so skip the dependency check
151 # If we have btrfs partition we need to check necessary tools
152 for part in self.ks.handler.partition.partitions:
153 if part.fstype and part.fstype == "btrfs":
154 self._dep_checks.append("mkfs.btrfs")
156 if len(self.ks.handler.partition.partitions) > 1:
157 self.multiple_partitions = True
160 if self.target_arch.startswith("arm"):
161 for dep in self._dep_checks:
162 if dep == "extlinux":
163 self._dep_checks.remove(dep)
165 if not os.path.exists("/usr/bin/qemu-arm") or \
166 not misc.is_statically_linked("/usr/bin/qemu-arm"):
167 self._dep_checks.append("qemu-arm-static")
169 if os.path.exists("/proc/sys/vm/vdso_enabled"):
170 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
171 vdso_value = vdso_fh.read().strip()
173 if (int)(vdso_value) == 1:
174 msger.warning("vdso is enabled on your host, which might "
175 "cause problems with arm emulations.\n"
176 "\tYou can disable vdso with following command before "
177 "starting image build:\n"
178 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
179 elif self.target_arch == "mipsel":
180 for dep in self._dep_checks:
181 if dep == "extlinux":
182 self._dep_checks.remove(dep)
184 if not os.path.exists("/usr/bin/qemu-mipsel") or \
185 not misc.is_statically_linked("/usr/bin/qemu-mipsel"):
186 self._dep_checks.append("qemu-mipsel-static")
188 if os.path.exists("/proc/sys/vm/vdso_enabled"):
189 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
190 vdso_value = vdso_fh.read().strip()
192 if (int)(vdso_value) == 1:
193 msger.warning("vdso is enabled on your host, which might "
194 "cause problems with mipsel emulations.\n"
195 "\tYou can disable vdso with following command before "
196 "starting image build:\n"
197 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
199 # make sure the specified tmpdir and cachedir exist
200 if not os.path.exists(self.tmpdir):
201 os.makedirs(self.tmpdir)
202 if not os.path.exists(self.cachedir):
203 os.makedirs(self.cachedir)
209 def __get_instroot(self):
210 if self.__builddir is None:
211 raise CreatorError("_instroot is not valid before calling mount()")
212 return self.__builddir + "/install_root"
213 _instroot = property(__get_instroot)
214 """The location of the install root directory.
216 This is the directory into which the system is installed. Subclasses may
217 mount a filesystem image here or copy files to/from here.
219 Note, this directory does not exist before ImageCreator.mount() is called.
221 Note also, this is a read-only attribute.
225 def __get_outdir(self):
226 if self.__builddir is None:
227 raise CreatorError("_outdir is not valid before calling mount()")
228 return self.__builddir + "/out"
229 _outdir = property(__get_outdir)
230 """The staging location for the final image.
232 This is where subclasses should stage any files that are part of the final
233 image. ImageCreator.package() will copy any files found here into the
234 requested destination directory.
236 Note, this directory does not exist before ImageCreator.mount() is called.
238 Note also, this is a read-only attribute.
244 # Hooks for subclasses
246 def _mount_instroot(self, base_on = None):
247 """Mount or prepare the install root directory.
249 This is the hook where subclasses may prepare the install root by e.g.
250 mounting creating and loopback mounting a filesystem image to
253 There is no default implementation.
255 base_on -- this is the value passed to mount() and can be interpreted
256 as the subclass wishes; it might e.g. be the location of
257 a previously created ISO containing a system image.
262 def _unmount_instroot(self):
263 """Undo anything performed in _mount_instroot().
265 This is the hook where subclasses must undo anything which was done
266 in _mount_instroot(). For example, if a filesystem image was mounted
267 onto _instroot, it should be unmounted here.
269 There is no default implementation.
274 def _create_bootconfig(self):
275 """Configure the image so that it's bootable.
277 This is the hook where subclasses may prepare the image for booting by
278 e.g. creating an initramfs and bootloader configuration.
280 This hook is called while the install root is still mounted, after the
281 packages have been installed and the kickstart configuration has been
282 applied, but before the %post scripts have been executed.
284 There is no default implementation.
289 def _stage_final_image(self):
290 """Stage the final system image in _outdir.
292 This is the hook where subclasses should place the image in _outdir
293 so that package() can copy it to the requested destination directory.
295 By default, this moves the install root into _outdir.
298 shutil.move(self._instroot, self._outdir + "/" + self.name)
300 def get_installed_packages(self):
301 return self._pkgs_content.keys()
303 def _save_recording_pkgs(self, destdir):
304 """Save the list or content of installed packages to file.
306 pkgs = self._pkgs_content.keys()
307 pkgs.sort() # inplace op
309 if not os.path.exists(destdir):
313 if 'vcs' in self._recording_pkgs:
314 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
315 content = '\n'.join(sorted(vcslst))
316 elif 'name' in self._recording_pkgs:
317 content = '\n'.join(pkgs)
319 namefile = os.path.join(destdir, self.name + '.packages')
320 f = open(namefile, "w")
323 self.outimage.append(namefile);
325 # if 'content', save more details
326 if 'content' in self._recording_pkgs:
327 contfile = os.path.join(destdir, self.name + '.files')
328 f = open(contfile, "w")
333 pkgcont = self._pkgs_content[pkg]
335 content += '\n '.join(pkgcont)
341 self.outimage.append(contfile)
343 if 'license' in self._recording_pkgs:
344 licensefile = os.path.join(destdir, self.name + '.license')
345 f = open(licensefile, "w")
347 f.write('Summary:\n')
348 for license in reversed(sorted(self._pkgs_license, key=\
349 lambda license: len(self._pkgs_license[license]))):
350 f.write(" - %s: %s\n" \
351 % (license, len(self._pkgs_license[license])))
353 f.write('\nDetails:\n')
354 for license in reversed(sorted(self._pkgs_license, key=\
355 lambda license: len(self._pkgs_license[license]))):
356 f.write(" - %s:\n" % (license))
357 for pkg in sorted(self._pkgs_license[license]):
358 f.write(" - %s\n" % (pkg))
362 self.outimage.append(licensefile)
364 def _get_required_packages(self):
365 """Return a list of required packages.
367 This is the hook where subclasses may specify a set of packages which
368 it requires to be installed.
370 This returns an empty list by default.
372 Note, subclasses should usually chain up to the base class
373 implementation of this hook.
378 def _get_excluded_packages(self):
379 """Return a list of excluded packages.
381 This is the hook where subclasses may specify a set of packages which
382 it requires _not_ to be installed.
384 This returns an empty list by default.
386 Note, subclasses should usually chain up to the base class
387 implementation of this hook.
392 def _get_local_packages(self):
393 """Return a list of rpm path to be local installed.
395 This is the hook where subclasses may specify a set of rpms which
396 it requires to be installed locally.
398 This returns an empty list by default.
400 Note, subclasses should usually chain up to the base class
401 implementation of this hook.
404 if self._local_pkgs_path:
405 if os.path.isdir(self._local_pkgs_path):
407 os.path.join(self._local_pkgs_path, '*.rpm'))
408 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
409 return [self._local_pkgs_path]
413 def _get_fstab(self):
414 """Return the desired contents of /etc/fstab.
416 This is the hook where subclasses may specify the contents of
417 /etc/fstab by returning a string containing the desired contents.
419 A sensible default implementation is provided.
422 s = "/dev/root / %s %s 0 0\n" \
424 "defaults,noatime" if not self._fsopts else self._fsopts)
425 s += self._get_fstab_special()
428 def _get_fstab_special(self):
429 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
430 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
431 s += "proc /proc proc defaults 0 0\n"
432 s += "sysfs /sys sysfs defaults 0 0\n"
435 def _set_part_env(self, pnum, prop, value):
436 """ This is a helper function which generates an environment variable
437 for a property "prop" with value "value" of a partition number "pnum".
439 The naming convention is:
440 * Variables start with INSTALLERFW_PART
441 * Then goes the partition number, the order is the same as
442 specified in the KS file
443 * Then goes the property name
451 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
452 return { name : value }
454 def _get_post_scripts_env(self, in_chroot):
455 """Return an environment dict for %post scripts.
457 This is the hook where subclasses may specify some environment
458 variables for %post scripts by return a dict containing the desired
461 in_chroot -- whether this %post script is to be executed chroot()ed
468 for p in kickstart.get_partitions(self.ks):
469 env.update(self._set_part_env(pnum, "SIZE", p.size))
470 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
471 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
472 env.update(self._set_part_env(pnum, "LABEL", p.label))
473 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
474 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
475 env.update(self._set_part_env(pnum, "ALIGN", p.align))
476 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
477 env.update(self._set_part_env(pnum, "UUID", p.uuid))
478 env.update(self._set_part_env(pnum, "DEVNODE",
479 "/dev/%s%d" % (p.disk, pnum + 1)))
480 env.update(self._set_part_env(pnum, "DISK_DEVNODE",
485 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
487 # Partition table format
488 ptable_format = self.ks.handler.bootloader.ptable
489 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
491 # The kerned boot parameters
492 kernel_opts = self.ks.handler.bootloader.appendLine
493 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
495 # Name of the image creation tool
496 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
498 # The real current location of the mounted file-systems
502 mount_prefix = self._instroot
503 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
505 # These are historical variables which lack the common name prefix
507 env["INSTALL_ROOT"] = self._instroot
508 env["IMG_NAME"] = self._name
512 def __get_imgname(self):
514 _name = property(__get_imgname)
515 """The name of the image file.
519 def _get_kernel_versions(self):
520 """Return a dict detailing the available kernel types/versions.
522 This is the hook where subclasses may override what kernel types and
523 versions should be available for e.g. creating the booloader
526 A dict should be returned mapping the available kernel types to a list
527 of the available versions for those kernels.
529 The default implementation uses rpm to iterate over everything
530 providing 'kernel', finds /boot/vmlinuz-* and returns the version
531 obtained from the vmlinuz filename. (This can differ from the kernel
532 RPM's n-v-r in the case of e.g. xen)
535 def get_kernel_versions(instroot):
538 files = glob.glob(instroot + "/boot/vmlinuz-*")
540 version = os.path.basename(file)[8:]
543 versions.add(version)
544 ret["kernel"] = list(versions)
547 def get_version(header):
549 for f in header['filenames']:
550 if f.startswith('/boot/vmlinuz-'):
555 return get_kernel_versions(self._instroot)
557 ts = rpm.TransactionSet(self._instroot)
560 for header in ts.dbMatch('provides', 'kernel'):
561 version = get_version(header)
565 name = header['name']
567 ret[name] = [version]
568 elif not version in ret[name]:
569 ret[name].append(version)
575 # Helpers for subclasses
577 def _do_bindmounts(self):
578 """Mount various system directories onto _instroot.
580 This method is called by mount(), but may also be used by subclasses
581 in order to re-mount the bindmounts after modifying the underlying
585 for b in self.__bindmounts:
588 def _undo_bindmounts(self):
589 """Unmount the bind-mounted system directories from _instroot.
591 This method is usually only called by unmount(), but may also be used
592 by subclasses in order to gain access to the filesystem obscured by
593 the bindmounts - e.g. in order to create device nodes on the image
597 self.__bindmounts.reverse()
598 for b in self.__bindmounts:
602 """Chroot into the install root.
604 This method may be used by subclasses when executing programs inside
605 the install root e.g.
607 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
610 os.chroot(self._instroot)
613 def _mkdtemp(self, prefix = "tmp-"):
614 """Create a temporary directory.
616 This method may be used by subclasses to create a temporary directory
617 for use in building the final image - e.g. a subclass might create
618 a temporary directory in order to bundle a set of files into a package.
620 The subclass may delete this directory if it wishes, but it will be
621 automatically deleted by cleanup().
623 The absolute path to the temporary directory is returned.
625 Note, this method should only be called after mount() has been called.
627 prefix -- a prefix which should be used when creating the directory;
631 self.__ensure_builddir()
632 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
634 def _mkstemp(self, prefix = "tmp-"):
635 """Create a temporary file.
637 This method may be used by subclasses to create a temporary file
638 for use in building the final image - e.g. a subclass might need
639 a temporary location to unpack a compressed file.
641 The subclass may delete this file if it wishes, but it will be
642 automatically deleted by cleanup().
644 A tuple containing a file descriptor (returned from os.open() and the
645 absolute path to the temporary directory is returned.
647 Note, this method should only be called after mount() has been called.
649 prefix -- a prefix which should be used when creating the file;
653 self.__ensure_builddir()
654 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
656 def _mktemp(self, prefix = "tmp-"):
657 """Create a temporary file.
659 This method simply calls _mkstemp() and closes the returned file
662 The absolute path to the temporary file is returned.
664 Note, this method should only be called after mount() has been called.
666 prefix -- a prefix which should be used when creating the file;
671 (f, path) = self._mkstemp(prefix)
677 # Actual implementation
679 def __ensure_builddir(self):
680 if not self.__builddir is None:
684 self.workdir = os.path.join(self.tmpdir, "build")
685 if not os.path.exists(self.workdir):
686 os.makedirs(self.workdir)
687 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
688 prefix = "imgcreate-")
689 except OSError, (err, msg):
690 raise CreatorError("Failed create build directory in %s: %s" %
693 def get_cachedir(self, cachedir = None):
697 self.__ensure_builddir()
699 self.cachedir = cachedir
701 self.cachedir = self.__builddir + "/mic-cache"
702 fs.makedirs(self.cachedir)
705 def __sanity_check(self):
706 """Ensure that the config we've been given is sane."""
707 if not (kickstart.get_packages(self.ks) or
708 kickstart.get_groups(self.ks)):
709 raise CreatorError("No packages or groups specified")
711 kickstart.convert_method_to_repo(self.ks)
713 if not kickstart.get_repos(self.ks):
714 raise CreatorError("No repositories specified")
716 def __write_fstab(self):
717 if kickstart.use_installerfw(self.ks, "fstab"):
718 # The fstab file will be generated by installer framework scripts
721 fstab_contents = self._get_fstab()
723 fstab = open(self._instroot + "/etc/fstab", "w")
724 fstab.write(fstab_contents)
727 def __create_minimal_dev(self):
728 """Create a minimal /dev so that we don't corrupt the host /dev"""
729 origumask = os.umask(0000)
730 devices = (('null', 1, 3, 0666),
731 ('urandom',1, 9, 0666),
732 ('random', 1, 8, 0666),
733 ('full', 1, 7, 0666),
734 ('ptmx', 5, 2, 0666),
736 ('zero', 1, 5, 0666))
738 links = (("/proc/self/fd", "/dev/fd"),
739 ("/proc/self/fd/0", "/dev/stdin"),
740 ("/proc/self/fd/1", "/dev/stdout"),
741 ("/proc/self/fd/2", "/dev/stderr"))
743 for (node, major, minor, perm) in devices:
744 if not os.path.exists(self._instroot + "/dev/" + node):
745 os.mknod(self._instroot + "/dev/" + node,
747 os.makedev(major,minor))
749 for (src, dest) in links:
750 if not os.path.exists(self._instroot + dest):
751 os.symlink(src, self._instroot + dest)
755 def __setup_tmpdir(self):
756 if not self.enabletmpfs:
759 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
761 def __clean_tmpdir(self):
762 if not self.enabletmpfs:
765 runner.show('umount -l %s' % self.workdir)
767 def mount(self, base_on = None, cachedir = None):
768 """Setup the target filesystem in preparation for an install.
770 This function sets up the filesystem which the ImageCreator will
771 install into and configure. The ImageCreator class merely creates an
772 install root directory, bind mounts some system directories (e.g. /dev)
773 and writes out /etc/fstab. Other subclasses may also e.g. create a
774 sparse file, format it and loopback mount it to the install root.
776 base_on -- a previous install on which to base this install; defaults
777 to None, causing a new image to be created
779 cachedir -- a directory in which to store the Yum cache; defaults to
780 None, causing a new cache to be created; by setting this
781 to another directory, the same cache can be reused across
785 self.__setup_tmpdir()
786 self.__ensure_builddir()
788 # prevent popup dialog in Ubuntu(s)
789 misc.hide_loopdev_presentation()
791 fs.makedirs(self._instroot)
792 fs.makedirs(self._outdir)
794 self._mount_instroot(base_on)
796 for d in ("/dev/pts",
803 fs.makedirs(self._instroot + d)
805 if self.target_arch and self.target_arch.startswith("arm") or \
806 self.target_arch == "aarch64" or self.target_arch == "mipsel" :
807 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
810 self.get_cachedir(cachedir)
812 # bind mount system directories into _instroot
813 for (f, dest) in [("/sys", None),
815 ("/proc/sys/fs/binfmt_misc", None),
817 self.__bindmounts.append(
819 f, self._instroot, dest))
821 self._do_bindmounts()
823 self.__create_minimal_dev()
825 if os.path.exists(self._instroot + "/etc/mtab"):
826 os.unlink(self._instroot + "/etc/mtab")
827 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
831 # get size of available space in 'instroot' fs
832 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
835 """Unmounts the target filesystem.
837 The ImageCreator class detaches the system from the install root, but
838 other subclasses may also detach the loopback mounted filesystem image
839 from the install root.
843 mtab = self._instroot + "/etc/mtab"
844 if not os.path.islink(mtab):
845 os.unlink(self._instroot + "/etc/mtab")
847 if self.qemu_emulator:
848 os.unlink(self._instroot + self.qemu_emulator)
852 self._undo_bindmounts()
854 """ Clean up yum garbage """
856 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
857 if os.path.exists(instroot_pdir):
858 shutil.rmtree(instroot_pdir, ignore_errors = True)
859 yumlibdir = self._instroot + "/var/lib/yum"
860 if os.path.exists(yumlibdir):
861 shutil.rmtree(yumlibdir, ignore_errors = True)
865 self._unmount_instroot()
867 # reset settings of popup dialog in Ubuntu(s)
868 misc.unhide_loopdev_presentation()
872 """Unmounts the target filesystem and deletes temporary files.
874 This method calls unmount() and then deletes any temporary files and
875 directories that were created on the host system while building the
878 Note, make sure to call this method once finished with the creator
879 instance in order to ensure no stale files are left on the host e.g.:
881 creator = ImageCreator(ks, name)
888 if not self.__builddir:
891 kill_proc_inchroot(self._instroot)
895 shutil.rmtree(self.__builddir, ignore_errors = True)
896 self.__builddir = None
898 self.__clean_tmpdir()
900 def __is_excluded_pkg(self, pkg):
901 if pkg in self._excluded_pkgs:
902 self._excluded_pkgs.remove(pkg)
905 for xpkg in self._excluded_pkgs:
906 if xpkg.endswith('*'):
907 if pkg.startswith(xpkg[:-1]):
909 elif xpkg.startswith('*'):
910 if pkg.endswith(xpkg[1:]):
915 def __select_packages(self, pkg_manager):
917 for pkg in self._required_pkgs:
918 e = pkg_manager.selectPackage(pkg)
920 if kickstart.ignore_missing(self.ks):
921 skipped_pkgs.append(pkg)
922 elif self.__is_excluded_pkg(pkg):
923 skipped_pkgs.append(pkg)
925 raise CreatorError("Failed to find package '%s' : %s" %
928 for pkg in skipped_pkgs:
929 msger.warning("Skipping missing package '%s'" % (pkg,))
931 def __select_groups(self, pkg_manager):
933 for group in self._required_groups:
934 e = pkg_manager.selectGroup(group.name, group.include)
936 if kickstart.ignore_missing(self.ks):
937 skipped_groups.append(group)
939 raise CreatorError("Failed to find group '%s' : %s" %
942 for group in skipped_groups:
943 msger.warning("Skipping missing group '%s'" % (group.name,))
945 def __deselect_packages(self, pkg_manager):
946 for pkg in self._excluded_pkgs:
947 pkg_manager.deselectPackage(pkg)
949 def __localinst_packages(self, pkg_manager):
950 for rpm_path in self._get_local_packages():
951 pkg_manager.installLocal(rpm_path)
953 def __preinstall_packages(self, pkg_manager):
957 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
958 for pkg in self._preinstall_pkgs:
959 pkg_manager.preInstall(pkg)
961 def __check_packages(self, pkg_manager):
962 for pkg in self.check_pkgs:
963 pkg_manager.checkPackage(pkg)
965 def __attachment_packages(self, pkg_manager):
969 self._attachment = []
970 for item in kickstart.get_attachment(self.ks):
971 if item.startswith('/'):
972 fpaths = os.path.join(self._instroot, item.lstrip('/'))
973 for fpath in glob.glob(fpaths):
974 self._attachment.append(fpath)
977 filelist = pkg_manager.getFilelist(item)
979 # found rpm in rootfs
980 for pfile in pkg_manager.getFilelist(item):
981 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
982 self._attachment.append(fpath)
985 # try to retrieve rpm file
986 (url, proxies) = pkg_manager.package_url(item)
988 msger.warning("Can't get url from repo for %s" % item)
990 fpath = os.path.join(self.cachedir, os.path.basename(url))
991 if not os.path.exists(fpath):
994 fpath = grabber.myurlgrab(url.full, fpath, proxies, None)
998 tmpdir = self._mkdtemp()
999 misc.extract_rpm(fpath, tmpdir)
1000 for (root, dirs, files) in os.walk(tmpdir):
1002 fpath = os.path.join(root, fname)
1003 self._attachment.append(fpath)
1005 def install(self, repo_urls=None):
1006 """Install packages into the install root.
1008 This function installs the packages listed in the supplied kickstart
1009 into the install root. By default, the packages are installed from the
1010 repository URLs specified in the kickstart.
1012 repo_urls -- a dict which maps a repository name to a repository;
1013 if supplied, this causes any repository URLs specified in
1014 the kickstart to be overridden.
1017 def checkScriptletError(dirname, suffix):
1018 if os.path.exists(dirname):
1019 list = os.listdir(dirname)
1021 filepath = os.path.join(dirname, line)
1022 if os.path.isfile(filepath) and 0 < line.find(suffix):
1029 def showErrorInfo(filepath):
1030 if os.path.isfile(filepath):
1031 msger.info("The error install package info:")
1032 for line in open(filepath):
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):
1256 cpiomountdir = self._instroot + '/mnt/initrd'
1257 if os.path.exists(cpiomountdir):
1258 msger.info("Create image by cpio.")
1259 imgfile = os.path.join(self._imgdir, 'ramdisk.img')
1263 cpiocmd = fs.find_binary_path('cpio')
1265 oldoutdir = os.getcwd()
1266 os.chdir(cpiomountdir)
1267 # find . | cpio --create --'format=newc' | gzip > ../ramdisk.img
1268 runner.show('find . | cpio -o -H newc | gzip > %s' % 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 msger.warning("Do not create image by cpio. There is no directory '/mnt/initrd'.")
1277 def package(self, destdir = "."):
1278 """Prepares the created image for final delivery.
1280 In its simplest form, this method merely copies the install root to the
1281 supplied destination directory; other subclasses may choose to package
1282 the image by e.g. creating a bootable ISO containing the image and
1283 bootloader configuration.
1285 destdir -- the directory into which the final image should be moved;
1286 this defaults to the current directory.
1289 self.remove_exclude_image()
1291 self._stage_final_image()
1293 if not os.path.exists(destdir):
1294 fs.makedirs(destdir)
1296 if self._recording_pkgs:
1297 self._save_recording_pkgs(destdir)
1299 # For image formats with two or multiple image files, it will be
1300 # better to put them under a directory
1301 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1302 destdir = os.path.join(destdir, "%s-%s" \
1303 % (self.name, self.image_format))
1304 msger.debug("creating destination dir: %s" % destdir)
1305 fs.makedirs(destdir)
1307 # Ensure all data is flushed to _outdir
1308 runner.quiet('sync')
1310 misc.check_space_pre_cp(self._outdir, destdir)
1311 for f in os.listdir(self._outdir):
1312 shutil.move(os.path.join(self._outdir, f),
1313 os.path.join(destdir, f))
1314 self.outimage.append(os.path.join(destdir, f))
1315 self.do_genchecksum(os.path.join(destdir, f))
1317 def print_outimage_info(self):
1318 msg = "The new image can be found here:\n"
1319 self.outimage.sort()
1320 for file in self.outimage:
1321 msg += ' %s\n' % os.path.abspath(file)
1325 def check_depend_tools(self):
1326 for tool in self._dep_checks:
1327 fs.find_binary_path(tool)
1329 def package_output(self, image_format, destdir = ".", package="none"):
1330 if not package or package == "none":
1333 destdir = os.path.abspath(os.path.expanduser(destdir))
1334 (pkg, comp) = os.path.splitext(package)
1336 comp=comp.lstrip(".")
1340 dst = "%s/%s-%s.tar.%s" %\
1341 (destdir, self.name, image_format, comp)
1343 dst = "%s/%s-%s.tar" %\
1344 (destdir, self.name, image_format)
1346 msger.info("creating %s" % dst)
1347 tar = tarfile.open(dst, "w:" + comp)
1349 for file in self.outimage:
1350 msger.info("adding %s to %s" % (file, dst))
1352 arcname=os.path.join("%s-%s" \
1353 % (self.name, image_format),
1354 os.path.basename(file)))
1355 if os.path.isdir(file):
1356 shutil.rmtree(file, ignore_errors = True)
1362 '''All the file in outimage has been packaged into tar.* file'''
1363 self.outimage = [dst]
1365 def release_output(self, config, destdir, release):
1366 """ Create release directory and files
1370 """ release path """
1371 return os.path.join(destdir, fn)
1373 outimages = self.outimage
1376 new_kspath = _rpath(self.name+'.ks')
1377 with open(config) as fr:
1378 with open(new_kspath, "w") as wf:
1379 # When building a release we want to make sure the .ks
1380 # file generates the same build even when --release not used.
1381 wf.write(fr.read().replace("@BUILD_ID@", release))
1382 outimages.append(new_kspath)
1384 # save log file, logfile is only available in creator attrs
1385 if hasattr(self, 'releaselog') and self.releaselog:
1386 outimages.append(self.logfile)
1388 # rename iso and usbimg
1389 for f in os.listdir(destdir):
1390 if f.endswith(".iso"):
1391 newf = f[:-4] + '.img'
1392 elif f.endswith(".usbimg"):
1393 newf = f[:-7] + '.img'
1396 os.rename(_rpath(f), _rpath(newf))
1397 outimages.append(_rpath(newf))
1399 # generate MD5SUMS SHA1SUMS SHA256SUMS
1400 def generate_hashsum(hash_name, hash_method):
1401 with open(_rpath(hash_name), "w") as wf:
1402 for f in os.listdir(destdir):
1403 if f.endswith('SUMS'):
1406 if os.path.isdir(os.path.join(destdir, f)):
1409 hash_value = hash_method(_rpath(f))
1410 # There needs to be two spaces between the sum and
1411 # filepath to match the syntax with md5sum,sha1sum,
1412 # sha256sum. This way also *sum -c *SUMS can be used.
1413 wf.write("%s %s\n" % (hash_value, f))
1415 outimages.append("%s/%s" % (destdir, hash_name))
1418 'MD5SUMS' : misc.get_md5sum,
1419 'SHA1SUMS' : misc.get_sha1sum,
1420 'SHA256SUMS' : misc.get_sha256sum
1423 for k, v in hash_dict.items():
1424 generate_hashsum(k, v)
1426 # Filter out the nonexist file
1427 for fp in outimages[:]:
1428 if not os.path.exists("%s" % fp):
1429 outimages.remove(fp)
1431 def copy_kernel(self):
1432 """ Copy kernel files to the outimage directory.
1433 NOTE: This needs to be called before unmounting the instroot.
1436 if not self._need_copy_kernel:
1439 if not os.path.exists(self.destdir):
1440 os.makedirs(self.destdir)
1442 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1443 kernelfilename = "%s/%s-%s" % (self.destdir,
1445 os.path.basename(kernel))
1446 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1448 shutil.copy(kernel, kernelfilename)
1449 self.outimage.append(kernelfilename)
1451 def copy_attachment(self):
1452 """ Subclass implement it to handle attachment files
1453 NOTE: This needs to be called before unmounting the instroot.
1457 def get_pkg_manager(self):
1458 return self.pkgmgr(target_arch = self.target_arch,
1459 instroot = self._instroot,
1460 cachedir = self.cachedir,
1461 strict_mode = self.strict_mode)
1463 def create_manifest(self):
1464 def get_pack_suffix():
1465 return '.' + self.pack_to.split('.', 1)[1]
1467 if not os.path.exists(self.destdir):
1468 os.makedirs(self.destdir)
1470 now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1471 manifest_dict = {'version': VERSION,
1474 manifest_dict.update({'format': self.img_format})
1476 if hasattr(self, 'logfile') and self.logfile:
1477 manifest_dict.update({'log_file': self.logfile})
1479 if self.image_files:
1481 self.image_files.update({'pack': get_pack_suffix()})
1482 manifest_dict.update({self.img_format: self.image_files})
1484 msger.info('Creating manifest file...')
1485 manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1486 with open(manifest_file_path, 'w') as fest_file:
1487 json.dump(manifest_dict, fest_file, indent=4)
1488 self.outimage.append(manifest_file_path)