Create useable fstab for kfreebsd systems
[forks/vmdebootstrap.git] / vmextract.py
1 #! /usr/bin/python
2 # -*- coding: utf-8 -*-
3 #
4 # Copyright 2015 Neil Williams <codehelp@debian.org>
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 import os
20 import sys
21 import cliapp
22 import guestfs
23 import tarfile
24 import logging
25
26 __version__ = '0.1'
27 __desc__ = "Helper to mount an image read-only and extract files" + \
28 " or directories."
29
30
31 # pylint: disable=missing-docstring
32
33
34 class VmExtract(cliapp.Application): # pylint: disable=too-many-public-methods
35 """
36 Support for extracting useful content from VM images.
37 For example, to assist in validation.
38 """
39
40 def __init__(
41 self, progname=None,
42 version=__version__, description=__desc__, epilog=None):
43 super(VmExtract, self).__init__(
44 progname, version,
45 description, epilog)
46 self.guest_os = None
47 self.mps = []
48
49 def add_settings(self):
50 self.settings.boolean(
51 ['verbose'], 'report what is going on')
52 self.settings.string(
53 ['image'], 'image to read', metavar='FILE')
54 self.settings.string(
55 ['directory'], 'directory to extract as a tarball.')
56 self.settings.string_list(
57 ['path'], 'path to the filename to extract - can repeat.')
58 self.settings.boolean(
59 ['boot'], 'mount the boot partition as well as root')
60 self.settings.string(
61 ['filename'],
62 'name of tarball containing the extracted directory',
63 default='vmextract.tgz',
64 metavar='FILE')
65
66 # pylint: disable=too-many-branches,too-many-statements
67 def process_args(self, args):
68
69 if not self.settings['image']:
70 raise cliapp.AppException(
71 'You must give an image to read')
72 if not self.settings['directory'] and not self.settings['path']:
73 raise cliapp.AppException(
74 'You must provide either a filename or directory '
75 'to extract')
76
77 try:
78 self.prepare()
79 self.mount_root()
80 if self.settings['boot']:
81 self.mount_boot()
82 if self.settings['directory']:
83 self.extract_directory()
84 elif self.settings['path']:
85 for path in self.settings['path']:
86 self.download(path)
87 except BaseException as exc:
88 self.message('EEEK! Something bad happened... %s' % exc)
89 sys.exit(1)
90
91 def message(self, msg):
92 logging.info(msg)
93 if self.settings['verbose']:
94 print msg
95
96 def prepare(self):
97 """
98 Initialise guestfs
99 """
100 self.message("Preparing %s" % self.settings['image'])
101 self.guest_os = guestfs.GuestFS(python_return_dict=True)
102 self.guest_os.add_drive_opts(
103 self.settings['image'],
104 format="raw",
105 readonly=1)
106 # ensure launch is only called once per run
107 self.guest_os.launch()
108 drives = self.guest_os.inspect_os()
109 self.mps = self.guest_os.inspect_get_mountpoints(drives[0])
110
111 def download(self, path):
112 """
113 Copy a single file out of the image
114 If a filename is not specified, use the basename of the original.
115 """
116 filename = os.path.basename(path)
117 self.message(
118 "Extracting %s as %s" % (path, filename))
119 self.guest_os.download(path, filename)
120 if not os.path.exists(filename):
121 return RuntimeError("Download failed")
122
123 def mount_root(self):
124 """
125 Mounts the root partition to /
126 """
127 root = [part for part in self.mps if part == '/'][0]
128 if not root:
129 raise RuntimeError("Unable to identify root partition")
130 self.guest_os.mount_ro(self.mps[root], '/')
131
132 def mount_boot(self):
133 """
134 Mounts the /boot partition to a new /boot mountpoint
135 """
136 boot = [part for part in self.mps if part == '/boot'][0]
137 if not boot:
138 raise RuntimeError("Unable to identify boot partition")
139 if not self.guest_os.is_dir('/boot'):
140 self.guest_os.mkmountpoint('/boot')
141 self.guest_os.mount_ro(self.mps[boot], '/boot')
142
143 def extract_directory(self):
144 """
145 Create a tarball of a complete directory existing in the image.
146 """
147 if not self.settings['filename']:
148 self.settings['filename'] = 'vmextract.tgz'
149 self.guest_os.tar_out(
150 self.settings['directory'],
151 self.settings['filename'], compress='gzip')
152 if not tarfile.is_tarfile(self.settings['filename']):
153 raise RuntimeError("Extraction failed")
154
155
156 def main():
157 VmExtract(version=__version__).run()
158 return 0
159
160
161 if __name__ == '__main__':
162 main()