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 len(self.ks.handler.partition.partitions) > 1:
156 self.multiple_partitions = True
159 if self.target_arch.startswith("arm"):
160 for dep in self._dep_checks:
161 if dep == "extlinux":
162 self._dep_checks.remove(dep)
164 if not os.path.exists("/usr/bin/qemu-arm") or \
165 not misc.is_statically_linked("/usr/bin/qemu-arm"):
166 self._dep_checks.append("qemu-arm-static")
168 if os.path.exists("/proc/sys/vm/vdso_enabled"):
169 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
170 vdso_value = vdso_fh.read().strip()
172 if (int)(vdso_value) == 1:
173 msger.warning("vdso is enabled on your host, which might "
174 "cause problems with arm emulations.\n"
175 "\tYou can disable vdso with following command before "
176 "starting image build:\n"
177 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
178 elif self.target_arch == "mipsel":
179 for dep in self._dep_checks:
180 if dep == "extlinux":
181 self._dep_checks.remove(dep)
183 if not os.path.exists("/usr/bin/qemu-mipsel") or \
184 not misc.is_statically_linked("/usr/bin/qemu-mipsel"):
185 self._dep_checks.append("qemu-mipsel-static")
187 if os.path.exists("/proc/sys/vm/vdso_enabled"):
188 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
189 vdso_value = vdso_fh.read().strip()
191 if (int)(vdso_value) == 1:
192 msger.warning("vdso is enabled on your host, which might "
193 "cause problems with mipsel emulations.\n"
194 "\tYou can disable vdso with following command before "
195 "starting image build:\n"
196 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
198 # make sure the specified tmpdir and cachedir exist
199 if not os.path.exists(self.tmpdir):
200 os.makedirs(self.tmpdir)
201 if not os.path.exists(self.cachedir):
202 os.makedirs(self.cachedir)
208 def __get_instroot(self):
209 if self.__builddir is None:
210 raise CreatorError("_instroot is not valid before calling mount()")
211 return self.__builddir + "/install_root"
212 _instroot = property(__get_instroot)
213 """The location of the install root directory.
215 This is the directory into which the system is installed. Subclasses may
216 mount a filesystem image here or copy files to/from here.
218 Note, this directory does not exist before ImageCreator.mount() is called.
220 Note also, this is a read-only attribute.
224 def __get_outdir(self):
225 if self.__builddir is None:
226 raise CreatorError("_outdir is not valid before calling mount()")
227 return self.__builddir + "/out"
228 _outdir = property(__get_outdir)
229 """The staging location for the final image.
231 This is where subclasses should stage any files that are part of the final
232 image. ImageCreator.package() will copy any files found here into the
233 requested destination directory.
235 Note, this directory does not exist before ImageCreator.mount() is called.
237 Note also, this is a read-only attribute.
243 # Hooks for subclasses
245 def _mount_instroot(self, base_on = None):
246 """Mount or prepare the install root directory.
248 This is the hook where subclasses may prepare the install root by e.g.
249 mounting creating and loopback mounting a filesystem image to
252 There is no default implementation.
254 base_on -- this is the value passed to mount() and can be interpreted
255 as the subclass wishes; it might e.g. be the location of
256 a previously created ISO containing a system image.
261 def _unmount_instroot(self):
262 """Undo anything performed in _mount_instroot().
264 This is the hook where subclasses must undo anything which was done
265 in _mount_instroot(). For example, if a filesystem image was mounted
266 onto _instroot, it should be unmounted here.
268 There is no default implementation.
273 def _create_bootconfig(self):
274 """Configure the image so that it's bootable.
276 This is the hook where subclasses may prepare the image for booting by
277 e.g. creating an initramfs and bootloader configuration.
279 This hook is called while the install root is still mounted, after the
280 packages have been installed and the kickstart configuration has been
281 applied, but before the %post scripts have been executed.
283 There is no default implementation.
288 def _stage_final_image(self):
289 """Stage the final system image in _outdir.
291 This is the hook where subclasses should place the image in _outdir
292 so that package() can copy it to the requested destination directory.
294 By default, this moves the install root into _outdir.
297 shutil.move(self._instroot, self._outdir + "/" + self.name)
299 def get_installed_packages(self):
300 return self._pkgs_content.keys()
302 def _save_recording_pkgs(self, destdir):
303 """Save the list or content of installed packages to file.
305 pkgs = self._pkgs_content.keys()
306 pkgs.sort() # inplace op
308 if not os.path.exists(destdir):
312 if 'vcs' in self._recording_pkgs:
313 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
314 content = '\n'.join(sorted(vcslst))
315 elif 'name' in self._recording_pkgs:
316 content = '\n'.join(pkgs)
318 namefile = os.path.join(destdir, self.name + '.packages')
319 f = open(namefile, "w")
322 self.outimage.append(namefile);
324 # if 'content', save more details
325 if 'content' in self._recording_pkgs:
326 contfile = os.path.join(destdir, self.name + '.files')
327 f = open(contfile, "w")
332 pkgcont = self._pkgs_content[pkg]
334 content += '\n '.join(pkgcont)
340 self.outimage.append(contfile)
342 if 'license' in self._recording_pkgs:
343 licensefile = os.path.join(destdir, self.name + '.license')
344 f = open(licensefile, "w")
346 f.write('Summary:\n')
347 for license in reversed(sorted(self._pkgs_license, key=\
348 lambda license: len(self._pkgs_license[license]))):
349 f.write(" - %s: %s\n" \
350 % (license, len(self._pkgs_license[license])))
352 f.write('\nDetails:\n')
353 for license in reversed(sorted(self._pkgs_license, key=\
354 lambda license: len(self._pkgs_license[license]))):
355 f.write(" - %s:\n" % (license))
356 for pkg in sorted(self._pkgs_license[license]):
357 f.write(" - %s\n" % (pkg))
361 self.outimage.append(licensefile)
363 def _get_required_packages(self):
364 """Return a list of required packages.
366 This is the hook where subclasses may specify a set of packages which
367 it requires to be installed.
369 This returns an empty list by default.
371 Note, subclasses should usually chain up to the base class
372 implementation of this hook.
377 def _get_excluded_packages(self):
378 """Return a list of excluded packages.
380 This is the hook where subclasses may specify a set of packages which
381 it requires _not_ to be installed.
383 This returns an empty list by default.
385 Note, subclasses should usually chain up to the base class
386 implementation of this hook.
391 def _get_local_packages(self):
392 """Return a list of rpm path to be local installed.
394 This is the hook where subclasses may specify a set of rpms which
395 it requires to be installed locally.
397 This returns an empty list by default.
399 Note, subclasses should usually chain up to the base class
400 implementation of this hook.
403 if self._local_pkgs_path:
404 if os.path.isdir(self._local_pkgs_path):
406 os.path.join(self._local_pkgs_path, '*.rpm'))
407 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
408 return [self._local_pkgs_path]
412 def _get_fstab(self):
413 """Return the desired contents of /etc/fstab.
415 This is the hook where subclasses may specify the contents of
416 /etc/fstab by returning a string containing the desired contents.
418 A sensible default implementation is provided.
421 s = "/dev/root / %s %s 0 0\n" \
423 "defaults,noatime" if not self._fsopts else self._fsopts)
424 s += self._get_fstab_special()
427 def _get_fstab_special(self):
428 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
429 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
430 s += "proc /proc proc defaults 0 0\n"
431 s += "sysfs /sys sysfs defaults 0 0\n"
434 def _set_part_env(self, pnum, prop, value):
435 """ This is a helper function which generates an environment variable
436 for a property "prop" with value "value" of a partition number "pnum".
438 The naming convention is:
439 * Variables start with INSTALLERFW_PART
440 * Then goes the partition number, the order is the same as
441 specified in the KS file
442 * Then goes the property name
450 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
451 return { name : value }
453 def _get_post_scripts_env(self, in_chroot):
454 """Return an environment dict for %post scripts.
456 This is the hook where subclasses may specify some environment
457 variables for %post scripts by return a dict containing the desired
460 in_chroot -- whether this %post script is to be executed chroot()ed
467 for p in kickstart.get_partitions(self.ks):
468 env.update(self._set_part_env(pnum, "SIZE", p.size))
469 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
470 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
471 env.update(self._set_part_env(pnum, "LABEL", p.label))
472 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
473 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
474 env.update(self._set_part_env(pnum, "ALIGN", p.align))
475 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
476 env.update(self._set_part_env(pnum, "UUID", p.uuid))
477 env.update(self._set_part_env(pnum, "DEVNODE",
478 "/dev/%s%d" % (p.disk, pnum + 1)))
479 env.update(self._set_part_env(pnum, "DISK_DEVNODE",
484 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
486 # Partition table format
487 ptable_format = self.ks.handler.bootloader.ptable
488 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
490 # The kerned boot parameters
491 kernel_opts = self.ks.handler.bootloader.appendLine
492 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
494 # Name of the image creation tool
495 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
497 # The real current location of the mounted file-systems
501 mount_prefix = self._instroot
502 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
504 # These are historical variables which lack the common name prefix
506 env["INSTALL_ROOT"] = self._instroot
507 env["IMG_NAME"] = self._name
511 def __get_imgname(self):
513 _name = property(__get_imgname)
514 """The name of the image file.
518 def _get_kernel_versions(self):
519 """Return a dict detailing the available kernel types/versions.
521 This is the hook where subclasses may override what kernel types and
522 versions should be available for e.g. creating the booloader
525 A dict should be returned mapping the available kernel types to a list
526 of the available versions for those kernels.
528 The default implementation uses rpm to iterate over everything
529 providing 'kernel', finds /boot/vmlinuz-* and returns the version
530 obtained from the vmlinuz filename. (This can differ from the kernel
531 RPM's n-v-r in the case of e.g. xen)
534 def get_kernel_versions(instroot):
537 files = glob.glob(instroot + "/boot/vmlinuz-*")
539 version = os.path.basename(file)[8:]
542 versions.add(version)
543 ret["kernel"] = list(versions)
546 def get_version(header):
548 for f in header['filenames']:
549 if f.startswith('/boot/vmlinuz-'):
554 return get_kernel_versions(self._instroot)
556 ts = rpm.TransactionSet(self._instroot)
559 for header in ts.dbMatch('provides', 'kernel'):
560 version = get_version(header)
564 name = header['name']
566 ret[name] = [version]
567 elif not version in ret[name]:
568 ret[name].append(version)
574 # Helpers for subclasses
576 def _do_bindmounts(self):
577 """Mount various system directories onto _instroot.
579 This method is called by mount(), but may also be used by subclasses
580 in order to re-mount the bindmounts after modifying the underlying
584 for b in self.__bindmounts:
587 def _undo_bindmounts(self):
588 """Unmount the bind-mounted system directories from _instroot.
590 This method is usually only called by unmount(), but may also be used
591 by subclasses in order to gain access to the filesystem obscured by
592 the bindmounts - e.g. in order to create device nodes on the image
596 self.__bindmounts.reverse()
597 for b in self.__bindmounts:
601 """Chroot into the install root.
603 This method may be used by subclasses when executing programs inside
604 the install root e.g.
606 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
609 os.chroot(self._instroot)
612 def _mkdtemp(self, prefix = "tmp-"):
613 """Create a temporary directory.
615 This method may be used by subclasses to create a temporary directory
616 for use in building the final image - e.g. a subclass might create
617 a temporary directory in order to bundle a set of files into a package.
619 The subclass may delete this directory if it wishes, but it will be
620 automatically deleted by cleanup().
622 The absolute path to the temporary directory is returned.
624 Note, this method should only be called after mount() has been called.
626 prefix -- a prefix which should be used when creating the directory;
630 self.__ensure_builddir()
631 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
633 def _mkstemp(self, prefix = "tmp-"):
634 """Create a temporary file.
636 This method may be used by subclasses to create a temporary file
637 for use in building the final image - e.g. a subclass might need
638 a temporary location to unpack a compressed file.
640 The subclass may delete this file if it wishes, but it will be
641 automatically deleted by cleanup().
643 A tuple containing a file descriptor (returned from os.open() and the
644 absolute path to the temporary directory is returned.
646 Note, this method should only be called after mount() has been called.
648 prefix -- a prefix which should be used when creating the file;
652 self.__ensure_builddir()
653 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
655 def _mktemp(self, prefix = "tmp-"):
656 """Create a temporary file.
658 This method simply calls _mkstemp() and closes the returned file
661 The absolute path to the temporary file is returned.
663 Note, this method should only be called after mount() has been called.
665 prefix -- a prefix which should be used when creating the file;
670 (f, path) = self._mkstemp(prefix)
676 # Actual implementation
678 def __ensure_builddir(self):
679 if not self.__builddir is None:
683 self.workdir = os.path.join(self.tmpdir, "build")
684 if not os.path.exists(self.workdir):
685 os.makedirs(self.workdir)
686 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
687 prefix = "imgcreate-")
688 except OSError, (err, msg):
689 raise CreatorError("Failed create build directory in %s: %s" %
692 def get_cachedir(self, cachedir = None):
696 self.__ensure_builddir()
698 self.cachedir = cachedir
700 self.cachedir = self.__builddir + "/mic-cache"
701 fs.makedirs(self.cachedir)
704 def __sanity_check(self):
705 """Ensure that the config we've been given is sane."""
706 if not (kickstart.get_packages(self.ks) or
707 kickstart.get_groups(self.ks)):
708 raise CreatorError("No packages or groups specified")
710 kickstart.convert_method_to_repo(self.ks)
712 if not kickstart.get_repos(self.ks):
713 raise CreatorError("No repositories specified")
715 def __write_fstab(self):
716 if kickstart.use_installerfw(self.ks, "fstab"):
717 # The fstab file will be generated by installer framework scripts
720 fstab_contents = self._get_fstab()
722 fstab = open(self._instroot + "/etc/fstab", "w")
723 fstab.write(fstab_contents)
726 def __create_minimal_dev(self):
727 """Create a minimal /dev so that we don't corrupt the host /dev"""
728 origumask = os.umask(0000)
729 devices = (('null', 1, 3, 0666),
730 ('urandom',1, 9, 0666),
731 ('random', 1, 8, 0666),
732 ('full', 1, 7, 0666),
733 ('ptmx', 5, 2, 0666),
735 ('zero', 1, 5, 0666))
737 links = (("/proc/self/fd", "/dev/fd"),
738 ("/proc/self/fd/0", "/dev/stdin"),
739 ("/proc/self/fd/1", "/dev/stdout"),
740 ("/proc/self/fd/2", "/dev/stderr"))
742 for (node, major, minor, perm) in devices:
743 if not os.path.exists(self._instroot + "/dev/" + node):
744 os.mknod(self._instroot + "/dev/" + node,
746 os.makedev(major,minor))
748 for (src, dest) in links:
749 if not os.path.exists(self._instroot + dest):
750 os.symlink(src, self._instroot + dest)
754 def __setup_tmpdir(self):
755 if not self.enabletmpfs:
758 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
760 def __clean_tmpdir(self):
761 if not self.enabletmpfs:
764 runner.show('umount -l %s' % self.workdir)
766 def mount(self, base_on = None, cachedir = None):
767 """Setup the target filesystem in preparation for an install.
769 This function sets up the filesystem which the ImageCreator will
770 install into and configure. The ImageCreator class merely creates an
771 install root directory, bind mounts some system directories (e.g. /dev)
772 and writes out /etc/fstab. Other subclasses may also e.g. create a
773 sparse file, format it and loopback mount it to the install root.
775 base_on -- a previous install on which to base this install; defaults
776 to None, causing a new image to be created
778 cachedir -- a directory in which to store the Yum cache; defaults to
779 None, causing a new cache to be created; by setting this
780 to another directory, the same cache can be reused across
784 self.__setup_tmpdir()
785 self.__ensure_builddir()
787 # prevent popup dialog in Ubuntu(s)
788 misc.hide_loopdev_presentation()
790 fs.makedirs(self._instroot)
791 fs.makedirs(self._outdir)
793 self._mount_instroot(base_on)
795 for d in ("/dev/pts",
802 fs.makedirs(self._instroot + d)
804 if self.target_arch and self.target_arch.startswith("arm") or \
805 self.target_arch == "aarch64" or self.target_arch == "mipsel" :
806 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
809 self.get_cachedir(cachedir)
811 # bind mount system directories into _instroot
812 for (f, dest) in [("/sys", None),
814 ("/proc/sys/fs/binfmt_misc", None),
816 self.__bindmounts.append(
818 f, self._instroot, dest))
820 self._do_bindmounts()
822 self.__create_minimal_dev()
824 if os.path.exists(self._instroot + "/etc/mtab"):
825 os.unlink(self._instroot + "/etc/mtab")
826 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
830 # get size of available space in 'instroot' fs
831 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
834 """Unmounts the target filesystem.
836 The ImageCreator class detaches the system from the install root, but
837 other subclasses may also detach the loopback mounted filesystem image
838 from the install root.
842 mtab = self._instroot + "/etc/mtab"
843 if not os.path.islink(mtab):
844 os.unlink(self._instroot + "/etc/mtab")
846 if self.qemu_emulator:
847 os.unlink(self._instroot + self.qemu_emulator)
851 self._undo_bindmounts()
853 """ Clean up yum garbage """
855 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
856 if os.path.exists(instroot_pdir):
857 shutil.rmtree(instroot_pdir, ignore_errors = True)
858 yumlibdir = self._instroot + "/var/lib/yum"
859 if os.path.exists(yumlibdir):
860 shutil.rmtree(yumlibdir, ignore_errors = True)
864 self._unmount_instroot()
866 # reset settings of popup dialog in Ubuntu(s)
867 misc.unhide_loopdev_presentation()
871 """Unmounts the target filesystem and deletes temporary files.
873 This method calls unmount() and then deletes any temporary files and
874 directories that were created on the host system while building the
877 Note, make sure to call this method once finished with the creator
878 instance in order to ensure no stale files are left on the host e.g.:
880 creator = ImageCreator(ks, name)
887 if not self.__builddir:
890 kill_proc_inchroot(self._instroot)
894 shutil.rmtree(self.__builddir, ignore_errors = True)
895 self.__builddir = None
897 self.__clean_tmpdir()
899 def __is_excluded_pkg(self, pkg):
900 if pkg in self._excluded_pkgs:
901 self._excluded_pkgs.remove(pkg)
904 for xpkg in self._excluded_pkgs:
905 if xpkg.endswith('*'):
906 if pkg.startswith(xpkg[:-1]):
908 elif xpkg.startswith('*'):
909 if pkg.endswith(xpkg[1:]):
914 def __select_packages(self, pkg_manager):
916 for pkg in self._required_pkgs:
917 e = pkg_manager.selectPackage(pkg)
919 if kickstart.ignore_missing(self.ks):
920 skipped_pkgs.append(pkg)
921 elif self.__is_excluded_pkg(pkg):
922 skipped_pkgs.append(pkg)
924 raise CreatorError("Failed to find package '%s' : %s" %
927 for pkg in skipped_pkgs:
928 msger.warning("Skipping missing package '%s'" % (pkg,))
930 def __select_groups(self, pkg_manager):
932 for group in self._required_groups:
933 e = pkg_manager.selectGroup(group.name, group.include)
935 if kickstart.ignore_missing(self.ks):
936 skipped_groups.append(group)
938 raise CreatorError("Failed to find group '%s' : %s" %
941 for group in skipped_groups:
942 msger.warning("Skipping missing group '%s'" % (group.name,))
944 def __deselect_packages(self, pkg_manager):
945 for pkg in self._excluded_pkgs:
946 pkg_manager.deselectPackage(pkg)
948 def __localinst_packages(self, pkg_manager):
949 for rpm_path in self._get_local_packages():
950 pkg_manager.installLocal(rpm_path)
952 def __preinstall_packages(self, pkg_manager):
956 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
957 for pkg in self._preinstall_pkgs:
958 pkg_manager.preInstall(pkg)
960 def __check_packages(self, pkg_manager):
961 for pkg in self.check_pkgs:
962 pkg_manager.checkPackage(pkg)
964 def __attachment_packages(self, pkg_manager):
968 self._attachment = []
969 for item in kickstart.get_attachment(self.ks):
970 if item.startswith('/'):
971 fpaths = os.path.join(self._instroot, item.lstrip('/'))
972 for fpath in glob.glob(fpaths):
973 self._attachment.append(fpath)
976 filelist = pkg_manager.getFilelist(item)
978 # found rpm in rootfs
979 for pfile in pkg_manager.getFilelist(item):
980 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
981 self._attachment.append(fpath)
984 # try to retrieve rpm file
985 (url, proxies) = pkg_manager.package_url(item)
987 msger.warning("Can't get url from repo for %s" % item)
989 fpath = os.path.join(self.cachedir, os.path.basename(url))
990 if not os.path.exists(fpath):
993 fpath = grabber.myurlgrab(url.full, fpath, proxies, None)
997 tmpdir = self._mkdtemp()
998 misc.extract_rpm(fpath, tmpdir)
999 for (root, dirs, files) in os.walk(tmpdir):
1001 fpath = os.path.join(root, fname)
1002 self._attachment.append(fpath)
1004 def install(self, repo_urls=None):
1005 """Install packages into the install root.
1007 This function installs the packages listed in the supplied kickstart
1008 into the install root. By default, the packages are installed from the
1009 repository URLs specified in the kickstart.
1011 repo_urls -- a dict which maps a repository name to a repository;
1012 if supplied, this causes any repository URLs specified in
1013 the kickstart to be overridden.
1016 def get_ssl_verify(ssl_verify=None):
1017 if ssl_verify is not None:
1018 return not ssl_verify.lower().strip() == 'no'
1020 return not self.ssl_verify.lower().strip() == 'no'
1022 # initialize pkg list to install
1024 self.__sanity_check()
1026 self._required_pkgs = \
1027 kickstart.get_packages(self.ks, self._get_required_packages())
1028 self._excluded_pkgs = \
1029 kickstart.get_excluded(self.ks, self._get_excluded_packages())
1030 self._required_groups = kickstart.get_groups(self.ks)
1032 self._required_pkgs = None
1033 self._excluded_pkgs = None
1034 self._required_groups = None
1037 repo_urls = self.extrarepos
1039 pkg_manager = self.get_pkg_manager()
1042 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1043 if 'debuginfo' in self.install_pkgs:
1044 pkg_manager.install_debuginfo = True
1046 for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1047 (name, baseurl, mirrorlist, inc, exc,
1048 proxy, proxy_username, proxy_password, debuginfo,
1049 source, gpgkey, disable, ssl_verify, nocache,
1050 cost, priority) = repo
1052 ssl_verify = get_ssl_verify(ssl_verify)
1053 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1054 proxy_username, proxy_password, inc, exc, ssl_verify,
1055 nocache, cost, priority)
1057 if kickstart.exclude_docs(self.ks):
1058 rpm.addMacro("_excludedocs", "1")
1059 rpm.addMacro("_dbpath", "/var/lib/rpm")
1060 rpm.addMacro("__file_context_path", "%{nil}")
1061 if kickstart.inst_langs(self.ks) != None:
1062 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1065 self.__preinstall_packages(pkg_manager)
1066 self.__select_packages(pkg_manager)
1067 self.__select_groups(pkg_manager)
1068 self.__deselect_packages(pkg_manager)
1069 self.__localinst_packages(pkg_manager)
1070 self.__check_packages(pkg_manager)
1072 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1073 checksize = self._root_fs_avail
1075 checksize -= BOOT_SAFEGUARD
1076 if self.target_arch:
1077 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1079 # If we have multiple partitions, don't check diskspace when rpm run transaction
1080 # because rpm check '/' partition only.
1081 if self.multiple_partitions:
1082 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_DISKSPACE)
1083 pkg_manager.runInstall(checksize)
1084 except CreatorError, e:
1086 except KeyboardInterrupt:
1089 self._pkgs_content = pkg_manager.getAllContent()
1090 self._pkgs_license = pkg_manager.getPkgsLicense()
1091 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1092 self.__attachment_packages(pkg_manager)
1099 # do some clean up to avoid lvm info leakage. this sucks.
1100 for subdir in ("cache", "backup", "archive"):
1101 lvmdir = self._instroot + "/etc/lvm/" + subdir
1103 for f in os.listdir(lvmdir):
1104 os.unlink(lvmdir + "/" + f)
1108 def postinstall(self):
1109 self.copy_attachment()
1111 def __run_post_scripts(self):
1112 msger.info("Running scripts ...")
1113 if os.path.exists(self._instroot + "/tmp"):
1114 shutil.rmtree(self._instroot + "/tmp")
1115 os.mkdir (self._instroot + "/tmp", 0755)
1116 for s in kickstart.get_post_scripts(self.ks):
1117 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1118 dir = self._instroot + "/tmp")
1120 s.script = s.script.replace("\r", "")
1121 os.write(fd, s.script)
1123 os.chmod(path, 0700)
1125 env = self._get_post_scripts_env(s.inChroot)
1126 if 'PATH' not in env:
1127 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1133 preexec = self._chroot
1134 script = "/tmp/" + os.path.basename(path)
1138 p = subprocess.Popen([s.interp, script],
1139 preexec_fn = preexec,
1141 stdout = subprocess.PIPE,
1142 stderr = subprocess.STDOUT)
1143 for entry in p.communicate()[0].splitlines():
1145 except OSError, (err, msg):
1146 raise CreatorError("Failed to execute %%post script "
1147 "with '%s' : %s" % (s.interp, msg))
1151 def __save_repo_keys(self, repodata):
1155 gpgkeydir = "/etc/pki/rpm-gpg"
1156 fs.makedirs(self._instroot + gpgkeydir)
1157 for repo in repodata:
1159 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1160 shutil.copy(repo["repokey"], self._instroot + repokey)
1162 def configure(self, repodata = None):
1163 """Configure the system image according to the kickstart.
1165 This method applies the (e.g. keyboard or network) configuration
1166 specified in the kickstart and executes the kickstart %post scripts.
1168 If necessary, it also prepares the image to be bootable by e.g.
1169 creating an initrd and bootloader configuration.
1172 ksh = self.ks.handler
1174 msger.info('Applying configurations ...')
1176 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1177 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1178 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1179 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1180 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1181 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1182 kickstart.UserConfig(self._instroot).apply(ksh.user)
1183 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1184 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1185 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1186 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1187 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1188 self.__save_repo_keys(repodata)
1189 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1191 msger.warning("Failed to apply configuration to image")
1194 self._create_bootconfig()
1195 self.__run_post_scripts()
1197 def launch_shell(self, launch):
1198 """Launch a shell in the install root.
1200 This method is launches a bash shell chroot()ed in the install root;
1201 this can be useful for debugging.
1205 msger.info("Launching shell. Exit to continue.")
1206 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1208 def do_genchecksum(self, image_name):
1209 if not self._genchecksum:
1212 md5sum = misc.get_md5sum(image_name)
1213 with open(image_name + ".md5sum", "w") as f:
1214 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1215 self.outimage.append(image_name+".md5sum")
1217 def package(self, destdir = "."):
1218 """Prepares the created image for final delivery.
1220 In its simplest form, this method merely copies the install root to the
1221 supplied destination directory; other subclasses may choose to package
1222 the image by e.g. creating a bootable ISO containing the image and
1223 bootloader configuration.
1225 destdir -- the directory into which the final image should be moved;
1226 this defaults to the current directory.
1229 self._stage_final_image()
1231 if not os.path.exists(destdir):
1232 fs.makedirs(destdir)
1234 if self._recording_pkgs:
1235 self._save_recording_pkgs(destdir)
1237 # For image formats with two or multiple image files, it will be
1238 # better to put them under a directory
1239 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1240 destdir = os.path.join(destdir, "%s-%s" \
1241 % (self.name, self.image_format))
1242 msger.debug("creating destination dir: %s" % destdir)
1243 fs.makedirs(destdir)
1245 # Ensure all data is flushed to _outdir
1246 runner.quiet('sync')
1248 misc.check_space_pre_cp(self._outdir, destdir)
1249 for f in os.listdir(self._outdir):
1250 shutil.move(os.path.join(self._outdir, f),
1251 os.path.join(destdir, f))
1252 self.outimage.append(os.path.join(destdir, f))
1253 self.do_genchecksum(os.path.join(destdir, f))
1255 def print_outimage_info(self):
1256 msg = "The new image can be found here:\n"
1257 self.outimage.sort()
1258 for file in self.outimage:
1259 msg += ' %s\n' % os.path.abspath(file)
1263 def check_depend_tools(self):
1264 for tool in self._dep_checks:
1265 fs.find_binary_path(tool)
1267 def package_output(self, image_format, destdir = ".", package="none"):
1268 if not package or package == "none":
1271 destdir = os.path.abspath(os.path.expanduser(destdir))
1272 (pkg, comp) = os.path.splitext(package)
1274 comp=comp.lstrip(".")
1278 dst = "%s/%s-%s.tar.%s" %\
1279 (destdir, self.name, image_format, comp)
1281 dst = "%s/%s-%s.tar" %\
1282 (destdir, self.name, image_format)
1284 msger.info("creating %s" % dst)
1285 tar = tarfile.open(dst, "w:" + comp)
1287 for file in self.outimage:
1288 msger.info("adding %s to %s" % (file, dst))
1290 arcname=os.path.join("%s-%s" \
1291 % (self.name, image_format),
1292 os.path.basename(file)))
1293 if os.path.isdir(file):
1294 shutil.rmtree(file, ignore_errors = True)
1300 '''All the file in outimage has been packaged into tar.* file'''
1301 self.outimage = [dst]
1303 def release_output(self, config, destdir, release):
1304 """ Create release directory and files
1308 """ release path """
1309 return os.path.join(destdir, fn)
1311 outimages = self.outimage
1314 new_kspath = _rpath(self.name+'.ks')
1315 with open(config) as fr:
1316 with open(new_kspath, "w") as wf:
1317 # When building a release we want to make sure the .ks
1318 # file generates the same build even when --release not used.
1319 wf.write(fr.read().replace("@BUILD_ID@", release))
1320 outimages.append(new_kspath)
1322 # save log file, logfile is only available in creator attrs
1323 if hasattr(self, 'releaselog') and self.releaselog:
1324 outimages.append(self.logfile)
1326 # rename iso and usbimg
1327 for f in os.listdir(destdir):
1328 if f.endswith(".iso"):
1329 newf = f[:-4] + '.img'
1330 elif f.endswith(".usbimg"):
1331 newf = f[:-7] + '.img'
1334 os.rename(_rpath(f), _rpath(newf))
1335 outimages.append(_rpath(newf))
1337 # generate MD5SUMS SHA1SUMS SHA256SUMS
1338 def generate_hashsum(hash_name, hash_method):
1339 with open(_rpath(hash_name), "w") as wf:
1340 for f in os.listdir(destdir):
1341 if f.endswith('SUMS'):
1344 if os.path.isdir(os.path.join(destdir, f)):
1347 hash_value = hash_method(_rpath(f))
1348 # There needs to be two spaces between the sum and
1349 # filepath to match the syntax with md5sum,sha1sum,
1350 # sha256sum. This way also *sum -c *SUMS can be used.
1351 wf.write("%s %s\n" % (hash_value, f))
1353 outimages.append("%s/%s" % (destdir, hash_name))
1356 'MD5SUMS' : misc.get_md5sum,
1357 'SHA1SUMS' : misc.get_sha1sum,
1358 'SHA256SUMS' : misc.get_sha256sum
1361 for k, v in hash_dict.items():
1362 generate_hashsum(k, v)
1364 # Filter out the nonexist file
1365 for fp in outimages[:]:
1366 if not os.path.exists("%s" % fp):
1367 outimages.remove(fp)
1369 def copy_kernel(self):
1370 """ Copy kernel files to the outimage directory.
1371 NOTE: This needs to be called before unmounting the instroot.
1374 if not self._need_copy_kernel:
1377 if not os.path.exists(self.destdir):
1378 os.makedirs(self.destdir)
1380 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1381 kernelfilename = "%s/%s-%s" % (self.destdir,
1383 os.path.basename(kernel))
1384 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1386 shutil.copy(kernel, kernelfilename)
1387 self.outimage.append(kernelfilename)
1389 def copy_attachment(self):
1390 """ Subclass implement it to handle attachment files
1391 NOTE: This needs to be called before unmounting the instroot.
1395 def get_pkg_manager(self):
1396 return self.pkgmgr(target_arch = self.target_arch,
1397 instroot = self._instroot,
1398 cachedir = self.cachedir,
1399 strict_mode = self.strict_mode)
1401 def create_manifest(self):
1402 def get_pack_suffix():
1403 return '.' + self.pack_to.split('.', 1)[1]
1405 if not os.path.exists(self.destdir):
1406 os.makedirs(self.destdir)
1408 now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1409 manifest_dict = {'version': VERSION,
1412 manifest_dict.update({'format': self.img_format})
1414 if hasattr(self, 'logfile') and self.logfile:
1415 manifest_dict.update({'log_file': self.logfile})
1417 if self.image_files:
1419 self.image_files.update({'pack': get_pack_suffix()})
1420 manifest_dict.update({self.img_format: self.image_files})
1422 msger.info('Creating manifest file...')
1423 manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1424 with open(manifest_file_path, 'w') as fest_file:
1425 json.dump(manifest_dict, fest_file, indent=4)
1426 self.outimage.append(manifest_file_path)