summaryrefslogtreecommitdiff
path: root/chromium/build/fuchsia/remote_cmd.py
blob: e103851bd5c5c58d408a5908ca45f2a0f3835b46 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# Copyright 2018 The Chromium Authors
# 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

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=False, 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: Suppresses all logging in case of success or failure.
    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
    _SSH_LOGGER.debug('ssh exec: ' + ' '.join(ssh_command))
    retval, stdout, stderr = SubprocessCallWithTimeout(ssh_command,
                                                       timeout_secs)
    if silent:
      return retval

    stripped_stdout = stdout.strip()
    stripped_stderr = stderr.strip()
    if retval:
      _SSH_LOGGER.error('"%s" failed with exit code %d%s%s',
                        ' '.join(ssh_command),
                        retval,
                        (' and stdout: "%s"' % stripped_stdout) \
                          if stripped_stdout else '',
                        (' and stderr: "%s"' % stripped_stderr) \
                          if stripped_stderr else '',
                        )
    elif stripped_stdout or stripped_stderr:
      _SSH_LOGGER.debug('succeeded with%s%s',
                        (' stdout: "%s"' % stripped_stdout) \
                          if stripped_stdout else '',
                        (' stderr: "%s"' % stripped_stderr) \
                          if stripped_stderr else '',
                        )

    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
    _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