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
94 # If the kernel is save to the destdir when copy_kernel cmd is called.
95 self._need_copy_kernel = False
96 # setup tmpfs tmpdir when enabletmpfs is True
97 self.enabletmpfs = False
100 # Mapping table for variables that have different names.
101 optmap = {"pkgmgr" : "pkgmgr_name",
102 "arch" : "target_arch",
103 "local_pkgs_path" : "_local_pkgs_path",
104 "copy_kernel" : "_need_copy_kernel",
105 "strict_mode" : "strict_mode",
108 # update setting from createopts
109 for key in createopts.keys():
114 setattr(self, option, createopts[key])
116 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
119 if '@NAME@' in self.pack_to:
120 self.pack_to = self.pack_to.replace('@NAME@', self.name)
121 (tar, ext) = os.path.splitext(self.pack_to)
122 if ext in (".gz", ".bz2", ".lzo", ".bz") and tar.endswith(".tar"):
124 if ext not in get_archive_suffixes():
125 self.pack_to += ".tar"
127 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
129 # Output image file names
131 # Output info related with manifest
132 self.image_files = {}
133 # A flag to generate checksum
134 self._genchecksum = False
136 self._alt_initrd_name = None
138 self._recording_pkgs = []
140 # available size in root fs, init to 0
141 self._root_fs_avail = 0
143 # Name of the disk image file that is created.
144 self._img_name = None
146 self.image_format = None
148 # Save qemu emulator file names in order to clean up it finally
149 self.qemu_emulators = []
151 # No ks provided when called by convertor, so skip the dependency check
153 # If we have btrfs partition we need to check necessary tools
154 for part in self.ks.handler.partition.partitions:
155 if part.fstype and part.fstype == "btrfs":
156 self._dep_checks.append("mkfs.btrfs")
158 if part.fstype == "cpio":
160 if len(self.ks.handler.partition.partitions) > 1:
161 self.multiple_partitions = True
164 if self.target_arch.startswith("arm"):
165 for dep in self._dep_checks:
166 if dep == "extlinux":
167 self._dep_checks.remove(dep)
169 if not os.path.exists("/usr/bin/qemu-arm") or \
170 not misc.is_statically_linked("/usr/bin/qemu-arm"):
171 self._dep_checks.append("qemu-arm-static")
173 if os.path.exists("/proc/sys/vm/vdso_enabled"):
174 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
175 vdso_value = vdso_fh.read().strip()
177 if (int)(vdso_value) == 1:
178 msger.warning("vdso is enabled on your host, which might "
179 "cause problems with arm emulations.\n"
180 "\tYou can disable vdso with following command before "
181 "starting image build:\n"
182 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
183 elif self.target_arch == "mipsel":
184 for dep in self._dep_checks:
185 if dep == "extlinux":
186 self._dep_checks.remove(dep)
188 if not os.path.exists("/usr/bin/qemu-mipsel") or \
189 not misc.is_statically_linked("/usr/bin/qemu-mipsel"):
190 self._dep_checks.append("qemu-mipsel-static")
192 if os.path.exists("/proc/sys/vm/vdso_enabled"):
193 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
194 vdso_value = vdso_fh.read().strip()
196 if (int)(vdso_value) == 1:
197 msger.warning("vdso is enabled on your host, which might "
198 "cause problems with mipsel emulations.\n"
199 "\tYou can disable vdso with following command before "
200 "starting image build:\n"
201 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
203 # make sure the specified tmpdir and cachedir exist
204 if not os.path.exists(self.tmpdir):
205 os.makedirs(self.tmpdir)
206 if not os.path.exists(self.cachedir):
207 os.makedirs(self.cachedir)
213 def __get_instroot(self):
214 if self.__builddir is None:
215 raise CreatorError("_instroot is not valid before calling mount()")
216 return self.__builddir + "/install_root"
217 _instroot = property(__get_instroot)
218 """The location of the install root directory.
220 This is the directory into which the system is installed. Subclasses may
221 mount a filesystem image here or copy files to/from here.
223 Note, this directory does not exist before ImageCreator.mount() is called.
225 Note also, this is a read-only attribute.
229 def __get_outdir(self):
230 if self.__builddir is None:
231 raise CreatorError("_outdir is not valid before calling mount()")
232 return self.__builddir + "/out"
233 _outdir = property(__get_outdir)
234 """The staging location for the final image.
236 This is where subclasses should stage any files that are part of the final
237 image. ImageCreator.package() will copy any files found here into the
238 requested destination directory.
240 Note, this directory does not exist before ImageCreator.mount() is called.
242 Note also, this is a read-only attribute.
248 # Hooks for subclasses
250 def _mount_instroot(self, base_on = None):
251 """Mount or prepare the install root directory.
253 This is the hook where subclasses may prepare the install root by e.g.
254 mounting creating and loopback mounting a filesystem image to
257 There is no default implementation.
259 base_on -- this is the value passed to mount() and can be interpreted
260 as the subclass wishes; it might e.g. be the location of
261 a previously created ISO containing a system image.
266 def _unmount_instroot(self):
267 """Undo anything performed in _mount_instroot().
269 This is the hook where subclasses must undo anything which was done
270 in _mount_instroot(). For example, if a filesystem image was mounted
271 onto _instroot, it should be unmounted here.
273 There is no default implementation.
278 def _create_bootconfig(self):
279 """Configure the image so that it's bootable.
281 This is the hook where subclasses may prepare the image for booting by
282 e.g. creating an initramfs and bootloader configuration.
284 This hook is called while the install root is still mounted, after the
285 packages have been installed and the kickstart configuration has been
286 applied, but before the %post scripts have been executed.
288 There is no default implementation.
293 def _stage_final_image(self):
294 """Stage the final system image in _outdir.
296 This is the hook where subclasses should place the image in _outdir
297 so that package() can copy it to the requested destination directory.
299 By default, this moves the install root into _outdir.
302 shutil.move(self._instroot, self._outdir + "/" + self.name)
304 def get_installed_packages(self):
305 return self._pkgs_content.keys()
307 def _save_recording_pkgs(self, destdir):
308 """Save the list or content of installed packages to file.
310 pkgs = self._pkgs_content.keys()
311 pkgs.sort() # inplace op
313 if not os.path.exists(destdir):
317 if 'vcs' in self._recording_pkgs:
318 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
319 content = '\n'.join(sorted(vcslst))
320 elif 'name' in self._recording_pkgs:
321 content = '\n'.join(pkgs)
323 namefile = os.path.join(destdir, self.name + '.packages')
324 f = open(namefile, "w")
327 self.outimage.append(namefile);
329 # if 'content', save more details
330 if 'content' in self._recording_pkgs:
331 contfile = os.path.join(destdir, self.name + '.files')
332 f = open(contfile, "w")
337 pkgcont = self._pkgs_content[pkg]
339 content += '\n '.join(pkgcont)
345 self.outimage.append(contfile)
347 if 'license' in self._recording_pkgs:
348 licensefile = os.path.join(destdir, self.name + '.license')
349 f = open(licensefile, "w")
351 f.write('Summary:\n')
352 for license in reversed(sorted(self._pkgs_license, key=\
353 lambda license: len(self._pkgs_license[license]))):
354 f.write(" - %s: %s\n" \
355 % (license, len(self._pkgs_license[license])))
357 f.write('\nDetails:\n')
358 for license in reversed(sorted(self._pkgs_license, key=\
359 lambda license: len(self._pkgs_license[license]))):
360 f.write(" - %s:\n" % (license))
361 for pkg in sorted(self._pkgs_license[license]):
362 f.write(" - %s\n" % (pkg))
366 self.outimage.append(licensefile)
368 def _get_required_packages(self):
369 """Return a list of required packages.
371 This is the hook where subclasses may specify a set of packages which
372 it requires to be installed.
374 This returns an empty list by default.
376 Note, subclasses should usually chain up to the base class
377 implementation of this hook.
382 def _get_excluded_packages(self):
383 """Return a list of excluded packages.
385 This is the hook where subclasses may specify a set of packages which
386 it requires _not_ to be installed.
388 This returns an empty list by default.
390 Note, subclasses should usually chain up to the base class
391 implementation of this hook.
396 def _get_local_packages(self):
397 """Return a list of rpm path to be local installed.
399 This is the hook where subclasses may specify a set of rpms which
400 it requires to be installed locally.
402 This returns an empty list by default.
404 Note, subclasses should usually chain up to the base class
405 implementation of this hook.
408 if self._local_pkgs_path:
409 if os.path.isdir(self._local_pkgs_path):
411 os.path.join(self._local_pkgs_path, '*.rpm'))
412 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
413 return [self._local_pkgs_path]
417 def _get_fstab(self):
418 """Return the desired contents of /etc/fstab.
420 This is the hook where subclasses may specify the contents of
421 /etc/fstab by returning a string containing the desired contents.
423 A sensible default implementation is provided.
426 s = "/dev/root / %s %s 0 0\n" \
428 "defaults,noatime" if not self._fsopts else self._fsopts)
429 s += self._get_fstab_special()
432 def _get_fstab_special(self):
433 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
434 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
435 s += "proc /proc proc defaults 0 0\n"
436 s += "sysfs /sys sysfs defaults 0 0\n"
439 def _set_part_env(self, pnum, prop, value):
440 """ This is a helper function which generates an environment variable
441 for a property "prop" with value "value" of a partition number "pnum".
443 The naming convention is:
444 * Variables start with INSTALLERFW_PART
445 * Then goes the partition number, the order is the same as
446 specified in the KS file
447 * Then goes the property name
455 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
456 return { name : value }
458 def _get_post_scripts_env(self, in_chroot):
459 """Return an environment dict for %post scripts.
461 This is the hook where subclasses may specify some environment
462 variables for %post scripts by return a dict containing the desired
465 in_chroot -- whether this %post script is to be executed chroot()ed
472 for p in kickstart.get_partitions(self.ks):
473 env.update(self._set_part_env(pnum, "SIZE", p.size))
474 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
475 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
476 env.update(self._set_part_env(pnum, "LABEL", p.label))
477 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
478 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
479 env.update(self._set_part_env(pnum, "ALIGN", p.align))
480 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
481 env.update(self._set_part_env(pnum, "UUID", p.uuid))
482 env.update(self._set_part_env(pnum, "DEVNODE",
483 "/dev/%s%d" % (p.disk, pnum + 1)))
484 env.update(self._set_part_env(pnum, "DISK_DEVNODE",
489 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
491 # Partition table format
492 ptable_format = self.ks.handler.bootloader.ptable
493 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
495 # The kerned boot parameters
496 kernel_opts = self.ks.handler.bootloader.appendLine
497 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
499 # Name of the image creation tool
500 env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
502 # The real current location of the mounted file-systems
506 mount_prefix = self._instroot
507 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
509 # These are historical variables which lack the common name prefix
511 env["INSTALL_ROOT"] = self._instroot
512 env["IMG_NAME"] = self._name
516 def __get_imgname(self):
518 _name = property(__get_imgname)
519 """The name of the image file.
523 def _get_kernel_versions(self):
524 """Return a dict detailing the available kernel types/versions.
526 This is the hook where subclasses may override what kernel types and
527 versions should be available for e.g. creating the booloader
530 A dict should be returned mapping the available kernel types to a list
531 of the available versions for those kernels.
533 The default implementation uses rpm to iterate over everything
534 providing 'kernel', finds /boot/vmlinuz-* and returns the version
535 obtained from the vmlinuz filename. (This can differ from the kernel
536 RPM's n-v-r in the case of e.g. xen)
539 def get_kernel_versions(instroot):
542 files = glob.glob(instroot + "/boot/vmlinuz-*")
544 version = os.path.basename(file)[8:]
547 versions.add(version)
548 ret["kernel"] = list(versions)
551 def get_version(header):
553 for f in header['filenames']:
554 if f.startswith('/boot/vmlinuz-'):
559 return get_kernel_versions(self._instroot)
561 ts = rpm.TransactionSet(self._instroot)
564 for header in ts.dbMatch('provides', 'kernel'):
565 version = get_version(header)
569 name = header['name']
571 ret[name] = [version]
572 elif not version in ret[name]:
573 ret[name].append(version)
579 # Helpers for subclasses
581 def _do_bindmounts(self):
582 """Mount various system directories onto _instroot.
584 This method is called by mount(), but may also be used by subclasses
585 in order to re-mount the bindmounts after modifying the underlying
589 for b in self.__bindmounts:
592 def _undo_bindmounts(self):
593 """Unmount the bind-mounted system directories from _instroot.
595 This method is usually only called by unmount(), but may also be used
596 by subclasses in order to gain access to the filesystem obscured by
597 the bindmounts - e.g. in order to create device nodes on the image
601 self.__bindmounts.reverse()
602 for b in self.__bindmounts:
606 """Chroot into the install root.
608 This method may be used by subclasses when executing programs inside
609 the install root e.g.
611 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
614 os.chroot(self._instroot)
617 def _mkdtemp(self, prefix = "tmp-"):
618 """Create a temporary directory.
620 This method may be used by subclasses to create a temporary directory
621 for use in building the final image - e.g. a subclass might create
622 a temporary directory in order to bundle a set of files into a package.
624 The subclass may delete this directory if it wishes, but it will be
625 automatically deleted by cleanup().
627 The absolute path to the temporary directory is returned.
629 Note, this method should only be called after mount() has been called.
631 prefix -- a prefix which should be used when creating the directory;
635 self.__ensure_builddir()
636 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
638 def _mkstemp(self, prefix = "tmp-"):
639 """Create a temporary file.
641 This method may be used by subclasses to create a temporary file
642 for use in building the final image - e.g. a subclass might need
643 a temporary location to unpack a compressed file.
645 The subclass may delete this file if it wishes, but it will be
646 automatically deleted by cleanup().
648 A tuple containing a file descriptor (returned from os.open() and the
649 absolute path to the temporary directory is returned.
651 Note, this method should only be called after mount() has been called.
653 prefix -- a prefix which should be used when creating the file;
657 self.__ensure_builddir()
658 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
660 def _mktemp(self, prefix = "tmp-"):
661 """Create a temporary file.
663 This method simply calls _mkstemp() and closes the returned file
666 The absolute path to the temporary file is returned.
668 Note, this method should only be called after mount() has been called.
670 prefix -- a prefix which should be used when creating the file;
675 (f, path) = self._mkstemp(prefix)
681 # Actual implementation
683 def __ensure_builddir(self):
684 if not self.__builddir is None:
688 self.workdir = os.path.join(self.tmpdir, "build")
689 if not os.path.exists(self.workdir):
690 os.makedirs(self.workdir)
691 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
692 prefix = "imgcreate-")
693 except OSError, (err, msg):
694 raise CreatorError("Failed create build directory in %s: %s" %
697 def get_cachedir(self, cachedir = None):
701 self.__ensure_builddir()
703 self.cachedir = cachedir
705 self.cachedir = self.__builddir + "/mic-cache"
706 fs.makedirs(self.cachedir)
709 def __sanity_check(self):
710 """Ensure that the config we've been given is same."""
711 if not (kickstart.get_packages(self.ks) or
712 kickstart.get_groups(self.ks)):
713 raise CreatorError("No packages or groups specified")
715 kickstart.convert_method_to_repo(self.ks)
717 if not kickstart.get_repos(self.ks):
718 raise CreatorError("No repositories specified")
720 def __write_fstab(self):
721 if kickstart.use_installerfw(self.ks, "fstab"):
722 # The fstab file will be generated by installer framework scripts
725 fstab_contents = self._get_fstab()
727 fstab = open(self._instroot + "/etc/fstab", "w")
728 fstab.write(fstab_contents)
731 def __create_minimal_dev(self):
732 """Create a minimal /dev so that we don't corrupt the host /dev"""
733 origumask = os.umask(0000)
734 devices = (('null', 1, 3, 0666),
735 ('urandom',1, 9, 0666),
736 ('random', 1, 8, 0666),
737 ('full', 1, 7, 0666),
738 ('ptmx', 5, 2, 0666),
740 ('zero', 1, 5, 0666))
742 links = (("/proc/self/fd", "/dev/fd"),
743 ("/proc/self/fd/0", "/dev/stdin"),
744 ("/proc/self/fd/1", "/dev/stdout"),
745 ("/proc/self/fd/2", "/dev/stderr"))
747 for (node, major, minor, perm) in devices:
748 if not os.path.exists(self._instroot + "/dev/" + node):
749 os.mknod(self._instroot + "/dev/" + node,
751 os.makedev(major,minor))
753 for (src, dest) in links:
754 if not os.path.exists(self._instroot + dest):
755 os.symlink(src, self._instroot + dest)
759 def __setup_tmpdir(self):
760 if not self.enabletmpfs:
763 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
765 def __clean_tmpdir(self):
766 if not self.enabletmpfs:
769 runner.show('umount -l %s' % self.workdir)
772 #Add tpk-install option
773 createopts = configmgr.create
774 if createopts['tpk_install']:
775 path = createopts['tpk_install']
776 file_list = os.listdir(path)
778 sub = os.path.splitext(f)[1]
780 raise CreatorError("Not all files in the path: "+path +" is tpk")
782 tpk_dir = "/usr/apps/.preload-tpk"
783 fs.makedirs(self._instroot + "/usr/apps")
784 fs.makedirs(self._instroot + tpk_dir)
786 shutil.copy(path+"/"+f,self._instroot + tpk_dir)
788 def mount(self, base_on = None, cachedir = None):
789 """Setup the target filesystem in preparation for an install.
791 This function sets up the filesystem which the ImageCreator will
792 install into and configure. The ImageCreator class merely creates an
793 install root directory, bind mounts some system directories (e.g. /dev)
794 and writes out /etc/fstab. Other subclasses may also e.g. create a
795 sparse file, format it and loopback mount it to the install root.
797 base_on -- a previous install on which to base this install; defaults
798 to None, causing a new image to be created
800 cachedir -- a directory in which to store the Yum cache; defaults to
801 None, causing a new cache to be created; by setting this
802 to another directory, the same cache can be reused across
806 self.__setup_tmpdir()
807 self.__ensure_builddir()
809 # prevent popup dialog in Ubuntu(s)
810 misc.hide_loopdev_presentation()
812 fs.makedirs(self._instroot)
813 fs.makedirs(self._outdir)
815 self._mount_instroot(base_on)
817 for d in ("/dev/pts",
824 fs.makedirs(self._instroot + d)
826 if self.target_arch and self.target_arch.startswith("arm") or \
827 self.target_arch == "aarch64":
828 self.qemu_emulators = misc.setup_qemu_emulator(self._instroot,
831 self.get_cachedir(cachedir)
833 # bind mount system directories into _instroot
834 for (f, dest) in [("/sys", None),
836 ("/proc/sys/fs/binfmt_misc", None),
838 self.__bindmounts.append(
840 f, self._instroot, dest))
842 self._do_bindmounts()
844 self.__create_minimal_dev()
846 if os.path.exists(self._instroot + "/etc/mtab"):
847 os.unlink(self._instroot + "/etc/mtab")
848 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
852 # get size of available space in 'instroot' fs
853 self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
857 """Unmounts the target filesystem.
859 The ImageCreator class detaches the system from the install root, but
860 other subclasses may also detach the loopback mounted filesystem image
861 from the install root.
865 mtab = self._instroot + "/etc/mtab"
866 if not os.path.islink(mtab):
867 os.unlink(self._instroot + "/etc/mtab")
869 for qemu_emulator in self.qemu_emulators:
870 os.unlink(self._instroot + qemu_emulator)
874 self._undo_bindmounts()
876 """ Clean up yum garbage """
878 instroot_pdir = os.path.dirname(self._instroot + self._instroot)
879 if os.path.exists(instroot_pdir):
880 shutil.rmtree(instroot_pdir, ignore_errors = True)
881 yumlibdir = self._instroot + "/var/lib/yum"
882 if os.path.exists(yumlibdir):
883 shutil.rmtree(yumlibdir, ignore_errors = True)
887 self._unmount_instroot()
889 # reset settings of popup dialog in Ubuntu(s)
890 misc.unhide_loopdev_presentation()
894 """Unmounts the target filesystem and deletes temporary files.
896 This method calls unmount() and then deletes any temporary files and
897 directories that were created on the host system while building the
900 Note, make sure to call this method once finished with the creator
901 instance in order to ensure no stale files are left on the host e.g.:
903 creator = ImageCreator(ks, name)
910 if not self.__builddir:
913 kill_proc_inchroot(self._instroot)
917 shutil.rmtree(self.__builddir, ignore_errors = True)
918 self.__builddir = None
920 self.__clean_tmpdir()
922 def __is_excluded_pkg(self, pkg):
923 if pkg in self._excluded_pkgs:
924 self._excluded_pkgs.remove(pkg)
927 for xpkg in self._excluded_pkgs:
928 if xpkg.endswith('*'):
929 if pkg.startswith(xpkg[:-1]):
931 elif xpkg.startswith('*'):
932 if pkg.endswith(xpkg[1:]):
937 def __select_packages(self, pkg_manager):
939 for pkg in self._required_pkgs:
940 e = pkg_manager.selectPackage(pkg)
942 if kickstart.ignore_missing(self.ks):
943 skipped_pkgs.append(pkg)
944 elif self.__is_excluded_pkg(pkg):
945 skipped_pkgs.append(pkg)
947 raise CreatorError("Failed to find package '%s' : %s" %
950 for pkg in skipped_pkgs:
951 msger.warning("Skipping missing package '%s'" % (pkg,))
953 def __select_groups(self, pkg_manager):
955 for group in self._required_groups:
956 e = pkg_manager.selectGroup(group.name, group.include)
958 if kickstart.ignore_missing(self.ks):
959 skipped_groups.append(group)
961 raise CreatorError("Failed to find group '%s' : %s" %
964 for group in skipped_groups:
965 msger.warning("Skipping missing group '%s'" % (group.name,))
967 def __deselect_packages(self, pkg_manager):
968 for pkg in self._excluded_pkgs:
969 pkg_manager.deselectPackage(pkg)
971 def __localinst_packages(self, pkg_manager):
972 for rpm_path in self._get_local_packages():
973 pkg_manager.installLocal(rpm_path)
975 def __preinstall_packages(self, pkg_manager):
979 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
980 for pkg in self._preinstall_pkgs:
981 pkg_manager.preInstall(pkg)
983 def __check_packages(self, pkg_manager):
984 for pkg in self.check_pkgs:
985 pkg_manager.checkPackage(pkg)
987 def __attachment_packages(self, pkg_manager):
991 self._attachment = []
992 for item in kickstart.get_attachment(self.ks):
993 if item.startswith('/'):
994 fpaths = os.path.join(self._instroot, item.lstrip('/'))
995 for fpath in glob.glob(fpaths):
996 self._attachment.append(fpath)
999 filelist = pkg_manager.getFilelist(item)
1001 # found rpm in rootfs
1002 for pfile in pkg_manager.getFilelist(item):
1003 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
1004 self._attachment.append(fpath)
1007 # try to retrieve rpm file
1008 (url, proxies) = pkg_manager.package_url(item)
1010 msger.warning("Can't get url from repo for %s" % item)
1012 fpath = os.path.join(self.cachedir, os.path.basename(url))
1013 if not os.path.exists(fpath):
1016 fpath = grabber.myurlgrab(url.full, fpath, proxies, None)
1017 except CreatorError:
1020 tmpdir = self._mkdtemp()
1021 misc.extract_rpm(fpath, tmpdir)
1022 for (root, dirs, files) in os.walk(tmpdir):
1024 fpath = os.path.join(root, fname)
1025 self._attachment.append(fpath)
1027 def install(self, repo_urls=None):
1028 """Install packages into the install root.
1030 This function installs the packages listed in the supplied kickstart
1031 into the install root. By default, the packages are installed from the
1032 repository URLs specified in the kickstart.
1034 repo_urls -- a dict which maps a repository name to a repository;
1035 if supplied, this causes any repository URLs specified in
1036 the kickstart to be overridden.
1039 def checkScriptletError(dirname, suffix):
1040 if os.path.exists(dirname):
1041 list = os.listdir(dirname)
1043 filepath = os.path.join(dirname, line)
1044 if os.path.isfile(filepath) and 0 < line.find(suffix):
1051 def showErrorInfo(filepath):
1052 if os.path.isfile(filepath):
1053 for line in open(filepath):
1054 msger.info("The error install package info: %s" % line)
1056 msger.info("%s is not found." % filepath)
1058 def get_ssl_verify(ssl_verify=None):
1059 if ssl_verify is not None:
1060 return not ssl_verify.lower().strip() == 'no'
1062 return not self.ssl_verify.lower().strip() == 'no'
1064 # initialize pkg list to install
1066 self.__sanity_check()
1068 self._required_pkgs = \
1069 kickstart.get_packages(self.ks, self._get_required_packages())
1070 self._excluded_pkgs = \
1071 kickstart.get_excluded(self.ks, self._get_excluded_packages())
1072 self._required_groups = kickstart.get_groups(self.ks)
1074 self._required_pkgs = None
1075 self._excluded_pkgs = None
1076 self._required_groups = None
1079 repo_urls = self.extrarepos
1081 pkg_manager = self.get_pkg_manager()
1084 if hasattr(self, 'install_pkgs') and self.install_pkgs:
1085 if 'debuginfo' in self.install_pkgs:
1086 pkg_manager.install_debuginfo = True
1088 for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1089 (name, baseurl, mirrorlist, inc, exc,
1090 proxy, proxy_username, proxy_password, debuginfo,
1091 source, gpgkey, disable, ssl_verify, nocache,
1092 cost, priority) = repo
1094 ssl_verify = get_ssl_verify(ssl_verify)
1095 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1096 proxy_username, proxy_password, inc, exc, ssl_verify,
1097 nocache, cost, priority)
1099 if kickstart.exclude_docs(self.ks):
1100 rpm.addMacro("_excludedocs", "1")
1101 rpm.addMacro("_dbpath", "/var/lib/rpm")
1102 rpm.addMacro("__file_context_path", "%{nil}")
1103 if kickstart.inst_langs(self.ks) != None:
1104 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1107 self.__preinstall_packages(pkg_manager)
1108 self.__select_packages(pkg_manager)
1109 self.__select_groups(pkg_manager)
1110 self.__deselect_packages(pkg_manager)
1111 self.__localinst_packages(pkg_manager)
1112 self.__check_packages(pkg_manager)
1114 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1115 checksize = self._root_fs_avail
1117 checksize -= BOOT_SAFEGUARD
1118 if self.target_arch:
1119 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1121 # If we have multiple partitions, don't check diskspace when rpm run transaction
1122 # because rpm check '/' partition only.
1123 if self.multiple_partitions:
1124 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_DISKSPACE)
1125 pkg_manager.runInstall(checksize)
1126 except CreatorError, e:
1128 except KeyboardInterrupt:
1131 self._pkgs_content = pkg_manager.getAllContent()
1132 self._pkgs_license = pkg_manager.getPkgsLicense()
1133 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1134 self.__attachment_packages(pkg_manager)
1138 if checkScriptletError(self._instroot + "/tmp/.postscript/error/", "_error"):
1139 showErrorInfo(self._instroot + "/tmp/.preload_install_error")
1140 raise CreatorError('scriptlet errors occurred')
1145 # do some clean up to avoid lvm info leakage. this sucks.
1146 for subdir in ("cache", "backup", "archive"):
1147 lvmdir = self._instroot + "/etc/lvm/" + subdir
1149 for f in os.listdir(lvmdir):
1150 os.unlink(lvmdir + "/" + f)
1154 def postinstall(self):
1155 self.copy_attachment()
1157 def run_sign_scripts(self):
1158 if kickstart.get_sign_scripts(self.ks)==[]:
1160 msger.info("Running sign scripts ...")
1161 if os.path.exists(self._instroot + "/tmp"):
1162 shutil.rmtree(self._instroot + "/tmp")
1163 os.mkdir (self._instroot + "/tmp", 0755)
1164 for s in kickstart.get_sign_scripts(self.ks):
1165 (fd, path) = tempfile.mkstemp(prefix = "ks-runscript-",
1166 dir = self._instroot + "/tmp")
1167 s.script = s.script.replace("\r", "")
1168 os.write(fd, s.script)
1169 if s.interp == '/bin/sh' or s.interp == '/bin/bash':
1171 os.write(fd, 'exit 0\n')
1173 os.chmod(path, 0700)
1174 for item in os.listdir(self._imgdir):
1175 sub = os.path.splitext(item)[1]
1177 shutil.move(os.path.join(self._imgdir, item),
1178 os.path.join(self._instroot + "/tmp", item))
1179 oldoutdir = os.getcwd()
1180 os.chdir(self._instroot + "/tmp")
1183 p = subprocess.Popen([s.interp, path],
1184 stdout = subprocess.PIPE,
1185 stderr = subprocess.STDOUT)
1186 while p.poll() == None:
1187 msger.info(p.stdout.readline().strip())
1188 if p.returncode != 0:
1189 raise CreatorError("Failed to execute %%sign script "
1190 "with '%s'" % (s.interp))
1191 except OSError, (err, msg):
1192 raise CreatorError("Failed to execute %%sign script "
1193 "with '%s' : %s" % (s.interp, msg))
1197 for item in os.listdir(self._instroot + "/tmp"):
1198 shutil.move(os.path.join(self._instroot + "/tmp", item),
1199 os.path.join(self._imgdir, item))
1200 def __run_post_scripts(self):
1201 msger.info("Running post scripts ...")
1202 if os.path.exists(self._instroot + "/tmp"):
1203 shutil.rmtree(self._instroot + "/tmp")
1204 os.mkdir (self._instroot + "/tmp", 0755)
1205 for s in kickstart.get_post_scripts(self.ks):
1206 (fd, path) = tempfile.mkstemp(prefix = "ks-postscript-",
1207 dir = self._instroot + "/tmp")
1209 s.script = s.script.replace("\r", "")
1210 os.write(fd, s.script)
1211 if s.interp == '/bin/sh' or s.interp == '/bin/bash':
1213 os.write(fd, 'exit 0\n')
1215 os.chmod(path, 0700)
1217 env = self._get_post_scripts_env(s.inChroot)
1218 if 'PATH' not in env:
1219 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1225 preexec = self._chroot
1226 script = "/tmp/" + os.path.basename(path)
1228 start_time = time.time()
1231 p = subprocess.Popen([s.interp, script],
1232 preexec_fn = preexec,
1234 stdout = subprocess.PIPE,
1235 stderr = subprocess.STDOUT)
1236 while p.poll() == None:
1237 msger.info(p.stdout.readline().strip())
1238 end_time = time.time()
1239 if (end_time - start_time)/60 > MAX_RUN_TIME:
1240 raise CreatorError("Your post script is executed more than "+MAX_RUN_TIME+"mins, please check it!")
1241 if p.returncode != 0:
1242 raise CreatorError("Failed to execute %%post script "
1243 "with '%s'" % (s.interp))
1244 except OSError, (err, msg):
1245 raise CreatorError("Failed to execute %%post script "
1246 "with '%s' : %s" % (s.interp, msg))
1250 def __save_repo_keys(self, repodata):
1254 gpgkeydir = "/etc/pki/rpm-gpg"
1255 fs.makedirs(self._instroot + gpgkeydir)
1256 for repo in repodata:
1258 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1259 shutil.copy(repo["repokey"], self._instroot + repokey)
1261 def configure(self, repodata = None):
1262 """Configure the system image according to the kickstart.
1264 This method applies the (e.g. keyboard or network) configuration
1265 specified in the kickstart and executes the kickstart %post scripts.
1267 If necessary, it also prepares the image to be bootable by e.g.
1268 creating an initrd and bootloader configuration.
1271 ksh = self.ks.handler
1273 msger.info('Applying configurations ...')
1275 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1276 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1277 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1278 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1279 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1280 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1281 kickstart.UserConfig(self._instroot).apply(ksh.user)
1282 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1283 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1284 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1285 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1286 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1287 self.__save_repo_keys(repodata)
1288 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1290 msger.warning("Failed to apply configuration to image")
1293 self._create_bootconfig()
1294 self.__run_post_scripts()
1296 def launch_shell(self, launch):
1297 """Launch a shell in the install root.
1299 This method is launches a bash shell chroot()ed in the install root;
1300 this can be useful for debugging.
1304 msger.info("Launching shell. Exit to continue.")
1305 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1307 def do_genchecksum(self, image_name):
1308 if not self._genchecksum:
1311 md5sum = misc.get_md5sum(image_name)
1312 with open(image_name + ".md5sum", "w") as f:
1313 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1314 self.outimage.append(image_name+".md5sum")
1316 def remove_exclude_image(self):
1317 for item in self._instloops[:]:
1318 if item['exclude_image']:
1319 msger.info("Removing %s in image." % item['name'])
1320 imgfile = os.path.join(self._imgdir, item['name'])
1323 except OSError as err:
1324 if err.errno == errno.ENOENT:
1326 self._instloops.remove(item)
1328 def create_cpio_image(self):
1329 for item in self._instloops:
1330 if item['cpioopts']:
1331 msger.info("Create image by cpio.")
1332 tmp_cpio = self.__builddir + "/tmp-cpio"
1333 if not os.path.exists(tmp_cpio):
1335 tmp_cpio_imgfile = os.path.join(tmp_cpio, item['name'])
1337 cpiocmd = fs.find_binary_path('cpio')
1339 oldoutdir = os.getcwd()
1340 os.chdir(os.path.join(self._instroot, item['mountpoint'].lstrip('/')))
1341 # find . | cpio --create --'format=newc' | gzip > ../ramdisk.img
1342 runner.show('find . | cpio --create %s | gzip > %s' % (item['cpioopts'], tmp_cpio_imgfile))
1344 except OSError, (errno, msg):
1345 raise errors.CreatorError("Create image by cpio error: %s" % msg)
1347 def copy_cpio_image(self):
1348 for item in self._instloops:
1349 if item['cpioopts']:
1350 tmp_cpio = self.__builddir + "/tmp-cpio"
1351 msger.info("Copy cpio image from %s to %s." %(tmp_cpio, self._imgdir))
1353 shutil.copyfile(os.path.join(tmp_cpio, item['name']),os.path.join(self._imgdir, item['name']))
1355 raise errors.CreatorError("Copy cpio image error")
1356 os.remove(os.path.join(tmp_cpio, item['name']))
1357 if not os.listdir(tmp_cpio):
1358 shutil.rmtree(tmp_cpio, ignore_errors=True)
1360 def package(self, destdir = "."):
1361 """Prepares the created image for final delivery.
1363 In its simplest form, this method merely copies the install root to the
1364 supplied destination directory; other subclasses may choose to package
1365 the image by e.g. creating a bootable ISO containing the image and
1366 bootloader configuration.
1368 destdir -- the directory into which the final image should be moved;
1369 this defaults to the current directory.
1372 self.remove_exclude_image()
1374 self._stage_final_image()
1376 if not os.path.exists(destdir):
1377 fs.makedirs(destdir)
1379 if self._recording_pkgs:
1380 self._save_recording_pkgs(destdir)
1382 # For image formats with two or multiple image files, it will be
1383 # better to put them under a directory
1384 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1385 destdir = os.path.join(destdir, "%s-%s" \
1386 % (self.name, self.image_format))
1387 msger.debug("creating destination dir: %s" % destdir)
1388 fs.makedirs(destdir)
1390 # Ensure all data is flushed to _outdir
1391 runner.quiet('sync')
1393 misc.check_space_pre_cp(self._outdir, destdir)
1394 for f in os.listdir(self._outdir):
1395 shutil.move(os.path.join(self._outdir, f),
1396 os.path.join(destdir, f))
1397 self.outimage.append(os.path.join(destdir, f))
1398 self.do_genchecksum(os.path.join(destdir, f))
1400 def print_outimage_info(self):
1401 msg = "The new image can be found here:\n"
1402 self.outimage.sort()
1403 for file in self.outimage:
1404 msg += ' %s\n' % os.path.abspath(file)
1408 def check_depend_tools(self):
1409 for tool in self._dep_checks:
1410 fs.find_binary_path(tool)
1412 def package_output(self, image_format, destdir = ".", package="none"):
1413 if not package or package == "none":
1416 destdir = os.path.abspath(os.path.expanduser(destdir))
1417 (pkg, comp) = os.path.splitext(package)
1419 comp=comp.lstrip(".")
1423 dst = "%s/%s-%s.tar.%s" %\
1424 (destdir, self.name, image_format, comp)
1426 dst = "%s/%s-%s.tar" %\
1427 (destdir, self.name, image_format)
1429 msger.info("creating %s" % dst)
1430 tar = tarfile.open(dst, "w:" + comp)
1432 for file in self.outimage:
1433 msger.info("adding %s to %s" % (file, dst))
1435 arcname=os.path.join("%s-%s" \
1436 % (self.name, image_format),
1437 os.path.basename(file)))
1438 if os.path.isdir(file):
1439 shutil.rmtree(file, ignore_errors = True)
1445 '''All the file in outimage has been packaged into tar.* file'''
1446 self.outimage = [dst]
1448 def release_output(self, config, destdir, release):
1449 """ Create release directory and files
1453 """ release path """
1454 return os.path.join(destdir, fn)
1456 outimages = self.outimage
1459 new_kspath = _rpath(self.name+'.ks')
1460 with open(config) as fr:
1461 with open(new_kspath, "w") as wf:
1462 # When building a release we want to make sure the .ks
1463 # file generates the same build even when --release not used.
1464 wf.write(fr.read().replace("@BUILD_ID@", release))
1465 outimages.append(new_kspath)
1467 # save log file, logfile is only available in creator attrs
1468 if hasattr(self, 'releaselog') and self.releaselog:
1469 outimages.append(self.logfile)
1471 # rename iso and usbimg
1472 for f in os.listdir(destdir):
1473 if f.endswith(".iso"):
1474 newf = f[:-4] + '.img'
1475 elif f.endswith(".usbimg"):
1476 newf = f[:-7] + '.img'
1479 os.rename(_rpath(f), _rpath(newf))
1480 outimages.append(_rpath(newf))
1482 # generate MD5SUMS SHA1SUMS SHA256SUMS
1483 def generate_hashsum(hash_name, hash_method):
1484 with open(_rpath(hash_name), "w") as wf:
1485 for f in os.listdir(destdir):
1486 if f.endswith('SUMS'):
1489 if os.path.isdir(os.path.join(destdir, f)):
1492 hash_value = hash_method(_rpath(f))
1493 # There needs to be two spaces between the sum and
1494 # filepath to match the syntax with md5sum,sha1sum,
1495 # sha256sum. This way also *sum -c *SUMS can be used.
1496 wf.write("%s %s\n" % (hash_value, f))
1498 outimages.append("%s/%s" % (destdir, hash_name))
1501 'MD5SUMS' : misc.get_md5sum,
1502 'SHA1SUMS' : misc.get_sha1sum,
1503 'SHA256SUMS' : misc.get_sha256sum
1506 for k, v in hash_dict.items():
1507 generate_hashsum(k, v)
1509 # Filter out the nonexist file
1510 for fp in outimages[:]:
1511 if not os.path.exists("%s" % fp):
1512 outimages.remove(fp)
1514 def copy_kernel(self):
1515 """ Copy kernel files to the outimage directory.
1516 NOTE: This needs to be called before unmounting the instroot.
1519 if not self._need_copy_kernel:
1522 if not os.path.exists(self.destdir):
1523 os.makedirs(self.destdir)
1525 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1526 kernelfilename = "%s/%s-%s" % (self.destdir,
1528 os.path.basename(kernel))
1529 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1531 shutil.copy(kernel, kernelfilename)
1532 self.outimage.append(kernelfilename)
1534 def copy_attachment(self):
1535 """ Subclass implement it to handle attachment files
1536 NOTE: This needs to be called before unmounting the instroot.
1540 def get_pkg_manager(self):
1541 return self.pkgmgr(target_arch = self.target_arch,
1542 instroot = self._instroot,
1543 cachedir = self.cachedir,
1544 strict_mode = self.strict_mode)
1546 def create_manifest(self):
1547 def get_pack_suffix():
1548 return '.' + self.pack_to.split('.', 1)[1]
1550 if not os.path.exists(self.destdir):
1551 os.makedirs(self.destdir)
1553 now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1554 manifest_dict = {'version': VERSION,
1557 manifest_dict.update({'format': self.img_format})
1559 if hasattr(self, 'logfile') and self.logfile:
1560 manifest_dict.update({'log_file': self.logfile})
1562 if self.image_files:
1564 self.image_files.update({'pack': get_pack_suffix()})
1565 manifest_dict.update({self.img_format: self.image_files})
1567 msger.info('Creating manifest file...')
1568 manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1569 with open(manifest_file_path, 'w') as fest_file:
1570 json.dump(manifest_dict, fest_file, indent=4)
1571 self.outimage.append(manifest_file_path)