remote_ruby 0.3.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +1 -2
- data/.rubocop.yml +37 -6
- data/CHANGELOG.md +69 -0
- data/LICENSE.txt +1 -1
- data/README.md +358 -90
- data/lib/remote_ruby/adapter_builder.rb +75 -0
- data/lib/remote_ruby/cache_adapter.rb +41 -0
- data/lib/remote_ruby/{connection_adapter/caching_adapter.rb → caching_adapter.rb} +23 -13
- data/lib/remote_ruby/code_templates/compiler/main.rb.erb +17 -29
- data/lib/remote_ruby/compat_io_reader.rb +36 -0
- data/lib/remote_ruby/compat_io_writer.rb +38 -0
- data/lib/remote_ruby/compiler.rb +8 -8
- data/lib/remote_ruby/connection_adapter.rb +16 -16
- data/lib/remote_ruby/execution_context.rb +56 -74
- data/lib/remote_ruby/extensions.rb +14 -0
- data/lib/remote_ruby/parser_factory.rb +29 -0
- data/lib/remote_ruby/plugin.rb +25 -0
- data/lib/remote_ruby/{flavour/rails_flavour.rb → rails_plugin.rb} +7 -5
- data/lib/remote_ruby/remote_context.rb +52 -0
- data/lib/remote_ruby/remote_error.rb +55 -0
- data/lib/remote_ruby/source_extractor.rb +2 -12
- data/lib/remote_ruby/ssh_adapter.rb +129 -0
- data/lib/remote_ruby/stream_prefixer.rb +25 -0
- data/lib/remote_ruby/tee_writer.rb +16 -0
- data/lib/remote_ruby/text_mode_adapter.rb +63 -0
- data/lib/remote_ruby/text_mode_builder.rb +44 -0
- data/lib/remote_ruby/tmp_file_adapter.rb +62 -0
- data/lib/remote_ruby/version.rb +1 -1
- data/lib/remote_ruby.rb +57 -15
- metadata +72 -28
- data/.github/workflows/main.yml +0 -26
- data/.gitignore +0 -17
- data/Gemfile +0 -23
- data/lib/remote_ruby/connection_adapter/cache_adapter.rb +0 -41
- data/lib/remote_ruby/connection_adapter/eval_adapter.rb +0 -96
- data/lib/remote_ruby/connection_adapter/local_stdin_adapter.rb +0 -29
- data/lib/remote_ruby/connection_adapter/ssh_stdin_adapter.rb +0 -34
- data/lib/remote_ruby/connection_adapter/stdin_process_adapter.rb +0 -35
- data/lib/remote_ruby/flavour.rb +0 -27
- data/lib/remote_ruby/runner.rb +0 -55
- data/lib/remote_ruby/stream_cacher.rb +0 -36
- data/lib/remote_ruby/unmarshaler.rb +0 -59
- data/remote_ruby.gemspec +0 -36
data/README.md
CHANGED
@@ -6,6 +6,8 @@
|
|
6
6
|
|
7
7
|
RemoteRuby allows you to execute Ruby code on remote servers via SSH right from the Ruby script running on your local machine, as if it was executed locally.
|
8
8
|
|
9
|
+
[Changelog](CHANGELOG.md)
|
10
|
+
|
9
11
|
## Contents
|
10
12
|
* [Requirements](#requirements)
|
11
13
|
* [Overview](#overview)
|
@@ -14,33 +16,36 @@ RemoteRuby allows you to execute Ruby code on remote servers via SSH right from
|
|
14
16
|
* [Limitations](#limitations)
|
15
17
|
* [Installation](#installation)
|
16
18
|
* [Usage](#usage)
|
19
|
+
* [Configuration](#configuration)
|
17
20
|
* [Basic usage](#basic-usage)
|
18
21
|
* [Output](#output)
|
19
22
|
* [Parameters](#parameters)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
* [SSH Parameters](#ssh-parameters)
|
24
|
+
* [Local variables](#local-variables)
|
25
|
+
* [Return value and remote assignments](#return-value-and-remote-assignments)
|
26
|
+
* [Error handling](#error-handling)
|
27
|
+
* [Caching](#caching)
|
28
|
+
* [Text mode](#text-mode)
|
29
|
+
* [Plugins](#plugins)
|
30
|
+
* [Adding custom plugins](#adding-custom-plugins)
|
31
|
+
* [Rails](#rails)
|
27
32
|
* [Contributing](#contributing)
|
28
33
|
* [License](#license)
|
29
34
|
|
30
35
|
## Requirements
|
31
36
|
|
32
|
-
RemoteRuby requires at least Ruby 2.
|
37
|
+
RemoteRuby requires at least Ruby 2.7 to run.
|
33
38
|
|
34
39
|
## Overview
|
35
40
|
|
36
|
-
Here is a short example
|
41
|
+
Here is a short example of how you can run your code remotely.
|
37
42
|
|
38
43
|
```ruby
|
39
44
|
# This is test.rb file on the local developer machine
|
40
45
|
|
41
46
|
require 'remote_ruby'
|
42
47
|
|
43
|
-
remotely(
|
48
|
+
remotely(host: 'my_ssh_server') do
|
44
49
|
# Everything inside this block is executed on my_ssh_server
|
45
50
|
puts 'Hello, RemoteRuby!'
|
46
51
|
end
|
@@ -50,7 +55,7 @@ end
|
|
50
55
|
|
51
56
|
When you call `#remotely` or `RemoteRuby::ExecutionContext#execute`, the passed block source is read and is then transformed to a standalone Ruby script, which also includes serialization/deserialization of local variables and return value, and other features (see [compiler.rb](lib/remote_ruby/compiler.rb) for more detail).
|
52
57
|
|
53
|
-
|
58
|
+
RemoteRuby then opens an SSH connection to the specified host, copies the script to a temporary file on the host, and launches it remotely using the Ruby interpreter. Standard output and standard error streams of SSH client are being captured. Standard input is passed to the remote code as well.
|
54
59
|
|
55
60
|
### Key features
|
56
61
|
|
@@ -59,20 +64,19 @@ After that, RemoteRuby opens an SSH connection to the specified host, launches R
|
|
59
64
|
```ruby
|
60
65
|
user_id = 1213
|
61
66
|
|
62
|
-
remotely(
|
67
|
+
remotely(host: 'my_ssh_server') do
|
63
68
|
puts user_id # => 1213
|
64
69
|
end
|
65
|
-
|
66
70
|
```
|
67
71
|
|
68
72
|
* Access return value of the remote block, just as in case of a regular block:
|
73
|
+
|
69
74
|
```ruby
|
70
|
-
res = remotely(
|
75
|
+
res = remotely(host: 'my_ssh_server') do
|
71
76
|
'My result'
|
72
77
|
end
|
73
78
|
|
74
79
|
puts res # => My result
|
75
|
-
|
76
80
|
```
|
77
81
|
|
78
82
|
* Assignment to local variables inside remote block, just as in case of a regular block:
|
@@ -80,20 +84,30 @@ puts res # => My result
|
|
80
84
|
```ruby
|
81
85
|
a = 1
|
82
86
|
|
83
|
-
remotely(
|
87
|
+
remotely(host: 'my_ssh_server') do
|
84
88
|
a = 100
|
85
89
|
end
|
86
90
|
|
87
91
|
puts a # => 100
|
88
92
|
```
|
89
93
|
|
90
|
-
|
91
|
-
|
94
|
+
* Reading from the client's standard input in the remote block:
|
95
|
+
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
remotely(host: 'my_ssh_server') do
|
99
|
+
puts 'What is your name?'
|
100
|
+
name = gets
|
101
|
+
puts "Hello, #{name}!"
|
102
|
+
end
|
103
|
+
```
|
92
104
|
|
93
|
-
|
105
|
+
### Limitations
|
106
|
+
* macOS keychain is not supported. If you are using a private SSH key with a passphrase, and you don't want to enter a passphrase each time a context is executed, the identity must be added to the SSH-agent, e.g. using `ssh-add`.
|
94
107
|
|
95
108
|
* As RemoteRuby reads the block source from the script's source file, the script source file should reside on your machine's disk (e.g. you cannot use RemoteRuby from IRB console).
|
96
|
-
|
109
|
+
|
110
|
+
* Since local and server scripts have different execution contexts, can have different gems (and even Ruby versions) installed, sometimes local variables as well as the block return value, will not be accessible, assigned or can even cause exception. See the [usage](#local-variables-and-return-value) section below for more details.
|
97
111
|
|
98
112
|
## Installation
|
99
113
|
|
@@ -115,62 +129,141 @@ gem install remote_ruby
|
|
115
129
|
|
116
130
|
## Usage
|
117
131
|
|
132
|
+
### Configuration
|
133
|
+
|
134
|
+
There are a few options that may be configured on the global level.
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
RemoteRuby.configure do |c|
|
138
|
+
# Defines, where Remote Ruby will cache output, error and result streams on the
|
139
|
+
# local machine.
|
140
|
+
# By default they are saved to .remote_ruby/cache (relative to the working directory).
|
141
|
+
c.cache_dir = File.join(Dir.pwd, '.remote_ruby/cache')
|
142
|
+
|
143
|
+
# Defines, where Remote Ruby will store compiled code on the local machine, if
|
144
|
+
# `dump_code` is set to `true` in the ExecutionContext.
|
145
|
+
# By default code is saved to .remote_ruby/code (relative to the working directory).
|
146
|
+
c.code_dir = File.join(Dir.pwd, '.remote_ruby/code')
|
147
|
+
|
148
|
+
# Set to true if you don't want to see warnings about parser gem compatibility with
|
149
|
+
# current Ruby version.
|
150
|
+
# False by default.
|
151
|
+
c.suppress_parser_warnings = false
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
118
155
|
### Basic usage
|
119
156
|
|
120
157
|
The main class to work with is the `ExecutionContext`, which provides an `#execute` method:
|
121
158
|
|
122
159
|
```ruby
|
123
|
-
my_server = ::RemoteRuby::ExecutionContext.new(
|
160
|
+
my_server = ::RemoteRuby::ExecutionContext.new(host: 'my_ssh_server')
|
124
161
|
|
125
162
|
my_server.execute do
|
126
|
-
|
163
|
+
puts Dir.pwd
|
127
164
|
end
|
128
165
|
```
|
129
166
|
|
130
167
|
You can easily define more than one context to access several servers.
|
131
168
|
|
132
|
-
Along with `ExecutionContext#execute` method there is also `.remotely` method, which
|
169
|
+
Along with `ExecutionContext#execute` method there is also `.remotely` method, which can be included from `RemoteRuby::Extensions` module. For instance, the code above is equivalent to the code below:
|
133
170
|
|
134
171
|
```ruby
|
135
|
-
|
136
|
-
|
172
|
+
include RemoteRuby::Extensions
|
173
|
+
|
174
|
+
remotely(host: 'my_ssh_server') do
|
175
|
+
puts Dir.pwd
|
137
176
|
end
|
138
177
|
```
|
139
178
|
|
140
179
|
All parameters passed to the `remotely` method will be passed to the underlying `ExecutionContext` initializer. The only exception is an optional `locals` parameter, which will be passed to the `#execute` method (see [below](#local-variables-and-return-value)).
|
141
180
|
|
142
|
-
|
143
|
-
|
144
|
-
Parameters, passed to the `ExecutionContext` can be general and _adapter-specific_. For adapter-specific parameters, refer to the [Adapters](#adapters) section below.
|
181
|
+
In all the examples in this document, where `.remote` method is used, it is assumed, that `RemoteRuby::Extensions` is included to the scope.
|
145
182
|
|
146
|
-
|
183
|
+
### Parameters
|
147
184
|
|
148
185
|
| Parameter | Type | Required | Default value | Description |
|
149
186
|
| --------- | ---- | ---------| ------------- | ----------- |
|
150
|
-
|
|
187
|
+
| host | String | no | - | Name of the SSH host to connect to. If omitted, the code will be executed on the local host, in a separate Ruby process |
|
188
|
+
| use_ssh_config_file | String or Boolean | no | true | When boolean, specifies, whether to use ~/.ssh/config file for the initial set of parameters. When string, interpreted as a path to an SSH configuration file to use |
|
189
|
+
| ruby_executable | String | no | `ruby` | Absolute path to Ruby executable on the remote host, or executable name, reachable from $PATH |
|
190
|
+
| working_dir | String | no | '~' if running over SSH, or current dir, if running locally | Path to the directory where the script should be executed |
|
151
191
|
| use_cache | Boolean | no | `false` | Specifies if the cache should be used for execution of the block (if the cache is available). Refer to the [Caching](#caching) section to find out more about caching. |
|
152
192
|
| save_cache | Boolean | no | `false` | Specifies if the result of the block execution (i.e. output and error streams) should be cached for the subsequent use. Refer to the [Caching](#caching) section to find out more about caching. |
|
153
|
-
|
154
|
-
|
|
155
|
-
|
|
193
|
+
RemoteRuby will try to create it. Refer to the [Caching](#caching) section to find out more about caching. |
|
194
|
+
| in_stream | Stream open for reading | no | `$stdin` | Source stream for server standard input |
|
195
|
+
| out_stream | Stream open for writing | no | `$stdout` | Redirection stream for server standard output |
|
196
|
+
| err_stream | Stream open for writing | no | `$stderr` | Redirection stream for server standard error|
|
197
|
+
| text_mode | Boolean or Hash | no | `false` | Specifies, if the connection should be run in text mode. See [Text Mode](#text-mode) section below to find out more about text mode. |
|
198
|
+
| dump_code | Boolean | no | `false` | When set to true, the compiled script that will be run on the remote server will be dumped to a local file for inspection. See [Configuration](#configuration) to configure where the code is written. |
|
199
|
+
|
200
|
+
### SSH Parameters
|
201
|
+
|
202
|
+
In addition to the arguments above, you can fine-tune the SSH connection to the remote host, if SSH is used (that is, if the `host` argument is specified). The arguments for SSH configuration can be anything that is supported by [net-ssh gem](https://github.com/net-ssh/net-ssh). The complete list of parameters can be found in [the documentation for net-ssh](https://net-ssh.github.io/net-ssh/Net/SSH.html#method-c-start). Some of the parameters are in the table below.
|
203
|
+
|
204
|
+
If the SSH configuration file is used (see `ssh_config` parameter in the table above), the explicitly specified values **will override** those taken from SSH config.
|
205
|
+
|
206
|
+
| Parameter | Type | Description |
|
207
|
+
| --------- | ---- | ----------- |
|
208
|
+
| user | String | the user name to log in as |
|
209
|
+
| password | String | the password to use to log in |
|
210
|
+
| keys | Array of strings | an array of file names of private keys to use for publickey and hostbased authentication |
|
211
|
+
| passphrase | String | the passphrase to use when loading a private key (default is `nil`, for no passphrase) |
|
212
|
+
| auth_methods | Array of strings | an array of authentication methods to try |
|
213
|
+
|
214
|
+
Example SSH configurations may look like:
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
# Use ~/.ssh/config file, but override some parameters
|
218
|
+
ec1 = RemoteRuby::ExecutionContext.new(
|
219
|
+
host: 'my_ssh_server',
|
220
|
+
auth_methods: %w(password),
|
221
|
+
user: 'jdoe',
|
222
|
+
password: 'p@ssw0rd'
|
223
|
+
)
|
224
|
+
|
225
|
+
# Custom key file
|
226
|
+
ec2 = RemoteRuby::ExecutionContext.new(
|
227
|
+
host: 'my_ssh_server',
|
228
|
+
keys: '/home/jdoe/.ssh/custom_id_rsa'
|
229
|
+
)
|
230
|
+
|
231
|
+
# Ignore SSH configuration and provide everything explicitly
|
232
|
+
ec3 = RemoteRuby::ExecutionContext.new(
|
233
|
+
host: 'my_ssh_server',
|
234
|
+
use_ssh_config_file: false,
|
235
|
+
auth_methods: %w(password),
|
236
|
+
user: 'jdoe',
|
237
|
+
password: 'p@ssw0rd'
|
238
|
+
)
|
239
|
+
|
240
|
+
```
|
156
241
|
|
157
242
|
### Output
|
158
243
|
|
159
|
-
Standard output and standard error streams from the remote process are captured, and then, depending on your parameters are either forwarded to local
|
244
|
+
Standard output and standard error streams from the remote process are captured, and then, depending on your parameters are either forwarded to local standard output/error or to the specified streams.
|
160
245
|
|
161
246
|
```ruby
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
247
|
+
remotely(host: 'my_ssh_server', working_dir: '/home/john') do
|
248
|
+
puts 'This is an output'
|
249
|
+
warn 'This is a warning'
|
250
|
+
end
|
166
251
|
```
|
167
252
|
|
168
|
-
|
169
|
-
|
170
|
-
|
253
|
+
### Input
|
254
|
+
|
255
|
+
Remote script can receive data from standard input. By default the input is captured from client's standard input, but this can be set to any readable stream using `in_stream` argument to the `ExecutionContext` initializer.
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
name = remotely(host: 'my_ssh_server') do
|
259
|
+
puts "What is your name?"
|
260
|
+
gets
|
261
|
+
end
|
262
|
+
|
263
|
+
puts "Hello locally, #{name}!"
|
171
264
|
```
|
172
265
|
|
173
|
-
### Local variables
|
266
|
+
### Local variables
|
174
267
|
|
175
268
|
When you call a remote block RemoteRuby will try to serialize all local variables from the calling context, and include them to the remote script.
|
176
269
|
|
@@ -181,7 +274,7 @@ some_number = 3
|
|
181
274
|
name = 'Alice'
|
182
275
|
|
183
276
|
# Explicitly setting locals with .remotely method
|
184
|
-
remotely(locals: { name: 'John Doe' },
|
277
|
+
remotely(locals: { name: 'John Doe' }, host: 'my_ssh_server') do
|
185
278
|
# name is 'John Doe', not 'Alice'
|
186
279
|
puts name # => John Doe
|
187
280
|
# some_number is not defined
|
@@ -189,7 +282,7 @@ remotely(locals: { name: 'John Doe' }, server: 'my_ssh_server') do
|
|
189
282
|
end
|
190
283
|
|
191
284
|
# Explicitly setting locals with ExecutionContext#execute method
|
192
|
-
execution_context = ::RemoteRuby::ExecutionContext.new(
|
285
|
+
execution_context = ::RemoteRuby::ExecutionContext.new(host: 'my_ssh_server')
|
193
286
|
|
194
287
|
execution_context.execute(name: 'John Doe') do
|
195
288
|
# name is 'John Doe', not 'Alice'
|
@@ -205,7 +298,7 @@ However, some objects cannot be serialized. In this case, RemoteRuby will print
|
|
205
298
|
# We cannot serialize a file stream
|
206
299
|
file = File.open('some_file.txt', 'rb')
|
207
300
|
|
208
|
-
remotely(
|
301
|
+
remotely(host: 'my_ssh_server') do
|
209
302
|
puts file.read # undefined local variable or method `file'
|
210
303
|
end
|
211
304
|
```
|
@@ -215,7 +308,7 @@ Moreover, if such variables are assigned to in the remote block, their value **w
|
|
215
308
|
```ruby
|
216
309
|
file = File.open('some_file.txt', 'rb')
|
217
310
|
|
218
|
-
remotely(
|
311
|
+
remotely(host: 'my_ssh_server') do
|
219
312
|
file = 3 # No exception here, as we are assigning
|
220
313
|
end
|
221
314
|
|
@@ -226,10 +319,11 @@ puts file == 3 # false
|
|
226
319
|
If the variable can be serialized, but the remote server context lacks the knowledge on how to deserialize it, the variable will be defined inside the remote block, but its value will be `nil`:
|
227
320
|
|
228
321
|
```ruby
|
229
|
-
# Something,
|
322
|
+
# Something, that is not present on the remote server
|
230
323
|
special_thing = SomeSpecialGem::SpecialThing.new
|
231
324
|
|
232
|
-
remotely(
|
325
|
+
remotely(host: 'my_ssh_server') do
|
326
|
+
puts defined?(special_thing) # => local-variable
|
233
327
|
# special_thing is defined, but its value is nil
|
234
328
|
puts special_thing.nil? # => true
|
235
329
|
|
@@ -240,33 +334,62 @@ end
|
|
240
334
|
puts special_thing == 3 # => true
|
241
335
|
```
|
242
336
|
|
243
|
-
If RemoteRuby cannot deserialize variable on server side, it will print a warning to server's
|
337
|
+
If RemoteRuby cannot deserialize variable on server side, it will print a warning to server's standard error.
|
338
|
+
|
339
|
+
It is possible to ignore certain types, so that RemoteRuby will never try to send variables of these types to the remote host. This can be done by adding configuration:
|
244
340
|
|
245
|
-
|
341
|
+
```ruby
|
342
|
+
RemoteRuby::Configure do |c|
|
343
|
+
c.ignore_types SomeSpecialGem::SpecialThing
|
344
|
+
end
|
345
|
+
```
|
346
|
+
|
347
|
+
If a type is ignored, the remote block will behave as if the local variable is not defined:
|
246
348
|
|
247
349
|
```ruby
|
248
|
-
|
350
|
+
RemoteRuby::Configure do |c|
|
351
|
+
c.ignore_types SomeSpecialGem::SpecialThing
|
352
|
+
end
|
353
|
+
|
354
|
+
special_thing = SomeSpecialGem::SpecialThing.new
|
355
|
+
|
356
|
+
remotely(host: 'my_ssh_server') do
|
357
|
+
puts defined?(special_thing) # => nil
|
249
358
|
|
250
|
-
|
359
|
+
# special_thing is not defined
|
360
|
+
puts special_thing.nil? # NameError undefined local variable or method `special_thing' for main:Object
|
361
|
+
end
|
362
|
+
```
|
363
|
+
|
364
|
+
RemoteRuby always ignores variables of type `RemoteRuby::ExecutionContext`.
|
365
|
+
|
366
|
+
### Return value and remote assignments
|
367
|
+
|
368
|
+
If remote block returns a value that cannot be deserialized on the client side, or if it assigns such a value to the local variable, the exception on the client side will be always raised:
|
369
|
+
|
370
|
+
```ruby
|
371
|
+
# Unsupported return value example
|
372
|
+
|
373
|
+
remotely(host: 'my_ssh_server') do
|
251
374
|
# this is not present in the client context
|
252
375
|
server_specific_var = ServerSpecificClass.new
|
253
376
|
end
|
254
377
|
|
255
|
-
#
|
378
|
+
# undefined class/module ServerSpecificClass (ArgumentError)
|
256
379
|
```
|
257
380
|
|
258
381
|
```ruby
|
259
|
-
#
|
382
|
+
# Unsupported local value example
|
260
383
|
|
261
384
|
my_local = nil
|
262
385
|
|
263
|
-
remotely(
|
386
|
+
remotely(host: 'my_ssh_server') do
|
264
387
|
# this is not present in the client context
|
265
388
|
my_local = ServerSpecificClass.new
|
266
389
|
nil
|
267
390
|
end
|
268
391
|
|
269
|
-
#
|
392
|
+
# undefined class/module ServerSpecificClass (ArgumentError)
|
270
393
|
```
|
271
394
|
|
272
395
|
To avoid these situations, do not assign/return values unsupported on the client side, or, if you don't need any return value, add `nil` at the end of your block:
|
@@ -274,13 +397,61 @@ To avoid these situations, do not assign/return values unsupported on the client
|
|
274
397
|
```ruby
|
275
398
|
# No exception
|
276
399
|
|
277
|
-
remotely(
|
400
|
+
remotely(host: 'my_ssh_server') do
|
278
401
|
# this is not present in the client context
|
279
402
|
server_specific_var = ServerSpecificClass.new
|
280
403
|
nil
|
281
404
|
end
|
282
405
|
```
|
283
406
|
|
407
|
+
### Error handling
|
408
|
+
|
409
|
+
If remote code raises an error, RemoteRuby intercepts it and raises a `RemoteRuby::RemoteError` on the local machine. Since remote code potentially can raise an exception of a type (or containing a type), that is not present in the local context, the exception itself is not wrapped. Instead, `RemoteRuby::RemoteError` will contain the class name, message and the stack trace of the causing remote error.
|
410
|
+
|
411
|
+
```ruby
|
412
|
+
a = 1
|
413
|
+
b = 2
|
414
|
+
res = 'unchanged'
|
415
|
+
begin
|
416
|
+
res = remotely(host: 'my_ssh_server', dump_code: true) do
|
417
|
+
a = 10
|
418
|
+
raise StandardError.new('remote error text')
|
419
|
+
b = 20
|
420
|
+
'changed'
|
421
|
+
end
|
422
|
+
rescue RemoteRuby::RemoteError
|
423
|
+
puts a
|
424
|
+
puts b
|
425
|
+
puts res
|
426
|
+
|
427
|
+
raise
|
428
|
+
end
|
429
|
+
```
|
430
|
+
|
431
|
+
This will produce something like the following.
|
432
|
+
|
433
|
+
```
|
434
|
+
10
|
435
|
+
2
|
436
|
+
unchanged
|
437
|
+
/path/to/gemset/remote_ruby/lib/remote_ruby/execution_context.rb:36:in 'RemoteRuby::ExecutionContext#execute': Remote error: StandardError (RemoteRuby::RemoteError)
|
438
|
+
remote error text
|
439
|
+
|
440
|
+
from /tmp/remote_ruby.qwGUHP:71:in `block in <main>'
|
441
|
+
(See /home/jdoe/Work/remote_ruby_test/.remote_ruby/code/dcbb2493288b1d10be042a32a31bf8af43da660234f1731f03966aa67ac870e3.rb:71:in `block in <main>'
|
442
|
+
68:
|
443
|
+
69: # Start of client code
|
444
|
+
70: a = 10
|
445
|
+
71: >> raise(StandardError.new("remote error text"))
|
446
|
+
72: b = 20
|
447
|
+
73: "changed"
|
448
|
+
74: # End of client code
|
449
|
+
```
|
450
|
+
|
451
|
+
As you can see, the behaviour of remote error corresponds to the situation when an error is raised in normal block. Local variables that are assigned before the error have the new value. The result of block in case of an error is `nil`.
|
452
|
+
|
453
|
+
Note that when printed to console `RemoteError` displays the context of the error in the **remote script**. If `dump_code` is set to `true`, `RemoteError` will also print the location of the line in the local copy of the remote script. This may be very useful for debugging.
|
454
|
+
|
284
455
|
|
285
456
|
### Caching
|
286
457
|
|
@@ -289,14 +460,13 @@ RemoteRuby allows you to save the result of previous block excutions in the loca
|
|
289
460
|
```ruby
|
290
461
|
# Caching example
|
291
462
|
# First time this script will take 60 seconds to run,
|
292
|
-
# but on subsequent runs it will return the result
|
463
|
+
# but on subsequent runs it will return the result immediately
|
293
464
|
|
294
465
|
require 'remote_ruby'
|
295
466
|
|
296
|
-
res = remotely(
|
467
|
+
res = remotely(host: 'my_ssh_server', save_cache: true, use_cache: true) do
|
297
468
|
60.times do
|
298
469
|
puts 'One second has passed'
|
299
|
-
STDOUT.flush
|
300
470
|
sleep 1
|
301
471
|
end
|
302
472
|
|
@@ -306,54 +476,153 @@ end
|
|
306
476
|
puts res # => Some result
|
307
477
|
```
|
308
478
|
|
309
|
-
You can specify where to put your cache files explicitly, by
|
479
|
+
You can specify where to put your cache files explicitly, by [configuring](#configuration) the `cache_dir` which is by default set to ".remote_ruby/cache" inside your current working directory.
|
310
480
|
|
311
|
-
RemoteRuby calculates the cache file to use, based on the code you pass to the remote block, as well as on ExecutionContext 'contextual' parameters (e. g. server or working directory) and serialized local variables. Therefore, if you change anything in your remote block, local variables (passed to the block), or in any of the 'contextual' parameters, RemoteRuby will use different cache file. However, if you revert all your changes back, the old file will be used again.
|
481
|
+
RemoteRuby calculates the cache file to use, based on the code you pass to the remote block, as well as on `ExecutionContext` 'contextual' parameters (e. g. server or working directory) and serialized local variables. Therefore, if you change anything in your remote block, local variables (passed to the block), or in any of the 'contextual' parameters, RemoteRuby will use different cache file. However, if you revert all your changes back, the old file will be used again.
|
312
482
|
|
313
483
|
**IMPORTANT**: RemoteRuby does not know when to clear the cache. Therefore, it is up to you to take care of cleaning the cache when you no longer need it. This is especially important if your output can contain sensitive data.
|
314
484
|
|
315
|
-
###
|
485
|
+
### Text mode
|
316
486
|
|
317
|
-
|
487
|
+
Text mode allows to treat the output and/or the standard error of the remote process as text. If it is enabled, the server output is prefixed with some string, which makes it easier to distinguish local output, and the output coming from the remote code. Additionally, it may help distinguishing when the output is taken from cache.
|
318
488
|
|
319
|
-
|
489
|
+
The text mode is controlled by the `text_mode` parameter to the `::RemoteRuby::ExecutionContext` initializer, and is `false` by default.
|
320
490
|
|
321
|
-
|
491
|
+
The easiest way to enable it is to set `text_mode` to `true`.
|
322
492
|
|
323
|
-
|
493
|
+
```ruby
|
494
|
+
ctx = ::RemoteRuby::ExecutionContext.new(
|
495
|
+
host: 'my_ssh_server',
|
496
|
+
user: 'jdoe'
|
497
|
+
text_mode: true,
|
498
|
+
)
|
499
|
+
|
500
|
+
ctx.execute do
|
501
|
+
puts "This is a greeting"
|
502
|
+
warn "This is an error"
|
503
|
+
end
|
504
|
+
```
|
324
505
|
|
325
|
-
|
326
|
-
| --------- | ---- | ---------| ------------- | ----------- |
|
327
|
-
| server | String | yes | - | Name of the SSH server to connect to |
|
328
|
-
| working_dir | String | no | ~ | Path to the directory on the remote server where the script should be executed |
|
329
|
-
| user | String | no | - | User on the remote host to connect as |
|
330
|
-
| key_file| String | no | - | Path to the private SSH key |
|
331
|
-
| bundler | Boolean | no | false | Specifies, whether the code should be executed with `bundle exec` on the remote server |
|
506
|
+
This will produce:
|
332
507
|
|
508
|
+
```
|
509
|
+
jdoe@my_ssh_server:~> This is a greeting
|
510
|
+
jdoe@my_ssh_server:~> This is a greeting
|
511
|
+
```
|
333
512
|
|
334
|
-
|
513
|
+
By default, the prefixes for standard output and standard error can be different when running over SSH and locally. Output prefix is marked with green italic, and error with red italic. If the cache is used, the default configuration will append a bold blue '[C]' prefix in front of each output line.
|
335
514
|
|
336
|
-
This adapter changes to the specified directory on the **local** machine, launches Ruby interpreter there, and feeds the script to the interpreter via STDIN. Therefore everything will be executed on the local machine, but in a child process. This adapter can be used for testing, or it can be useful if you want to execute some code in context of several code bases you have on the local machine. To use this adapter, pass `adapter: ::RemoteRuby::LocalStdinAdapter` parameter to the `ExecutionContext` initializer.
|
337
515
|
|
516
|
+
You can fine-tune the text mode to your needs by passing a hash as a value to `text_mode` parameter:
|
338
517
|
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
518
|
+
```ruby
|
519
|
+
ctx = ::RemoteRuby::ExecutionContext.new(
|
520
|
+
host: 'my_ssh_server',
|
521
|
+
user: 'jdoe'
|
522
|
+
text_mode: {
|
523
|
+
stdout_prefix: 'server says: ',
|
524
|
+
stdout_prefix: 'server warns: ',
|
525
|
+
stdout_mode: { color: :blue, mode: :underline }
|
526
|
+
}
|
527
|
+
)
|
528
|
+
```
|
343
529
|
|
530
|
+
This will produce
|
344
531
|
|
345
|
-
|
532
|
+
```
|
533
|
+
server says: This is a greeting
|
534
|
+
server warns: This is a greeting
|
535
|
+
```
|
346
536
|
|
347
|
-
|
537
|
+
It is reasonable to avoid text mode if you want to put binary data to the standard output:
|
538
|
+
|
539
|
+
```ruby
|
540
|
+
# copy_avatar.rb
|
541
|
+
# Reads a file from remote server and writes it to client's standard output.
|
542
|
+
|
543
|
+
remotely(host: 'my_ssh_server', text_mode: false) do
|
544
|
+
STDOUT.write File.read('avatar.jpg')
|
545
|
+
end
|
546
|
+
```
|
547
|
+
|
548
|
+
Now you could do:
|
549
|
+
|
550
|
+
```shell
|
551
|
+
ruby copy_avatar.rb > avatar.jpg
|
552
|
+
```
|
348
553
|
|
349
|
-
| Parameter | Type | Required | Default value | Description |
|
350
|
-
| --------- | ---- | ---------| ------------- | ----------- |
|
351
|
-
| working_dir | String | no | . | Path to the directory on the local machine where the script should be executed |
|
352
|
-
| async | Boolean | no | false | Enables or disables asynchronous mode of the adapter |
|
353
554
|
|
555
|
+
The complete list of text mode parameters is in the table below:
|
354
556
|
|
355
|
-
|
356
|
-
|
557
|
+
| Parameter | Type | Default value | Description |
|
558
|
+
|-|-|-|-|
|
559
|
+
| stdout_prefix | String | `user@host:/path/to/working/dir> ` | Prepended to standard output lines. Set to `nil` to disable |
|
560
|
+
| stderr_prefix | String | `user@host:/path/to/working/dir> ` | Prepended to standard error lines. Set to `nil` to disable |
|
561
|
+
| cache_prefix | String | `'[C] '` | Prepended to standard output and standard error lines if the context is using cache. Only added if corresponding prefix is not `nil`. Set to `nil` to disable |
|
562
|
+
| disable_unless_tty | Boolean | true | Disables the text mode if the corresponding IO is not TTY. Useful if you want to disable the prefixes and coloring when e.g. outputting to a file |
|
563
|
+
| stdout_mode | Hash | `{ color: :green, mode: :italic }` | Text effects and colors applied to the standard output prefix. See [colorize gem](https://github.com/fazibear/colorize) for available parameters.
|
564
|
+
| stderr_mode | Hash | `{ color: :red, mode: :italic }` | Text effects and colors applied to the standard error prefix. See [colorize gem](https://github.com/fazibear/colorize) for available parameters.
|
565
|
+
| cache_mode | Hash | `{ color: :blue, mode: :bold }` | Text effects and colors applied to the cache prefix. See [colorize gem](https://github.com/fazibear/colorize) for available parameters.
|
566
|
+
|
567
|
+
### Plugins
|
568
|
+
RemoteRuby can be extended with plugins. Plugins are used to insert additional code to the script, which is executed in the remote context. There is also a built-in plugin that allows for automatically loading Rails environment.
|
569
|
+
|
570
|
+
#### Adding custom plugins
|
571
|
+
|
572
|
+
RemoteRuby plugin must be a class. Instances of a plugin class must respond to `#code_header` method without any parameters. Plugins are instantiated when the `ExecutionContext` is created.
|
573
|
+
|
574
|
+
You may inherit your class from `::RemoteRuby::Plugin` class but that is not necessary.
|
575
|
+
|
576
|
+
Let's take a look at an example plugin:
|
577
|
+
|
578
|
+
```ruby
|
579
|
+
class UsernamePlugin < RemoteRuby::Plugin
|
580
|
+
# This plugin prints a name of the user on the calling host.
|
581
|
+
attr_reader :username
|
582
|
+
|
583
|
+
def initialize(username:)
|
584
|
+
@username = username
|
585
|
+
end
|
586
|
+
|
587
|
+
def code_header
|
588
|
+
<<~RUBY
|
589
|
+
puts "This code is run by #{username}"
|
590
|
+
RUBY
|
591
|
+
end
|
592
|
+
end
|
593
|
+
```
|
594
|
+
|
595
|
+
In order to be used, the plugin needs to be registered. You can register a plugin by calling `#register_plugin` method.
|
596
|
+
|
597
|
+
```ruby
|
598
|
+
RemoteRuby.configure do |c|
|
599
|
+
c.register_plugin(:username_printer, UsernamePlugin)
|
600
|
+
end
|
601
|
+
```
|
602
|
+
|
603
|
+
Now, when creating an `ExecutionContext` we can use `username_printer` argument to the initializer. Plugin argument value must be a hash. All hash values will be passed to plugin class initializer as name arguments.
|
604
|
+
|
605
|
+
```ruby
|
606
|
+
ec = RemoteRuby::ExecutionContext.new(
|
607
|
+
host: 'my_ssh_server',
|
608
|
+
username_printer: { username: ENV['USER'] }
|
609
|
+
)
|
610
|
+
|
611
|
+
ec.execute do
|
612
|
+
puts "Hello world!"
|
613
|
+
end
|
614
|
+
```
|
615
|
+
|
616
|
+
This should print the following:
|
617
|
+
|
618
|
+
```
|
619
|
+
This code is run by jdoe
|
620
|
+
Hello world!
|
621
|
+
```
|
622
|
+
|
623
|
+
|
624
|
+
#### Rails plugin
|
625
|
+
RemoteRuby can load Rails environment for you, if you want to execute a script in a Rails context. To do this, simply add built-in Rails plugin by adding `rails` argument to your call:
|
357
626
|
|
358
627
|
```ruby
|
359
628
|
# Rails integration example
|
@@ -361,7 +630,7 @@ RemoteRuby can load Rails environment for you, if you want to execute a script i
|
|
361
630
|
require 'remote_ruby'
|
362
631
|
|
363
632
|
remote_service = ::RemoteRuby::ExecutionContext.new(
|
364
|
-
|
633
|
+
host: 'rails-server',
|
365
634
|
working_dir: '/var/www/rails_app/www/current',
|
366
635
|
# This specifies ENV['RAILS_ENV'] and can be changed
|
367
636
|
rails: { environment: :production }
|
@@ -377,7 +646,6 @@ end
|
|
377
646
|
puts phone
|
378
647
|
```
|
379
648
|
|
380
|
-
|
381
649
|
## Contributing
|
382
650
|
|
383
651
|
Bug reports and pull requests are welcome on GitHub at https://github.com/nu-hin/remote_ruby.
|