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 checkScriptletError(dirname, suffix):
1017 if os.path.exists(dirname):
1018 list = os.listdir(dirname)
1020 filepath = os.path.join(dirname, line)
1021 if os.path.isfile(filepath) and 0 < line.find(suffix):
1028 def showErrorInfo(filepath):
1029 if os.path.isfile(filepath):
1030 msger.info("The error install package info:")
1031 for line in open(filepath):
1034 msger.info("%s is not found." % filepath)
1036 def get_ssl_verify(ssl_verify=None):
1037 if ssl_verify is not None:
1038 return not ssl_verify.lower().strip() == 'no'
1040 return not self.ssl_verify.lower().strip() == 'no'
1042 # initialize pkg list to install
1044 self.__sanity_check()
1046 self._required_pkgs = \
1047 kickstart.get_packages(self.ks, self._get_required_packages())
1048 self._excluded_pkgs = \
1049 kickstart.get_excluded(self.ks, self._get_excluded_packages())
1050 self._required_groups = kickstart.get_groups(self.ks)
1052 self._required_pkgs = None
1053 self._excluded_pkgs = None
1054 self._required_groups = None
1057 repo_urls = self.extrarepos
1059 pkg_manager = self.get_pkg_manager()
1062 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1063 if 'debuginfo' in self.install_pkgs:
1064 pkg_manager.install_debuginfo = True
1066 for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1067 (name, baseurl, mirrorlist, inc, exc,
1068 proxy, proxy_username, proxy_password, debuginfo,
1069 source, gpgkey, disable, ssl_verify, nocache,
1070 cost, priority) = repo
1072 ssl_verify = get_ssl_verify(ssl_verify)
1073 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1074 proxy_username, proxy_password, inc, exc, ssl_verify,
1075 nocache, cost, priority)
1077 if kickstart.exclude_docs(self.ks):
1078 rpm.addMacro("_excludedocs", "1")
1079 rpm.addMacro("_dbpath", "/var/lib/rpm")
1080 rpm.addMacro("__file_context_path", "%{nil}")
1081 if kickstart.inst_langs(self.ks) != None:
1082 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1085 self.__preinstall_packages(pkg_manager)
1086 self.__select_packages(pkg_manager)
1087 self.__select_groups(pkg_manager)
1088 self.__deselect_packages(pkg_manager)
1089 self.__localinst_packages(pkg_manager)
1090 self.__check_packages(pkg_manager)
1092 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1093 checksize = self._root_fs_avail
1095 checksize -= BOOT_SAFEGUARD
1096 if self.target_arch:
1097 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1099 # If we have multiple partitions, don't check diskspace when rpm run transaction
1100 # because rpm check '/' partition only.
1101 if self.multiple_partitions:
1102 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_DISKSPACE)
1103 pkg_manager.runInstall(checksize)
1104 except CreatorError, e:
1106 except KeyboardInterrupt:
1109 self._pkgs_content = pkg_manager.getAllContent()
1110 self._pkgs_license = pkg_manager.getPkgsLicense()
1111 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1112 self.__attachment_packages(pkg_manager)
1116 if checkScriptletError(self._instroot + "/tmp/.postscript/error/", "_error"):
1117 showErrorInfo(self._instroot + "/tmp/.preload_install_error")
1118 raise CreatorError('scriptlet errors occurred')
1123 # do some clean up to avoid lvm info leakage. this sucks.
1124 for subdir in ("cache", "backup", "archive"):
1125 lvmdir = self._instroot + "/etc/lvm/" + subdir
1127 for f in os.listdir(lvmdir):
1128 os.unlink(lvmdir + "/" + f)
1132 def postinstall(self):
1133 self.copy_attachment()
1135 def __run_post_scripts(self):
1136 msger.info("Running scripts ...")
1137 if os.path.exists(self._instroot + "/tmp"):
1138 shutil.rmtree(self._instroot + "/tmp")
1139 os.mkdir (self._instroot + "/tmp", 0755)
1140 for s in kickstart.get_post_scripts(self.ks):
1141 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1142 dir = self._instroot + "/tmp")
1144 s.script = s.script.replace("\r", "")
1145 os.write(fd, s.script)
1147 os.chmod(path, 0700)
1149 env = self._get_post_scripts_env(s.inChroot)
1150 if 'PATH' not in env:
1151 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1157 preexec = self._chroot
1158 script = "/tmp/" + os.path.basename(path)
1162 p = subprocess.Popen([s.interp, script],
1163 preexec_fn = preexec,
1165 stdout = subprocess.PIPE,
1166 stderr = subprocess.STDOUT)
1167 for entry in p.communicate()[0].splitlines():
1169 except OSError, (err, msg):
1170 raise CreatorError("Failed to execute %%post script "
1171 "with '%s' : %s" % (s.interp, msg))
1175 def __save_repo_keys(self, repodata):
1179 gpgkeydir = "/etc/pki/rpm-gpg"
1180 fs.makedirs(self._instroot + gpgkeydir)
1181 for repo in repodata:
1183 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1184 shutil.copy(repo["repokey"], self._instroot + repokey)
1186 def configure(self, repodata = None):
1187 """Configure the system image according to the kickstart.
1189 This method applies the (e.g. keyboard or network) configuration
1190 specified in the kickstart and executes the kickstart %post scripts.
1192 If necessary, it also prepares the image to be bootable by e.g.
1193 creating an initrd and bootloader configuration.
1196 ksh = self.ks.handler
1198 msger.info('Applying configurations ...')
1200 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1201 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1202 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1203 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1204 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1205 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1206 kickstart.UserConfig(self._instroot).apply(ksh.user)
1207 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1208 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1209 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1210 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1211 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1212 self.__save_repo_keys(repodata)
1213 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1215 msger.warning("Failed to apply configuration to image")
1218 self._create_bootconfig()
1219 self.__run_post_scripts()
1221 def launch_shell(self, launch):
1222 """Launch a shell in the install root.
1224 This method is launches a bash shell chroot()ed in the install root;
1225 this can be useful for debugging.
1229 msger.info("Launching shell. Exit to continue.")
1230 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1232 def do_genchecksum(self, image_name):
1233 if not self._genchecksum:
1236 md5sum = misc.get_md5sum(image_name)
1237 with open(image_name + ".md5sum", "w") as f:
1238 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1239 self.outimage.append(image_name+".md5sum")
1241 def remove_exclude_image(self):
1242 for item in self._instloops[:]:
1243 if item['exclude_image']:
1244 msger.info("Removing %s in image." % item['name'])
1245 imgfile = os.path.join(self._imgdir, item['name'])
1248 except OSError as err:
1249 if err.errno == errno.ENOENT:
1251 self._instloops.remove(item)
1253 def package(self, destdir = "."):
1254 """Prepares the created image for final delivery.
1256 In its simplest form, this method merely copies the install root to the
1257 supplied destination directory; other subclasses may choose to package
1258 the image by e.g. creating a bootable ISO containing the image and
1259 bootloader configuration.
1261 destdir -- the directory into which the final image should be moved;
1262 this defaults to the current directory.
1265 self.remove_exclude_image()
1267 self._stage_final_image()
1269 if not os.path.exists(destdir):
1270 fs.makedirs(destdir)
1272 if self._recording_pkgs:
1273 self._save_recording_pkgs(destdir)
1275 # For image formats with two or multiple image files, it will be
1276 # better to put them under a directory
1277 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1278 destdir = os.path.join(destdir, "%s-%s" \
1279 % (self.name, self.image_format))
1280 msger.debug("creating destination dir: %s" % destdir)
1281 fs.makedirs(destdir)
1283 # Ensure all data is flushed to _outdir
1284 runner.quiet('sync')
1286 misc.check_space_pre_cp(self._outdir, destdir)
1287 for f in os.listdir(self._outdir):
1288 shutil.move(os.path.join(self._outdir, f),
1289 os.path.join(destdir, f))
1290 self.outimage.append(os.path.join(destdir, f))
1291 self.do_genchecksum(os.path.join(destdir, f))
1293 def print_outimage_info(self):
1294 msg = "The new image can be found here:\n"
1295 self.outimage.sort()
1296 for file in self.outimage:
1297 msg += ' %s\n' % os.path.abspath(file)
1301 def check_depend_tools(self):
1302 for tool in self._dep_checks:
1303 fs.find_binary_path(tool)
1305 def package_output(self, image_format, destdir = ".", package="none"):
1306 if not package or package == "none":
1309 destdir = os.path.abspath(os.path.expanduser(destdir))
1310 (pkg, comp) = os.path.splitext(package)
1312 comp=comp.lstrip(".")
1316 dst = "%s/%s-%s.tar.%s" %\
1317 (destdir, self.name, image_format, comp)
1319 dst = "%s/%s-%s.tar" %\
1320 (destdir, self.name, image_format)
1322 msger.info("creating %s" % dst)
1323 tar = tarfile.open(dst, "w:" + comp)
1325 for file in self.outimage:
1326 msger.info("adding %s to %s" % (file, dst))
1328 arcname=os.path.join("%s-%s" \
1329 % (self.name, image_format),
1330 os.path.basename(file)))
1331 if os.path.isdir(file):
1332 shutil.rmtree(file, ignore_errors = True)
1338 '''All the file in outimage has been packaged into tar.* file'''
1339 self.outimage = [dst]
1341 def release_output(self, config, destdir, release):
1342 """ Create release directory and files
1346 """ release path """
1347 return os.path.join(destdir, fn)
1349 outimages = self.outimage
1352 new_kspath = _rpath(self.name+'.ks')
1353 with open(config) as fr:
1354 with open(new_kspath, "w") as wf:
1355 # When building a release we want to make sure the .ks
1356 # file generates the same build even when --release not used.
1357 wf.write(fr.read().replace("@BUILD_ID@", release))
1358 outimages.append(new_kspath)
1360 # save log file, logfile is only available in creator attrs
1361 if hasattr(self, 'releaselog') and self.releaselog:
1362 outimages.append(self.logfile)
1364 # rename iso and usbimg
1365 for f in os.listdir(destdir):
1366 if f.endswith(".iso"):
1367 newf = f[:-4] + '.img'
1368 elif f.endswith(".usbimg"):
1369 newf = f[:-7] + '.img'
1372 os.rename(_rpath(f), _rpath(newf))
1373 outimages.append(_rpath(newf))
1375 # generate MD5SUMS SHA1SUMS SHA256SUMS
1376 def generate_hashsum(hash_name, hash_method):
1377 with open(_rpath(hash_name), "w") as wf:
1378 for f in os.listdir(destdir):
1379 if f.endswith('SUMS'):
1382 if os.path.isdir(os.path.join(destdir, f)):
1385 hash_value = hash_method(_rpath(f))
1386 # There needs to be two spaces between the sum and
1387 # filepath to match the syntax with md5sum,sha1sum,
1388 # sha256sum. This way also *sum -c *SUMS can be used.
1389 wf.write("%s %s\n" % (hash_value, f))
1391 outimages.append("%s/%s" % (destdir, hash_name))
1394 'MD5SUMS' : misc.get_md5sum,
1395 'SHA1SUMS' : misc.get_sha1sum,
1396 'SHA256SUMS' : misc.get_sha256sum
1399 for k, v in hash_dict.items():
1400 generate_hashsum(k, v)
1402 # Filter out the nonexist file
1403 for fp in outimages[:]:
1404 if not os.path.exists("%s" % fp):
1405 outimages.remove(fp)
1407 def copy_kernel(self):
1408 """ Copy kernel files to the outimage directory.
1409 NOTE: This needs to be called before unmounting the instroot.
1412 if not self._need_copy_kernel:
1415 if not os.path.exists(self.destdir):
1416 os.makedirs(self.destdir)
1418 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1419 kernelfilename = "%s/%s-%s" % (self.destdir,
1421 os.path.basename(kernel))
1422 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1424 shutil.copy(kernel, kernelfilename)
1425 self.outimage.append(kernelfilename)
1427 def copy_attachment(self):
1428 """ Subclass implement it to handle attachment files
1429 NOTE: This needs to be called before unmounting the instroot.
1433 def get_pkg_manager(self):
1434 return self.pkgmgr(target_arch = self.target_arch,
1435 instroot = self._instroot,
1436 cachedir = self.cachedir,
1437 strict_mode = self.strict_mode)
1439 def create_manifest(self):
1440 def get_pack_suffix():
1441 return '.' + self.pack_to.split('.', 1)[1]
1443 if not os.path.exists(self.destdir):
1444 os.makedirs(self.destdir)
1446 now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1447 manifest_dict = {'version': VERSION,
1450 manifest_dict.update({'format': self.img_format})
1452 if hasattr(self, 'logfile') and self.logfile:
1453 manifest_dict.update({'log_file': self.logfile})
1455 if self.image_files:
1457 self.image_files.update({'pack': get_pack_suffix()})
1458 manifest_dict.update({self.img_format: self.image_files})
1460 msger.info('Creating manifest file...')
1461 manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1462 with open(manifest_file_path, 'w') as fest_file:
1463 json.dump(manifest_dict, fest_file, indent=4)
1464 self.outimage.append(manifest_file_path)