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 name in order to clean up it finally
149 self.qemu_emulator = None
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" or self.target_arch == "mipsel" :
828 self.qemu_emulator = 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 if self.qemu_emulator:
870 os.unlink(self._instroot + self.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)
1170 os.chmod(path, 0700)
1171 for item in os.listdir(self._imgdir):
1172 sub = os.path.splitext(item)[1]
1174 shutil.move(os.path.join(self._imgdir, item),
1175 os.path.join(self._instroot + "/tmp", item))
1176 oldoutdir = os.getcwd()
1177 os.chdir(self._instroot + "/tmp")
1180 p = subprocess.Popen([s.interp, path],
1181 stdout = subprocess.PIPE,
1182 stderr = subprocess.STDOUT)
1183 while p.poll() == None:
1184 msger.info(p.stdout.readline().strip())
1185 except OSError, (err, msg):
1186 raise CreatorError("Failed to execute %%sign script "
1187 "with '%s' : %s" % (s.interp, msg))
1191 for item in os.listdir(self._instroot + "/tmp"):
1192 shutil.move(os.path.join(self._instroot + "/tmp", item),
1193 os.path.join(self._imgdir, item))
1194 def __run_post_scripts(self):
1195 msger.info("Running post scripts ...")
1196 if os.path.exists(self._instroot + "/tmp"):
1197 shutil.rmtree(self._instroot + "/tmp")
1198 os.mkdir (self._instroot + "/tmp", 0755)
1199 for s in kickstart.get_post_scripts(self.ks):
1200 (fd, path) = tempfile.mkstemp(prefix = "ks-postscript-",
1201 dir = self._instroot + "/tmp")
1203 s.script = s.script.replace("\r", "")
1204 os.write(fd, s.script)
1206 os.chmod(path, 0700)
1208 env = self._get_post_scripts_env(s.inChroot)
1209 if 'PATH' not in env:
1210 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1216 preexec = self._chroot
1217 script = "/tmp/" + os.path.basename(path)
1219 start_time = time.time()
1222 p = subprocess.Popen([s.interp, script],
1223 preexec_fn = preexec,
1225 stdout = subprocess.PIPE,
1226 stderr = subprocess.STDOUT)
1227 while p.poll() == None:
1228 msger.info(p.stdout.readline().strip())
1229 end_time = time.time()
1230 if (end_time - start_time)/60 > MAX_RUN_TIME:
1231 raise CreatorError("Your post script is executed more than "+MAX_RUN_TIME+"mins, please check it!")
1232 except OSError, (err, msg):
1233 raise CreatorError("Failed to execute %%post script "
1234 "with '%s' : %s" % (s.interp, msg))
1238 def __save_repo_keys(self, repodata):
1242 gpgkeydir = "/etc/pki/rpm-gpg"
1243 fs.makedirs(self._instroot + gpgkeydir)
1244 for repo in repodata:
1246 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1247 shutil.copy(repo["repokey"], self._instroot + repokey)
1249 def configure(self, repodata = None):
1250 """Configure the system image according to the kickstart.
1252 This method applies the (e.g. keyboard or network) configuration
1253 specified in the kickstart and executes the kickstart %post scripts.
1255 If necessary, it also prepares the image to be bootable by e.g.
1256 creating an initrd and bootloader configuration.
1259 ksh = self.ks.handler
1261 msger.info('Applying configurations ...')
1263 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1264 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1265 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1266 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1267 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1268 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1269 kickstart.UserConfig(self._instroot).apply(ksh.user)
1270 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1271 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1272 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1273 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1274 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1275 self.__save_repo_keys(repodata)
1276 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1278 msger.warning("Failed to apply configuration to image")
1281 self._create_bootconfig()
1282 self.__run_post_scripts()
1284 def launch_shell(self, launch):
1285 """Launch a shell in the install root.
1287 This method is launches a bash shell chroot()ed in the install root;
1288 this can be useful for debugging.
1292 msger.info("Launching shell. Exit to continue.")
1293 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1295 def do_genchecksum(self, image_name):
1296 if not self._genchecksum:
1299 md5sum = misc.get_md5sum(image_name)
1300 with open(image_name + ".md5sum", "w") as f:
1301 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1302 self.outimage.append(image_name+".md5sum")
1304 def remove_exclude_image(self):
1305 for item in self._instloops[:]:
1306 if item['exclude_image']:
1307 msger.info("Removing %s in image." % item['name'])
1308 imgfile = os.path.join(self._imgdir, item['name'])
1311 except OSError as err:
1312 if err.errno == errno.ENOENT:
1314 self._instloops.remove(item)
1316 def create_cpio_image(self):
1317 for item in self._instloops:
1318 if item['cpioopts']:
1319 msger.info("Create image by cpio.")
1320 tmp_cpio = self.__builddir + "/tmp-cpio"
1321 if not os.path.exists(tmp_cpio):
1323 tmp_cpio_imgfile = os.path.join(tmp_cpio, item['name'])
1325 cpiocmd = fs.find_binary_path('cpio')
1327 oldoutdir = os.getcwd()
1328 os.chdir(os.path.join(self._instroot, item['mountpoint'].lstrip('/')))
1329 # find . | cpio --create --'format=newc' | gzip > ../ramdisk.img
1330 runner.show('find . | cpio --create %s | gzip > %s' % (item['cpioopts'], tmp_cpio_imgfile))
1332 except OSError, (errno, msg):
1333 raise errors.CreatorError("Create image by cpio error: %s" % msg)
1335 def copy_cpio_image(self):
1336 for item in self._instloops:
1337 if item['cpioopts']:
1338 tmp_cpio = self.__builddir + "/tmp-cpio"
1339 msger.info("Copy cpio image from %s to %s." %(tmp_cpio, self._imgdir))
1341 shutil.copyfile(os.path.join(tmp_cpio, item['name']),os.path.join(self._imgdir, item['name']))
1343 raise errors.CreatorError("Copy cpio image error")
1344 os.remove(os.path.join(tmp_cpio, item['name']))
1345 if not os.listdir(tmp_cpio):
1346 shutil.rmtree(tmp_cpio, ignore_errors=True)
1348 def package(self, destdir = "."):
1349 """Prepares the created image for final delivery.
1351 In its simplest form, this method merely copies the install root to the
1352 supplied destination directory; other subclasses may choose to package
1353 the image by e.g. creating a bootable ISO containing the image and
1354 bootloader configuration.
1356 destdir -- the directory into which the final image should be moved;
1357 this defaults to the current directory.
1360 self.remove_exclude_image()
1362 self._stage_final_image()
1364 if not os.path.exists(destdir):
1365 fs.makedirs(destdir)
1367 if self._recording_pkgs:
1368 self._save_recording_pkgs(destdir)
1370 # For image formats with two or multiple image files, it will be
1371 # better to put them under a directory
1372 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1373 destdir = os.path.join(destdir, "%s-%s" \
1374 % (self.name, self.image_format))
1375 msger.debug("creating destination dir: %s" % destdir)
1376 fs.makedirs(destdir)
1378 # Ensure all data is flushed to _outdir
1379 runner.quiet('sync')
1381 misc.check_space_pre_cp(self._outdir, destdir)
1382 for f in os.listdir(self._outdir):
1383 shutil.move(os.path.join(self._outdir, f),
1384 os.path.join(destdir, f))
1385 self.outimage.append(os.path.join(destdir, f))
1386 self.do_genchecksum(os.path.join(destdir, f))
1388 def print_outimage_info(self):
1389 msg = "The new image can be found here:\n"
1390 self.outimage.sort()
1391 for file in self.outimage:
1392 msg += ' %s\n' % os.path.abspath(file)
1396 def check_depend_tools(self):
1397 for tool in self._dep_checks:
1398 fs.find_binary_path(tool)
1400 def package_output(self, image_format, destdir = ".", package="none"):
1401 if not package or package == "none":
1404 destdir = os.path.abspath(os.path.expanduser(destdir))
1405 (pkg, comp) = os.path.splitext(package)
1407 comp=comp.lstrip(".")
1411 dst = "%s/%s-%s.tar.%s" %\
1412 (destdir, self.name, image_format, comp)
1414 dst = "%s/%s-%s.tar" %\
1415 (destdir, self.name, image_format)
1417 msger.info("creating %s" % dst)
1418 tar = tarfile.open(dst, "w:" + comp)
1420 for file in self.outimage:
1421 msger.info("adding %s to %s" % (file, dst))
1423 arcname=os.path.join("%s-%s" \
1424 % (self.name, image_format),
1425 os.path.basename(file)))
1426 if os.path.isdir(file):
1427 shutil.rmtree(file, ignore_errors = True)
1433 '''All the file in outimage has been packaged into tar.* file'''
1434 self.outimage = [dst]
1436 def release_output(self, config, destdir, release):
1437 """ Create release directory and files
1441 """ release path """
1442 return os.path.join(destdir, fn)
1444 outimages = self.outimage
1447 new_kspath = _rpath(self.name+'.ks')
1448 with open(config) as fr:
1449 with open(new_kspath, "w") as wf:
1450 # When building a release we want to make sure the .ks
1451 # file generates the same build even when --release not used.
1452 wf.write(fr.read().replace("@BUILD_ID@", release))
1453 outimages.append(new_kspath)
1455 # save log file, logfile is only available in creator attrs
1456 if hasattr(self, 'releaselog') and self.releaselog:
1457 outimages.append(self.logfile)
1459 # rename iso and usbimg
1460 for f in os.listdir(destdir):
1461 if f.endswith(".iso"):
1462 newf = f[:-4] + '.img'
1463 elif f.endswith(".usbimg"):
1464 newf = f[:-7] + '.img'
1467 os.rename(_rpath(f), _rpath(newf))
1468 outimages.append(_rpath(newf))
1470 # generate MD5SUMS SHA1SUMS SHA256SUMS
1471 def generate_hashsum(hash_name, hash_method):
1472 with open(_rpath(hash_name), "w") as wf:
1473 for f in os.listdir(destdir):
1474 if f.endswith('SUMS'):
1477 if os.path.isdir(os.path.join(destdir, f)):
1480 hash_value = hash_method(_rpath(f))
1481 # There needs to be two spaces between the sum and
1482 # filepath to match the syntax with md5sum,sha1sum,
1483 # sha256sum. This way also *sum -c *SUMS can be used.
1484 wf.write("%s %s\n" % (hash_value, f))
1486 outimages.append("%s/%s" % (destdir, hash_name))
1489 'MD5SUMS' : misc.get_md5sum,
1490 'SHA1SUMS' : misc.get_sha1sum,
1491 'SHA256SUMS' : misc.get_sha256sum
1494 for k, v in hash_dict.items():
1495 generate_hashsum(k, v)
1497 # Filter out the nonexist file
1498 for fp in outimages[:]:
1499 if not os.path.exists("%s" % fp):
1500 outimages.remove(fp)
1502 def copy_kernel(self):
1503 """ Copy kernel files to the outimage directory.
1504 NOTE: This needs to be called before unmounting the instroot.
1507 if not self._need_copy_kernel:
1510 if not os.path.exists(self.destdir):
1511 os.makedirs(self.destdir)
1513 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1514 kernelfilename = "%s/%s-%s" % (self.destdir,
1516 os.path.basename(kernel))
1517 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1519 shutil.copy(kernel, kernelfilename)
1520 self.outimage.append(kernelfilename)
1522 def copy_attachment(self):
1523 """ Subclass implement it to handle attachment files
1524 NOTE: This needs to be called before unmounting the instroot.
1528 def get_pkg_manager(self):
1529 return self.pkgmgr(target_arch = self.target_arch,
1530 instroot = self._instroot,
1531 cachedir = self.cachedir,
1532 strict_mode = self.strict_mode)
1534 def create_manifest(self):
1535 def get_pack_suffix():
1536 return '.' + self.pack_to.split('.', 1)[1]
1538 if not os.path.exists(self.destdir):
1539 os.makedirs(self.destdir)
1541 now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1542 manifest_dict = {'version': VERSION,
1545 manifest_dict.update({'format': self.img_format})
1547 if hasattr(self, 'logfile') and self.logfile:
1548 manifest_dict.update({'log_file': self.logfile})
1550 if self.image_files:
1552 self.image_files.update({'pack': get_pack_suffix()})
1553 manifest_dict.update({self.img_format: self.image_files})
1555 msger.info('Creating manifest file...')
1556 manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1557 with open(manifest_file_path, 'w') as fest_file:
1558 json.dump(manifest_dict, fest_file, indent=4)
1559 self.outimage.append(manifest_file_path)