Add factory toolkit installer

This installer is packed into factory toolkit and used to install the
toolkit to a live device or to patch a test image.

BUG=chrome-os-partner:24798
TEST=unittest
TEST=Manual on a live device

Change-Id: I8b8d3064efece37ec19db2fa09ae90aecf97888d
Signed-off-by: Vic (Chun-Ju) Yang <victoryang@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/182272
Reviewed-by: Dean Liao <deanliao@chromium.org>
diff --git a/py/toolkit/installer.py b/py/toolkit/installer.py
new file mode 100755
index 0000000..2790b00
--- /dev/null
+++ b/py/toolkit/installer.py
@@ -0,0 +1,123 @@
+#!/usr/bin/python -Bu
+#
+# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Factory toolkit installer.
+
+The factory toolkit is a self-extracting shellball containing factory test
+related files and this installer. This installer is invoked when the toolkit
+is deployed and is responsible for installing files.
+"""
+
+
+import argparse
+import factory_common  # pylint: disable=W0611
+import os
+import subprocess
+import sys
+
+from cros.factory.test import factory
+
+
+class FactoryToolkitInstaller():
+  """Factory toolkit installer.
+
+  Args:
+    src: Source path containing usr/ and var/.
+    args: Arguments including
+      dest: Installation destination path. Set this to the mount point of the
+            stateful partition if patching a test image.
+      patch_test_image: True if patching a test image.
+  """
+  def __init__(self, src, args):
+    if args.patch_test_image:
+      self._usr_local_dest = os.path.join(args.dest, 'dev_image', 'local')
+      self._var_dest = os.path.join(args.dest, 'var_overlay')
+      if (not os.path.exists(self._usr_local_dest) or
+          not os.path.exists(self._var_dest)):
+        raise Exception(
+            'The destination path %s is not a stateful partition!' % args.dest)
+    else:
+      self._usr_local_dest = os.path.join(args.dest, 'usr', 'local')
+      self._var_dest = os.path.join(args.dest, 'var')
+      if os.getuid() != 0:
+        raise Exception('Must be root to install on live machine!')
+      if not os.path.exists('/etc/lsb-release'):
+        raise Exception('/etc/lsb-release is missing. '
+                        'Are you running this in chroot?')
+
+    self._patch_test_image = args.patch_test_image
+    self._dest = args.dest
+    self._usr_local_src = os.path.join(src, 'usr', 'local')
+    self._var_src = os.path.join(src, 'var')
+
+    if (not os.path.exists(self._usr_local_src) or
+        not os.path.exists(self._var_src)):
+      raise Exception(
+          'This installer must be run from within the factory toolkit!')
+
+  def WarningMessage(self):
+    ret = (
+        '\n'
+        '\n'
+        '*** You are about to install factory toolkit to:\n'
+        '***   %s\n'
+        '***' % self._dest)
+    if self._dest == '/':
+      ret += ('\n'
+        '*** After this process is done, your device will start factory tests\n'
+        '*** on the next reboot.\n'
+        '***\n'
+        '*** Factory tests can be disabled by deleting factory enabled tag:\n'
+        '***   /usr/local/factory/enabled\n'
+        '***')
+    return ret
+
+  def _Rsync(self, src, dest):
+    print '***   %s -> %s' % (src, dest)
+    subprocess.check_call(['rsync', '-a', src + '/', dest])
+
+  def Install(self):
+    print '*** Installing factory toolkit...'
+    self._Rsync(self._usr_local_src, self._usr_local_dest)
+    self._Rsync(self._var_src, self._var_dest)
+
+    print '*** Installing factory enabled tag...'
+    open(os.path.join(self._usr_local_dest, 'factory', 'enabled'), 'w').close()
+
+    print '*** Installation completed.'
+
+
+def main():
+  parser = argparse.ArgumentParser(
+      description='Factory toolkit installer.')
+  parser.add_argument('--dest', '-d', default='/',
+      help='Destination path. Mount point of stateful partition if patching '
+           'a test image.')
+  parser.add_argument('--patch-test-image', '-p', action='store_true',
+      help='Patching a test image instead of installing to live system.')
+  parser.add_argument('--yes', '-y', action='store_true',
+      help="Don't ask for confirmation")
+  args = parser.parse_args()
+
+  try:
+    src_root = factory.FACTORY_PATH
+    for _ in xrange(3):
+      src_root = os.path.dirname(src_root)
+    installer = FactoryToolkitInstaller(src_root, args)
+  except Exception as e:
+    parser.error(e.message)
+
+  print installer.WarningMessage()
+
+  if not args.yes:
+    answer = raw_input('*** Continue? [y/N] ')
+    if not answer or answer[0] not in 'yY':
+      sys.exit('Aborting.')
+
+  installer.Install()
+
+if __name__ == '__main__':
+  main()