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