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