remote_ruby 0.3.0 → 1.0.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 +64 -0
- data/LICENSE.txt +1 -1
- data/README.md +348 -81
- 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 +128 -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,22 +16,25 @@ 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-assignements)
|
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
|
|
@@ -40,7 +45,7 @@ Here is a short example on how you can run your code remotely.
|
|
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
|
-
After that, RemoteRuby opens an SSH connection to the specified host,
|
58
|
+
After that, RemoteRuby opens an SSH connection to the specified host, copies the script to the host (to a temporary file), and then launches runs this script using the Ruby interpreter on the host. 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,19 +84,29 @@ 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).
|
109
|
+
|
96
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 [usage](#local-variables-and-return-value) section below for more detail.
|
97
111
|
|
98
112
|
## Installation
|
@@ -115,62 +129,140 @@ 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
|
+
| 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
190
|
| 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
191
|
| 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
|
-
|
|
192
|
+
RemoteRuby will try to create it. Refer to the [Caching](#caching) section to find out more about caching. |
|
193
|
+
| in_stream | Stream open for reading | no | `$stdin` | Source stream for server standard input |
|
194
|
+
| out_stream | Stream open for writing | no | `$stdout` | Redirection stream for server standard output |
|
195
|
+
| err_stream | Stream open for writing | no | `$stderr` | Redirection stream for server standard error|
|
196
|
+
| 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. |
|
197
|
+
| 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. |
|
198
|
+
|
199
|
+
### SSH Parameters
|
200
|
+
|
201
|
+
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.
|
202
|
+
|
203
|
+
If the SSH configuration file is used (see `ssh_config` parameter in the table above), the explicitly specified values **will override** the values taken from SSH config.
|
204
|
+
|
205
|
+
| Parameter | Type | Description |
|
206
|
+
| --------- | ---- | ----------- |
|
207
|
+
| user | String | the user name to log in as |
|
208
|
+
| password | String | the password to use to log in |
|
209
|
+
| keys | Array of strings | an array of file names of private keys to use for publickey and hostbased authentication |
|
210
|
+
| passphrase | String | the passphrase to use when loading a private key (default is `nil`, for no passphrase) |
|
211
|
+
| auth_methods | Array of strings | an array of authentication methods to try |
|
212
|
+
|
213
|
+
Example SSH configurations may look like:
|
214
|
+
|
215
|
+
```ruby
|
216
|
+
# Use ~/.ssh/config file, but override some parameters
|
217
|
+
ec1 = RemoteRuby::ExecutionContext.new(
|
218
|
+
host: 'my_ssh_server',
|
219
|
+
auth_methods: %w(password),
|
220
|
+
user: 'jdoe',
|
221
|
+
password: 'p@ssw0rd'
|
222
|
+
)
|
223
|
+
|
224
|
+
# Custom key file
|
225
|
+
ec2 = RemoteRuby::ExecutionContext.new(
|
226
|
+
host: 'my_ssh_server',
|
227
|
+
keys: '/home/jdoe/.ssh/custom_id_rsa'
|
228
|
+
)
|
229
|
+
|
230
|
+
# Ignore SSH configuration and provide everything explicitly
|
231
|
+
ec3 = RemoteRuby::ExecutionContext.new(
|
232
|
+
host: 'my_ssh_server',
|
233
|
+
use_ssh_config_file: false,
|
234
|
+
auth_methods: %w(password),
|
235
|
+
user: 'jdoe',
|
236
|
+
password: 'p@ssw0rd'
|
237
|
+
)
|
238
|
+
|
239
|
+
```
|
156
240
|
|
157
241
|
### Output
|
158
242
|
|
159
|
-
Standard output and standard error streams from the remote process are captured, and then, depending on your parameters are either forwarded to local
|
243
|
+
Standard output and standard error streams from the remote process are captured, and then, depending on your parameters are either forwarded to local STDOUT/STDERR or to the specified streams.
|
160
244
|
|
161
245
|
```ruby
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
246
|
+
remotely(host: 'my_ssh_server', working_dir: '/home/john') do
|
247
|
+
puts 'This is an output'
|
248
|
+
warn 'This is a warning'
|
249
|
+
end
|
166
250
|
```
|
167
251
|
|
168
|
-
|
169
|
-
|
170
|
-
|
252
|
+
### Input
|
253
|
+
|
254
|
+
Standard input from the client is captured and passed to the remote code. By default the input is captured from STDIN.
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
name = remotely(host: 'my_ssh_server') do
|
258
|
+
puts "What is your name?"
|
259
|
+
gets
|
260
|
+
end
|
261
|
+
|
262
|
+
puts "Hello locally, #{name}!"
|
171
263
|
```
|
172
264
|
|
173
|
-
### Local variables
|
265
|
+
### Local variables
|
174
266
|
|
175
267
|
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
268
|
|
@@ -181,7 +273,7 @@ some_number = 3
|
|
181
273
|
name = 'Alice'
|
182
274
|
|
183
275
|
# Explicitly setting locals with .remotely method
|
184
|
-
remotely(locals: { name: 'John Doe' },
|
276
|
+
remotely(locals: { name: 'John Doe' }, host: 'my_ssh_server') do
|
185
277
|
# name is 'John Doe', not 'Alice'
|
186
278
|
puts name # => John Doe
|
187
279
|
# some_number is not defined
|
@@ -189,7 +281,7 @@ remotely(locals: { name: 'John Doe' }, server: 'my_ssh_server') do
|
|
189
281
|
end
|
190
282
|
|
191
283
|
# Explicitly setting locals with ExecutionContext#execute method
|
192
|
-
execution_context = ::RemoteRuby::ExecutionContext.new(
|
284
|
+
execution_context = ::RemoteRuby::ExecutionContext.new(host: 'my_ssh_server')
|
193
285
|
|
194
286
|
execution_context.execute(name: 'John Doe') do
|
195
287
|
# name is 'John Doe', not 'Alice'
|
@@ -205,7 +297,7 @@ However, some objects cannot be serialized. In this case, RemoteRuby will print
|
|
205
297
|
# We cannot serialize a file stream
|
206
298
|
file = File.open('some_file.txt', 'rb')
|
207
299
|
|
208
|
-
remotely(
|
300
|
+
remotely(host: 'my_ssh_server') do
|
209
301
|
puts file.read # undefined local variable or method `file'
|
210
302
|
end
|
211
303
|
```
|
@@ -215,7 +307,7 @@ Moreover, if such variables are assigned to in the remote block, their value **w
|
|
215
307
|
```ruby
|
216
308
|
file = File.open('some_file.txt', 'rb')
|
217
309
|
|
218
|
-
remotely(
|
310
|
+
remotely(host: 'my_ssh_server') do
|
219
311
|
file = 3 # No exception here, as we are assigning
|
220
312
|
end
|
221
313
|
|
@@ -226,10 +318,11 @@ puts file == 3 # false
|
|
226
318
|
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
319
|
|
228
320
|
```ruby
|
229
|
-
# Something,
|
321
|
+
# Something, that is not present on the remote server
|
230
322
|
special_thing = SomeSpecialGem::SpecialThing.new
|
231
323
|
|
232
|
-
remotely(
|
324
|
+
remotely(host: 'my_ssh_server') do
|
325
|
+
puts defined?(special_thing) # => local-variable
|
233
326
|
# special_thing is defined, but its value is nil
|
234
327
|
puts special_thing.nil? # => true
|
235
328
|
|
@@ -242,17 +335,46 @@ puts special_thing == 3 # => true
|
|
242
335
|
|
243
336
|
If RemoteRuby cannot deserialize variable on server side, it will print a warning to server's STDERR stream.
|
244
337
|
|
338
|
+
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:
|
339
|
+
|
340
|
+
```ruby
|
341
|
+
RemoteRuby::Configure do |c|
|
342
|
+
c.ignore_types SomeSpecialGem::SpecialThing
|
343
|
+
end
|
344
|
+
```
|
345
|
+
|
346
|
+
If a type is ignored, the remote block will behave as if the local variable is not defined:
|
347
|
+
|
348
|
+
```ruby
|
349
|
+
RemoteRuby::Configure do |c|
|
350
|
+
c.ignore_types SomeSpecialGem::SpecialThing
|
351
|
+
end
|
352
|
+
|
353
|
+
special_thing = SomeSpecialGem::SpecialThing.new
|
354
|
+
|
355
|
+
remotely(host: 'my_ssh_server') do
|
356
|
+
puts defined?(special_thing) # => nil
|
357
|
+
|
358
|
+
# special_thing is not defined
|
359
|
+
puts special_thing.nil? # NameError undefined local variable or method `special_thing' for main:Object
|
360
|
+
end
|
361
|
+
```
|
362
|
+
|
363
|
+
RemoteRuby always ignores variables of type `RemoteRuby::ExecutionContext`.
|
364
|
+
|
365
|
+
### Return value and remote assignements
|
366
|
+
|
245
367
|
If remote block returns a value which 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:
|
246
368
|
|
247
369
|
```ruby
|
248
370
|
# Unsupportable return value example
|
249
371
|
|
250
|
-
remotely(
|
372
|
+
remotely(host: 'my_ssh_server') do
|
251
373
|
# this is not present in the client context
|
252
374
|
server_specific_var = ServerSpecificClass.new
|
253
375
|
end
|
254
376
|
|
255
|
-
#
|
377
|
+
# undefined class/module ServerSpecificClass (ArgumentError)
|
256
378
|
```
|
257
379
|
|
258
380
|
```ruby
|
@@ -260,13 +382,13 @@ end
|
|
260
382
|
|
261
383
|
my_local = nil
|
262
384
|
|
263
|
-
remotely(
|
385
|
+
remotely(host: 'my_ssh_server') do
|
264
386
|
# this is not present in the client context
|
265
387
|
my_local = ServerSpecificClass.new
|
266
388
|
nil
|
267
389
|
end
|
268
390
|
|
269
|
-
#
|
391
|
+
# undefined class/module ServerSpecificClass (ArgumentError)
|
270
392
|
```
|
271
393
|
|
272
394
|
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 +396,61 @@ To avoid these situations, do not assign/return values unsupported on the client
|
|
274
396
|
```ruby
|
275
397
|
# No exception
|
276
398
|
|
277
|
-
remotely(
|
399
|
+
remotely(host: 'my_ssh_server') do
|
278
400
|
# this is not present in the client context
|
279
401
|
server_specific_var = ServerSpecificClass.new
|
280
402
|
nil
|
281
403
|
end
|
282
404
|
```
|
283
405
|
|
406
|
+
### Error handling
|
407
|
+
|
408
|
+
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.
|
409
|
+
|
410
|
+
```ruby
|
411
|
+
a = 1
|
412
|
+
b = 2
|
413
|
+
res = 'unchanged'
|
414
|
+
begin
|
415
|
+
res = remotely(host: 'my_ssh_server', dump_code: true) do
|
416
|
+
a = 10
|
417
|
+
raise StandardError.new('remote error text')
|
418
|
+
b = 20
|
419
|
+
'changed'
|
420
|
+
end
|
421
|
+
rescue RemoteRuby::RemoteError
|
422
|
+
puts a
|
423
|
+
puts b
|
424
|
+
puts res
|
425
|
+
|
426
|
+
raise
|
427
|
+
end
|
428
|
+
```
|
429
|
+
|
430
|
+
This will produce something like the following.
|
431
|
+
|
432
|
+
```
|
433
|
+
10
|
434
|
+
2
|
435
|
+
unchanged
|
436
|
+
/path/to/gemset/remote_ruby/lib/remote_ruby/execution_context.rb:36:in 'RemoteRuby::ExecutionContext#execute': Remote error: StandardError (RemoteRuby::RemoteError)
|
437
|
+
remote error text
|
438
|
+
|
439
|
+
from /tmp/remote_ruby.qwGUHP:71:in `block in <main>'
|
440
|
+
(See /home/jdoe/Work/remote_ruby_test/.remote_ruby/code/dcbb2493288b1d10be042a32a31bf8af43da660234f1731f03966aa67ac870e3.rb:71:in `block in <main>'
|
441
|
+
68:
|
442
|
+
69: # Start of client code
|
443
|
+
70: a = 10
|
444
|
+
71: >> raise(StandardError.new("remote error text"))
|
445
|
+
72: b = 20
|
446
|
+
73: "changed"
|
447
|
+
74: # End of client code
|
448
|
+
```
|
449
|
+
|
450
|
+
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`.
|
451
|
+
|
452
|
+
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.
|
453
|
+
|
284
454
|
|
285
455
|
### Caching
|
286
456
|
|
@@ -293,10 +463,9 @@ RemoteRuby allows you to save the result of previous block excutions in the loca
|
|
293
463
|
|
294
464
|
require 'remote_ruby'
|
295
465
|
|
296
|
-
res = remotely(
|
466
|
+
res = remotely(host: 'my_ssh_server', save_cache: true, use_cache: true) do
|
297
467
|
60.times do
|
298
468
|
puts 'One second has passed'
|
299
|
-
STDOUT.flush
|
300
469
|
sleep 1
|
301
470
|
end
|
302
471
|
|
@@ -312,48 +481,147 @@ RemoteRuby calculates the cache file to use, based on the code you pass to the r
|
|
312
481
|
|
313
482
|
**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
483
|
|
315
|
-
###
|
484
|
+
### Text mode
|
316
485
|
|
317
|
-
|
486
|
+
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 ouput, and the output coming from the remote code. Additionally it may help distinguishing when the output is taken from cache.
|
318
487
|
|
319
|
-
|
488
|
+
The text mode is controlled by the `text_mode` parameter to the `::RemoteRuby::ExecutionContext` initializer, and is `false` by default.
|
320
489
|
|
321
|
-
|
490
|
+
The easiest way to enable it is to set `text_mode` to `true`.
|
322
491
|
|
323
|
-
|
492
|
+
```ruby
|
493
|
+
ctx = ::RemoteRuby::ExecutionContext.new(
|
494
|
+
host: 'my_ssh_server',
|
495
|
+
user: 'jdoe'
|
496
|
+
text_mode: true,
|
497
|
+
)
|
498
|
+
|
499
|
+
ctx.execute do
|
500
|
+
puts "This is a greeting"
|
501
|
+
warn "This is an error"
|
502
|
+
end
|
503
|
+
```
|
324
504
|
|
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 |
|
505
|
+
This will produce:
|
332
506
|
|
507
|
+
```
|
508
|
+
jdoe@my_ssh_server:~> This is a greeting
|
509
|
+
jdoe@my_ssh_server:~> This is a greeting
|
510
|
+
```
|
333
511
|
|
334
|
-
|
512
|
+
By default, the prefixes for stdout and stderr 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 ouput line.
|
335
513
|
|
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
514
|
|
515
|
+
You can fine-tune the text mode to your needs by passing a hash as a value to `text_mode` parameter:
|
338
516
|
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
517
|
+
```ruby
|
518
|
+
ctx = ::RemoteRuby::ExecutionContext.new(
|
519
|
+
host: 'my_ssh_server',
|
520
|
+
user: 'jdoe'
|
521
|
+
text_mode: {
|
522
|
+
stdout_prefix: 'server says: ',
|
523
|
+
stdout_prefix: 'server warns: ',
|
524
|
+
stdout_mode: { color: :blue, mode: :underline }
|
525
|
+
}
|
526
|
+
)
|
527
|
+
```
|
343
528
|
|
529
|
+
This will produce
|
344
530
|
|
345
|
-
|
531
|
+
```
|
532
|
+
server says: This is a greeting
|
533
|
+
server warns: This is a greeting
|
534
|
+
```
|
346
535
|
|
347
|
-
|
536
|
+
It is reasonable to avoid text mode if you want to put binary data to stdout:
|
537
|
+
|
538
|
+
```ruby
|
539
|
+
# copy_avatar.rb
|
540
|
+
# Reads a file from remote server and writes it to client's stdout.
|
541
|
+
|
542
|
+
remotely(host: 'my_ssh_server', text_mode: false) do
|
543
|
+
STDOUT.write File.read('avatar.jpg')
|
544
|
+
end
|
545
|
+
```
|
546
|
+
|
547
|
+
Now you could do:
|
548
|
+
|
549
|
+
```shell
|
550
|
+
ruby copy_avatar.rb > avatar.jpg
|
551
|
+
```
|
348
552
|
|
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
553
|
|
554
|
+
The complete list of text mode parameters is in the table below:
|
354
555
|
|
355
|
-
|
356
|
-
|
556
|
+
| Parameter | Type | Default value | Description |
|
557
|
+
|-|-|-|-|
|
558
|
+
| stdout_prefix | String | `user@host:/path/to/working/dir> ` | Prepended to standard output lines. Set to `nil` to disable |
|
559
|
+
| stderr_prefix | String | `user@host:/path/to/working/dir> ` | Prepended to standard error lines. Set to `nil` to disable |
|
560
|
+
| 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 |
|
561
|
+
| 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 |
|
562
|
+
| 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.
|
563
|
+
| 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.
|
564
|
+
| 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.
|
565
|
+
|
566
|
+
### Plugins
|
567
|
+
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.
|
568
|
+
|
569
|
+
#### Adding custom plugins
|
570
|
+
|
571
|
+
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.
|
572
|
+
|
573
|
+
You may inherit your class from `::RemoteRuby::Plugin` class but that is not necessary.
|
574
|
+
|
575
|
+
Let's take a look at an example plugin:
|
576
|
+
|
577
|
+
```ruby
|
578
|
+
class UsernamePlugin < RemoteRuby::Plugin
|
579
|
+
# This plugin prints a name of the user on the calling host.
|
580
|
+
attr_reader :username
|
581
|
+
|
582
|
+
def initialize(username:)
|
583
|
+
@username = username
|
584
|
+
end
|
585
|
+
|
586
|
+
def code_header
|
587
|
+
<<~RUBY
|
588
|
+
puts "This code is run by #{username}"
|
589
|
+
RUBY
|
590
|
+
end
|
591
|
+
end
|
592
|
+
```
|
593
|
+
|
594
|
+
In order to be used, the plugin needs to be registered. You can register a plugin by calling `#register_plugin` method.
|
595
|
+
|
596
|
+
```ruby
|
597
|
+
RemoteRuby.configure do |c|
|
598
|
+
c.register_plugin(:username_printer, UsernamePlugin)
|
599
|
+
end
|
600
|
+
```
|
601
|
+
|
602
|
+
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.
|
603
|
+
|
604
|
+
```ruby
|
605
|
+
ec = RemoteRuby::ExecutionContext.new(
|
606
|
+
host: 'my_ssh_server',
|
607
|
+
username_printer: { username: ENV['USER'] }
|
608
|
+
)
|
609
|
+
|
610
|
+
ec.execute do
|
611
|
+
puts "Hello world!"
|
612
|
+
end
|
613
|
+
```
|
614
|
+
|
615
|
+
This should print the following:
|
616
|
+
|
617
|
+
```
|
618
|
+
This code is run by jdoe
|
619
|
+
Hello world!
|
620
|
+
```
|
621
|
+
|
622
|
+
|
623
|
+
#### Rails plugin
|
624
|
+
RemoteRuby can load Rails environment for you, if you want to execute a script in a Rails context. To do this, simply add add built-in Rails plugin by adding `rails` argument to your call:
|
357
625
|
|
358
626
|
```ruby
|
359
627
|
# Rails integration example
|
@@ -361,7 +629,7 @@ RemoteRuby can load Rails environment for you, if you want to execute a script i
|
|
361
629
|
require 'remote_ruby'
|
362
630
|
|
363
631
|
remote_service = ::RemoteRuby::ExecutionContext.new(
|
364
|
-
|
632
|
+
host: 'rails-server',
|
365
633
|
working_dir: '/var/www/rails_app/www/current',
|
366
634
|
# This specifies ENV['RAILS_ENV'] and can be changed
|
367
635
|
rails: { environment: :production }
|
@@ -377,7 +645,6 @@ end
|
|
377
645
|
puts phone
|
378
646
|
```
|
379
647
|
|
380
|
-
|
381
648
|
## Contributing
|
382
649
|
|
383
650
|
Bug reports and pull requests are welcome on GitHub at https://github.com/nu-hin/remote_ruby.
|