hide loopdev presentation to avoid popup message in Ubuntu 12.04
[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         # prevent popup dialog in Ubuntu(s)
671         misc.hide_loopdev_presentation()
672
673         fs.makedirs(self._instroot)
674         fs.makedirs(self._outdir)
675
676         self._mount_instroot(base_on)
677
678         for d in ("/dev/pts",
679                   "/etc",
680                   "/boot",
681                   "/var/log",
682                   "/sys",
683                   "/proc",
684                   "/usr/bin"):
685             fs.makedirs(self._instroot + d)
686
687         if self.target_arch and self.target_arch.startswith("arm"):
688             self.qemu_emulator = misc.setup_qemu_emulator(self._instroot,
689                                                           self.target_arch)
690
691         self.get_cachedir(cachedir)
692
693         # bind mount system directories into _instroot
694         for (f, dest) in [("/sys", None),
695                           ("/proc", None),
696                           ("/proc/sys/fs/binfmt_misc", None),
697                           ("/dev/pts", None)]:
698             self.__bindmounts.append(fs.BindChrootMount(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 = rpmmisc.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         for repo in kickstart.get_repos(self.ks, repo_urls):
906             (name, baseurl, mirrorlist, inc, exc,
907              proxy, proxy_username, proxy_password, debuginfo,
908              source, gpgkey, disable, ssl_verify, cost, priority) = repo
909
910             yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
911                         proxy_username, proxy_password, inc, exc, ssl_verify,
912                         cost, priority)
913
914         if kickstart.exclude_docs(self.ks):
915             rpm.addMacro("_excludedocs", "1")
916         rpm.addMacro("_dbpath", "/var/lib/rpm")
917         rpm.addMacro("__file_context_path", "%{nil}")
918         if kickstart.inst_langs(self.ks) != None:
919             rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
920
921         try:
922             try:
923                 self.__preinstall_packages(pkg_manager)
924                 self.__select_packages(pkg_manager)
925                 self.__select_groups(pkg_manager)
926                 self.__deselect_packages(pkg_manager)
927                 self.__localinst_packages(pkg_manager)
928
929                 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
930                 checksize = self._root_fs_avail
931                 if checksize:
932                     checksize -= BOOT_SAFEGUARD
933                 if self.target_arch:
934                     pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
935                 pkg_manager.runInstall(checksize)
936             except CreatorError, e:
937                 raise
938         finally:
939             self._pkgs_content = pkg_manager.getAllContent()
940             self._pkgs_license = pkg_manager.getPkgsLicense()
941             self.__attachment_packages(pkg_manager)
942
943             pkg_manager.close()
944
945         # hook post install
946         self.postinstall()
947
948         # do some clean up to avoid lvm info leakage.  this sucks.
949         for subdir in ("cache", "backup", "archive"):
950             lvmdir = self._instroot + "/etc/lvm/" + subdir
951             try:
952                 for f in os.listdir(lvmdir):
953                     os.unlink(lvmdir + "/" + f)
954             except:
955                 pass
956
957     def postinstall(self):
958         self.copy_attachment()
959
960     def __run_post_scripts(self):
961         msger.info("Running scripts ...")
962         if os.path.exists(self._instroot + "/tmp"):
963             shutil.rmtree(self._instroot + "/tmp")
964         os.mkdir (self._instroot + "/tmp", 0755)
965         for s in kickstart.get_post_scripts(self.ks):
966             (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
967                                           dir = self._instroot + "/tmp")
968
969             s.script = s.script.replace("\r", "")
970             os.write(fd, s.script)
971             os.close(fd)
972             os.chmod(path, 0700)
973
974             env = self._get_post_scripts_env(s.inChroot)
975
976             if not s.inChroot:
977                 env["INSTALL_ROOT"] = self._instroot
978                 env["IMG_NAME"] = self._name
979                 preexec = None
980                 script = path
981             else:
982                 preexec = self._chroot
983                 script = "/tmp/" + os.path.basename(path)
984
985             try:
986                 try:
987                     subprocess.call([s.interp, script],
988                                     preexec_fn = preexec,
989                                     env = env,
990                                     stdout = sys.stdout,
991                                     stderr = sys.stderr)
992                 except OSError, (err, msg):
993                     raise CreatorError("Failed to execute %%post script "
994                                        "with '%s' : %s" % (s.interp, msg))
995             finally:
996                 os.unlink(path)
997
998     def __save_repo_keys(self, repodata):
999         if not repodata:
1000             return None
1001
1002         gpgkeydir = "/etc/pki/rpm-gpg"
1003         fs.makedirs(self._instroot + gpgkeydir)
1004         for repo in repodata:
1005             if repo["repokey"]:
1006                 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" %  repo["name"]
1007                 shutil.copy(repo["repokey"], self._instroot + repokey)
1008
1009     def configure(self, repodata = None):
1010         """Configure the system image according to the kickstart.
1011
1012         This method applies the (e.g. keyboard or network) configuration
1013         specified in the kickstart and executes the kickstart %post scripts.
1014
1015         If necessary, it also prepares the image to be bootable by e.g.
1016         creating an initrd and bootloader configuration.
1017
1018         """
1019         ksh = self.ks.handler
1020
1021         msger.info('Applying configurations ...')
1022         try:
1023             kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1024             kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1025             kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1026             #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1027             kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1028             kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1029             kickstart.UserConfig(self._instroot).apply(ksh.user)
1030             kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1031             kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1032             kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1033             kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1034             kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1035             self.__save_repo_keys(repodata)
1036             kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata)
1037         except:
1038             msger.warning("Failed to apply configuration to image")
1039             raise
1040
1041         self._create_bootconfig()
1042         self.__run_post_scripts()
1043
1044     def launch_shell(self, launch):
1045         """Launch a shell in the install root.
1046
1047         This method is launches a bash shell chroot()ed in the install root;
1048         this can be useful for debugging.
1049
1050         """
1051         if launch:
1052             msger.info("Launching shell. Exit to continue.")
1053             subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1054
1055     def do_genchecksum(self, image_name):
1056         if not self._genchecksum:
1057             return
1058
1059         md5sum = misc.get_md5sum(image_name)
1060         with open(image_name + ".md5sum", "w") as f:
1061             f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1062         self.outimage.append(image_name+".md5sum")
1063
1064     def package(self, destdir = "."):
1065         """Prepares the created image for final delivery.
1066
1067         In its simplest form, this method merely copies the install root to the
1068         supplied destination directory; other subclasses may choose to package
1069         the image by e.g. creating a bootable ISO containing the image and
1070         bootloader configuration.
1071
1072         destdir -- the directory into which the final image should be moved;
1073                    this defaults to the current directory.
1074
1075         """
1076         self._stage_final_image()
1077
1078         if not os.path.exists(destdir):
1079             fs.makedirs(destdir)
1080
1081         if self._recording_pkgs:
1082             self._save_recording_pkgs(destdir)
1083
1084         # For image formats with two or multiple image files, it will be
1085         # better to put them under a directory
1086         if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1087             destdir = os.path.join(destdir, "%s-%s" \
1088                                             % (self.name, self.image_format))
1089             msger.debug("creating destination dir: %s" % destdir)
1090             fs.makedirs(destdir)
1091
1092         # Ensure all data is flushed to _outdir
1093         runner.quiet('sync')
1094
1095         misc.check_space_pre_cp(self._outdir, destdir)
1096         for f in os.listdir(self._outdir):
1097             shutil.move(os.path.join(self._outdir, f),
1098                         os.path.join(destdir, f))
1099             self.outimage.append(os.path.join(destdir, f))
1100             self.do_genchecksum(os.path.join(destdir, f))
1101
1102     def print_outimage_info(self):
1103         msg = "The new image can be found here:\n"
1104         self.outimage.sort()
1105         for file in self.outimage:
1106             msg += '  %s\n' % os.path.abspath(file)
1107
1108         msger.info(msg)
1109
1110     def check_depend_tools(self):
1111         for tool in self._dep_checks:
1112             fs.find_binary_path(tool)
1113
1114     def package_output(self, image_format, destdir = ".", package="none"):
1115         if not package or package == "none":
1116             return
1117
1118         destdir = os.path.abspath(os.path.expanduser(destdir))
1119         (pkg, comp) = os.path.splitext(package)
1120         if comp:
1121             comp=comp.lstrip(".")
1122
1123         if pkg == "tar":
1124             if comp:
1125                 dst = "%s/%s-%s.tar.%s" % (destdir, self.name, image_format, comp)
1126             else:
1127                 dst = "%s/%s-%s.tar" % (destdir, self.name, image_format)
1128             msger.info("creating %s" % dst)
1129             tar = tarfile.open(dst, "w:" + comp)
1130
1131             for file in self.outimage:
1132                 msger.info("adding %s to %s" % (file, dst))
1133                 tar.add(file,
1134                         arcname=os.path.join("%s-%s" \
1135                                            % (self.name, image_format),
1136                                               os.path.basename(file)))
1137                 if os.path.isdir(file):
1138                     shutil.rmtree(file, ignore_errors = True)
1139                 else:
1140                     os.remove(file)
1141
1142             tar.close()
1143
1144             '''All the file in outimage has been packaged into tar.* file'''
1145             self.outimage = [dst]
1146
1147     def release_output(self, config, destdir, release):
1148         """ Create release directory and files
1149         """
1150
1151         def _rpath(fn):
1152             """ release path """
1153             return os.path.join(destdir, fn)
1154
1155         outimages = self.outimage
1156
1157         # new ks
1158         new_kspath = _rpath(self.name+'.ks')
1159         with open(config) as fr:
1160             with open(new_kspath, "w") as wf:
1161                 # When building a release we want to make sure the .ks
1162                 # file generates the same build even when --release= is not used.
1163                 wf.write(fr.read().replace("@BUILD_ID@", release))
1164         outimages.append(new_kspath)
1165
1166         # save log file, logfile is only available in creator attrs
1167         if hasattr(self, 'logfile') and not self.logfile:
1168             log_path = _rpath(self.name + ".log")
1169             # touch the log file, else outimages will filter it out
1170             with open(log_path, 'w') as wf:
1171                 wf.write('')
1172             msger.set_logfile(log_path)
1173             outimages.append(_rpath(self.name + ".log"))
1174
1175         # rename iso and usbimg
1176         for f in os.listdir(destdir):
1177             if f.endswith(".iso"):
1178                 newf = f[:-4] + '.img'
1179             elif f.endswith(".usbimg"):
1180                 newf = f[:-7] + '.img'
1181             else:
1182                 continue
1183             os.rename(_rpath(f), _rpath(newf))
1184             outimages.append(_rpath(newf))
1185
1186         # generate MANIFEST
1187         with open(_rpath("MANIFEST"), "w") as wf:
1188             for f in os.listdir(destdir):
1189                 if f == "MANIFEST":
1190                     continue
1191
1192                 if os.path.isdir(os.path.join(destdir, f)):
1193                     continue
1194
1195                 md5sum = misc.get_md5sum(_rpath(f))
1196                 # There needs to be two spaces between the sum and
1197                 # filepath to match the syntax with md5sum. 
1198                 # This way also md5sum -c MANIFEST can be used by users
1199                 wf.write("%s *%s\n" % (md5sum, f))
1200
1201         outimages.append("%s/MANIFEST" % destdir)
1202
1203         # Filter out the nonexist file
1204         for fp in outimages[:]:
1205             if not os.path.exists("%s" % fp):
1206                 outimages.remove(fp)
1207
1208     def copy_kernel(self):
1209         """ Copy kernel files to the outimage directory.
1210         
1211         NOTE: This needs to be called before unmounting the instroot.
1212         """
1213
1214         if not self._need_copy_kernel:
1215             return
1216
1217         if not os.path.exists(self.destdir):
1218             os.makedirs(self.destdir)
1219
1220         for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1221             kernelfilename = "%s/%s-%s" % (self.destdir, self.name, os.path.basename(kernel))
1222             msger.info('copy kernel file %s as %s' % (os.path.basename(kernel), kernelfilename))
1223             shutil.copy(kernel, kernelfilename)
1224             self.outimage.append(kernelfilename)
1225
1226
1227     def copy_attachment(self):
1228         """ Subclass implement it to handle attachment files
1229
1230         NOTE: This needs to be called before unmounting the instroot.
1231         """
1232         pass
1233
1234     def get_pkg_manager(self):
1235         return self.pkgmgr(target_arch = self.target_arch, instroot = self._instroot, cachedir = self.cachedir)