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.
30 from mic import kickstart
32 from mic.utils.errors import CreatorError
33 from mic.utils import misc, rpmmisc, runner, fs_related as fs
35 class BaseImageCreator(object):
36 """Installs a system to a chroot directory.
38 ImageCreator is the simplest creator class available; it will install and
39 configure a system image according to the supplied kickstart file.
43 import mic.imgcreate as imgcreate
44 ks = imgcreate.read_kickstart("foo.ks")
45 imgcreate.ImageCreator(ks, "foo").create()
49 def __init__(self, createopts = None, pkgmgr = None):
50 """Initialize an ImageCreator instance.
52 ks -- a pykickstart.KickstartParser instance; this instance will be
53 used to drive the install by e.g. providing the list of packages
54 to be installed, the system configuration and %post scripts
56 name -- a name for the image; used for e.g. image filenames or
63 # A pykickstart.KickstartParser instance."""
64 self.ks = createopts['ks']
66 # A name for the image."""
67 self.name = createopts['name']
69 # The directory in which all temporary files will be created."""
70 self.tmpdir = createopts['tmpdir']
72 self.cachedir = createopts['cachedir']
74 self.destdir = createopts['outdir']
76 target_arch = createopts['arch']
77 self._local_pkgs_path = createopts['local_pkgs_path']
82 self.tmpdir = "/var/tmp/mic"
83 self.cachedir = "/var/tmp/mic/cache"
85 target_arch = "noarch"
86 self._local_pkgs_path = None
88 self.__builddir = None
89 self.__bindmounts = []
91 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe", "passwd"]
94 self.distro_name = "MeeGo"
96 # Output image file names"""
99 # A flag to generate checksum"""
100 self._genchecksum = False
102 self._alt_initrd_name = None
104 # the disk image after creation, e.g., bz2.
105 # This value is set with compression_method function. """
106 self.__img_compression_method = None
108 self._recording_pkgs = []
110 # available size in root fs, init to 0
111 self._root_fs_avail = 0
113 # Name of the disk image file that is created. """
114 self._img_name = None
116 self.image_format = None
118 # Save qemu emulator file name in order to clean up it finally """
119 self.qemu_emulator = None
121 # No ks provided when called by convertor, so skip the dependency check """
123 # If we have btrfs partition we need to check that we have toosl for those """
124 for part in self.ks.handler.partition.partitions:
125 if part.fstype and part.fstype == "btrfs":
126 self._dep_checks.append("mkfs.btrfs")
129 if target_arch.startswith("arm"):
130 if not self.set_target_arch(target_arch):
131 raise CreatorError('arch "%s" can not be supported' % target_arch)
133 self.target_arch = None
135 # make sure the specified tmpdir and cachedir exist
136 if not os.path.exists(self.tmpdir):
137 os.makedirs(self.tmpdir)
138 if not os.path.exists(self.cachedir):
139 os.makedirs(self.cachedir)
141 def set_target_arch(self, arch):
142 if arch not in rpmmisc.arches:
145 self.target_arch = arch
146 for dep in self._dep_checks:
147 if dep == "extlinux":
148 self._dep_checks.remove(dep)
150 if not os.path.exists("/usr/bin/qemu-arm") or not misc.is_statically_linked("/usr/bin/qemu-arm"):
151 self._dep_checks.append("qemu-arm-static")
153 if os.path.exists("/proc/sys/vm/vdso_enabled"):
154 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
155 vdso_value = vdso_fh.read().strip()
157 if (int)(vdso_value) == 1:
158 msger.warning("vdso is enabled on your host, which might cause problems with arm emulations.\n"
159 "\tYou can disable vdso with following command before starting image build:\n"
160 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
170 def __get_instroot(self):
171 if self.__builddir is None:
172 raise CreatorError("_instroot is not valid before calling mount()")
173 return self.__builddir + "/install_root"
174 _instroot = property(__get_instroot)
175 """The location of the install root directory.
177 This is the directory into which the system is installed. Subclasses may
178 mount a filesystem image here or copy files to/from here.
180 Note, this directory does not exist before ImageCreator.mount() is called.
182 Note also, this is a read-only attribute.
186 def __get_outdir(self):
187 if self.__builddir is None:
188 raise CreatorError("_outdir is not valid before calling mount()")
189 return self.__builddir + "/out"
190 _outdir = property(__get_outdir)
191 """The staging location for the final image.
193 This is where subclasses should stage any files that are part of the final
194 image. ImageCreator.package() will copy any files found here into the
195 requested destination directory.
197 Note, this directory does not exist before ImageCreator.mount() is called.
199 Note also, this is a read-only attribute.
204 # Hooks for subclasses
206 def _mount_instroot(self, base_on = None):
207 """Mount or prepare the install root directory.
209 This is the hook where subclasses may prepare the install root by e.g.
210 mounting creating and loopback mounting a filesystem image to
213 There is no default implementation.
215 base_on -- this is the value passed to mount() and can be interpreted
216 as the subclass wishes; it might e.g. be the location of
217 a previously created ISO containing a system image.
222 def _unmount_instroot(self):
223 """Undo anything performed in _mount_instroot().
225 This is the hook where subclasses must undo anything which was done
226 in _mount_instroot(). For example, if a filesystem image was mounted
227 onto _instroot, it should be unmounted here.
229 There is no default implementation.
234 def _create_bootconfig(self):
235 """Configure the image so that it's bootable.
237 This is the hook where subclasses may prepare the image for booting by
238 e.g. creating an initramfs and bootloader configuration.
240 This hook is called while the install root is still mounted, after the
241 packages have been installed and the kickstart configuration has been
242 applied, but before the %post scripts have been executed.
244 There is no default implementation.
249 def _stage_final_image(self):
250 """Stage the final system image in _outdir.
252 This is the hook where subclasses should place the image in _outdir
253 so that package() can copy it to the requested destination directory.
255 By default, this moves the install root into _outdir.
258 shutil.move(self._instroot, self._outdir + "/" + self.name)
260 def get_installed_packages(self):
261 return self._pkgs_content.keys()
263 def _save_recording_pkgs(self, destdir):
264 """Save the list or content of installed packages to file.
266 pkgs = self._pkgs_content.keys()
267 pkgs.sort() # inplace op
269 if not os.path.exists(destdir):
271 if 'name' in self._recording_pkgs :
272 namefile = os.path.join(destdir, self.name + '-pkgs.txt')
273 f = open(namefile, "w")
274 content = '\n'.join(pkgs)
277 self.outimage.append(namefile);
279 # if 'content', save more details
280 if 'content' in self._recording_pkgs :
281 contfile = os.path.join(destdir, self.name + '-pkgs-content.txt')
282 f = open(contfile, "w")
287 pkgcont = self._pkgs_content[pkg]
289 if pkgcont.has_key('dir'):
290 items = map(lambda x:x+'/', pkgcont['dir'])
291 if pkgcont.has_key('file'):
292 items.extend(pkgcont['file'])
296 content += '\n '.join(items)
302 self.outimage.append(contfile)
304 if 'license' in self._recording_pkgs:
305 licensefile = os.path.join(destdir, self.name + '-license.txt')
306 f = open(licensefile, "w")
308 f.write('Summary:\n')
309 for license in reversed(sorted(self._pkgs_license, key=lambda license: len(self._pkgs_license[license]))):
310 f.write(" - %s: %s\n" % (license, len(self._pkgs_license[license])))
312 f.write('\nDetails:\n')
313 for license in reversed(sorted(self._pkgs_license, key=lambda license: len(self._pkgs_license[license]))):
314 f.write(" - %s:\n" % (license))
315 for pkg in sorted(self._pkgs_license[license]):
316 f.write(" - %s\n" % (pkg))
320 self.outimage.append(licensefile);
322 def _get_required_packages(self):
323 """Return a list of required packages.
325 This is the hook where subclasses may specify a set of packages which
326 it requires to be installed.
328 This returns an empty list by default.
330 Note, subclasses should usually chain up to the base class
331 implementation of this hook.
336 def _get_excluded_packages(self):
337 """Return a list of excluded packages.
339 This is the hook where subclasses may specify a set of packages which
340 it requires _not_ to be installed.
342 This returns an empty list by default.
344 Note, subclasses should usually chain up to the base class
345 implementation of this hook.
350 def _get_local_packages(self):
351 """Return a list of rpm path to be local installed.
353 This is the hook where subclasses may specify a set of rpms which
354 it requires to be installed locally.
356 This returns an empty list by default.
358 Note, subclasses should usually chain up to the base class
359 implementation of this hook.
362 if self._local_pkgs_path:
363 if os.path.isdir(self._local_pkgs_path):
365 os.path.join(self._local_pkgs_path, '*.rpm'))
366 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
367 return [self._local_pkgs_path]
371 def _get_fstab(self):
372 """Return the desired contents of /etc/fstab.
374 This is the hook where subclasses may specify the contents of
375 /etc/fstab by returning a string containing the desired contents.
377 A sensible default implementation is provided.
380 s = "/dev/root / %s %s 0 0\n" % (self._fstype, "defaults,noatime" if not self._fsopts else self._fsopts)
381 s += self._get_fstab_special()
384 def _get_fstab_special(self):
385 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
386 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
387 s += "proc /proc proc defaults 0 0\n"
388 s += "sysfs /sys sysfs defaults 0 0\n"
391 def _get_post_scripts_env(self, in_chroot):
392 """Return an environment dict for %post scripts.
394 This is the hook where subclasses may specify some environment
395 variables for %post scripts by return a dict containing the desired
398 By default, this returns an empty dict.
400 in_chroot -- whether this %post script is to be executed chroot()ed
406 def __get_imgname(self):
408 _name = property(__get_imgname)
409 """The name of the image file.
413 def _get_kernel_versions(self):
414 """Return a dict detailing the available kernel types/versions.
416 This is the hook where subclasses may override what kernel types and
417 versions should be available for e.g. creating the booloader
420 A dict should be returned mapping the available kernel types to a list
421 of the available versions for those kernels.
423 The default implementation uses rpm to iterate over everything
424 providing 'kernel', finds /boot/vmlinuz-* and returns the version
425 obtained from the vmlinuz filename. (This can differ from the kernel
426 RPM's n-v-r in the case of e.g. xen)
429 def get_kernel_versions(instroot):
432 files = glob.glob(instroot + "/boot/vmlinuz-*")
434 version = os.path.basename(file)[8:]
437 versions.add(version)
438 ret["kernel"] = list(versions)
441 def get_version(header):
443 for f in header['filenames']:
444 if f.startswith('/boot/vmlinuz-'):
449 return get_kernel_versions(self._instroot)
451 ts = rpm.TransactionSet(self._instroot)
454 for header in ts.dbMatch('provides', 'kernel'):
455 version = get_version(header)
459 name = header['name']
461 ret[name] = [version]
462 elif not version in ret[name]:
463 ret[name].append(version)
468 # Helpers for subclasses
470 def _do_bindmounts(self):
471 """Mount various system directories onto _instroot.
473 This method is called by mount(), but may also be used by subclasses
474 in order to re-mount the bindmounts after modifying the underlying
478 for b in self.__bindmounts:
481 def _undo_bindmounts(self):
482 """Unmount the bind-mounted system directories from _instroot.
484 This method is usually only called by unmount(), but may also be used
485 by subclasses in order to gain access to the filesystem obscured by
486 the bindmounts - e.g. in order to create device nodes on the image
490 self.__bindmounts.reverse()
491 for b in self.__bindmounts:
495 """Chroot into the install root.
497 This method may be used by subclasses when executing programs inside
498 the install root e.g.
500 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
503 os.chroot(self._instroot)
506 def _mkdtemp(self, prefix = "tmp-"):
507 """Create a temporary directory.
509 This method may be used by subclasses to create a temporary directory
510 for use in building the final image - e.g. a subclass might create
511 a temporary directory in order to bundle a set of files into a package.
513 The subclass may delete this directory if it wishes, but it will be
514 automatically deleted by cleanup().
516 The absolute path to the temporary directory is returned.
518 Note, this method should only be called after mount() has been called.
520 prefix -- a prefix which should be used when creating the directory;
524 self.__ensure_builddir()
525 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
527 def _mkstemp(self, prefix = "tmp-"):
528 """Create a temporary file.
530 This method may be used by subclasses to create a temporary file
531 for use in building the final image - e.g. a subclass might need
532 a temporary location to unpack a compressed file.
534 The subclass may delete this file if it wishes, but it will be
535 automatically deleted by cleanup().
537 A tuple containing a file descriptor (returned from os.open() and the
538 absolute path to the temporary directory is returned.
540 Note, this method should only be called after mount() has been called.
542 prefix -- a prefix which should be used when creating the file;
546 self.__ensure_builddir()
547 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
549 def _mktemp(self, prefix = "tmp-"):
550 """Create a temporary file.
552 This method simply calls _mkstemp() and closes the returned file
555 The absolute path to the temporary file 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;
564 (f, path) = self._mkstemp(prefix)
569 # Actual implementation
571 def __ensure_builddir(self):
572 if not self.__builddir is None:
576 self.__builddir = tempfile.mkdtemp(dir = self.tmpdir,
577 prefix = "imgcreate-")
578 except OSError, (err, msg):
579 raise CreatorError("Failed create build directory in %s: %s" %
582 def get_cachedir(self, cachedir = None):
586 self.__ensure_builddir()
588 self.cachedir = cachedir
590 self.cachedir = self.__builddir + "/yum-cache"
591 fs.makedirs(self.cachedir)
594 def __sanity_check(self):
595 """Ensure that the config we've been given is sane."""
596 if not (kickstart.get_packages(self.ks) or
597 kickstart.get_groups(self.ks)):
598 raise CreatorError("No packages or groups specified")
600 kickstart.convert_method_to_repo(self.ks)
602 if not kickstart.get_repos(self.ks):
603 raise CreatorError("No repositories specified")
605 def __write_fstab(self):
606 fstab = open(self._instroot + "/etc/fstab", "w")
607 fstab.write(self._get_fstab())
610 def __create_minimal_dev(self):
611 """Create a minimal /dev so that we don't corrupt the host /dev"""
612 origumask = os.umask(0000)
613 devices = (('null', 1, 3, 0666),
614 ('urandom',1, 9, 0666),
615 ('random', 1, 8, 0666),
616 ('full', 1, 7, 0666),
617 ('ptmx', 5, 2, 0666),
619 ('zero', 1, 5, 0666))
620 links = (("/proc/self/fd", "/dev/fd"),
621 ("/proc/self/fd/0", "/dev/stdin"),
622 ("/proc/self/fd/1", "/dev/stdout"),
623 ("/proc/self/fd/2", "/dev/stderr"))
625 for (node, major, minor, perm) in devices:
626 if not os.path.exists(self._instroot + "/dev/" + node):
627 os.mknod(self._instroot + "/dev/" + node, perm | stat.S_IFCHR, os.makedev(major,minor))
628 for (src, dest) in links:
629 if not os.path.exists(self._instroot + dest):
630 os.symlink(src, self._instroot + dest)
633 def mount(self, base_on = None, cachedir = None):
634 """Setup the target filesystem in preparation for an install.
636 This function sets up the filesystem which the ImageCreator will
637 install into and configure. The ImageCreator class merely creates an
638 install root directory, bind mounts some system directories (e.g. /dev)
639 and writes out /etc/fstab. Other subclasses may also e.g. create a
640 sparse file, format it and loopback mount it to the install root.
642 base_on -- a previous install on which to base this install; defaults
643 to None, causing a new image to be created
645 cachedir -- a directory in which to store the Yum cache; defaults to
646 None, causing a new cache to be created; by setting this
647 to another directory, the same cache can be reused across
651 self.__ensure_builddir()
653 fs.makedirs(self._instroot)
654 fs.makedirs(self._outdir)
656 self._mount_instroot(base_on)
658 for d in ("/dev/pts", "/etc", "/boot", "/var/log", "/var/cache/yum", "/sys", "/proc", "/usr/bin"):
659 fs.makedirs(self._instroot + d)
661 if self.target_arch and self.target_arch.startswith("arm"):
662 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot, self.target_arch)
664 self.get_cachedir(cachedir)
666 # bind mount system directories into _instroot
667 for (f, dest) in [("/sys", None),
669 ("/proc/sys/fs/binfmt_misc", None),
671 (self.get_cachedir(), "/var/cache/yum")]:
672 self.__bindmounts.append(fs.BindChrootMount(f, self._instroot, dest))
674 self._do_bindmounts()
676 self.__create_minimal_dev()
678 if os.path.exists(self._instroot + "/etc/mtab"):
679 os.unlink(self._instroot + "/etc/mtab")
680 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
684 # get size of available space in 'instroot' fs
685 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
688 """Unmounts the target filesystem.
690 The ImageCreator class detaches the system from the install root, but
691 other subclasses may also detach the loopback mounted filesystem image
692 from the install root.
696 mtab = self._instroot + "/etc/mtab"
697 if not os.path.islink(mtab):
698 os.unlink(self._instroot + "/etc/mtab")
700 if self.qemu_emulator:
701 os.unlink(self._instroot + self.qemu_emulator)
705 self._undo_bindmounts()
707 """ Clean up yum garbage """
709 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
710 if os.path.exists(instroot_pdir):
711 shutil.rmtree(instroot_pdir, ignore_errors = True)
712 yumcachedir = self._instroot + "/var/cache/yum"
713 if os.path.exists(yumcachedir):
714 shutil.rmtree(yumcachedir, ignore_errors = True)
715 yumlibdir = self._instroot + "/var/lib/yum"
716 if os.path.exists(yumlibdir):
717 shutil.rmtree(yumlibdir, ignore_errors = True)
721 self._unmount_instroot()
724 """Unmounts the target filesystem and deletes temporary files.
726 This method calls unmount() and then deletes any temporary files and
727 directories that were created on the host system while building the
730 Note, make sure to call this method once finished with the creator
731 instance in order to ensure no stale files are left on the host e.g.:
733 creator = ImageCreator(ks, name)
740 if not self.__builddir:
745 shutil.rmtree(self.__builddir, ignore_errors = True)
746 self.__builddir = None
748 def __is_excluded_pkg(self, pkg):
749 if pkg in self._excluded_pkgs:
750 self._excluded_pkgs.remove(pkg)
753 for xpkg in self._excluded_pkgs:
754 if xpkg.endswith('*'):
755 if pkg.startswith(xpkg[:-1]):
757 elif xpkg.startswith('*'):
758 if pkg.endswith(xpkg[1:]):
763 def __select_packages(self, pkg_manager):
765 for pkg in self._required_pkgs:
766 e = pkg_manager.selectPackage(pkg)
768 if kickstart.ignore_missing(self.ks):
769 skipped_pkgs.append(pkg)
770 elif self.__is_excluded_pkg(pkg):
771 skipped_pkgs.append(pkg)
773 raise CreatorError("Failed to find package '%s' : %s" %
776 for pkg in skipped_pkgs:
777 msger.warning("Skipping missing package '%s'" % (pkg,))
779 def __select_groups(self, pkg_manager):
781 for group in self._required_groups:
782 e = pkg_manager.selectGroup(group.name, group.include)
784 if kickstart.ignore_missing(self.ks):
785 skipped_groups.append(group)
787 raise CreatorError("Failed to find group '%s' : %s" %
790 for group in skipped_groups:
791 msger.warning("Skipping missing group '%s'" % (group.name,))
793 def __deselect_packages(self, pkg_manager):
794 for pkg in self._excluded_pkgs:
795 pkg_manager.deselectPackage(pkg)
797 def __localinst_packages(self, pkg_manager):
798 for rpm_path in self._get_local_packages():
799 pkg_manager.installLocal(rpm_path)
801 def install(self, repo_urls = {}):
802 """Install packages into the install root.
804 This function installs the packages listed in the supplied kickstart
805 into the install root. By default, the packages are installed from the
806 repository URLs specified in the kickstart.
808 repo_urls -- a dict which maps a repository name to a repository URL;
809 if supplied, this causes any repository URLs specified in
810 the kickstart to be overridden.
814 # initialize pkg list to install
816 self.__sanity_check()
818 self._required_pkgs = \
819 kickstart.get_packages(self.ks, self._get_required_packages())
820 self._excluded_pkgs = \
821 kickstart.get_excluded(self.ks, self._get_excluded_packages())
822 self._required_groups = kickstart.get_groups(self.ks)
824 self._required_pkgs = None
825 self._excluded_pkgs = None
826 self._required_groups = None
828 yum_conf = self._mktemp(prefix = "yum.conf-")
831 if len(self._recording_pkgs) > 0:
832 keep_record = self._recording_pkgs
834 pkg_manager = self.get_pkg_manager(keep_record)
835 pkg_manager.setup(yum_conf, self._instroot)
837 for repo in kickstart.get_repos(self.ks, repo_urls):
838 (name, baseurl, mirrorlist, inc, exc, proxy, proxy_username, proxy_password, debuginfo, source, gpgkey, disable) = repo
840 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy, proxy_username, proxy_password, inc, exc)
842 if kickstart.exclude_docs(self.ks):
843 rpm.addMacro("_excludedocs", "1")
844 rpm.addMacro("__file_context_path", "%{nil}")
845 if kickstart.inst_langs(self.ks) != None:
846 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
850 self.__select_packages(pkg_manager)
851 self.__select_groups(pkg_manager)
852 self.__deselect_packages(pkg_manager)
853 self.__localinst_packages(pkg_manager)
855 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
856 checksize = self._root_fs_avail
858 checksize -= BOOT_SAFEGUARD
860 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
861 pkg_manager.runInstall(checksize)
862 except CreatorError, e:
866 self._pkgs_content = pkg_manager.getAllContent()
867 self._pkgs_license = pkg_manager.getPkgsLicense()
869 pkg_manager.closeRpmDB()
873 # do some clean up to avoid lvm info leakage. this sucks.
874 for subdir in ("cache", "backup", "archive"):
875 lvmdir = self._instroot + "/etc/lvm/" + subdir
877 for f in os.listdir(lvmdir):
878 os.unlink(lvmdir + "/" + f)
882 def __run_post_scripts(self):
883 msger.info("Running scripts ...")
884 if os.path.exists(self._instroot + "/tmp"):
885 shutil.rmtree(self._instroot + "/tmp")
886 os.mkdir (self._instroot + "/tmp", 0755)
887 for s in kickstart.get_post_scripts(self.ks):
888 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
889 dir = self._instroot + "/tmp")
891 s.script = s.script.replace("\r", "")
892 os.write(fd, s.script)
896 env = self._get_post_scripts_env(s.inChroot)
899 env["INSTALL_ROOT"] = self._instroot
900 env["IMG_NAME"] = self._name
904 preexec = self._chroot
905 script = "/tmp/" + os.path.basename(path)
909 subprocess.call([s.interp, script],
910 preexec_fn = preexec, env = env, stdout = sys.stdout, stderr = sys.stderr)
911 except OSError, (err, msg):
912 raise CreatorError("Failed to execute %%post script "
913 "with '%s' : %s" % (s.interp, msg))
917 def __save_repo_keys(self, repodata):
921 gpgkeydir = "/etc/pki/rpm-gpg"
922 fs.makedirs(self._instroot + gpgkeydir)
923 for repo in repodata:
925 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
926 shutil.copy(repo["repokey"], self._instroot + repokey)
928 def configure(self, repodata = None):
929 """Configure the system image according to the kickstart.
931 This method applies the (e.g. keyboard or network) configuration
932 specified in the kickstart and executes the kickstart %post scripts.
934 If neccessary, it also prepares the image to be bootable by e.g.
935 creating an initrd and bootloader configuration.
938 ksh = self.ks.handler
940 msger.info('Applying configurations ...')
942 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
943 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
944 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
945 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
946 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
947 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
948 kickstart.UserConfig(self._instroot).apply(ksh.user)
949 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
950 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
951 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
952 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
953 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
954 self.__save_repo_keys(repodata)
955 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata)
957 msger.warning("Failed to apply configuration to image")
960 self._create_bootconfig()
961 self.__run_post_scripts()
963 def launch_shell(self, launch):
964 """Launch a shell in the install root.
966 This method is launches a bash shell chroot()ed in the install root;
967 this can be useful for debugging.
971 msger.info("Launching shell. Exit to continue.")
972 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
974 def do_genchecksum(self, image_name):
975 if not self._genchecksum:
978 """ Generate md5sum if /usr/bin/md5sum is available """
979 if os.path.exists("/usr/bin/md5sum"):
980 (rc, md5sum) = runner.runtool(["/usr/bin/md5sum", "-b", image_name])
982 msger.warning("Can't generate md5sum for image %s" % image_name)
984 pattern = re.compile("\*.*$")
985 md5sum = pattern.sub("*" + os.path.basename(image_name), md5sum)
986 fd = open(image_name + ".md5sum", "w")
989 self.outimage.append(image_name+".md5sum")
991 def package(self, destdir = "."):
992 """Prepares the created image for final delivery.
994 In its simplest form, this method merely copies the install root to the
995 supplied destination directory; other subclasses may choose to package
996 the image by e.g. creating a bootable ISO containing the image and
997 bootloader configuration.
999 destdir -- the directory into which the final image should be moved;
1000 this defaults to the current directory.
1003 self._stage_final_image()
1005 if not os.path.exists(destdir):
1006 fs.makedirs(destdir)
1007 if self.__img_compression_method:
1008 if not self._img_name:
1009 raise CreatorError("Image name not set.")
1011 img_location = os.path.join(self._outdir,self._img_name)
1012 if self.__img_compression_method == "bz2":
1013 bzip2 = fs.find_binary_path('bzip2')
1014 msger.info("Compressing %s with bzip2. Please wait..." % img_location)
1015 rc = runner.show([bzip2, "-f", img_location])
1017 raise CreatorError("Failed to compress image %s with %s." % (img_location, self.__img_compression_method))
1018 for bootimg in glob.glob(os.path.dirname(img_location) + "/*-boot.bin"):
1019 msger.info("Compressing %s with bzip2. Please wait..." % bootimg)
1020 rc = runner.show([bzip2, "-f", bootimg])
1022 raise CreatorError("Failed to compress image %s with %s." % (bootimg, self.__img_compression_method))
1024 if self._recording_pkgs:
1025 self._save_recording_pkgs(destdir)
1027 """ For image formats with two or multiple image files, it will be better to put them under a directory """
1028 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1029 destdir = os.path.join(destdir, "%s-%s" % (self.name, self.image_format))
1030 msger.debug("creating destination dir: %s" % destdir)
1031 fs.makedirs(destdir)
1033 # Ensure all data is flushed to _outdir
1034 runner.quiet('sync')
1036 for f in os.listdir(self._outdir):
1037 shutil.move(os.path.join(self._outdir, f),
1038 os.path.join(destdir, f))
1039 self.outimage.append(os.path.join(destdir, f))
1040 self.do_genchecksum(os.path.join(destdir, f))
1042 def print_outimage_info(self):
1043 msg = "The new image can be found here:\n"
1044 self.outimage.sort()
1045 for file in self.outimage:
1046 msg += ' %s\n' % os.path.abspath(file)
1050 def check_depend_tools(self):
1051 for tool in self._dep_checks:
1052 fs.find_binary_path(tool)
1054 def package_output(self, image_format, destdir = ".", package="none"):
1055 if not package or package == "none":
1058 destdir = os.path.abspath(os.path.expanduser(destdir))
1059 (pkg, comp) = os.path.splitext(package)
1061 comp=comp.lstrip(".")
1065 dst = "%s/%s-%s.tar.%s" % (destdir, self.name, image_format, comp)
1067 dst = "%s/%s-%s.tar" % (destdir, self.name, image_format)
1068 msger.info("creating %s" % dst)
1069 tar = tarfile.open(dst, "w:" + comp)
1071 for file in self.outimage:
1072 msger.info("adding %s to %s" % (file, dst))
1073 tar.add(file, arcname=os.path.join("%s-%s" % (self.name, image_format), os.path.basename(file)))
1074 if os.path.isdir(file):
1075 shutil.rmtree(file, ignore_errors = True)
1081 '''All the file in outimage has been packaged into tar.* file'''
1082 self.outimage = [dst]
1084 def release_output(self, config, destdir, name, release):
1085 self.outimage = misc.create_release(config, destdir, name, self.outimage, release)
1087 def save_kernel(self, destdir):
1088 if not os.path.exists(destdir):
1089 os.makedirs(destdir)
1090 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1091 kernelfilename = "%s/%s-%s" % (destdir, self.name, os.path.basename(kernel))
1092 shutil.copy(kernel, kernelfilename)
1093 self.outimage.append(kernelfilename)
1095 def compress_disk_image(self, compression_method):
1097 With this you can set the method that is used to compress the disk
1098 image after it is created.
1101 if compression_method not in ('bz2'):
1102 raise CreatorError("Given disk image compression method ('%s') is not valid." % (compression_method))
1104 self.__img_compression_method = compression_method
1106 def get_pkg_manager(self, recording_pkgs=None):
1107 return self.pkgmgr(creator = self, recording_pkgs = recording_pkgs)