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.
985 def get_ssl_verify(ssl_verify=None):
986 if ssl_verify is not None:
987 return not ssl_verify.lower().strip() == 'no'
989 return not self.ssl_verify.lower().strip() == 'no'
991 # initialize pkg list to install
993 self.__sanity_check()
995 self._required_pkgs = \
996 kickstart.get_packages(self.ks, self._get_required_packages())
997 self._excluded_pkgs = \
998 kickstart.get_excluded(self.ks, self._get_excluded_packages())
999 self._required_groups = kickstart.get_groups(self.ks)
1001 self._required_pkgs = None
1002 self._excluded_pkgs = None
1003 self._required_groups = None
1006 repo_urls = self.extrarepos
1008 pkg_manager = self.get_pkg_manager()
1011 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1012 if 'debuginfo' in self.install_pkgs:
1013 pkg_manager.install_debuginfo = True
1015 for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1016 (name, baseurl, mirrorlist, inc, exc,
1017 proxy, proxy_username, proxy_password, debuginfo,
1018 source, gpgkey, disable, ssl_verify, nocache,
1019 cost, priority) = repo
1021 ssl_verify = get_ssl_verify(ssl_verify)
1022 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1023 proxy_username, proxy_password, inc, exc, ssl_verify,
1024 nocache, cost, priority)
1026 if kickstart.exclude_docs(self.ks):
1027 rpm.addMacro("_excludedocs", "1")
1028 rpm.addMacro("_dbpath", "/var/lib/rpm")
1029 rpm.addMacro("__file_context_path", "%{nil}")
1030 if kickstart.inst_langs(self.ks) != None:
1031 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1034 self.__preinstall_packages(pkg_manager)
1035 self.__select_packages(pkg_manager)
1036 self.__select_groups(pkg_manager)
1037 self.__deselect_packages(pkg_manager)
1038 self.__localinst_packages(pkg_manager)
1039 self.__check_packages(pkg_manager)
1041 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1042 checksize = self._root_fs_avail
1044 checksize -= BOOT_SAFEGUARD
1045 if self.target_arch:
1046 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1047 pkg_manager.runInstall(checksize)
1048 except CreatorError, e:
1050 except KeyboardInterrupt:
1053 self._pkgs_content = pkg_manager.getAllContent()
1054 self._pkgs_license = pkg_manager.getPkgsLicense()
1055 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1056 self.__attachment_packages(pkg_manager)
1063 # do some clean up to avoid lvm info leakage. this sucks.
1064 for subdir in ("cache", "backup", "archive"):
1065 lvmdir = self._instroot + "/etc/lvm/" + subdir
1067 for f in os.listdir(lvmdir):
1068 os.unlink(lvmdir + "/" + f)
1072 def postinstall(self):
1073 self.copy_attachment()
1075 def __run_post_scripts(self):
1076 msger.info("Running scripts ...")
1077 if os.path.exists(self._instroot + "/tmp"):
1078 shutil.rmtree(self._instroot + "/tmp")
1079 os.mkdir (self._instroot + "/tmp", 0755)
1080 for s in kickstart.get_post_scripts(self.ks):
1081 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1082 dir = self._instroot + "/tmp")
1084 s.script = s.script.replace("\r", "")
1085 os.write(fd, s.script)
1087 os.chmod(path, 0700)
1089 env = self._get_post_scripts_env(s.inChroot)
1090 if 'PATH' not in env:
1091 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1097 preexec = self._chroot
1098 script = "/tmp/" + os.path.basename(path)
1102 p = subprocess.Popen([s.interp, script],
1103 preexec_fn = preexec,
1105 stdout = subprocess.PIPE,
1106 stderr = subprocess.STDOUT)
1107 for entry in p.communicate()[0].splitlines():
1109 except OSError, (err, msg):
1110 raise CreatorError("Failed to execute %%post script "
1111 "with '%s' : %s" % (s.interp, msg))
1115 def __save_repo_keys(self, repodata):
1119 gpgkeydir = "/etc/pki/rpm-gpg"
1120 fs.makedirs(self._instroot + gpgkeydir)
1121 for repo in repodata:
1123 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1124 shutil.copy(repo["repokey"], self._instroot + repokey)
1126 def configure(self, repodata = None):
1127 """Configure the system image according to the kickstart.
1129 This method applies the (e.g. keyboard or network) configuration
1130 specified in the kickstart and executes the kickstart %post scripts.
1132 If necessary, it also prepares the image to be bootable by e.g.
1133 creating an initrd and bootloader configuration.
1136 ksh = self.ks.handler
1138 msger.info('Applying configurations ...')
1140 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1141 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1142 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1143 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1144 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1145 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1146 kickstart.UserConfig(self._instroot).apply(ksh.user)
1147 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1148 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1149 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1150 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1151 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1152 self.__save_repo_keys(repodata)
1153 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1155 msger.warning("Failed to apply configuration to image")
1158 self._create_bootconfig()
1159 self.__run_post_scripts()
1161 def launch_shell(self, launch):
1162 """Launch a shell in the install root.
1164 This method is launches a bash shell chroot()ed in the install root;
1165 this can be useful for debugging.
1169 msger.info("Launching shell. Exit to continue.")
1170 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1172 def do_genchecksum(self, image_name):
1173 if not self._genchecksum:
1176 md5sum = misc.get_md5sum(image_name)
1177 with open(image_name + ".md5sum", "w") as f:
1178 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1179 self.outimage.append(image_name+".md5sum")
1181 def package(self, destdir = "."):
1182 """Prepares the created image for final delivery.
1184 In its simplest form, this method merely copies the install root to the
1185 supplied destination directory; other subclasses may choose to package
1186 the image by e.g. creating a bootable ISO containing the image and
1187 bootloader configuration.
1189 destdir -- the directory into which the final image should be moved;
1190 this defaults to the current directory.
1193 self._stage_final_image()
1195 if not os.path.exists(destdir):
1196 fs.makedirs(destdir)
1198 if self._recording_pkgs:
1199 self._save_recording_pkgs(destdir)
1201 # For image formats with two or multiple image files, it will be
1202 # better to put them under a directory
1203 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1204 destdir = os.path.join(destdir, "%s-%s" \
1205 % (self.name, self.image_format))
1206 msger.debug("creating destination dir: %s" % destdir)
1207 fs.makedirs(destdir)
1209 # Ensure all data is flushed to _outdir
1210 runner.quiet('sync')
1212 misc.check_space_pre_cp(self._outdir, destdir)
1213 for f in os.listdir(self._outdir):
1214 shutil.move(os.path.join(self._outdir, f),
1215 os.path.join(destdir, f))
1216 self.outimage.append(os.path.join(destdir, f))
1217 self.do_genchecksum(os.path.join(destdir, f))
1219 def print_outimage_info(self):
1220 msg = "The new image can be found here:\n"
1221 self.outimage.sort()
1222 for file in self.outimage:
1223 msg += ' %s\n' % os.path.abspath(file)
1227 def check_depend_tools(self):
1228 for tool in self._dep_checks:
1229 fs.find_binary_path(tool)
1231 def package_output(self, image_format, destdir = ".", package="none"):
1232 if not package or package == "none":
1235 destdir = os.path.abspath(os.path.expanduser(destdir))
1236 (pkg, comp) = os.path.splitext(package)
1238 comp=comp.lstrip(".")
1242 dst = "%s/%s-%s.tar.%s" %\
1243 (destdir, self.name, image_format, comp)
1245 dst = "%s/%s-%s.tar" %\
1246 (destdir, self.name, image_format)
1248 msger.info("creating %s" % dst)
1249 tar = tarfile.open(dst, "w:" + comp)
1251 for file in self.outimage:
1252 msger.info("adding %s to %s" % (file, dst))
1254 arcname=os.path.join("%s-%s" \
1255 % (self.name, image_format),
1256 os.path.basename(file)))
1257 if os.path.isdir(file):
1258 shutil.rmtree(file, ignore_errors = True)
1264 '''All the file in outimage has been packaged into tar.* file'''
1265 self.outimage = [dst]
1267 def release_output(self, config, destdir, release):
1268 """ Create release directory and files
1272 """ release path """
1273 return os.path.join(destdir, fn)
1275 outimages = self.outimage
1278 new_kspath = _rpath(self.name+'.ks')
1279 with open(config) as fr:
1280 with open(new_kspath, "w") as wf:
1281 # When building a release we want to make sure the .ks
1282 # file generates the same build even when --release not used.
1283 wf.write(fr.read().replace("@BUILD_ID@", release))
1284 outimages.append(new_kspath)
1286 # save log file, logfile is only available in creator attrs
1287 if hasattr(self, 'releaselog') and self.releaselog:
1288 final_logfile = _rpath(self.name+'.log')
1289 shutil.move(self.logfile, final_logfile)
1290 self.logfile = final_logfile
1291 outimages.append(self.logfile)
1293 # rename iso and usbimg
1294 for f in os.listdir(destdir):
1295 if f.endswith(".iso"):
1296 newf = f[:-4] + '.img'
1297 elif f.endswith(".usbimg"):
1298 newf = f[:-7] + '.img'
1301 os.rename(_rpath(f), _rpath(newf))
1302 outimages.append(_rpath(newf))
1305 with open(_rpath("MD5SUMS"), "w") as wf:
1306 for f in os.listdir(destdir):
1310 if os.path.isdir(os.path.join(destdir, f)):
1313 md5sum = misc.get_md5sum(_rpath(f))
1314 # There needs to be two spaces between the sum and
1315 # filepath to match the syntax with md5sum.
1316 # This way also md5sum -c MD5SUMS can be used by users
1317 wf.write("%s %s\n" % (md5sum, f))
1319 outimages.append("%s/MD5SUMS" % destdir)
1321 # Filter out the nonexist file
1322 for fp in outimages[:]:
1323 if not os.path.exists("%s" % fp):
1324 outimages.remove(fp)
1326 def copy_kernel(self):
1327 """ Copy kernel files to the outimage directory.
1328 NOTE: This needs to be called before unmounting the instroot.
1331 if not self._need_copy_kernel:
1334 if not os.path.exists(self.destdir):
1335 os.makedirs(self.destdir)
1337 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1338 kernelfilename = "%s/%s-%s" % (self.destdir,
1340 os.path.basename(kernel))
1341 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1343 shutil.copy(kernel, kernelfilename)
1344 self.outimage.append(kernelfilename)
1346 def copy_attachment(self):
1347 """ Subclass implement it to handle attachment files
1348 NOTE: This needs to be called before unmounting the instroot.
1352 def get_pkg_manager(self):
1353 return self.pkgmgr(target_arch = self.target_arch,
1354 instroot = self._instroot,
1355 cachedir = self.cachedir)