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