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
90 # If the kernel is save to the destdir when copy_kernel cmd is called.
91 self._need_copy_kernel = False
92 # setup tmpfs tmpdir when enabletmpfs is True
93 self.enabletmpfs = False
96 # Mapping table for variables that have different names.
97 optmap = {"pkgmgr" : "pkgmgr_name",
98 "arch" : "target_arch",
99 "local_pkgs_path" : "_local_pkgs_path",
100 "copy_kernel" : "_need_copy_kernel",
101 "strict_mode" : "strict_mode",
104 # update setting from createopts
105 for key in createopts.keys():
110 setattr(self, option, createopts[key])
112 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
115 if '@NAME@' in self.pack_to:
116 self.pack_to = self.pack_to.replace('@NAME@', self.name)
117 (tar, ext) = os.path.splitext(self.pack_to)
118 if ext in (".gz", ".bz2", ".lzo", ".bz") and tar.endswith(".tar"):
120 if ext not in get_archive_suffixes():
121 self.pack_to += ".tar"
123 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
125 # Output image file names
127 # Output info related with manifest
128 self.image_files = {}
129 # A flag to generate checksum
130 self._genchecksum = False
132 self._alt_initrd_name = None
134 self._recording_pkgs = []
136 # available size in root fs, init to 0
137 self._root_fs_avail = 0
139 # Name of the disk image file that is created.
140 self._img_name = None
142 self.image_format = None
144 # Save qemu emulator file name in order to clean up it finally
145 self.qemu_emulator = None
147 # No ks provided when called by convertor, so skip the dependency check
149 # If we have btrfs partition we need to check necessary tools
150 for part in self.ks.handler.partition.partitions:
151 if part.fstype and part.fstype == "btrfs":
152 self._dep_checks.append("mkfs.btrfs")
156 if self.target_arch.startswith("arm"):
157 for dep in self._dep_checks:
158 if dep == "extlinux":
159 self._dep_checks.remove(dep)
161 if not os.path.exists("/usr/bin/qemu-arm") or \
162 not misc.is_statically_linked("/usr/bin/qemu-arm"):
163 self._dep_checks.append("qemu-arm-static")
165 if os.path.exists("/proc/sys/vm/vdso_enabled"):
166 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
167 vdso_value = vdso_fh.read().strip()
169 if (int)(vdso_value) == 1:
170 msger.warning("vdso is enabled on your host, which might "
171 "cause problems with arm emulations.\n"
172 "\tYou can disable vdso with following command before "
173 "starting image build:\n"
174 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
175 elif self.target_arch == "mipsel":
176 for dep in self._dep_checks:
177 if dep == "extlinux":
178 self._dep_checks.remove(dep)
180 if not os.path.exists("/usr/bin/qemu-mipsel") or \
181 not misc.is_statically_linked("/usr/bin/qemu-mipsel"):
182 self._dep_checks.append("qemu-mipsel-static")
184 if os.path.exists("/proc/sys/vm/vdso_enabled"):
185 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
186 vdso_value = vdso_fh.read().strip()
188 if (int)(vdso_value) == 1:
189 msger.warning("vdso is enabled on your host, which might "
190 "cause problems with mipsel emulations.\n"
191 "\tYou can disable vdso with following command before "
192 "starting image build:\n"
193 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
195 # make sure the specified tmpdir and cachedir exist
196 if not os.path.exists(self.tmpdir):
197 os.makedirs(self.tmpdir)
198 if not os.path.exists(self.cachedir):
199 os.makedirs(self.cachedir)
205 def __get_instroot(self):
206 if self.__builddir is None:
207 raise CreatorError("_instroot is not valid before calling mount()")
208 return self.__builddir + "/install_root"
209 _instroot = property(__get_instroot)
210 """The location of the install root directory.
212 This is the directory into which the system is installed. Subclasses may
213 mount a filesystem image here or copy files to/from here.
215 Note, this directory does not exist before ImageCreator.mount() is called.
217 Note also, this is a read-only attribute.
221 def __get_outdir(self):
222 if self.__builddir is None:
223 raise CreatorError("_outdir is not valid before calling mount()")
224 return self.__builddir + "/out"
225 _outdir = property(__get_outdir)
226 """The staging location for the final image.
228 This is where subclasses should stage any files that are part of the final
229 image. ImageCreator.package() will copy any files found here into the
230 requested destination directory.
232 Note, this directory does not exist before ImageCreator.mount() is called.
234 Note also, this is a read-only attribute.
240 # Hooks for subclasses
242 def _mount_instroot(self, base_on = None):
243 """Mount or prepare the install root directory.
245 This is the hook where subclasses may prepare the install root by e.g.
246 mounting creating and loopback mounting a filesystem image to
249 There is no default implementation.
251 base_on -- this is the value passed to mount() and can be interpreted
252 as the subclass wishes; it might e.g. be the location of
253 a previously created ISO containing a system image.
258 def _unmount_instroot(self):
259 """Undo anything performed in _mount_instroot().
261 This is the hook where subclasses must undo anything which was done
262 in _mount_instroot(). For example, if a filesystem image was mounted
263 onto _instroot, it should be unmounted here.
265 There is no default implementation.
270 def _create_bootconfig(self):
271 """Configure the image so that it's bootable.
273 This is the hook where subclasses may prepare the image for booting by
274 e.g. creating an initramfs and bootloader configuration.
276 This hook is called while the install root is still mounted, after the
277 packages have been installed and the kickstart configuration has been
278 applied, but before the %post scripts have been executed.
280 There is no default implementation.
285 def _stage_final_image(self):
286 """Stage the final system image in _outdir.
288 This is the hook where subclasses should place the image in _outdir
289 so that package() can copy it to the requested destination directory.
291 By default, this moves the install root into _outdir.
294 shutil.move(self._instroot, self._outdir + "/" + self.name)
296 def get_installed_packages(self):
297 return self._pkgs_content.keys()
299 def _save_recording_pkgs(self, destdir):
300 """Save the list or content of installed packages to file.
302 pkgs = self._pkgs_content.keys()
303 pkgs.sort() # inplace op
305 if not os.path.exists(destdir):
309 if 'vcs' in self._recording_pkgs:
310 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
311 content = '\n'.join(sorted(vcslst))
312 elif 'name' in self._recording_pkgs:
313 content = '\n'.join(pkgs)
315 namefile = os.path.join(destdir, self.name + '.packages')
316 f = open(namefile, "w")
319 self.outimage.append(namefile);
321 # if 'content', save more details
322 if 'content' in self._recording_pkgs:
323 contfile = os.path.join(destdir, self.name + '.files')
324 f = open(contfile, "w")
329 pkgcont = self._pkgs_content[pkg]
331 content += '\n '.join(pkgcont)
337 self.outimage.append(contfile)
339 if 'license' in self._recording_pkgs:
340 licensefile = os.path.join(destdir, self.name + '.license')
341 f = open(licensefile, "w")
343 f.write('Summary:\n')
344 for license in reversed(sorted(self._pkgs_license, key=\
345 lambda license: len(self._pkgs_license[license]))):
346 f.write(" - %s: %s\n" \
347 % (license, len(self._pkgs_license[license])))
349 f.write('\nDetails:\n')
350 for license in reversed(sorted(self._pkgs_license, key=\
351 lambda license: len(self._pkgs_license[license]))):
352 f.write(" - %s:\n" % (license))
353 for pkg in sorted(self._pkgs_license[license]):
354 f.write(" - %s\n" % (pkg))
358 self.outimage.append(licensefile)
360 def _get_required_packages(self):
361 """Return a list of required packages.
363 This is the hook where subclasses may specify a set of packages which
364 it requires to be installed.
366 This returns an empty list by default.
368 Note, subclasses should usually chain up to the base class
369 implementation of this hook.
374 def _get_excluded_packages(self):
375 """Return a list of excluded packages.
377 This is the hook where subclasses may specify a set of packages which
378 it requires _not_ to be installed.
380 This returns an empty list by default.
382 Note, subclasses should usually chain up to the base class
383 implementation of this hook.
388 def _get_local_packages(self):
389 """Return a list of rpm path to be local installed.
391 This is the hook where subclasses may specify a set of rpms which
392 it requires to be installed locally.
394 This returns an empty list by default.
396 Note, subclasses should usually chain up to the base class
397 implementation of this hook.
400 if self._local_pkgs_path:
401 if os.path.isdir(self._local_pkgs_path):
403 os.path.join(self._local_pkgs_path, '*.rpm'))
404 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
405 return [self._local_pkgs_path]
409 def _get_fstab(self):
410 """Return the desired contents of /etc/fstab.
412 This is the hook where subclasses may specify the contents of
413 /etc/fstab by returning a string containing the desired contents.
415 A sensible default implementation is provided.
418 s = "/dev/root / %s %s 0 0\n" \
420 "defaults,noatime" if not self._fsopts else self._fsopts)
421 s += self._get_fstab_special()
424 def _get_fstab_special(self):
425 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
426 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
427 s += "proc /proc proc defaults 0 0\n"
428 s += "sysfs /sys sysfs defaults 0 0\n"
431 def _set_part_env(self, pnum, prop, value):
432 """ This is a helper function which generates an environment variable
433 for a property "prop" with value "value" of a partition number "pnum".
435 The naming convention is:
436 * Variables start with INSTALLERFW_PART
437 * Then goes the partition number, the order is the same as
438 specified in the KS file
439 * Then goes the property name
447 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
448 return { name : value }
450 def _get_post_scripts_env(self, in_chroot):
451 """Return an environment dict for %post scripts.
453 This is the hook where subclasses may specify some environment
454 variables for %post scripts by return a dict containing the desired
457 in_chroot -- whether this %post script is to be executed chroot()ed
464 for p in kickstart.get_partitions(self.ks):
465 env.update(self._set_part_env(pnum, "SIZE", p.size))
466 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
467 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
468 env.update(self._set_part_env(pnum, "LABEL", p.label))
469 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
470 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
471 env.update(self._set_part_env(pnum, "ALIGN", p.align))
472 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
473 env.update(self._set_part_env(pnum, "UUID", p.uuid))
474 env.update(self._set_part_env(pnum, "DEVNODE",
475 "/dev/%s%d" % (p.disk, pnum + 1)))
476 env.update(self._set_part_env(pnum, "DISK_DEVNODE",
481 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
483 # Partition table format
484 ptable_format = self.ks.handler.bootloader.ptable
485 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
487 # The kerned boot parameters
488 kernel_opts = self.ks.handler.bootloader.appendLine
489 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
491 # Name of the image creation tool
492 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
494 # The real current location of the mounted file-systems
498 mount_prefix = self._instroot
499 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
501 # These are historical variables which lack the common name prefix
503 env["INSTALL_ROOT"] = self._instroot
504 env["IMG_NAME"] = self._name
508 def __get_imgname(self):
510 _name = property(__get_imgname)
511 """The name of the image file.
515 def _get_kernel_versions(self):
516 """Return a dict detailing the available kernel types/versions.
518 This is the hook where subclasses may override what kernel types and
519 versions should be available for e.g. creating the booloader
522 A dict should be returned mapping the available kernel types to a list
523 of the available versions for those kernels.
525 The default implementation uses rpm to iterate over everything
526 providing 'kernel', finds /boot/vmlinuz-* and returns the version
527 obtained from the vmlinuz filename. (This can differ from the kernel
528 RPM's n-v-r in the case of e.g. xen)
531 def get_kernel_versions(instroot):
534 files = glob.glob(instroot + "/boot/vmlinuz-*")
536 version = os.path.basename(file)[8:]
539 versions.add(version)
540 ret["kernel"] = list(versions)
543 def get_version(header):
545 for f in header['filenames']:
546 if f.startswith('/boot/vmlinuz-'):
551 return get_kernel_versions(self._instroot)
553 ts = rpm.TransactionSet(self._instroot)
556 for header in ts.dbMatch('provides', 'kernel'):
557 version = get_version(header)
561 name = header['name']
563 ret[name] = [version]
564 elif not version in ret[name]:
565 ret[name].append(version)
571 # Helpers for subclasses
573 def _do_bindmounts(self):
574 """Mount various system directories onto _instroot.
576 This method is called by mount(), but may also be used by subclasses
577 in order to re-mount the bindmounts after modifying the underlying
581 for b in self.__bindmounts:
584 def _undo_bindmounts(self):
585 """Unmount the bind-mounted system directories from _instroot.
587 This method is usually only called by unmount(), but may also be used
588 by subclasses in order to gain access to the filesystem obscured by
589 the bindmounts - e.g. in order to create device nodes on the image
593 self.__bindmounts.reverse()
594 for b in self.__bindmounts:
598 """Chroot into the install root.
600 This method may be used by subclasses when executing programs inside
601 the install root e.g.
603 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
606 os.chroot(self._instroot)
609 def _mkdtemp(self, prefix = "tmp-"):
610 """Create a temporary directory.
612 This method may be used by subclasses to create a temporary directory
613 for use in building the final image - e.g. a subclass might create
614 a temporary directory in order to bundle a set of files into a package.
616 The subclass may delete this directory if it wishes, but it will be
617 automatically deleted by cleanup().
619 The 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 directory;
627 self.__ensure_builddir()
628 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
630 def _mkstemp(self, prefix = "tmp-"):
631 """Create a temporary file.
633 This method may be used by subclasses to create a temporary file
634 for use in building the final image - e.g. a subclass might need
635 a temporary location to unpack a compressed file.
637 The subclass may delete this file if it wishes, but it will be
638 automatically deleted by cleanup().
640 A tuple containing a file descriptor (returned from os.open() and the
641 absolute path to the temporary directory is returned.
643 Note, this method should only be called after mount() has been called.
645 prefix -- a prefix which should be used when creating the file;
649 self.__ensure_builddir()
650 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
652 def _mktemp(self, prefix = "tmp-"):
653 """Create a temporary file.
655 This method simply calls _mkstemp() and closes the returned file
658 The absolute path to the temporary file is returned.
660 Note, this method should only be called after mount() has been called.
662 prefix -- a prefix which should be used when creating the file;
667 (f, path) = self._mkstemp(prefix)
673 # Actual implementation
675 def __ensure_builddir(self):
676 if not self.__builddir is None:
680 self.workdir = os.path.join(self.tmpdir, "build")
681 if not os.path.exists(self.workdir):
682 os.makedirs(self.workdir)
683 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
684 prefix = "imgcreate-")
685 except OSError, (err, msg):
686 raise CreatorError("Failed create build directory in %s: %s" %
689 def get_cachedir(self, cachedir = None):
693 self.__ensure_builddir()
695 self.cachedir = cachedir
697 self.cachedir = self.__builddir + "/mic-cache"
698 fs.makedirs(self.cachedir)
701 def __sanity_check(self):
702 """Ensure that the config we've been given is sane."""
703 if not (kickstart.get_packages(self.ks) or
704 kickstart.get_groups(self.ks)):
705 raise CreatorError("No packages or groups specified")
707 kickstart.convert_method_to_repo(self.ks)
709 if not kickstart.get_repos(self.ks):
710 raise CreatorError("No repositories specified")
712 def __write_fstab(self):
713 if kickstart.use_installerfw(self.ks, "fstab"):
714 # The fstab file will be generated by installer framework scripts
717 fstab_contents = self._get_fstab()
719 fstab = open(self._instroot + "/etc/fstab", "w")
720 fstab.write(fstab_contents)
723 def __create_minimal_dev(self):
724 """Create a minimal /dev so that we don't corrupt the host /dev"""
725 origumask = os.umask(0000)
726 devices = (('null', 1, 3, 0666),
727 ('urandom',1, 9, 0666),
728 ('random', 1, 8, 0666),
729 ('full', 1, 7, 0666),
730 ('ptmx', 5, 2, 0666),
732 ('zero', 1, 5, 0666))
734 links = (("/proc/self/fd", "/dev/fd"),
735 ("/proc/self/fd/0", "/dev/stdin"),
736 ("/proc/self/fd/1", "/dev/stdout"),
737 ("/proc/self/fd/2", "/dev/stderr"))
739 for (node, major, minor, perm) in devices:
740 if not os.path.exists(self._instroot + "/dev/" + node):
741 os.mknod(self._instroot + "/dev/" + node,
743 os.makedev(major,minor))
745 for (src, dest) in links:
746 if not os.path.exists(self._instroot + dest):
747 os.symlink(src, self._instroot + dest)
751 def __setup_tmpdir(self):
752 if not self.enabletmpfs:
755 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
757 def __clean_tmpdir(self):
758 if not self.enabletmpfs:
761 runner.show('umount -l %s' % self.workdir)
763 def mount(self, base_on = None, cachedir = None):
764 """Setup the target filesystem in preparation for an install.
766 This function sets up the filesystem which the ImageCreator will
767 install into and configure. The ImageCreator class merely creates an
768 install root directory, bind mounts some system directories (e.g. /dev)
769 and writes out /etc/fstab. Other subclasses may also e.g. create a
770 sparse file, format it and loopback mount it to the install root.
772 base_on -- a previous install on which to base this install; defaults
773 to None, causing a new image to be created
775 cachedir -- a directory in which to store the Yum cache; defaults to
776 None, causing a new cache to be created; by setting this
777 to another directory, the same cache can be reused across
781 self.__setup_tmpdir()
782 self.__ensure_builddir()
784 # prevent popup dialog in Ubuntu(s)
785 misc.hide_loopdev_presentation()
787 fs.makedirs(self._instroot)
788 fs.makedirs(self._outdir)
790 self._mount_instroot(base_on)
792 for d in ("/dev/pts",
799 fs.makedirs(self._instroot + d)
801 if self.target_arch and self.target_arch.startswith("arm") or \
802 self.target_arch == "aarch64" or self.target_arch == "mipsel" :
803 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
806 self.get_cachedir(cachedir)
808 # bind mount system directories into _instroot
809 for (f, dest) in [("/sys", None),
811 ("/proc/sys/fs/binfmt_misc", None),
813 self.__bindmounts.append(
815 f, self._instroot, dest))
817 self._do_bindmounts()
819 self.__create_minimal_dev()
821 if os.path.exists(self._instroot + "/etc/mtab"):
822 os.unlink(self._instroot + "/etc/mtab")
823 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
827 # get size of available space in 'instroot' fs
828 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
831 """Unmounts the target filesystem.
833 The ImageCreator class detaches the system from the install root, but
834 other subclasses may also detach the loopback mounted filesystem image
835 from the install root.
839 mtab = self._instroot + "/etc/mtab"
840 if not os.path.islink(mtab):
841 os.unlink(self._instroot + "/etc/mtab")
843 if self.qemu_emulator:
844 os.unlink(self._instroot + self.qemu_emulator)
848 self._undo_bindmounts()
850 """ Clean up yum garbage """
852 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
853 if os.path.exists(instroot_pdir):
854 shutil.rmtree(instroot_pdir, ignore_errors = True)
855 yumlibdir = self._instroot + "/var/lib/yum"
856 if os.path.exists(yumlibdir):
857 shutil.rmtree(yumlibdir, ignore_errors = True)
861 self._unmount_instroot()
863 # reset settings of popup dialog in Ubuntu(s)
864 misc.unhide_loopdev_presentation()
868 """Unmounts the target filesystem and deletes temporary files.
870 This method calls unmount() and then deletes any temporary files and
871 directories that were created on the host system while building the
874 Note, make sure to call this method once finished with the creator
875 instance in order to ensure no stale files are left on the host e.g.:
877 creator = ImageCreator(ks, name)
884 if not self.__builddir:
887 kill_proc_inchroot(self._instroot)
891 shutil.rmtree(self.__builddir, ignore_errors = True)
892 self.__builddir = None
894 self.__clean_tmpdir()
896 def __is_excluded_pkg(self, pkg):
897 if pkg in self._excluded_pkgs:
898 self._excluded_pkgs.remove(pkg)
901 for xpkg in self._excluded_pkgs:
902 if xpkg.endswith('*'):
903 if pkg.startswith(xpkg[:-1]):
905 elif xpkg.startswith('*'):
906 if pkg.endswith(xpkg[1:]):
911 def __select_packages(self, pkg_manager):
913 for pkg in self._required_pkgs:
914 e = pkg_manager.selectPackage(pkg)
916 if kickstart.ignore_missing(self.ks):
917 skipped_pkgs.append(pkg)
918 elif self.__is_excluded_pkg(pkg):
919 skipped_pkgs.append(pkg)
921 raise CreatorError("Failed to find package '%s' : %s" %
924 for pkg in skipped_pkgs:
925 msger.warning("Skipping missing package '%s'" % (pkg,))
927 def __select_groups(self, pkg_manager):
929 for group in self._required_groups:
930 e = pkg_manager.selectGroup(group.name, group.include)
932 if kickstart.ignore_missing(self.ks):
933 skipped_groups.append(group)
935 raise CreatorError("Failed to find group '%s' : %s" %
938 for group in skipped_groups:
939 msger.warning("Skipping missing group '%s'" % (group.name,))
941 def __deselect_packages(self, pkg_manager):
942 for pkg in self._excluded_pkgs:
943 pkg_manager.deselectPackage(pkg)
945 def __localinst_packages(self, pkg_manager):
946 for rpm_path in self._get_local_packages():
947 pkg_manager.installLocal(rpm_path)
949 def __preinstall_packages(self, pkg_manager):
953 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
954 for pkg in self._preinstall_pkgs:
955 pkg_manager.preInstall(pkg)
957 def __check_packages(self, pkg_manager):
958 for pkg in self.check_pkgs:
959 pkg_manager.checkPackage(pkg)
961 def __attachment_packages(self, pkg_manager):
965 self._attachment = []
966 for item in kickstart.get_attachment(self.ks):
967 if item.startswith('/'):
968 fpaths = os.path.join(self._instroot, item.lstrip('/'))
969 for fpath in glob.glob(fpaths):
970 self._attachment.append(fpath)
973 filelist = pkg_manager.getFilelist(item)
975 # found rpm in rootfs
976 for pfile in pkg_manager.getFilelist(item):
977 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
978 self._attachment.append(fpath)
981 # try to retrieve rpm file
982 (url, proxies) = pkg_manager.package_url(item)
984 msger.warning("Can't get url from repo for %s" % item)
986 fpath = os.path.join(self.cachedir, os.path.basename(url))
987 if not os.path.exists(fpath):
990 fpath = grabber.myurlgrab(url.full, fpath, proxies, None)
994 tmpdir = self._mkdtemp()
995 misc.extract_rpm(fpath, tmpdir)
996 for (root, dirs, files) in os.walk(tmpdir):
998 fpath = os.path.join(root, fname)
999 self._attachment.append(fpath)
1001 def install(self, repo_urls=None):
1002 """Install packages into the install root.
1004 This function installs the packages listed in the supplied kickstart
1005 into the install root. By default, the packages are installed from the
1006 repository URLs specified in the kickstart.
1008 repo_urls -- a dict which maps a repository name to a repository;
1009 if supplied, this causes any repository URLs specified in
1010 the kickstart to be overridden.
1013 def get_ssl_verify(ssl_verify=None):
1014 if ssl_verify is not None:
1015 return not ssl_verify.lower().strip() == 'no'
1017 return not self.ssl_verify.lower().strip() == 'no'
1019 # initialize pkg list to install
1021 self.__sanity_check()
1023 self._required_pkgs = \
1024 kickstart.get_packages(self.ks, self._get_required_packages())
1025 self._excluded_pkgs = \
1026 kickstart.get_excluded(self.ks, self._get_excluded_packages())
1027 self._required_groups = kickstart.get_groups(self.ks)
1029 self._required_pkgs = None
1030 self._excluded_pkgs = None
1031 self._required_groups = None
1034 repo_urls = self.extrarepos
1036 pkg_manager = self.get_pkg_manager()
1039 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1040 if 'debuginfo' in self.install_pkgs:
1041 pkg_manager.install_debuginfo = True
1043 for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1044 (name, baseurl, mirrorlist, inc, exc,
1045 proxy, proxy_username, proxy_password, debuginfo,
1046 source, gpgkey, disable, ssl_verify, nocache,
1047 cost, priority) = repo
1049 ssl_verify = get_ssl_verify(ssl_verify)
1050 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1051 proxy_username, proxy_password, inc, exc, ssl_verify,
1052 nocache, cost, priority)
1054 if kickstart.exclude_docs(self.ks):
1055 rpm.addMacro("_excludedocs", "1")
1056 rpm.addMacro("_dbpath", "/var/lib/rpm")
1057 rpm.addMacro("__file_context_path", "%{nil}")
1058 if kickstart.inst_langs(self.ks) != None:
1059 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1062 self.__preinstall_packages(pkg_manager)
1063 self.__select_packages(pkg_manager)
1064 self.__select_groups(pkg_manager)
1065 self.__deselect_packages(pkg_manager)
1066 self.__localinst_packages(pkg_manager)
1067 self.__check_packages(pkg_manager)
1069 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1070 checksize = self._root_fs_avail
1072 checksize -= BOOT_SAFEGUARD
1073 if self.target_arch:
1074 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1075 pkg_manager.runInstall(checksize)
1076 except CreatorError, e:
1078 except KeyboardInterrupt:
1081 self._pkgs_content = pkg_manager.getAllContent()
1082 self._pkgs_license = pkg_manager.getPkgsLicense()
1083 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1084 self.__attachment_packages(pkg_manager)
1091 # do some clean up to avoid lvm info leakage. this sucks.
1092 for subdir in ("cache", "backup", "archive"):
1093 lvmdir = self._instroot + "/etc/lvm/" + subdir
1095 for f in os.listdir(lvmdir):
1096 os.unlink(lvmdir + "/" + f)
1100 def postinstall(self):
1101 self.copy_attachment()
1103 def __run_post_scripts(self):
1104 msger.info("Running scripts ...")
1105 if os.path.exists(self._instroot + "/tmp"):
1106 shutil.rmtree(self._instroot + "/tmp")
1107 os.mkdir (self._instroot + "/tmp", 0755)
1108 for s in kickstart.get_post_scripts(self.ks):
1109 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1110 dir = self._instroot + "/tmp")
1112 s.script = s.script.replace("\r", "")
1113 os.write(fd, s.script)
1115 os.chmod(path, 0700)
1117 env = self._get_post_scripts_env(s.inChroot)
1118 if 'PATH' not in env:
1119 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1125 preexec = self._chroot
1126 script = "/tmp/" + os.path.basename(path)
1130 p = subprocess.Popen([s.interp, script],
1131 preexec_fn = preexec,
1133 stdout = subprocess.PIPE,
1134 stderr = subprocess.STDOUT)
1135 for entry in p.communicate()[0].splitlines():
1137 except OSError, (err, msg):
1138 raise CreatorError("Failed to execute %%post script "
1139 "with '%s' : %s" % (s.interp, msg))
1143 def __save_repo_keys(self, repodata):
1147 gpgkeydir = "/etc/pki/rpm-gpg"
1148 fs.makedirs(self._instroot + gpgkeydir)
1149 for repo in repodata:
1151 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1152 shutil.copy(repo["repokey"], self._instroot + repokey)
1154 def configure(self, repodata = None):
1155 """Configure the system image according to the kickstart.
1157 This method applies the (e.g. keyboard or network) configuration
1158 specified in the kickstart and executes the kickstart %post scripts.
1160 If necessary, it also prepares the image to be bootable by e.g.
1161 creating an initrd and bootloader configuration.
1164 ksh = self.ks.handler
1166 msger.info('Applying configurations ...')
1168 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1169 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1170 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1171 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1172 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1173 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1174 kickstart.UserConfig(self._instroot).apply(ksh.user)
1175 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1176 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1177 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1178 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1179 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1180 self.__save_repo_keys(repodata)
1181 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1183 msger.warning("Failed to apply configuration to image")
1186 self._create_bootconfig()
1187 self.__run_post_scripts()
1189 def launch_shell(self, launch):
1190 """Launch a shell in the install root.
1192 This method is launches a bash shell chroot()ed in the install root;
1193 this can be useful for debugging.
1197 msger.info("Launching shell. Exit to continue.")
1198 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1200 def do_genchecksum(self, image_name):
1201 if not self._genchecksum:
1204 md5sum = misc.get_md5sum(image_name)
1205 with open(image_name + ".md5sum", "w") as f:
1206 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1207 self.outimage.append(image_name+".md5sum")
1209 def package(self, destdir = "."):
1210 """Prepares the created image for final delivery.
1212 In its simplest form, this method merely copies the install root to the
1213 supplied destination directory; other subclasses may choose to package
1214 the image by e.g. creating a bootable ISO containing the image and
1215 bootloader configuration.
1217 destdir -- the directory into which the final image should be moved;
1218 this defaults to the current directory.
1221 self._stage_final_image()
1223 if not os.path.exists(destdir):
1224 fs.makedirs(destdir)
1226 if self._recording_pkgs:
1227 self._save_recording_pkgs(destdir)
1229 # For image formats with two or multiple image files, it will be
1230 # better to put them under a directory
1231 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1232 destdir = os.path.join(destdir, "%s-%s" \
1233 % (self.name, self.image_format))
1234 msger.debug("creating destination dir: %s" % destdir)
1235 fs.makedirs(destdir)
1237 # Ensure all data is flushed to _outdir
1238 runner.quiet('sync')
1240 misc.check_space_pre_cp(self._outdir, destdir)
1241 for f in os.listdir(self._outdir):
1242 shutil.move(os.path.join(self._outdir, f),
1243 os.path.join(destdir, f))
1244 self.outimage.append(os.path.join(destdir, f))
1245 self.do_genchecksum(os.path.join(destdir, f))
1247 def print_outimage_info(self):
1248 msg = "The new image can be found here:\n"
1249 self.outimage.sort()
1250 for file in self.outimage:
1251 msg += ' %s\n' % os.path.abspath(file)
1255 def check_depend_tools(self):
1256 for tool in self._dep_checks:
1257 fs.find_binary_path(tool)
1259 def package_output(self, image_format, destdir = ".", package="none"):
1260 if not package or package == "none":
1263 destdir = os.path.abspath(os.path.expanduser(destdir))
1264 (pkg, comp) = os.path.splitext(package)
1266 comp=comp.lstrip(".")
1270 dst = "%s/%s-%s.tar.%s" %\
1271 (destdir, self.name, image_format, comp)
1273 dst = "%s/%s-%s.tar" %\
1274 (destdir, self.name, image_format)
1276 msger.info("creating %s" % dst)
1277 tar = tarfile.open(dst, "w:" + comp)
1279 for file in self.outimage:
1280 msger.info("adding %s to %s" % (file, dst))
1282 arcname=os.path.join("%s-%s" \
1283 % (self.name, image_format),
1284 os.path.basename(file)))
1285 if os.path.isdir(file):
1286 shutil.rmtree(file, ignore_errors = True)
1292 '''All the file in outimage has been packaged into tar.* file'''
1293 self.outimage = [dst]
1295 def release_output(self, config, destdir, release):
1296 """ Create release directory and files
1300 """ release path """
1301 return os.path.join(destdir, fn)
1303 outimages = self.outimage
1306 new_kspath = _rpath(self.name+'.ks')
1307 with open(config) as fr:
1308 with open(new_kspath, "w") as wf:
1309 # When building a release we want to make sure the .ks
1310 # file generates the same build even when --release not used.
1311 wf.write(fr.read().replace("@BUILD_ID@", release))
1312 outimages.append(new_kspath)
1314 # save log file, logfile is only available in creator attrs
1315 if hasattr(self, 'releaselog') and self.releaselog:
1316 final_logfile = _rpath(self.name+'.log')
1317 shutil.move(self.logfile, final_logfile)
1318 self.logfile = final_logfile
1319 outimages.append(self.logfile)
1321 # rename iso and usbimg
1322 for f in os.listdir(destdir):
1323 if f.endswith(".iso"):
1324 newf = f[:-4] + '.img'
1325 elif f.endswith(".usbimg"):
1326 newf = f[:-7] + '.img'
1329 os.rename(_rpath(f), _rpath(newf))
1330 outimages.append(_rpath(newf))
1332 # generate MD5SUMS SHA1SUMS SHA256SUMS
1333 def generate_hashsum(hash_name, hash_method):
1334 with open(_rpath(hash_name), "w") as wf:
1335 for f in os.listdir(destdir):
1336 if f.endswith('SUMS'):
1339 if os.path.isdir(os.path.join(destdir, f)):
1342 hash_value = hash_method(_rpath(f))
1343 # There needs to be two spaces between the sum and
1344 # filepath to match the syntax with md5sum,sha1sum,
1345 # sha256sum. This way also *sum -c *SUMS can be used.
1346 wf.write("%s %s\n" % (hash_value, f))
1348 outimages.append("%s/%s" % (destdir, hash_name))
1351 'MD5SUMS' : misc.get_md5sum,
1352 'SHA1SUMS' : misc.get_sha1sum,
1353 'SHA256SUMS' : misc.get_sha256sum
1356 for k, v in hash_dict.items():
1357 generate_hashsum(k, v)
1359 # Filter out the nonexist file
1360 for fp in outimages[:]:
1361 if not os.path.exists("%s" % fp):
1362 outimages.remove(fp)
1364 def copy_kernel(self):
1365 """ Copy kernel files to the outimage directory.
1366 NOTE: This needs to be called before unmounting the instroot.
1369 if not self._need_copy_kernel:
1372 if not os.path.exists(self.destdir):
1373 os.makedirs(self.destdir)
1375 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1376 kernelfilename = "%s/%s-%s" % (self.destdir,
1378 os.path.basename(kernel))
1379 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1381 shutil.copy(kernel, kernelfilename)
1382 self.outimage.append(kernelfilename)
1384 def copy_attachment(self):
1385 """ Subclass implement it to handle attachment files
1386 NOTE: This needs to be called before unmounting the instroot.
1390 def get_pkg_manager(self):
1391 return self.pkgmgr(target_arch = self.target_arch,
1392 instroot = self._instroot,
1393 cachedir = self.cachedir,
1394 strict_mode = self.strict_mode)
1396 def create_manifest(self):
1397 def get_pack_suffix():
1398 return '.' + self.pack_to.split('.', 1)[1]
1400 if not os.path.exists(self.destdir):
1401 os.makedirs(self.destdir)
1403 now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1404 manifest_dict = {'version': VERSION,
1407 manifest_dict.update({'format': self.img_format})
1409 if hasattr(self, 'logfile') and self.logfile:
1410 manifest_dict.update({'log_file': self.logfile})
1412 if self.image_files:
1414 self.image_files.update({'pack': get_pack_suffix()})
1415 manifest_dict.update({self.img_format: self.image_files})
1417 msger.info('Creating manifest file...')
1418 manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1419 with open(manifest_file_path, 'w') as fest_file:
1420 json.dump(manifest_dict, fest_file, indent=4)
1421 self.outimage.append(manifest_file_path)