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