Exemplo n.º 1
0
def run_cmd(command,
            stdin=None,
            stdin_str=None,
            capture_process=False,
            capture_status=False,
            manage=False):
    """Run a given cylc command on another account and/or host.

    Arguments:
        command (list):
            command inclusive of all opts and args required to run via ssh.
        stdin (file):
            If specified, it should be a readable file object.
            If None, DEVNULL is set if output is to be captured.
        stdin_str (str):
            A string to be passed to stdin.
            Implies `stdin=PIPE`.
        capture_process (boolean):
            If True, set stdout=PIPE and return the Popen object.
        capture_status (boolean):
            If True, and the remote command is unsuccessful, return the
            associated exit code instead of exiting with an error.
        manage (boolean):
            If True, watch ancestor processes and kill command if they change
            (e.g. kill tail-follow commands when parent ssh connection dies).

    Return:
        * If capture_process=True, the Popen object if created successfully.
        * Else True if the remote command is executed successfully, or
          if unsuccessful and capture_status=True the remote command exit code.
        * Otherwise exit with an error message.
    """
    # CODACY ISSUE:
    #   subprocess call - check for execution of untrusted input.
    # REASON IGNORED:
    #   The command is read from the site/user global config file, but we check
    #   above that it ends in 'cylc', and in any case the user could execute
    #   any such command directly via ssh.
    stdout = None
    stderr = None
    if capture_process:
        stdout = PIPE
        stderr = PIPE
        if stdin is None:
            stdin = DEVNULL
    if stdin_str:
        read, write = os.pipe()
        os.write(write, stdin_str.encode())
        os.close(write)
        stdin = read

    try:
        proc = Popen(command, stdin=stdin, stdout=stdout, stderr=stderr)
    except OSError as exc:
        sys.exit(r'ERROR: %s: %s' %
                 (exc, ' '.join(quote(item) for item in command)))

    if capture_process:
        return proc
    else:
        if manage:
            watch_and_kill(proc)
        res = proc.wait()
        if WIFSIGNALED(res):
            sys.exit(r'ERROR: command terminated by signal %d: %s' %
                     (res, ' '.join(quote(item) for item in command)))
        elif res and capture_status:
            return res
        elif res:
            sys.exit(r'ERROR: command returns %d: %s' %
                     (res, ' '.join(quote(item) for item in command)))
        else:
            return True
Exemplo n.º 2
0
def remote_cylc_cmd(cmd,
                    user=None,
                    host=None,
                    capture=False,
                    manage=False,
                    ssh_login_shell=None,
                    ssh_cylc=None,
                    stdin=None):
    """Run a given cylc command on another account and/or host.

    Arguments:
        cmd (list): command to run remotely.
        user (string): user ID for the remote login.
        host (string): remote host name. Use 'localhost' if not specified.
        capture (boolean):
            If True, set stdout=PIPE and return the Popen object.
        manage (boolean):
            If True, watch ancestor processes and kill command if they change
            (e.g. kill tail-follow commands when parent ssh connection dies).
        ssh_login_shell (boolean):
            If True, launch remote command with `bash -l -c 'exec "$0" "$@"'`.
        ssh_cylc (string):
            Location of the remote cylc executable.
        stdin (file):
            If specified, it should be a readable file object.
            If None, it will be set to `open(os.devnull)` and the `-n` option
            will be added to the SSH command line.

    Return:
        If capture=True, return the Popen object if created successfully.
        Otherwise, return the exit code of the remote command.
    """
    if host is None:
        host = "localhost"
    if user is None:
        user_at_host = host
    else:
        user_at_host = '%s@%s' % (user, host)

    # Build the remote command
    command = shlex.split(
        str(glbl_cfg().get_host_item('ssh command', host, user)))
    if stdin is None:
        command.append('-n')
        stdin = open(os.devnull)
    command.append(user_at_host)

    # Pass cylc version through.
    command += ['env', r'CYLC_VERSION=%s' % CYLC_VERSION]

    if ssh_login_shell is None:
        ssh_login_shell = glbl_cfg().get_host_item('use login shell', host,
                                                   user)
    if ssh_login_shell:
        # A login shell will always source /etc/profile and the user's bash
        # profile file. To avoid having to quote the entire remote command
        # it is passed as arguments to bash.
        command += ['bash', '--login', '-c', quote(r'exec "$0" "$@"')]
    if ssh_cylc is None:
        ssh_cylc = glbl_cfg().get_host_item('cylc executable', host, user)
        if not ssh_cylc.endswith('cylc'):
            raise ValueError(
                r'ERROR: bad cylc executable in global config: %s' % ssh_cylc)
    command.append(ssh_cylc)
    command += cmd
    if cylc.flags.debug:
        sys.stderr.write('%s\n' % command)
    if capture:
        stdout = PIPE
    else:
        stdout = None
    # CODACY ISSUE:
    #   subprocess call - check for execution of untrusted input.
    # REASON IGNORED:
    #   The command is read from the site/user global config file, but we check
    #   above that it ends in 'cylc', and in any case the user could execute
    #   any such command directly via ssh.

    proc = Popen(command, stdout=stdout, stdin=stdin)
    if capture:
        return proc
    else:
        if manage:
            watch_and_kill(proc)
        res = proc.wait()
        if WIFSIGNALED(res):
            sys.stderr.write(
                'ERROR: remote command terminated by signal %d\n' % res)
        elif res:
            sys.stderr.write('ERROR: remote command failed %d\n' % res)
        return res
Exemplo n.º 3
0
Arquivo: remote.py Projeto: kaday/cylc
    def execute(self, force_required=False, env=None, path=None,
                dry_run=False):
        """Execute command on remote host.

        Returns False if remote re-invocation is not needed, True if it is
        needed and executes successfully otherwise aborts.

        """
        if not self.is_remote:
            return False

        from cylc.cfgspec.globalcfg import GLOBAL_CFG
        from cylc.version import CYLC_VERSION

        name = os.path.basename(self.argv[0])[5:]  # /path/to/cylc-foo => foo

        user_at_host = ''
        if self.owner:
            user_at_host = self.owner + '@'
        if self.host:
            user_at_host += self.host
        else:
            user_at_host += 'localhost'

        # Build the remote command

        # ssh command and options (X forwarding)
        ssh_tmpl = str(GLOBAL_CFG.get_host_item(
            "remote shell template", self.host, self.owner)).replace(" %s", "")
        command = shlex.split(ssh_tmpl) + ["-Y", user_at_host]

        # Use bash -l?
        ssh_login_shell = self.ssh_login_shell
        if ssh_login_shell is None:
            ssh_login_shell = GLOBAL_CFG.get_host_item(
                "use login shell", self.host, self.owner)

        # Pass cylc version through.
        command += ["env", "CYLC_VERSION=%s" % CYLC_VERSION]

        if ssh_login_shell:
            # A login shell will always source /etc/profile and the user's bash
            # profile file. To avoid having to quote the entire remote command
            # it is passed as arguments to the bash script.
            command += ["bash", "--login", "-c", "'exec $0 \"$@\"'"]

        # "cylc" on the remote host
        if path:
            command.append(os.sep.join(path + ["cylc"]))
        else:
            command.append(GLOBAL_CFG.get_host_item(
                "cylc executable", self.host, self.owner))

        command.append(name)

        if env is None:
            env = {}
        for var, val in env.iteritems():
            command.append("--env=%s=%s" % (var, val))

        for arg in self.args:
            command.append("'" + arg + "'")
            # above: args quoted to avoid interpretation by the shell,
            # e.g. for match patterns such as '.*' on the command line.

        if cylc.flags.verbose:
            # Wordwrap the command, quoting arguments so they can be run
            # properly from the command line
            command_str = ' '.join([quote(arg) for arg in command])
            print '\n'.join(
                TextWrapper(subsequent_indent='\t').wrap(command_str))

        if dry_run:
            return command

        try:
            popen = subprocess.Popen(command)
        except OSError as exc:
            sys.exit("ERROR: remote command invocation failed %s" % str(exc))

        res = popen.wait()
        if WIFSIGNALED(res):
            sys.exit("ERROR: remote command terminated by signal %d" % res)
        elif res:
            sys.exit("ERROR: remote command failed %d" % res)
        else:
            return True
Exemplo n.º 4
0
    def execute(self, dry_run=False, forward_x11=False, abort_if=None):
        """Execute command on remote host.

        Returns False if remote re-invocation is not needed, True if it is
        needed and executes successfully otherwise aborts.

        """
        if not self.is_remote:
            return False

        if abort_if is not None and abort_if in sys.argv:
            sys.stderr.write(
                "ERROR: option '%s' not available for remote run\n" % abort_if)
            return True

        # Build the remote command
        command = shlex.split(glbl_cfg().get_host_item('ssh command',
                                                       self.host, self.owner))
        if forward_x11:
            command.append('-Y')

        user_at_host = ''
        if self.owner:
            user_at_host = self.owner + '@'
        if self.host:
            user_at_host += self.host
        else:
            user_at_host += 'localhost'
        command.append(user_at_host)

        # Pass cylc version through.
        command += ['env', quote(r'CYLC_VERSION=%s' % CYLC_VERSION)]
        if 'CYLC_UTC' in os.environ:
            command.append(quote(r'CYLC_UTC=True'))
            command.append(quote(r'TZ=UTC'))

        # Use bash -l?
        ssh_login_shell = self.ssh_login_shell
        if ssh_login_shell is None:
            ssh_login_shell = glbl_cfg().get_host_item('use login shell',
                                                       self.host, self.owner)
        if ssh_login_shell:
            # A login shell will always source /etc/profile and the user's bash
            # profile file. To avoid having to quote the entire remote command
            # it is passed as arguments to the bash script.
            command += ['bash', '--login', '-c', quote(r'exec "$0" "$@"')]

        # 'cylc' on the remote host
        if self.ssh_cylc:
            command.append(self.ssh_cylc)
        else:
            command.append(glbl_cfg().get_host_item('cylc executable',
                                                    self.host, self.owner))

        # /path/to/cylc-foo => foo
        command.append(os.path.basename(self.argv[0])[5:])

        if cylc.flags.verbose or os.getenv('CYLC_VERBOSE') in ["True", "true"]:
            command.append(r'--verbose')
        if cylc.flags.debug or os.getenv('CYLC_DEBUG') in ["True", "true"]:
            command.append(r'--debug')
        for arg in self.args:
            command.append(quote(arg))
            # above: args quoted to avoid interpretation by the shell,
            # e.g. for match patterns such as '.*' on the command line.

        if cylc.flags.debug:
            sys.stderr.write(' '.join(quote(c) for c in command) + '\n')

        if dry_run:
            return command

        try:
            popen = Popen(command)
        except OSError as exc:
            sys.exit(r'ERROR: remote command invocation failed %s' % exc)

        res = popen.wait()
        if WIFSIGNALED(res):
            sys.exit(r'ERROR: remote command terminated by signal %d' % res)
        elif res:
            sys.exit(r'ERROR: remote command failed %d' % res)
        else:
            return True