fabric / fabric
1 1
from invoke import Runner, pty_size, Result as InvokeResult
2

3

4 1
class Remote(Runner):
5
    """
6
    Run a shell command over an SSH connection.
7

8
    This class subclasses `invoke.runners.Runner`; please see its documentation
9
    for most public API details.
10

11
    .. note::
12
        `.Remote`'s ``__init__`` method expects a `.Connection` (or subclass)
13
        instance for its ``context`` argument.
14

15
    .. versionadded:: 2.0
16
    """
17

18 1
    def __init__(self, *args, **kwargs):
19
        """
20
        Thin wrapper for superclass' ``__init__``; please see it for details.
21

22
        Additional keyword arguments defined here are listed below.
23

24
        :param bool inline_env:
25
            Whether to 'inline' shell env vars as prefixed parameters, instead
26
            of trying to submit them via `.Channel.update_environment`.
27
            Default:: ``False``.
28

29
        .. versionchanged:: 2.3
30
            Added the ``inline_env`` parameter.
31
        """
32 1
        self.inline_env = kwargs.pop("inline_env", None)
33 1
        super(Remote, self).__init__(*args, **kwargs)
34

35 1
    def start(self, command, shell, env, timeout=None):
36 1
        self.channel = self.context.create_session()
37 1
        if self.using_pty:
38 1
            rows, cols = pty_size()
39 1
            self.channel.get_pty(width=rows, height=cols)
40 1
        if env:
41
            # TODO: honor SendEnv from ssh_config (but if we do, _should_ we
42
            # honor it even when prefixing? That would depart from OpenSSH
43
            # somewhat (albeit as a "what we can do that it cannot" feature...)
44 1
            if self.inline_env:
45
                # TODO: escaping, if we can find a FOOLPROOF THIRD PARTY METHOD
46
                # for doing so!
47
                # TODO: switch to using a higher-level generic command
48
                # prefixing functionality, when implemented.
49 1
                parameters = " ".join(
50
                    ["{}={}".format(k, v) for k, v in sorted(env.items())]
51
                )
52
                # NOTE: we can assume 'export' and '&&' relatively safely, as
53
                # sshd always brings some shell into play, even if it's just
54
                # /bin/sh.
55 1
                command = "export {} && {}".format(parameters, command)
56
            else:
57 1
                self.channel.update_environment(env)
58 1
        self.channel.exec_command(command)
59

60 1
    def read_proc_stdout(self, num_bytes):
61 1
        return self.channel.recv(num_bytes)
62

63 1
    def read_proc_stderr(self, num_bytes):
64 1
        return self.channel.recv_stderr(num_bytes)
65

66 1
    def _write_proc_stdin(self, data):
67 0
        return self.channel.sendall(data)
68

69 1
    def close_proc_stdin(self):
70 0
        return self.channel.shutdown_write()
71

72 1
    @property
73
    def process_is_finished(self):
74 1
        return self.channel.exit_status_ready()
75

76 1
    def send_interrupt(self, interrupt):
77
        # NOTE: in v1, we just reraised the KeyboardInterrupt unless a PTY was
78
        # present; this seems to have been because without a PTY, the
79
        # below escape sequence is ignored, so all we can do is immediately
80
        # terminate on our end.
81
        # NOTE: also in v1, the raising of the KeyboardInterrupt completely
82
        # skipped all thread joining & cleanup; presumably regular interpreter
83
        # shutdown suffices to tie everything off well enough.
84 1
        if self.using_pty:
85
            # Submit hex ASCII character 3, aka ETX, which most Unix PTYs
86
            # interpret as a foreground SIGINT.
87
            # TODO: is there anything else we can do here to be more portable?
88 0
            self.channel.send(u"\x03")
89
        else:
90 0
            raise interrupt
91

92 1
    def returncode(self):
93 1
        return self.channel.recv_exit_status()
94

95 1
    def generate_result(self, **kwargs):
96 1
        kwargs["connection"] = self.context
97 1
        return Result(**kwargs)
98

99 1
    def stop(self):
100 1
        if hasattr(self, "channel"):
101 1
            self.channel.close()
102

103 1
    def kill(self):
104
        # Just close the channel immediately, which is about as close as we can
105
        # get to a local SIGKILL unfortunately.
106
        # TODO: consider _also_ calling .send_interrupt() and only doing this
107
        # after another few seconds; but A) kinda fragile/complex and B) would
108
        # belong in invoke.Runner anyways?
109 1
        self.channel.close()
110

111
    # TODO: shit that is in fab 1 run() but could apply to invoke.Local too:
112
    # * see rest of stuff in _run_command/_execute in operations.py...there is
113
    # a bunch that applies generally like optional exit codes, etc
114

115
    # TODO: general shit not done yet
116
    # * stdin; Local relies on local process management to ensure stdin is
117
    # hooked up; we cannot do that.
118
    # * output prefixing
119
    # * agent forwarding
120
    # * reading at 4096 bytes/time instead of whatever inv defaults to (also,
121
    # document why we are doing that, iirc it changed recentlyish via ticket)
122
    # * TODO: oh god so much more, go look it up
123

124
    # TODO: shit that has no Local equivalent that we probs need to backfill
125
    # into Runner, probably just as a "finish()" or "stop()" (to mirror
126
    # start()):
127
    # * channel close()
128
    # * agent-forward close()
129

130

131 1
class Result(InvokeResult):
132
    """
133
    An `invoke.runners.Result` exposing which `.Connection` was run against.
134

135
    Exposes all attributes from its superclass, then adds a ``.connection``,
136
    which is simply a reference to the `.Connection` whose method yielded this
137
    result.
138

139
    .. versionadded:: 2.0
140
    """
141

142 1
    def __init__(self, **kwargs):
143 1
        connection = kwargs.pop("connection")
144 1
        super(Result, self).__init__(**kwargs)
145 1
        self.connection = connection
146

147
    # TODO: have useful str/repr differentiation from invoke.Result,
148
    # transfer.Result etc.

Read our documentation on viewing source code .

Loading