Home | History | Annotate | Download | only in pyautolib
      1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """Chromoting helper to install/uninstall host and replace pref pane."""
      6 
      7 import abc
      8 import os
      9 import shutil
     10 import sys
     11 import subprocess
     12 
     13 
     14 class ChromotingHelper(object):
     15   """Chromoting helper base class."""
     16   __metaclass__ = abc.ABCMeta
     17 
     18   @abc.abstractmethod
     19   def InstallHost(self, bin_dir):
     20     """Installs the chromoting host"""
     21     return
     22 
     23   @abc.abstractmethod
     24   def UninstallHost(self, bin_dir):
     25     """Uninstalls the chromoting host"""
     26     return
     27 
     28 
     29 class ChromotingHelperMac(ChromotingHelper):
     30   """Chromoting Helper class for Mac.
     31 
     32   Installs/uninstalls host and replace the pref pane for testing purpose.
     33   """
     34 
     35   def InstallHost(self, bin_dir):
     36     """Installs host on Mac."""
     37     assert os.geteuid() == 0, 'Need superuser privileges'
     38 
     39     # Run most of the steps here with login user
     40     login_uid = os.getuid()
     41     os.seteuid(login_uid)
     42 
     43     # Change the working dir to the dir that has the host zip file
     44     current_dir = os.getcwd()
     45     pyautolib_dir = os.path.dirname(os.path.abspath(__file__))
     46     os.chdir(bin_dir)
     47     host_dir = 'remoting-me2me-host-mac'
     48     output_dir = os.path.join(host_dir, 'output')
     49 
     50     # Remove remoting-me2me-host-mac dir just in case
     51     shutil.rmtree(host_dir, True)
     52 
     53     # Unzip the host archive and prepare the files/dirs
     54     subprocess.call('unzip remoting-me2me-host-mac.zip', shell=True)
     55     subprocess.call('mkdir ' + output_dir, shell=True)
     56 
     57     # Prepare security identity for code signing purpose
     58     os.seteuid(0)
     59     key_chain = '/Library/Keychains/ChromotingTest'
     60     password = '1111'
     61     key = os.path.join(pyautolib_dir, 'chromoting_key.p12')
     62     cert = os.path.join(pyautolib_dir, 'chromoting_cert.p12')
     63     subprocess.call(['security', 'delete-keychain', key_chain])
     64     subprocess.call(['security', 'create-keychain', '-p',
     65                      password, key_chain])
     66     subprocess.call(['security', 'import', key,
     67                      '-k', key_chain, '-P', password, '-A'])
     68     subprocess.call(['security', 'import', cert,
     69                      '-k', key_chain, '-P', password])
     70     os.seteuid(login_uid)
     71 
     72     # Sign the host
     73     do_signing = os.path.join(host_dir, 'do_signing.sh')
     74     subprocess.call(do_signing + ' ' + output_dir + ' ' + host_dir + ' ' +
     75                     key_chain + ' "Chromoting Test"', shell=True)
     76 
     77     # Remove security identify
     78     os.seteuid(0)
     79     subprocess.call(['security', 'delete-keychain', key_chain])
     80     os.seteuid(login_uid)
     81 
     82     # Figure out the dmg name
     83     version = ""
     84     for output_file in os.listdir(output_dir):
     85       if output_file.endswith('.dmg'):
     86         version = os.path.basename(output_file)[len('ChromotingHost-'):-4]
     87 
     88     # Mount before installation
     89     dmg = os.path.join(output_dir, 'ChromotingHost-' + version + '.dmg')
     90     subprocess.call('hdiutil' + ' mount ' + dmg, shell=True)
     91 
     92     # Install host
     93     os.seteuid(0)
     94     mpkg = os.path.join('/Volumes', 'Chromoting Host ' + version,
     95                         'Chromoting Host.pkg')
     96     subprocess.call(['/usr/sbin/installer', '-pkg',
     97                      mpkg, '-target', '/'])
     98     os.seteuid(login_uid)
     99 
    100     # Unmount after installation
    101     mounted = os.path.join('/Volumes', 'Chromoting Host ' + version)
    102     subprocess.call('hdiutil detach "' + mounted + '"', shell=True)
    103 
    104     # Clean up remoting-me2me-host-mac dir
    105     shutil.rmtree(host_dir, True)
    106 
    107     # Resume the original working dir
    108     os.chdir(current_dir)
    109 
    110   def UninstallHost(self, bin_dir):
    111     """Uninstalls host on Mac."""
    112     assert os.geteuid() == 0, 'Need superuser privileges'
    113     uninstall_app = os.path.join('/', 'Applications',
    114                                  'Chromoting Host Uninstaller.app',
    115                                  'Contents', 'MacOS',
    116                                  'remoting_host_uninstaller')
    117     subprocess.call([uninstall_app, '--no-ui'])
    118 
    119   def ReplacePrefPaneMac(self, operation):
    120     """Constructs mock pref pane to replace the actual pref pane on Mac."""
    121     assert os.geteuid() == 0, 'Need superuser privileges'
    122 
    123     pref_pane_dir = os.path.join('/Library', 'PreferencePanes')
    124 
    125     mock_pref_pane = os.path.join(pref_pane_dir, 'mock_pref_pane')
    126     pref_pane = os.path.join(pref_pane_dir,
    127                              'org.chromium.chromoting.prefPane')
    128     mock_pref_pane_python = os.path.join(
    129         os.path.dirname(os.path.abspath(__file__)),
    130         'mock_pref_pane.py')
    131 
    132     # When the symlink from real pref pane to mock pref pane exists,
    133     # mock pref pane will be modified to be a dir when the host is installed.
    134     # After the host is installed and mock pref pane is modified to be a file,
    135     # it will be a file until next host installation.
    136     if os.path.isdir(mock_pref_pane):
    137       shutil.rmtree(mock_pref_pane, True)
    138     elif os.path.isfile(mock_pref_pane):
    139       os.remove(mock_pref_pane)
    140 
    141     mock_pref_pane_file = open(mock_pref_pane, 'w')
    142     mock_pref_pane_file.write('#!/bin/bash\n')
    143     mock_pref_pane_file.write('\n')
    144     mock_pref_pane_file.write('suid-python'  +
    145                               ' ' + mock_pref_pane_python + ' ' + operation)
    146     mock_pref_pane_file.close()
    147 
    148     subprocess.call(['chmod', 'a+x', mock_pref_pane])
    149 
    150     # The real pref pane is a dir if the host is installed on a clean machine.
    151     # Once the test is run on the machine, real pref pane will be replaced to
    152     # a symlink.
    153     if os.path.isdir(pref_pane):
    154       shutil.rmtree(pref_pane, True)
    155     elif os.path.isfile(pref_pane):
    156       os.remove(pref_pane)
    157 
    158     subprocess.call(['ln', '-s', mock_pref_pane, pref_pane])
    159 
    160 
    161 class ChromotingHelperWindows(ChromotingHelper):
    162   """Chromoting Helper class for Windows for installing/uninstalling host."""
    163 
    164   def InstallHost(self, bin_dir):
    165     """Installs host on Windows."""
    166     host_msi = os.path.join(bin_dir, 'chromoting.msi')
    167     subprocess.Popen(['msiexec', '/i', host_msi, '/passive']).wait()
    168 
    169   def UninstallHost(self, bin_dir):
    170     """Uninstalls host on Windows."""
    171     host_msi = os.path.join(bin_dir, 'chromoting.msi')
    172     subprocess.Popen(['msiexec', '/x', host_msi, '/passive']).wait()
    173 
    174 
    175 def Main():
    176   """Main function to dispatch operations."""
    177   assert sys.platform.startswith('win') or \
    178       sys.platform.startswith('darwin'), \
    179       'Only support Windows and Mac'
    180 
    181   if sys.platform.startswith('win'):
    182     helper = ChromotingHelperWindows()
    183   elif sys.platform.startswith('darwin'):
    184     helper = ChromotingHelperMac()
    185 
    186   if sys.argv[1] == 'install':
    187     helper.InstallHost(sys.argv[2])
    188   elif sys.argv[1] == 'uninstall':
    189     helper.UninstallHost(sys.argv[2])
    190   elif sys.argv[1] in ['enable', 'disable', 'changepin']:
    191     assert sys.platform.startswith('darwin'), \
    192       'Replacing pref pane is Mac specific'
    193     helper.ReplacePrefPaneMac(sys.argv[1])
    194   else:
    195     print >>sys.stderr, 'Invalid syntax'
    196     return 1
    197 
    198 
    199 if __name__ == '__main__':
    200   Main()