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
30 from datetime import datetime
34 from mic import kickstart
35 from mic import msger, __version__ as VERSION
36 from mic.utils.errors import CreatorError, Abort
37 from mic.utils import misc, grabber, runner, fs_related as fs
38 from mic.chroot import kill_proc_inchroot
39 from mic.archive import get_archive_suffixes
40 from mic.conf import configmgr
41 #post script max run time
44 class BaseImageCreator(object):
45 """Installs a system to a chroot directory.
47 ImageCreator is the simplest creator class available; it will install and
48 configure a system image according to the supplied kickstart file.
52 import mic.imgcreate as imgcreate
53 ks = imgcreate.read_kickstart("foo.ks")
54 imgcreate.ImageCreator(ks, "foo").create()
63 def __init__(self, createopts = None, pkgmgr = None):
64 """Initialize an ImageCreator instance.
66 ks -- a pykickstart.KickstartParser instance; this instance will be
67 used to drive the install by e.g. providing the list of packages
68 to be installed, the system configuration and %post scripts
70 name -- a name for the image; used for e.g. image filenames or
77 self.__builddir = None
78 self.__bindmounts = []
82 self.tmpdir = "/var/tmp/mic"
83 self.cachedir = "/var/tmp/mic/cache"
84 self.workdir = "/var/tmp/mic/build"
86 self.installerfw_prefix = "INSTALLERFW_"
87 self.target_arch = "noarch"
88 self.strict_mode = False
89 self._local_pkgs_path = None
92 self.multiple_partitions = False
95 # If the kernel is save to the destdir when copy_kernel cmd is called.
96 self._need_copy_kernel = False
97 # setup tmpfs tmpdir when enabletmpfs is True
98 self.enabletmpfs = False
101 # Mapping table for variables that have different names.
102 optmap = {"pkgmgr" : "pkgmgr_name",
103 "arch" : "target_arch",
104 "local_pkgs_path" : "_local_pkgs_path",
105 "copy_kernel" : "_need_copy_kernel",
106 "strict_mode" : "strict_mode",
109 # update setting from createopts
110 for key in createopts.keys():
115 setattr(self, option, createopts[key])
117 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
120 if '@NAME@' in self.pack_to:
121 self.pack_to = self.pack_to.replace('@NAME@', self.name)
122 (tar, ext) = os.path.splitext(self.pack_to)
123 if ext in (".gz", ".bz2", ".lzo", ".bz") and tar.endswith(".tar"):
125 if ext not in get_archive_suffixes():
126 self.pack_to += ".tar"
128 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
130 # Output image file names
132 # Output info related with manifest
133 self.image_files = {}
134 # A flag to generate checksum
135 self._genchecksum = False
137 self._alt_initrd_name = None
139 self._recording_pkgs = []
141 # available size in root fs, init to 0
142 self._root_fs_avail = 0
144 # Name of the disk image file that is created.
145 self._img_name = None
147 self.image_format = None
149 # Save qemu emulator file names in order to clean up it finally
150 self.qemu_emulators = []
152 # No ks provided when called by convertor, so skip the dependency check
154 # If we have btrfs partition we need to check necessary tools
155 for part in self.ks.handler.partition.partitions:
156 if part.fstype and part.fstype == "btrfs":
157 self._dep_checks.append("mkfs.btrfs")
159 if part.fstype == "cpio":
161 if len(self.ks.handler.partition.partitions) > 1:
162 self.multiple_partitions = True
165 if self.target_arch.startswith("arm"):
166 for dep in self._dep_checks:
167 if dep == "extlinux":
168 self._dep_checks.remove(dep)
170 if not os.path.exists("/usr/bin/qemu-arm") or \
171 not misc.is_statically_linked("/usr/bin/qemu-arm"):
172 self._dep_checks.append("qemu-arm-static")
174 if os.path.exists("/proc/sys/vm/vdso_enabled"):
175 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
176 vdso_value = vdso_fh.read().strip()
178 if (int)(vdso_value) == 1:
179 msger.warning("vdso is enabled on your host, which might "
180 "cause problems with arm emulations.\n"
181 "\tYou can disable vdso with following command before "
182 "starting image build:\n"
183 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
184 elif self.target_arch == "mipsel":
185 for dep in self._dep_checks:
186 if dep == "extlinux":
187 self._dep_checks.remove(dep)
189 if not os.path.exists("/usr/bin/qemu-mipsel") or \
190 not misc.is_statically_linked("/usr/bin/qemu-mipsel"):
191 self._dep_checks.append("qemu-mipsel-static")
193 if os.path.exists("/proc/sys/vm/vdso_enabled"):
194 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
195 vdso_value = vdso_fh.read().strip()
197 if (int)(vdso_value) == 1:
198 msger.warning("vdso is enabled on your host, which might "
199 "cause problems with mipsel emulations.\n"
200 "\tYou can disable vdso with following command before "
201 "starting image build:\n"
202 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
204 # make sure the specified tmpdir and cachedir exist
205 if not os.path.exists(self.tmpdir):
206 os.makedirs(self.tmpdir)
207 if not os.path.exists(self.cachedir):
208 os.makedirs(self.cachedir)
214 def __get_instroot(self):
215 if self.__builddir is None:
216 raise CreatorError("_instroot is not valid before calling mount()")
217 return self.__builddir + "/install_root"
218 _instroot = property(__get_instroot)
219 """The location of the install root directory.
221 This is the directory into which the system is installed. Subclasses may
222 mount a filesystem image here or copy files to/from here.
224 Note, this directory does not exist before ImageCreator.mount() is called.
226 Note also, this is a read-only attribute.
230 def __get_outdir(self):
231 if self.__builddir is None:
232 raise CreatorError("_outdir is not valid before calling mount()")
233 return self.__builddir + "/out"
234 _outdir = property(__get_outdir)
235 """The staging location for the final image.
237 This is where subclasses should stage any files that are part of the final
238 image. ImageCreator.package() will copy any files found here into the
239 requested destination directory.
241 Note, this directory does not exist before ImageCreator.mount() is called.
243 Note also, this is a read-only attribute.
249 # Hooks for subclasses
251 def _mount_instroot(self, base_on = None):
252 """Mount or prepare the install root directory.
254 This is the hook where subclasses may prepare the install root by e.g.
255 mounting creating and loopback mounting a filesystem image to
258 There is no default implementation.
260 base_on -- this is the value passed to mount() and can be interpreted
261 as the subclass wishes; it might e.g. be the location of
262 a previously created ISO containing a system image.
267 def _unmount_instroot(self):
268 """Undo anything performed in _mount_instroot().
270 This is the hook where subclasses must undo anything which was done
271 in _mount_instroot(). For example, if a filesystem image was mounted
272 onto _instroot, it should be unmounted here.
274 There is no default implementation.
279 def _create_bootconfig(self):
280 """Configure the image so that it's bootable.
282 This is the hook where subclasses may prepare the image for booting by
283 e.g. creating an initramfs and bootloader configuration.
285 This hook is called while the install root is still mounted, after the
286 packages have been installed and the kickstart configuration has been
287 applied, but before the %post scripts have been executed.
289 There is no default implementation.
294 def _stage_final_image(self):
295 """Stage the final system image in _outdir.
297 This is the hook where subclasses should place the image in _outdir
298 so that package() can copy it to the requested destination directory.
300 By default, this moves the install root into _outdir.
303 shutil.move(self._instroot, self._outdir + "/" + self.name)
305 def get_installed_packages(self):
306 return self._pkgs_content.keys()
308 def _save_recording_pkgs(self, destdir):
309 """Save the list or content of installed packages to file.
311 pkgs = self._pkgs_content.keys()
312 pkgs.sort() # inplace op
314 if not os.path.exists(destdir):
318 if 'vcs' in self._recording_pkgs:
319 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
320 content = '\n'.join(sorted(vcslst))
321 elif 'name' in self._recording_pkgs:
322 content = '\n'.join(pkgs)
324 namefile = os.path.join(destdir, self.name + '.packages')
325 f = open(namefile, "w")
328 self.outimage.append(namefile);
330 # if 'content', save more details
331 if 'content' in self._recording_pkgs:
332 contfile = os.path.join(destdir, self.name + '.files')
333 f = open(contfile, "w")
338 pkgcont = self._pkgs_content[pkg]
340 content += '\n '.join(pkgcont)
346 self.outimage.append(contfile)
348 if 'license' in self._recording_pkgs:
349 licensefile = os.path.join(destdir, self.name + '.license')
350 f = open(licensefile, "w")
352 f.write('Summary:\n')
353 for license in reversed(sorted(self._pkgs_license, key=\
354 lambda license: len(self._pkgs_license[license]))):
355 f.write(" - %s: %s\n" \
356 % (license, len(self._pkgs_license[license])))
358 f.write('\nDetails:\n')
359 for license in reversed(sorted(self._pkgs_license, key=\
360 lambda license: len(self._pkgs_license[license]))):
361 f.write(" - %s:\n" % (license))
362 for pkg in sorted(self._pkgs_license[license]):
363 f.write(" - %s\n" % (pkg))
367 self.outimage.append(licensefile)
369 def _get_required_packages(self):
370 """Return a list of required packages.
372 This is the hook where subclasses may specify a set of packages which
373 it requires to be installed.
375 This returns an empty list by default.
377 Note, subclasses should usually chain up to the base class
378 implementation of this hook.
383 def _get_excluded_packages(self):
384 """Return a list of excluded packages.
386 This is the hook where subclasses may specify a set of packages which
387 it requires _not_ to be installed.
389 This returns an empty list by default.
391 Note, subclasses should usually chain up to the base class
392 implementation of this hook.
397 def _get_local_packages(self):
398 """Return a list of rpm path to be local installed.
400 This is the hook where subclasses may specify a set of rpms which
401 it requires to be installed locally.
403 This returns an empty list by default.
405 Note, subclasses should usually chain up to the base class
406 implementation of this hook.
409 if self._local_pkgs_path:
410 if os.path.isdir(self._local_pkgs_path):
412 os.path.join(self._local_pkgs_path, '*.rpm'))
413 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
414 return [self._local_pkgs_path]
418 def _get_fstab(self):
419 """Return the desired contents of /etc/fstab.
421 This is the hook where subclasses may specify the contents of
422 /etc/fstab by returning a string containing the desired contents.
424 A sensible default implementation is provided.
427 s = "/dev/root / %s %s 0 0\n" \
429 "defaults,noatime" if not self._fsopts else self._fsopts)
430 s += self._get_fstab_special()
433 def _get_fstab_special(self):
434 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
435 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
436 s += "proc /proc proc defaults 0 0\n"
437 s += "sysfs /sys sysfs defaults 0 0\n"
440 def _set_part_env(self, pnum, prop, value):
441 """ This is a helper function which generates an environment variable
442 for a property "prop" with value "value" of a partition number "pnum".
444 The naming convention is:
445 * Variables start with INSTALLERFW_PART
446 * Then goes the partition number, the order is the same as
447 specified in the KS file
448 * Then goes the property name
456 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
457 return { name : value }
459 def _get_post_scripts_env(self, in_chroot):
460 """Return an environment dict for %post scripts.
462 This is the hook where subclasses may specify some environment
463 variables for %post scripts by return a dict containing the desired
466 in_chroot -- whether this %post script is to be executed chroot()ed
473 for p in kickstart.get_partitions(self.ks):
474 env.update(self._set_part_env(pnum, "SIZE", p.size))
475 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
476 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
477 env.update(self._set_part_env(pnum, "LABEL", p.label))
478 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
479 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
480 env.update(self._set_part_env(pnum, "ALIGN", p.align))
481 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
482 env.update(self._set_part_env(pnum, "UUID", p.uuid))
483 env.update(self._set_part_env(pnum, "DEVNODE",
484 "/dev/%s%d" % (p.disk, pnum + 1)))
485 env.update(self._set_part_env(pnum, "DISK_DEVNODE",
490 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
492 # Partition table format
493 ptable_format = self.ks.handler.bootloader.ptable
494 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
496 # The kerned boot parameters
497 kernel_opts = self.ks.handler.bootloader.appendLine
498 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
500 # Name of the image creation tool
501 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
503 # The real current location of the mounted file-systems
507 mount_prefix = self._instroot
508 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
510 # These are historical variables which lack the common name prefix
512 env["INSTALL_ROOT"] = self._instroot
513 env["IMG_NAME"] = self._name
517 def __get_imgname(self):
519 _name = property(__get_imgname)
520 """The name of the image file.
524 def _get_kernel_versions(self):
525 """Return a dict detailing the available kernel types/versions.
527 This is the hook where subclasses may override what kernel types and
528 versions should be available for e.g. creating the booloader
531 A dict should be returned mapping the available kernel types to a list
532 of the available versions for those kernels.
534 The default implementation uses rpm to iterate over everything
535 providing 'kernel', finds /boot/vmlinuz-* and returns the version
536 obtained from the vmlinuz filename. (This can differ from the kernel
537 RPM's n-v-r in the case of e.g. xen)
540 def get_kernel_versions(instroot):
543 files = glob.glob(instroot + "/boot/vmlinuz-*")
545 version = os.path.basename(file)[8:]
548 versions.add(version)
549 ret["kernel"] = list(versions)
552 def get_version(header):
554 for f in header['filenames']:
555 if f.startswith('/boot/vmlinuz-'):
560 return get_kernel_versions(self._instroot)
562 ts = rpm.TransactionSet(self._instroot)
565 for header in ts.dbMatch('provides', 'kernel'):
566 version = get_version(header)
570 name = header['name']
572 ret[name] = [version]
573 elif not version in ret[name]:
574 ret[name].append(version)
580 # Helpers for subclasses
582 def _do_bindmounts(self):
583 """Mount various system directories onto _instroot.
585 This method is called by mount(), but may also be used by subclasses
586 in order to re-mount the bindmounts after modifying the underlying
590 for b in self.__bindmounts:
593 def _undo_bindmounts(self):
594 """Unmount the bind-mounted system directories from _instroot.
596 This method is usually only called by unmount(), but may also be used
597 by subclasses in order to gain access to the filesystem obscured by
598 the bindmounts - e.g. in order to create device nodes on the image
602 self.__bindmounts.reverse()
603 for b in self.__bindmounts:
607 """Chroot into the install root.
609 This method may be used by subclasses when executing programs inside
610 the install root e.g.
612 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
615 os.chroot(self._instroot)
618 def _mkdtemp(self, prefix = "tmp-"):
619 """Create a temporary directory.
621 This method may be used by subclasses to create a temporary directory
622 for use in building the final image - e.g. a subclass might create
623 a temporary directory in order to bundle a set of files into a package.
625 The subclass may delete this directory if it wishes, but it will be
626 automatically deleted by cleanup().
628 The absolute path to the temporary directory is returned.
630 Note, this method should only be called after mount() has been called.
632 prefix -- a prefix which should be used when creating the directory;
636 self.__ensure_builddir()
637 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
639 def _mkstemp(self, prefix = "tmp-"):
640 """Create a temporary file.
642 This method may be used by subclasses to create a temporary file
643 for use in building the final image - e.g. a subclass might need
644 a temporary location to unpack a compressed file.
646 The subclass may delete this file if it wishes, but it will be
647 automatically deleted by cleanup().
649 A tuple containing a file descriptor (returned from os.open() and the
650 absolute path to the temporary directory is returned.
652 Note, this method should only be called after mount() has been called.
654 prefix -- a prefix which should be used when creating the file;
658 self.__ensure_builddir()
659 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
661 def _mktemp(self, prefix = "tmp-"):
662 """Create a temporary file.
664 This method simply calls _mkstemp() and closes the returned file
667 The absolute path to the temporary file is returned.
669 Note, this method should only be called after mount() has been called.
671 prefix -- a prefix which should be used when creating the file;
676 (f, path) = self._mkstemp(prefix)
682 # Actual implementation
684 def __ensure_builddir(self):
685 if not self.__builddir is None:
689 self.workdir = os.path.join(self.tmpdir, "build")
690 if not os.path.exists(self.workdir):
691 os.makedirs(self.workdir)
692 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
693 prefix = "imgcreate-")
694 except OSError, (err, msg):
695 raise CreatorError("Failed create build directory in %s: %s" %
698 def get_cachedir(self, cachedir = None):
702 self.__ensure_builddir()
704 self.cachedir = cachedir
706 self.cachedir = self.__builddir + "/mic-cache"
707 fs.makedirs(self.cachedir)
710 def __sanity_check(self):
711 """Ensure that the config we've been given is same."""
712 if not (kickstart.get_packages(self.ks) or
713 kickstart.get_groups(self.ks)):
714 raise CreatorError("No packages or groups specified")
716 kickstart.convert_method_to_repo(self.ks)
718 if not kickstart.get_repos(self.ks):
719 raise CreatorError("No repositories specified")
721 def __write_fstab(self):
722 if kickstart.use_installerfw(self.ks, "fstab"):
723 # The fstab file will be generated by installer framework scripts
726 fstab_contents = self._get_fstab()
728 fstab = open(self._instroot + "/etc/fstab", "w")
729 fstab.write(fstab_contents)
732 def __create_minimal_dev(self):
733 """Create a minimal /dev so that we don't corrupt the host /dev"""
734 origumask = os.umask(0000)
735 devices = (('null', 1, 3, 0666),
736 ('urandom',1, 9, 0666),
737 ('random', 1, 8, 0666),
738 ('full', 1, 7, 0666),
739 ('ptmx', 5, 2, 0666),
741 ('zero', 1, 5, 0666))
743 links = (("/proc/self/fd", "/dev/fd"),
744 ("/proc/self/fd/0", "/dev/stdin"),
745 ("/proc/self/fd/1", "/dev/stdout"),
746 ("/proc/self/fd/2", "/dev/stderr"))
748 for (node, major, minor, perm) in devices:
749 if not os.path.exists(self._instroot + "/dev/" + node):
750 os.mknod(self._instroot + "/dev/" + node,
752 os.makedev(major,minor))
754 for (src, dest) in links:
755 if not os.path.exists(self._instroot + dest):
756 os.symlink(src, self._instroot + dest)
760 def __setup_tmpdir(self):
761 if not self.enabletmpfs:
764 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
766 def __clean_tmpdir(self):
767 if not self.enabletmpfs:
770 runner.show('umount -l %s' % self.workdir)
773 #Add tpk-install option
774 createopts = configmgr.create
775 if createopts['tpk_install']:
776 path = createopts['tpk_install']
777 file_list = os.listdir(path)
779 sub = os.path.splitext(f)[1]
781 raise CreatorError("Not all files in the path: "+path +" is tpk")
783 tpk_dir = "/usr/apps/.preload-tpk"
784 fs.makedirs(self._instroot + "/usr/apps")
785 fs.makedirs(self._instroot + tpk_dir)
787 shutil.copy(path+"/"+f,self._instroot + tpk_dir)
789 def mount(self, base_on = None, cachedir = None):
790 """Setup the target filesystem in preparation for an install.
792 This function sets up the filesystem which the ImageCreator will
793 install into and configure. The ImageCreator class merely creates an
794 install root directory, bind mounts some system directories (e.g. /dev)
795 and writes out /etc/fstab. Other subclasses may also e.g. create a
796 sparse file, format it and loopback mount it to the install root.
798 base_on -- a previous install on which to base this install; defaults
799 to None, causing a new image to be created
801 cachedir -- a directory in which to store the Yum cache; defaults to
802 None, causing a new cache to be created; by setting this
803 to another directory, the same cache can be reused across
807 self.__setup_tmpdir()
808 self.__ensure_builddir()
810 # prevent popup dialog in Ubuntu(s)
811 misc.hide_loopdev_presentation()
813 fs.makedirs(self._instroot)
814 fs.makedirs(self._outdir)
816 self._mount_instroot(base_on)
818 for d in ("/dev/pts",
825 fs.makedirs(self._instroot + d)
827 if self.target_arch and self.target_arch.startswith("arm") or \
828 self.target_arch == "aarch64":
829 self.qemu_emulators = misc.setup_qemu_emulator(self._instroot,
832 self.get_cachedir(cachedir)
834 # bind mount system directories into _instroot
835 for (f, dest) in [("/sys", None),
837 ("/proc/sys/fs/binfmt_misc", None),
839 self.__bindmounts.append(
841 f, self._instroot, dest))
843 self._do_bindmounts()
845 self.__create_minimal_dev()
847 if os.path.exists(self._instroot + "/etc/mtab"):
848 os.unlink(self._instroot + "/etc/mtab")
849 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
853 # get size of available space in 'instroot' fs
854 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
858 """Unmounts the target filesystem.
860 The ImageCreator class detaches the system from the install root, but
861 other subclasses may also detach the loopback mounted filesystem image
862 from the install root.
866 mtab = self._instroot + "/etc/mtab"
867 if not os.path.islink(mtab):
868 os.unlink(self._instroot + "/etc/mtab")
870 for qemu_emulator in self.qemu_emulators:
871 os.unlink(self._instroot + qemu_emulator)
875 self._undo_bindmounts()
877 """ Clean up yum garbage """
879 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
880 if os.path.exists(instroot_pdir):
881 shutil.rmtree(instroot_pdir, ignore_errors = True)
882 yumlibdir = self._instroot + "/var/lib/yum"
883 if os.path.exists(yumlibdir):
884 shutil.rmtree(yumlibdir, ignore_errors = True)
888 self._unmount_instroot()
890 # reset settings of popup dialog in Ubuntu(s)
891 misc.unhide_loopdev_presentation()
895 """Unmounts the target filesystem and deletes temporary files.
897 This method calls unmount() and then deletes any temporary files and
898 directories that were created on the host system while building the
901 Note, make sure to call this method once finished with the creator
902 instance in order to ensure no stale files are left on the host e.g.:
904 creator = ImageCreator(ks, name)
911 if not self.__builddir:
914 kill_proc_inchroot(self._instroot)
918 shutil.rmtree(self.__builddir, ignore_errors = True)
919 self.__builddir = None
921 self.__clean_tmpdir()
923 def __is_excluded_pkg(self, pkg):
924 if pkg in self._excluded_pkgs:
925 self._excluded_pkgs.remove(pkg)
928 for xpkg in self._excluded_pkgs:
929 if xpkg.endswith('*'):
930 if pkg.startswith(xpkg[:-1]):
932 elif xpkg.startswith('*'):
933 if pkg.endswith(xpkg[1:]):
938 def __select_packages(self, pkg_manager):
940 for pkg in self._required_pkgs:
941 e = pkg_manager.selectPackage(pkg)
943 if kickstart.ignore_missing(self.ks):
944 skipped_pkgs.append(pkg)
945 elif self.__is_excluded_pkg(pkg):
946 skipped_pkgs.append(pkg)
948 raise CreatorError("Failed to find package '%s' : %s" %
951 for pkg in skipped_pkgs:
952 msger.warning("Skipping missing package '%s'" % (pkg,))
954 def __select_groups(self, pkg_manager):
956 for group in self._required_groups:
957 e = pkg_manager.selectGroup(group.name, group.include)
959 if kickstart.ignore_missing(self.ks):
960 skipped_groups.append(group)
962 raise CreatorError("Failed to find group '%s' : %s" %
965 for group in skipped_groups:
966 msger.warning("Skipping missing group '%s'" % (group.name,))
968 def __deselect_packages(self, pkg_manager):
969 for pkg in self._excluded_pkgs:
970 pkg_manager.deselectPackage(pkg)
972 def __localinst_packages(self, pkg_manager):
973 for rpm_path in self._get_local_packages():
974 pkg_manager.installLocal(rpm_path)
976 def __preinstall_packages(self, pkg_manager):
980 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
981 for pkg in self._preinstall_pkgs:
982 pkg_manager.preInstall(pkg)
984 def __check_packages(self, pkg_manager):
985 for pkg in self.check_pkgs:
986 pkg_manager.checkPackage(pkg)
988 def __attachment_packages(self, pkg_manager):
992 self._attachment = []
993 for item in kickstart.get_attachment(self.ks):
994 if item.startswith('/'):
995 fpaths = os.path.join(self._instroot, item.lstrip('/'))
996 for fpath in glob.glob(fpaths):
997 self._attachment.append(fpath)
1000 filelist = pkg_manager.getFilelist(item)
1002 # found rpm in rootfs
1003 for pfile in pkg_manager.getFilelist(item):
1004 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
1005 self._attachment.append(fpath)
1008 # try to retrieve rpm file
1009 (url, proxies) = pkg_manager.package_url(item)
1011 msger.warning("Can't get url from repo for %s" % item)
1013 fpath = os.path.join(self.cachedir, os.path.basename(url))
1014 if not os.path.exists(fpath):
1017 fpath = grabber.myurlgrab(url.full, fpath, proxies, None)
1018 except CreatorError:
1021 tmpdir = self._mkdtemp()
1022 misc.extract_rpm(fpath, tmpdir)
1023 for (root, dirs, files) in os.walk(tmpdir):
1025 fpath = os.path.join(root, fname)
1026 self._attachment.append(fpath)
1028 def install(self, repo_urls=None):
1029 """Install packages into the install root.
1031 This function installs the packages listed in the supplied kickstart
1032 into the install root. By default, the packages are installed from the
1033 repository URLs specified in the kickstart.
1035 repo_urls -- a dict which maps a repository name to a repository;
1036 if supplied, this causes any repository URLs specified in
1037 the kickstart to be overridden.
1040 def checkScriptletError(dirname, suffix):
1041 if os.path.exists(dirname):
1042 list = os.listdir(dirname)
1044 filepath = os.path.join(dirname, line)
1045 if os.path.isfile(filepath) and 0 < line.find(suffix):
1052 def showErrorInfo(filepath):
1053 if os.path.isfile(filepath):
1054 for line in open(filepath):
1055 msger.info("The error install package info: %s" % line)
1057 msger.info("%s is not found." % filepath)
1059 def get_ssl_verify(ssl_verify=None):
1060 if ssl_verify is not None:
1061 return not ssl_verify.lower().strip() == 'no'
1063 return not self.ssl_verify.lower().strip() == 'no'
1065 # initialize pkg list to install
1067 self.__sanity_check()
1069 self._required_pkgs = \
1070 kickstart.get_packages(self.ks, self._get_required_packages())
1071 self._excluded_pkgs = \
1072 kickstart.get_excluded(self.ks, self._get_excluded_packages())
1073 self._required_groups = kickstart.get_groups(self.ks)
1075 self._required_pkgs = None
1076 self._excluded_pkgs = None
1077 self._required_groups = None
1080 repo_urls = self.extrarepos
1082 pkg_manager = self.get_pkg_manager()
1085 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1086 if 'debuginfo' in self.install_pkgs:
1087 pkg_manager.install_debuginfo = True
1089 for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1090 (name, baseurl, mirrorlist, inc, exc,
1091 proxy, proxy_username, proxy_password, debuginfo,
1092 source, gpgkey, disable, ssl_verify, nocache,
1093 cost, priority) = repo
1095 ssl_verify = get_ssl_verify(ssl_verify)
1096 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1097 proxy_username, proxy_password, inc, exc, ssl_verify,
1098 nocache, cost, priority)
1100 if kickstart.exclude_docs(self.ks):
1101 rpm.addMacro("_excludedocs", "1")
1102 rpm.addMacro("_dbpath", "/var/lib/rpm")
1103 rpm.addMacro("__file_context_path", "%{nil}")
1104 if kickstart.inst_langs(self.ks) != None:
1105 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1108 self.__preinstall_packages(pkg_manager)
1109 self.__select_packages(pkg_manager)
1110 self.__select_groups(pkg_manager)
1111 self.__deselect_packages(pkg_manager)
1112 self.__localinst_packages(pkg_manager)
1113 self.__check_packages(pkg_manager)
1115 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1116 checksize = self._root_fs_avail
1118 checksize -= BOOT_SAFEGUARD
1119 if self.target_arch:
1120 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1122 # If we have multiple partitions, don't check diskspace when rpm run transaction
1123 # because rpm check '/' partition only.
1124 if self.multiple_partitions:
1125 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_DISKSPACE)
1126 pkg_manager.runInstall(checksize)
1127 except CreatorError, e:
1129 except KeyboardInterrupt:
1132 self._pkgs_content = pkg_manager.getAllContent()
1133 self._pkgs_license = pkg_manager.getPkgsLicense()
1134 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1135 self.__attachment_packages(pkg_manager)
1139 if checkScriptletError(self._instroot + "/tmp/.postscript/error/", "_error"):
1140 showErrorInfo(self._instroot + "/tmp/.preload_install_error")
1141 raise CreatorError('scriptlet errors occurred')
1146 # do some clean up to avoid lvm info leakage. this sucks.
1147 for subdir in ("cache", "backup", "archive"):
1148 lvmdir = self._instroot + "/etc/lvm/" + subdir
1150 for f in os.listdir(lvmdir):
1151 os.unlink(lvmdir + "/" + f)
1155 def postinstall(self):
1156 self.copy_attachment()
1158 def _get_sign_scripts_env(self):
1159 """Return an environment dict for %post-umount scripts.
1161 This is the hook where subclasses may specify some environment
1162 variables for %post-umount scripts by return a dict containing the
1163 desired environment.
1168 # Directory path of images
1170 env['IMG_DIR_PATH'] = str(self._imgdir)
1174 for item in self._instloops:
1175 imgfiles.append(item['name'])
1177 imgpaths.append(os.path.join(self._imgdir, item['name']))
1180 env['IMG_FILES'] = ' '.join(imgfiles)
1182 # Absolute path of images
1183 env['IMG_PATHS'] = ' '.join(imgpaths)
1187 def run_sign_scripts(self):
1188 if kickstart.get_sign_scripts(self.ks)==[]:
1190 msger.info("Running sign scripts ...")
1191 if os.path.exists(self._instroot + "/tmp"):
1192 shutil.rmtree(self._instroot + "/tmp")
1193 os.mkdir (self._instroot + "/tmp", 0755)
1194 for s in kickstart.get_sign_scripts(self.ks):
1195 (fd, path) = tempfile.mkstemp(prefix = "ks-runscript-",
1196 dir = self._instroot + "/tmp")
1197 s.script = s.script.replace("\r", "")
1198 os.write(fd, s.script)
1199 if s.interp == '/bin/sh' or s.interp == '/bin/bash':
1201 os.write(fd, 'exit 0\n')
1203 os.chmod(path, 0700)
1205 for item in os.listdir(self._imgdir):
1206 sub = os.path.splitext(item)[1]
1208 shutil.move(os.path.join(self._imgdir, item),
1209 os.path.join(self._instroot + "/tmp", item))
1210 oldoutdir = os.getcwd()
1211 os.chdir(self._instroot + "/tmp")
1213 env = self._get_sign_scripts_env()
1214 #*.img files are moved to self._instroot + "/tmp" directory in running runscripts
1215 env['IMG_PATHS'] = env['IMG_PATHS'].replace(self._imgdir,self._instroot + "/tmp")
1218 p = subprocess.Popen([s.interp, path],
1220 stdout = subprocess.PIPE,
1221 stderr = subprocess.STDOUT)
1222 while p.poll() == None:
1223 msger.info(p.stdout.readline().strip())
1224 if p.returncode != 0:
1225 raise CreatorError("Failed to execute %%sign script "
1226 "with '%s'" % (s.interp))
1227 except OSError, (err, msg):
1228 raise CreatorError("Failed to execute %%sign script "
1229 "with '%s' : %s" % (s.interp, msg))
1233 for item in os.listdir(self._instroot + "/tmp"):
1234 shutil.move(os.path.join(self._instroot + "/tmp", item),
1235 os.path.join(self._imgdir, item))
1237 def __run_post_scripts(self):
1238 msger.info("Running post scripts ...")
1239 if os.path.exists(self._instroot + "/tmp"):
1240 shutil.rmtree(self._instroot + "/tmp")
1241 os.mkdir (self._instroot + "/tmp", 0755)
1242 for s in kickstart.get_post_scripts(self.ks):
1243 (fd, path) = tempfile.mkstemp(prefix = "ks-postscript-",
1244 dir = self._instroot + "/tmp")
1246 s.script = s.script.replace("\r", "")
1247 os.write(fd, s.script)
1248 if s.interp == '/bin/sh' or s.interp == '/bin/bash':
1250 os.write(fd, 'exit 0\n')
1252 os.chmod(path, 0700)
1254 env = self._get_post_scripts_env(s.inChroot)
1255 if 'PATH' not in env:
1256 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1262 preexec = self._chroot
1263 script = "/tmp/" + os.path.basename(path)
1265 start_time = time.time()
1268 p = subprocess.Popen([s.interp, script],
1269 preexec_fn = preexec,
1271 stdout = subprocess.PIPE,
1272 stderr = subprocess.STDOUT)
1273 while p.poll() == None:
1274 msger.info(p.stdout.readline().strip())
1275 end_time = time.time()
1276 if (end_time - start_time)/60 > MAX_RUN_TIME:
1277 raise CreatorError("Your post script is executed more than "+MAX_RUN_TIME+"mins, please check it!")
1278 if p.returncode != 0:
1279 raise CreatorError("Failed to execute %%post script "
1280 "with '%s'" % (s.interp))
1281 except OSError, (err, msg):
1282 raise CreatorError("Failed to execute %%post script "
1283 "with '%s' : %s" % (s.interp, msg))
1287 def __save_repo_keys(self, repodata):
1291 gpgkeydir = "/etc/pki/rpm-gpg"
1292 fs.makedirs(self._instroot + gpgkeydir)
1293 for repo in repodata:
1295 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1296 shutil.copy(repo["repokey"], self._instroot + repokey)
1298 def configure(self, repodata = None):
1299 """Configure the system image according to the kickstart.
1301 This method applies the (e.g. keyboard or network) configuration
1302 specified in the kickstart and executes the kickstart %post scripts.
1304 If necessary, it also prepares the image to be bootable by e.g.
1305 creating an initrd and bootloader configuration.
1308 ksh = self.ks.handler
1310 msger.info('Applying configurations ...')
1312 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1313 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1314 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1315 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1316 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1317 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1318 kickstart.UserConfig(self._instroot).apply(ksh.user)
1319 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1320 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1321 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1322 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1323 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1324 self.__save_repo_keys(repodata)
1325 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1327 msger.warning("Failed to apply configuration to image")
1330 self._create_bootconfig()
1331 self.__run_post_scripts()
1333 def launch_shell(self, launch):
1334 """Launch a shell in the install root.
1336 This method is launches a bash shell chroot()ed in the install root;
1337 this can be useful for debugging.
1341 msger.info("Launching shell. Exit to continue.")
1342 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1344 def do_genchecksum(self, image_name):
1345 if not self._genchecksum:
1348 md5sum = misc.get_md5sum(image_name)
1349 with open(image_name + ".md5sum", "w") as f:
1350 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1351 self.outimage.append(image_name+".md5sum")
1353 def remove_exclude_image(self):
1354 for item in self._instloops[:]:
1355 if item['exclude_image']:
1356 msger.info("Removing %s in image." % item['name'])
1357 imgfile = os.path.join(self._imgdir, item['name'])
1360 except OSError as err:
1361 if err.errno == errno.ENOENT:
1363 self._instloops.remove(item)
1365 def create_cpio_image(self):
1366 for item in self._instloops:
1367 if item['cpioopts']:
1368 msger.info("Create image by cpio.")
1369 tmp_cpio = self.__builddir + "/tmp-cpio"
1370 if not os.path.exists(tmp_cpio):
1372 tmp_cpio_imgfile = os.path.join(tmp_cpio, item['name'])
1374 cpiocmd = fs.find_binary_path('cpio')
1376 oldoutdir = os.getcwd()
1377 os.chdir(os.path.join(self._instroot, item['mountpoint'].lstrip('/')))
1378 # find . | cpio --create --'format=newc' | gzip > ../ramdisk.img
1379 runner.show('find . | cpio --create %s | gzip > %s' % (item['cpioopts'], tmp_cpio_imgfile))
1381 except OSError, (errno, msg):
1382 raise errors.CreatorError("Create image by cpio error: %s" % msg)
1384 def copy_cpio_image(self):
1385 for item in self._instloops:
1386 if item['cpioopts']:
1387 tmp_cpio = self.__builddir + "/tmp-cpio"
1388 msger.info("Copy cpio image from %s to %s." %(tmp_cpio, self._imgdir))
1390 shutil.copyfile(os.path.join(tmp_cpio, item['name']),os.path.join(self._imgdir, item['name']))
1392 raise errors.CreatorError("Copy cpio image error")
1393 os.remove(os.path.join(tmp_cpio, item['name']))
1394 if not os.listdir(tmp_cpio):
1395 shutil.rmtree(tmp_cpio, ignore_errors=True)
1397 def package(self, destdir = "."):
1398 """Prepares the created image for final delivery.
1400 In its simplest form, this method merely copies the install root to the
1401 supplied destination directory; other subclasses may choose to package
1402 the image by e.g. creating a bootable ISO containing the image and
1403 bootloader configuration.
1405 destdir -- the directory into which the final image should be moved;
1406 this defaults to the current directory.
1409 self.remove_exclude_image()
1411 self._stage_final_image()
1413 if not os.path.exists(destdir):
1414 fs.makedirs(destdir)
1416 if self._recording_pkgs:
1417 self._save_recording_pkgs(destdir)
1419 # For image formats with two or multiple image files, it will be
1420 # better to put them under a directory
1421 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1422 destdir = os.path.join(destdir, "%s-%s" \
1423 % (self.name, self.image_format))
1424 msger.debug("creating destination dir: %s" % destdir)
1425 fs.makedirs(destdir)
1427 # Ensure all data is flushed to _outdir
1428 runner.quiet('sync')
1430 misc.check_space_pre_cp(self._outdir, destdir)
1431 for f in os.listdir(self._outdir):
1432 shutil.move(os.path.join(self._outdir, f),
1433 os.path.join(destdir, f))
1434 self.outimage.append(os.path.join(destdir, f))
1435 self.do_genchecksum(os.path.join(destdir, f))
1437 def print_outimage_info(self):
1438 msg = "The new image can be found here:\n"
1439 self.outimage.sort()
1440 for file in self.outimage:
1441 msg += ' %s\n' % os.path.abspath(file)
1445 def check_depend_tools(self):
1446 for tool in self._dep_checks:
1447 fs.find_binary_path(tool)
1449 def package_output(self, image_format, destdir = ".", package="none"):
1450 if not package or package == "none":
1453 destdir = os.path.abspath(os.path.expanduser(destdir))
1454 (pkg, comp) = os.path.splitext(package)
1456 comp=comp.lstrip(".")
1460 dst = "%s/%s-%s.tar.%s" %\
1461 (destdir, self.name, image_format, comp)
1463 dst = "%s/%s-%s.tar" %\
1464 (destdir, self.name, image_format)
1466 msger.info("creating %s" % dst)
1467 tar = tarfile.open(dst, "w:" + comp)
1469 for file in self.outimage:
1470 msger.info("adding %s to %s" % (file, dst))
1472 arcname=os.path.join("%s-%s" \
1473 % (self.name, image_format),
1474 os.path.basename(file)))
1475 if os.path.isdir(file):
1476 shutil.rmtree(file, ignore_errors = True)
1482 '''All the file in outimage has been packaged into tar.* file'''
1483 self.outimage = [dst]
1485 def release_output(self, config, destdir, release):
1486 """ Create release directory and files
1490 """ release path """
1491 return os.path.join(destdir, fn)
1493 outimages = self.outimage
1496 new_kspath = _rpath(self.name+'.ks')
1497 with open(config) as fr:
1498 with open(new_kspath, "w") as wf:
1499 # When building a release we want to make sure the .ks
1500 # file generates the same build even when --release not used.
1501 wf.write(fr.read().replace("@BUILD_ID@", release))
1502 outimages.append(new_kspath)
1504 # save log file, logfile is only available in creator attrs
1505 if hasattr(self, 'releaselog') and self.releaselog:
1506 outimages.append(self.logfile)
1508 # rename iso and usbimg
1509 for f in os.listdir(destdir):
1510 if f.endswith(".iso"):
1511 newf = f[:-4] + '.img'
1512 elif f.endswith(".usbimg"):
1513 newf = f[:-7] + '.img'
1516 os.rename(_rpath(f), _rpath(newf))
1517 outimages.append(_rpath(newf))
1519 # generate MD5SUMS SHA1SUMS SHA256SUMS
1520 def generate_hashsum(hash_name, hash_method):
1521 with open(_rpath(hash_name), "w") as wf:
1522 for f in os.listdir(destdir):
1523 if f.endswith('SUMS'):
1526 if os.path.isdir(os.path.join(destdir, f)):
1529 hash_value = hash_method(_rpath(f))
1530 # There needs to be two spaces between the sum and
1531 # filepath to match the syntax with md5sum,sha1sum,
1532 # sha256sum. This way also *sum -c *SUMS can be used.
1533 wf.write("%s %s\n" % (hash_value, f))
1535 outimages.append("%s/%s" % (destdir, hash_name))
1538 'MD5SUMS' : misc.get_md5sum,
1539 'SHA1SUMS' : misc.get_sha1sum,
1540 'SHA256SUMS' : misc.get_sha256sum
1543 for k, v in hash_dict.items():
1544 generate_hashsum(k, v)
1546 # Filter out the nonexist file
1547 for fp in outimages[:]:
1548 if not os.path.exists("%s" % fp):
1549 outimages.remove(fp)
1551 def copy_kernel(self):
1552 """ Copy kernel files to the outimage directory.
1553 NOTE: This needs to be called before unmounting the instroot.
1556 if not self._need_copy_kernel:
1559 if not os.path.exists(self.destdir):
1560 os.makedirs(self.destdir)
1562 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1563 kernelfilename = "%s/%s-%s" % (self.destdir,
1565 os.path.basename(kernel))
1566 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1568 shutil.copy(kernel, kernelfilename)
1569 self.outimage.append(kernelfilename)
1571 def copy_attachment(self):
1572 """ Subclass implement it to handle attachment files
1573 NOTE: This needs to be called before unmounting the instroot.
1577 def get_pkg_manager(self):
1578 return self.pkgmgr(target_arch = self.target_arch,
1579 instroot = self._instroot,
1580 cachedir = self.cachedir,
1581 strict_mode = self.strict_mode)
1583 def create_manifest(self):
1584 def get_pack_suffix():
1585 return '.' + self.pack_to.split('.', 1)[1]
1587 if not os.path.exists(self.destdir):
1588 os.makedirs(self.destdir)
1590 now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1591 manifest_dict = {'version': VERSION,
1594 manifest_dict.update({'format': self.img_format})
1596 if hasattr(self, 'logfile') and self.logfile:
1597 manifest_dict.update({'log_file': self.logfile})
1599 if self.image_files:
1601 self.image_files.update({'pack': get_pack_suffix()})
1602 manifest_dict.update({self.img_format: self.image_files})
1604 msger.info('Creating manifest file...')
1605 manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1606 with open(manifest_file_path, 'w') as fest_file:
1607 json.dump(manifest_dict, fest_file, indent=4)
1608 self.outimage.append(manifest_file_path)