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
36 from mic.chroot import kill_proc_inchroot
38 class BaseImageCreator(object):
39 """Installs a system to a chroot directory.
41 ImageCreator is the simplest creator class available; it will install and
42 configure a system image according to the supplied kickstart file.
46 import mic.imgcreate as imgcreate
47 ks = imgcreate.read_kickstart("foo.ks")
48 imgcreate.ImageCreator(ks, "foo").create()
55 def __init__(self, createopts = None, pkgmgr = None):
56 """Initialize an ImageCreator instance.
58 ks -- a pykickstart.KickstartParser instance; this instance will be
59 used to drive the install by e.g. providing the list of packages
60 to be installed, the system configuration and %post scripts
62 name -- a name for the image; used for e.g. image filenames or
69 self.__builddir = None
70 self.__bindmounts = []
74 self.tmpdir = "/var/tmp/mic"
75 self.cachedir = "/var/tmp/mic/cache"
76 self.workdir = "/var/tmp/mic/build"
78 self.installerfw_prefix = "INSTALLERFW_"
79 self.target_arch = "noarch"
80 self._local_pkgs_path = None
84 # If the kernel is save to the destdir when copy_kernel cmd is called.
85 self._need_copy_kernel = False
86 # setup tmpfs tmpdir when enabletmpfs is True
87 self.enabletmpfs = False
90 # Mapping table for variables that have different names.
91 optmap = {"pkgmgr" : "pkgmgr_name",
92 "arch" : "target_arch",
93 "local_pkgs_path" : "_local_pkgs_path",
94 "copy_kernel" : "_need_copy_kernel",
97 # update setting from createopts
98 for key in createopts.keys():
103 setattr(self, option, createopts[key])
105 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
108 if '@NAME@' in self.pack_to:
109 self.pack_to = self.pack_to.replace('@NAME@', self.name)
110 (tar, ext) = os.path.splitext(self.pack_to)
111 if ext in (".gz", ".bz2") and tar.endswith(".tar"):
113 if ext not in misc.pack_formats:
114 self.pack_to += ".tar"
116 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
118 # Output image file names
121 # A flag to generate checksum
122 self._genchecksum = False
124 self._alt_initrd_name = None
126 self._recording_pkgs = []
128 # available size in root fs, init to 0
129 self._root_fs_avail = 0
131 # Name of the disk image file that is created.
132 self._img_name = None
134 self.image_format = None
136 # Save qemu emulator file name in order to clean up it finally
137 self.qemu_emulator = None
139 # No ks provided when called by convertor, so skip the dependency check
141 # If we have btrfs partition we need to check necessary tools
142 for part in self.ks.handler.partition.partitions:
143 if part.fstype and part.fstype == "btrfs":
144 self._dep_checks.append("mkfs.btrfs")
147 if self.target_arch and self.target_arch.startswith("arm"):
148 for dep in self._dep_checks:
149 if dep == "extlinux":
150 self._dep_checks.remove(dep)
152 if not os.path.exists("/usr/bin/qemu-arm") or \
153 not misc.is_statically_linked("/usr/bin/qemu-arm"):
154 self._dep_checks.append("qemu-arm-static")
156 if os.path.exists("/proc/sys/vm/vdso_enabled"):
157 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
158 vdso_value = vdso_fh.read().strip()
160 if (int)(vdso_value) == 1:
161 msger.warning("vdso is enabled on your host, which might "
162 "cause problems with arm emulations.\n"
163 "\tYou can disable vdso with following command before "
164 "starting image build:\n"
165 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
167 # make sure the specified tmpdir and cachedir exist
168 if not os.path.exists(self.tmpdir):
169 os.makedirs(self.tmpdir)
170 if not os.path.exists(self.cachedir):
171 os.makedirs(self.cachedir)
177 def __get_instroot(self):
178 if self.__builddir is None:
179 raise CreatorError("_instroot is not valid before calling mount()")
180 return self.__builddir + "/install_root"
181 _instroot = property(__get_instroot)
182 """The location of the install root directory.
184 This is the directory into which the system is installed. Subclasses may
185 mount a filesystem image here or copy files to/from here.
187 Note, this directory does not exist before ImageCreator.mount() is called.
189 Note also, this is a read-only attribute.
193 def __get_outdir(self):
194 if self.__builddir is None:
195 raise CreatorError("_outdir is not valid before calling mount()")
196 return self.__builddir + "/out"
197 _outdir = property(__get_outdir)
198 """The staging location for the final image.
200 This is where subclasses should stage any files that are part of the final
201 image. ImageCreator.package() will copy any files found here into the
202 requested destination directory.
204 Note, this directory does not exist before ImageCreator.mount() is called.
206 Note also, this is a read-only attribute.
212 # Hooks for subclasses
214 def _mount_instroot(self, base_on = None):
215 """Mount or prepare the install root directory.
217 This is the hook where subclasses may prepare the install root by e.g.
218 mounting creating and loopback mounting a filesystem image to
221 There is no default implementation.
223 base_on -- this is the value passed to mount() and can be interpreted
224 as the subclass wishes; it might e.g. be the location of
225 a previously created ISO containing a system image.
230 def _unmount_instroot(self):
231 """Undo anything performed in _mount_instroot().
233 This is the hook where subclasses must undo anything which was done
234 in _mount_instroot(). For example, if a filesystem image was mounted
235 onto _instroot, it should be unmounted here.
237 There is no default implementation.
242 def _create_bootconfig(self):
243 """Configure the image so that it's bootable.
245 This is the hook where subclasses may prepare the image for booting by
246 e.g. creating an initramfs and bootloader configuration.
248 This hook is called while the install root is still mounted, after the
249 packages have been installed and the kickstart configuration has been
250 applied, but before the %post scripts have been executed.
252 There is no default implementation.
257 def _stage_final_image(self):
258 """Stage the final system image in _outdir.
260 This is the hook where subclasses should place the image in _outdir
261 so that package() can copy it to the requested destination directory.
263 By default, this moves the install root into _outdir.
266 shutil.move(self._instroot, self._outdir + "/" + self.name)
268 def get_installed_packages(self):
269 return self._pkgs_content.keys()
271 def _save_recording_pkgs(self, destdir):
272 """Save the list or content of installed packages to file.
274 pkgs = self._pkgs_content.keys()
275 pkgs.sort() # inplace op
277 if not os.path.exists(destdir):
281 if 'vcs' in self._recording_pkgs:
282 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
283 content = '\n'.join(sorted(vcslst))
284 elif 'name' in self._recording_pkgs:
285 content = '\n'.join(pkgs)
287 namefile = os.path.join(destdir, self.name + '.packages')
288 f = open(namefile, "w")
291 self.outimage.append(namefile);
293 # if 'content', save more details
294 if 'content' in self._recording_pkgs:
295 contfile = os.path.join(destdir, self.name + '.files')
296 f = open(contfile, "w")
301 pkgcont = self._pkgs_content[pkg]
303 content += '\n '.join(pkgcont)
309 self.outimage.append(contfile)
311 if 'license' in self._recording_pkgs:
312 licensefile = os.path.join(destdir, self.name + '.license')
313 f = open(licensefile, "w")
315 f.write('Summary:\n')
316 for license in reversed(sorted(self._pkgs_license, key=\
317 lambda license: len(self._pkgs_license[license]))):
318 f.write(" - %s: %s\n" \
319 % (license, len(self._pkgs_license[license])))
321 f.write('\nDetails:\n')
322 for license in reversed(sorted(self._pkgs_license, key=\
323 lambda license: len(self._pkgs_license[license]))):
324 f.write(" - %s:\n" % (license))
325 for pkg in sorted(self._pkgs_license[license]):
326 f.write(" - %s\n" % (pkg))
330 self.outimage.append(licensefile)
332 def _get_required_packages(self):
333 """Return a list of required packages.
335 This is the hook where subclasses may specify a set of packages which
336 it requires to be installed.
338 This returns an empty list by default.
340 Note, subclasses should usually chain up to the base class
341 implementation of this hook.
346 def _get_excluded_packages(self):
347 """Return a list of excluded packages.
349 This is the hook where subclasses may specify a set of packages which
350 it requires _not_ to be installed.
352 This returns an empty list by default.
354 Note, subclasses should usually chain up to the base class
355 implementation of this hook.
360 def _get_local_packages(self):
361 """Return a list of rpm path to be local installed.
363 This is the hook where subclasses may specify a set of rpms which
364 it requires to be installed locally.
366 This returns an empty list by default.
368 Note, subclasses should usually chain up to the base class
369 implementation of this hook.
372 if self._local_pkgs_path:
373 if os.path.isdir(self._local_pkgs_path):
375 os.path.join(self._local_pkgs_path, '*.rpm'))
376 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
377 return [self._local_pkgs_path]
381 def _get_fstab(self):
382 """Return the desired contents of /etc/fstab.
384 This is the hook where subclasses may specify the contents of
385 /etc/fstab by returning a string containing the desired contents.
387 A sensible default implementation is provided.
390 s = "/dev/root / %s %s 0 0\n" \
392 "defaults,noatime" if not self._fsopts else self._fsopts)
393 s += self._get_fstab_special()
396 def _get_fstab_special(self):
397 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
398 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
399 s += "proc /proc proc defaults 0 0\n"
400 s += "sysfs /sys sysfs defaults 0 0\n"
403 def _set_part_env(self, pnum, prop, value):
404 """ This is a helper function which generates an environment variable
405 for a property "prop" with value "value" of a partition number "pnum".
407 The naming convention is:
408 * Variables start with INSTALLERFW_PART
409 * Then goes the partition number, the order is the same as
410 specified in the KS file
411 * Then goes the property name
419 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
420 return { name : value }
422 def _get_post_scripts_env(self, in_chroot):
423 """Return an environment dict for %post scripts.
425 This is the hook where subclasses may specify some environment
426 variables for %post scripts by return a dict containing the desired
429 in_chroot -- whether this %post script is to be executed chroot()ed
436 for p in kickstart.get_partitions(self.ks):
437 env.update(self._set_part_env(pnum, "SIZE", p.size))
438 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
439 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
440 env.update(self._set_part_env(pnum, "LABEL", p.label))
441 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
442 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
443 env.update(self._set_part_env(pnum, "ALIGN", p.align))
444 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
445 env.update(self._set_part_env(pnum, "UUID", p.uuid))
446 env.update(self._set_part_env(pnum, "DEVNODE",
447 "/dev/%s%d" % (p.disk, pnum + 1)))
448 env.update(self._set_part_env(pnum, "DISK_DEVNODE",
453 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
455 # Partition table format
456 ptable_format = self.ks.handler.bootloader.ptable
457 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
459 # The kerned boot parameters
460 kernel_opts = self.ks.handler.bootloader.appendLine
461 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
463 # Name of the image creation tool
464 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
466 # The real current location of the mounted file-systems
470 mount_prefix = self._instroot
471 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
473 # These are historical variables which lack the common name prefix
475 env["INSTALL_ROOT"] = self._instroot
476 env["IMG_NAME"] = self._name
480 def __get_imgname(self):
482 _name = property(__get_imgname)
483 """The name of the image file.
487 def _get_kernel_versions(self):
488 """Return a dict detailing the available kernel types/versions.
490 This is the hook where subclasses may override what kernel types and
491 versions should be available for e.g. creating the booloader
494 A dict should be returned mapping the available kernel types to a list
495 of the available versions for those kernels.
497 The default implementation uses rpm to iterate over everything
498 providing 'kernel', finds /boot/vmlinuz-* and returns the version
499 obtained from the vmlinuz filename. (This can differ from the kernel
500 RPM's n-v-r in the case of e.g. xen)
503 def get_kernel_versions(instroot):
506 files = glob.glob(instroot + "/boot/vmlinuz-*")
508 version = os.path.basename(file)[8:]
511 versions.add(version)
512 ret["kernel"] = list(versions)
515 def get_version(header):
517 for f in header['filenames']:
518 if f.startswith('/boot/vmlinuz-'):
523 return get_kernel_versions(self._instroot)
525 ts = rpm.TransactionSet(self._instroot)
528 for header in ts.dbMatch('provides', 'kernel'):
529 version = get_version(header)
533 name = header['name']
535 ret[name] = [version]
536 elif not version in ret[name]:
537 ret[name].append(version)
543 # Helpers for subclasses
545 def _do_bindmounts(self):
546 """Mount various system directories onto _instroot.
548 This method is called by mount(), but may also be used by subclasses
549 in order to re-mount the bindmounts after modifying the underlying
553 for b in self.__bindmounts:
556 def _undo_bindmounts(self):
557 """Unmount the bind-mounted system directories from _instroot.
559 This method is usually only called by unmount(), but may also be used
560 by subclasses in order to gain access to the filesystem obscured by
561 the bindmounts - e.g. in order to create device nodes on the image
565 self.__bindmounts.reverse()
566 for b in self.__bindmounts:
570 """Chroot into the install root.
572 This method may be used by subclasses when executing programs inside
573 the install root e.g.
575 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
578 os.chroot(self._instroot)
581 def _mkdtemp(self, prefix = "tmp-"):
582 """Create a temporary directory.
584 This method may be used by subclasses to create a temporary directory
585 for use in building the final image - e.g. a subclass might create
586 a temporary directory in order to bundle a set of files into a package.
588 The subclass may delete this directory if it wishes, but it will be
589 automatically deleted by cleanup().
591 The absolute path to the temporary directory is returned.
593 Note, this method should only be called after mount() has been called.
595 prefix -- a prefix which should be used when creating the directory;
599 self.__ensure_builddir()
600 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
602 def _mkstemp(self, prefix = "tmp-"):
603 """Create a temporary file.
605 This method may be used by subclasses to create a temporary file
606 for use in building the final image - e.g. a subclass might need
607 a temporary location to unpack a compressed file.
609 The subclass may delete this file if it wishes, but it will be
610 automatically deleted by cleanup().
612 A tuple containing a file descriptor (returned from os.open() and the
613 absolute path to the temporary directory is returned.
615 Note, this method should only be called after mount() has been called.
617 prefix -- a prefix which should be used when creating the file;
621 self.__ensure_builddir()
622 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
624 def _mktemp(self, prefix = "tmp-"):
625 """Create a temporary file.
627 This method simply calls _mkstemp() and closes the returned file
630 The absolute path to the temporary file is returned.
632 Note, this method should only be called after mount() has been called.
634 prefix -- a prefix which should be used when creating the file;
639 (f, path) = self._mkstemp(prefix)
645 # Actual implementation
647 def __ensure_builddir(self):
648 if not self.__builddir is None:
652 self.workdir = os.path.join(self.tmpdir, "build")
653 if not os.path.exists(self.workdir):
654 os.makedirs(self.workdir)
655 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
656 prefix = "imgcreate-")
657 except OSError, (err, msg):
658 raise CreatorError("Failed create build directory in %s: %s" %
661 def get_cachedir(self, cachedir = None):
665 self.__ensure_builddir()
667 self.cachedir = cachedir
669 self.cachedir = self.__builddir + "/mic-cache"
670 fs.makedirs(self.cachedir)
673 def __sanity_check(self):
674 """Ensure that the config we've been given is sane."""
675 if not (kickstart.get_packages(self.ks) or
676 kickstart.get_groups(self.ks)):
677 raise CreatorError("No packages or groups specified")
679 kickstart.convert_method_to_repo(self.ks)
681 if not kickstart.get_repos(self.ks):
682 raise CreatorError("No repositories specified")
684 def __write_fstab(self):
685 if kickstart.use_installerfw(self.ks, "fstab"):
686 # The fstab file will be generated by installer framework scripts
689 fstab_contents = self._get_fstab()
691 fstab = open(self._instroot + "/etc/fstab", "w")
692 fstab.write(fstab_contents)
695 def __create_minimal_dev(self):
696 """Create a minimal /dev so that we don't corrupt the host /dev"""
697 origumask = os.umask(0000)
698 devices = (('null', 1, 3, 0666),
699 ('urandom',1, 9, 0666),
700 ('random', 1, 8, 0666),
701 ('full', 1, 7, 0666),
702 ('ptmx', 5, 2, 0666),
704 ('zero', 1, 5, 0666))
706 links = (("/proc/self/fd", "/dev/fd"),
707 ("/proc/self/fd/0", "/dev/stdin"),
708 ("/proc/self/fd/1", "/dev/stdout"),
709 ("/proc/self/fd/2", "/dev/stderr"))
711 for (node, major, minor, perm) in devices:
712 if not os.path.exists(self._instroot + "/dev/" + node):
713 os.mknod(self._instroot + "/dev/" + node,
715 os.makedev(major,minor))
717 for (src, dest) in links:
718 if not os.path.exists(self._instroot + dest):
719 os.symlink(src, self._instroot + dest)
723 def __setup_tmpdir(self):
724 if not self.enabletmpfs:
727 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
729 def __clean_tmpdir(self):
730 if not self.enabletmpfs:
733 runner.show('umount -l %s' % self.workdir)
735 def mount(self, base_on = None, cachedir = None):
736 """Setup the target filesystem in preparation for an install.
738 This function sets up the filesystem which the ImageCreator will
739 install into and configure. The ImageCreator class merely creates an
740 install root directory, bind mounts some system directories (e.g. /dev)
741 and writes out /etc/fstab. Other subclasses may also e.g. create a
742 sparse file, format it and loopback mount it to the install root.
744 base_on -- a previous install on which to base this install; defaults
745 to None, causing a new image to be created
747 cachedir -- a directory in which to store the Yum cache; defaults to
748 None, causing a new cache to be created; by setting this
749 to another directory, the same cache can be reused across
753 self.__setup_tmpdir()
754 self.__ensure_builddir()
756 # prevent popup dialog in Ubuntu(s)
757 misc.hide_loopdev_presentation()
759 fs.makedirs(self._instroot)
760 fs.makedirs(self._outdir)
762 self._mount_instroot(base_on)
764 for d in ("/dev/pts",
771 fs.makedirs(self._instroot + d)
773 if self.target_arch and self.target_arch.startswith("arm") or \
774 self.target_arch == "aarch64":
775 self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
778 self.get_cachedir(cachedir)
780 # bind mount system directories into _instroot
781 for (f, dest) in [("/sys", None),
783 ("/proc/sys/fs/binfmt_misc", None),
785 self.__bindmounts.append(
787 f, self._instroot, dest))
789 self._do_bindmounts()
791 self.__create_minimal_dev()
793 if os.path.exists(self._instroot + "/etc/mtab"):
794 os.unlink(self._instroot + "/etc/mtab")
795 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
799 # get size of available space in 'instroot' fs
800 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
803 """Unmounts the target filesystem.
805 The ImageCreator class detaches the system from the install root, but
806 other subclasses may also detach the loopback mounted filesystem image
807 from the install root.
811 mtab = self._instroot + "/etc/mtab"
812 if not os.path.islink(mtab):
813 os.unlink(self._instroot + "/etc/mtab")
815 if self.qemu_emulator:
816 os.unlink(self._instroot + self.qemu_emulator)
820 self._undo_bindmounts()
822 """ Clean up yum garbage """
824 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
825 if os.path.exists(instroot_pdir):
826 shutil.rmtree(instroot_pdir, ignore_errors = True)
827 yumlibdir = self._instroot + "/var/lib/yum"
828 if os.path.exists(yumlibdir):
829 shutil.rmtree(yumlibdir, ignore_errors = True)
833 self._unmount_instroot()
835 # reset settings of popup dialog in Ubuntu(s)
836 misc.unhide_loopdev_presentation()
840 """Unmounts the target filesystem and deletes temporary files.
842 This method calls unmount() and then deletes any temporary files and
843 directories that were created on the host system while building the
846 Note, make sure to call this method once finished with the creator
847 instance in order to ensure no stale files are left on the host e.g.:
849 creator = ImageCreator(ks, name)
856 if not self.__builddir:
859 kill_proc_inchroot(self._instroot)
863 shutil.rmtree(self.__builddir, ignore_errors = True)
864 self.__builddir = None
866 self.__clean_tmpdir()
868 def __is_excluded_pkg(self, pkg):
869 if pkg in self._excluded_pkgs:
870 self._excluded_pkgs.remove(pkg)
873 for xpkg in self._excluded_pkgs:
874 if xpkg.endswith('*'):
875 if pkg.startswith(xpkg[:-1]):
877 elif xpkg.startswith('*'):
878 if pkg.endswith(xpkg[1:]):
883 def __select_packages(self, pkg_manager):
885 for pkg in self._required_pkgs:
886 e = pkg_manager.selectPackage(pkg)
888 if kickstart.ignore_missing(self.ks):
889 skipped_pkgs.append(pkg)
890 elif self.__is_excluded_pkg(pkg):
891 skipped_pkgs.append(pkg)
893 raise CreatorError("Failed to find package '%s' : %s" %
896 for pkg in skipped_pkgs:
897 msger.warning("Skipping missing package '%s'" % (pkg,))
899 def __select_groups(self, pkg_manager):
901 for group in self._required_groups:
902 e = pkg_manager.selectGroup(group.name, group.include)
904 if kickstart.ignore_missing(self.ks):
905 skipped_groups.append(group)
907 raise CreatorError("Failed to find group '%s' : %s" %
910 for group in skipped_groups:
911 msger.warning("Skipping missing group '%s'" % (group.name,))
913 def __deselect_packages(self, pkg_manager):
914 for pkg in self._excluded_pkgs:
915 pkg_manager.deselectPackage(pkg)
917 def __localinst_packages(self, pkg_manager):
918 for rpm_path in self._get_local_packages():
919 pkg_manager.installLocal(rpm_path)
921 def __preinstall_packages(self, pkg_manager):
925 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
926 for pkg in self._preinstall_pkgs:
927 pkg_manager.preInstall(pkg)
929 def __check_packages(self, pkg_manager):
930 for pkg in self.check_pkgs:
931 pkg_manager.checkPackage(pkg)
933 def __attachment_packages(self, pkg_manager):
937 self._attachment = []
938 for item in kickstart.get_attachment(self.ks):
939 if item.startswith('/'):
940 fpaths = os.path.join(self._instroot, item.lstrip('/'))
941 for fpath in glob.glob(fpaths):
942 self._attachment.append(fpath)
945 filelist = pkg_manager.getFilelist(item)
947 # found rpm in rootfs
948 for pfile in pkg_manager.getFilelist(item):
949 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
950 self._attachment.append(fpath)
953 # try to retrieve rpm file
954 (url, proxies) = pkg_manager.package_url(item)
956 msger.warning("Can't get url from repo for %s" % item)
958 fpath = os.path.join(self.cachedir, os.path.basename(url))
959 if not os.path.exists(fpath):
962 fpath = grabber.myurlgrab(url.full, fpath, proxies, None)
966 tmpdir = self._mkdtemp()
967 misc.extract_rpm(fpath, tmpdir)
968 for (root, dirs, files) in os.walk(tmpdir):
970 fpath = os.path.join(root, fname)
971 self._attachment.append(fpath)
973 def install(self, repo_urls=None):
974 """Install packages into the install root.
976 This function installs the packages listed in the supplied kickstart
977 into the install root. By default, the packages are installed from the
978 repository URLs specified in the kickstart.
980 repo_urls -- a dict which maps a repository name to a repository;
981 if supplied, this causes any repository URLs specified in
982 the kickstart to be overridden.
986 # initialize pkg list to install
988 self.__sanity_check()
990 self._required_pkgs = \
991 kickstart.get_packages(self.ks, self._get_required_packages())
992 self._excluded_pkgs = \
993 kickstart.get_excluded(self.ks, self._get_excluded_packages())
994 self._required_groups = kickstart.get_groups(self.ks)
996 self._required_pkgs = None
997 self._excluded_pkgs = None
998 self._required_groups = None
1001 repo_urls = self.extrarepos
1003 pkg_manager = self.get_pkg_manager()
1006 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1007 if 'debuginfo' in self.install_pkgs:
1008 pkg_manager.install_debuginfo = True
1010 for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1011 (name, baseurl, mirrorlist, inc, exc,
1012 proxy, proxy_username, proxy_password, debuginfo,
1013 source, gpgkey, disable, ssl_verify, nocache,
1014 cost, priority) = repo
1016 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1017 proxy_username, proxy_password, inc, exc, ssl_verify,
1018 nocache, cost, priority)
1020 if kickstart.exclude_docs(self.ks):
1021 rpm.addMacro("_excludedocs", "1")
1022 rpm.addMacro("_dbpath", "/var/lib/rpm")
1023 rpm.addMacro("__file_context_path", "%{nil}")
1024 if kickstart.inst_langs(self.ks) != None:
1025 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1028 self.__preinstall_packages(pkg_manager)
1029 self.__select_packages(pkg_manager)
1030 self.__select_groups(pkg_manager)
1031 self.__deselect_packages(pkg_manager)
1032 self.__localinst_packages(pkg_manager)
1033 self.__check_packages(pkg_manager)
1035 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1036 checksize = self._root_fs_avail
1038 checksize -= BOOT_SAFEGUARD
1039 if self.target_arch:
1040 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1041 pkg_manager.runInstall(checksize)
1042 except CreatorError, e:
1044 except KeyboardInterrupt:
1047 self._pkgs_content = pkg_manager.getAllContent()
1048 self._pkgs_license = pkg_manager.getPkgsLicense()
1049 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1050 self.__attachment_packages(pkg_manager)
1057 # do some clean up to avoid lvm info leakage. this sucks.
1058 for subdir in ("cache", "backup", "archive"):
1059 lvmdir = self._instroot + "/etc/lvm/" + subdir
1061 for f in os.listdir(lvmdir):
1062 os.unlink(lvmdir + "/" + f)
1066 def postinstall(self):
1067 self.copy_attachment()
1069 def __run_post_scripts(self):
1070 msger.info("Running scripts ...")
1071 if os.path.exists(self._instroot + "/tmp"):
1072 shutil.rmtree(self._instroot + "/tmp")
1073 os.mkdir (self._instroot + "/tmp", 0755)
1074 for s in kickstart.get_post_scripts(self.ks):
1075 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1076 dir = self._instroot + "/tmp")
1078 s.script = s.script.replace("\r", "")
1079 os.write(fd, s.script)
1081 os.chmod(path, 0700)
1083 env = self._get_post_scripts_env(s.inChroot)
1084 if 'PATH' not in env:
1085 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1091 preexec = self._chroot
1092 script = "/tmp/" + os.path.basename(path)
1096 p = subprocess.Popen([s.interp, script],
1097 preexec_fn = preexec,
1099 stdout = subprocess.PIPE,
1100 stderr = subprocess.STDOUT)
1101 for entry in p.communicate()[0].splitlines():
1103 except OSError, (err, msg):
1104 raise CreatorError("Failed to execute %%post script "
1105 "with '%s' : %s" % (s.interp, msg))
1109 def __save_repo_keys(self, repodata):
1113 gpgkeydir = "/etc/pki/rpm-gpg"
1114 fs.makedirs(self._instroot + gpgkeydir)
1115 for repo in repodata:
1117 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1118 shutil.copy(repo["repokey"], self._instroot + repokey)
1120 def configure(self, repodata = None):
1121 """Configure the system image according to the kickstart.
1123 This method applies the (e.g. keyboard or network) configuration
1124 specified in the kickstart and executes the kickstart %post scripts.
1126 If necessary, it also prepares the image to be bootable by e.g.
1127 creating an initrd and bootloader configuration.
1130 ksh = self.ks.handler
1132 msger.info('Applying configurations ...')
1134 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1135 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1136 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1137 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1138 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1139 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1140 kickstart.UserConfig(self._instroot).apply(ksh.user)
1141 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1142 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1143 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1144 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1145 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1146 self.__save_repo_keys(repodata)
1147 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1149 msger.warning("Failed to apply configuration to image")
1152 self._create_bootconfig()
1153 self.__run_post_scripts()
1155 def launch_shell(self, launch):
1156 """Launch a shell in the install root.
1158 This method is launches a bash shell chroot()ed in the install root;
1159 this can be useful for debugging.
1163 msger.info("Launching shell. Exit to continue.")
1164 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1166 def do_genchecksum(self, image_name):
1167 if not self._genchecksum:
1170 md5sum = misc.get_md5sum(image_name)
1171 with open(image_name + ".md5sum", "w") as f:
1172 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1173 self.outimage.append(image_name+".md5sum")
1175 def package(self, destdir = "."):
1176 """Prepares the created image for final delivery.
1178 In its simplest form, this method merely copies the install root to the
1179 supplied destination directory; other subclasses may choose to package
1180 the image by e.g. creating a bootable ISO containing the image and
1181 bootloader configuration.
1183 destdir -- the directory into which the final image should be moved;
1184 this defaults to the current directory.
1187 self._stage_final_image()
1189 if not os.path.exists(destdir):
1190 fs.makedirs(destdir)
1192 if self._recording_pkgs:
1193 self._save_recording_pkgs(destdir)
1195 # For image formats with two or multiple image files, it will be
1196 # better to put them under a directory
1197 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1198 destdir = os.path.join(destdir, "%s-%s" \
1199 % (self.name, self.image_format))
1200 msger.debug("creating destination dir: %s" % destdir)
1201 fs.makedirs(destdir)
1203 # Ensure all data is flushed to _outdir
1204 runner.quiet('sync')
1206 misc.check_space_pre_cp(self._outdir, destdir)
1207 for f in os.listdir(self._outdir):
1208 shutil.move(os.path.join(self._outdir, f),
1209 os.path.join(destdir, f))
1210 self.outimage.append(os.path.join(destdir, f))
1211 self.do_genchecksum(os.path.join(destdir, f))
1213 def print_outimage_info(self):
1214 msg = "The new image can be found here:\n"
1215 self.outimage.sort()
1216 for file in self.outimage:
1217 msg += ' %s\n' % os.path.abspath(file)
1221 def check_depend_tools(self):
1222 for tool in self._dep_checks:
1223 fs.find_binary_path(tool)
1225 def package_output(self, image_format, destdir = ".", package="none"):
1226 if not package or package == "none":
1229 destdir = os.path.abspath(os.path.expanduser(destdir))
1230 (pkg, comp) = os.path.splitext(package)
1232 comp=comp.lstrip(".")
1236 dst = "%s/%s-%s.tar.%s" %\
1237 (destdir, self.name, image_format, comp)
1239 dst = "%s/%s-%s.tar" %\
1240 (destdir, self.name, image_format)
1242 msger.info("creating %s" % dst)
1243 tar = tarfile.open(dst, "w:" + comp)
1245 for file in self.outimage:
1246 msger.info("adding %s to %s" % (file, dst))
1248 arcname=os.path.join("%s-%s" \
1249 % (self.name, image_format),
1250 os.path.basename(file)))
1251 if os.path.isdir(file):
1252 shutil.rmtree(file, ignore_errors = True)
1258 '''All the file in outimage has been packaged into tar.* file'''
1259 self.outimage = [dst]
1261 def release_output(self, config, destdir, release):
1262 """ Create release directory and files
1266 """ release path """
1267 return os.path.join(destdir, fn)
1269 outimages = self.outimage
1272 new_kspath = _rpath(self.name+'.ks')
1273 with open(config) as fr:
1274 with open(new_kspath, "w") as wf:
1275 # When building a release we want to make sure the .ks
1276 # file generates the same build even when --release not used.
1277 wf.write(fr.read().replace("@BUILD_ID@", release))
1278 outimages.append(new_kspath)
1280 # save log file, logfile is only available in creator attrs
1281 if hasattr(self, 'releaselog') and self.releaselog:
1282 final_logfile = _rpath(self.name+'.log')
1283 shutil.move(self.logfile, final_logfile)
1284 self.logfile = final_logfile
1285 outimages.append(self.logfile)
1287 # rename iso and usbimg
1288 for f in os.listdir(destdir):
1289 if f.endswith(".iso"):
1290 newf = f[:-4] + '.img'
1291 elif f.endswith(".usbimg"):
1292 newf = f[:-7] + '.img'
1295 os.rename(_rpath(f), _rpath(newf))
1296 outimages.append(_rpath(newf))
1299 with open(_rpath("MD5SUMS"), "w") as wf:
1300 for f in os.listdir(destdir):
1304 if os.path.isdir(os.path.join(destdir, f)):
1307 md5sum = misc.get_md5sum(_rpath(f))
1308 # There needs to be two spaces between the sum and
1309 # filepath to match the syntax with md5sum.
1310 # This way also md5sum -c MD5SUMS can be used by users
1311 wf.write("%s *%s\n" % (md5sum, f))
1313 outimages.append("%s/MD5SUMS" % destdir)
1315 # Filter out the nonexist file
1316 for fp in outimages[:]:
1317 if not os.path.exists("%s" % fp):
1318 outimages.remove(fp)
1320 def copy_kernel(self):
1321 """ Copy kernel files to the outimage directory.
1322 NOTE: This needs to be called before unmounting the instroot.
1325 if not self._need_copy_kernel:
1328 if not os.path.exists(self.destdir):
1329 os.makedirs(self.destdir)
1331 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1332 kernelfilename = "%s/%s-%s" % (self.destdir,
1334 os.path.basename(kernel))
1335 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1337 shutil.copy(kernel, kernelfilename)
1338 self.outimage.append(kernelfilename)
1340 def copy_attachment(self):
1341 """ Subclass implement it to handle attachment files
1342 NOTE: This needs to be called before unmounting the instroot.
1346 def get_pkg_manager(self):
1347 return self.pkgmgr(target_arch = self.target_arch,
1348 instroot = self._instroot,
1349 cachedir = self.cachedir)