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 = []
67 # A pykickstart.KickstartParser instance."""
68 self.ks = createopts['ks']
70 self.destdir = os.path.abspath(os.path.expanduser(createopts["outdir"]))
72 # A name for the image."""
73 self.name = createopts['name']
74 if createopts['release']:
75 self.name += '-' + createopts['release']
77 # check whether destine dir exist
78 if os.path.exists(self.destdir):
79 if msger.ask('Image dir: %s already exists, cleanup and continue?' % self.destdir):
80 shutil.rmtree(self.destdir, ignore_errors = True)
82 raise Abort('Canceled')
84 # pending FEA: save log by default for --release
86 # The directory in which all temporary files will be created."""
87 self.tmpdir = createopts['tmpdir']
88 self.cachedir = createopts['cachedir']
89 self.target_arch = createopts['arch']
90 self._local_pkgs_path = createopts['local_pkgs_path']
95 self.tmpdir = "/var/tmp/mic"
96 self.cachedir = "/var/tmp/mic/cache"
98 self.target_arch = "noarch"
99 self._local_pkgs_path = None
101 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe", "passwd"]
103 #FIXME to be obsolete, make it configurable
104 self.distro_name = "Tizen"
106 # Output image file names"""
109 # A flag to generate checksum"""
110 self._genchecksum = False
112 self._alt_initrd_name = None
114 # the disk image after creation, e.g., bz2.
115 # This value is set with compression_method function. """
116 self.__img_compression_method = None
118 self._recording_pkgs = []
120 # available size in root fs, init to 0
121 self._root_fs_avail = 0
123 # Name of the disk image file that is created. """
124 self._img_name = None
126 self.image_format = None
128 # Save qemu emulator file name in order to clean up it finally """
129 self.qemu_emulator = None
131 # No ks provided when called by convertor, so skip the dependency check """
133 # If we have btrfs partition we need to check that we have toosl for those """
134 for part in self.ks.handler.partition.partitions:
135 if part.fstype and part.fstype == "btrfs":
136 self._dep_checks.append("mkfs.btrfs")
139 if self.target_arch and self.target_arch.startswith("arm"):
140 for dep in self._dep_checks:
141 if dep == "extlinux":
142 self._dep_checks.remove(dep)
144 if not os.path.exists("/usr/bin/qemu-arm") or not misc.is_statically_linked("/usr/bin/qemu-arm"):
145 self._dep_checks.append("qemu-arm-static")
147 if os.path.exists("/proc/sys/vm/vdso_enabled"):
148 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
149 vdso_value = vdso_fh.read().strip()
151 if (int)(vdso_value) == 1:
152 msger.warning("vdso is enabled on your host, which might cause problems with arm emulations.\n"
153 "\tYou can disable vdso with following command before starting image build:\n"
154 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
156 # make sure the specified tmpdir and cachedir exist
157 if not os.path.exists(self.tmpdir):
158 os.makedirs(self.tmpdir)
159 if not os.path.exists(self.cachedir):
160 os.makedirs(self.cachedir)
168 def __get_instroot(self):
169 if self.__builddir is None:
170 raise CreatorError("_instroot is not valid before calling mount()")
171 return self.__builddir + "/install_root"
172 _instroot = property(__get_instroot)
173 """The location of the install root directory.
175 This is the directory into which the system is installed. Subclasses may
176 mount a filesystem image here or copy files to/from here.
178 Note, this directory does not exist before ImageCreator.mount() is called.
180 Note also, this is a read-only attribute.
184 def __get_outdir(self):
185 if self.__builddir is None:
186 raise CreatorError("_outdir is not valid before calling mount()")
187 return self.__builddir + "/out"
188 _outdir = property(__get_outdir)
189 """The staging location for the final image.
191 This is where subclasses should stage any files that are part of the final
192 image. ImageCreator.package() will copy any files found here into the
193 requested destination directory.
195 Note, this directory does not exist before ImageCreator.mount() is called.
197 Note also, this is a read-only attribute.
202 # Hooks for subclasses
204 def _mount_instroot(self, base_on = None):
205 """Mount or prepare the install root directory.
207 This is the hook where subclasses may prepare the install root by e.g.
208 mounting creating and loopback mounting a filesystem image to
211 There is no default implementation.
213 base_on -- this is the value passed to mount() and can be interpreted
214 as the subclass wishes; it might e.g. be the location of
215 a previously created ISO containing a system image.
220 def _unmount_instroot(self):
221 """Undo anything performed in _mount_instroot().
223 This is the hook where subclasses must undo anything which was done
224 in _mount_instroot(). For example, if a filesystem image was mounted
225 onto _instroot, it should be unmounted here.
227 There is no default implementation.
232 def _create_bootconfig(self):
233 """Configure the image so that it's bootable.
235 This is the hook where subclasses may prepare the image for booting by
236 e.g. creating an initramfs and bootloader configuration.
238 This hook is called while the install root is still mounted, after the
239 packages have been installed and the kickstart configuration has been
240 applied, but before the %post scripts have been executed.
242 There is no default implementation.
247 def _stage_final_image(self):
248 """Stage the final system image in _outdir.
250 This is the hook where subclasses should place the image in _outdir
251 so that package() can copy it to the requested destination directory.
253 By default, this moves the install root into _outdir.
256 shutil.move(self._instroot, self._outdir + "/" + self.name)
258 def get_installed_packages(self):
259 return self._pkgs_content.keys()
261 def _save_recording_pkgs(self, destdir):
262 """Save the list or content of installed packages to file.
264 pkgs = self._pkgs_content.keys()
265 pkgs.sort() # inplace op
267 if not os.path.exists(destdir):
269 if 'name' in self._recording_pkgs :
270 namefile = os.path.join(destdir, self.name + '.packages')
271 f = open(namefile, "w")
272 content = '\n'.join(pkgs)
275 self.outimage.append(namefile);
277 # if 'content', save more details
278 if 'content' in self._recording_pkgs :
279 contfile = os.path.join(destdir, self.name + '.files')
280 f = open(contfile, "w")
285 pkgcont = self._pkgs_content[pkg]
287 if pkgcont.has_key('dir'):
288 items = map(lambda x:x+'/', pkgcont['dir'])
289 if pkgcont.has_key('file'):
290 items.extend(pkgcont['file'])
294 content += '\n '.join(items)
300 self.outimage.append(contfile)
302 if 'license' in self._recording_pkgs:
303 licensefile = os.path.join(destdir, self.name + '.license')
304 f = open(licensefile, "w")
306 f.write('Summary:\n')
307 for license in reversed(sorted(self._pkgs_license, key=lambda license: len(self._pkgs_license[license]))):
308 f.write(" - %s: %s\n" % (license, len(self._pkgs_license[license])))
310 f.write('\nDetails:\n')
311 for license in reversed(sorted(self._pkgs_license, key=lambda license: len(self._pkgs_license[license]))):
312 f.write(" - %s:\n" % (license))
313 for pkg in sorted(self._pkgs_license[license]):
314 f.write(" - %s\n" % (pkg))
318 self.outimage.append(licensefile);
320 def _get_required_packages(self):
321 """Return a list of required packages.
323 This is the hook where subclasses may specify a set of packages which
324 it requires to be installed.
326 This returns an empty list by default.
328 Note, subclasses should usually chain up to the base class
329 implementation of this hook.
334 def _get_excluded_packages(self):
335 """Return a list of excluded packages.
337 This is the hook where subclasses may specify a set of packages which
338 it requires _not_ to be installed.
340 This returns an empty list by default.
342 Note, subclasses should usually chain up to the base class
343 implementation of this hook.
348 def _get_local_packages(self):
349 """Return a list of rpm path to be local installed.
351 This is the hook where subclasses may specify a set of rpms which
352 it requires to be installed locally.
354 This returns an empty list by default.
356 Note, subclasses should usually chain up to the base class
357 implementation of this hook.
360 if self._local_pkgs_path:
361 if os.path.isdir(self._local_pkgs_path):
363 os.path.join(self._local_pkgs_path, '*.rpm'))
364 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
365 return [self._local_pkgs_path]
369 def _get_fstab(self):
370 """Return the desired contents of /etc/fstab.
372 This is the hook where subclasses may specify the contents of
373 /etc/fstab by returning a string containing the desired contents.
375 A sensible default implementation is provided.
378 s = "/dev/root / %s %s 0 0\n" % (self._fstype, "defaults,noatime" if not self._fsopts else self._fsopts)
379 s += self._get_fstab_special()
382 def _get_fstab_special(self):
383 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
384 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
385 s += "proc /proc proc defaults 0 0\n"
386 s += "sysfs /sys sysfs defaults 0 0\n"
389 def _get_post_scripts_env(self, in_chroot):
390 """Return an environment dict for %post scripts.
392 This is the hook where subclasses may specify some environment
393 variables for %post scripts by return a dict containing the desired
396 By default, this returns an empty dict.
398 in_chroot -- whether this %post script is to be executed chroot()ed
404 def __get_imgname(self):
406 _name = property(__get_imgname)
407 """The name of the image file.
411 def _get_kernel_versions(self):
412 """Return a dict detailing the available kernel types/versions.
414 This is the hook where subclasses may override what kernel types and
415 versions should be available for e.g. creating the booloader
418 A dict should be returned mapping the available kernel types to a list
419 of the available versions for those kernels.
421 The default implementation uses rpm to iterate over everything
422 providing 'kernel', finds /boot/vmlinuz-* and returns the version
423 obtained from the vmlinuz filename. (This can differ from the kernel
424 RPM's n-v-r in the case of e.g. xen)
427 def get_kernel_versions(instroot):
430 files = glob.glob(instroot + "/boot/vmlinuz-*")
432 version = os.path.basename(file)[8:]
435 versions.add(version)
436 ret["kernel"] = list(versions)
439 def get_version(header):
441 for f in header['filenames']:
442 if f.startswith('/boot/vmlinuz-'):
447 return get_kernel_versions(self._instroot)
449 ts = rpm.TransactionSet(self._instroot)
452 for header in ts.dbMatch('provides', 'kernel'):
453 version = get_version(header)
457 name = header['name']
459 ret[name] = [version]
460 elif not version in ret[name]:
461 ret[name].append(version)
466 # Helpers for subclasses
468 def _do_bindmounts(self):
469 """Mount various system directories onto _instroot.
471 This method is called by mount(), but may also be used by subclasses
472 in order to re-mount the bindmounts after modifying the underlying
476 for b in self.__bindmounts:
479 def _undo_bindmounts(self):
480 """Unmount the bind-mounted system directories from _instroot.
482 This method is usually only called by unmount(), but may also be used
483 by subclasses in order to gain access to the filesystem obscured by
484 the bindmounts - e.g. in order to create device nodes on the image
488 self.__bindmounts.reverse()
489 for b in self.__bindmounts:
493 """Chroot into the install root.
495 This method may be used by subclasses when executing programs inside
496 the install root e.g.
498 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
501 os.chroot(self._instroot)
504 def _mkdtemp(self, prefix = "tmp-"):
505 """Create a temporary directory.
507 This method may be used by subclasses to create a temporary directory
508 for use in building the final image - e.g. a subclass might create
509 a temporary directory in order to bundle a set of files into a package.
511 The subclass may delete this directory if it wishes, but it will be
512 automatically deleted by cleanup().
514 The absolute path to the temporary directory is returned.
516 Note, this method should only be called after mount() has been called.
518 prefix -- a prefix which should be used when creating the directory;
522 self.__ensure_builddir()
523 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
525 def _mkstemp(self, prefix = "tmp-"):
526 """Create a temporary file.
528 This method may be used by subclasses to create a temporary file
529 for use in building the final image - e.g. a subclass might need
530 a temporary location to unpack a compressed file.
532 The subclass may delete this file if it wishes, but it will be
533 automatically deleted by cleanup().
535 A tuple containing a file descriptor (returned from os.open() and the
536 absolute path to the temporary directory is returned.
538 Note, this method should only be called after mount() has been called.
540 prefix -- a prefix which should be used when creating the file;
544 self.__ensure_builddir()
545 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
547 def _mktemp(self, prefix = "tmp-"):
548 """Create a temporary file.
550 This method simply calls _mkstemp() and closes the returned file
553 The absolute path to the temporary file is returned.
555 Note, this method should only be called after mount() has been called.
557 prefix -- a prefix which should be used when creating the file;
562 (f, path) = self._mkstemp(prefix)
567 # Actual implementation
569 def __ensure_builddir(self):
570 if not self.__builddir is None:
574 self.__builddir = tempfile.mkdtemp(dir = self.tmpdir,
575 prefix = "imgcreate-")
576 except OSError, (err, msg):
577 raise CreatorError("Failed create build directory in %s: %s" %
580 def get_cachedir(self, cachedir = None):
584 self.__ensure_builddir()
586 self.cachedir = cachedir
588 self.cachedir = self.__builddir + "/yum-cache"
589 fs.makedirs(self.cachedir)
592 def __sanity_check(self):
593 """Ensure that the config we've been given is sane."""
594 if not (kickstart.get_packages(self.ks) or
595 kickstart.get_groups(self.ks)):
596 raise CreatorError("No packages or groups specified")
598 kickstart.convert_method_to_repo(self.ks)
600 if not kickstart.get_repos(self.ks):
601 raise CreatorError("No repositories specified")
603 def __write_fstab(self):
604 fstab = open(self._instroot + "/etc/fstab", "w")
605 fstab.write(self._get_fstab())
608 def __create_minimal_dev(self):
609 """Create a minimal /dev so that we don't corrupt the host /dev"""
610 origumask = os.umask(0000)
611 devices = (('null', 1, 3, 0666),
612 ('urandom',1, 9, 0666),
613 ('random', 1, 8, 0666),
614 ('full', 1, 7, 0666),
615 ('ptmx', 5, 2, 0666),
617 ('zero', 1, 5, 0666))
618 links = (("/proc/self/fd", "/dev/fd"),
619 ("/proc/self/fd/0", "/dev/stdin"),
620 ("/proc/self/fd/1", "/dev/stdout"),
621 ("/proc/self/fd/2", "/dev/stderr"))
623 for (node, major, minor, perm) in devices:
624 if not os.path.exists(self._instroot + "/dev/" + node):
625 os.mknod(self._instroot + "/dev/" + node, perm | stat.S_IFCHR, os.makedev(major,minor))
626 for (src, dest) in links:
627 if not os.path.exists(self._instroot + dest):
628 os.symlink(src, self._instroot + dest)
631 def mount(self, base_on = None, cachedir = None):
632 """Setup the target filesystem in preparation for an install.
634 This function sets up the filesystem which the ImageCreator will
635 install into and configure. The ImageCreator class merely creates an
636 install root directory, bind mounts some system directories (e.g. /dev)
637 and writes out /etc/fstab. Other subclasses may also e.g. create a
638 sparse file, format it and loopback mount it to the install root.
640 base_on -- a previous install on which to base this install; defaults
641 to None, causing a new image to be created
643 cachedir -- a directory in which to store the Yum cache; defaults to
644 None, causing a new cache to be created; by setting this
645 to another directory, the same cache can be reused across
649 self.__ensure_builddir()
651 fs.makedirs(self._instroot)
652 fs.makedirs(self._outdir)
654 self._mount_instroot(base_on)
656 for d in ("/dev/pts", "/etc", "/boot", "/var/log", "/var/cache/yum", "/sys", "/proc", "/usr/bin"):
657 fs.makedirs(self._instroot + d)
659 if self.target_arch and self.target_arch.startswith("arm"):
660 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot, self.target_arch)
662 self.get_cachedir(cachedir)
664 # bind mount system directories into _instroot
665 for (f, dest) in [("/sys", None),
667 ("/proc/sys/fs/binfmt_misc", None),
669 (self.get_cachedir(), "/var/cache/yum")]:
670 self.__bindmounts.append(fs.BindChrootMount(f, self._instroot, dest))
672 self._do_bindmounts()
674 self.__create_minimal_dev()
676 if os.path.exists(self._instroot + "/etc/mtab"):
677 os.unlink(self._instroot + "/etc/mtab")
678 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
682 # get size of available space in 'instroot' fs
683 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
686 """Unmounts the target filesystem.
688 The ImageCreator class detaches the system from the install root, but
689 other subclasses may also detach the loopback mounted filesystem image
690 from the install root.
694 mtab = self._instroot + "/etc/mtab"
695 if not os.path.islink(mtab):
696 os.unlink(self._instroot + "/etc/mtab")
698 if self.qemu_emulator:
699 os.unlink(self._instroot + self.qemu_emulator)
703 self._undo_bindmounts()
705 """ Clean up yum garbage """
707 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
708 if os.path.exists(instroot_pdir):
709 shutil.rmtree(instroot_pdir, ignore_errors = True)
710 yumcachedir = self._instroot + "/var/cache/yum"
711 if os.path.exists(yumcachedir):
712 shutil.rmtree(yumcachedir, ignore_errors = True)
713 yumlibdir = self._instroot + "/var/lib/yum"
714 if os.path.exists(yumlibdir):
715 shutil.rmtree(yumlibdir, ignore_errors = True)
719 self._unmount_instroot()
722 """Unmounts the target filesystem and deletes temporary files.
724 This method calls unmount() and then deletes any temporary files and
725 directories that were created on the host system while building the
728 Note, make sure to call this method once finished with the creator
729 instance in order to ensure no stale files are left on the host e.g.:
731 creator = ImageCreator(ks, name)
738 if not self.__builddir:
743 shutil.rmtree(self.__builddir, ignore_errors = True)
744 self.__builddir = None
746 def __is_excluded_pkg(self, pkg):
747 if pkg in self._excluded_pkgs:
748 self._excluded_pkgs.remove(pkg)
751 for xpkg in self._excluded_pkgs:
752 if xpkg.endswith('*'):
753 if pkg.startswith(xpkg[:-1]):
755 elif xpkg.startswith('*'):
756 if pkg.endswith(xpkg[1:]):
761 def __select_packages(self, pkg_manager):
763 for pkg in self._required_pkgs:
764 e = pkg_manager.selectPackage(pkg)
766 if kickstart.ignore_missing(self.ks):
767 skipped_pkgs.append(pkg)
768 elif self.__is_excluded_pkg(pkg):
769 skipped_pkgs.append(pkg)
771 raise CreatorError("Failed to find package '%s' : %s" %
774 for pkg in skipped_pkgs:
775 msger.warning("Skipping missing package '%s'" % (pkg,))
777 def __select_groups(self, pkg_manager):
779 for group in self._required_groups:
780 e = pkg_manager.selectGroup(group.name, group.include)
782 if kickstart.ignore_missing(self.ks):
783 skipped_groups.append(group)
785 raise CreatorError("Failed to find group '%s' : %s" %
788 for group in skipped_groups:
789 msger.warning("Skipping missing group '%s'" % (group.name,))
791 def __deselect_packages(self, pkg_manager):
792 for pkg in self._excluded_pkgs:
793 pkg_manager.deselectPackage(pkg)
795 def __localinst_packages(self, pkg_manager):
796 for rpm_path in self._get_local_packages():
797 pkg_manager.installLocal(rpm_path)
799 def install(self, repo_urls = {}):
800 """Install packages into the install root.
802 This function installs the packages listed in the supplied kickstart
803 into the install root. By default, the packages are installed from the
804 repository URLs specified in the kickstart.
806 repo_urls -- a dict which maps a repository name to a repository URL;
807 if supplied, this causes any repository URLs specified in
808 the kickstart to be overridden.
812 # initialize pkg list to install
814 self.__sanity_check()
816 self._required_pkgs = \
817 kickstart.get_packages(self.ks, self._get_required_packages())
818 self._excluded_pkgs = \
819 kickstart.get_excluded(self.ks, self._get_excluded_packages())
820 self._required_groups = kickstart.get_groups(self.ks)
822 self._required_pkgs = None
823 self._excluded_pkgs = None
824 self._required_groups = None
826 yum_conf = self._mktemp(prefix = "yum.conf-")
828 pkg_manager = self.get_pkg_manager()
829 pkg_manager.setup(yum_conf, self._instroot)
831 for repo in kickstart.get_repos(self.ks, repo_urls):
832 (name, baseurl, mirrorlist, inc, exc,
833 proxy, proxy_username, proxy_password, debuginfo,
834 source, gpgkey, disable, ssl_verify) = repo
836 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
837 proxy_username, proxy_password, inc, exc, ssl_verify)
839 if kickstart.exclude_docs(self.ks):
840 rpm.addMacro("_excludedocs", "1")
841 rpm.addMacro("_dbpath", "/var/lib/rpm")
842 rpm.addMacro("__file_context_path", "%{nil}")
843 if kickstart.inst_langs(self.ks) != None:
844 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
848 self.__select_packages(pkg_manager)
849 self.__select_groups(pkg_manager)
850 self.__deselect_packages(pkg_manager)
851 self.__localinst_packages(pkg_manager)
853 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
854 checksize = self._root_fs_avail
856 checksize -= BOOT_SAFEGUARD
858 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
859 pkg_manager.runInstall(checksize)
860 except CreatorError, e:
863 self._pkgs_content = pkg_manager.getAllContent()
864 self._pkgs_license = pkg_manager.getPkgsLicense()
866 pkg_manager.closeRpmDB()
870 # do some clean up to avoid lvm info leakage. this sucks.
871 for subdir in ("cache", "backup", "archive"):
872 lvmdir = self._instroot + "/etc/lvm/" + subdir
874 for f in os.listdir(lvmdir):
875 os.unlink(lvmdir + "/" + f)
879 def __run_post_scripts(self):
880 msger.info("Running scripts ...")
881 if os.path.exists(self._instroot + "/tmp"):
882 shutil.rmtree(self._instroot + "/tmp")
883 os.mkdir (self._instroot + "/tmp", 0755)
884 for s in kickstart.get_post_scripts(self.ks):
885 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
886 dir = self._instroot + "/tmp")
888 s.script = s.script.replace("\r", "")
889 os.write(fd, s.script)
893 env = self._get_post_scripts_env(s.inChroot)
896 env["INSTALL_ROOT"] = self._instroot
897 env["IMG_NAME"] = self._name
901 preexec = self._chroot
902 script = "/tmp/" + os.path.basename(path)
906 subprocess.call([s.interp, script],
907 preexec_fn = preexec, env = env, stdout = sys.stdout, stderr = sys.stderr)
908 except OSError, (err, msg):
909 raise CreatorError("Failed to execute %%post script "
910 "with '%s' : %s" % (s.interp, msg))
914 def __save_repo_keys(self, repodata):
918 gpgkeydir = "/etc/pki/rpm-gpg"
919 fs.makedirs(self._instroot + gpgkeydir)
920 for repo in repodata:
922 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
923 shutil.copy(repo["repokey"], self._instroot + repokey)
925 def configure(self, repodata = None):
926 """Configure the system image according to the kickstart.
928 This method applies the (e.g. keyboard or network) configuration
929 specified in the kickstart and executes the kickstart %post scripts.
931 If necessary, it also prepares the image to be bootable by e.g.
932 creating an initrd and bootloader configuration.
935 ksh = self.ks.handler
937 msger.info('Applying configurations ...')
939 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
940 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
941 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
942 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
943 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
944 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
945 kickstart.UserConfig(self._instroot).apply(ksh.user)
946 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
947 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
948 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
949 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
950 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
951 self.__save_repo_keys(repodata)
952 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata)
954 msger.warning("Failed to apply configuration to image")
957 self._create_bootconfig()
958 self.__run_post_scripts()
960 def launch_shell(self, launch):
961 """Launch a shell in the install root.
963 This method is launches a bash shell chroot()ed in the install root;
964 this can be useful for debugging.
968 msger.info("Launching shell. Exit to continue.")
969 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
971 def do_genchecksum(self, image_name):
972 if not self._genchecksum:
975 """ Generate md5sum if /usr/bin/md5sum is available """
976 if os.path.exists("/usr/bin/md5sum"):
977 (rc, md5sum) = runner.runtool(["/usr/bin/md5sum", "-b", image_name])
979 msger.warning("Can't generate md5sum for image %s" % image_name)
981 pattern = re.compile("\*.*$")
982 md5sum = pattern.sub("*" + os.path.basename(image_name), md5sum)
983 fd = open(image_name + ".md5sum", "w")
986 self.outimage.append(image_name+".md5sum")
988 def package(self, destdir = "."):
989 """Prepares the created image for final delivery.
991 In its simplest form, this method merely copies the install root to the
992 supplied destination directory; other subclasses may choose to package
993 the image by e.g. creating a bootable ISO containing the image and
994 bootloader configuration.
996 destdir -- the directory into which the final image should be moved;
997 this defaults to the current directory.
1000 self._stage_final_image()
1002 if not os.path.exists(destdir):
1003 fs.makedirs(destdir)
1004 if self.__img_compression_method:
1005 if not self._img_name:
1006 raise CreatorError("Image name not set.")
1008 img_location = os.path.join(self._outdir,self._img_name)
1009 if self.__img_compression_method == "bz2":
1010 bzip2 = fs.find_binary_path('bzip2')
1011 msger.info("Compressing %s with bzip2. Please wait..." % img_location)
1012 rc = runner.show([bzip2, "-f", img_location])
1014 raise CreatorError("Failed to compress image %s with %s." % (img_location, self.__img_compression_method))
1015 for bootimg in glob.glob(os.path.dirname(img_location) + "/*-boot.bin"):
1016 msger.info("Compressing %s with bzip2. Please wait..." % bootimg)
1017 rc = runner.show([bzip2, "-f", bootimg])
1019 raise CreatorError("Failed to compress image %s with %s." % (bootimg, self.__img_compression_method))
1021 if self._recording_pkgs:
1022 self._save_recording_pkgs(destdir)
1024 """ For image formats with two or multiple image files, it will be better to put them under a directory """
1025 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1026 destdir = os.path.join(destdir, "%s-%s" % (self.name, self.image_format))
1027 msger.debug("creating destination dir: %s" % destdir)
1028 fs.makedirs(destdir)
1030 # Ensure all data is flushed to _outdir
1031 runner.quiet('sync')
1033 for f in os.listdir(self._outdir):
1034 shutil.move(os.path.join(self._outdir, f),
1035 os.path.join(destdir, f))
1036 self.outimage.append(os.path.join(destdir, f))
1037 self.do_genchecksum(os.path.join(destdir, f))
1039 def print_outimage_info(self):
1040 msg = "The new image can be found here:\n"
1041 self.outimage.sort()
1042 for file in self.outimage:
1043 msg += ' %s\n' % os.path.abspath(file)
1047 def check_depend_tools(self):
1048 for tool in self._dep_checks:
1049 fs.find_binary_path(tool)
1051 def package_output(self, image_format, destdir = ".", package="none"):
1052 if not package or package == "none":
1055 destdir = os.path.abspath(os.path.expanduser(destdir))
1056 (pkg, comp) = os.path.splitext(package)
1058 comp=comp.lstrip(".")
1062 dst = "%s/%s-%s.tar.%s" % (destdir, self.name, image_format, comp)
1064 dst = "%s/%s-%s.tar" % (destdir, self.name, image_format)
1065 msger.info("creating %s" % dst)
1066 tar = tarfile.open(dst, "w:" + comp)
1068 for file in self.outimage:
1069 msger.info("adding %s to %s" % (file, dst))
1070 tar.add(file, arcname=os.path.join("%s-%s" % (self.name, image_format), os.path.basename(file)))
1071 if os.path.isdir(file):
1072 shutil.rmtree(file, ignore_errors = True)
1078 '''All the file in outimage has been packaged into tar.* file'''
1079 self.outimage = [dst]
1081 def release_output(self, config, destdir, release):
1082 """ Create release directory and files
1086 """ release path """
1087 return os.path.join(destdir, fn)
1089 outimages = self.outimage
1092 new_kspath = _rpath(self.name+'.ks')
1093 with open(config) as fr:
1094 with open(new_kspath, "w") as wf:
1095 # When building a release we want to make sure the .ks
1096 # file generates the same build even when --release= is not used.
1097 wf.write(fr.read().replace("@BUILD_ID@", release))
1098 outimages.append(new_kspath)
1100 # rename iso and usbimg
1101 for f in os.listdir(destdir):
1102 if f.endswith(".iso"):
1103 newf = f[:-4] + '.img'
1104 elif f.endswith(".usbimg"):
1105 newf = f[:-7] + '.img'
1108 os.rename(_rpath(f), _rpath(newf))
1109 outimages.append(_rpath(newf))
1112 with open(_rpath("MANIFEST"), "w") as wf:
1113 if os.path.exists("/usr/bin/md5sum"):
1114 for f in os.listdir(destdir):
1115 if f == "MANIFEST": continue
1116 if os.path.isdir(os.path.join(destdir,f)):
1119 rc, md5sum = runner.runtool(["/usr/bin/md5sum", "-b", _rpath(f)])
1121 msger.warning("Failed to generate md5sum for file %s" % _rpath(f))
1123 md5sum = md5sum.lstrip().split()[0]
1124 wf.write(md5sum+" "+ f +"\n")
1126 msger.warning('no md5sum tool found, no checksum string in MANIFEST')
1127 wf.writelines(os.listdir(destdir))
1128 outimages.append("%s/MANIFEST" % destdir)
1130 # Filter out the nonexist file
1131 for fp in outimages[:]:
1132 if not os.path.exists("%s" % fp):
1133 outimages.remove(fp)
1135 def save_kernel(self, destdir):
1136 if not os.path.exists(destdir):
1137 os.makedirs(destdir)
1138 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1139 kernelfilename = "%s/%s-%s" % (destdir, self.name, os.path.basename(kernel))
1140 shutil.copy(kernel, kernelfilename)
1141 self.outimage.append(kernelfilename)
1143 def compress_disk_image(self, compression_method):
1145 With this you can set the method that is used to compress the disk
1146 image after it is created.
1149 if compression_method not in ('bz2'):
1150 raise CreatorError("Given disk image compression method ('%s') is not valid." % (compression_method))
1152 self.__img_compression_method = compression_method
1154 def get_pkg_manager(self):
1155 return self.pkgmgr(creator = self)