blob: 83ef29a4859a545321a0be8af9f1004e229fffe5 [file] [log] [blame]
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +08001#!/usr/bin/python -Bu
2#
3# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Factory toolkit installer.
8
9The factory toolkit is a self-extracting shellball containing factory test
10related files and this installer. This installer is invoked when the toolkit
11is deployed and is responsible for installing files.
12"""
13
14
15import argparse
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +080016from contextlib import contextmanager
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080017import os
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080018import sys
Jon Salz4f3ade52014-02-20 17:55:09 +080019import tempfile
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080020
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +080021import factory_common # pylint: disable=W0611
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080022from cros.factory.test import factory
Vic Yang196d5242014-08-05 13:51:35 +080023from cros.factory.test import utils
Jon Salz25590302014-07-11 16:07:20 +080024from cros.factory.tools import install_symlinks
Rong Chang6d65fad2014-08-22 16:00:12 +080025from cros.factory.utils import file_utils
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +080026from cros.factory.utils.process_utils import Spawn
Hung-Te Lincdc2a042014-08-27 13:05:16 +080027from cros.factory.utils.sys_utils import MountPartition
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080028
29
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +080030INSTALLER_PATH = 'usr/local/factory/py/toolkit/installer.py'
Rong Chang6d65fad2014-08-22 16:00:12 +080031MAKESELF_SHELL = '/bin/sh'
32TOOLKIT_NAME = 'install_factory_toolkit.run'
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +080033
Jon Salz4f3ade52014-02-20 17:55:09 +080034# Short and sweet help header for the executable generated by makeself.
35HELP_HEADER = """
36Installs the factory toolkit, transforming a test image into a factory test
37image. You can:
38
39- Install the factory toolkit on a CrOS device that is running a test
40 image. To do this, copy install_factory_toolkit.run to the device and
41 run it. The factory tests will then come up on the next boot.
42
43 rsync -a install_factory_toolkit.run crosdevice:/tmp
44 ssh crosdevice '/tmp/install_factory_toolkit.run && sync && reboot'
45
46- Modify a test image, turning it into a factory test image. When you
47 use the image on a device, the factory tests will come up.
48
49 install_factory_toolkit.run chromiumos_test_image.bin
50"""
51
52HELP_HEADER_ADVANCED = """
53- (advanced) Modify a mounted stateful partition, turning it into a factory
54 test image. This is equivalent to the previous command:
55
56 mount_partition -rw chromiumos_test_image.bin 1 /mnt/stateful
57 install_factory_toolkit.run /mnt/stateful
58 umount /mnt/stateful
59
60- (advanced) Unpack the factory toolkit, modify a file, and then repack it.
61
62 # Unpack but don't actually install
63 install_factory_toolkit.run --target /tmp/toolkit --noexec
64 # Edit some files in /tmp/toolkit
65 emacs /tmp/toolkit/whatever
66 # Repack
67 install_factory_toolkit.run -- --repack /tmp/toolkit \\
68 --pack-into /path/to/new/install_factory_toolkit.run
69"""
70
71# The makeself-generated header comes next. This is a little confusing,
72# so explain.
73HELP_HEADER_MAKESELF = """
74For complete usage information and advanced operations, run
75"install_factory_toolkit.run -- --help" (note the extra "--").
76
77Following is the help message from makeself, which was used to create
78this self-extracting archive.
79
80-----
81"""
82
Vic Yang196d5242014-08-05 13:51:35 +080083# The method to determine whether running on Chrome OS device or not.
84# Override this for unit testing.
85_in_cros_device = utils.in_cros_device
86
Jon Salz4f3ade52014-02-20 17:55:09 +080087
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080088class FactoryToolkitInstaller():
89 """Factory toolkit installer.
90
91 Args:
92 src: Source path containing usr/ and var/.
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +080093 dest: Installation destination path. Set this to the mount point of the
94 stateful partition if patching a test image.
95 no_enable: True to not install the tag file.
96 system_root: The path to the root of the file system. This must be left
97 as its default value except for unit testing.
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +080098 """
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +080099
Jon Salzb7e44262014-05-07 15:53:37 +0800100 # Whether to sudo when rsyncing; set to False for testing.
101 _sudo = True
102
Peter Ammon948b7172014-07-15 12:43:06 -0700103 def __init__(self, src, dest, no_enable, enable_presenter,
Vic Yang7039f422014-07-07 15:38:13 -0700104 enable_device, system_root='/'):
Jon Salz4f3ade52014-02-20 17:55:09 +0800105 self._src = src
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800106 self._system_root = system_root
107 if dest == self._system_root:
108 self._usr_local_dest = os.path.join(dest, 'usr', 'local')
109 self._var_dest = os.path.join(dest, 'var')
Jon Salz4f3ade52014-02-20 17:55:09 +0800110
111 # Make sure we're on a CrOS device.
Vic Yang196d5242014-08-05 13:51:35 +0800112 if not _in_cros_device():
Jon Salz4f3ade52014-02-20 17:55:09 +0800113 sys.stderr.write(
Vic Yang196d5242014-08-05 13:51:35 +0800114 "ERROR: You're not on a CrOS device (for more details, please\n"
115 "check utils.py:in_cros_device), so you must specify a test\n"
116 "image or a mounted stateful partition on which to install the\n"
117 "factory toolkit. Please run\n"
Jon Salz4f3ade52014-02-20 17:55:09 +0800118 "\n"
119 " install_factory_toolkit.run -- --help\n"
120 "\n"
121 "for help.\n")
122 sys.exit(1)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800123 if os.getuid() != 0:
Jon Salz4f3ade52014-02-20 17:55:09 +0800124 raise Exception('You must be root to install the factory toolkit on a '
125 'CrOS device.')
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800126 else:
127 self._usr_local_dest = os.path.join(dest, 'dev_image')
128 self._var_dest = os.path.join(dest, 'var_overlay')
129 if (not os.path.exists(self._usr_local_dest) or
130 not os.path.exists(self._var_dest)):
131 raise Exception(
132 'The destination path %s is not a stateful partition!' % dest)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800133
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800134 self._dest = dest
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800135 self._usr_local_src = os.path.join(src, 'usr', 'local')
136 self._var_src = os.path.join(src, 'var')
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800137 self._no_enable = no_enable
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800138 self._tag_file = os.path.join(self._usr_local_dest, 'factory', 'enabled')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800139
Peter Ammon948b7172014-07-15 12:43:06 -0700140 self._enable_presenter = enable_presenter
141 self._presenter_tag_file = os.path.join(self._usr_local_dest, 'factory',
142 'init', 'run_goofy_presenter')
Vic Yang7039f422014-07-07 15:38:13 -0700143
144 self._enable_device = enable_device
Ricky Liangd7716912014-07-10 11:52:24 +0800145 self._device_tag_file = os.path.join(self._usr_local_dest, 'factory',
146 'init', 'run_goofy_device')
Vic Yang7039f422014-07-07 15:38:13 -0700147
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800148 if (not os.path.exists(self._usr_local_src) or
149 not os.path.exists(self._var_src)):
150 raise Exception(
151 'This installer must be run from within the factory toolkit!')
152
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800153 def WarningMessage(self, target_test_image=None):
Jon Salz4f3ade52014-02-20 17:55:09 +0800154 with open(os.path.join(self._src, 'VERSION')) as f:
155 ret = f.read()
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800156 if target_test_image:
Jon Salz4f3ade52014-02-20 17:55:09 +0800157 ret += (
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800158 '\n'
159 '\n'
Jon Salz4f3ade52014-02-20 17:55:09 +0800160 '*** You are about to patch the factory toolkit into:\n'
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800161 '*** %s\n'
162 '***' % target_test_image)
163 else:
Jon Salz4f3ade52014-02-20 17:55:09 +0800164 ret += (
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800165 '\n'
166 '\n'
Jon Salz4f3ade52014-02-20 17:55:09 +0800167 '*** You are about to install the factory toolkit to:\n'
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800168 '*** %s\n'
169 '***' % self._dest)
170 if self._dest == self._system_root:
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800171 if self._no_enable:
172 ret += ('\n'
173 '*** Factory tests will be disabled after this process is done, but\n'
Jon Salz4f3ade52014-02-20 17:55:09 +0800174 '*** you can enable them by creating the factory enabled tag:\n'
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800175 '*** %s\n'
176 '***' % self._tag_file)
177 else:
178 ret += ('\n'
179 '*** After this process is done, your device will start factory\n'
180 '*** tests on the next reboot.\n'
181 '***\n'
Jon Salz4f3ade52014-02-20 17:55:09 +0800182 '*** Factory tests can be disabled by deleting the factory enabled\n'
183 '*** tag:\n'
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800184 '*** %s\n'
185 '***' % self._tag_file)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800186 return ret
187
Vic Yang7039f422014-07-07 15:38:13 -0700188 def _SetTagFile(self, name, path, enabled):
189 """Install or remove a tag file."""
190 if enabled:
191 print '*** Installing %s enabled tag...' % name
192 Spawn(['touch', path], sudo=True, log=True, check_call=True)
Ricky Liang8c88a122014-07-11 21:21:22 +0800193 Spawn(['chmod', 'go+r', path], sudo=True, log=True, check_call=True)
Vic Yang7039f422014-07-07 15:38:13 -0700194 else:
195 print '*** Removing %s enabled tag...' % name
196 Spawn(['rm', '-f', path], sudo=True, log=True, check_call=True)
197
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800198 def Install(self):
199 print '*** Installing factory toolkit...'
Jon Salzb7e44262014-05-07 15:53:37 +0800200 for src, dest in ((self._usr_local_src, self._usr_local_dest),
201 (self._var_src, self._var_dest)):
202 # Change the source directory to root, and add group/world read
203 # permissions. This is necessary because when the toolkit was
204 # unpacked, the user may not have been root so the permessions
205 # may be hosed. This is skipped for testing.
Peter Ammon5ac58422014-06-09 14:45:50 -0700206 # --force is necessary to allow goofy directory from prior
207 # toolkit installations to be overwritten by the goofy symlink.
Ricky Liang5e95be22014-07-09 12:52:07 +0800208 try:
209 if self._sudo:
210 Spawn(['chown', '-R', 'root', src],
211 sudo=True, log=True, check_call=True)
212 Spawn(['chmod', '-R', 'go+rX', src],
213 sudo=True, log=True, check_call=True)
214 print '*** %s -> %s' % (src, dest)
215 Spawn(['rsync', '-a', '--force', src + '/', dest],
216 sudo=self._sudo, log=True, check_output=True)
217 finally:
218 # Need to change the source directory back to the original user, or the
219 # script in makeself will fail to remove the temporary source directory.
220 if self._sudo:
221 myuser = os.environ.get('USER')
222 Spawn(['chown', '-R', myuser, src],
223 sudo=True, log=True, check_call=True)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800224
Jon Salz25590302014-07-11 16:07:20 +0800225 print '*** Installing symlinks...'
226 install_symlinks.InstallSymlinks(
227 '../factory/bin',
228 os.path.join(self._usr_local_dest, 'bin'),
229 install_symlinks.MODE_FULL,
230 sudo=self._sudo)
231
232 print '*** Removing factory-mini...'
233 Spawn(['rm', '-rf', os.path.join(self._usr_local_dest, 'factory-mini')],
234 sudo=self._sudo, log=True, check_call=True)
235
Vic Yang7039f422014-07-07 15:38:13 -0700236 self._SetTagFile('factory', self._tag_file, not self._no_enable)
Peter Ammon948b7172014-07-15 12:43:06 -0700237 self._SetTagFile('presenter', self._presenter_tag_file,
238 self._enable_presenter)
Vic Yang7039f422014-07-07 15:38:13 -0700239 self._SetTagFile('device', self._device_tag_file, self._enable_device)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800240
241 print '*** Installation completed.'
242
243
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800244@contextmanager
245def DummyContext(arg):
246 """A context manager that simply yields its argument."""
247 yield arg
248
249
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800250def PrintBuildInfo(src_root):
251 """Print build information."""
252 info_file = os.path.join(src_root, 'REPO_STATUS')
253 if not os.path.exists(info_file):
254 raise OSError('Build info file not found!')
255 with open(info_file, 'r') as f:
256 print f.read()
257
258
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800259def PackFactoryToolkit(src_root, output_path):
260 """Packs the files containing this script into a factory toolkit."""
261 with open(os.path.join(src_root, 'VERSION'), 'r') as f:
262 version = f.read().strip()
Jon Salz4f3ade52014-02-20 17:55:09 +0800263 with tempfile.NamedTemporaryFile() as help_header:
264 help_header.write(version + "\n" + HELP_HEADER + HELP_HEADER_MAKESELF)
265 help_header.flush()
266 Spawn([os.path.join(src_root, 'makeself.sh'), '--bzip2', '--nox11',
267 '--help-header', help_header.name,
268 src_root, output_path, version, INSTALLER_PATH, '--in-exe'],
269 check_call=True, log=True)
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800270 print ('\n'
271 ' Factory toolkit generated at %s.\n'
272 '\n'
273 ' To install factory toolkit on a live device running a test image,\n'
274 ' copy this to the device and execute it as root.\n'
275 '\n'
276 ' Alternatively, the factory toolkit can be used to patch a test\n'
277 ' image. For more information, run:\n'
Jon Salz4f3ade52014-02-20 17:55:09 +0800278 ' %s --help\n'
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800279 '\n' % (output_path, output_path))
280
281
Rong Chang6d65fad2014-08-22 16:00:12 +0800282def InitUmpire(exe_path, src_root, target_board):
283 """Inits Umpire server environment."""
284 if exe_path is None:
285 parent_cmdline = open('/proc/%s/cmdline' % os.getppid(),
286 'r').read().rstrip('\0').split('\0')
287
288 if parent_cmdline > 1 and parent_cmdline[0] == MAKESELF_SHELL:
289 # Get parent script name from parent process.
290 exe_path = parent_cmdline[1]
291 else:
292 # Set to default.
293 exe_path = TOOLKIT_NAME
294
295 if not exe_path.startswith('/'):
296 exe_path = os.path.join(os.environ.get('OLDPWD'), exe_path)
297
298 with file_utils.TempDirectory() as nano_bundle:
299 bundle_toolkit_dir = os.path.join(nano_bundle, 'factory_toolkit')
300 os.mkdir(bundle_toolkit_dir)
301 os.symlink(exe_path, os.path.join(bundle_toolkit_dir,
302 os.path.basename(exe_path)))
303 umpire_bin = os.path.join(src_root, 'usr', 'local', 'factory', 'bin',
304 'umpire')
305 Spawn([umpire_bin, 'init', '--board', target_board, nano_bundle],
306 check_call=True, log=True)
307 print ('\n'
308 ' Umpire initialized successfully. Upstart service is running:\n'
309 ' umpire BOARD=%(board)s.\n'
310 ' For more information, please check umpire command line:\n'
311 '\n'
312 ' umpire-%(board)s --help (if your id is in umpire group)\n'
313 ' or sudo umpire-%(board)s --help\n'
314 '\n' % {'board': target_board})
315
316
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800317def main():
Jon Salz4f3ade52014-02-20 17:55:09 +0800318 import logging
319 logging.basicConfig(level=logging.INFO)
320
321 # In order to determine which usage message to show, first determine
322 # whether we're in the self-extracting archive. Do this first
323 # because we need it to even parse the arguments.
324 if '--in-exe' in sys.argv:
325 sys.argv = [x for x in sys.argv if x != '--in-exe']
326 in_archive = True
327 else:
328 in_archive = False
329
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800330 parser = argparse.ArgumentParser(
Jon Salz4f3ade52014-02-20 17:55:09 +0800331 description=HELP_HEADER + HELP_HEADER_ADVANCED,
332 usage=('install_factory_toolkit.run -- [options]' if in_archive
333 else None),
334 formatter_class=argparse.RawDescriptionHelpFormatter)
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800335 parser.add_argument('dest', nargs='?', default='/',
336 help='A test image or the mount point of the stateful partition. '
337 "If omitted, install to live system, i.e. '/'.")
Vic (Chun-Ju) Yang7cc3e672014-01-20 14:06:39 +0800338 parser.add_argument('--no-enable', '-n', action='store_true',
339 help="Don't enable factory tests after installing")
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800340 parser.add_argument('--yes', '-y', action='store_true',
341 help="Don't ask for confirmation")
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800342 parser.add_argument('--build-info', action='store_true',
343 help="Print build information and exit")
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800344 parser.add_argument('--pack-into', metavar='NEW_TOOLKIT',
345 help="Pack the files into a new factory toolkit")
346 parser.add_argument('--repack', metavar='UNPACKED_TOOLKIT',
347 help="Repack from previously unpacked toolkit")
Vic Yang7039f422014-07-07 15:38:13 -0700348
Peter Ammon948b7172014-07-15 12:43:06 -0700349 parser.add_argument('--enable-presenter', dest='enable_presenter',
Vic Yang7039f422014-07-07 15:38:13 -0700350 action='store_true',
Peter Ammon948b7172014-07-15 12:43:06 -0700351 help="Run goofy in presenter mode on startup")
352 parser.add_argument('--no-enable-presenter', dest='enable_presenter',
Vic Yang7039f422014-07-07 15:38:13 -0700353 action='store_false', help=argparse.SUPPRESS)
Peter Ammon948b7172014-07-15 12:43:06 -0700354 parser.set_defaults(enable_presenter=False)
Vic Yang7039f422014-07-07 15:38:13 -0700355
356 parser.add_argument('--enable-device', dest='enable_device',
357 action='store_true',
Peter Ammon948b7172014-07-15 12:43:06 -0700358 help="Run goofy in device mode on startup")
Vic Yang7039f422014-07-07 15:38:13 -0700359 parser.add_argument('--no-enable-device', dest='enable_device',
360 action='store_false', help=argparse.SUPPRESS)
Rong Chang6d65fad2014-08-22 16:00:12 +0800361 parser.add_argument('--init-umpire-board', dest='umpire_board',
362 nargs='?', default=None,
363 help="Locally install Umpire server for specific board")
364 parser.add_argument('--exe-path', dest='exe_path',
365 nargs='?', default=None,
366 help="Current self-extracting archive pathname")
Peter Ammon948b7172014-07-15 12:43:06 -0700367 parser.set_defaults(enable_device=True)
Vic Yang7039f422014-07-07 15:38:13 -0700368
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800369 args = parser.parse_args()
370
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800371 src_root = factory.FACTORY_PATH
372 for _ in xrange(3):
373 src_root = os.path.dirname(src_root)
374
Rong Chang6d65fad2014-08-22 16:00:12 +0800375 # --init-umpire-board creates a nano bundle, then calls umpire command
376 # line utility to install the server code and upstart configurations.
377 if args.umpire_board:
378 InitUmpire(args.exe_path, src_root, args.umpire_board)
379 return
380
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800381 # --pack-into may be called directly so this must be done before changing
382 # working directory to OLDPWD.
383 if args.pack_into and args.repack is None:
384 PackFactoryToolkit(src_root, args.pack_into)
Vic (Chun-Ju) Yang98b4fbc2014-02-18 19:32:32 +0800385 return
386
Jon Salz4f3ade52014-02-20 17:55:09 +0800387 if not in_archive:
388 # If you're not in the self-extracting archive, you're not allowed to
389 # do anything except the above --pack-into call.
390 parser.error('Not running from install_factory_toolkit.run; '
391 'only --pack-into (without --repack) is allowed')
392
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800393 # Change to original working directory in case the user specifies
394 # a relative path.
395 # TODO: Use USER_PWD instead when makeself is upgraded
396 os.chdir(os.environ['OLDPWD'])
397
Vic (Chun-Ju) Yangb7388f72014-02-19 15:22:58 +0800398 if args.repack:
399 if args.pack_into is None:
400 parser.error('Must specify --pack-into when using --repack.')
401 Spawn([os.path.join(args.repack, INSTALLER_PATH),
402 '--pack-into', args.pack_into], check_call=True, log=True)
403 return
404
405 if args.build_info:
406 PrintBuildInfo(src_root)
407 return
408
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800409 if not os.path.exists(args.dest):
410 parser.error('Destination %s does not exist!' % args.dest)
411
412 patch_test_image = os.path.isfile(args.dest)
413
414 with (MountPartition(args.dest, 1, rw=True) if patch_test_image
415 else DummyContext(args.dest)) as dest:
Peter Ammon948b7172014-07-15 12:43:06 -0700416 installer = FactoryToolkitInstaller(src_root, dest, args.no_enable,
417 args.enable_presenter, args.enable_device)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800418
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800419 print installer.WarningMessage(args.dest if patch_test_image else None)
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800420
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800421 if not args.yes:
422 answer = raw_input('*** Continue? [y/N] ')
423 if not answer or answer[0] not in 'yY':
424 sys.exit('Aborting.')
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800425
Vic (Chun-Ju) Yang469592b2014-02-18 19:15:41 +0800426 installer.Install()
Vic (Chun-Ju) Yang296871a2014-01-13 12:05:18 +0800427
428if __name__ == '__main__':
429 main()