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
79 # If the kernel is save to the destdir when copy_kernel cmd is called.
80 self._need_copy_kernel = False
83 # Mapping table for variables that have different names.
84 optmap = {"pkgmgr" : "pkgmgr_name",
86 "arch" : "target_arch",
87 "local_pkgs_path" : "_local_pkgs_path",
88 "copy_kernel" : "_need_copy_kernel",
91 # update setting from createopts
92 for key in createopts.keys():
97 setattr(self, option, createopts[key])
99 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
101 if 'release' in createopts and createopts['release']:
102 self.name += '-' + createopts['release']
105 if '@NAME@' in self.pack_to:
106 self.pack_to = self.pack_to.replace('@NAME@', self.name)
107 (tar, ext) = os.path.splitext(self.pack_to)
108 if ext in (".gz", ".bz2") and tar.endswith(".tar"):
110 if ext not in misc.pack_formats:
111 self.pack_to += ".tar"
113 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe", "passwd"]
115 # Output image file names
118 # A flag to generate checksum
119 self._genchecksum = False
121 self._alt_initrd_name = None
123 self._recording_pkgs = []
125 # available size in root fs, init to 0
126 self._root_fs_avail = 0
128 # Name of the disk image file that is created.
129 self._img_name = None
131 self.image_format = None
133 # Save qemu emulator file name in order to clean up it finally
134 self.qemu_emulator = None
136 # No ks provided when called by convertor, so skip the dependency check
138 # If we have btrfs partition we need to check that we have toosl for those
139 for part in self.ks.handler.partition.partitions:
140 if part.fstype and part.fstype == "btrfs":
141 self._dep_checks.append("mkfs.btrfs")
144 if self.target_arch and self.target_arch.startswith("arm"):
145 for dep in self._dep_checks:
146 if dep == "extlinux":
147 self._dep_checks.remove(dep)
149 if not os.path.exists("/usr/bin/qemu-arm") or \
150 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 "
159 "cause problems with arm emulations.\n"
160 "\tYou can disable vdso with following command before "
161 "starting image build:\n"
162 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
164 # make sure the specified tmpdir and cachedir exist
165 if not os.path.exists(self.tmpdir):
166 os.makedirs(self.tmpdir)
167 if not os.path.exists(self.cachedir):
168 os.makedirs(self.cachedir)
174 def __get_instroot(self):
175 if self.__builddir is None:
176 raise CreatorError("_instroot is not valid before calling mount()")
177 return self.__builddir + "/install_root"
178 _instroot = property(__get_instroot)
179 """The location of the install root directory.
181 This is the directory into which the system is installed. Subclasses may
182 mount a filesystem image here or copy files to/from here.
184 Note, this directory does not exist before ImageCreator.mount() is called.
186 Note also, this is a read-only attribute.
190 def __get_outdir(self):
191 if self.__builddir is None:
192 raise CreatorError("_outdir is not valid before calling mount()")
193 return self.__builddir + "/out"
194 _outdir = property(__get_outdir)
195 """The staging location for the final image.
197 This is where subclasses should stage any files that are part of the final
198 image. ImageCreator.package() will copy any files found here into the
199 requested destination directory.
201 Note, this directory does not exist before ImageCreator.mount() is called.
203 Note also, this is a read-only attribute.
209 # Hooks for subclasses
211 def _mount_instroot(self, base_on = None):
212 """Mount or prepare the install root directory.
214 This is the hook where subclasses may prepare the install root by e.g.
215 mounting creating and loopback mounting a filesystem image to
218 There is no default implementation.
220 base_on -- this is the value passed to mount() and can be interpreted
221 as the subclass wishes; it might e.g. be the location of
222 a previously created ISO containing a system image.
227 def _unmount_instroot(self):
228 """Undo anything performed in _mount_instroot().
230 This is the hook where subclasses must undo anything which was done
231 in _mount_instroot(). For example, if a filesystem image was mounted
232 onto _instroot, it should be unmounted here.
234 There is no default implementation.
239 def _create_bootconfig(self):
240 """Configure the image so that it's bootable.
242 This is the hook where subclasses may prepare the image for booting by
243 e.g. creating an initramfs and bootloader configuration.
245 This hook is called while the install root is still mounted, after the
246 packages have been installed and the kickstart configuration has been
247 applied, but before the %post scripts have been executed.
249 There is no default implementation.
254 def _stage_final_image(self):
255 """Stage the final system image in _outdir.
257 This is the hook where subclasses should place the image in _outdir
258 so that package() can copy it to the requested destination directory.
260 By default, this moves the install root into _outdir.
263 shutil.move(self._instroot, self._outdir + "/" + self.name)
265 def get_installed_packages(self):
266 return self._pkgs_content.keys()
268 def _save_recording_pkgs(self, destdir):
269 """Save the list or content of installed packages to file.
271 pkgs = self._pkgs_content.keys()
272 pkgs.sort() # inplace op
274 if not os.path.exists(destdir):
276 if 'name' in self._recording_pkgs :
277 namefile = os.path.join(destdir, self.name + '.packages')
278 f = open(namefile, "w")
279 content = '\n'.join(pkgs)
282 self.outimage.append(namefile);
284 # if 'content', save more details
285 if 'content' in self._recording_pkgs :
286 contfile = os.path.join(destdir, self.name + '.files')
287 f = open(contfile, "w")
292 pkgcont = self._pkgs_content[pkg]
294 if pkgcont.has_key('dir'):
295 items = map(lambda x:x+'/', pkgcont['dir'])
296 if pkgcont.has_key('file'):
297 items.extend(pkgcont['file'])
301 content += '\n '.join(items)
307 self.outimage.append(contfile)
309 if 'license' in self._recording_pkgs:
310 licensefile = os.path.join(destdir, self.name + '.license')
311 f = open(licensefile, "w")
313 f.write('Summary:\n')
314 for license in reversed(sorted(self._pkgs_license, key=\
315 lambda license: len(self._pkgs_license[license]))):
316 f.write(" - %s: %s\n" \
317 % (license, len(self._pkgs_license[license])))
319 f.write('\nDetails:\n')
320 for license in reversed(sorted(self._pkgs_license, key=\
321 lambda license: len(self._pkgs_license[license]))):
322 f.write(" - %s:\n" % (license))
323 for pkg in sorted(self._pkgs_license[license]):
324 f.write(" - %s\n" % (pkg))
328 self.outimage.append(licensefile);
330 def _get_required_packages(self):
331 """Return a list of required packages.
333 This is the hook where subclasses may specify a set of packages which
334 it requires to be installed.
336 This returns an empty list by default.
338 Note, subclasses should usually chain up to the base class
339 implementation of this hook.
344 def _get_excluded_packages(self):
345 """Return a list of excluded packages.
347 This is the hook where subclasses may specify a set of packages which
348 it requires _not_ 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_local_packages(self):
359 """Return a list of rpm path to be local installed.
361 This is the hook where subclasses may specify a set of rpms which
362 it requires to be installed locally.
364 This returns an empty list by default.
366 Note, subclasses should usually chain up to the base class
367 implementation of this hook.
370 if self._local_pkgs_path:
371 if os.path.isdir(self._local_pkgs_path):
373 os.path.join(self._local_pkgs_path, '*.rpm'))
374 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
375 return [self._local_pkgs_path]
379 def _get_fstab(self):
380 """Return the desired contents of /etc/fstab.
382 This is the hook where subclasses may specify the contents of
383 /etc/fstab by returning a string containing the desired contents.
385 A sensible default implementation is provided.
388 s = "/dev/root / %s %s 0 0\n" \
390 "defaults,noatime" if not self._fsopts else self._fsopts)
391 s += self._get_fstab_special()
394 def _get_fstab_special(self):
395 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
396 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
397 s += "proc /proc proc defaults 0 0\n"
398 s += "sysfs /sys sysfs defaults 0 0\n"
401 def _get_post_scripts_env(self, in_chroot):
402 """Return an environment dict for %post scripts.
404 This is the hook where subclasses may specify some environment
405 variables for %post scripts by return a dict containing the desired
408 By default, this returns an empty dict.
410 in_chroot -- whether this %post script is to be executed chroot()ed
416 def __get_imgname(self):
418 _name = property(__get_imgname)
419 """The name of the image file.
423 def _get_kernel_versions(self):
424 """Return a dict detailing the available kernel types/versions.
426 This is the hook where subclasses may override what kernel types and
427 versions should be available for e.g. creating the booloader
430 A dict should be returned mapping the available kernel types to a list
431 of the available versions for those kernels.
433 The default implementation uses rpm to iterate over everything
434 providing 'kernel', finds /boot/vmlinuz-* and returns the version
435 obtained from the vmlinuz filename. (This can differ from the kernel
436 RPM's n-v-r in the case of e.g. xen)
439 def get_kernel_versions(instroot):
442 files = glob.glob(instroot + "/boot/vmlinuz-*")
444 version = os.path.basename(file)[8:]
447 versions.add(version)
448 ret["kernel"] = list(versions)
451 def get_version(header):
453 for f in header['filenames']:
454 if f.startswith('/boot/vmlinuz-'):
459 return get_kernel_versions(self._instroot)
461 ts = rpm.TransactionSet(self._instroot)
464 for header in ts.dbMatch('provides', 'kernel'):
465 version = get_version(header)
469 name = header['name']
471 ret[name] = [version]
472 elif not version in ret[name]:
473 ret[name].append(version)
479 # Helpers for subclasses
481 def _do_bindmounts(self):
482 """Mount various system directories onto _instroot.
484 This method is called by mount(), but may also be used by subclasses
485 in order to re-mount the bindmounts after modifying the underlying
489 for b in self.__bindmounts:
492 def _undo_bindmounts(self):
493 """Unmount the bind-mounted system directories from _instroot.
495 This method is usually only called by unmount(), but may also be used
496 by subclasses in order to gain access to the filesystem obscured by
497 the bindmounts - e.g. in order to create device nodes on the image
501 self.__bindmounts.reverse()
502 for b in self.__bindmounts:
506 """Chroot into the install root.
508 This method may be used by subclasses when executing programs inside
509 the install root e.g.
511 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
514 os.chroot(self._instroot)
517 def _mkdtemp(self, prefix = "tmp-"):
518 """Create a temporary directory.
520 This method may be used by subclasses to create a temporary directory
521 for use in building the final image - e.g. a subclass might create
522 a temporary directory in order to bundle a set of files into a package.
524 The subclass may delete this directory if it wishes, but it will be
525 automatically deleted by cleanup().
527 The absolute path to the temporary directory is returned.
529 Note, this method should only be called after mount() has been called.
531 prefix -- a prefix which should be used when creating the directory;
535 self.__ensure_builddir()
536 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
538 def _mkstemp(self, prefix = "tmp-"):
539 """Create a temporary file.
541 This method may be used by subclasses to create a temporary file
542 for use in building the final image - e.g. a subclass might need
543 a temporary location to unpack a compressed file.
545 The subclass may delete this file if it wishes, but it will be
546 automatically deleted by cleanup().
548 A tuple containing a file descriptor (returned from os.open() and the
549 absolute path to the temporary directory is returned.
551 Note, this method should only be called after mount() has been called.
553 prefix -- a prefix which should be used when creating the file;
557 self.__ensure_builddir()
558 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
560 def _mktemp(self, prefix = "tmp-"):
561 """Create a temporary file.
563 This method simply calls _mkstemp() and closes the returned file
566 The absolute path to the temporary file is returned.
568 Note, this method should only be called after mount() has been called.
570 prefix -- a prefix which should be used when creating the file;
575 (f, path) = self._mkstemp(prefix)
581 # Actual implementation
583 def __ensure_builddir(self):
584 if not self.__builddir is None:
588 self.__builddir = tempfile.mkdtemp(dir = self.tmpdir,
589 prefix = "imgcreate-")
590 except OSError, (err, msg):
591 raise CreatorError("Failed create build directory in %s: %s" %
594 def get_cachedir(self, cachedir = None):
598 self.__ensure_builddir()
600 self.cachedir = cachedir
602 self.cachedir = self.__builddir + "/mic-cache"
603 fs.makedirs(self.cachedir)
606 def __sanity_check(self):
607 """Ensure that the config we've been given is sane."""
608 if not (kickstart.get_packages(self.ks) or
609 kickstart.get_groups(self.ks)):
610 raise CreatorError("No packages or groups specified")
612 kickstart.convert_method_to_repo(self.ks)
614 if not kickstart.get_repos(self.ks):
615 raise CreatorError("No repositories specified")
617 def __write_fstab(self):
618 fstab = open(self._instroot + "/etc/fstab", "w")
619 fstab.write(self._get_fstab())
622 def __create_minimal_dev(self):
623 """Create a minimal /dev so that we don't corrupt the host /dev"""
624 origumask = os.umask(0000)
625 devices = (('null', 1, 3, 0666),
626 ('urandom',1, 9, 0666),
627 ('random', 1, 8, 0666),
628 ('full', 1, 7, 0666),
629 ('ptmx', 5, 2, 0666),
631 ('zero', 1, 5, 0666))
633 links = (("/proc/self/fd", "/dev/fd"),
634 ("/proc/self/fd/0", "/dev/stdin"),
635 ("/proc/self/fd/1", "/dev/stdout"),
636 ("/proc/self/fd/2", "/dev/stderr"))
638 for (node, major, minor, perm) in devices:
639 if not os.path.exists(self._instroot + "/dev/" + node):
640 os.mknod(self._instroot + "/dev/" + node,
642 os.makedev(major,minor))
644 for (src, dest) in links:
645 if not os.path.exists(self._instroot + dest):
646 os.symlink(src, self._instroot + dest)
650 def mount(self, base_on = None, cachedir = None):
651 """Setup the target filesystem in preparation for an install.
653 This function sets up the filesystem which the ImageCreator will
654 install into and configure. The ImageCreator class merely creates an
655 install root directory, bind mounts some system directories (e.g. /dev)
656 and writes out /etc/fstab. Other subclasses may also e.g. create a
657 sparse file, format it and loopback mount it to the install root.
659 base_on -- a previous install on which to base this install; defaults
660 to None, causing a new image to be created
662 cachedir -- a directory in which to store the Yum cache; defaults to
663 None, causing a new cache to be created; by setting this
664 to another directory, the same cache can be reused across
668 self.__ensure_builddir()
670 fs.makedirs(self._instroot)
671 fs.makedirs(self._outdir)
673 self._mount_instroot(base_on)
675 for d in ("/dev/pts",
682 fs.makedirs(self._instroot + d)
684 if self.target_arch and self.target_arch.startswith("arm"):
685 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
688 self.get_cachedir(cachedir)
690 # bind mount system directories into _instroot
691 for (f, dest) in [("/sys", None),
693 ("/proc/sys/fs/binfmt_misc", None),
695 self.__bindmounts.append(fs.BindChrootMount(f, self._instroot, dest))
697 self._do_bindmounts()
699 self.__create_minimal_dev()
701 if os.path.exists(self._instroot + "/etc/mtab"):
702 os.unlink(self._instroot + "/etc/mtab")
703 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
707 # get size of available space in 'instroot' fs
708 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
711 """Unmounts the target filesystem.
713 The ImageCreator class detaches the system from the install root, but
714 other subclasses may also detach the loopback mounted filesystem image
715 from the install root.
719 mtab = self._instroot + "/etc/mtab"
720 if not os.path.islink(mtab):
721 os.unlink(self._instroot + "/etc/mtab")
723 if self.qemu_emulator:
724 os.unlink(self._instroot + self.qemu_emulator)
728 self._undo_bindmounts()
730 """ Clean up yum garbage """
732 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
733 if os.path.exists(instroot_pdir):
734 shutil.rmtree(instroot_pdir, ignore_errors = True)
735 yumlibdir = self._instroot + "/var/lib/yum"
736 if os.path.exists(yumlibdir):
737 shutil.rmtree(yumlibdir, ignore_errors = True)
741 self._unmount_instroot()
744 """Unmounts the target filesystem and deletes temporary files.
746 This method calls unmount() and then deletes any temporary files and
747 directories that were created on the host system while building the
750 Note, make sure to call this method once finished with the creator
751 instance in order to ensure no stale files are left on the host e.g.:
753 creator = ImageCreator(ks, name)
760 if not self.__builddir:
765 shutil.rmtree(self.__builddir, ignore_errors = True)
766 self.__builddir = None
768 def __is_excluded_pkg(self, pkg):
769 if pkg in self._excluded_pkgs:
770 self._excluded_pkgs.remove(pkg)
773 for xpkg in self._excluded_pkgs:
774 if xpkg.endswith('*'):
775 if pkg.startswith(xpkg[:-1]):
777 elif xpkg.startswith('*'):
778 if pkg.endswith(xpkg[1:]):
783 def __select_packages(self, pkg_manager):
785 for pkg in self._required_pkgs:
786 e = pkg_manager.selectPackage(pkg)
788 if kickstart.ignore_missing(self.ks):
789 skipped_pkgs.append(pkg)
790 elif self.__is_excluded_pkg(pkg):
791 skipped_pkgs.append(pkg)
793 raise CreatorError("Failed to find package '%s' : %s" %
796 for pkg in skipped_pkgs:
797 msger.warning("Skipping missing package '%s'" % (pkg,))
799 def __select_groups(self, pkg_manager):
801 for group in self._required_groups:
802 e = pkg_manager.selectGroup(group.name, group.include)
804 if kickstart.ignore_missing(self.ks):
805 skipped_groups.append(group)
807 raise CreatorError("Failed to find group '%s' : %s" %
810 for group in skipped_groups:
811 msger.warning("Skipping missing group '%s'" % (group.name,))
813 def __deselect_packages(self, pkg_manager):
814 for pkg in self._excluded_pkgs:
815 pkg_manager.deselectPackage(pkg)
817 def __localinst_packages(self, pkg_manager):
818 for rpm_path in self._get_local_packages():
819 pkg_manager.installLocal(rpm_path)
821 def __preinstall_packages(self, pkg_manager):
825 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
826 for pkg in self._preinstall_pkgs:
827 pkg_manager.preInstall(pkg)
829 def __attachment_packages(self, pkg_manager):
833 self._attachment = []
834 for item in kickstart.get_attachment(self.ks):
835 if item.startswith('/'):
836 fpaths = os.path.join(self._instroot, item.lstrip('/'))
837 for fpath in glob.glob(fpaths):
838 self._attachment.append(fpath)
841 filelist = pkg_manager.getFilelist(item)
843 # found rpm in rootfs
844 for pfile in pkg_manager.getFilelist(item):
845 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
846 self._attachment.append(fpath)
849 # try to retrieve rpm file
850 (url, proxies) = pkg_manager.package_url(item)
852 msger.warning("Can't get url from repo for %s" % item)
854 fpath = os.path.join(self.cachedir, os.path.basename(url))
855 if not os.path.exists(fpath):
858 fpath = rpmmisc.myurlgrab(url, fpath, proxies, None)
862 tmpdir = self._mkdtemp()
863 misc.extract_rpm(fpath, tmpdir)
864 for (root, dirs, files) in os.walk(tmpdir):
866 fpath = os.path.join(root, fname)
867 self._attachment.append(fpath)
869 def install(self, repo_urls = {}):
870 """Install packages into the install root.
872 This function installs the packages listed in the supplied kickstart
873 into the install root. By default, the packages are installed from the
874 repository URLs specified in the kickstart.
876 repo_urls -- a dict which maps a repository name to a repository URL;
877 if supplied, this causes any repository URLs specified in
878 the kickstart to be overridden.
882 # initialize pkg list to install
884 self.__sanity_check()
886 self._required_pkgs = \
887 kickstart.get_packages(self.ks, self._get_required_packages())
888 self._excluded_pkgs = \
889 kickstart.get_excluded(self.ks, self._get_excluded_packages())
890 self._required_groups = kickstart.get_groups(self.ks)
892 self._required_pkgs = None
893 self._excluded_pkgs = None
894 self._required_groups = None
896 pkg_manager = self.get_pkg_manager()
899 for repo in kickstart.get_repos(self.ks, repo_urls):
900 (name, baseurl, mirrorlist, inc, exc,
901 proxy, proxy_username, proxy_password, debuginfo,
902 source, gpgkey, disable, ssl_verify, cost, priority) = repo
904 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
905 proxy_username, proxy_password, inc, exc, ssl_verify,
908 if kickstart.exclude_docs(self.ks):
909 rpm.addMacro("_excludedocs", "1")
910 rpm.addMacro("_dbpath", "/var/lib/rpm")
911 rpm.addMacro("__file_context_path", "%{nil}")
912 if kickstart.inst_langs(self.ks) != None:
913 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
917 self.__preinstall_packages(pkg_manager)
918 self.__select_packages(pkg_manager)
919 self.__select_groups(pkg_manager)
920 self.__deselect_packages(pkg_manager)
921 self.__localinst_packages(pkg_manager)
923 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
924 checksize = self._root_fs_avail
926 checksize -= BOOT_SAFEGUARD
928 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
929 pkg_manager.runInstall(checksize)
930 except CreatorError, e:
933 self._pkgs_content = pkg_manager.getAllContent()
934 self._pkgs_license = pkg_manager.getPkgsLicense()
935 self.__attachment_packages(pkg_manager)
942 # do some clean up to avoid lvm info leakage. this sucks.
943 for subdir in ("cache", "backup", "archive"):
944 lvmdir = self._instroot + "/etc/lvm/" + subdir
946 for f in os.listdir(lvmdir):
947 os.unlink(lvmdir + "/" + f)
951 def postinstall(self):
952 self.copy_attachment()
954 def __run_post_scripts(self):
955 msger.info("Running scripts ...")
956 if os.path.exists(self._instroot + "/tmp"):
957 shutil.rmtree(self._instroot + "/tmp")
958 os.mkdir (self._instroot + "/tmp", 0755)
959 for s in kickstart.get_post_scripts(self.ks):
960 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
961 dir = self._instroot + "/tmp")
963 s.script = s.script.replace("\r", "")
964 os.write(fd, s.script)
968 env = self._get_post_scripts_env(s.inChroot)
971 env["INSTALL_ROOT"] = self._instroot
972 env["IMG_NAME"] = self._name
976 preexec = self._chroot
977 script = "/tmp/" + os.path.basename(path)
981 subprocess.call([s.interp, script],
982 preexec_fn = preexec,
986 except OSError, (err, msg):
987 raise CreatorError("Failed to execute %%post script "
988 "with '%s' : %s" % (s.interp, msg))
992 def __save_repo_keys(self, repodata):
996 gpgkeydir = "/etc/pki/rpm-gpg"
997 fs.makedirs(self._instroot + gpgkeydir)
998 for repo in repodata:
1000 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1001 shutil.copy(repo["repokey"], self._instroot + repokey)
1003 def configure(self, repodata = None):
1004 """Configure the system image according to the kickstart.
1006 This method applies the (e.g. keyboard or network) configuration
1007 specified in the kickstart and executes the kickstart %post scripts.
1009 If necessary, it also prepares the image to be bootable by e.g.
1010 creating an initrd and bootloader configuration.
1013 ksh = self.ks.handler
1015 msger.info('Applying configurations ...')
1017 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1018 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1019 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1020 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1021 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1022 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1023 kickstart.UserConfig(self._instroot).apply(ksh.user)
1024 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1025 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1026 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1027 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1028 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1029 self.__save_repo_keys(repodata)
1030 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata)
1032 msger.warning("Failed to apply configuration to image")
1035 self._create_bootconfig()
1036 self.__run_post_scripts()
1038 def launch_shell(self, launch):
1039 """Launch a shell in the install root.
1041 This method is launches a bash shell chroot()ed in the install root;
1042 this can be useful for debugging.
1046 msger.info("Launching shell. Exit to continue.")
1047 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1049 def do_genchecksum(self, image_name):
1050 if not self._genchecksum:
1053 md5sum = misc.get_md5sum(image_name)
1054 with open(image_name + ".md5sum", "w") as f:
1055 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1056 self.outimage.append(image_name+".md5sum")
1058 def package(self, destdir = "."):
1059 """Prepares the created image for final delivery.
1061 In its simplest form, this method merely copies the install root to the
1062 supplied destination directory; other subclasses may choose to package
1063 the image by e.g. creating a bootable ISO containing the image and
1064 bootloader configuration.
1066 destdir -- the directory into which the final image should be moved;
1067 this defaults to the current directory.
1070 self._stage_final_image()
1072 if not os.path.exists(destdir):
1073 fs.makedirs(destdir)
1075 if self._recording_pkgs:
1076 self._save_recording_pkgs(destdir)
1078 # For image formats with two or multiple image files, it will be
1079 # better to put them under a directory
1080 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1081 destdir = os.path.join(destdir, "%s-%s" \
1082 % (self.name, self.image_format))
1083 msger.debug("creating destination dir: %s" % destdir)
1084 fs.makedirs(destdir)
1086 # Ensure all data is flushed to _outdir
1087 runner.quiet('sync')
1089 misc.check_space_pre_cp(self._outdir, destdir)
1090 for f in os.listdir(self._outdir):
1091 shutil.move(os.path.join(self._outdir, f),
1092 os.path.join(destdir, f))
1093 self.outimage.append(os.path.join(destdir, f))
1094 self.do_genchecksum(os.path.join(destdir, f))
1096 def print_outimage_info(self):
1097 msg = "The new image can be found here:\n"
1098 self.outimage.sort()
1099 for file in self.outimage:
1100 msg += ' %s\n' % os.path.abspath(file)
1104 def check_depend_tools(self):
1105 for tool in self._dep_checks:
1106 fs.find_binary_path(tool)
1108 def package_output(self, image_format, destdir = ".", package="none"):
1109 if not package or package == "none":
1112 destdir = os.path.abspath(os.path.expanduser(destdir))
1113 (pkg, comp) = os.path.splitext(package)
1115 comp=comp.lstrip(".")
1119 dst = "%s/%s-%s.tar.%s" % (destdir, self.name, image_format, comp)
1121 dst = "%s/%s-%s.tar" % (destdir, self.name, image_format)
1122 msger.info("creating %s" % dst)
1123 tar = tarfile.open(dst, "w:" + comp)
1125 for file in self.outimage:
1126 msger.info("adding %s to %s" % (file, dst))
1128 arcname=os.path.join("%s-%s" \
1129 % (self.name, image_format),
1130 os.path.basename(file)))
1131 if os.path.isdir(file):
1132 shutil.rmtree(file, ignore_errors = True)
1138 '''All the file in outimage has been packaged into tar.* file'''
1139 self.outimage = [dst]
1141 def release_output(self, config, destdir, release):
1142 """ Create release directory and files
1146 """ release path """
1147 return os.path.join(destdir, fn)
1149 outimages = self.outimage
1152 new_kspath = _rpath(self.name+'.ks')
1153 with open(config) as fr:
1154 with open(new_kspath, "w") as wf:
1155 # When building a release we want to make sure the .ks
1156 # file generates the same build even when --release= is not used.
1157 wf.write(fr.read().replace("@BUILD_ID@", release))
1158 outimages.append(new_kspath)
1160 # save log file, logfile is only available in creator attrs
1161 if hasattr(self, 'logfile') and not self.logfile:
1162 log_path = _rpath(self.name + ".log")
1163 # touch the log file, else outimages will filter it out
1164 with open(log_path, 'w') as wf:
1166 msger.set_logfile(log_path)
1167 outimages.append(_rpath(self.name + ".log"))
1169 # rename iso and usbimg
1170 for f in os.listdir(destdir):
1171 if f.endswith(".iso"):
1172 newf = f[:-4] + '.img'
1173 elif f.endswith(".usbimg"):
1174 newf = f[:-7] + '.img'
1177 os.rename(_rpath(f), _rpath(newf))
1178 outimages.append(_rpath(newf))
1181 with open(_rpath("MANIFEST"), "w") as wf:
1182 for f in os.listdir(destdir):
1186 if os.path.isdir(os.path.join(destdir, f)):
1189 md5sum = misc.get_md5sum(_rpath(f))
1190 # There needs to be two spaces between the sum and
1191 # filepath to match the syntax with md5sum.
1192 # This way also md5sum -c MANIFEST can be used by users
1193 wf.write("%s *%s\n" % (md5sum, f))
1195 outimages.append("%s/MANIFEST" % destdir)
1197 # Filter out the nonexist file
1198 for fp in outimages[:]:
1199 if not os.path.exists("%s" % fp):
1200 outimages.remove(fp)
1202 def copy_kernel(self):
1203 """ Copy kernel files to the outimage directory.
1205 NOTE: This needs to be called before unmounting the instroot.
1208 if not self._need_copy_kernel:
1211 if not os.path.exists(self.destdir):
1212 os.makedirs(self.destdir)
1214 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1215 kernelfilename = "%s/%s-%s" % (self.destdir, self.name, os.path.basename(kernel))
1216 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel), kernelfilename))
1217 shutil.copy(kernel, kernelfilename)
1218 self.outimage.append(kernelfilename)
1221 def copy_attachment(self):
1222 """ Subclass implement it to handle attachment files
1224 NOTE: This needs to be called before unmounting the instroot.
1228 def get_pkg_manager(self):
1229 return self.pkgmgr(target_arch = self.target_arch, instroot = self._instroot, cachedir = self.cachedir)