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