3 # Copyright (c) 2007 Red Hat Inc.
4 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms of the GNU General Public License as published by the Free
8 # Software Foundation; version 2 of the License
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc., 59
17 # Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 from __future__ import with_statement
29 from datetime import datetime
32 from mic import kickstart
33 from mic import msger, __version__ as VERSION
34 from mic.utils.errors import CreatorError, KsError, Abort
35 from mic.utils import misc, grabber, runner, fs_related as fs
36 from mic.chroot import kill_proc_inchroot
37 from mic.archive import get_archive_suffixes
38 from mic.conf import configmgr
39 from mic.utils.grabber import myurlgrab
40 #post script max run time
43 class BaseImageCreator(object):
44 """Installs a system to a chroot directory.
46 ImageCreator is the simplest creator class available; it will install and
47 configure a system image according to the supplied kickstart file.
51 import mic.imgcreate as imgcreate
52 ks = imgcreate.read_kickstart("foo.ks")
53 imgcreate.ImageCreator(ks, "foo").create()
62 def __init__(self, createopts = None, pkgmgr = None):
63 """Initialize an ImageCreator instance.
65 ks -- a pykickstart.KickstartParser instance; this instance will be
66 used to drive the install by e.g. providing the list of packages
67 to be installed, the system configuration and %post scripts
69 name -- a name for the image; used for e.g. image filenames or
76 self.__builddir = None
77 self.__bindmounts = []
81 self.tmpdir = "/var/tmp/mic"
82 self.cachedir = "/var/tmp/mic/cache"
83 self.workdir = "/var/tmp/mic/build"
85 self.installerfw_prefix = "INSTALLERFW_"
86 self.target_arch = "noarch"
87 self.strict_mode = False
88 self._local_pkgs_path = None
91 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 (tar, ext) = os.path.splitext(self.pack_to)
120 if ext in (".gz", ".bz2", ".lzo", ".bz") and tar.endswith(".tar"):
122 if ext not in get_archive_suffixes():
123 self.pack_to += ".tar"
125 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
127 # Output image file names
129 # Output info related with manifest
130 self.image_files = {}
131 # A flag to generate checksum
132 self._genchecksum = False
134 self._alt_initrd_name = None
136 self._recording_pkgs = []
138 # available size in root fs, init to 0
139 self._root_fs_avail = 0
141 # Name of the disk image file that is created.
142 self._img_name = None
144 self.image_format = None
146 # Save qemu emulator file names in order to clean up it finally
147 self.qemu_emulators = []
149 # No ks provided when called by convertor, so skip the dependency check
151 # If we have btrfs partition we need to check necessary tools
152 for part in self.ks.handler.partition.partitions:
153 if part.fstype and part.fstype == "btrfs":
154 self._dep_checks.append("mkfs.btrfs")
157 if part.fstype == "cpio":
160 raise KsError("The '--fstype' in ks file need to set 'cpio' when you want to generate image by 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
1090 for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1091 (name, baseurl, mirrorlist, inc, exc,
1092 proxy, proxy_username, proxy_password, debuginfo,
1093 source, gpgkey, disable, ssl_verify, nocache,
1094 cost, priority) = repo
1096 ssl_verify = get_ssl_verify(ssl_verify)
1097 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1098 proxy_username, proxy_password, inc, exc, ssl_verify,
1099 nocache, cost, priority)
1101 if kickstart.exclude_docs(self.ks):
1102 rpm.addMacro("_excludedocs", "1")
1103 rpm.addMacro("_dbpath", "/var/lib/rpm")
1104 rpm.addMacro("__file_context_path", "%{nil}")
1105 if kickstart.inst_langs(self.ks) != None:
1106 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1109 self.__preinstall_packages(pkg_manager)
1110 self.__select_packages(pkg_manager)
1111 self.__select_groups(pkg_manager)
1112 self.__deselect_packages(pkg_manager)
1113 #self.__localinst_packages(pkg_manager)
1114 self.__check_packages(pkg_manager)
1116 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1117 checksize = self._root_fs_avail
1119 checksize -= BOOT_SAFEGUARD
1120 if self.target_arch:
1121 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1123 # If we have multiple partitions, don't check diskspace when rpm run transaction
1124 # because rpm check '/' partition only.
1125 if self.multiple_partitions:
1126 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_DISKSPACE)
1127 pkg_manager.runInstall(checksize)
1128 except CreatorError, e:
1130 except KeyboardInterrupt:
1133 self._pkgs_content = pkg_manager.getAllContent()
1134 self._pkgs_license = pkg_manager.getPkgsLicense()
1135 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1136 self.__attachment_packages(pkg_manager)
1140 if checkScriptletError(self._instroot + "/tmp/.postscript/error/", "_error"):
1141 showErrorInfo(self._instroot + "/tmp/.preload_install_error")
1142 raise CreatorError('scriptlet errors occurred')
1147 # do some clean up to avoid lvm info leakage. this sucks.
1148 for subdir in ("cache", "backup", "archive"):
1149 lvmdir = self._instroot + "/etc/lvm/" + subdir
1151 for f in os.listdir(lvmdir):
1152 os.unlink(lvmdir + "/" + f)
1156 def tpkinstall(self):
1158 tpk_pkgs = kickstart.get_tpkpackages(self.ks)
1159 tpk_repoList = kickstart.get_tpkrepos(self.ks)
1160 if tpk_repoList and tpk_pkgs:
1161 tpk_dir = "/usr/apps/.preload-tpk"
1162 fs.makedirs(self._instroot + "/usr/apps")
1163 fs.makedirs(self._instroot + tpk_dir)
1164 for pkg in tpk_pkgs:
1166 for tpk_repo in tpk_repoList:
1167 if hasattr(tpk_repo,'baseurl') and tpk_repo.baseurl.startswith("file:"):
1168 tpk_repourl = tpk_repo.baseurl.replace('file:','')
1169 tpk_repourl = "/%s" % tpk_repourl.lstrip('/')
1170 tpk_pkgpath = tpk_repourl + "/"+ pkg
1171 if os.path.isfile(tpk_pkgpath):
1172 shutil.copy(tpk_pkgpath,self._instroot + tpk_dir)
1175 elif hasattr(tpk_repo,'baseurl'):
1176 url = tpk_repo.baseurl.join(pkg)
1177 filename = self._instroot+tpk_dir+"/"+pkg
1178 if tpk_repo.baseurl.startswith("http:"):
1180 status = urllib.urlopen(url).code
1182 filename = myurlgrab(url.full, filename, None)
1185 elif status == 404 or status == None:
1187 #url is ok, then download, url wrong, check other url.
1188 elif tpk_repo.baseurl.startswith("https:") :
1191 filename = myurlgrab(url.full, filename, None)
1192 except CreatorError:
1195 raise CreatorError("Tpk package missing.")
1197 def postinstall(self):
1200 def _get_sign_scripts_env(self):
1201 """Return an environment dict for %post-umount scripts.
1203 This is the hook where subclasses may specify some environment
1204 variables for %post-umount scripts by return a dict containing the
1205 desired environment.
1210 # Directory path of images
1212 env['IMG_DIR_PATH'] = str(self._imgdir)
1216 for item in self._instloops:
1217 imgfiles.append(item['name'])
1219 imgpaths.append(os.path.join(self._imgdir, item['name']))
1222 env['IMG_FILES'] = ' '.join(imgfiles)
1224 # Absolute path of images
1225 env['IMG_PATHS'] = ' '.join(imgpaths)
1229 def run_sign_scripts(self):
1230 if kickstart.get_sign_scripts(self.ks)==[]:
1232 msger.info("Running sign scripts ...")
1233 if os.path.exists(self._instroot + "/tmp"):
1234 shutil.rmtree(self._instroot + "/tmp")
1235 os.mkdir (self._instroot + "/tmp", 0755)
1236 for s in kickstart.get_sign_scripts(self.ks):
1237 (fd, path) = tempfile.mkstemp(prefix = "ks-runscript-",
1238 dir = self._instroot + "/tmp")
1239 s.script = s.script.replace("\r", "")
1240 os.write(fd, s.script)
1241 if s.interp == '/bin/sh' or s.interp == '/bin/bash':
1243 os.write(fd, 'exit 0\n')
1245 os.chmod(path, 0700)
1247 for item in os.listdir(self._imgdir):
1248 sub = os.path.splitext(item)[1]
1250 shutil.move(os.path.join(self._imgdir, item),
1251 os.path.join(self._instroot + "/tmp", item))
1252 oldoutdir = os.getcwd()
1253 os.chdir(self._instroot + "/tmp")
1255 env = self._get_sign_scripts_env()
1256 #*.img files are moved to self._instroot + "/tmp" directory in running runscripts
1257 env['IMG_PATHS'] = env['IMG_PATHS'].replace(self._imgdir,self._instroot + "/tmp")
1260 p = subprocess.Popen([s.interp, path],
1262 stdout = subprocess.PIPE,
1263 stderr = subprocess.STDOUT)
1264 while p.poll() == None:
1265 msger.info(p.stdout.readline().strip())
1266 if p.returncode != 0:
1267 raise CreatorError("Failed to execute %%sign script "
1268 "with '%s'" % (s.interp))
1269 except OSError, (err, msg):
1270 raise CreatorError("Failed to execute %%sign script "
1271 "with '%s' : %s" % (s.interp, msg))
1275 for item in os.listdir(self._instroot + "/tmp"):
1276 shutil.move(os.path.join(self._instroot + "/tmp", item),
1277 os.path.join(self._imgdir, item))
1279 def __run_post_scripts(self):
1280 msger.info("Running post scripts ...")
1281 if os.path.exists(self._instroot + "/tmp"):
1282 shutil.rmtree(self._instroot + "/tmp")
1283 os.mkdir (self._instroot + "/tmp", 0755)
1284 for s in kickstart.get_post_scripts(self.ks):
1285 (fd, path) = tempfile.mkstemp(prefix = "ks-postscript-",
1286 dir = self._instroot + "/tmp")
1288 s.script = s.script.replace("\r", "")
1289 os.write(fd, s.script)
1290 if s.interp == '/bin/sh' or s.interp == '/bin/bash':
1292 os.write(fd, 'exit 0\n')
1294 os.chmod(path, 0700)
1296 env = self._get_post_scripts_env(s.inChroot)
1297 if 'PATH' not in env:
1298 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1304 preexec = self._chroot
1305 script = "/tmp/" + os.path.basename(path)
1307 start_time = time.time()
1310 p = subprocess.Popen([s.interp, script],
1311 preexec_fn = preexec,
1313 stdout = subprocess.PIPE,
1314 stderr = subprocess.STDOUT)
1315 while p.poll() == None:
1316 msger.info(p.stdout.readline().strip())
1317 end_time = time.time()
1318 if (end_time - start_time)/60 > MAX_RUN_TIME:
1319 raise CreatorError("Your post script is executed more than "+MAX_RUN_TIME+"mins, please check it!")
1320 if p.returncode != 0:
1321 raise CreatorError("Failed to execute %%post script "
1322 "with '%s'" % (s.interp))
1323 except OSError, (err, msg):
1324 raise CreatorError("Failed to execute %%post script "
1325 "with '%s' : %s" % (s.interp, msg))
1329 def __save_repo_keys(self, repodata):
1333 gpgkeydir = "/etc/pki/rpm-gpg"
1334 fs.makedirs(self._instroot + gpgkeydir)
1335 for repo in repodata:
1337 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1338 shutil.copy(repo["repokey"], self._instroot + repokey)
1340 def configure(self, repodata = None):
1341 """Configure the system image according to the kickstart.
1343 This method applies the (e.g. keyboard or network) configuration
1344 specified in the kickstart and executes the kickstart %post scripts.
1346 If necessary, it also prepares the image to be bootable by e.g.
1347 creating an initrd and bootloader configuration.
1350 ksh = self.ks.handler
1352 msger.info('Applying configurations ...')
1354 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1355 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1356 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1357 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1358 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1359 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1360 kickstart.UserConfig(self._instroot).apply(ksh.user)
1361 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1362 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1363 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1364 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1365 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1366 self.__save_repo_keys(repodata)
1367 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1369 msger.warning("Failed to apply configuration to image")
1372 self._create_bootconfig()
1373 self.__run_post_scripts()
1375 def launch_shell(self, launch):
1376 """Launch a shell in the install root.
1378 This method is launches a bash shell chroot()ed in the install root;
1379 this can be useful for debugging.
1383 msger.info("Launching shell. Exit to continue.")
1384 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1386 def do_genchecksum(self, image_name):
1387 if not self._genchecksum:
1390 md5sum = misc.get_md5sum(image_name)
1391 with open(image_name + ".md5sum", "w") as f:
1392 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1393 self.outimage.append(image_name+".md5sum")
1395 def remove_exclude_image(self):
1396 for item in self._instloops[:]:
1397 if item['exclude_image']:
1398 msger.info("Removing %s in image." % item['name'])
1399 imgfile = os.path.join(self._imgdir, item['name'])
1402 except OSError as err:
1403 if err.errno == errno.ENOENT:
1405 self._instloops.remove(item)
1407 def create_cpio_image(self):
1408 for item in self._instloops:
1409 if item['cpioopts']:
1410 msger.info("Create image by cpio.")
1411 tmp_cpio = self.__builddir + "/tmp-cpio"
1412 if not os.path.exists(tmp_cpio):
1414 tmp_cpio_imgfile = os.path.join(tmp_cpio, item['name'])
1416 cpiocmd = fs.find_binary_path('cpio')
1418 oldoutdir = os.getcwd()
1419 os.chdir(os.path.join(self._instroot, item['mountpoint'].lstrip('/')))
1420 # find . | cpio --create --'format=newc' | gzip > ../ramdisk.img
1421 runner.show('find . | cpio --create %s | gzip > %s' % (item['cpioopts'], tmp_cpio_imgfile))
1423 except OSError, (errno, msg):
1424 raise CreatorError("Create image by cpio error: %s" % msg)
1426 def copy_cpio_image(self):
1427 for item in self._instloops:
1428 if item['cpioopts']:
1429 tmp_cpio = self.__builddir + "/tmp-cpio"
1430 msger.info("Copy cpio image from %s to %s." %(tmp_cpio, self._imgdir))
1432 shutil.copyfile(os.path.join(tmp_cpio, item['name']),os.path.join(self._imgdir, item['name']))
1434 raise CreatorError("Copy cpio image error")
1435 os.remove(os.path.join(tmp_cpio, item['name']))
1436 if not os.listdir(tmp_cpio):
1437 shutil.rmtree(tmp_cpio, ignore_errors=True)
1439 def package(self, destdir = "."):
1440 """Prepares the created image for final delivery.
1442 In its simplest form, this method merely copies the install root to the
1443 supplied destination directory; other subclasses may choose to package
1444 the image by e.g. creating a bootable ISO containing the image and
1445 bootloader configuration.
1447 destdir -- the directory into which the final image should be moved;
1448 this defaults to the current directory.
1451 self.remove_exclude_image()
1453 self._stage_final_image()
1455 if not os.path.exists(destdir):
1456 fs.makedirs(destdir)
1458 if self._recording_pkgs:
1459 self._save_recording_pkgs(destdir)
1461 # For image formats with two or multiple image files, it will be
1462 # better to put them under a directory
1463 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1464 destdir = os.path.join(destdir, "%s-%s" \
1465 % (self.name, self.image_format))
1466 msger.debug("creating destination dir: %s" % destdir)
1467 fs.makedirs(destdir)
1469 # Ensure all data is flushed to _outdir
1470 runner.quiet('sync')
1472 misc.check_space_pre_cp(self._outdir, destdir)
1473 for f in os.listdir(self._outdir):
1474 shutil.move(os.path.join(self._outdir, f),
1475 os.path.join(destdir, f))
1476 self.outimage.append(os.path.join(destdir, f))
1477 self.do_genchecksum(os.path.join(destdir, f))
1479 def print_outimage_info(self):
1480 msg = "The new image can be found here:\n"
1481 self.outimage.sort()
1482 for file in self.outimage:
1483 msg += ' %s\n' % os.path.abspath(file)
1487 def check_depend_tools(self):
1488 for tool in self._dep_checks:
1489 fs.find_binary_path(tool)
1491 def package_output(self, image_format, destdir = ".", package="none"):
1492 if not package or package == "none":
1495 destdir = os.path.abspath(os.path.expanduser(destdir))
1496 (pkg, comp) = os.path.splitext(package)
1498 comp=comp.lstrip(".")
1502 dst = "%s/%s-%s.tar.%s" %\
1503 (destdir, self.name, image_format, comp)
1505 dst = "%s/%s-%s.tar" %\
1506 (destdir, self.name, image_format)
1508 msger.info("creating %s" % dst)
1509 tar = tarfile.open(dst, "w:" + comp)
1511 for file in self.outimage:
1512 msger.info("adding %s to %s" % (file, dst))
1514 arcname=os.path.join("%s-%s" \
1515 % (self.name, image_format),
1516 os.path.basename(file)))
1517 if os.path.isdir(file):
1518 shutil.rmtree(file, ignore_errors = True)
1524 '''All the file in outimage has been packaged into tar.* file'''
1525 self.outimage = [dst]
1527 def release_output(self, config, destdir, release):
1528 """ Create release directory and files
1532 """ release path """
1533 return os.path.join(destdir, fn)
1535 outimages = self.outimage
1538 new_kspath = _rpath(self.name+'.ks')
1539 with open(config) as fr:
1540 with open(new_kspath, "w") as wf:
1541 # When building a release we want to make sure the .ks
1542 # file generates the same build even when --release not used.
1543 wf.write(fr.read().replace("@BUILD_ID@", release))
1544 outimages.append(new_kspath)
1546 # save log file, logfile is only available in creator attrs
1547 if hasattr(self, 'releaselog') and self.releaselog:
1548 outimages.append(self.logfile)
1550 # rename iso and usbimg
1551 for f in os.listdir(destdir):
1552 if f.endswith(".iso"):
1553 newf = f[:-4] + '.img'
1554 elif f.endswith(".usbimg"):
1555 newf = f[:-7] + '.img'
1558 os.rename(_rpath(f), _rpath(newf))
1559 outimages.append(_rpath(newf))
1561 # generate MD5SUMS SHA1SUMS SHA256SUMS
1562 def generate_hashsum(hash_name, hash_method):
1563 with open(_rpath(hash_name), "w") as wf:
1564 for f in os.listdir(destdir):
1565 if f.endswith('SUMS'):
1568 if os.path.isdir(os.path.join(destdir, f)):
1571 hash_value = hash_method(_rpath(f))
1572 # There needs to be two spaces between the sum and
1573 # filepath to match the syntax with md5sum,sha1sum,
1574 # sha256sum. This way also *sum -c *SUMS can be used.
1575 wf.write("%s %s\n" % (hash_value, f))
1577 outimages.append("%s/%s" % (destdir, hash_name))
1580 'MD5SUMS' : misc.get_md5sum,
1581 'SHA1SUMS' : misc.get_sha1sum,
1582 'SHA256SUMS' : misc.get_sha256sum
1585 for k, v in hash_dict.items():
1586 generate_hashsum(k, v)
1588 # Filter out the nonexist file
1589 for fp in outimages[:]:
1590 if not os.path.exists("%s" % fp):
1591 outimages.remove(fp)
1593 def copy_kernel(self):
1594 """ Copy kernel files to the outimage directory.
1595 NOTE: This needs to be called before unmounting the instroot.
1598 if not self._need_copy_kernel:
1601 if not os.path.exists(self.destdir):
1602 os.makedirs(self.destdir)
1604 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1605 kernelfilename = "%s/%s-%s" % (self.destdir,
1607 os.path.basename(kernel))
1608 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1610 shutil.copy(kernel, kernelfilename)
1611 self.outimage.append(kernelfilename)
1613 def copy_attachment(self):
1614 """ Subclass implement it to handle attachment files
1615 NOTE: This needs to be called before unmounting the instroot.
1619 def get_pkg_manager(self):
1620 return self.pkgmgr(target_arch = self.target_arch,
1621 instroot = self._instroot,
1622 cachedir = self.cachedir,
1623 strict_mode = self.strict_mode)
1625 def create_manifest(self):
1626 def get_pack_suffix():
1627 return '.' + self.pack_to.split('.', 1)[1]
1629 if not os.path.exists(self.destdir):
1630 os.makedirs(self.destdir)
1632 now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1633 manifest_dict = {'version': VERSION,
1636 manifest_dict.update({'format': self.img_format})
1638 if hasattr(self, 'logfile') and self.logfile:
1639 manifest_dict.update({'log_file': self.logfile})
1641 if self.image_files:
1643 self.image_files.update({'pack': get_pack_suffix()})
1644 manifest_dict.update({self.img_format: self.image_files})
1646 msger.info('Creating manifest file...')
1647 manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1648 with open(manifest_file_path, 'w') as fest_file:
1649 json.dump(manifest_dict, fest_file, indent=4)
1650 self.outimage.append(manifest_file_path)