131 lines
4.3 KiB
Python
131 lines
4.3 KiB
Python
# Copyright 2018 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
import threading
|
|
|
|
from common import SubprocessCallWithTimeout
|
|
|
|
_SSH = ['ssh']
|
|
_SCP = ['scp', '-C'] # Use gzip compression.
|
|
_SSH_LOGGER = logging.getLogger('ssh')
|
|
|
|
COPY_TO_TARGET = 0
|
|
COPY_FROM_TARGET = 1
|
|
|
|
|
|
def _IsLinkLocalIPv6(hostname):
|
|
return hostname.startswith('fe80::')
|
|
|
|
def _EscapeIfIPv6Address(address):
|
|
if ':' in address:
|
|
return '[' + address + ']'
|
|
else:
|
|
return address
|
|
|
|
class CommandRunner(object):
|
|
"""Helper class used to execute commands on a remote host over SSH."""
|
|
|
|
def __init__(self, config_path, host, port):
|
|
"""Creates a CommandRunner that connects to the specified |host| and |port|
|
|
using the ssh config at the specified |config_path|.
|
|
|
|
config_path: Full path to SSH configuration.
|
|
host: The hostname or IP address of the remote host.
|
|
port: The port to connect to."""
|
|
|
|
self._config_path = config_path
|
|
self._host = host
|
|
self._port = port
|
|
|
|
def _GetSshCommandLinePrefix(self):
|
|
cmd_prefix = _SSH + ['-F', self._config_path, self._host]
|
|
if self._port:
|
|
cmd_prefix += ['-p', str(self._port)]
|
|
return cmd_prefix
|
|
|
|
def RunCommand(self, command, silent, timeout_secs=None):
|
|
"""Executes an SSH command on the remote host and blocks until completion.
|
|
|
|
command: A list of strings containing the command and its arguments.
|
|
silent: If true, suppresses all output from 'ssh'.
|
|
timeout_secs: If set, limits the amount of time that |command| may run.
|
|
Commands which exceed the timeout are killed.
|
|
|
|
Returns the exit code from the remote command."""
|
|
|
|
ssh_command = self._GetSshCommandLinePrefix() + command
|
|
logging.warning(ssh_command)
|
|
_SSH_LOGGER.debug('ssh exec: ' + ' '.join(ssh_command))
|
|
retval, _, _ = SubprocessCallWithTimeout(ssh_command, silent, timeout_secs)
|
|
return retval
|
|
|
|
|
|
def RunCommandPiped(self, command, stdout, stderr, ssh_args = None, **kwargs):
|
|
"""Executes an SSH command on the remote host and returns a process object
|
|
with access to the command's stdio streams. Does not block.
|
|
|
|
command: A list of strings containing the command and its arguments.
|
|
stdout: subprocess stdout. Must not be None.
|
|
stderr: subprocess stderr. Must not be None.
|
|
ssh_args: Arguments that will be passed to SSH.
|
|
kwargs: A dictionary of parameters to be passed to subprocess.Popen().
|
|
The parameters can be used to override stdin and stdout, for
|
|
example.
|
|
|
|
Returns a Popen object for the command."""
|
|
|
|
if not stdout or not stderr:
|
|
raise Exception('Stdout/stderr must be specified explicitly')
|
|
|
|
if not ssh_args:
|
|
ssh_args = []
|
|
|
|
ssh_command = self._GetSshCommandLinePrefix() + ssh_args + ['--'] + command
|
|
logging.warning(ssh_command)
|
|
_SSH_LOGGER.debug(' '.join(ssh_command))
|
|
return subprocess.Popen(ssh_command, stdout=stdout, stderr=stderr, **kwargs)
|
|
|
|
|
|
def RunScp(self, sources, dest, direction, recursive=False):
|
|
"""Copies a file to or from a remote host using SCP and blocks until
|
|
completion.
|
|
|
|
sources: Paths of the files to be copied.
|
|
dest: The path that |source| will be copied to.
|
|
direction: Indicates whether the file should be copied to
|
|
or from the remote side.
|
|
Valid values are COPY_TO_TARGET or COPY_FROM_TARGET.
|
|
recursive: If true, performs a recursive copy.
|
|
|
|
Function will raise an assertion if a failure occurred."""
|
|
|
|
scp_command = _SCP[:]
|
|
if _SSH_LOGGER.getEffectiveLevel() == logging.DEBUG:
|
|
scp_command.append('-v')
|
|
if recursive:
|
|
scp_command.append('-r')
|
|
|
|
host = _EscapeIfIPv6Address(self._host)
|
|
|
|
if direction == COPY_TO_TARGET:
|
|
dest = "%s:%s" % (host, dest)
|
|
else:
|
|
sources = ["%s:%s" % (host, source) for source in sources]
|
|
|
|
scp_command += ['-F', self._config_path]
|
|
if self._port:
|
|
scp_command += ['-P', str(self._port)]
|
|
scp_command += sources
|
|
scp_command += [dest]
|
|
|
|
_SSH_LOGGER.debug(' '.join(scp_command))
|
|
try:
|
|
scp_output = subprocess.check_output(scp_command,
|
|
stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as error:
|
|
_SSH_LOGGER.info(error.output)
|
|
raise
|