4 # Copyright (c) 2007 Red Hat Inc.
5 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
7 # This program is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by the Free
9 # Software Foundation; version 2 of the License
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc., 59
18 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 from __future__ import with_statement
32 from mic import kickstart
34 from mic.utils.errors import CreatorError, Abort
35 from mic.utils import misc, rpmmisc, runner, fs_related as fs
37 class BaseImageCreator(object):
38 """Installs a system to a chroot directory.
40 ImageCreator is the simplest creator class available; it will install and
41 configure a system image according to the supplied kickstart file.
45 import mic.imgcreate as imgcreate
46 ks = imgcreate.read_kickstart("foo.ks")
47 imgcreate.ImageCreator(ks, "foo").create()
54 def __init__(self, createopts = None, pkgmgr = None):
55 """Initialize an ImageCreator instance.
57 ks -- a pykickstart.KickstartParser instance; this instance will be
58 used to drive the install by e.g. providing the list of packages
59 to be installed, the system configuration and %post scripts
61 name -- a name for the image; used for e.g. image filenames or
67 self.__builddir = None
68 self.__bindmounts = []
72 self.tmpdir = "/var/tmp/mic"
73 self.cachedir = "/var/tmp/mic/cache"
75 self.target_arch = "noarch"
76 self._local_pkgs_path = None
77 # If the kernel is save to the destdir when copy_kernel cmd is called.
78 self._copy_kernel = False
80 # Eeach image type can change these values as they might be image type
82 if not hasattr(self, "_valid_compression_methods"):
83 self._valid_compression_methods = ['bz2']
85 # The compression method for disk image.
86 self._img_compression_method = None
89 # Mapping table for variables that have different names.
90 optmap = {"pkgmgr" : "pkgmgr_name",
92 "arch" : "target_arch",
93 "local_pkgs_path" : "_local_pkgs_path",
94 "compress_disk_image" : "_img_compression_method",
95 "copy_kernel" : "_copy_kernel",
98 # update setting from createopts
99 for key in createopts.keys():
104 setattr(self, option, createopts[key])
106 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
108 if 'release' in createopts and createopts['release']:
109 self.name += '-' + createopts['release']
111 if os.path.exists(self.destdir):
112 if msger.ask("Image dir: %s already exists, cleanup and" \
113 "continue?" % self.destdir):
114 shutil.rmtree(self.destdir, ignore_errors = True)
116 raise Abort("Canceled")
118 # pending FEA: save log by default for --release
120 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe", "passwd"]
122 # Output image file names
125 # A flag to generate checksum
126 self._genchecksum = False
128 self._alt_initrd_name = None
130 self._recording_pkgs = []
132 # available size in root fs, init to 0
133 self._root_fs_avail = 0
135 # Name of the disk image file that is created.
136 self._img_name = None
138 self.image_format = None
140 # Save qemu emulator file name in order to clean up it finally
141 self.qemu_emulator = None
143 # No ks provided when called by convertor, so skip the dependency check
145 # If we have btrfs partition we need to check that we have toosl for those
146 for part in self.ks.handler.partition.partitions:
147 if part.fstype and part.fstype == "btrfs":
148 self._dep_checks.append("mkfs.btrfs")
151 if self.target_arch and self.target_arch.startswith("arm"):
152 for dep in self._dep_checks:
153 if dep == "extlinux":
154 self._dep_checks.remove(dep)
156 if not os.path.exists("/usr/bin/qemu-arm") or \
157 not misc.is_statically_linked("/usr/bin/qemu-arm"):
158 self._dep_checks.append("qemu-arm-static")
160 if os.path.exists("/proc/sys/vm/vdso_enabled"):
161 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
162 vdso_value = vdso_fh.read().strip()
164 if (int)(vdso_value) == 1:
165 msger.warning("vdso is enabled on your host, which might "
166 "cause problems with arm emulations.\n"
167 "\tYou can disable vdso with following command before "
168 "starting image build:\n"
169 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
171 # make sure the specified tmpdir and cachedir exist
172 if not os.path.exists(self.tmpdir):
173 os.makedirs(self.tmpdir)
174 if not os.path.exists(self.cachedir):
175 os.makedirs(self.cachedir)
177 if self._img_compression_method != None and \
178 self._img_compression_method not in self._valid_compression_methods:
179 raise CreatorError("Given disk image compression method ('%s') is "
180 "not valid. Valid values are '%s'." \
181 % (self._img_compression_method,
182 ' '.join(self._valid_compression_methods)))
188 def __get_instroot(self):
189 if self.__builddir is None:
190 raise CreatorError("_instroot is not valid before calling mount()")
191 return self.__builddir + "/install_root"
192 _instroot = property(__get_instroot)
193 """The location of the install root directory.
195 This is the directory into which the system is installed. Subclasses may
196 mount a filesystem image here or copy files to/from here.
198 Note, this directory does not exist before ImageCreator.mount() is called.
200 Note also, this is a read-only attribute.
204 def __get_outdir(self):
205 if self.__builddir is None:
206 raise CreatorError("_outdir is not valid before calling mount()")
207 return self.__builddir + "/out"
208 _outdir = property(__get_outdir)
209 """The staging location for the final image.
211 This is where subclasses should stage any files that are part of the final
212 image. ImageCreator.package() will copy any files found here into the
213 requested destination directory.
215 Note, this directory does not exist before ImageCreator.mount() is called.
217 Note also, this is a read-only attribute.
223 # Hooks for subclasses
225 def _mount_instroot(self, base_on = None):
226 """Mount or prepare the install root directory.
228 This is the hook where subclasses may prepare the install root by e.g.
229 mounting creating and loopback mounting a filesystem image to
232 There is no default implementation.
234 base_on -- this is the value passed to mount() and can be interpreted
235 as the subclass wishes; it might e.g. be the location of
236 a previously created ISO containing a system image.
241 def _unmount_instroot(self):
242 """Undo anything performed in _mount_instroot().
244 This is the hook where subclasses must undo anything which was done
245 in _mount_instroot(). For example, if a filesystem image was mounted
246 onto _instroot, it should be unmounted here.
248 There is no default implementation.
253 def _create_bootconfig(self):
254 """Configure the image so that it's bootable.
256 This is the hook where subclasses may prepare the image for booting by
257 e.g. creating an initramfs and bootloader configuration.
259 This hook is called while the install root is still mounted, after the
260 packages have been installed and the kickstart configuration has been
261 applied, but before the %post scripts have been executed.
263 There is no default implementation.
268 def _stage_final_image(self):
269 """Stage the final system image in _outdir.
271 This is the hook where subclasses should place the image in _outdir
272 so that package() can copy it to the requested destination directory.
274 By default, this moves the install root into _outdir.
277 shutil.move(self._instroot, self._outdir + "/" + self.name)
279 def get_installed_packages(self):
280 return self._pkgs_content.keys()
282 def _save_recording_pkgs(self, destdir):
283 """Save the list or content of installed packages to file.
285 pkgs = self._pkgs_content.keys()
286 pkgs.sort() # inplace op
288 if not os.path.exists(destdir):
290 if 'name' in self._recording_pkgs :
291 namefile = os.path.join(destdir, self.name + '.packages')
292 f = open(namefile, "w")
293 content = '\n'.join(pkgs)
296 self.outimage.append(namefile);
298 # if 'content', save more details
299 if 'content' in self._recording_pkgs :
300 contfile = os.path.join(destdir, self.name + '.files')
301 f = open(contfile, "w")
306 pkgcont = self._pkgs_content[pkg]
308 if pkgcont.has_key('dir'):
309 items = map(lambda x:x+'/', pkgcont['dir'])
310 if pkgcont.has_key('file'):
311 items.extend(pkgcont['file'])
315 content += '\n '.join(items)
321 self.outimage.append(contfile)
323 if 'license' in self._recording_pkgs:
324 licensefile = os.path.join(destdir, self.name + '.license')
325 f = open(licensefile, "w")
327 f.write('Summary:\n')
328 for license in reversed(sorted(self._pkgs_license, key=\
329 lambda license: len(self._pkgs_license[license]))):
330 f.write(" - %s: %s\n" \
331 % (license, len(self._pkgs_license[license])))
333 f.write('\nDetails:\n')
334 for license in reversed(sorted(self._pkgs_license, key=\
335 lambda license: len(self._pkgs_license[license]))):
336 f.write(" - %s:\n" % (license))
337 for pkg in sorted(self._pkgs_license[license]):
338 f.write(" - %s\n" % (pkg))
342 self.outimage.append(licensefile);
344 def _get_required_packages(self):
345 """Return a list of required packages.
347 This is the hook where subclasses may specify a set of packages which
348 it requires to be installed.
350 This returns an empty list by default.
352 Note, subclasses should usually chain up to the base class
353 implementation of this hook.
358 def _get_excluded_packages(self):
359 """Return a list of excluded packages.
361 This is the hook where subclasses may specify a set of packages which
362 it requires _not_ to be installed.
364 This returns an empty list by default.
366 Note, subclasses should usually chain up to the base class
367 implementation of this hook.
372 def _get_local_packages(self):
373 """Return a list of rpm path to be local installed.
375 This is the hook where subclasses may specify a set of rpms which
376 it requires to be installed locally.
378 This returns an empty list by default.
380 Note, subclasses should usually chain up to the base class
381 implementation of this hook.
384 if self._local_pkgs_path:
385 if os.path.isdir(self._local_pkgs_path):
387 os.path.join(self._local_pkgs_path, '*.rpm'))
388 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
389 return [self._local_pkgs_path]
393 def _get_fstab(self):
394 """Return the desired contents of /etc/fstab.
396 This is the hook where subclasses may specify the contents of
397 /etc/fstab by returning a string containing the desired contents.
399 A sensible default implementation is provided.
402 s = "/dev/root / %s %s 0 0\n" \
404 "defaults,noatime" if not self._fsopts else self._fsopts)
405 s += self._get_fstab_special()
408 def _get_fstab_special(self):
409 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
410 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
411 s += "proc /proc proc defaults 0 0\n"
412 s += "sysfs /sys sysfs defaults 0 0\n"
415 def _get_post_scripts_env(self, in_chroot):
416 """Return an environment dict for %post scripts.
418 This is the hook where subclasses may specify some environment
419 variables for %post scripts by return a dict containing the desired
422 By default, this returns an empty dict.
424 in_chroot -- whether this %post script is to be executed chroot()ed
430 def __get_imgname(self):
432 _name = property(__get_imgname)
433 """The name of the image file.
437 def _get_kernel_versions(self):
438 """Return a dict detailing the available kernel types/versions.
440 This is the hook where subclasses may override what kernel types and
441 versions should be available for e.g. creating the booloader
444 A dict should be returned mapping the available kernel types to a list
445 of the available versions for those kernels.
447 The default implementation uses rpm to iterate over everything
448 providing 'kernel', finds /boot/vmlinuz-* and returns the version
449 obtained from the vmlinuz filename. (This can differ from the kernel
450 RPM's n-v-r in the case of e.g. xen)
453 def get_kernel_versions(instroot):
456 files = glob.glob(instroot + "/boot/vmlinuz-*")
458 version = os.path.basename(file)[8:]
461 versions.add(version)
462 ret["kernel"] = list(versions)
465 def get_version(header):
467 for f in header['filenames']:
468 if f.startswith('/boot/vmlinuz-'):
473 return get_kernel_versions(self._instroot)
475 ts = rpm.TransactionSet(self._instroot)
478 for header in ts.dbMatch('provides', 'kernel'):
479 version = get_version(header)
483 name = header['name']
485 ret[name] = [version]
486 elif not version in ret[name]:
487 ret[name].append(version)
493 # Helpers for subclasses
495 def _do_bindmounts(self):
496 """Mount various system directories onto _instroot.
498 This method is called by mount(), but may also be used by subclasses
499 in order to re-mount the bindmounts after modifying the underlying
503 for b in self.__bindmounts:
506 def _undo_bindmounts(self):
507 """Unmount the bind-mounted system directories from _instroot.
509 This method is usually only called by unmount(), but may also be used
510 by subclasses in order to gain access to the filesystem obscured by
511 the bindmounts - e.g. in order to create device nodes on the image
515 self.__bindmounts.reverse()
516 for b in self.__bindmounts:
520 """Chroot into the install root.
522 This method may be used by subclasses when executing programs inside
523 the install root e.g.
525 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
528 os.chroot(self._instroot)
531 def _mkdtemp(self, prefix = "tmp-"):
532 """Create a temporary directory.
534 This method may be used by subclasses to create a temporary directory
535 for use in building the final image - e.g. a subclass might create
536 a temporary directory in order to bundle a set of files into a package.
538 The subclass may delete this directory if it wishes, but it will be
539 automatically deleted by cleanup().
541 The absolute path to the temporary directory is returned.
543 Note, this method should only be called after mount() has been called.
545 prefix -- a prefix which should be used when creating the directory;
549 self.__ensure_builddir()
550 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
552 def _mkstemp(self, prefix = "tmp-"):
553 """Create a temporary file.
555 This method may be used by subclasses to create a temporary file
556 for use in building the final image - e.g. a subclass might need
557 a temporary location to unpack a compressed file.
559 The subclass may delete this file if it wishes, but it will be
560 automatically deleted by cleanup().
562 A tuple containing a file descriptor (returned from os.open() and the
563 absolute path to the temporary directory is returned.
565 Note, this method should only be called after mount() has been called.
567 prefix -- a prefix which should be used when creating the file;
571 self.__ensure_builddir()
572 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
574 def _mktemp(self, prefix = "tmp-"):
575 """Create a temporary file.
577 This method simply calls _mkstemp() and closes the returned file
580 The absolute path to the temporary file is returned.
582 Note, this method should only be called after mount() has been called.
584 prefix -- a prefix which should be used when creating the file;
589 (f, path) = self._mkstemp(prefix)
595 # Actual implementation
597 def __ensure_builddir(self):
598 if not self.__builddir is None:
602 self.__builddir = tempfile.mkdtemp(dir = self.tmpdir,
603 prefix = "imgcreate-")
604 except OSError, (err, msg):
605 raise CreatorError("Failed create build directory in %s: %s" %
608 def get_cachedir(self, cachedir = None):
612 self.__ensure_builddir()
614 self.cachedir = cachedir
616 self.cachedir = self.__builddir + "/yum-cache"
617 fs.makedirs(self.cachedir)
620 def __sanity_check(self):
621 """Ensure that the config we've been given is sane."""
622 if not (kickstart.get_packages(self.ks) or
623 kickstart.get_groups(self.ks)):
624 raise CreatorError("No packages or groups specified")
626 kickstart.convert_method_to_repo(self.ks)
628 if not kickstart.get_repos(self.ks):
629 raise CreatorError("No repositories specified")
631 def __write_fstab(self):
632 fstab = open(self._instroot + "/etc/fstab", "w")
633 fstab.write(self._get_fstab())
636 def __create_minimal_dev(self):
637 """Create a minimal /dev so that we don't corrupt the host /dev"""
638 origumask = os.umask(0000)
639 devices = (('null', 1, 3, 0666),
640 ('urandom',1, 9, 0666),
641 ('random', 1, 8, 0666),
642 ('full', 1, 7, 0666),
643 ('ptmx', 5, 2, 0666),
645 ('zero', 1, 5, 0666))
647 links = (("/proc/self/fd", "/dev/fd"),
648 ("/proc/self/fd/0", "/dev/stdin"),
649 ("/proc/self/fd/1", "/dev/stdout"),
650 ("/proc/self/fd/2", "/dev/stderr"))
652 for (node, major, minor, perm) in devices:
653 if not os.path.exists(self._instroot + "/dev/" + node):
654 os.mknod(self._instroot + "/dev/" + node,
656 os.makedev(major,minor))
658 for (src, dest) in links:
659 if not os.path.exists(self._instroot + dest):
660 os.symlink(src, self._instroot + dest)
664 def mount(self, base_on = None, cachedir = None):
665 """Setup the target filesystem in preparation for an install.
667 This function sets up the filesystem which the ImageCreator will
668 install into and configure. The ImageCreator class merely creates an
669 install root directory, bind mounts some system directories (e.g. /dev)
670 and writes out /etc/fstab. Other subclasses may also e.g. create a
671 sparse file, format it and loopback mount it to the install root.
673 base_on -- a previous install on which to base this install; defaults
674 to None, causing a new image to be created
676 cachedir -- a directory in which to store the Yum cache; defaults to
677 None, causing a new cache to be created; by setting this
678 to another directory, the same cache can be reused across
682 self.__ensure_builddir()
684 fs.makedirs(self._instroot)
685 fs.makedirs(self._outdir)
687 self._mount_instroot(base_on)
689 for d in ("/dev/pts",
697 fs.makedirs(self._instroot + d)
699 if self.target_arch and self.target_arch.startswith("arm"):
700 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
703 self.get_cachedir(cachedir)
705 # bind mount system directories into _instroot
706 for (f, dest) in [("/sys", None),
708 ("/proc/sys/fs/binfmt_misc", None),
710 (self.get_cachedir(), "/var/cache/yum")]:
711 self.__bindmounts.append(fs.BindChrootMount(f, self._instroot, dest))
713 self._do_bindmounts()
715 self.__create_minimal_dev()
717 if os.path.exists(self._instroot + "/etc/mtab"):
718 os.unlink(self._instroot + "/etc/mtab")
719 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
723 # get size of available space in 'instroot' fs
724 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
727 """Unmounts the target filesystem.
729 The ImageCreator class detaches the system from the install root, but
730 other subclasses may also detach the loopback mounted filesystem image
731 from the install root.
735 mtab = self._instroot + "/etc/mtab"
736 if not os.path.islink(mtab):
737 os.unlink(self._instroot + "/etc/mtab")
739 if self.qemu_emulator:
740 os.unlink(self._instroot + self.qemu_emulator)
744 self._undo_bindmounts()
746 """ Clean up yum garbage """
748 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
749 if os.path.exists(instroot_pdir):
750 shutil.rmtree(instroot_pdir, ignore_errors = True)
751 yumcachedir = self._instroot + "/var/cache/yum"
752 if os.path.exists(yumcachedir):
753 shutil.rmtree(yumcachedir, ignore_errors = True)
754 yumlibdir = self._instroot + "/var/lib/yum"
755 if os.path.exists(yumlibdir):
756 shutil.rmtree(yumlibdir, ignore_errors = True)
760 self._unmount_instroot()
763 """Unmounts the target filesystem and deletes temporary files.
765 This method calls unmount() and then deletes any temporary files and
766 directories that were created on the host system while building the
769 Note, make sure to call this method once finished with the creator
770 instance in order to ensure no stale files are left on the host e.g.:
772 creator = ImageCreator(ks, name)
779 if not self.__builddir:
784 shutil.rmtree(self.__builddir, ignore_errors = True)
785 self.__builddir = None
787 def __is_excluded_pkg(self, pkg):
788 if pkg in self._excluded_pkgs:
789 self._excluded_pkgs.remove(pkg)
792 for xpkg in self._excluded_pkgs:
793 if xpkg.endswith('*'):
794 if pkg.startswith(xpkg[:-1]):
796 elif xpkg.startswith('*'):
797 if pkg.endswith(xpkg[1:]):
802 def __select_packages(self, pkg_manager):
804 for pkg in self._required_pkgs:
805 e = pkg_manager.selectPackage(pkg)
807 if kickstart.ignore_missing(self.ks):
808 skipped_pkgs.append(pkg)
809 elif self.__is_excluded_pkg(pkg):
810 skipped_pkgs.append(pkg)
812 raise CreatorError("Failed to find package '%s' : %s" %
815 for pkg in skipped_pkgs:
816 msger.warning("Skipping missing package '%s'" % (pkg,))
818 def __select_groups(self, pkg_manager):
820 for group in self._required_groups:
821 e = pkg_manager.selectGroup(group.name, group.include)
823 if kickstart.ignore_missing(self.ks):
824 skipped_groups.append(group)
826 raise CreatorError("Failed to find group '%s' : %s" %
829 for group in skipped_groups:
830 msger.warning("Skipping missing group '%s'" % (group.name,))
832 def __deselect_packages(self, pkg_manager):
833 for pkg in self._excluded_pkgs:
834 pkg_manager.deselectPackage(pkg)
836 def __localinst_packages(self, pkg_manager):
837 for rpm_path in self._get_local_packages():
838 pkg_manager.installLocal(rpm_path)
840 def install(self, repo_urls = {}):
841 """Install packages into the install root.
843 This function installs the packages listed in the supplied kickstart
844 into the install root. By default, the packages are installed from the
845 repository URLs specified in the kickstart.
847 repo_urls -- a dict which maps a repository name to a repository URL;
848 if supplied, this causes any repository URLs specified in
849 the kickstart to be overridden.
853 # initialize pkg list to install
855 self.__sanity_check()
857 self._required_pkgs = \
858 kickstart.get_packages(self.ks, self._get_required_packages())
859 self._excluded_pkgs = \
860 kickstart.get_excluded(self.ks, self._get_excluded_packages())
861 self._required_groups = kickstart.get_groups(self.ks)
863 self._required_pkgs = None
864 self._excluded_pkgs = None
865 self._required_groups = None
867 yum_conf = self._mktemp(prefix = "yum.conf-")
869 pkg_manager = self.get_pkg_manager()
870 pkg_manager.setup(yum_conf, self._instroot)
872 for repo in kickstart.get_repos(self.ks, repo_urls):
873 (name, baseurl, mirrorlist, inc, exc,
874 proxy, proxy_username, proxy_password, debuginfo,
875 source, gpgkey, disable, ssl_verify, cost, priority) = repo
877 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
878 proxy_username, proxy_password, inc, exc, ssl_verify,
881 if kickstart.exclude_docs(self.ks):
882 rpm.addMacro("_excludedocs", "1")
883 rpm.addMacro("_dbpath", "/var/lib/rpm")
884 rpm.addMacro("__file_context_path", "%{nil}")
885 if kickstart.inst_langs(self.ks) != None:
886 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
890 self.__select_packages(pkg_manager)
891 self.__select_groups(pkg_manager)
892 self.__deselect_packages(pkg_manager)
893 self.__localinst_packages(pkg_manager)
895 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
896 checksize = self._root_fs_avail
898 checksize -= BOOT_SAFEGUARD
900 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
901 pkg_manager.runInstall(checksize)
902 except CreatorError, e:
905 self._pkgs_content = pkg_manager.getAllContent()
906 self._pkgs_license = pkg_manager.getPkgsLicense()
908 pkg_manager.closeRpmDB()
912 # do some clean up to avoid lvm info leakage. this sucks.
913 for subdir in ("cache", "backup", "archive"):
914 lvmdir = self._instroot + "/etc/lvm/" + subdir
916 for f in os.listdir(lvmdir):
917 os.unlink(lvmdir + "/" + f)
921 def __run_post_scripts(self):
922 msger.info("Running scripts ...")
923 if os.path.exists(self._instroot + "/tmp"):
924 shutil.rmtree(self._instroot + "/tmp")
925 os.mkdir (self._instroot + "/tmp", 0755)
926 for s in kickstart.get_post_scripts(self.ks):
927 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
928 dir = self._instroot + "/tmp")
930 s.script = s.script.replace("\r", "")
931 os.write(fd, s.script)
935 env = self._get_post_scripts_env(s.inChroot)
938 env["INSTALL_ROOT"] = self._instroot
939 env["IMG_NAME"] = self._name
943 preexec = self._chroot
944 script = "/tmp/" + os.path.basename(path)
948 subprocess.call([s.interp, script],
949 preexec_fn = preexec,
953 except OSError, (err, msg):
954 raise CreatorError("Failed to execute %%post script "
955 "with '%s' : %s" % (s.interp, msg))
959 def __save_repo_keys(self, repodata):
963 gpgkeydir = "/etc/pki/rpm-gpg"
964 fs.makedirs(self._instroot + gpgkeydir)
965 for repo in repodata:
967 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
968 shutil.copy(repo["repokey"], self._instroot + repokey)
970 def configure(self, repodata = None):
971 """Configure the system image according to the kickstart.
973 This method applies the (e.g. keyboard or network) configuration
974 specified in the kickstart and executes the kickstart %post scripts.
976 If necessary, it also prepares the image to be bootable by e.g.
977 creating an initrd and bootloader configuration.
980 ksh = self.ks.handler
982 msger.info('Applying configurations ...')
984 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
985 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
986 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
987 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
988 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
989 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
990 kickstart.UserConfig(self._instroot).apply(ksh.user)
991 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
992 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
993 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
994 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
995 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
996 self.__save_repo_keys(repodata)
997 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata)
999 msger.warning("Failed to apply configuration to image")
1002 self._create_bootconfig()
1003 self.__run_post_scripts()
1005 def launch_shell(self, launch):
1006 """Launch a shell in the install root.
1008 This method is launches a bash shell chroot()ed in the install root;
1009 this can be useful for debugging.
1013 msger.info("Launching shell. Exit to continue.")
1014 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1016 def do_genchecksum(self, image_name):
1017 if not self._genchecksum:
1020 md5sum = misc.get_md5sum(image_name)
1021 with open(image_name + ".md5sum", "w") as f:
1022 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1023 self.outimage.append(image_name+".md5sum")
1025 def package(self, destdir = "."):
1026 """Prepares the created image for final delivery.
1028 In its simplest form, this method merely copies the install root to the
1029 supplied destination directory; other subclasses may choose to package
1030 the image by e.g. creating a bootable ISO containing the image and
1031 bootloader configuration.
1033 destdir -- the directory into which the final image should be moved;
1034 this defaults to the current directory.
1037 self._stage_final_image()
1039 if not os.path.exists(destdir):
1040 fs.makedirs(destdir)
1041 if self._img_compression_method:
1042 if not self._img_name:
1043 raise CreatorError("Image name not set.")
1045 img_location = os.path.join(self._outdir,self._img_name)
1046 if self._img_compression_method == "bz2":
1047 bzip2 = fs.find_binary_path('bzip2')
1048 msger.info("Compressing %s with bzip2. Please wait..." \
1050 rc = runner.show([bzip2, "-f", img_location])
1052 raise CreatorError("Failed to compress image %s with %s." \
1053 % (img_location, self._img_compression_method))
1055 for bootimg in glob.glob(os.path.dirname(img_location) + \
1057 msger.info("Compressing %s with bzip2. Please wait..." \
1059 rc = runner.show([bzip2, "-f", bootimg])
1061 raise CreatorError("Failed to compress image %s with "
1064 self._img_compression_method))
1066 if self._recording_pkgs:
1067 self._save_recording_pkgs(destdir)
1069 # For image formats with two or multiple image files, it will be
1070 # better to put them under a directory
1071 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1072 destdir = os.path.join(destdir, "%s-%s" \
1073 % (self.name, self.image_format))
1074 msger.debug("creating destination dir: %s" % destdir)
1075 fs.makedirs(destdir)
1077 # Ensure all data is flushed to _outdir
1078 runner.quiet('sync')
1080 for f in os.listdir(self._outdir):
1081 shutil.move(os.path.join(self._outdir, f),
1082 os.path.join(destdir, f))
1083 self.outimage.append(os.path.join(destdir, f))
1084 self.do_genchecksum(os.path.join(destdir, f))
1086 def print_outimage_info(self):
1087 msg = "The new image can be found here:\n"
1088 self.outimage.sort()
1089 for file in self.outimage:
1090 msg += ' %s\n' % os.path.abspath(file)
1094 def check_depend_tools(self):
1095 for tool in self._dep_checks:
1096 fs.find_binary_path(tool)
1098 def package_output(self, image_format, destdir = ".", package="none"):
1099 if not package or package == "none":
1102 destdir = os.path.abspath(os.path.expanduser(destdir))
1103 (pkg, comp) = os.path.splitext(package)
1105 comp=comp.lstrip(".")
1109 dst = "%s/%s-%s.tar.%s" % (destdir, self.name, image_format, comp)
1111 dst = "%s/%s-%s.tar" % (destdir, self.name, image_format)
1112 msger.info("creating %s" % dst)
1113 tar = tarfile.open(dst, "w:" + comp)
1115 for file in self.outimage:
1116 msger.info("adding %s to %s" % (file, dst))
1118 arcname=os.path.join("%s-%s" \
1119 % (self.name, image_format),
1120 os.path.basename(file)))
1121 if os.path.isdir(file):
1122 shutil.rmtree(file, ignore_errors = True)
1128 '''All the file in outimage has been packaged into tar.* file'''
1129 self.outimage = [dst]
1131 def release_output(self, config, destdir, release):
1132 """ Create release directory and files
1136 """ release path """
1137 return os.path.join(destdir, fn)
1139 outimages = self.outimage
1142 new_kspath = _rpath(self.name+'.ks')
1143 with open(config) as fr:
1144 with open(new_kspath, "w") as wf:
1145 # When building a release we want to make sure the .ks
1146 # file generates the same build even when --release= is not used.
1147 wf.write(fr.read().replace("@BUILD_ID@", release))
1148 outimages.append(new_kspath)
1150 # rename iso and usbimg
1151 for f in os.listdir(destdir):
1152 if f.endswith(".iso"):
1153 newf = f[:-4] + '.img'
1154 elif f.endswith(".usbimg"):
1155 newf = f[:-7] + '.img'
1158 os.rename(_rpath(f), _rpath(newf))
1159 outimages.append(_rpath(newf))
1162 with open(_rpath("MANIFEST"), "w") as wf:
1163 for f in os.listdir(destdir):
1167 if os.path.isdir(os.path.join(destdir, f)):
1170 md5sum = misc.get_md5sum(_rpath(f))
1171 wf.write("%s %s\n" % (md5sum, f))
1173 outimages.append("%s/MANIFEST" % destdir)
1175 # Filter out the nonexist file
1176 for fp in outimages[:]:
1177 if not os.path.exists("%s" % fp):
1178 outimages.remove(fp)
1180 def copy_kernel(self):
1181 """ Copy kernel files to the outimage directory.
1183 NOTE: This needs to be called before unmounting the instroot.
1186 if not self._copy_kernel:
1188 if not os.path.exists(self.destdir):
1189 os.makedirs(self.destdir)
1190 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1191 kernelfilename = "%s/%s-%s" % (self.destdir, self.name, os.path.basename(kernel))
1192 shutil.copy(kernel, kernelfilename)
1193 self.outimage.append(kernelfilename)
1195 def get_pkg_manager(self):
1196 return self.pkgmgr(target_arch = self.target_arch, instroot = self._instroot, cachedir = self.cachedir)