51ca9f76b7bb97c8a96b26b331c48c484c8f22e0
[tools/mic.git] / mic / imager / baseimager.py
1
2 #!/usr/bin/python -tt
3 #
4 # Copyright (c) 2007 Red Hat  Inc.
5 # Copyright (c) 2009, 2010, 2011 Intel, Inc.
6 #
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
10 #
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
14 # for more details.
15 #
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.
19
20 from __future__ import with_statement
21 import os, sys
22 import stat
23 import tempfile
24 import shutil
25 import subprocess
26 import re
27 import tarfile
28 import glob
29 import json
30 from datetime import datetime
31
32 import rpm
33
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
41 class BaseImageCreator(object):
42     """Installs a system to a chroot directory.
43
44     ImageCreator is the simplest creator class available; it will install and
45     configure a system image according to the supplied kickstart file.
46
47     e.g.
48
49       import mic.imgcreate as imgcreate
50       ks = imgcreate.read_kickstart("foo.ks")
51       imgcreate.ImageCreator(ks, "foo").create()
52
53     """
54     # Output image format
55     img_format = ''
56
57     def __del__(self):
58         self.cleanup()
59
60     def __init__(self, createopts = None, pkgmgr = None):
61         """Initialize an ImageCreator instance.
62
63         ks -- a pykickstart.KickstartParser instance; this instance will be
64               used to drive the install by e.g. providing the list of packages
65               to be installed, the system configuration and %post scripts
66
67         name -- a name for the image; used for e.g. image filenames or
68                 filesystem labels
69         """
70
71         self.pkgmgr = pkgmgr
72         self.distro_name = ""
73
74         self.__builddir = None
75         self.__bindmounts = []
76
77         self.ks = None
78         self.name = "target"
79         self.tmpdir = "/var/tmp/mic"
80         self.cachedir = "/var/tmp/mic/cache"
81         self.workdir = "/var/tmp/mic/build"
82         self.destdir = "."
83         self.installerfw_prefix = "INSTALLERFW_"
84         self.target_arch = "noarch"
85         self.strict_mode = False
86         self._local_pkgs_path = None
87         self.pack_to = None
88         self.repourl = {}
89         self.multiple_partitions = False
90
91         # If the kernel is save to the destdir when copy_kernel cmd is called.
92         self._need_copy_kernel = False
93         # setup tmpfs tmpdir when enabletmpfs is True
94         self.enabletmpfs = False
95
96         if createopts:
97             # Mapping table for variables that have different names.
98             optmap = {"pkgmgr" : "pkgmgr_name",
99                       "arch" : "target_arch",
100                       "local_pkgs_path" : "_local_pkgs_path",
101                       "copy_kernel" : "_need_copy_kernel",
102                       "strict_mode" : "strict_mode",
103                      }
104
105             # update setting from createopts
106             for key in createopts.keys():
107                 if key in optmap:
108                     option = optmap[key]
109                 else:
110                     option = key
111                 setattr(self, option, createopts[key])
112
113             self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
114
115             if self.pack_to:
116                 if '@NAME@' in self.pack_to:
117                     self.pack_to = self.pack_to.replace('@NAME@', self.name)
118                 (tar, ext) = os.path.splitext(self.pack_to)
119                 if ext in (".gz", ".bz2", ".lzo", ".bz") and tar.endswith(".tar"):
120                     ext = ".tar" + ext
121                 if ext not in get_archive_suffixes():
122                     self.pack_to += ".tar"
123
124         self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
125
126         # Output image file names
127         self.outimage = []
128         # Output info related with manifest
129         self.image_files = {}
130         # A flag to generate checksum
131         self._genchecksum = False
132
133         self._alt_initrd_name = None
134
135         self._recording_pkgs = []
136
137         # available size in root fs, init to 0
138         self._root_fs_avail = 0
139
140         # Name of the disk image file that is created.
141         self._img_name = None
142
143         self.image_format = None
144
145         # Save qemu emulator file name in order to clean up it finally
146         self.qemu_emulator = None
147
148         # No ks provided when called by convertor, so skip the dependency check
149         if self.ks:
150             # If we have btrfs partition we need to check necessary tools
151             for part in self.ks.handler.partition.partitions:
152                 if part.fstype and part.fstype == "btrfs":
153                     self._dep_checks.append("mkfs.btrfs")
154                     break
155             if len(self.ks.handler.partition.partitions) > 1:
156                 self.multiple_partitions = True
157
158         if self.target_arch:
159             if self.target_arch.startswith("arm"):
160                 for dep in self._dep_checks:
161                     if dep == "extlinux":
162                         self._dep_checks.remove(dep)
163
164                 if not os.path.exists("/usr/bin/qemu-arm") or \
165                    not misc.is_statically_linked("/usr/bin/qemu-arm"):
166                     self._dep_checks.append("qemu-arm-static")
167
168                 if os.path.exists("/proc/sys/vm/vdso_enabled"):
169                     vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
170                     vdso_value = vdso_fh.read().strip()
171                     vdso_fh.close()
172                     if (int)(vdso_value) == 1:
173                         msger.warning("vdso is enabled on your host, which might "
174                             "cause problems with arm emulations.\n"
175                             "\tYou can disable vdso with following command before "
176                             "starting image build:\n"
177                             "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
178             elif self.target_arch == "mipsel":
179                 for dep in self._dep_checks:
180                     if dep == "extlinux":
181                         self._dep_checks.remove(dep)
182
183                 if not os.path.exists("/usr/bin/qemu-mipsel") or \
184                    not misc.is_statically_linked("/usr/bin/qemu-mipsel"):
185                     self._dep_checks.append("qemu-mipsel-static")
186
187                 if os.path.exists("/proc/sys/vm/vdso_enabled"):
188                     vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
189                     vdso_value = vdso_fh.read().strip()
190                     vdso_fh.close()
191                     if (int)(vdso_value) == 1:
192                         msger.warning("vdso is enabled on your host, which might "
193                             "cause problems with mipsel emulations.\n"
194                             "\tYou can disable vdso with following command before "
195                             "starting image build:\n"
196                             "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
197
198         # make sure the specified tmpdir and cachedir exist
199         if not os.path.exists(self.tmpdir):
200             os.makedirs(self.tmpdir)
201         if not os.path.exists(self.cachedir):
202             os.makedirs(self.cachedir)
203
204
205     #
206     # Properties
207     #
208     def __get_instroot(self):
209         if self.__builddir is None:
210             raise CreatorError("_instroot is not valid before calling mount()")
211         return self.__builddir + "/install_root"
212     _instroot = property(__get_instroot)
213     """The location of the install root directory.
214
215     This is the directory into which the system is installed. Subclasses may
216     mount a filesystem image here or copy files to/from here.
217
218     Note, this directory does not exist before ImageCreator.mount() is called.
219
220     Note also, this is a read-only attribute.
221
222     """
223
224     def __get_outdir(self):
225         if self.__builddir is None:
226             raise CreatorError("_outdir is not valid before calling mount()")
227         return self.__builddir + "/out"
228     _outdir = property(__get_outdir)
229     """The staging location for the final image.
230
231     This is where subclasses should stage any files that are part of the final
232     image. ImageCreator.package() will copy any files found here into the
233     requested destination directory.
234
235     Note, this directory does not exist before ImageCreator.mount() is called.
236
237     Note also, this is a read-only attribute.
238
239     """
240
241
242     #
243     # Hooks for subclasses
244     #
245     def _mount_instroot(self, base_on = None):
246         """Mount or prepare the install root directory.
247
248         This is the hook where subclasses may prepare the install root by e.g.
249         mounting creating and loopback mounting a filesystem image to
250         _instroot.
251
252         There is no default implementation.
253
254         base_on -- this is the value passed to mount() and can be interpreted
255                    as the subclass wishes; it might e.g. be the location of
256                    a previously created ISO containing a system image.
257
258         """
259         pass
260
261     def _unmount_instroot(self):
262         """Undo anything performed in _mount_instroot().
263
264         This is the hook where subclasses must undo anything which was done
265         in _mount_instroot(). For example, if a filesystem image was mounted
266         onto _instroot, it should be unmounted here.
267
268         There is no default implementation.
269
270         """
271         pass
272
273     def _create_bootconfig(self):
274         """Configure the image so that it's bootable.
275
276         This is the hook where subclasses may prepare the image for booting by
277         e.g. creating an initramfs and bootloader configuration.
278
279         This hook is called while the install root is still mounted, after the
280         packages have been installed and the kickstart configuration has been
281         applied, but before the %post scripts have been executed.
282
283         There is no default implementation.
284
285         """
286         pass
287
288     def _stage_final_image(self):
289         """Stage the final system image in _outdir.
290
291         This is the hook where subclasses should place the image in _outdir
292         so that package() can copy it to the requested destination directory.
293
294         By default, this moves the install root into _outdir.
295
296         """
297         shutil.move(self._instroot, self._outdir + "/" + self.name)
298
299     def get_installed_packages(self):
300         return self._pkgs_content.keys()
301
302     def _save_recording_pkgs(self, destdir):
303         """Save the list or content of installed packages to file.
304         """
305         pkgs = self._pkgs_content.keys()
306         pkgs.sort() # inplace op
307
308         if not os.path.exists(destdir):
309             os.makedirs(destdir)
310
311         content = None
312         if 'vcs' in self._recording_pkgs:
313             vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
314             content = '\n'.join(sorted(vcslst))
315         elif 'name' in self._recording_pkgs:
316             content = '\n'.join(pkgs)
317         if content:
318             namefile = os.path.join(destdir, self.name + '.packages')
319             f = open(namefile, "w")
320             f.write(content)
321             f.close()
322             self.outimage.append(namefile);
323
324         # if 'content', save more details
325         if 'content' in self._recording_pkgs:
326             contfile = os.path.join(destdir, self.name + '.files')
327             f = open(contfile, "w")
328
329             for pkg in pkgs:
330                 content = pkg + '\n'
331
332                 pkgcont = self._pkgs_content[pkg]
333                 content += '    '
334                 content += '\n    '.join(pkgcont)
335                 content += '\n'
336
337                 content += '\n'
338                 f.write(content)
339             f.close()
340             self.outimage.append(contfile)
341
342         if 'license' in self._recording_pkgs:
343             licensefile = os.path.join(destdir, self.name + '.license')
344             f = open(licensefile, "w")
345
346             f.write('Summary:\n')
347             for license in reversed(sorted(self._pkgs_license, key=\
348                             lambda license: len(self._pkgs_license[license]))):
349                 f.write("    - %s: %s\n" \
350                         % (license, len(self._pkgs_license[license])))
351
352             f.write('\nDetails:\n')
353             for license in reversed(sorted(self._pkgs_license, key=\
354                             lambda license: len(self._pkgs_license[license]))):
355                 f.write("    - %s:\n" % (license))
356                 for pkg in sorted(self._pkgs_license[license]):
357                     f.write("        - %s\n" % (pkg))
358                 f.write('\n')
359
360             f.close()
361             self.outimage.append(licensefile)
362
363     def _get_required_packages(self):
364         """Return a list of required packages.
365
366         This is the hook where subclasses may specify a set of packages which
367         it requires to be installed.
368
369         This returns an empty list by default.
370
371         Note, subclasses should usually chain up to the base class
372         implementation of this hook.
373
374         """
375         return []
376
377     def _get_excluded_packages(self):
378         """Return a list of excluded packages.
379
380         This is the hook where subclasses may specify a set of packages which
381         it requires _not_ to be installed.
382
383         This returns an empty list by default.
384
385         Note, subclasses should usually chain up to the base class
386         implementation of this hook.
387
388         """
389         return []
390
391     def _get_local_packages(self):
392         """Return a list of rpm path to be local installed.
393
394         This is the hook where subclasses may specify a set of rpms which
395         it requires to be installed locally.
396
397         This returns an empty list by default.
398
399         Note, subclasses should usually chain up to the base class
400         implementation of this hook.
401
402         """
403         if self._local_pkgs_path:
404             if os.path.isdir(self._local_pkgs_path):
405                 return glob.glob(
406                         os.path.join(self._local_pkgs_path, '*.rpm'))
407             elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
408                 return [self._local_pkgs_path]
409
410         return []
411
412     def _get_fstab(self):
413         """Return the desired contents of /etc/fstab.
414
415         This is the hook where subclasses may specify the contents of
416         /etc/fstab by returning a string containing the desired contents.
417
418         A sensible default implementation is provided.
419
420         """
421         s =  "/dev/root  /         %s    %s 0 0\n" \
422              % (self._fstype,
423                 "defaults,noatime" if not self._fsopts else self._fsopts)
424         s += self._get_fstab_special()
425         return s
426
427     def _get_fstab_special(self):
428         s = "devpts     /dev/pts  devpts  gid=5,mode=620   0 0\n"
429         s += "tmpfs      /dev/shm  tmpfs   defaults         0 0\n"
430         s += "proc       /proc     proc    defaults         0 0\n"
431         s += "sysfs      /sys      sysfs   defaults         0 0\n"
432         return s
433
434     def _set_part_env(self, pnum, prop, value):
435         """ This is a helper function which generates an environment variable
436         for a property "prop" with value "value" of a partition number "pnum".
437
438         The naming convention is:
439            * Variables start with INSTALLERFW_PART
440            * Then goes the partition number, the order is the same as
441              specified in the KS file
442            * Then goes the property name
443         """
444
445         if value == None:
446             value = ""
447         else:
448             value = str(value)
449
450         name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
451         return { name : value }
452
453     def _get_post_scripts_env(self, in_chroot):
454         """Return an environment dict for %post scripts.
455
456         This is the hook where subclasses may specify some environment
457         variables for %post scripts by return a dict containing the desired
458         environment.
459
460         in_chroot -- whether this %post script is to be executed chroot()ed
461                      into _instroot.
462         """
463
464         env = {}
465         pnum = 0
466
467         for p in kickstart.get_partitions(self.ks):
468             env.update(self._set_part_env(pnum, "SIZE", p.size))
469             env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
470             env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
471             env.update(self._set_part_env(pnum, "LABEL", p.label))
472             env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
473             env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
474             env.update(self._set_part_env(pnum, "ALIGN", p.align))
475             env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
476             env.update(self._set_part_env(pnum, "UUID", p.uuid))
477             env.update(self._set_part_env(pnum, "DEVNODE",
478                                           "/dev/%s%d" % (p.disk, pnum + 1)))
479             env.update(self._set_part_env(pnum, "DISK_DEVNODE",
480                                           "/dev/%s" % p.disk))
481             pnum += 1
482
483         # Count of paritions
484         env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
485
486         # Partition table format
487         ptable_format = self.ks.handler.bootloader.ptable
488         env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
489
490         # The kerned boot parameters
491         kernel_opts = self.ks.handler.bootloader.appendLine
492         env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
493
494         # Name of the image creation tool
495         env[self.installerfw_prefix + "INSTALLER_NAME"] = "mic"
496
497         # The real current location of the mounted file-systems
498         if in_chroot:
499             mount_prefix = "/"
500         else:
501             mount_prefix = self._instroot
502         env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
503
504         # These are historical variables which lack the common name prefix
505         if not in_chroot:
506             env["INSTALL_ROOT"] = self._instroot
507             env["IMG_NAME"] = self._name
508
509         return env
510
511     def __get_imgname(self):
512         return self.name
513     _name = property(__get_imgname)
514     """The name of the image file.
515
516     """
517
518     def _get_kernel_versions(self):
519         """Return a dict detailing the available kernel types/versions.
520
521         This is the hook where subclasses may override what kernel types and
522         versions should be available for e.g. creating the booloader
523         configuration.
524
525         A dict should be returned mapping the available kernel types to a list
526         of the available versions for those kernels.
527
528         The default implementation uses rpm to iterate over everything
529         providing 'kernel', finds /boot/vmlinuz-* and returns the version
530         obtained from the vmlinuz filename. (This can differ from the kernel
531         RPM's n-v-r in the case of e.g. xen)
532
533         """
534         def get_kernel_versions(instroot):
535             ret = {}
536             versions = set()
537             files = glob.glob(instroot + "/boot/vmlinuz-*")
538             for file in files:
539                 version = os.path.basename(file)[8:]
540                 if version is None:
541                     continue
542                 versions.add(version)
543             ret["kernel"] = list(versions)
544             return ret
545
546         def get_version(header):
547             version = None
548             for f in header['filenames']:
549                 if f.startswith('/boot/vmlinuz-'):
550                     version = f[14:]
551             return version
552
553         if self.ks is None:
554             return get_kernel_versions(self._instroot)
555
556         ts = rpm.TransactionSet(self._instroot)
557
558         ret = {}
559         for header in ts.dbMatch('provides', 'kernel'):
560             version = get_version(header)
561             if version is None:
562                 continue
563
564             name = header['name']
565             if not name in ret:
566                 ret[name] = [version]
567             elif not version in ret[name]:
568                 ret[name].append(version)
569
570         return ret
571
572
573     #
574     # Helpers for subclasses
575     #
576     def _do_bindmounts(self):
577         """Mount various system directories onto _instroot.
578
579         This method is called by mount(), but may also be used by subclasses
580         in order to re-mount the bindmounts after modifying the underlying
581         filesystem.
582
583         """
584         for b in self.__bindmounts:
585             b.mount()
586
587     def _undo_bindmounts(self):
588         """Unmount the bind-mounted system directories from _instroot.
589
590         This method is usually only called by unmount(), but may also be used
591         by subclasses in order to gain access to the filesystem obscured by
592         the bindmounts - e.g. in order to create device nodes on the image
593         filesystem.
594
595         """
596         self.__bindmounts.reverse()
597         for b in self.__bindmounts:
598             b.unmount()
599
600     def _chroot(self):
601         """Chroot into the install root.
602
603         This method may be used by subclasses when executing programs inside
604         the install root e.g.
605
606           subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
607
608         """
609         os.chroot(self._instroot)
610         os.chdir("/")
611
612     def _mkdtemp(self, prefix = "tmp-"):
613         """Create a temporary directory.
614
615         This method may be used by subclasses to create a temporary directory
616         for use in building the final image - e.g. a subclass might create
617         a temporary directory in order to bundle a set of files into a package.
618
619         The subclass may delete this directory if it wishes, but it will be
620         automatically deleted by cleanup().
621
622         The absolute path to the temporary directory is returned.
623
624         Note, this method should only be called after mount() has been called.
625
626         prefix -- a prefix which should be used when creating the directory;
627                   defaults to "tmp-".
628
629         """
630         self.__ensure_builddir()
631         return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
632
633     def _mkstemp(self, prefix = "tmp-"):
634         """Create a temporary file.
635
636         This method may be used by subclasses to create a temporary file
637         for use in building the final image - e.g. a subclass might need
638         a temporary location to unpack a compressed file.
639
640         The subclass may delete this file if it wishes, but it will be
641         automatically deleted by cleanup().
642
643         A tuple containing a file descriptor (returned from os.open() and the
644         absolute path to the temporary directory is returned.
645
646         Note, this method should only be called after mount() has been called.
647
648         prefix -- a prefix which should be used when creating the file;
649                   defaults to "tmp-".
650
651         """
652         self.__ensure_builddir()
653         return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
654
655     def _mktemp(self, prefix = "tmp-"):
656         """Create a temporary file.
657
658         This method simply calls _mkstemp() and closes the returned file
659         descriptor.
660
661         The absolute path to the temporary file is returned.
662
663         Note, this method should only be called after mount() has been called.
664
665         prefix -- a prefix which should be used when creating the file;
666                   defaults to "tmp-".
667
668         """
669
670         (f, path) = self._mkstemp(prefix)
671         os.close(f)
672         return path
673
674
675     #
676     # Actual implementation
677     #
678     def __ensure_builddir(self):
679         if not self.__builddir is None:
680             return
681
682         try:
683             self.workdir = os.path.join(self.tmpdir, "build")
684             if not os.path.exists(self.workdir):
685                 os.makedirs(self.workdir)
686             self.__builddir = tempfile.mkdtemp(dir = self.workdir,
687                                                prefix = "imgcreate-")
688         except OSError, (err, msg):
689             raise CreatorError("Failed create build directory in %s: %s" %
690                                (self.tmpdir, msg))
691
692     def get_cachedir(self, cachedir = None):
693         if self.cachedir:
694             return self.cachedir
695
696         self.__ensure_builddir()
697         if cachedir:
698             self.cachedir = cachedir
699         else:
700             self.cachedir = self.__builddir + "/mic-cache"
701         fs.makedirs(self.cachedir)
702         return self.cachedir
703
704     def __sanity_check(self):
705         """Ensure that the config we've been given is sane."""
706         if not (kickstart.get_packages(self.ks) or
707                 kickstart.get_groups(self.ks)):
708             raise CreatorError("No packages or groups specified")
709
710         kickstart.convert_method_to_repo(self.ks)
711
712         if not kickstart.get_repos(self.ks):
713             raise CreatorError("No repositories specified")
714
715     def __write_fstab(self):
716         if kickstart.use_installerfw(self.ks, "fstab"):
717             # The fstab file will be generated by installer framework scripts
718             # instead.
719             return None
720         fstab_contents = self._get_fstab()
721         if fstab_contents:
722             fstab = open(self._instroot + "/etc/fstab", "w")
723             fstab.write(fstab_contents)
724             fstab.close()
725
726     def __create_minimal_dev(self):
727         """Create a minimal /dev so that we don't corrupt the host /dev"""
728         origumask = os.umask(0000)
729         devices = (('null',   1, 3, 0666),
730                    ('urandom',1, 9, 0666),
731                    ('random', 1, 8, 0666),
732                    ('full',   1, 7, 0666),
733                    ('ptmx',   5, 2, 0666),
734                    ('tty',    5, 0, 0666),
735                    ('zero',   1, 5, 0666))
736
737         links = (("/proc/self/fd", "/dev/fd"),
738                  ("/proc/self/fd/0", "/dev/stdin"),
739                  ("/proc/self/fd/1", "/dev/stdout"),
740                  ("/proc/self/fd/2", "/dev/stderr"))
741
742         for (node, major, minor, perm) in devices:
743             if not os.path.exists(self._instroot + "/dev/" + node):
744                 os.mknod(self._instroot + "/dev/" + node,
745                          perm | stat.S_IFCHR,
746                          os.makedev(major,minor))
747
748         for (src, dest) in links:
749             if not os.path.exists(self._instroot + dest):
750                 os.symlink(src, self._instroot + dest)
751
752         os.umask(origumask)
753
754     def __setup_tmpdir(self):
755         if not self.enabletmpfs:
756             return
757
758         runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
759
760     def __clean_tmpdir(self):
761         if not self.enabletmpfs:
762             return
763
764         runner.show('umount -l %s' % self.workdir)
765
766     def mount(self, base_on = None, cachedir = None):
767         """Setup the target filesystem in preparation for an install.
768
769         This function sets up the filesystem which the ImageCreator will
770         install into and configure. The ImageCreator class merely creates an
771         install root directory, bind mounts some system directories (e.g. /dev)
772         and writes out /etc/fstab. Other subclasses may also e.g. create a
773         sparse file, format it and loopback mount it to the install root.
774
775         base_on -- a previous install on which to base this install; defaults
776                    to None, causing a new image to be created
777
778         cachedir -- a directory in which to store the Yum cache; defaults to
779                     None, causing a new cache to be created; by setting this
780                     to another directory, the same cache can be reused across
781                     multiple installs.
782
783         """
784         self.__setup_tmpdir()
785         self.__ensure_builddir()
786
787         # prevent popup dialog in Ubuntu(s)
788         misc.hide_loopdev_presentation()
789
790         fs.makedirs(self._instroot)
791         fs.makedirs(self._outdir)
792
793         self._mount_instroot(base_on)
794
795         for d in ("/dev/pts",
796                   "/etc",
797                   "/boot",
798                   "/var/log",
799                   "/sys",
800                   "/proc",
801                   "/usr/bin"):
802             fs.makedirs(self._instroot + d)
803
804         if self.target_arch and self.target_arch.startswith("arm") or \
805             self.target_arch == "aarch64" or self.target_arch == "mipsel" :
806             self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
807                                                           self.target_arch)
808
809         self.get_cachedir(cachedir)
810
811         # bind mount system directories into _instroot
812         for (f, dest) in [("/sys", None),
813                           ("/proc", None),
814                           ("/proc/sys/fs/binfmt_misc", None),
815                           ("/dev/pts", None)]:
816             self.__bindmounts.append(
817                     fs.BindChrootMount(
818                         f, self._instroot, dest))
819
820         self._do_bindmounts()
821
822         self.__create_minimal_dev()
823
824         if os.path.exists(self._instroot + "/etc/mtab"):
825             os.unlink(self._instroot + "/etc/mtab")
826         os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
827
828         self.__write_fstab()
829
830         # get size of available space in 'instroot' fs
831         self._root_fs_avail = misc.get_filesystem_avail(self._instroot)
832
833     def unmount(self):
834         """Unmounts the target filesystem.
835
836         The ImageCreator class detaches the system from the install root, but
837         other subclasses may also detach the loopback mounted filesystem image
838         from the install root.
839
840         """
841         try:
842             mtab = self._instroot + "/etc/mtab"
843             if not os.path.islink(mtab):
844                 os.unlink(self._instroot + "/etc/mtab")
845
846             if self.qemu_emulator:
847                 os.unlink(self._instroot + self.qemu_emulator)
848         except OSError:
849             pass
850
851         self._undo_bindmounts()
852
853         """ Clean up yum garbage """
854         try:
855             instroot_pdir = os.path.dirname(self._instroot + self._instroot)
856             if os.path.exists(instroot_pdir):
857                 shutil.rmtree(instroot_pdir, ignore_errors = True)
858             yumlibdir = self._instroot + "/var/lib/yum"
859             if os.path.exists(yumlibdir):
860                 shutil.rmtree(yumlibdir, ignore_errors = True)
861         except OSError:
862             pass
863
864         self._unmount_instroot()
865
866         # reset settings of popup dialog in Ubuntu(s)
867         misc.unhide_loopdev_presentation()
868
869
870     def cleanup(self):
871         """Unmounts the target filesystem and deletes temporary files.
872
873         This method calls unmount() and then deletes any temporary files and
874         directories that were created on the host system while building the
875         image.
876
877         Note, make sure to call this method once finished with the creator
878         instance in order to ensure no stale files are left on the host e.g.:
879
880           creator = ImageCreator(ks, name)
881           try:
882               creator.create()
883           finally:
884               creator.cleanup()
885
886         """
887         if not self.__builddir:
888             return
889
890         kill_proc_inchroot(self._instroot)
891
892         self.unmount()
893
894         shutil.rmtree(self.__builddir, ignore_errors = True)
895         self.__builddir = None
896
897         self.__clean_tmpdir()
898
899     def __is_excluded_pkg(self, pkg):
900         if pkg in self._excluded_pkgs:
901             self._excluded_pkgs.remove(pkg)
902             return True
903
904         for xpkg in self._excluded_pkgs:
905             if xpkg.endswith('*'):
906                 if pkg.startswith(xpkg[:-1]):
907                     return True
908             elif xpkg.startswith('*'):
909                 if pkg.endswith(xpkg[1:]):
910                     return True
911
912         return None
913
914     def __select_packages(self, pkg_manager):
915         skipped_pkgs = []
916         for pkg in self._required_pkgs:
917             e = pkg_manager.selectPackage(pkg)
918             if e:
919                 if kickstart.ignore_missing(self.ks):
920                     skipped_pkgs.append(pkg)
921                 elif self.__is_excluded_pkg(pkg):
922                     skipped_pkgs.append(pkg)
923                 else:
924                     raise CreatorError("Failed to find package '%s' : %s" %
925                                        (pkg, e))
926
927         for pkg in skipped_pkgs:
928             msger.warning("Skipping missing package '%s'" % (pkg,))
929
930     def __select_groups(self, pkg_manager):
931         skipped_groups = []
932         for group in self._required_groups:
933             e = pkg_manager.selectGroup(group.name, group.include)
934             if e:
935                 if kickstart.ignore_missing(self.ks):
936                     skipped_groups.append(group)
937                 else:
938                     raise CreatorError("Failed to find group '%s' : %s" %
939                                        (group.name, e))
940
941         for group in skipped_groups:
942             msger.warning("Skipping missing group '%s'" % (group.name,))
943
944     def __deselect_packages(self, pkg_manager):
945         for pkg in self._excluded_pkgs:
946             pkg_manager.deselectPackage(pkg)
947
948     def __localinst_packages(self, pkg_manager):
949         for rpm_path in self._get_local_packages():
950             pkg_manager.installLocal(rpm_path)
951
952     def __preinstall_packages(self, pkg_manager):
953         if not self.ks:
954             return
955
956         self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
957         for pkg in self._preinstall_pkgs:
958             pkg_manager.preInstall(pkg)
959
960     def __check_packages(self, pkg_manager):
961         for pkg in self.check_pkgs:
962             pkg_manager.checkPackage(pkg)
963
964     def __attachment_packages(self, pkg_manager):
965         if not self.ks:
966             return
967
968         self._attachment = []
969         for item in kickstart.get_attachment(self.ks):
970             if item.startswith('/'):
971                 fpaths = os.path.join(self._instroot, item.lstrip('/'))
972                 for fpath in glob.glob(fpaths):
973                     self._attachment.append(fpath)
974                 continue
975
976             filelist = pkg_manager.getFilelist(item)
977             if filelist:
978                 # found rpm in rootfs
979                 for pfile in pkg_manager.getFilelist(item):
980                     fpath = os.path.join(self._instroot, pfile.lstrip('/'))
981                     self._attachment.append(fpath)
982                 continue
983
984             # try to retrieve rpm file
985             (url, proxies) = pkg_manager.package_url(item)
986             if not url:
987                 msger.warning("Can't get url from repo for %s" % item)
988                 continue
989             fpath = os.path.join(self.cachedir, os.path.basename(url))
990             if not os.path.exists(fpath):
991                 # download pkgs
992                 try:
993                     fpath = grabber.myurlgrab(url.full, fpath, proxies, None)
994                 except CreatorError:
995                     raise
996
997             tmpdir = self._mkdtemp()
998             misc.extract_rpm(fpath, tmpdir)
999             for (root, dirs, files) in os.walk(tmpdir):
1000                 for fname in files:
1001                     fpath = os.path.join(root, fname)
1002                     self._attachment.append(fpath)
1003
1004     def install(self, repo_urls=None):
1005         """Install packages into the install root.
1006
1007         This function installs the packages listed in the supplied kickstart
1008         into the install root. By default, the packages are installed from the
1009         repository URLs specified in the kickstart.
1010
1011         repo_urls -- a dict which maps a repository name to a repository;
1012                      if supplied, this causes any repository URLs specified in
1013                      the kickstart to be overridden.
1014
1015         """
1016         def checkScriptletError(dirname, suffix):
1017             if os.path.exists(dirname):
1018                 list = os.listdir(dirname)
1019                 for line in list:
1020                     filepath = os.path.join(dirname, line)
1021                     if os.path.isfile(filepath) and 0 < line.find(suffix):
1022                         return True
1023                     else:
1024                         continue
1025              
1026             return False
1027
1028         def showErrorInfo(filepath):
1029             if os.path.isfile(filepath):
1030                 msger.info("The error install package info:")
1031                 for line in open(filepath):
1032                     msger.info(line)
1033             else:
1034                 msger.info("%s is not found." % filepath)
1035
1036         def get_ssl_verify(ssl_verify=None):
1037             if ssl_verify is not None:
1038                 return not ssl_verify.lower().strip() == 'no'
1039             else:
1040                 return not self.ssl_verify.lower().strip() == 'no'
1041
1042         # initialize pkg list to install
1043         if self.ks:
1044             self.__sanity_check()
1045
1046             self._required_pkgs = \
1047                 kickstart.get_packages(self.ks, self._get_required_packages())
1048             self._excluded_pkgs = \
1049                 kickstart.get_excluded(self.ks, self._get_excluded_packages())
1050             self._required_groups = kickstart.get_groups(self.ks)
1051         else:
1052             self._required_pkgs = None
1053             self._excluded_pkgs = None
1054             self._required_groups = None
1055
1056         if not repo_urls:
1057             repo_urls = self.extrarepos
1058
1059         pkg_manager = self.get_pkg_manager()
1060         pkg_manager.setup()
1061
1062         if hasattr(self, 'install_pkgs') and self.install_pkgs:
1063             if 'debuginfo' in self.install_pkgs:
1064                 pkg_manager.install_debuginfo = True
1065
1066         for repo in kickstart.get_repos(self.ks, repo_urls, self.ignore_ksrepo):
1067             (name, baseurl, mirrorlist, inc, exc,
1068              proxy, proxy_username, proxy_password, debuginfo,
1069              source, gpgkey, disable, ssl_verify, nocache,
1070              cost, priority) = repo
1071
1072             ssl_verify = get_ssl_verify(ssl_verify)
1073             yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
1074                         proxy_username, proxy_password, inc, exc, ssl_verify,
1075                         nocache, cost, priority)
1076
1077         if kickstart.exclude_docs(self.ks):
1078             rpm.addMacro("_excludedocs", "1")
1079         rpm.addMacro("_dbpath", "/var/lib/rpm")
1080         rpm.addMacro("__file_context_path", "%{nil}")
1081         if kickstart.inst_langs(self.ks) != None:
1082             rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
1083
1084         try:
1085             self.__preinstall_packages(pkg_manager)
1086             self.__select_packages(pkg_manager)
1087             self.__select_groups(pkg_manager)
1088             self.__deselect_packages(pkg_manager)
1089             self.__localinst_packages(pkg_manager)
1090             self.__check_packages(pkg_manager)
1091
1092             BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
1093             checksize = self._root_fs_avail
1094             if checksize:
1095                 checksize -= BOOT_SAFEGUARD
1096             if self.target_arch:
1097                 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
1098
1099             # If we have multiple partitions, don't check diskspace when rpm run transaction
1100             # because rpm check '/' partition only.
1101             if self.multiple_partitions:
1102                 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_DISKSPACE)
1103             pkg_manager.runInstall(checksize)
1104         except CreatorError, e:
1105             raise
1106         except  KeyboardInterrupt:
1107             raise
1108         else:
1109             self._pkgs_content = pkg_manager.getAllContent()
1110             self._pkgs_license = pkg_manager.getPkgsLicense()
1111             self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
1112             self.__attachment_packages(pkg_manager)
1113         finally:
1114             pkg_manager.close()
1115
1116         if checkScriptletError(self._instroot + "/tmp/.postscript/error/", "_error"):
1117             showErrorInfo(self._instroot + "/tmp/.preload_install_error")
1118             raise CreatorError('scriptlet errors occurred')
1119             
1120         # hook post install
1121         self.postinstall()
1122
1123         # do some clean up to avoid lvm info leakage.  this sucks.
1124         for subdir in ("cache", "backup", "archive"):
1125             lvmdir = self._instroot + "/etc/lvm/" + subdir
1126             try:
1127                 for f in os.listdir(lvmdir):
1128                     os.unlink(lvmdir + "/" + f)
1129             except:
1130                 pass
1131
1132     def postinstall(self):
1133         self.copy_attachment()
1134
1135     def __run_post_scripts(self):
1136         msger.info("Running scripts ...")
1137         if os.path.exists(self._instroot + "/tmp"):
1138             shutil.rmtree(self._instroot + "/tmp")
1139         os.mkdir (self._instroot + "/tmp", 0755)
1140         for s in kickstart.get_post_scripts(self.ks):
1141             (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
1142                                           dir = self._instroot + "/tmp")
1143
1144             s.script = s.script.replace("\r", "")
1145             os.write(fd, s.script)
1146             os.close(fd)
1147             os.chmod(path, 0700)
1148
1149             env = self._get_post_scripts_env(s.inChroot)
1150             if 'PATH' not in env:
1151                 env['PATH'] = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin'
1152
1153             if not s.inChroot:
1154                 preexec = None
1155                 script = path
1156             else:
1157                 preexec = self._chroot
1158                 script = "/tmp/" + os.path.basename(path)
1159
1160             try:
1161                 try:
1162                     p = subprocess.Popen([s.interp, script],
1163                                        preexec_fn = preexec,
1164                                        env = env,
1165                                        stdout = subprocess.PIPE,
1166                                        stderr = subprocess.STDOUT)
1167                     for entry in p.communicate()[0].splitlines():
1168                         msger.info(entry)
1169                 except OSError, (err, msg):
1170                     raise CreatorError("Failed to execute %%post script "
1171                                        "with '%s' : %s" % (s.interp, msg))
1172             finally:
1173                 os.unlink(path)
1174
1175     def __save_repo_keys(self, repodata):
1176         if not repodata:
1177             return None
1178
1179         gpgkeydir = "/etc/pki/rpm-gpg"
1180         fs.makedirs(self._instroot + gpgkeydir)
1181         for repo in repodata:
1182             if repo["repokey"]:
1183                 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" %  repo["name"]
1184                 shutil.copy(repo["repokey"], self._instroot + repokey)
1185
1186     def configure(self, repodata = None):
1187         """Configure the system image according to the kickstart.
1188
1189         This method applies the (e.g. keyboard or network) configuration
1190         specified in the kickstart and executes the kickstart %post scripts.
1191
1192         If necessary, it also prepares the image to be bootable by e.g.
1193         creating an initrd and bootloader configuration.
1194
1195         """
1196         ksh = self.ks.handler
1197
1198         msger.info('Applying configurations ...')
1199         try:
1200             kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1201             kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1202             kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1203             #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1204             kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1205             kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1206             kickstart.UserConfig(self._instroot).apply(ksh.user)
1207             kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1208             kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1209             kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1210             kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1211             kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1212             self.__save_repo_keys(repodata)
1213             kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1214         except:
1215             msger.warning("Failed to apply configuration to image")
1216             raise
1217
1218         self._create_bootconfig()
1219         self.__run_post_scripts()
1220
1221     def launch_shell(self, launch):
1222         """Launch a shell in the install root.
1223
1224         This method is launches a bash shell chroot()ed in the install root;
1225         this can be useful for debugging.
1226
1227         """
1228         if launch:
1229             msger.info("Launching shell. Exit to continue.")
1230             subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1231
1232     def do_genchecksum(self, image_name):
1233         if not self._genchecksum:
1234             return
1235
1236         md5sum = misc.get_md5sum(image_name)
1237         with open(image_name + ".md5sum", "w") as f:
1238             f.write("%s  %s" % (md5sum, os.path.basename(image_name)))
1239         self.outimage.append(image_name+".md5sum")
1240
1241     def remove_exclude_image(self):
1242         for item in self._instloops[:]:
1243             if item['exclude_image']:
1244                 msger.info("Removing %s in image." % item['name'])
1245                 imgfile = os.path.join(self._imgdir, item['name'])
1246                 try:
1247                     os.remove(imgfile)
1248                 except OSError as err:
1249                     if err.errno == errno.ENOENT:
1250                         pass
1251                 self._instloops.remove(item)
1252
1253     def package(self, destdir = "."):
1254         """Prepares the created image for final delivery.
1255
1256         In its simplest form, this method merely copies the install root to the
1257         supplied destination directory; other subclasses may choose to package
1258         the image by e.g. creating a bootable ISO containing the image and
1259         bootloader configuration.
1260
1261         destdir -- the directory into which the final image should be moved;
1262                    this defaults to the current directory.
1263
1264         """
1265         self.remove_exclude_image()
1266
1267         self._stage_final_image()
1268
1269         if not os.path.exists(destdir):
1270             fs.makedirs(destdir)
1271
1272         if self._recording_pkgs:
1273             self._save_recording_pkgs(destdir)
1274
1275         # For image formats with two or multiple image files, it will be
1276         # better to put them under a directory
1277         if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1278             destdir = os.path.join(destdir, "%s-%s" \
1279                                             % (self.name, self.image_format))
1280             msger.debug("creating destination dir: %s" % destdir)
1281             fs.makedirs(destdir)
1282
1283         # Ensure all data is flushed to _outdir
1284         runner.quiet('sync')
1285
1286         misc.check_space_pre_cp(self._outdir, destdir)
1287         for f in os.listdir(self._outdir):
1288             shutil.move(os.path.join(self._outdir, f),
1289                         os.path.join(destdir, f))
1290             self.outimage.append(os.path.join(destdir, f))
1291             self.do_genchecksum(os.path.join(destdir, f))
1292
1293     def print_outimage_info(self):
1294         msg = "The new image can be found here:\n"
1295         self.outimage.sort()
1296         for file in self.outimage:
1297             msg += '  %s\n' % os.path.abspath(file)
1298
1299         msger.info(msg)
1300
1301     def check_depend_tools(self):
1302         for tool in self._dep_checks:
1303             fs.find_binary_path(tool)
1304
1305     def package_output(self, image_format, destdir = ".", package="none"):
1306         if not package or package == "none":
1307             return
1308
1309         destdir = os.path.abspath(os.path.expanduser(destdir))
1310         (pkg, comp) = os.path.splitext(package)
1311         if comp:
1312             comp=comp.lstrip(".")
1313
1314         if pkg == "tar":
1315             if comp:
1316                 dst = "%s/%s-%s.tar.%s" %\
1317                       (destdir, self.name, image_format, comp)
1318             else:
1319                 dst = "%s/%s-%s.tar" %\
1320                       (destdir, self.name, image_format)
1321
1322             msger.info("creating %s" % dst)
1323             tar = tarfile.open(dst, "w:" + comp)
1324
1325             for file in self.outimage:
1326                 msger.info("adding %s to %s" % (file, dst))
1327                 tar.add(file,
1328                         arcname=os.path.join("%s-%s" \
1329                                            % (self.name, image_format),
1330                                               os.path.basename(file)))
1331                 if os.path.isdir(file):
1332                     shutil.rmtree(file, ignore_errors = True)
1333                 else:
1334                     os.remove(file)
1335
1336             tar.close()
1337
1338             '''All the file in outimage has been packaged into tar.* file'''
1339             self.outimage = [dst]
1340
1341     def release_output(self, config, destdir, release):
1342         """ Create release directory and files
1343         """
1344
1345         def _rpath(fn):
1346             """ release path """
1347             return os.path.join(destdir, fn)
1348
1349         outimages = self.outimage
1350
1351         # new ks
1352         new_kspath = _rpath(self.name+'.ks')
1353         with open(config) as fr:
1354             with open(new_kspath, "w") as wf:
1355                 # When building a release we want to make sure the .ks
1356                 # file generates the same build even when --release not used.
1357                 wf.write(fr.read().replace("@BUILD_ID@", release))
1358         outimages.append(new_kspath)
1359
1360         # save log file, logfile is only available in creator attrs
1361         if hasattr(self, 'releaselog') and self.releaselog:
1362             outimages.append(self.logfile)
1363
1364         # rename iso and usbimg
1365         for f in os.listdir(destdir):
1366             if f.endswith(".iso"):
1367                 newf = f[:-4] + '.img'
1368             elif f.endswith(".usbimg"):
1369                 newf = f[:-7] + '.img'
1370             else:
1371                 continue
1372             os.rename(_rpath(f), _rpath(newf))
1373             outimages.append(_rpath(newf))
1374
1375         # generate MD5SUMS SHA1SUMS SHA256SUMS
1376         def generate_hashsum(hash_name, hash_method):
1377             with open(_rpath(hash_name), "w") as wf:
1378                 for f in os.listdir(destdir):
1379                     if f.endswith('SUMS'):
1380                         continue
1381
1382                     if os.path.isdir(os.path.join(destdir, f)):
1383                         continue
1384
1385                     hash_value = hash_method(_rpath(f))
1386                     # There needs to be two spaces between the sum and
1387                     # filepath to match the syntax with md5sum,sha1sum,
1388                     # sha256sum. This way also *sum -c *SUMS can be used.
1389                     wf.write("%s  %s\n" % (hash_value, f))
1390
1391             outimages.append("%s/%s" % (destdir, hash_name))
1392
1393         hash_dict = {
1394                      'MD5SUMS'    : misc.get_md5sum,
1395                      'SHA1SUMS'   : misc.get_sha1sum,
1396                      'SHA256SUMS' : misc.get_sha256sum
1397                     }
1398
1399         for k, v in hash_dict.items():
1400             generate_hashsum(k, v)
1401
1402         # Filter out the nonexist file
1403         for fp in outimages[:]:
1404             if not os.path.exists("%s" % fp):
1405                 outimages.remove(fp)
1406
1407     def copy_kernel(self):
1408         """ Copy kernel files to the outimage directory.
1409         NOTE: This needs to be called before unmounting the instroot.
1410         """
1411
1412         if not self._need_copy_kernel:
1413             return
1414
1415         if not os.path.exists(self.destdir):
1416             os.makedirs(self.destdir)
1417
1418         for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1419             kernelfilename = "%s/%s-%s" % (self.destdir,
1420                                            self.name,
1421                                            os.path.basename(kernel))
1422             msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1423                                                       kernelfilename))
1424             shutil.copy(kernel, kernelfilename)
1425             self.outimage.append(kernelfilename)
1426
1427     def copy_attachment(self):
1428         """ Subclass implement it to handle attachment files
1429         NOTE: This needs to be called before unmounting the instroot.
1430         """
1431         pass
1432
1433     def get_pkg_manager(self):
1434         return self.pkgmgr(target_arch = self.target_arch,
1435                            instroot = self._instroot,
1436                            cachedir = self.cachedir,
1437                            strict_mode = self.strict_mode)
1438
1439     def create_manifest(self):
1440         def get_pack_suffix():
1441             return '.' + self.pack_to.split('.', 1)[1]
1442
1443         if not os.path.exists(self.destdir):
1444             os.makedirs(self.destdir)
1445
1446         now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
1447         manifest_dict = {'version': VERSION,
1448                          'created': now}
1449         if self.img_format:
1450             manifest_dict.update({'format': self.img_format})
1451
1452         if hasattr(self, 'logfile') and self.logfile:
1453             manifest_dict.update({'log_file': self.logfile})
1454
1455         if self.image_files:
1456             if self.pack_to:
1457                 self.image_files.update({'pack': get_pack_suffix()})
1458             manifest_dict.update({self.img_format: self.image_files})
1459
1460         msger.info('Creating manifest file...')
1461         manifest_file_path = os.path.join(self.destdir, 'manifest.json')
1462         with open(manifest_file_path, 'w') as fest_file:
1463             json.dump(manifest_dict, fest_file, indent=4)
1464         self.outimage.append(manifest_file_path)