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
40 class BaseImageCreator(object):
41 """Installs a system to a chroot directory.
43 ImageCreator is the simplest creator class available; it will install and
44 configure a system image according to the supplied kickstart file.
48 import mic.imgcreate as imgcreate
49 ks = imgcreate.read_kickstart("foo.ks")
50 imgcreate.ImageCreator(ks, "foo").create()
59 def __init__(self, createopts = None, pkgmgr = None):
60 """Initialize an ImageCreator instance.
62 ks -- a pykickstart.KickstartParser instance; this instance will be
63 used to drive the install by e.g. providing the list of packages
64 to be installed, the system configuration and %post scripts
66 name -- a name for the image; used for e.g. image filenames or
73 self.__builddir = None
74 self.__bindmounts = []
78 self.tmpdir = "/var/tmp/mic"
79 self.cachedir = "/var/tmp/mic/cache"
80 self.workdir = "/var/tmp/mic/build"
82 self.installerfw_prefix = "INSTALLERFW_"
83 self.target_arch = "noarch"
84 self._local_pkgs_path = None
88 # If the kernel is save to the destdir when copy_kernel cmd is called.
89 self._need_copy_kernel = False
90 # setup tmpfs tmpdir when enabletmpfs is True
91 self.enabletmpfs = False
94 # Mapping table for variables that have different names.
95 optmap = {"pkgmgr" : "pkgmgr_name",
96 "arch" : "target_arch",
97 "local_pkgs_path" : "_local_pkgs_path",
98 "copy_kernel" : "_need_copy_kernel",
101 # update setting from createopts
102 for key in createopts.keys():
107 setattr(self, option, createopts[key])
109 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
112 if '@NAME@' in self.pack_to:
113 self.pack_to = self.pack_to.replace('@NAME@', self.name)
114 (tar, ext) = os.path.splitext(self.pack_to)
115 if ext in (".gz", ".bz2") and tar.endswith(".tar"):
117 if ext not in misc.pack_formats:
118 self.pack_to += ".tar"
120 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
122 # Output image file names
124 # Output info related with manifest
125 self.image_files = {}
126 # A flag to generate checksum
127 self._genchecksum = False
129 self._alt_initrd_name = None
131 self._recording_pkgs = []
133 # available size in root fs, init to 0
134 self._root_fs_avail = 0
136 # Name of the disk image file that is created.
137 self._img_name = None
139 self.image_format = None
141 # Save qemu emulator file name in order to clean up it finally
142 self.qemu_emulator = None
144 # No ks provided when called by convertor, so skip the dependency check
146 # If we have btrfs partition we need to check necessary tools
147 for part in self.ks.handler.partition.partitions:
148 if part.fstype and part.fstype == "btrfs":
149 self._dep_checks.append("mkfs.btrfs")
152 if self.target_arch and self.target_arch.startswith("arm"):
153 for dep in self._dep_checks:
154 if dep == "extlinux":
155 self._dep_checks.remove(dep)
157 if not os.path.exists("/usr/bin/qemu-arm") or \
158 not misc.is_statically_linked("/usr/bin/qemu-arm"):
159 self._dep_checks.append("qemu-arm-static")
161 if os.path.exists("/proc/sys/vm/vdso_enabled"):
162 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
163 vdso_value = vdso_fh.read().strip()
165 if (int)(vdso_value) == 1:
166 msger.warning("vdso is enabled on your host, which might "
167 "cause problems with arm emulations.\n"
168 "\tYou can disable vdso with following command before "
169 "starting image build:\n"
170 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
172 # make sure the specified tmpdir and cachedir exist
173 if not os.path.exists(self.tmpdir):
174 os.makedirs(self.tmpdir)
175 if not os.path.exists(self.cachedir):
176 os.makedirs(self.cachedir)
182 def __get_instroot(self):
183 if self.__builddir is None:
184 raise CreatorError("_instroot is not valid before calling mount()")
185 return self.__builddir + "/install_root"
186 _instroot = property(__get_instroot)
187 """The location of the install root directory.
189 This is the directory into which the system is installed. Subclasses may
190 mount a filesystem image here or copy files to/from here.
192 Note, this directory does not exist before ImageCreator.mount() is called.
194 Note also, this is a read-only attribute.
198 def __get_outdir(self):
199 if self.__builddir is None:
200 raise CreatorError("_outdir is not valid before calling mount()")
201 return self.__builddir + "/out"
202 _outdir = property(__get_outdir)
203 """The staging location for the final image.
205 This is where subclasses should stage any files that are part of the final
206 image. ImageCreator.package() will copy any files found here into the
207 requested destination directory.
209 Note, this directory does not exist before ImageCreator.mount() is called.
211 Note also, this is a read-only attribute.
217 # Hooks for subclasses
219 def _mount_instroot(self, base_on = None):
220 """Mount or prepare the install root directory.
222 This is the hook where subclasses may prepare the install root by e.g.
223 mounting creating and loopback mounting a filesystem image to
226 There is no default implementation.
228 base_on -- this is the value passed to mount() and can be interpreted
229 as the subclass wishes; it might e.g. be the location of
230 a previously created ISO containing a system image.
235 def _unmount_instroot(self):
236 """Undo anything performed in _mount_instroot().
238 This is the hook where subclasses must undo anything which was done
239 in _mount_instroot(). For example, if a filesystem image was mounted
240 onto _instroot, it should be unmounted here.
242 There is no default implementation.
247 def _create_bootconfig(self):
248 """Configure the image so that it's bootable.
250 This is the hook where subclasses may prepare the image for booting by
251 e.g. creating an initramfs and bootloader configuration.
253 This hook is called while the install root is still mounted, after the
254 packages have been installed and the kickstart configuration has been
255 applied, but before the %post scripts have been executed.
257 There is no default implementation.
262 def _stage_final_image(self):
263 """Stage the final system image in _outdir.
265 This is the hook where subclasses should place the image in _outdir
266 so that package() can copy it to the requested destination directory.
268 By default, this moves the install root into _outdir.
271 shutil.move(self._instroot, self._outdir + "/" + self.name)
273 def get_installed_packages(self):
274 return self._pkgs_content.keys()
276 def _save_recording_pkgs(self, destdir):
277 """Save the list or content of installed packages to file.
279 pkgs = self._pkgs_content.keys()
280 pkgs.sort() # inplace op
282 if not os.path.exists(destdir):
286 if 'vcs' in self._recording_pkgs:
287 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
288 content = '\n'.join(sorted(vcslst))
289 elif 'name' in self._recording_pkgs:
290 content = '\n'.join(pkgs)
292 namefile = os.path.join(destdir, self.name + '.packages')
293 f = open(namefile, "w")
296 self.outimage.append(namefile);
298 # if 'content', save more details
299 if 'content' in self._recording_pkgs:
300 contfile = os.path.join(destdir, self.name + '.files')
301 f = open(contfile, "w")
306 pkgcont = self._pkgs_content[pkg]
308 content += '\n '.join(pkgcont)
314 self.outimage.append(contfile)
316 if 'license' in self._recording_pkgs:
317 licensefile = os.path.join(destdir, self.name + '.license')
318 f = open(licensefile, "w")
320 f.write('Summary:\n')
321 for license in reversed(sorted(self._pkgs_license, key=\
322 lambda license: len(self._pkgs_license[license]))):
323 f.write(" - %s: %s\n" \
324 % (license, len(self._pkgs_license[license])))
326 f.write('\nDetails:\n')
327 for license in reversed(sorted(self._pkgs_license, key=\
328 lambda license: len(self._pkgs_license[license]))):
329 f.write(" - %s:\n" % (license))
330 for pkg in sorted(self._pkgs_license[license]):
331 f.write(" - %s\n" % (pkg))
335 self.outimage.append(licensefile)
337 def _get_required_packages(self):
338 """Return a list of required packages.
340 This is the hook where subclasses may specify a set of packages which
341 it requires to be installed.
343 This returns an empty list by default.
345 Note, subclasses should usually chain up to the base class
346 implementation of this hook.
351 def _get_excluded_packages(self):
352 """Return a list of excluded packages.
354 This is the hook where subclasses may specify a set of packages which
355 it requires _not_ to be installed.
357 This returns an empty list by default.
359 Note, subclasses should usually chain up to the base class
360 implementation of this hook.
365 def _get_local_packages(self):
366 """Return a list of rpm path to be local installed.
368 This is the hook where subclasses may specify a set of rpms which
369 it requires to be installed locally.
371 This returns an empty list by default.
373 Note, subclasses should usually chain up to the base class
374 implementation of this hook.
377 if self._local_pkgs_path:
378 if os.path.isdir(self._local_pkgs_path):
380 os.path.join(self._local_pkgs_path, '*.rpm'))
381 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
382 return [self._local_pkgs_path]
386 def _get_fstab(self):
387 """Return the desired contents of /etc/fstab.
389 This is the hook where subclasses may specify the contents of
390 /etc/fstab by returning a string containing the desired contents.
392 A sensible default implementation is provided.
395 s = "/dev/root / %s %s 0 0\n" \
397 "defaults,noatime" if not self._fsopts else self._fsopts)
398 s += self._get_fstab_special()
401 def _get_fstab_special(self):
402 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
403 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
404 s += "proc /proc proc defaults 0 0\n"
405 s += "sysfs /sys sysfs defaults 0 0\n"
408 def _set_part_env(self, pnum, prop, value):
409 """ This is a helper function which generates an environment variable
410 for a property "prop" with value "value" of a partition number "pnum".
412 The naming convention is:
413 * Variables start with INSTALLERFW_PART
414 * Then goes the partition number, the order is the same as
415 specified in the KS file
416 * Then goes the property name
424 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
425 return { name : value }
427 def _get_post_scripts_env(self, in_chroot):
428 """Return an environment dict for %post scripts.
430 This is the hook where subclasses may specify some environment
431 variables for %post scripts by return a dict containing the desired
434 in_chroot -- whether this %post script is to be executed chroot()ed
441 for p in kickstart.get_partitions(self.ks):
442 env.update(self._set_part_env(pnum, "SIZE", p.size))
443 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
444 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
445 env.update(self._set_part_env(pnum, "LABEL", p.label))
446 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
447 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
448 env.update(self._set_part_env(pnum, "ALIGN", p.align))
449 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
450 env.update(self._set_part_env(pnum, "UUID", p.uuid))
451 env.update(self._set_part_env(pnum, "DEVNODE",
452 "/dev/%s%d" % (p.disk, pnum + 1)))
453 env.update(self._set_part_env(pnum, "DISK_DEVNODE",
458 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
460 # Partition table format
461 ptable_format = self.ks.handler.bootloader.ptable
462 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
464 # The kerned boot parameters
465 kernel_opts = self.ks.handler.bootloader.appendLine
466 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
468 # Name of the image creation tool
469 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
471 # The real current location of the mounted file-systems
475 mount_prefix = self._instroot
476 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
478 # These are historical variables which lack the common name prefix
480 env["INSTALL_ROOT"] = self._instroot
481 env["IMG_NAME"] = self._name
485 def __get_imgname(self):
487 _name = property(__get_imgname)
488 """The name of the image file.
492 def _get_kernel_versions(self):
493 """Return a dict detailing the available kernel types/versions.
495 This is the hook where subclasses may override what kernel types and
496 versions should be available for e.g. creating the booloader
499 A dict should be returned mapping the available kernel types to a list
500 of the available versions for those kernels.
502 The default implementation uses rpm to iterate over everything
503 providing 'kernel', finds /boot/vmlinuz-* and returns the version
504 obtained from the vmlinuz filename. (This can differ from the kernel
505 RPM's n-v-r in the case of e.g. xen)
508 def get_kernel_versions(instroot):
511 files = glob.glob(instroot + "/boot/vmlinuz-*")
513 version = os.path.basename(file)[8:]
516 versions.add(version)
517 ret["kernel"] = list(versions)
520 def get_version(header):
522 for f in header['filenames']:
523 if f.startswith('/boot/vmlinuz-'):
528 return get_kernel_versions(self._instroot)
530 ts = rpm.TransactionSet(self._instroot)
533 for header in ts.dbMatch('provides', 'kernel'):
534 version = get_version(header)
538 name = header['name']
540 ret[name] = [version]
541 elif not version in ret[name]:
542 ret[name].append(version)
548 # Helpers for subclasses
550 def _do_bindmounts(self):
551 """Mount various system directories onto _instroot.
553 This method is called by mount(), but may also be used by subclasses
554 in order to re-mount the bindmounts after modifying the underlying
558 for b in self.__bindmounts:
561 def _undo_bindmounts(self):
562 """Unmount the bind-mounted system directories from _instroot.
564 This method is usually only called by unmount(), but may also be used
565 by subclasses in order to gain access to the filesystem obscured by
566 the bindmounts - e.g. in order to create device nodes on the image
570 self.__bindmounts.reverse()
571 for b in self.__bindmounts:
575 """Chroot into the install root.
577 This method may be used by subclasses when executing programs inside
578 the install root e.g.
580 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
583 os.chroot(self._instroot)
586 def _mkdtemp(self, prefix = "tmp-"):
587 """Create a temporary directory.
589 This method may be used by subclasses to create a temporary directory
590 for use in building the final image - e.g. a subclass might create
591 a temporary directory in order to bundle a set of files into a package.
593 The subclass may delete this directory if it wishes, but it will be
594 automatically deleted by cleanup().
596 The absolute path to the temporary directory is returned.
598 Note, this method should only be called after mount() has been called.
600 prefix -- a prefix which should be used when creating the directory;
604 self.__ensure_builddir()
605 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
607 def _mkstemp(self, prefix = "tmp-"):
608 """Create a temporary file.
610 This method may be used by subclasses to create a temporary file
611 for use in building the final image - e.g. a subclass might need
612 a temporary location to unpack a compressed file.
614 The subclass may delete this file if it wishes, but it will be
615 automatically deleted by cleanup().
617 A tuple containing a file descriptor (returned from os.open() and the
618 absolute path to the temporary directory is returned.
620 Note, this method should only be called after mount() has been called.
622 prefix -- a prefix which should be used when creating the file;
626 self.__ensure_builddir()
627 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
629 def _mktemp(self, prefix = "tmp-"):
630 """Create a temporary file.
632 This method simply calls _mkstemp() and closes the returned file
635 The absolute path to the temporary file is returned.
637 Note, this method should only be called after mount() has been called.
639 prefix -- a prefix which should be used when creating the file;
644 (f, path) = self._mkstemp(prefix)
650 # Actual implementation
652 def __ensure_builddir(self):
653 if not self.__builddir is None:
657 self.workdir = os.path.join(self.tmpdir, "build")
658 if not os.path.exists(self.workdir):
659 os.makedirs(self.workdir)
660 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
661 prefix = "imgcreate-")
662 except OSError, (err, msg):
663 raise CreatorError("Failed create build directory in %s: %s" %
666 def get_cachedir(self, cachedir = None):
670 self.__ensure_builddir()
672 self.cachedir = cachedir
674 self.cachedir = self.__builddir + "/mic-cache"
675 fs.makedirs(self.cachedir)
678 def __sanity_check(self):
679 """Ensure that the config we've been given is sane."""
680 if not (kickstart.get_packages(self.ks) or
681 kickstart.get_groups(self.ks)):
682 raise CreatorError("No packages or groups specified")
684 kickstart.convert_method_to_repo(self.ks)
686 if not kickstart.get_repos(self.ks):
687 raise CreatorError("No repositories specified")
689 def __write_fstab(self):
690 if kickstart.use_installerfw(self.ks, "fstab"):
691 # The fstab file will be generated by installer framework scripts
694 fstab_contents = self._get_fstab()
696 fstab = open(self._instroot + "/etc/fstab", "w")
697 fstab.write(fstab_contents)
700 def __create_minimal_dev(self):
701 """Create a minimal /dev so that we don't corrupt the host /dev"""
702 origumask = os.umask(0000)
703 devices = (('null', 1, 3, 0666),
704 ('urandom',1, 9, 0666),
705 ('random', 1, 8, 0666),
706 ('full', 1, 7, 0666),
707 ('ptmx', 5, 2, 0666),
709 ('zero', 1, 5, 0666))
711 links = (("/proc/self/fd", "/dev/fd"),
712 ("/proc/self/fd/0", "/dev/stdin"),
713 ("/proc/self/fd/1", "/dev/stdout"),
714 ("/proc/self/fd/2", "/dev/stderr"))
716 for (node, major, minor, perm) in devices:
717 if not os.path.exists(self._instroot + "/dev/" + node):
718 os.mknod(self._instroot + "/dev/" + node,
720 os.makedev(major,minor))
722 for (src, dest) in links:
723 if not os.path.exists(self._instroot + dest):
724 os.symlink(src, self._instroot + dest)
728 def __setup_tmpdir(self):
729 if not self.enabletmpfs:
732 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
734 def __clean_tmpdir(self):
735 if not self.enabletmpfs:
738 runner.show('umount -l %s' % self.workdir)
740 def mount(self, base_on = None, cachedir = None):
741 """Setup the target filesystem in preparation for an install.
743 This function sets up the filesystem which the ImageCreator will
744 install into and configure. The ImageCreator class merely creates an
745 install root directory, bind mounts some system directories (e.g. /dev)
746 and writes out /etc/fstab. Other subclasses may also e.g. create a
747 sparse file, format it and loopback mount it to the install root.
749 base_on -- a previous install on which to base this install; defaults
750 to None, causing a new image to be created
752 cachedir -- a directory in which to store the Yum cache; defaults to
753 None, causing a new cache to be created; by setting this
754 to another directory, the same cache can be reused across
758 self.__setup_tmpdir()
759 self.__ensure_builddir()
761 # prevent popup dialog in Ubuntu(s)
762 misc.hide_loopdev_presentation()
764 fs.makedirs(self._instroot)
765 fs.makedirs(self._outdir)
767 self._mount_instroot(base_on)
769 for d in ("/dev/pts",
776 fs.makedirs(self._instroot + d)
778 if self.target_arch and self.target_arch.startswith("arm") or \
779 self.target_arch == "aarch64":
780 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
783 self.get_cachedir(cachedir)
785 # bind mount system directories into _instroot
786 for (f, dest) in [("/sys", None),
788 ("/proc/sys/fs/binfmt_misc", None),
790 self.__bindmounts.append(
792 f, self._instroot, dest))
794 self._do_bindmounts()
796 self.__create_minimal_dev()
798 if os.path.exists(self._instroot + "/etc/mtab"):
799 os.unlink(self._instroot + "/etc/mtab")
800 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
804 # get size of available space in 'instroot' fs
805 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
808 """Unmounts the target filesystem.
810 The ImageCreator class detaches the system from the install root, but
811 other subclasses may also detach the loopback mounted filesystem image
812 from the install root.
816 mtab = self._instroot + "/etc/mtab"
817 if not os.path.islink(mtab):
818 os.unlink(self._instroot + "/etc/mtab")
820 if self.qemu_emulator:
821 os.unlink(self._instroot + self.qemu_emulator)
825 self._undo_bindmounts()
827 """ Clean up yum garbage """
829 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
830 if os.path.exists(instroot_pdir):
831 shutil.rmtree(instroot_pdir, ignore_errors = True)
832 yumlibdir = self._instroot + "/var/lib/yum"
833 if os.path.exists(yumlibdir):
834 shutil.rmtree(yumlibdir, ignore_errors = True)
838 self._unmount_instroot()
840 # reset settings of popup dialog in Ubuntu(s)
841 misc.unhide_loopdev_presentation()
845 """Unmounts the target filesystem and deletes temporary files.
847 This method calls unmount() and then deletes any temporary files and
848 directories that were created on the host system while building the
851 Note, make sure to call this method once finished with the creator
852 instance in order to ensure no stale files are left on the host e.g.:
854 creator = ImageCreator(ks, name)
861 if not self.__builddir:
864 kill_proc_inchroot(self._instroot)
868 shutil.rmtree(self.__builddir, ignore_errors = True)
869 self.__builddir = None
871 self.__clean_tmpdir()
873 def __is_excluded_pkg(self, pkg):
874 if pkg in self._excluded_pkgs:
875 self._excluded_pkgs.remove(pkg)
878 for xpkg in self._excluded_pkgs:
879 if xpkg.endswith('*'):
880 if pkg.startswith(xpkg[:-1]):
882 elif xpkg.startswith('*'):
883 if pkg.endswith(xpkg[1:]):
888 def __select_packages(self, pkg_manager):
890 for pkg in self._required_pkgs:
891 e = pkg_manager.selectPackage(pkg)
893 if kickstart.ignore_missing(self.ks):
894 skipped_pkgs.append(pkg)
895 elif self.__is_excluded_pkg(pkg):
896 skipped_pkgs.append(pkg)
898 raise CreatorError("Failed to find package '%s' : %s" %
901 for pkg in skipped_pkgs:
902 msger.warning("Skipping missing package '%s'" % (pkg,))
904 def __select_groups(self, pkg_manager):
906 for group in self._required_groups:
907 e = pkg_manager.selectGroup(group.name, group.include)
909 if kickstart.ignore_missing(self.ks):
910 skipped_groups.append(group)
912 raise CreatorError("Failed to find group '%s' : %s" %
915 for group in skipped_groups:
916 msger.warning("Skipping missing group '%s'" % (group.name,))
918 def __deselect_packages(self, pkg_manager):
919 for pkg in self._excluded_pkgs:
920 pkg_manager.deselectPackage(pkg)
922 def __localinst_packages(self, pkg_manager):
923 for rpm_path in self._get_local_packages():
924 pkg_manager.installLocal(rpm_path)
926 def __preinstall_packages(self, pkg_manager):
930 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
931 for pkg in self._preinstall_pkgs:
932 pkg_manager.preInstall(pkg)
934 def __check_packages(self, pkg_manager):
935 for pkg in self.check_pkgs:
936 pkg_manager.checkPackage(pkg)
938 def __attachment_packages(self, pkg_manager):
942 self._attachment = []
943 for item in kickstart.get_attachment(self.ks):
944 if item.startswith('/'):
945 fpaths = os.path.join(self._instroot, item.lstrip('/'))
946 for fpath in glob.glob(fpaths):
947 self._attachment.append(fpath)
950 filelist = pkg_manager.getFilelist(item)
952 # found rpm in rootfs
953 for pfile in pkg_manager.getFilelist(item):
954 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
955 self._attachment.append(fpath)
958 # try to retrieve rpm file
959 (url, proxies) = pkg_manager.package_url(item)
961 msger.warning("Can't get url from repo for %s" % item)
963 fpath = os.path.join(self.cachedir, os.path.basename(url))
964 if not os.path.exists(fpath):
967 fpath = grabber.myurlgrab(url.full, fpath, proxies, None)
971 tmpdir = self._mkdtemp()
972 misc.extract_rpm(fpath, tmpdir)
973 for (root, dirs, files) in os.walk(tmpdir):
975 fpath = os.path.join(root, fname)
976 self._attachment.append(fpath)
978 def install(self, repo_urls=None):
979 """Install packages into the install root.
981 This function installs the packages listed in the supplied kickstart
982 into the install root. By default, the packages are installed from the
983 repository URLs specified in the kickstart.
985 repo_urls -- a dict which maps a repository name to a repository;
986 if supplied, this causes any repository URLs specified in
987 the kickstart to be overridden.
990 def get_ssl_verify(ssl_verify=None):
991 if ssl_verify is not None:
992 return not ssl_verify.lower().strip() == 'no'
994 return not self.ssl_verify.lower().strip() == 'no'
996 # initialize pkg list to install
998 self.__sanity_check()
1000 self._required_pkgs = \
1001 kickstart.get_packages(self.ks, self._get_required_packages())
1002 self._excluded_pkgs = \
1003 kickstart.get_excluded(self.ks, self._get_excluded_packages())
1004 self._required_groups = kickstart.get_groups(self.ks)
1006 self._required_pkgs = None
1007 self._excluded_pkgs = None
1008 self._required_groups = None
1011 repo_urls = self.extrarepos
1013 pkg_manager = self.get_pkg_manager()
1016 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1017 if 'debuginfo' in self.install_pkgs:
1018 pkg_manager.install_debuginfo = True
1020 for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1021 (name, baseurl, mirrorlist, inc, exc,
1022 proxy, proxy_username, proxy_password, debuginfo,
1023 source, gpgkey, disable, ssl_verify, nocache,
1024 cost, priority) = repo
1026 ssl_verify = get_ssl_verify(ssl_verify)
1027 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1028 proxy_username, proxy_password, inc, exc, ssl_verify,
1029 nocache, cost, priority)
1031 if kickstart.exclude_docs(self.ks):
1032 rpm.addMacro("_excludedocs", "1")
1033 rpm.addMacro("_dbpath", "/var/lib/rpm")
1034 rpm.addMacro("__file_context_path", "%{nil}")
1035 if kickstart.inst_langs(self.ks) != None:
1036 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1039 self.__preinstall_packages(pkg_manager)
1040 self.__select_packages(pkg_manager)
1041 self.__select_groups(pkg_manager)
1042 self.__deselect_packages(pkg_manager)
1043 self.__localinst_packages(pkg_manager)
1044 self.__check_packages(pkg_manager)
1046 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1047 checksize = self._root_fs_avail
1049 checksize -= BOOT_SAFEGUARD
1050 if self.target_arch:
1051 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1052 pkg_manager.runInstall(checksize)
1053 except CreatorError, e:
1055 except KeyboardInterrupt:
1058 self._pkgs_content = pkg_manager.getAllContent()
1059 self._pkgs_license = pkg_manager.getPkgsLicense()
1060 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1061 self.__attachment_packages(pkg_manager)
1068 # do some clean up to avoid lvm info leakage. this sucks.
1069 for subdir in ("cache", "backup", "archive"):
1070 lvmdir = self._instroot + "/etc/lvm/" + subdir
1072 for f in os.listdir(lvmdir):
1073 os.unlink(lvmdir + "/" + f)
1077 def postinstall(self):
1078 self.copy_attachment()
1080 def __run_post_scripts(self):
1081 msger.info("Running scripts ...")
1082 if os.path.exists(self._instroot + "/tmp"):
1083 shutil.rmtree(self._instroot + "/tmp")
1084 os.mkdir (self._instroot + "/tmp", 0755)
1085 for s in kickstart.get_post_scripts(self.ks):
1086 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1087 dir = self._instroot + "/tmp")
1089 s.script = s.script.replace("\r", "")
1090 os.write(fd, s.script)
1092 os.chmod(path, 0700)
1094 env = self._get_post_scripts_env(s.inChroot)
1095 if 'PATH' not in env:
1096 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1102 preexec = self._chroot
1103 script = "/tmp/" + os.path.basename(path)
1107 p = subprocess.Popen([s.interp, script],
1108 preexec_fn = preexec,
1110 stdout = subprocess.PIPE,
1111 stderr = subprocess.STDOUT)
1112 for entry in p.communicate()[0].splitlines():
1114 except OSError, (err, msg):
1115 raise CreatorError("Failed to execute %%post script "
1116 "with '%s' : %s" % (s.interp, msg))
1120 def __save_repo_keys(self, repodata):
1124 gpgkeydir = "/etc/pki/rpm-gpg"
1125 fs.makedirs(self._instroot + gpgkeydir)
1126 for repo in repodata:
1128 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1129 shutil.copy(repo["repokey"], self._instroot + repokey)
1131 def configure(self, repodata = None):
1132 """Configure the system image according to the kickstart.
1134 This method applies the (e.g. keyboard or network) configuration
1135 specified in the kickstart and executes the kickstart %post scripts.
1137 If necessary, it also prepares the image to be bootable by e.g.
1138 creating an initrd and bootloader configuration.
1141 ksh = self.ks.handler
1143 msger.info('Applying configurations ...')
1145 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1146 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1147 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1148 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1149 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1150 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1151 kickstart.UserConfig(self._instroot).apply(ksh.user)
1152 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1153 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1154 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1155 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1156 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1157 self.__save_repo_keys(repodata)
1158 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1160 msger.warning("Failed to apply configuration to image")
1163 self._create_bootconfig()
1164 self.__run_post_scripts()
1166 def launch_shell(self, launch):
1167 """Launch a shell in the install root.
1169 This method is launches a bash shell chroot()ed in the install root;
1170 this can be useful for debugging.
1174 msger.info("Launching shell. Exit to continue.")
1175 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1177 def do_genchecksum(self, image_name):
1178 if not self._genchecksum:
1181 md5sum = misc.get_md5sum(image_name)
1182 with open(image_name + ".md5sum", "w") as f:
1183 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1184 self.outimage.append(image_name+".md5sum")
1186 def package(self, destdir = "."):
1187 """Prepares the created image for final delivery.
1189 In its simplest form, this method merely copies the install root to the
1190 supplied destination directory; other subclasses may choose to package
1191 the image by e.g. creating a bootable ISO containing the image and
1192 bootloader configuration.
1194 destdir -- the directory into which the final image should be moved;
1195 this defaults to the current directory.
1198 self._stage_final_image()
1200 if not os.path.exists(destdir):
1201 fs.makedirs(destdir)
1203 if self._recording_pkgs:
1204 self._save_recording_pkgs(destdir)
1206 # For image formats with two or multiple image files, it will be
1207 # better to put them under a directory
1208 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1209 destdir = os.path.join(destdir, "%s-%s" \
1210 % (self.name, self.image_format))
1211 msger.debug("creating destination dir: %s" % destdir)
1212 fs.makedirs(destdir)
1214 # Ensure all data is flushed to _outdir
1215 runner.quiet('sync')
1217 misc.check_space_pre_cp(self._outdir, destdir)
1218 for f in os.listdir(self._outdir):
1219 shutil.move(os.path.join(self._outdir, f),
1220 os.path.join(destdir, f))
1221 self.outimage.append(os.path.join(destdir, f))
1222 self.do_genchecksum(os.path.join(destdir, f))
1224 def print_outimage_info(self):
1225 msg = "The new image can be found here:\n"
1226 self.outimage.sort()
1227 for file in self.outimage:
1228 msg += ' %s\n' % os.path.abspath(file)
1232 def check_depend_tools(self):
1233 for tool in self._dep_checks:
1234 fs.find_binary_path(tool)
1236 def package_output(self, image_format, destdir = ".", package="none"):
1237 if not package or package == "none":
1240 destdir = os.path.abspath(os.path.expanduser(destdir))
1241 (pkg, comp) = os.path.splitext(package)
1243 comp=comp.lstrip(".")
1247 dst = "%s/%s-%s.tar.%s" %\
1248 (destdir, self.name, image_format, comp)
1250 dst = "%s/%s-%s.tar" %\
1251 (destdir, self.name, image_format)
1253 msger.info("creating %s" % dst)
1254 tar = tarfile.open(dst, "w:" + comp)
1256 for file in self.outimage:
1257 msger.info("adding %s to %s" % (file, dst))
1259 arcname=os.path.join("%s-%s" \
1260 % (self.name, image_format),
1261 os.path.basename(file)))
1262 if os.path.isdir(file):
1263 shutil.rmtree(file, ignore_errors = True)
1269 '''All the file in outimage has been packaged into tar.* file'''
1270 self.outimage = [dst]
1272 def release_output(self, config, destdir, release):
1273 """ Create release directory and files
1277 """ release path """
1278 return os.path.join(destdir, fn)
1280 outimages = self.outimage
1283 new_kspath = _rpath(self.name+'.ks')
1284 with open(config) as fr:
1285 with open(new_kspath, "w") as wf:
1286 # When building a release we want to make sure the .ks
1287 # file generates the same build even when --release not used.
1288 wf.write(fr.read().replace("@BUILD_ID@", release))
1289 outimages.append(new_kspath)
1291 # save log file, logfile is only available in creator attrs
1292 if hasattr(self, 'releaselog') and self.releaselog:
1293 final_logfile = _rpath(self.name+'.log')
1294 shutil.move(self.logfile, final_logfile)
1295 self.logfile = final_logfile
1296 outimages.append(self.logfile)
1298 # rename iso and usbimg
1299 for f in os.listdir(destdir):
1300 if f.endswith(".iso"):
1301 newf = f[:-4] + '.img'
1302 elif f.endswith(".usbimg"):
1303 newf = f[:-7] + '.img'
1306 os.rename(_rpath(f), _rpath(newf))
1307 outimages.append(_rpath(newf))
1310 with open(_rpath("MD5SUMS"), "w") as wf:
1311 for f in os.listdir(destdir):
1315 if os.path.isdir(os.path.join(destdir, f)):
1318 md5sum = misc.get_md5sum(_rpath(f))
1319 # There needs to be two spaces between the sum and
1320 # filepath to match the syntax with md5sum.
1321 # This way also md5sum -c MD5SUMS can be used by users
1322 wf.write("%s %s\n" % (md5sum, f))
1324 outimages.append("%s/MD5SUMS" % destdir)
1326 # Filter out the nonexist file
1327 for fp in outimages[:]:
1328 if not os.path.exists("%s" % fp):
1329 outimages.remove(fp)
1331 def copy_kernel(self):
1332 """ Copy kernel files to the outimage directory.
1333 NOTE: This needs to be called before unmounting the instroot.
1336 if not self._need_copy_kernel:
1339 if not os.path.exists(self.destdir):
1340 os.makedirs(self.destdir)
1342 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1343 kernelfilename = "%s/%s-%s" % (self.destdir,
1345 os.path.basename(kernel))
1346 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1348 shutil.copy(kernel, kernelfilename)
1349 self.outimage.append(kernelfilename)
1351 def copy_attachment(self):
1352 """ Subclass implement it to handle attachment files
1353 NOTE: This needs to be called before unmounting the instroot.
1357 def get_pkg_manager(self):
1358 return self.pkgmgr(target_arch = self.target_arch,
1359 instroot = self._instroot,
1360 cachedir = self.cachedir)
1362 def create_manifest(self):
1363 def get_pack_suffix():
1364 return '.' + self.pack_to.split('.', 1)[1]
1366 if not os.path.exists(self.destdir):
1367 os.makedirs(self.destdir)
1369 now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1370 manifest_dict = {'version': VERSION,
1373 manifest_dict.update({'format': self.img_format})
1375 if hasattr(self, 'logfile') and self.logfile:
1376 manifest_dict.update({'log_file': self.logfile})
1378 if self.image_files:
1380 self.image_files.update({'pack': get_pack_suffix()})
1381 manifest_dict.update({self.img_format: self.image_files})
1383 msger.info('Creating manifest file...')
1384 manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1385 with open(manifest_file_path, 'w') as fest_file:
1386 json.dump(manifest_dict, fest_file, indent=4)
1387 self.outimage.append(manifest_file_path)