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