2 # creator.py : ImageCreator and LoopImageCreator base classes
4 # Copyright 2007, Red Hat Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; version 2 of the License.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Library General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
33 from mic.utils.errors import *
34 from mic.utils.fs_related import *
35 from mic.utils.rpmmisc import *
36 from mic.utils.misc import *
37 from mic.utils import kickstart
39 class BaseImageCreator(object):
40 """Installs a system to a chroot directory.
42 ImageCreator is the simplest creator class available; it will install and
43 configure a system image according to the supplied kickstart file.
47 import mic.imgcreate as imgcreate
48 ks = imgcreate.read_kickstart("foo.ks")
49 imgcreate.ImageCreator(ks, "foo").create()
53 def __init__(self, createopts = None, pkgmgr = None):
54 """Initialize an ImageCreator instance.
56 ks -- a pykickstart.KickstartParser instance; this instance will be
57 used to drive the install by e.g. providing the list of packages
58 to be installed, the system configuration and %post scripts
60 name -- a name for the image; used for e.g. image filenames or
67 # A pykickstart.KickstartParser instance."""
68 self.ks = createopts['ks']
69 self.repometadata = createopts['repomd']
71 # A name for the image."""
74 # The directory in which all temporary files will be created."""
75 self.tmpdir = createopts['tmpdir']
77 self.cachedir = createopts['cachedir']
79 self.destdir = createopts['outdir']
82 self.repometadata = None
84 self.tmpdir = "/var/tmp"
85 self.cachedir = "/var/cache"
88 self.__builddir = None
89 self.__bindmounts = []
91 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe", "passwd"]
94 self.distro_name = "MeeGo"
95 # Output image file names"""
97 # A flag to generate checksum"""
98 self._genchecksum = False
99 self._alt_initrd_name = None
100 # the disk image after creation, e.g., bz2.
101 # This value is set with compression_method function. """
102 self.__img_compression_method = None
103 # dependent commands to check
104 self._recording_pkgs = None
105 self._include_src = None
106 self._local_pkgs_path = None
107 # available size in root fs, init to 0
108 self._root_fs_avail = 0
109 # target arch for non-x86 image
110 self.target_arch = None
111 # Name of the disk image file that is created. """
112 self._img_name = None
114 self.image_format = None
115 # Save qemu emulator file name in order to clean up it finally """
116 self.qemu_emulator = None
117 # No ks provided when called by convertor, so skip the dependency check """
119 # If we have btrfs partition we need to check that we have toosl for those """
120 for part in self.ks.handler.partition.partitions:
121 if part.fstype and part.fstype == "btrfs":
122 self._dep_checks.append("mkfs.btrfs")
125 def set_target_arch(self, arch):
126 if arch not in arches.keys():
128 self.target_arch = arch
129 if self.target_arch.startswith("arm"):
130 for dep in self._dep_checks:
131 if dep == "extlinux":
132 self._dep_checks.remove(dep)
134 if not os.path.exists("/usr/bin/qemu-arm") or not is_statically_linked("/usr/bin/qemu-arm"):
135 self._dep_checks.append("qemu-arm-static")
137 if os.path.exists("/proc/sys/vm/vdso_enabled"):
138 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
139 vdso_value = vdso_fh.read().strip()
141 if (int)(vdso_value) == 1:
142 print "\n= WARNING ="
143 print "vdso is enabled on your host, which might cause problems with arm emulations."
144 print "You can disable vdso with following command before starting image build:"
145 print "echo 0 | sudo tee /proc/sys/vm/vdso_enabled"
146 print "= WARNING =\n"
157 def __get_instroot(self):
158 if self.__builddir is None:
159 raise CreatorError("_instroot is not valid before calling mount()")
160 return self.__builddir + "/install_root"
161 _instroot = property(__get_instroot)
162 """The location of the install root directory.
164 This is the directory into which the system is installed. Subclasses may
165 mount a filesystem image here or copy files to/from here.
167 Note, this directory does not exist before ImageCreator.mount() is called.
169 Note also, this is a read-only attribute.
173 def __get_outdir(self):
174 if self.__builddir is None:
175 raise CreatorError("_outdir is not valid before calling mount()")
176 return self.__builddir + "/out"
177 _outdir = property(__get_outdir)
178 """The staging location for the final image.
180 This is where subclasses should stage any files that are part of the final
181 image. ImageCreator.package() will copy any files found here into the
182 requested destination directory.
184 Note, this directory does not exist before ImageCreator.mount() is called.
186 Note also, this is a read-only attribute.
191 # Hooks for subclasses
193 def _mount_instroot(self, base_on = None):
194 """Mount or prepare the install root directory.
196 This is the hook where subclasses may prepare the install root by e.g.
197 mounting creating and loopback mounting a filesystem image to
200 There is no default implementation.
202 base_on -- this is the value passed to mount() and can be interpreted
203 as the subclass wishes; it might e.g. be the location of
204 a previously created ISO containing a system image.
209 def _unmount_instroot(self):
210 """Undo anything performed in _mount_instroot().
212 This is the hook where subclasses must undo anything which was done
213 in _mount_instroot(). For example, if a filesystem image was mounted
214 onto _instroot, it should be unmounted here.
216 There is no default implementation.
221 def _create_bootconfig(self):
222 """Configure the image so that it's bootable.
224 This is the hook where subclasses may prepare the image for booting by
225 e.g. creating an initramfs and bootloader configuration.
227 This hook is called while the install root is still mounted, after the
228 packages have been installed and the kickstart configuration has been
229 applied, but before the %post scripts have been executed.
231 There is no default implementation.
236 def _stage_final_image(self):
237 """Stage the final system image in _outdir.
239 This is the hook where subclasses should place the image in _outdir
240 so that package() can copy it to the requested destination directory.
242 By default, this moves the install root into _outdir.
245 shutil.move(self._instroot, self._outdir + "/" + self.name)
247 def get_installed_packages(self):
248 return self._pkgs_content.keys()
250 def _save_recording_pkgs(self, destdir):
251 """Save the list or content of installed packages to file.
253 if self._recording_pkgs not in ('content', 'name'):
256 pkgs = self._pkgs_content.keys()
257 pkgs.sort() # inplace op
259 # save package name list anyhow
260 if not os.path.exists(destdir):
263 namefile = os.path.join(destdir, self.name + '-pkgs.txt')
264 f = open(namefile, "w")
265 content = '\n'.join(pkgs)
268 self.outimage.append(namefile);
270 # if 'content', save more details
271 if self._recording_pkgs == 'content':
272 contfile = os.path.join(destdir, self.name + '-pkgs-content.txt')
273 f = open(contfile, "w")
278 pkgcont = self._pkgs_content[pkg]
280 if pkgcont.has_key('dir'):
281 items = map(lambda x:x+'/', pkgcont['dir'])
282 if pkgcont.has_key('file'):
283 items.extend(pkgcont['file'])
287 content += '\n '.join(items)
293 self.outimage.append(contfile)
295 def _get_required_packages(self):
296 """Return a list of required packages.
298 This is the hook where subclasses may specify a set of packages which
299 it requires to be installed.
301 This returns an empty list by default.
303 Note, subclasses should usually chain up to the base class
304 implementation of this hook.
309 def _get_excluded_packages(self):
310 """Return a list of excluded packages.
312 This is the hook where subclasses may specify a set of packages which
313 it requires _not_ to be installed.
315 This returns an empty list by default.
317 Note, subclasses should usually chain up to the base class
318 implementation of this hook.
321 excluded_packages = []
322 for rpm_path in self._get_local_packages():
323 rpm_name = os.path.basename(rpm_path)
324 package_name = splitFilename(rpm_name)[0]
325 excluded_packages += [package_name]
326 return excluded_packages
328 def _get_local_packages(self):
329 """Return a list of rpm path to be local installed.
331 This is the hook where subclasses may specify a set of rpms which
332 it requires to be installed locally.
334 This returns an empty list by default.
336 Note, subclasses should usually chain up to the base class
337 implementation of this hook.
340 if self._local_pkgs_path:
341 if os.path.isdir(self._local_pkgs_path):
343 os.path.join(self._local_pkgs_path, '*.rpm'))
344 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
345 return [self._local_pkgs_path]
349 def _get_fstab(self):
350 """Return the desired contents of /etc/fstab.
352 This is the hook where subclasses may specify the contents of
353 /etc/fstab by returning a string containing the desired contents.
355 A sensible default implementation is provided.
358 s = "/dev/root / %s %s 0 0\n" % (self._fstype, "defaults,noatime" if not self._fsopts else self._fsopts)
359 s += self._get_fstab_special()
362 def _get_fstab_special(self):
363 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
364 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
365 s += "proc /proc proc defaults 0 0\n"
366 s += "sysfs /sys sysfs defaults 0 0\n"
369 def _get_post_scripts_env(self, in_chroot):
370 """Return an environment dict for %post scripts.
372 This is the hook where subclasses may specify some environment
373 variables for %post scripts by return a dict containing the desired
376 By default, this returns an empty dict.
378 in_chroot -- whether this %post script is to be executed chroot()ed
384 def __get_imgname(self):
386 _name = property(__get_imgname)
387 """The name of the image file.
391 def _get_kernel_versions(self):
392 """Return a dict detailing the available kernel types/versions.
394 This is the hook where subclasses may override what kernel types and
395 versions should be available for e.g. creating the booloader
398 A dict should be returned mapping the available kernel types to a list
399 of the available versions for those kernels.
401 The default implementation uses rpm to iterate over everything
402 providing 'kernel', finds /boot/vmlinuz-* and returns the version
403 obtained from the vmlinuz filename. (This can differ from the kernel
404 RPM's n-v-r in the case of e.g. xen)
407 def get_version(header):
409 for f in header['filenames']:
410 if f.startswith('/boot/vmlinuz-'):
414 ts = rpm.TransactionSet(self._instroot)
417 for header in ts.dbMatch('provides', 'kernel'):
418 version = get_version(header)
422 name = header['name']
424 ret[name] = [version]
425 elif not version in ret[name]:
426 ret[name].append(version)
431 # Helpers for subclasses
433 def _do_bindmounts(self):
434 """Mount various system directories onto _instroot.
436 This method is called by mount(), but may also be used by subclasses
437 in order to re-mount the bindmounts after modifying the underlying
441 for b in self.__bindmounts:
444 def _undo_bindmounts(self):
445 """Unmount the bind-mounted system directories from _instroot.
447 This method is usually only called by unmount(), but may also be used
448 by subclasses in order to gain access to the filesystem obscured by
449 the bindmounts - e.g. in order to create device nodes on the image
453 self.__bindmounts.reverse()
454 for b in self.__bindmounts:
458 """Chroot into the install root.
460 This method may be used by subclasses when executing programs inside
461 the install root e.g.
463 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
466 os.chroot(self._instroot)
469 def _mkdtemp(self, prefix = "tmp-"):
470 """Create a temporary directory.
472 This method may be used by subclasses to create a temporary directory
473 for use in building the final image - e.g. a subclass might create
474 a temporary directory in order to bundle a set of files into a package.
476 The subclass may delete this directory if it wishes, but it will be
477 automatically deleted by cleanup().
479 The absolute path to the temporary directory is returned.
481 Note, this method should only be called after mount() has been called.
483 prefix -- a prefix which should be used when creating the directory;
487 self.__ensure_builddir()
488 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
490 def _mkstemp(self, prefix = "tmp-"):
491 """Create a temporary file.
493 This method may be used by subclasses to create a temporary file
494 for use in building the final image - e.g. a subclass might need
495 a temporary location to unpack a compressed file.
497 The subclass may delete this file if it wishes, but it will be
498 automatically deleted by cleanup().
500 A tuple containing a file descriptor (returned from os.open() and the
501 absolute path to the temporary directory is returned.
503 Note, this method should only be called after mount() has been called.
505 prefix -- a prefix which should be used when creating the file;
509 self.__ensure_builddir()
510 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
512 def _mktemp(self, prefix = "tmp-"):
513 """Create a temporary file.
515 This method simply calls _mkstemp() and closes the returned file
518 The absolute path to the temporary file is returned.
520 Note, this method should only be called after mount() has been called.
522 prefix -- a prefix which should be used when creating the file;
527 (f, path) = self._mkstemp(prefix)
532 # Actual implementation
534 def __ensure_builddir(self):
535 if not self.__builddir is None:
539 self.__builddir = tempfile.mkdtemp(dir = self.tmpdir,
540 prefix = "imgcreate-")
541 except OSError, (err, msg):
542 raise CreatorError("Failed create build directory in %s: %s" %
545 def get_cachedir(self, cachedir = None):
549 self.__ensure_builddir()
551 self.cachedir = cachedir
553 self.cachedir = self.__builddir + "/yum-cache"
554 makedirs(self.cachedir)
557 def __sanity_check(self):
558 """Ensure that the config we've been given is sane."""
559 if not (kickstart.get_packages(self.ks) or
560 kickstart.get_groups(self.ks)):
561 raise CreatorError("No packages or groups specified")
563 kickstart.convert_method_to_repo(self.ks)
565 if not kickstart.get_repos(self.ks):
566 raise CreatorError("No repositories specified")
568 def __write_fstab(self):
569 fstab = open(self._instroot + "/etc/fstab", "w")
570 fstab.write(self._get_fstab())
573 def __create_minimal_dev(self):
574 """Create a minimal /dev so that we don't corrupt the host /dev"""
575 origumask = os.umask(0000)
576 devices = (('null', 1, 3, 0666),
577 ('urandom',1, 9, 0666),
578 ('random', 1, 8, 0666),
579 ('full', 1, 7, 0666),
580 ('ptmx', 5, 2, 0666),
582 ('zero', 1, 5, 0666))
583 links = (("/proc/self/fd", "/dev/fd"),
584 ("/proc/self/fd/0", "/dev/stdin"),
585 ("/proc/self/fd/1", "/dev/stdout"),
586 ("/proc/self/fd/2", "/dev/stderr"))
588 for (node, major, minor, perm) in devices:
589 if not os.path.exists(self._instroot + "/dev/" + node):
590 os.mknod(self._instroot + "/dev/" + node, perm | stat.S_IFCHR, os.makedev(major,minor))
591 for (src, dest) in links:
592 if not os.path.exists(self._instroot + dest):
593 os.symlink(src, self._instroot + dest)
597 def mount(self, base_on = None, cachedir = None):
598 """Setup the target filesystem in preparation for an install.
600 This function sets up the filesystem which the ImageCreator will
601 install into and configure. The ImageCreator class merely creates an
602 install root directory, bind mounts some system directories (e.g. /dev)
603 and writes out /etc/fstab. Other subclasses may also e.g. create a
604 sparse file, format it and loopback mount it to the install root.
606 base_on -- a previous install on which to base this install; defaults
607 to None, causing a new image to be created
609 cachedir -- a directory in which to store the Yum cache; defaults to
610 None, causing a new cache to be created; by setting this
611 to another directory, the same cache can be reused across
615 self.__ensure_builddir()
617 makedirs(self._instroot)
618 makedirs(self._outdir)
620 self._mount_instroot(base_on)
622 for d in ("/dev/pts", "/etc", "/boot", "/var/log", "/var/cache/yum", "/sys", "/proc", "/usr/bin"):
623 makedirs(self._instroot + d)
625 if self.target_arch and self.target_arch.startswith("arm"):
626 self.qemu_emulator = setup_qemu_emulator(self._instroot, self.target_arch)
628 self.get_cachedir(cachedir)
630 # bind mount system directories into _instroot
631 for (f, dest) in [("/sys", None), ("/proc", None), ("/proc/sys/fs/binfmt_misc", None),
633 (self.get_cachedir(), "/var/cache/yum")]:
634 self.__bindmounts.append(BindChrootMount(f, self._instroot, dest))
637 self._do_bindmounts()
639 self.__create_minimal_dev()
641 if os.path.exists(self._instroot + "/etc/mtab"):
642 os.unlink(self._instroot + "/etc/mtab")
643 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
647 # get size of available space in 'instroot' fs
648 self._root_fs_avail = get_filesystem_avail(self._instroot)
651 """Unmounts the target filesystem.
653 The ImageCreator class detaches the system from the install root, but
654 other subclasses may also detach the loopback mounted filesystem image
655 from the install root.
659 os.unlink(self._instroot + "/etc/mtab")
660 if self.qemu_emulator:
661 os.unlink(self._instroot + self.qemu_emulator)
662 """ Clean up yum garbage """
663 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
664 if os.path.exists(instroot_pdir):
665 shutil.rmtree(instroot_pdir, ignore_errors = True)
670 self._undo_bindmounts()
672 self._unmount_instroot()
675 """Unmounts the target filesystem and deletes temporary files.
677 This method calls unmount() and then deletes any temporary files and
678 directories that were created on the host system while building the
681 Note, make sure to call this method once finished with the creator
682 instance in order to ensure no stale files are left on the host e.g.:
684 creator = ImageCreator(ks, name)
691 if not self.__builddir:
696 shutil.rmtree(self.__builddir, ignore_errors = True)
697 self.__builddir = None
699 def __is_excluded_pkg(self, pkg):
700 if pkg in self._excluded_pkgs:
701 self._excluded_pkgs.remove(pkg)
704 for xpkg in self._excluded_pkgs:
705 if xpkg.endswith('*'):
706 if pkg.startswith(xpkg[:-1]):
708 elif xpkg.startswith('*'):
709 if pkg.endswith(xpkg[1:]):
714 def __select_packages(self, pkg_manager):
716 for pkg in self._required_pkgs:
717 e = pkg_manager.selectPackage(pkg)
719 if kickstart.ignore_missing(self.ks):
720 skipped_pkgs.append(pkg)
721 elif self.__is_excluded_pkg(pkg):
722 skipped_pkgs.append(pkg)
724 raise CreatorError("Failed to find package '%s' : %s" %
727 for pkg in skipped_pkgs:
728 logging.warn("Skipping missing package '%s'" % (pkg,))
730 def __select_groups(self, pkg_manager):
732 for group in self._required_groups:
733 e = pkg_manager.selectGroup(group.name, group.include)
735 if kickstart.ignore_missing(self.ks):
736 skipped_groups.append(group)
738 raise CreatorError("Failed to find group '%s' : %s" %
741 for group in skipped_groups:
742 logging.warn("Skipping missing group '%s'" % (group.name,))
744 def __deselect_packages(self, pkg_manager):
745 for pkg in self._excluded_pkgs:
746 pkg_manager.deselectPackage(pkg)
748 def __localinst_packages(self, pkg_manager):
749 for rpm_path in self._get_local_packages():
750 pkg_manager.installLocal(rpm_path)
752 def install(self, repo_urls = {}):
753 """Install packages into the install root.
755 This function installs the packages listed in the supplied kickstart
756 into the install root. By default, the packages are installed from the
757 repository URLs specified in the kickstart.
759 repo_urls -- a dict which maps a repository name to a repository URL;
760 if supplied, this causes any repository URLs specified in
761 the kickstart to be overridden.
766 # initialize pkg list to install
768 self.__sanity_check()
770 self._required_pkgs = \
771 kickstart.get_packages(self.ks, self._get_required_packages())
772 self._excluded_pkgs = \
773 kickstart.get_excluded(self.ks, self._get_excluded_packages())
774 self._required_groups = kickstart.get_groups(self.ks)
776 self._required_pkgs = None
777 self._excluded_pkgs = None
778 self._required_groups = None
780 yum_conf = self._mktemp(prefix = "yum.conf-")
783 if self._include_src:
784 keep_record = 'include_src'
785 if self._recording_pkgs in ('name', 'content'):
786 keep_record = self._recording_pkgs
788 pkg_manager = self.get_pkg_manager(keep_record)
789 pkg_manager.setup(yum_conf, self._instroot)
791 for repo in kickstart.get_repos(self.ks, repo_urls):
792 (name, baseurl, mirrorlist, inc, exc, proxy, proxy_username, proxy_password, debuginfo, source, gpgkey, disable) = repo
794 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy, proxy_username, proxy_password, inc, exc)
796 if kickstart.exclude_docs(self.ks):
797 rpm.addMacro("_excludedocs", "1")
798 rpm.addMacro("__file_context_path", "%{nil}")
799 if kickstart.inst_langs(self.ks) != None:
800 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
804 self.__select_packages(pkg_manager)
805 self.__select_groups(pkg_manager)
806 self.__deselect_packages(pkg_manager)
807 self.__localinst_packages(pkg_manager)
809 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
810 checksize = self._root_fs_avail
812 checksize -= BOOT_SAFEGUARD
814 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
815 pkg_manager.runInstall(checksize)
816 except CreatorError, e:
817 raise CreatorError("%s" % (e,))
820 self._pkgs_content = pkg_manager.getAllContent()
822 pkg_manager.closeRpmDB()
826 # do some clean up to avoid lvm info leakage. this sucks.
827 for subdir in ("cache", "backup", "archive"):
828 lvmdir = self._instroot + "/etc/lvm/" + subdir
830 for f in os.listdir(lvmdir):
831 os.unlink(lvmdir + "/" + f)
835 def __run_post_scripts(self):
836 print "Running scripts"
837 for s in kickstart.get_post_scripts(self.ks):
838 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
839 dir = self._instroot + "/tmp")
841 s.script = s.script.replace("\r", "")
842 os.write(fd, s.script)
846 env = self._get_post_scripts_env(s.inChroot)
849 env["INSTALL_ROOT"] = self._instroot
850 env["IMG_NAME"] = self._name
854 preexec = self._chroot
855 script = "/tmp/" + os.path.basename(path)
859 subprocess.call([s.interp, script],
860 preexec_fn = preexec, env = env, stdout = sys.stdout, stderr = sys.stderr)
861 except OSError, (err, msg):
862 raise CreatorError("Failed to execute %%post script "
863 "with '%s' : %s" % (s.interp, msg))
867 def __save_repo_keys(self, repodata):
870 gpgkeydir = "/etc/pki/rpm-gpg"
871 makedirs(self._instroot + gpgkeydir)
872 for repo in repodata:
874 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
875 shutil.copy(repo["repokey"], self._instroot + repokey)
877 def configure(self, repodata = None):
878 """Configure the system image according to the kickstart.
880 This method applies the (e.g. keyboard or network) configuration
881 specified in the kickstart and executes the kickstart %post scripts.
883 If neccessary, it also prepares the image to be bootable by e.g.
884 creating an initrd and bootloader configuration.
887 ksh = self.ks.handler
890 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
891 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
892 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
893 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
894 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
895 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
896 kickstart.UserConfig(self._instroot).apply(ksh.user)
897 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
898 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
899 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
900 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
901 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
902 self.__save_repo_keys(repodata)
903 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata)
905 print "Failed to apply configuration to image"
908 self._create_bootconfig()
909 self.__run_post_scripts()
911 def launch_shell(self, launch):
912 """Launch a shell in the install root.
914 This method is launches a bash shell chroot()ed in the install root;
915 this can be useful for debugging.
919 print "Launching shell. Exit to continue."
920 print "----------------------------------"
921 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
923 def do_genchecksum(self, image_name):
924 if not self._genchecksum:
927 """ Generate md5sum if /usr/bin/md5sum is available """
928 if os.path.exists("/usr/bin/md5sum"):
929 p = subprocess.Popen(["/usr/bin/md5sum", "-b", image_name],
930 stdout=subprocess.PIPE)
931 (md5sum, errorstr) = p.communicate()
932 if p.returncode != 0:
933 logging.warning("Can't generate md5sum for image %s" % image_name)
935 pattern = re.compile("\*.*$")
936 md5sum = pattern.sub("*" + os.path.basename(image_name), md5sum)
937 fd = open(image_name + ".md5sum", "w")
940 self.outimage.append(image_name+".md5sum")
942 def package(self, destdir = "."):
943 """Prepares the created image for final delivery.
945 In its simplest form, this method merely copies the install root to the
946 supplied destination directory; other subclasses may choose to package
947 the image by e.g. creating a bootable ISO containing the image and
948 bootloader configuration.
950 destdir -- the directory into which the final image should be moved;
951 this defaults to the current directory.
954 self._stage_final_image()
956 if self.__img_compression_method:
957 if not self._img_name:
958 raise CreatorError("Image name not set.")
960 img_location = os.path.join(self._outdir,self._img_name)
961 if self.__img_compression_method == "bz2":
962 bzip2 = find_binary_path('bzip2')
963 print "Compressing %s with bzip2. Please wait..." % img_location
964 rc = subprocess.call([bzip2, "-f", img_location])
966 raise CreatorError("Failed to compress image %s with %s." % (img_location, self.__img_compression_method))
967 for bootimg in glob.glob(os.path.dirname(img_location) + "/*-boot.bin"):
968 print "Compressing %s with bzip2. Please wait..." % bootimg
969 rc = subprocess.call([bzip2, "-f", bootimg])
971 raise CreatorError("Failed to compress image %s with %s." % (bootimg, self.__img_compression_method))
973 if self._recording_pkgs:
974 self._save_recording_pkgs(destdir)
976 """ For image formats with two or multiple image files, it will be better to put them under a directory """
977 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
978 destdir = os.path.join(destdir, "%s-%s" % (self.name, self.image_format))
979 logging.debug("creating destination dir: %s" % destdir)
982 # Ensure all data is flushed to _outdir
983 synccmd = find_binary_path("sync")
984 subprocess.call([synccmd])
986 for f in os.listdir(self._outdir):
987 shutil.move(os.path.join(self._outdir, f),
988 os.path.join(destdir, f))
989 self.outimage.append(os.path.join(destdir, f))
990 self.do_genchecksum(os.path.join(destdir, f))
993 """Install, configure and package an image.
995 This method is a utility method which creates and image by calling some
996 of the other methods in the following order - mount(), install(),
997 configure(), unmount and package().
1000 self.mount(None, self.cachedir)
1002 self.configure(self.repometadata)
1004 self.package(self.destdir)
1006 def print_outimage_info(self):
1007 print "Your new image can be found here:"
1008 self.outimage.sort()
1009 for file in self.outimage:
1010 print os.path.abspath(file)
1012 def check_depend_tools(self):
1013 for tool in self._dep_checks:
1014 find_binary_path(tool)
1016 def package_output(self, image_format, destdir = ".", package="none"):
1017 if not package or package == "none":
1020 destdir = os.path.abspath(os.path.expanduser(destdir))
1021 (pkg, comp) = os.path.splitext(package)
1023 comp=comp.lstrip(".")
1027 dst = "%s/%s-%s.tar.%s" % (destdir, self.name, image_format, comp)
1029 dst = "%s/%s-%s.tar" % (destdir, self.name, image_format)
1030 print "creating %s" % dst
1031 tar = tarfile.open(dst, "w:" + comp)
1033 for file in self.outimage:
1034 print "adding %s to %s" % (file, dst)
1035 tar.add(file, arcname=os.path.join("%s-%s" % (self.name, image_format), os.path.basename(file)))
1036 if os.path.isdir(file):
1037 shutil.rmtree(file, ignore_errors = True)
1044 '''All the file in outimage has been packaged into tar.* file'''
1045 self.outimage = [dst]
1047 def release_output(self, config, destdir, name, release):
1048 self.outimage = create_release(config, destdir, name, self.outimage, release)
1050 def save_kernel(self, destdir):
1051 if not os.path.exists(destdir):
1053 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1054 kernelfilename = "%s/%s-%s" % (destdir, self.name, os.path.basename(kernel))
1055 shutil.copy(kernel, kernelfilename)
1056 self.outimage.append(kernelfilename)
1058 def compress_disk_image(self, compression_method):
1060 With this you can set the method that is used to compress the disk
1061 image after it is created.
1064 if compression_method not in ('bz2'):
1065 raise CreatorError("Given disk image compression method ('%s') is not valid." % (compression_method))
1067 self.__img_compression_method = compression_method
1069 def get_pkg_manager(self, recording_pkgs=None):
1070 return self.pkgmgr(creator = self, recording_pkgs = recording_pkgs)