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, proxy, proxy_username, proxy_password, debuginfo, source, gpgkey, disable) = repo
834 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy, proxy_username, proxy_password, inc, exc)
836 if kickstart.exclude_docs(self.ks):
837 rpm.addMacro("_excludedocs", "1")
838 rpm.addMacro("_dbpath", "/var/lib/rpm")
839 rpm.addMacro("__file_context_path", "%{nil}")
840 if kickstart.inst_langs(self.ks) != None:
841 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
845 self.__select_packages(pkg_manager)
846 self.__select_groups(pkg_manager)
847 self.__deselect_packages(pkg_manager)
848 self.__localinst_packages(pkg_manager)
850 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
851 checksize = self._root_fs_avail
853 checksize -= BOOT_SAFEGUARD
855 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
856 pkg_manager.runInstall(checksize)
857 except CreatorError, e:
860 self._pkgs_content = pkg_manager.getAllContent()
861 self._pkgs_license = pkg_manager.getPkgsLicense()
863 pkg_manager.closeRpmDB()
867 # do some clean up to avoid lvm info leakage. this sucks.
868 for subdir in ("cache", "backup", "archive"):
869 lvmdir = self._instroot + "/etc/lvm/" + subdir
871 for f in os.listdir(lvmdir):
872 os.unlink(lvmdir + "/" + f)
876 def __run_post_scripts(self):
877 msger.info("Running scripts ...")
878 if os.path.exists(self._instroot + "/tmp"):
879 shutil.rmtree(self._instroot + "/tmp")
880 os.mkdir (self._instroot + "/tmp", 0755)
881 for s in kickstart.get_post_scripts(self.ks):
882 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
883 dir = self._instroot + "/tmp")
885 s.script = s.script.replace("\r", "")
886 os.write(fd, s.script)
890 env = self._get_post_scripts_env(s.inChroot)
893 env["INSTALL_ROOT"] = self._instroot
894 env["IMG_NAME"] = self._name
898 preexec = self._chroot
899 script = "/tmp/" + os.path.basename(path)
903 subprocess.call([s.interp, script],
904 preexec_fn = preexec, env = env, stdout = sys.stdout, stderr = sys.stderr)
905 except OSError, (err, msg):
906 raise CreatorError("Failed to execute %%post script "
907 "with '%s' : %s" % (s.interp, msg))
911 def __save_repo_keys(self, repodata):
915 gpgkeydir = "/etc/pki/rpm-gpg"
916 fs.makedirs(self._instroot + gpgkeydir)
917 for repo in repodata:
919 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
920 shutil.copy(repo["repokey"], self._instroot + repokey)
922 def configure(self, repodata = None):
923 """Configure the system image according to the kickstart.
925 This method applies the (e.g. keyboard or network) configuration
926 specified in the kickstart and executes the kickstart %post scripts.
928 If necessary, it also prepares the image to be bootable by e.g.
929 creating an initrd and bootloader configuration.
932 ksh = self.ks.handler
934 msger.info('Applying configurations ...')
936 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
937 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
938 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
939 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
940 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
941 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
942 kickstart.UserConfig(self._instroot).apply(ksh.user)
943 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
944 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
945 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
946 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
947 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
948 self.__save_repo_keys(repodata)
949 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata)
951 msger.warning("Failed to apply configuration to image")
954 self._create_bootconfig()
955 self.__run_post_scripts()
957 def launch_shell(self, launch):
958 """Launch a shell in the install root.
960 This method is launches a bash shell chroot()ed in the install root;
961 this can be useful for debugging.
965 msger.info("Launching shell. Exit to continue.")
966 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
968 def do_genchecksum(self, image_name):
969 if not self._genchecksum:
972 """ Generate md5sum if /usr/bin/md5sum is available """
973 if os.path.exists("/usr/bin/md5sum"):
974 (rc, md5sum) = runner.runtool(["/usr/bin/md5sum", "-b", image_name])
976 msger.warning("Can't generate md5sum for image %s" % image_name)
978 pattern = re.compile("\*.*$")
979 md5sum = pattern.sub("*" + os.path.basename(image_name), md5sum)
980 fd = open(image_name + ".md5sum", "w")
983 self.outimage.append(image_name+".md5sum")
985 def package(self, destdir = "."):
986 """Prepares the created image for final delivery.
988 In its simplest form, this method merely copies the install root to the
989 supplied destination directory; other subclasses may choose to package
990 the image by e.g. creating a bootable ISO containing the image and
991 bootloader configuration.
993 destdir -- the directory into which the final image should be moved;
994 this defaults to the current directory.
997 self._stage_final_image()
999 if not os.path.exists(destdir):
1000 fs.makedirs(destdir)
1001 if self.__img_compression_method:
1002 if not self._img_name:
1003 raise CreatorError("Image name not set.")
1005 img_location = os.path.join(self._outdir,self._img_name)
1006 if self.__img_compression_method == "bz2":
1007 bzip2 = fs.find_binary_path('bzip2')
1008 msger.info("Compressing %s with bzip2. Please wait..." % img_location)
1009 rc = runner.show([bzip2, "-f", img_location])
1011 raise CreatorError("Failed to compress image %s with %s." % (img_location, self.__img_compression_method))
1012 for bootimg in glob.glob(os.path.dirname(img_location) + "/*-boot.bin"):
1013 msger.info("Compressing %s with bzip2. Please wait..." % bootimg)
1014 rc = runner.show([bzip2, "-f", bootimg])
1016 raise CreatorError("Failed to compress image %s with %s." % (bootimg, self.__img_compression_method))
1018 if self._recording_pkgs:
1019 self._save_recording_pkgs(destdir)
1021 """ For image formats with two or multiple image files, it will be better to put them under a directory """
1022 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1023 destdir = os.path.join(destdir, "%s-%s" % (self.name, self.image_format))
1024 msger.debug("creating destination dir: %s" % destdir)
1025 fs.makedirs(destdir)
1027 # Ensure all data is flushed to _outdir
1028 runner.quiet('sync')
1030 for f in os.listdir(self._outdir):
1031 shutil.move(os.path.join(self._outdir, f),
1032 os.path.join(destdir, f))
1033 self.outimage.append(os.path.join(destdir, f))
1034 self.do_genchecksum(os.path.join(destdir, f))
1036 def print_outimage_info(self):
1037 msg = "The new image can be found here:\n"
1038 self.outimage.sort()
1039 for file in self.outimage:
1040 msg += ' %s\n' % os.path.abspath(file)
1044 def check_depend_tools(self):
1045 for tool in self._dep_checks:
1046 fs.find_binary_path(tool)
1048 def package_output(self, image_format, destdir = ".", package="none"):
1049 if not package or package == "none":
1052 destdir = os.path.abspath(os.path.expanduser(destdir))
1053 (pkg, comp) = os.path.splitext(package)
1055 comp=comp.lstrip(".")
1059 dst = "%s/%s-%s.tar.%s" % (destdir, self.name, image_format, comp)
1061 dst = "%s/%s-%s.tar" % (destdir, self.name, image_format)
1062 msger.info("creating %s" % dst)
1063 tar = tarfile.open(dst, "w:" + comp)
1065 for file in self.outimage:
1066 msger.info("adding %s to %s" % (file, dst))
1067 tar.add(file, arcname=os.path.join("%s-%s" % (self.name, image_format), os.path.basename(file)))
1068 if os.path.isdir(file):
1069 shutil.rmtree(file, ignore_errors = True)
1075 '''All the file in outimage has been packaged into tar.* file'''
1076 self.outimage = [dst]
1078 def release_output(self, config, destdir, release):
1079 """ Create release directory and files
1083 """ release path """
1084 return os.path.join(destdir, fn)
1086 outimages = self.outimage
1089 new_kspath = _rpath(self.name+'.ks')
1090 with open(config) as fr:
1091 with open(new_kspath, "w") as wf:
1092 # When building a release we want to make sure the .ks
1093 # file generates the same build even when --release= is not used.
1094 wf.write(fr.read().replace("@BUILD_ID@", release))
1095 outimages.append(new_kspath)
1097 # rename iso and usbimg
1098 for f in os.listdir(destdir):
1099 if f.endswith(".iso"):
1100 newf = f[:-4] + '.img'
1101 elif f.endswith(".usbimg"):
1102 newf = f[:-7] + '.img'
1105 os.rename(_rpath(f), _rpath(newf))
1106 outimages.append(_rpath(newf))
1109 with open(_rpath("MANIFEST"), "w") as wf:
1110 if os.path.exists("/usr/bin/md5sum"):
1111 for f in os.listdir(destdir):
1112 if f == "MANIFEST": continue
1113 if os.path.isdir(os.path.join(destdir,f)):
1116 rc, md5sum = runner.runtool(["/usr/bin/md5sum", "-b", _rpath(f)])
1118 msger.warning("Failed to generate md5sum for file %s" % _rpath(f))
1120 md5sum = md5sum.lstrip().split()[0]
1121 wf.write(md5sum+" "+ f +"\n")
1123 msger.warning('no md5sum tool found, no checksum string in MANIFEST')
1124 wf.writelines(os.listdir(destdir))
1125 outimages.append("%s/MANIFEST" % destdir)
1127 # Filter out the nonexist file
1128 for fp in outimages[:]:
1129 if not os.path.exists("%s" % fp):
1130 outimages.remove(fp)
1132 def save_kernel(self, destdir):
1133 if not os.path.exists(destdir):
1134 os.makedirs(destdir)
1135 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1136 kernelfilename = "%s/%s-%s" % (destdir, self.name, os.path.basename(kernel))
1137 shutil.copy(kernel, kernelfilename)
1138 self.outimage.append(kernelfilename)
1140 def compress_disk_image(self, compression_method):
1142 With this you can set the method that is used to compress the disk
1143 image after it is created.
1146 if compression_method not in ('bz2'):
1147 raise CreatorError("Given disk image compression method ('%s') is not valid." % (compression_method))
1149 self.__img_compression_method = compression_method
1151 def get_pkg_manager(self):
1152 return self.pkgmgr(creator = self)