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, grabber, 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
80 # If the kernel is save to the destdir when copy_kernel cmd is called.
81 self._need_copy_kernel = False
84 # Mapping table for variables that have different names.
85 optmap = {"pkgmgr" : "pkgmgr_name",
87 "arch" : "target_arch",
88 "local_pkgs_path" : "_local_pkgs_path",
89 "copy_kernel" : "_need_copy_kernel",
92 # update setting from createopts
93 for key in createopts.keys():
98 setattr(self, option, createopts[key])
100 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
102 if 'release' in createopts and createopts['release']:
103 self.name = createopts['release'] + '_' + self.name
106 if '@NAME@' in self.pack_to:
107 self.pack_to = self.pack_to.replace('@NAME@', self.name)
108 (tar, ext) = os.path.splitext(self.pack_to)
109 if ext in (".gz", ".bz2") and tar.endswith(".tar"):
111 if ext not in misc.pack_formats:
112 self.pack_to += ".tar"
114 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
116 # Output image file names
119 # A flag to generate checksum
120 self._genchecksum = False
122 self._alt_initrd_name = None
124 self._recording_pkgs = []
126 # available size in root fs, init to 0
127 self._root_fs_avail = 0
129 # Name of the disk image file that is created.
130 self._img_name = None
132 self.image_format = None
134 # Save qemu emulator file name in order to clean up it finally
135 self.qemu_emulator = None
137 # No ks provided when called by convertor, so skip the dependency check
139 # If we have btrfs partition we need to check necessary tools
140 for part in self.ks.handler.partition.partitions:
141 if part.fstype and part.fstype == "btrfs":
142 self._dep_checks.append("mkfs.btrfs")
145 if self.target_arch and self.target_arch.startswith("arm"):
146 for dep in self._dep_checks:
147 if dep == "extlinux":
148 self._dep_checks.remove(dep)
150 if not os.path.exists("/usr/bin/qemu-arm") or \
151 not misc.is_statically_linked("/usr/bin/qemu-arm"):
152 self._dep_checks.append("qemu-arm-static")
154 if os.path.exists("/proc/sys/vm/vdso_enabled"):
155 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
156 vdso_value = vdso_fh.read().strip()
158 if (int)(vdso_value) == 1:
159 msger.warning("vdso is enabled on your host, which might "
160 "cause problems with arm emulations.\n"
161 "\tYou can disable vdso with following command before "
162 "starting image build:\n"
163 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
165 # make sure the specified tmpdir and cachedir exist
166 if not os.path.exists(self.tmpdir):
167 os.makedirs(self.tmpdir)
168 if not os.path.exists(self.cachedir):
169 os.makedirs(self.cachedir)
175 def __get_instroot(self):
176 if self.__builddir is None:
177 raise CreatorError("_instroot is not valid before calling mount()")
178 return self.__builddir + "/install_root"
179 _instroot = property(__get_instroot)
180 """The location of the install root directory.
182 This is the directory into which the system is installed. Subclasses may
183 mount a filesystem image here or copy files to/from here.
185 Note, this directory does not exist before ImageCreator.mount() is called.
187 Note also, this is a read-only attribute.
191 def __get_outdir(self):
192 if self.__builddir is None:
193 raise CreatorError("_outdir is not valid before calling mount()")
194 return self.__builddir + "/out"
195 _outdir = property(__get_outdir)
196 """The staging location for the final image.
198 This is where subclasses should stage any files that are part of the final
199 image. ImageCreator.package() will copy any files found here into the
200 requested destination directory.
202 Note, this directory does not exist before ImageCreator.mount() is called.
204 Note also, this is a read-only attribute.
210 # Hooks for subclasses
212 def _mount_instroot(self, base_on = None):
213 """Mount or prepare the install root directory.
215 This is the hook where subclasses may prepare the install root by e.g.
216 mounting creating and loopback mounting a filesystem image to
219 There is no default implementation.
221 base_on -- this is the value passed to mount() and can be interpreted
222 as the subclass wishes; it might e.g. be the location of
223 a previously created ISO containing a system image.
228 def _unmount_instroot(self):
229 """Undo anything performed in _mount_instroot().
231 This is the hook where subclasses must undo anything which was done
232 in _mount_instroot(). For example, if a filesystem image was mounted
233 onto _instroot, it should be unmounted here.
235 There is no default implementation.
240 def _create_bootconfig(self):
241 """Configure the image so that it's bootable.
243 This is the hook where subclasses may prepare the image for booting by
244 e.g. creating an initramfs and bootloader configuration.
246 This hook is called while the install root is still mounted, after the
247 packages have been installed and the kickstart configuration has been
248 applied, but before the %post scripts have been executed.
250 There is no default implementation.
255 def _stage_final_image(self):
256 """Stage the final system image in _outdir.
258 This is the hook where subclasses should place the image in _outdir
259 so that package() can copy it to the requested destination directory.
261 By default, this moves the install root into _outdir.
264 shutil.move(self._instroot, self._outdir + "/" + self.name)
266 def get_installed_packages(self):
267 return self._pkgs_content.keys()
269 def _save_recording_pkgs(self, destdir):
270 """Save the list or content of installed packages to file.
272 pkgs = self._pkgs_content.keys()
273 pkgs.sort() # inplace op
275 if not os.path.exists(destdir):
279 if 'vcs' in self._recording_pkgs:
280 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
281 content = '\n'.join(sorted(vcslst))
282 elif 'name' in self._recording_pkgs:
283 content = '\n'.join(pkgs)
285 namefile = os.path.join(destdir, self.name + '.packages')
286 f = open(namefile, "w")
289 self.outimage.append(namefile);
291 # if 'content', save more details
292 if 'content' in self._recording_pkgs:
293 contfile = os.path.join(destdir, self.name + '.files')
294 f = open(contfile, "w")
299 pkgcont = self._pkgs_content[pkg]
301 content += '\n '.join(pkgcont)
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 # prevent popup dialog in Ubuntu(s)
671 misc.hide_loopdev_presentation()
673 fs.makedirs(self._instroot)
674 fs.makedirs(self._outdir)
676 self._mount_instroot(base_on)
678 for d in ("/dev/pts",
685 fs.makedirs(self._instroot + d)
687 if self.target_arch and self.target_arch.startswith("arm"):
688 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
691 self.get_cachedir(cachedir)
693 # bind mount system directories into _instroot
694 for (f, dest) in [("/sys", None),
696 ("/proc/sys/fs/binfmt_misc", None),
698 self.__bindmounts.append(
700 f, self._instroot, dest))
702 self._do_bindmounts()
704 self.__create_minimal_dev()
706 if os.path.exists(self._instroot + "/etc/mtab"):
707 os.unlink(self._instroot + "/etc/mtab")
708 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
712 # get size of available space in 'instroot' fs
713 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
716 """Unmounts the target filesystem.
718 The ImageCreator class detaches the system from the install root, but
719 other subclasses may also detach the loopback mounted filesystem image
720 from the install root.
724 mtab = self._instroot + "/etc/mtab"
725 if not os.path.islink(mtab):
726 os.unlink(self._instroot + "/etc/mtab")
728 if self.qemu_emulator:
729 os.unlink(self._instroot + self.qemu_emulator)
733 self._undo_bindmounts()
735 """ Clean up yum garbage """
737 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
738 if os.path.exists(instroot_pdir):
739 shutil.rmtree(instroot_pdir, ignore_errors = True)
740 yumlibdir = self._instroot + "/var/lib/yum"
741 if os.path.exists(yumlibdir):
742 shutil.rmtree(yumlibdir, ignore_errors = True)
746 self._unmount_instroot()
748 # reset settings of popup dialog in Ubuntu(s)
749 misc.unhide_loopdev_presentation()
752 """Unmounts the target filesystem and deletes temporary files.
754 This method calls unmount() and then deletes any temporary files and
755 directories that were created on the host system while building the
758 Note, make sure to call this method once finished with the creator
759 instance in order to ensure no stale files are left on the host e.g.:
761 creator = ImageCreator(ks, name)
768 if not self.__builddir:
773 shutil.rmtree(self.__builddir, ignore_errors = True)
774 self.__builddir = None
776 def __is_excluded_pkg(self, pkg):
777 if pkg in self._excluded_pkgs:
778 self._excluded_pkgs.remove(pkg)
781 for xpkg in self._excluded_pkgs:
782 if xpkg.endswith('*'):
783 if pkg.startswith(xpkg[:-1]):
785 elif xpkg.startswith('*'):
786 if pkg.endswith(xpkg[1:]):
791 def __select_packages(self, pkg_manager):
793 for pkg in self._required_pkgs:
794 e = pkg_manager.selectPackage(pkg)
796 if kickstart.ignore_missing(self.ks):
797 skipped_pkgs.append(pkg)
798 elif self.__is_excluded_pkg(pkg):
799 skipped_pkgs.append(pkg)
801 raise CreatorError("Failed to find package '%s' : %s" %
804 for pkg in skipped_pkgs:
805 msger.warning("Skipping missing package '%s'" % (pkg,))
807 def __select_groups(self, pkg_manager):
809 for group in self._required_groups:
810 e = pkg_manager.selectGroup(group.name, group.include)
812 if kickstart.ignore_missing(self.ks):
813 skipped_groups.append(group)
815 raise CreatorError("Failed to find group '%s' : %s" %
818 for group in skipped_groups:
819 msger.warning("Skipping missing group '%s'" % (group.name,))
821 def __deselect_packages(self, pkg_manager):
822 for pkg in self._excluded_pkgs:
823 pkg_manager.deselectPackage(pkg)
825 def __localinst_packages(self, pkg_manager):
826 for rpm_path in self._get_local_packages():
827 pkg_manager.installLocal(rpm_path)
829 def __preinstall_packages(self, pkg_manager):
833 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
834 for pkg in self._preinstall_pkgs:
835 pkg_manager.preInstall(pkg)
837 def __attachment_packages(self, pkg_manager):
841 self._attachment = []
842 for item in kickstart.get_attachment(self.ks):
843 if item.startswith('/'):
844 fpaths = os.path.join(self._instroot, item.lstrip('/'))
845 for fpath in glob.glob(fpaths):
846 self._attachment.append(fpath)
849 filelist = pkg_manager.getFilelist(item)
851 # found rpm in rootfs
852 for pfile in pkg_manager.getFilelist(item):
853 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
854 self._attachment.append(fpath)
857 # try to retrieve rpm file
858 (url, proxies) = pkg_manager.package_url(item)
860 msger.warning("Can't get url from repo for %s" % item)
862 fpath = os.path.join(self.cachedir, os.path.basename(url))
863 if not os.path.exists(fpath):
866 fpath = grabber.myurlgrab(url, fpath, proxies, None)
870 tmpdir = self._mkdtemp()
871 misc.extract_rpm(fpath, tmpdir)
872 for (root, dirs, files) in os.walk(tmpdir):
874 fpath = os.path.join(root, fname)
875 self._attachment.append(fpath)
877 def install(self, repo_urls=None):
878 """Install packages into the install root.
880 This function installs the packages listed in the supplied kickstart
881 into the install root. By default, the packages are installed from the
882 repository URLs specified in the kickstart.
884 repo_urls -- a dict which maps a repository name to a repository URL;
885 if supplied, this causes any repository URLs specified in
886 the kickstart to be overridden.
890 # initialize pkg list to install
892 self.__sanity_check()
894 self._required_pkgs = \
895 kickstart.get_packages(self.ks, self._get_required_packages())
896 self._excluded_pkgs = \
897 kickstart.get_excluded(self.ks, self._get_excluded_packages())
898 self._required_groups = kickstart.get_groups(self.ks)
900 self._required_pkgs = None
901 self._excluded_pkgs = None
902 self._required_groups = None
904 pkg_manager = self.get_pkg_manager()
907 if hasattr(self, 'install_pkgs') and self.install_pkgs:
908 if 'debuginfo' in self.install_pkgs:
909 pkg_manager.install_debuginfo = True
911 for repo in kickstart.get_repos(self.ks, repo_urls):
912 (name, baseurl, mirrorlist, inc, exc,
913 proxy, proxy_username, proxy_password, debuginfo,
914 source, gpgkey, disable, ssl_verify, nocache,
915 cost, priority) = repo
917 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
918 proxy_username, proxy_password, inc, exc, ssl_verify,
919 nocache, cost, priority)
921 if kickstart.exclude_docs(self.ks):
922 rpm.addMacro("_excludedocs", "1")
923 rpm.addMacro("_dbpath", "/var/lib/rpm")
924 rpm.addMacro("__file_context_path", "%{nil}")
925 if kickstart.inst_langs(self.ks) != None:
926 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
929 self.__preinstall_packages(pkg_manager)
930 self.__select_packages(pkg_manager)
931 self.__select_groups(pkg_manager)
932 self.__deselect_packages(pkg_manager)
933 self.__localinst_packages(pkg_manager)
935 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
936 checksize = self._root_fs_avail
938 checksize -= BOOT_SAFEGUARD
940 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
941 pkg_manager.runInstall(checksize)
942 except CreatorError, e:
944 except KeyboardInterrupt:
947 self._pkgs_content = pkg_manager.getAllContent()
948 self._pkgs_license = pkg_manager.getPkgsLicense()
949 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
950 self.__attachment_packages(pkg_manager)
957 # do some clean up to avoid lvm info leakage. this sucks.
958 for subdir in ("cache", "backup", "archive"):
959 lvmdir = self._instroot + "/etc/lvm/" + subdir
961 for f in os.listdir(lvmdir):
962 os.unlink(lvmdir + "/" + f)
966 def postinstall(self):
967 self.copy_attachment()
969 def __run_post_scripts(self):
970 msger.info("Running scripts ...")
971 if os.path.exists(self._instroot + "/tmp"):
972 shutil.rmtree(self._instroot + "/tmp")
973 os.mkdir (self._instroot + "/tmp", 0755)
974 for s in kickstart.get_post_scripts(self.ks):
975 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
976 dir = self._instroot + "/tmp")
978 s.script = s.script.replace("\r", "")
979 os.write(fd, s.script)
983 env = self._get_post_scripts_env(s.inChroot)
986 env["INSTALL_ROOT"] = self._instroot
987 env["IMG_NAME"] = self._name
991 preexec = self._chroot
992 script = "/tmp/" + os.path.basename(path)
996 subprocess.call([s.interp, script],
997 preexec_fn = preexec,
1000 stderr = sys.stderr)
1001 except OSError, (err, msg):
1002 raise CreatorError("Failed to execute %%post script "
1003 "with '%s' : %s" % (s.interp, msg))
1007 def __save_repo_keys(self, repodata):
1011 gpgkeydir = "/etc/pki/rpm-gpg"
1012 fs.makedirs(self._instroot + gpgkeydir)
1013 for repo in repodata:
1015 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1016 shutil.copy(repo["repokey"], self._instroot + repokey)
1018 def configure(self, repodata = None):
1019 """Configure the system image according to the kickstart.
1021 This method applies the (e.g. keyboard or network) configuration
1022 specified in the kickstart and executes the kickstart %post scripts.
1024 If necessary, it also prepares the image to be bootable by e.g.
1025 creating an initrd and bootloader configuration.
1028 ksh = self.ks.handler
1030 msger.info('Applying configurations ...')
1032 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1033 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1034 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1035 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1036 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1037 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1038 kickstart.UserConfig(self._instroot).apply(ksh.user)
1039 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1040 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1041 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1042 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1043 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1044 self.__save_repo_keys(repodata)
1045 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1047 msger.warning("Failed to apply configuration to image")
1050 self._create_bootconfig()
1051 self.__run_post_scripts()
1053 def launch_shell(self, launch):
1054 """Launch a shell in the install root.
1056 This method is launches a bash shell chroot()ed in the install root;
1057 this can be useful for debugging.
1061 msger.info("Launching shell. Exit to continue.")
1062 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1064 def do_genchecksum(self, image_name):
1065 if not self._genchecksum:
1068 md5sum = misc.get_md5sum(image_name)
1069 with open(image_name + ".md5sum", "w") as f:
1070 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1071 self.outimage.append(image_name+".md5sum")
1073 def package(self, destdir = "."):
1074 """Prepares the created image for final delivery.
1076 In its simplest form, this method merely copies the install root to the
1077 supplied destination directory; other subclasses may choose to package
1078 the image by e.g. creating a bootable ISO containing the image and
1079 bootloader configuration.
1081 destdir -- the directory into which the final image should be moved;
1082 this defaults to the current directory.
1085 self._stage_final_image()
1087 if not os.path.exists(destdir):
1088 fs.makedirs(destdir)
1090 if self._recording_pkgs:
1091 self._save_recording_pkgs(destdir)
1093 # For image formats with two or multiple image files, it will be
1094 # better to put them under a directory
1095 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1096 destdir = os.path.join(destdir, "%s-%s" \
1097 % (self.name, self.image_format))
1098 msger.debug("creating destination dir: %s" % destdir)
1099 fs.makedirs(destdir)
1101 # Ensure all data is flushed to _outdir
1102 runner.quiet('sync')
1104 misc.check_space_pre_cp(self._outdir, destdir)
1105 for f in os.listdir(self._outdir):
1106 shutil.move(os.path.join(self._outdir, f),
1107 os.path.join(destdir, f))
1108 self.outimage.append(os.path.join(destdir, f))
1109 self.do_genchecksum(os.path.join(destdir, f))
1111 def print_outimage_info(self):
1112 msg = "The new image can be found here:\n"
1113 self.outimage.sort()
1114 for file in self.outimage:
1115 msg += ' %s\n' % os.path.abspath(file)
1119 def check_depend_tools(self):
1120 for tool in self._dep_checks:
1121 fs.find_binary_path(tool)
1123 def package_output(self, image_format, destdir = ".", package="none"):
1124 if not package or package == "none":
1127 destdir = os.path.abspath(os.path.expanduser(destdir))
1128 (pkg, comp) = os.path.splitext(package)
1130 comp=comp.lstrip(".")
1134 dst = "%s/%s-%s.tar.%s" %\
1135 (destdir, self.name, image_format, comp)
1137 dst = "%s/%s-%s.tar" %\
1138 (destdir, self.name, image_format)
1140 msger.info("creating %s" % dst)
1141 tar = tarfile.open(dst, "w:" + comp)
1143 for file in self.outimage:
1144 msger.info("adding %s to %s" % (file, dst))
1146 arcname=os.path.join("%s-%s" \
1147 % (self.name, image_format),
1148 os.path.basename(file)))
1149 if os.path.isdir(file):
1150 shutil.rmtree(file, ignore_errors = True)
1156 '''All the file in outimage has been packaged into tar.* file'''
1157 self.outimage = [dst]
1159 def release_output(self, config, destdir, release):
1160 """ Create release directory and files
1164 """ release path """
1165 return os.path.join(destdir, fn)
1167 outimages = self.outimage
1170 new_kspath = _rpath(self.name+'.ks')
1171 with open(config) as fr:
1172 with open(new_kspath, "w") as wf:
1173 # When building a release we want to make sure the .ks
1174 # file generates the same build even when --release not used.
1175 wf.write(fr.read().replace("@BUILD_ID@", release))
1176 outimages.append(new_kspath)
1178 # save log file, logfile is only available in creator attrs
1179 if hasattr(self, 'logfile') and not self.logfile:
1180 log_path = _rpath(self.name + ".log")
1181 # touch the log file, else outimages will filter it out
1182 with open(log_path, 'w') as wf:
1184 msger.set_logfile(log_path)
1185 outimages.append(_rpath(self.name + ".log"))
1187 # rename iso and usbimg
1188 for f in os.listdir(destdir):
1189 if f.endswith(".iso"):
1190 newf = f[:-4] + '.img'
1191 elif f.endswith(".usbimg"):
1192 newf = f[:-7] + '.img'
1195 os.rename(_rpath(f), _rpath(newf))
1196 outimages.append(_rpath(newf))
1199 with open(_rpath("MD5SUMS"), "w") as wf:
1200 for f in os.listdir(destdir):
1204 if os.path.isdir(os.path.join(destdir, f)):
1207 md5sum = misc.get_md5sum(_rpath(f))
1208 # There needs to be two spaces between the sum and
1209 # filepath to match the syntax with md5sum.
1210 # This way also md5sum -c MD5SUMS can be used by users
1211 wf.write("%s *%s\n" % (md5sum, f))
1213 outimages.append("%s/MD5SUMS" % destdir)
1215 # Filter out the nonexist file
1216 for fp in outimages[:]:
1217 if not os.path.exists("%s" % fp):
1218 outimages.remove(fp)
1220 def copy_kernel(self):
1221 """ Copy kernel files to the outimage directory.
1222 NOTE: This needs to be called before unmounting the instroot.
1225 if not self._need_copy_kernel:
1228 if not os.path.exists(self.destdir):
1229 os.makedirs(self.destdir)
1231 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1232 kernelfilename = "%s/%s-%s" % (self.destdir,
1234 os.path.basename(kernel))
1235 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1237 shutil.copy(kernel, kernelfilename)
1238 self.outimage.append(kernelfilename)
1240 def copy_attachment(self):
1241 """ Subclass implement it to handle attachment files
1242 NOTE: This needs to be called before unmounting the instroot.
1246 def get_pkg_manager(self):
1247 return self.pkgmgr(target_arch = self.target_arch,
1248 instroot = self._instroot,
1249 cachedir = self.cachedir)