1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 from qm.executable import RedirectedExecutable
19 from qm.extension import Extension
20 import os.path
21
22
23
24
25
26 -class Host(Extension):
27 """A 'Host' is a logical machine.
28
29 Each logical machine has a default directory. When a file is
30 uploaded to or downloaded from the machine, and a relative path
31 is specified, the patch is relative to the default directory.
32 Similarly, when a program is run on the remote machine, its
33 initial working directory is the default directory.
34
35 The interface presented by 'Host' is a lowest common
36 denominator. The objective is not to expose all the functionality
37 of any host; rather it is to provide an interface that can be used
38 on many hosts."""
39
40 kind = "host"
41
43 """An 'Executable' is a simple redirected executable.
44
45 The standard error and standard output streams are combined
46 into a single stream.
47
48 The standard input is not closed before
49 invoking the program because SSH hangs if its standard input
50 is closed before it is invoked. For example, running:
51
52 ssh machine echo hi <&-
53
54 will hang with some versions of SSH."""
55
59
60
65
66
67 - def Run(self, path, arguments, environment = None, timeout = -1,
68 relative = False):
69 """Run a program on the remote host.
70
71 'path' -- The name of the program to run, on the remote host.
72 If 'relative' is true, or if 'path' is not an absolute path
73 but does contain at least one directory separator, then 'path'
74 is interpreted relative to the default directory. Otherwise,
75 'path' is used unmodified.
76
77 'arguments' -- The sequence of arguments that should be passed
78 to the program.
79
80 'environment' -- If not 'None', a dictionary of pairs of
81 strings to add to the environment of the running program.
82
83 'timeout' -- The number of seconds the program is permitted
84 to execute. After the 'timeout' expires, the program will be
85 terminated. However, in some cases (such as when using 'rsh')
86 it will be the local side of the connection that is closed.
87 The remote side of the connection may or may not continue to
88 operate, depending on the vagaries of the remote operating
89 system.
90
91 returns -- A pair '(status, output)'. The 'status' is the
92 exit status returned by the program, or 'None' if the exit
93 status is not available. The 'output' is a string giving the
94 combined standard output and standard error output from the
95 program."""
96
97
98 if environment is not None:
99 new_environment = os.environ.copy()
100 new_environment.update(environment)
101 environment = new_environment
102 executable = self.Executable(timeout)
103 if relative:
104 path = os.path.join(os.curdir, path)
105 status = executable.Run([path] + arguments, environment)
106 return (status, executable.stdout)
107
108
110 """Copy 'local_file' to 'remote_file'.
111
112 'local_file' -- The name of the file on the local machine.
113
114 'remote_file' -- The name of the file on the remote machine.
115 The 'remote_file' must be a relative path. It is interpreted
116 relative to the default directory. If 'None', the
117 'remote_file' is placed in the default directory using the
118 basename of the 'local_file'.
119
120 If the 'local_file' and 'remote_file' are the same, then this
121 function succeeds, but takes no action."""
122
123 raise NotImplementedError
124
125
127 """Copy 'remote_file' to 'local_file'.
128
129 'remote_file' -- The name of the file on the remote machine.
130 The 'remote_file' must be a relative path. It is interpreted
131 relative to the default directory.
132
133 'local_file' -- The name of the file on the local machine. If
134 'None', the 'local_file' is placed in the current directory
135 using the basename of the 'remote_file'.
136
137 If the 'local_file' and 'remote_file' are the same, then this
138 function succeeds, but takes no action."""
139
140 raise NotImplementedError
141
142
145 """Run a program on the remote host.
146
147 'path' -- The name of the program to run, as a path on the
148 local machine.
149
150 'arguments' -- As for 'Run'.
151
152 'environment' -- As for 'Run'.
153
154 'timeout' -- As for 'Run'.
155
156 returns -- As for 'Run'.
157
158 The program is uploaded to the default directory on the remote
159 host, run, and then deleted."""
160
161 self.UploadFile(path)
162 basename = os.path.basename(path)
163 result = self.Run(basename,
164 arguments,
165 environment,
166 timeout,
167 relative = True)
168 self.DeleteFile(basename)
169 return result
170
171
173 """Delete the 'remote_file'.
174
175 'remote_file' -- A relative path to the file to be deleted."""
176
177 raise NotImplementedError
178