3 # Copyright (c) 2007 Red Hat Inc.
4 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms of the GNU General Public License as published by the Free
8 # Software Foundation; version 2 of the License
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc., 59
17 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 from __future__ import with_statement
31 from mic import kickstart
33 from mic.utils.errors import CreatorError, Abort
34 from mic.utils import misc, rpmmisc, runner, fs_related as fs
36 class BaseImageCreator(object):
37 """Installs a system to a chroot directory.
39 ImageCreator is the simplest creator class available; it will install and
40 configure a system image according to the supplied kickstart file.
44 import mic.imgcreate as imgcreate
45 ks = imgcreate.read_kickstart("foo.ks")
46 imgcreate.ImageCreator(ks, "foo").create()
50 def __init__(self, createopts = None, pkgmgr = None):
51 """Initialize an ImageCreator instance.
53 ks -- a pykickstart.KickstartParser instance; this instance will be
54 used to drive the install by e.g. providing the list of packages
55 to be installed, the system configuration and %post scripts
57 name -- a name for the image; used for e.g. image filenames or
63 self.__builddir = None
64 self.__bindmounts = []
68 self.tmpdir = "/var/tmp/mic"
69 self.cachedir = "/var/tmp/mic/cache"
71 self.target_arch = "noarch"
72 self._local_pkgs_path = None
74 # Eeach image type can change these values as they might be image type
76 if not hasattr(self, "_valid_compression_methods"):
77 self._valid_compression_methods = ['bz2']
79 # The compression method for disk image.
80 self._img_compression_method = None
83 optmap = {"pkgmgr" : "pkgmgr_name",
85 "arch" : "target_arch",
86 "local_pkgs_path" : "_local_pkgs_path",
87 "compress_disk_image" : "_img_compression_method",
90 # update setting from createopts
91 for key in createopts.keys():
96 setattr(self, option, createopts[key])
98 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
100 if 'release' in createopts and createopts['release']:
101 self.name += '-' + createopts['release']
103 if os.path.exists(self.destdir):
104 if msger.ask("Image dir: %s already exists, cleanup and" \
105 "continue?" % self.destdir):
106 shutil.rmtree(self.destdir, ignore_errors = True)
108 raise Abort("Canceled")
110 # pending FEA: save log by default for --release
112 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe", "passwd"]
114 # Output image file names
117 # A flag to generate checksum
118 self._genchecksum = False
120 self._alt_initrd_name = None
122 self._recording_pkgs = []
124 # available size in root fs, init to 0
125 self._root_fs_avail = 0
127 # Name of the disk image file that is created.
128 self._img_name = None
130 self.image_format = None
132 # Save qemu emulator file name in order to clean up it finally
133 self.qemu_emulator = None
135 # No ks provided when called by convertor, so skip the dependency check
137 # If we have btrfs partition we need to check that we have toosl for those
138 for part in self.ks.handler.partition.partitions:
139 if part.fstype and part.fstype == "btrfs":
140 self._dep_checks.append("mkfs.btrfs")
143 if self.target_arch and self.target_arch.startswith("arm"):
144 for dep in self._dep_checks:
145 if dep == "extlinux":
146 self._dep_checks.remove(dep)
148 if not os.path.exists("/usr/bin/qemu-arm") or \
149 not misc.is_statically_linked("/usr/bin/qemu-arm"):
150 self._dep_checks.append("qemu-arm-static")
152 if os.path.exists("/proc/sys/vm/vdso_enabled"):
153 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
154 vdso_value = vdso_fh.read().strip()
156 if (int)(vdso_value) == 1:
157 msger.warning("vdso is enabled on your host, which might "
158 "cause problems with arm emulations.\n"
159 "\tYou can disable vdso with following command before "
160 "starting image build:\n"
161 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
163 # make sure the specified tmpdir and cachedir exist
164 if not os.path.exists(self.tmpdir):
165 os.makedirs(self.tmpdir)
166 if not os.path.exists(self.cachedir):
167 os.makedirs(self.cachedir)
169 if self._img_compression_method != None and \
170 self._img_compression_method not in self._valid_compression_methods:
171 raise CreatorError("Given disk image compression method ('%s') is "
172 "not valid. Valid values are '%s'." \
173 % (self._img_compression_method,
174 ' '.join(self._valid_compression_methods)))
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.
216 # Hooks for subclasses
218 def _mount_instroot(self, base_on = None):
219 """Mount or prepare the install root directory.
221 This is the hook where subclasses may prepare the install root by e.g.
222 mounting creating and loopback mounting a filesystem image to
225 There is no default implementation.
227 base_on -- this is the value passed to mount() and can be interpreted
228 as the subclass wishes; it might e.g. be the location of
229 a previously created ISO containing a system image.
234 def _unmount_instroot(self):
235 """Undo anything performed in _mount_instroot().
237 This is the hook where subclasses must undo anything which was done
238 in _mount_instroot(). For example, if a filesystem image was mounted
239 onto _instroot, it should be unmounted here.
241 There is no default implementation.
246 def _create_bootconfig(self):
247 """Configure the image so that it's bootable.
249 This is the hook where subclasses may prepare the image for booting by
250 e.g. creating an initramfs and bootloader configuration.
252 This hook is called while the install root is still mounted, after the
253 packages have been installed and the kickstart configuration has been
254 applied, but before the %post scripts have been executed.
256 There is no default implementation.
261 def _stage_final_image(self):
262 """Stage the final system image in _outdir.
264 This is the hook where subclasses should place the image in _outdir
265 so that package() can copy it to the requested destination directory.
267 By default, this moves the install root into _outdir.
270 shutil.move(self._instroot, self._outdir + "/" + self.name)
272 def get_installed_packages(self):
273 return self._pkgs_content.keys()
275 def _save_recording_pkgs(self, destdir):
276 """Save the list or content of installed packages to file.
278 pkgs = self._pkgs_content.keys()
279 pkgs.sort() # inplace op
281 if not os.path.exists(destdir):
283 if 'name' in self._recording_pkgs :
284 namefile = os.path.join(destdir, self.name + '.packages')
285 f = open(namefile, "w")
286 content = '\n'.join(pkgs)
289 self.outimage.append(namefile);
291 # if 'content', save more details
292 if 'content' in self._recording_pkgs :
293 contfile = os.path.join(destdir, self.name + '.files')
294 f = open(contfile, "w")
299 pkgcont = self._pkgs_content[pkg]
301 if pkgcont.has_key('dir'):
302 items = map(lambda x:x+'/', pkgcont['dir'])
303 if pkgcont.has_key('file'):
304 items.extend(pkgcont['file'])
308 content += '\n '.join(items)
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 _get_post_scripts_env(self, in_chroot):
409 """Return an environment dict for %post scripts.
411 This is the hook where subclasses may specify some environment
412 variables for %post scripts by return a dict containing the desired
415 By default, this returns an empty dict.
417 in_chroot -- whether this %post script is to be executed chroot()ed
423 def __get_imgname(self):
425 _name = property(__get_imgname)
426 """The name of the image file.
430 def _get_kernel_versions(self):
431 """Return a dict detailing the available kernel types/versions.
433 This is the hook where subclasses may override what kernel types and
434 versions should be available for e.g. creating the booloader
437 A dict should be returned mapping the available kernel types to a list
438 of the available versions for those kernels.
440 The default implementation uses rpm to iterate over everything
441 providing 'kernel', finds /boot/vmlinuz-* and returns the version
442 obtained from the vmlinuz filename. (This can differ from the kernel
443 RPM's n-v-r in the case of e.g. xen)
446 def get_kernel_versions(instroot):
449 files = glob.glob(instroot + "/boot/vmlinuz-*")
451 version = os.path.basename(file)[8:]
454 versions.add(version)
455 ret["kernel"] = list(versions)
458 def get_version(header):
460 for f in header['filenames']:
461 if f.startswith('/boot/vmlinuz-'):
466 return get_kernel_versions(self._instroot)
468 ts = rpm.TransactionSet(self._instroot)
471 for header in ts.dbMatch('provides', 'kernel'):
472 version = get_version(header)
476 name = header['name']
478 ret[name] = [version]
479 elif not version in ret[name]:
480 ret[name].append(version)
485 # Helpers for subclasses
487 def _do_bindmounts(self):
488 """Mount various system directories onto _instroot.
490 This method is called by mount(), but may also be used by subclasses
491 in order to re-mount the bindmounts after modifying the underlying
495 for b in self.__bindmounts:
498 def _undo_bindmounts(self):
499 """Unmount the bind-mounted system directories from _instroot.
501 This method is usually only called by unmount(), but may also be used
502 by subclasses in order to gain access to the filesystem obscured by
503 the bindmounts - e.g. in order to create device nodes on the image
507 self.__bindmounts.reverse()
508 for b in self.__bindmounts:
512 """Chroot into the install root.
514 This method may be used by subclasses when executing programs inside
515 the install root e.g.
517 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
520 os.chroot(self._instroot)
523 def _mkdtemp(self, prefix = "tmp-"):
524 """Create a temporary directory.
526 This method may be used by subclasses to create a temporary directory
527 for use in building the final image - e.g. a subclass might create
528 a temporary directory in order to bundle a set of files into a package.
530 The subclass may delete this directory if it wishes, but it will be
531 automatically deleted by cleanup().
533 The absolute path to the temporary directory is returned.
535 Note, this method should only be called after mount() has been called.
537 prefix -- a prefix which should be used when creating the directory;
541 self.__ensure_builddir()
542 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
544 def _mkstemp(self, prefix = "tmp-"):
545 """Create a temporary file.
547 This method may be used by subclasses to create a temporary file
548 for use in building the final image - e.g. a subclass might need
549 a temporary location to unpack a compressed file.
551 The subclass may delete this file if it wishes, but it will be
552 automatically deleted by cleanup().
554 A tuple containing a file descriptor (returned from os.open() and the
555 absolute path to the temporary directory is returned.
557 Note, this method should only be called after mount() has been called.
559 prefix -- a prefix which should be used when creating the file;
563 self.__ensure_builddir()
564 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
566 def _mktemp(self, prefix = "tmp-"):
567 """Create a temporary file.
569 This method simply calls _mkstemp() and closes the returned file
572 The absolute path to the temporary file is returned.
574 Note, this method should only be called after mount() has been called.
576 prefix -- a prefix which should be used when creating the file;
581 (f, path) = self._mkstemp(prefix)
586 # Actual implementation
588 def __ensure_builddir(self):
589 if not self.__builddir is None:
593 self.__builddir = tempfile.mkdtemp(dir = self.tmpdir,
594 prefix = "imgcreate-")
595 except OSError, (err, msg):
596 raise CreatorError("Failed create build directory in %s: %s" %
599 def get_cachedir(self, cachedir = None):
603 self.__ensure_builddir()
605 self.cachedir = cachedir
607 self.cachedir = self.__builddir + "/yum-cache"
608 fs.makedirs(self.cachedir)
611 def __sanity_check(self):
612 """Ensure that the config we've been given is sane."""
613 if not (kickstart.get_packages(self.ks) or
614 kickstart.get_groups(self.ks)):
615 raise CreatorError("No packages or groups specified")
617 kickstart.convert_method_to_repo(self.ks)
619 if not kickstart.get_repos(self.ks):
620 raise CreatorError("No repositories specified")
622 def __write_fstab(self):
623 fstab = open(self._instroot + "/etc/fstab", "w")
624 fstab.write(self._get_fstab())
627 def __create_minimal_dev(self):
628 """Create a minimal /dev so that we don't corrupt the host /dev"""
629 origumask = os.umask(0000)
630 devices = (('null', 1, 3, 0666),
631 ('urandom',1, 9, 0666),
632 ('random', 1, 8, 0666),
633 ('full', 1, 7, 0666),
634 ('ptmx', 5, 2, 0666),
636 ('zero', 1, 5, 0666))
638 links = (("/proc/self/fd", "/dev/fd"),
639 ("/proc/self/fd/0", "/dev/stdin"),
640 ("/proc/self/fd/1", "/dev/stdout"),
641 ("/proc/self/fd/2", "/dev/stderr"))
643 for (node, major, minor, perm) in devices:
644 if not os.path.exists(self._instroot + "/dev/" + node):
645 os.mknod(self._instroot + "/dev/" + node,
647 os.makedev(major,minor))
649 for (src, dest) in links:
650 if not os.path.exists(self._instroot + dest):
651 os.symlink(src, self._instroot + dest)
655 def mount(self, base_on = None, cachedir = None):
656 """Setup the target filesystem in preparation for an install.
658 This function sets up the filesystem which the ImageCreator will
659 install into and configure. The ImageCreator class merely creates an
660 install root directory, bind mounts some system directories (e.g. /dev)
661 and writes out /etc/fstab. Other subclasses may also e.g. create a
662 sparse file, format it and loopback mount it to the install root.
664 base_on -- a previous install on which to base this install; defaults
665 to None, causing a new image to be created
667 cachedir -- a directory in which to store the Yum cache; defaults to
668 None, causing a new cache to be created; by setting this
669 to another directory, the same cache can be reused across
673 self.__ensure_builddir()
675 fs.makedirs(self._instroot)
676 fs.makedirs(self._outdir)
678 self._mount_instroot(base_on)
680 for d in ("/dev/pts",
688 fs.makedirs(self._instroot + d)
690 if self.target_arch and self.target_arch.startswith("arm"):
691 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
694 self.get_cachedir(cachedir)
696 # bind mount system directories into _instroot
697 for (f, dest) in [("/sys", None),
699 ("/proc/sys/fs/binfmt_misc", None),
701 (self.get_cachedir(), "/var/cache/yum")]:
702 self.__bindmounts.append(fs.BindChrootMount(f, self._instroot, dest))
704 self._do_bindmounts()
706 self.__create_minimal_dev()
708 if os.path.exists(self._instroot + "/etc/mtab"):
709 os.unlink(self._instroot + "/etc/mtab")
710 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
714 # get size of available space in 'instroot' fs
715 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
718 """Unmounts the target filesystem.
720 The ImageCreator class detaches the system from the install root, but
721 other subclasses may also detach the loopback mounted filesystem image
722 from the install root.
726 mtab = self._instroot + "/etc/mtab"
727 if not os.path.islink(mtab):
728 os.unlink(self._instroot + "/etc/mtab")
730 if self.qemu_emulator:
731 os.unlink(self._instroot + self.qemu_emulator)
735 self._undo_bindmounts()
737 """ Clean up yum garbage """
739 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
740 if os.path.exists(instroot_pdir):
741 shutil.rmtree(instroot_pdir, ignore_errors = True)
742 yumcachedir = self._instroot + "/var/cache/yum"
743 if os.path.exists(yumcachedir):
744 shutil.rmtree(yumcachedir, ignore_errors = True)
745 yumlibdir = self._instroot + "/var/lib/yum"
746 if os.path.exists(yumlibdir):
747 shutil.rmtree(yumlibdir, ignore_errors = True)
751 self._unmount_instroot()
754 """Unmounts the target filesystem and deletes temporary files.
756 This method calls unmount() and then deletes any temporary files and
757 directories that were created on the host system while building the
760 Note, make sure to call this method once finished with the creator
761 instance in order to ensure no stale files are left on the host e.g.:
763 creator = ImageCreator(ks, name)
770 if not self.__builddir:
775 shutil.rmtree(self.__builddir, ignore_errors = True)
776 self.__builddir = None
778 def __is_excluded_pkg(self, pkg):
779 if pkg in self._excluded_pkgs:
780 self._excluded_pkgs.remove(pkg)
783 for xpkg in self._excluded_pkgs:
784 if xpkg.endswith('*'):
785 if pkg.startswith(xpkg[:-1]):
787 elif xpkg.startswith('*'):
788 if pkg.endswith(xpkg[1:]):
793 def __select_packages(self, pkg_manager):
795 for pkg in self._required_pkgs:
796 e = pkg_manager.selectPackage(pkg)
798 if kickstart.ignore_missing(self.ks):
799 skipped_pkgs.append(pkg)
800 elif self.__is_excluded_pkg(pkg):
801 skipped_pkgs.append(pkg)
803 raise CreatorError("Failed to find package '%s' : %s" %
806 for pkg in skipped_pkgs:
807 msger.warning("Skipping missing package '%s'" % (pkg,))
809 def __select_groups(self, pkg_manager):
811 for group in self._required_groups:
812 e = pkg_manager.selectGroup(group.name, group.include)
814 if kickstart.ignore_missing(self.ks):
815 skipped_groups.append(group)
817 raise CreatorError("Failed to find group '%s' : %s" %
820 for group in skipped_groups:
821 msger.warning("Skipping missing group '%s'" % (group.name,))
823 def __deselect_packages(self, pkg_manager):
824 for pkg in self._excluded_pkgs:
825 pkg_manager.deselectPackage(pkg)
827 def __localinst_packages(self, pkg_manager):
828 for rpm_path in self._get_local_packages():
829 pkg_manager.installLocal(rpm_path)
831 def install(self, repo_urls = {}):
832 """Install packages into the install root.
834 This function installs the packages listed in the supplied kickstart
835 into the install root. By default, the packages are installed from the
836 repository URLs specified in the kickstart.
838 repo_urls -- a dict which maps a repository name to a repository URL;
839 if supplied, this causes any repository URLs specified in
840 the kickstart to be overridden.
844 # initialize pkg list to install
846 self.__sanity_check()
848 self._required_pkgs = \
849 kickstart.get_packages(self.ks, self._get_required_packages())
850 self._excluded_pkgs = \
851 kickstart.get_excluded(self.ks, self._get_excluded_packages())
852 self._required_groups = kickstart.get_groups(self.ks)
854 self._required_pkgs = None
855 self._excluded_pkgs = None
856 self._required_groups = None
858 yum_conf = self._mktemp(prefix = "yum.conf-")
860 pkg_manager = self.get_pkg_manager()
861 pkg_manager.setup(yum_conf, self._instroot)
863 for repo in kickstart.get_repos(self.ks, repo_urls):
864 (name, baseurl, mirrorlist, inc, exc,
865 proxy, proxy_username, proxy_password, debuginfo,
866 source, gpgkey, disable, ssl_verify, cost, priority) = repo
868 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
869 proxy_username, proxy_password, inc, exc, ssl_verify,
872 if kickstart.exclude_docs(self.ks):
873 rpm.addMacro("_excludedocs", "1")
874 rpm.addMacro("_dbpath", "/var/lib/rpm")
875 rpm.addMacro("__file_context_path", "%{nil}")
876 if kickstart.inst_langs(self.ks) != None:
877 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
881 self.__select_packages(pkg_manager)
882 self.__select_groups(pkg_manager)
883 self.__deselect_packages(pkg_manager)
884 self.__localinst_packages(pkg_manager)
886 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
887 checksize = self._root_fs_avail
889 checksize -= BOOT_SAFEGUARD
891 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
892 pkg_manager.runInstall(checksize)
893 except CreatorError, e:
896 self._pkgs_content = pkg_manager.getAllContent()
897 self._pkgs_license = pkg_manager.getPkgsLicense()
899 pkg_manager.closeRpmDB()
903 # do some clean up to avoid lvm info leakage. this sucks.
904 for subdir in ("cache", "backup", "archive"):
905 lvmdir = self._instroot + "/etc/lvm/" + subdir
907 for f in os.listdir(lvmdir):
908 os.unlink(lvmdir + "/" + f)
912 def __run_post_scripts(self):
913 msger.info("Running scripts ...")
914 if os.path.exists(self._instroot + "/tmp"):
915 shutil.rmtree(self._instroot + "/tmp")
916 os.mkdir (self._instroot + "/tmp", 0755)
917 for s in kickstart.get_post_scripts(self.ks):
918 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
919 dir = self._instroot + "/tmp")
921 s.script = s.script.replace("\r", "")
922 os.write(fd, s.script)
926 env = self._get_post_scripts_env(s.inChroot)
929 env["INSTALL_ROOT"] = self._instroot
930 env["IMG_NAME"] = self._name
934 preexec = self._chroot
935 script = "/tmp/" + os.path.basename(path)
939 subprocess.call([s.interp, script],
940 preexec_fn = preexec,
944 except OSError, (err, msg):
945 raise CreatorError("Failed to execute %%post script "
946 "with '%s' : %s" % (s.interp, msg))
950 def __save_repo_keys(self, repodata):
954 gpgkeydir = "/etc/pki/rpm-gpg"
955 fs.makedirs(self._instroot + gpgkeydir)
956 for repo in repodata:
958 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
959 shutil.copy(repo["repokey"], self._instroot + repokey)
961 def configure(self, repodata = None):
962 """Configure the system image according to the kickstart.
964 This method applies the (e.g. keyboard or network) configuration
965 specified in the kickstart and executes the kickstart %post scripts.
967 If necessary, it also prepares the image to be bootable by e.g.
968 creating an initrd and bootloader configuration.
971 ksh = self.ks.handler
973 msger.info('Applying configurations ...')
975 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
976 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
977 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
978 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
979 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
980 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
981 kickstart.UserConfig(self._instroot).apply(ksh.user)
982 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
983 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
984 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
985 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
986 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
987 self.__save_repo_keys(repodata)
988 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata)
990 msger.warning("Failed to apply configuration to image")
993 self._create_bootconfig()
994 self.__run_post_scripts()
996 def launch_shell(self, launch):
997 """Launch a shell in the install root.
999 This method is launches a bash shell chroot()ed in the install root;
1000 this can be useful for debugging.
1004 msger.info("Launching shell. Exit to continue.")
1005 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1007 def do_genchecksum(self, image_name):
1008 if not self._genchecksum:
1011 md5sum = misc.get_md5sum(image_name)
1012 with open(image_name + ".md5sum", "w") as f:
1013 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1014 self.outimage.append(image_name+".md5sum")
1016 def package(self, destdir = "."):
1017 """Prepares the created image for final delivery.
1019 In its simplest form, this method merely copies the install root to the
1020 supplied destination directory; other subclasses may choose to package
1021 the image by e.g. creating a bootable ISO containing the image and
1022 bootloader configuration.
1024 destdir -- the directory into which the final image should be moved;
1025 this defaults to the current directory.
1028 self._stage_final_image()
1030 if not os.path.exists(destdir):
1031 fs.makedirs(destdir)
1032 if self._img_compression_method:
1033 if not self._img_name:
1034 raise CreatorError("Image name not set.")
1036 img_location = os.path.join(self._outdir,self._img_name)
1037 if self._img_compression_method == "bz2":
1038 bzip2 = fs.find_binary_path('bzip2')
1039 msger.info("Compressing %s with bzip2. Please wait..." \
1041 rc = runner.show([bzip2, "-f", img_location])
1043 raise CreatorError("Failed to compress image %s with %s." \
1044 % (img_location, self._img_compression_method))
1046 for bootimg in glob.glob(os.path.dirname(img_location) + \
1048 msger.info("Compressing %s with bzip2. Please wait..." \
1050 rc = runner.show([bzip2, "-f", bootimg])
1052 raise CreatorError("Failed to compress image %s with "
1055 self._img_compression_method))
1057 if self._recording_pkgs:
1058 self._save_recording_pkgs(destdir)
1060 # For image formats with two or multiple image files, it will be
1061 # better to put them under a directory
1062 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1063 destdir = os.path.join(destdir, "%s-%s" \
1064 % (self.name, self.image_format))
1065 msger.debug("creating destination dir: %s" % destdir)
1066 fs.makedirs(destdir)
1068 # Ensure all data is flushed to _outdir
1069 runner.quiet('sync')
1071 for f in os.listdir(self._outdir):
1072 shutil.move(os.path.join(self._outdir, f),
1073 os.path.join(destdir, f))
1074 self.outimage.append(os.path.join(destdir, f))
1075 self.do_genchecksum(os.path.join(destdir, f))
1077 def print_outimage_info(self):
1078 msg = "The new image can be found here:\n"
1079 self.outimage.sort()
1080 for file in self.outimage:
1081 msg += ' %s\n' % os.path.abspath(file)
1085 def check_depend_tools(self):
1086 for tool in self._dep_checks:
1087 fs.find_binary_path(tool)
1089 def package_output(self, image_format, destdir = ".", package="none"):
1090 if not package or package == "none":
1093 destdir = os.path.abspath(os.path.expanduser(destdir))
1094 (pkg, comp) = os.path.splitext(package)
1096 comp=comp.lstrip(".")
1100 dst = "%s/%s-%s.tar.%s" % (destdir, self.name, image_format, comp)
1102 dst = "%s/%s-%s.tar" % (destdir, self.name, image_format)
1103 msger.info("creating %s" % dst)
1104 tar = tarfile.open(dst, "w:" + comp)
1106 for file in self.outimage:
1107 msger.info("adding %s to %s" % (file, dst))
1109 arcname=os.path.join("%s-%s" \
1110 % (self.name, image_format),
1111 os.path.basename(file)))
1112 if os.path.isdir(file):
1113 shutil.rmtree(file, ignore_errors = True)
1119 '''All the file in outimage has been packaged into tar.* file'''
1120 self.outimage = [dst]
1122 def release_output(self, config, destdir, release):
1123 """ Create release directory and files
1127 """ release path """
1128 return os.path.join(destdir, fn)
1130 outimages = self.outimage
1133 new_kspath = _rpath(self.name+'.ks')
1134 with open(config) as fr:
1135 with open(new_kspath, "w") as wf:
1136 # When building a release we want to make sure the .ks
1137 # file generates the same build even when --release= is not used.
1138 wf.write(fr.read().replace("@BUILD_ID@", release))
1139 outimages.append(new_kspath)
1141 # rename iso and usbimg
1142 for f in os.listdir(destdir):
1143 if f.endswith(".iso"):
1144 newf = f[:-4] + '.img'
1145 elif f.endswith(".usbimg"):
1146 newf = f[:-7] + '.img'
1149 os.rename(_rpath(f), _rpath(newf))
1150 outimages.append(_rpath(newf))
1153 with open(_rpath("MANIFEST"), "w") as wf:
1154 for f in os.listdir(destdir):
1158 if os.path.isdir(os.path.join(destdir, f)):
1161 md5sum = misc.get_md5sum(_rpath(f))
1162 wf.write("%s %s\n" % (md5sum, f))
1164 outimages.append("%s/MANIFEST" % destdir)
1166 # Filter out the nonexist file
1167 for fp in outimages[:]:
1168 if not os.path.exists("%s" % fp):
1169 outimages.remove(fp)
1171 def save_kernel(self, destdir):
1172 if not os.path.exists(destdir):
1173 os.makedirs(destdir)
1174 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1175 kernelfilename = "%s/%s-%s" % (destdir, self.name, os.path.basename(kernel))
1176 shutil.copy(kernel, kernelfilename)
1177 self.outimage.append(kernelfilename)
1179 def get_pkg_manager(self):
1180 return self.pkgmgr(creator = self)