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