remote_ruby 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +4 -0
- data/.rubocop.yml +3 -0
- data/.travis.yml +12 -0
- data/Gemfile +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +374 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/remote_ruby/code_templates/compiler/main.rb.erb +41 -0
- data/lib/remote_ruby/compiler.rb +55 -0
- data/lib/remote_ruby/connection_adapter/cache_adapter.rb +38 -0
- data/lib/remote_ruby/connection_adapter/caching_adapter.rb +47 -0
- data/lib/remote_ruby/connection_adapter/eval_adapter.rb +97 -0
- data/lib/remote_ruby/connection_adapter/local_stdin_adapter.rb +21 -0
- data/lib/remote_ruby/connection_adapter/ssh_stdin_adapter.rb +25 -0
- data/lib/remote_ruby/connection_adapter/stdin_process_adapter.rb +33 -0
- data/lib/remote_ruby/connection_adapter.rb +28 -0
- data/lib/remote_ruby/execution_context.rb +134 -0
- data/lib/remote_ruby/flavour/rails_flavour.rb +19 -0
- data/lib/remote_ruby/flavour.rb +25 -0
- data/lib/remote_ruby/locals_extractor.rb +41 -0
- data/lib/remote_ruby/runner.rb +53 -0
- data/lib/remote_ruby/source_extractor.rb +39 -0
- data/lib/remote_ruby/stream_cacher.rb +34 -0
- data/lib/remote_ruby/unmarshaler.rb +58 -0
- data/lib/remote_ruby/version.rb +3 -0
- data/lib/remote_ruby.rb +25 -0
- data/remote_ruby.gemspec +29 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dfe882ffd1a38cbff3ecfb83645d2596ac0dfec4668bc96a834217dbaacf1ea0
|
4
|
+
data.tar.gz: 1cc79ea7002fdf02434e26ab86227f69de8a3d9ca6daa8263940115f456190bb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 914c030cec1dde0ac7d3c4444ca5af976feb8928018f3533bf990451b8d6079c58c365f7ac2a46a9b35cc49059f15c87ce956c050f41d31ebba69ce960be3f2e
|
7
|
+
data.tar.gz: e8a95a8592d317c8ee9ed3248203c9be5e5b032bf4ca49fd60162d2a756080bfe43d0a9b68b8038540029eee4329d5691bc12fd611b67424ae2900f6050efa3a
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
|
+
|
5
|
+
gemspec
|
6
|
+
|
7
|
+
group :development do
|
8
|
+
gem 'rubocop'
|
9
|
+
gem 'yard'
|
10
|
+
end
|
11
|
+
|
12
|
+
group :development, :test do
|
13
|
+
gem 'bundler', '~> 1.15'
|
14
|
+
gem 'byebug', '>= 8.0'
|
15
|
+
gem 'pry-byebug'
|
16
|
+
gem 'rspec', '~> 3.0'
|
17
|
+
end
|
18
|
+
|
19
|
+
group :test do
|
20
|
+
gem 'coveralls', require: false
|
21
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Nikita Chernukhin
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,374 @@
|
|
1
|
+
# remote_ruby
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/Nu-hin/remote_ruby.svg?branch=master)](https://travis-ci.org/Nu-hin/remote_ruby)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/github/Nu-hin/remote_ruby/badge.svg?branch=master)](https://coveralls.io/github/Nu-hin/remote_ruby?branch=master)
|
5
|
+
[![Maintainability](https://api.codeclimate.com/v1/badges/e57430aa6f626aeca41d/maintainability)](https://codeclimate.com/github/Nu-hin/remote_ruby/maintainability)
|
6
|
+
|
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
|
+
|
9
|
+
## Contents
|
10
|
+
* [Overview](#overview)
|
11
|
+
* [How it works](#how-it-works)
|
12
|
+
* [Key features](#key-features)
|
13
|
+
* [Limitations](#limitations)
|
14
|
+
* [Installation](#installation)
|
15
|
+
* [Usage](#usage)
|
16
|
+
* [Basic usage](#basic-usage)
|
17
|
+
* [Output](#output)
|
18
|
+
* [Parameters](#parameters)
|
19
|
+
* [Local variables and return value](#local-variables-and-return-value)
|
20
|
+
* [Caching](#caching)
|
21
|
+
* [Adapters](#adapters)
|
22
|
+
* [SSH STDIN adapter](#ssh-stdin-adapter)
|
23
|
+
* [Local SDIN adapter](#local-stdin-adapter)
|
24
|
+
* [Evaluating adapter](#evaluating-adapter)
|
25
|
+
* [Rails](#rails)
|
26
|
+
* [Contributing](#contributing)
|
27
|
+
* [License](#license)
|
28
|
+
|
29
|
+
## Overview
|
30
|
+
|
31
|
+
Here is a short example on how you can run your code remotely.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
# This is test.rb file on the local developer machine
|
35
|
+
|
36
|
+
require 'remote_ruby'
|
37
|
+
|
38
|
+
remotely(server: 'my_ssh_server') do
|
39
|
+
# Everything inside this block is executed on my_ssh_server
|
40
|
+
puts 'Hello, RemoteRuby!'
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
### How it works
|
45
|
+
|
46
|
+
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).
|
47
|
+
|
48
|
+
After that, RemoteRuby opens an SSH connection to the specified host, launches Ruby interpreter there, and feeds the generated script to it. Standard output and standard error streams of SSH client are being captured.
|
49
|
+
|
50
|
+
### Key features
|
51
|
+
|
52
|
+
* Access local variables inside the remote block, just as in case of a regular block:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
user_id = 1213
|
56
|
+
|
57
|
+
remotely(server: 'my_ssh_server') do
|
58
|
+
puts user_id # => 1213
|
59
|
+
end
|
60
|
+
|
61
|
+
```
|
62
|
+
|
63
|
+
* Access return value of the remote block, just as in case of a regular block:
|
64
|
+
```ruby
|
65
|
+
res = remotely(server: 'my_ssh_server') do
|
66
|
+
'My result'
|
67
|
+
end
|
68
|
+
|
69
|
+
puts res # => My result
|
70
|
+
|
71
|
+
```
|
72
|
+
|
73
|
+
* Assignment to local variables inside remote block, just as in case of a regular block:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
a = 1
|
77
|
+
|
78
|
+
remotely(server: 'my_ssh_server') do
|
79
|
+
a = 100
|
80
|
+
end
|
81
|
+
|
82
|
+
puts a # => 100
|
83
|
+
```
|
84
|
+
|
85
|
+
### Limitations
|
86
|
+
* Remote SSH server must be accessible with public-key authentication. Password authentication is not supported.
|
87
|
+
|
88
|
+
* Currently, code to be executed remotely cannot read anything from STDIN, because STDIN is used to pass the source to the Ruby interpreter.
|
89
|
+
|
90
|
+
* 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).
|
91
|
+
* 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.
|
92
|
+
|
93
|
+
## Installation
|
94
|
+
|
95
|
+
Add this line to your application's Gemfile:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
gem 'remote_ruby', git: 'https://github.com/nu-hin/remote_ruby'
|
99
|
+
```
|
100
|
+
|
101
|
+
And then execute:
|
102
|
+
|
103
|
+
$ bundle
|
104
|
+
|
105
|
+
## Usage
|
106
|
+
|
107
|
+
### Basic usage
|
108
|
+
|
109
|
+
The main class to work with is the `ExecutionContext`, which provides an `#execute` method:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
my_server = ::RemoteRuby::ExecutionContext.new(server: 'my_ssh_server')
|
113
|
+
|
114
|
+
my_server.execute do
|
115
|
+
put Dir.pwd
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
You can easily define more than one context to access several servers.
|
120
|
+
|
121
|
+
Along with `ExecutionContext#execute` method there is also `.remotely` method, which is included into the global scope. For instance, the code above is equivalent to the code below:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
remotely(server: 'my_ssh_server') do
|
125
|
+
put Dir.pwd
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
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)).
|
130
|
+
|
131
|
+
### Parameters
|
132
|
+
|
133
|
+
Parameters, passed to the `ExecutionContext` can be general and _adapter-specific_. For adapter-specific parameters, refer to the [Adapters](#adapters) section below.
|
134
|
+
|
135
|
+
The list of general parameters:
|
136
|
+
|
137
|
+
| Parameter | Type | Required | Default value | Description |
|
138
|
+
| --------- | ---- | ---------| ------------- | ----------- |
|
139
|
+
| adapter | Class | no | `::RemoteRuby::SSHStdinAdapter` | An adapter to use. Refer to the [Adapters](#adapters) section to learn about available adapters. |
|
140
|
+
| 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. |
|
141
|
+
| 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. |
|
142
|
+
| cache_dir | String | no | ./cache | Path to the directory on the local machine, where cache files should be saved. If the directory doesn't exist, RemoteRuby will try to create it. Refer to the [Caching](#caching) section to find out more about caching. |
|
143
|
+
| stdout | Stream open for writing | no | `$stdout` | Redirection stream for server standard output |
|
144
|
+
| stderr | Stream open for writing | no | `$stderr` | Redirection stream for server standard error output |
|
145
|
+
|
146
|
+
### Output
|
147
|
+
|
148
|
+
Standard output and standard error streams from the remote process are captured, and then, depending on your parameters are either forwarded to local STOUT/STDERR or to the specified streams. RemoteRuby will add a prefix to each line of server output to distinguish between local and server output. STDOUT prefix is displayed in green, STDERR prefix is red. If output is read from cache, then `[CACHE]` prefix will also be added. The prefix may also depend on the adapter used.
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
remotely(server: 'my_ssh_server', working_dir: '/home/john') do
|
152
|
+
puts 'This is an output'
|
153
|
+
warn 'This is a warning'
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
```bash
|
158
|
+
my_ssh_server:/home/john> This is an output
|
159
|
+
my_ssh_server:/home/john> This is a warning
|
160
|
+
```
|
161
|
+
|
162
|
+
### Local variables and return value
|
163
|
+
|
164
|
+
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.
|
165
|
+
|
166
|
+
If you do not want all local variables to be sent to the server, you can explicitly specify a set of local variables and their values.
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
some_number = 3
|
170
|
+
name = 'Alice'
|
171
|
+
|
172
|
+
# Explicitly setting locals with .remotely method
|
173
|
+
remotely(locals: { name: 'John Doe' }, server: 'my_ssh_server') do
|
174
|
+
# name is 'John Doe', not 'Alice'
|
175
|
+
puts name # => John Doe
|
176
|
+
# some_number is not defined
|
177
|
+
puts some_number # undefined local variable or method `some_number'
|
178
|
+
end
|
179
|
+
|
180
|
+
# Explicitly setting locals with ExecutionContext#execute method
|
181
|
+
execution_context = ::RemoteRuby::ExecutionContext.new(server: 'my_ssh_server')
|
182
|
+
|
183
|
+
execution_context.execute(name: 'John Doe') do
|
184
|
+
# name is 'John Doe', not 'Alice'
|
185
|
+
puts name # => John Doe
|
186
|
+
# some_number is not defined
|
187
|
+
puts some_number # undefined local variable or method `some_number'
|
188
|
+
end
|
189
|
+
```
|
190
|
+
|
191
|
+
However, some objects cannot be serialized. In this case, RemoteRuby will print a warning, and the variable **will not be defined** inside the remote block.
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
# We cannot serialize a file stream
|
195
|
+
file = File.open('some_file.txt', 'rb')
|
196
|
+
|
197
|
+
remotely(server: 'my_ssh_server') do
|
198
|
+
puts file.read # undefined local variable or method `file'
|
199
|
+
end
|
200
|
+
```
|
201
|
+
|
202
|
+
Moreover, if such variables are assigned to in the remote block, their value **will not change** in the calling scope:
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
file = File.open('some_file.txt', 'rb')
|
206
|
+
|
207
|
+
remotely(server: 'my_ssh_server') do
|
208
|
+
file = 3 # No exception here, as we are assigning
|
209
|
+
end
|
210
|
+
|
211
|
+
# Old value is retained
|
212
|
+
puts file == 3 # false
|
213
|
+
```
|
214
|
+
|
215
|
+
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`:
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
# Something, which is not present on the remote server
|
219
|
+
special_thing = SomeSpecialGem::SpecialThing.new
|
220
|
+
|
221
|
+
remotely(server: 'my_ssh_server') do
|
222
|
+
# special_thing is defined, but its value is nil
|
223
|
+
puts special_thing.nil? # => true
|
224
|
+
|
225
|
+
# but we can still reassign it:
|
226
|
+
special_thing = 3
|
227
|
+
end
|
228
|
+
|
229
|
+
puts special_thing == 3 # => true
|
230
|
+
```
|
231
|
+
|
232
|
+
If RemoteRuby cannot deserialize variable on server side, it will print a warning to server's STDERR stream.
|
233
|
+
|
234
|
+
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:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
# Unsupportable return value example
|
238
|
+
|
239
|
+
remotely(server: 'my_ssh_server') do
|
240
|
+
# this is not present in the client context
|
241
|
+
server_specific_var = ServerSpecificClass.new
|
242
|
+
end
|
243
|
+
|
244
|
+
# RemoteRuby::Unmarshaler::UnmarshalError
|
245
|
+
```
|
246
|
+
|
247
|
+
```ruby
|
248
|
+
# Unsupportable local value example
|
249
|
+
|
250
|
+
my_local = nil
|
251
|
+
|
252
|
+
remotely(server: 'my_ssh_server') do
|
253
|
+
# this is not present in the client context
|
254
|
+
my_local = ServerSpecificClass.new
|
255
|
+
nil
|
256
|
+
end
|
257
|
+
|
258
|
+
# RemoteRuby::Unmarshaler::UnmarshalError
|
259
|
+
```
|
260
|
+
|
261
|
+
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:
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
# No exception
|
265
|
+
|
266
|
+
remotely(server: 'my_ssh_server') do
|
267
|
+
# this is not present in the client context
|
268
|
+
server_specific_var = ServerSpecificClass.new
|
269
|
+
nil
|
270
|
+
end
|
271
|
+
```
|
272
|
+
|
273
|
+
|
274
|
+
### Caching
|
275
|
+
|
276
|
+
RemoteRuby allows you to save the result of previous block excutions in the local cache on the client machine to save you time on subsequent script runs. To enable saving of the cache, set `save_cache: true` parameter. To turn reading from cache on, use `use_cache: true` parameter.
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
# Caching example
|
280
|
+
# First time this script will take 60 seconds to run,
|
281
|
+
# but on subsequent runs it will return the result immidiately
|
282
|
+
|
283
|
+
require 'remote_ruby'
|
284
|
+
|
285
|
+
res = remotely(server: 'my_ssh_server', save_cache: true, use_cache: true) do
|
286
|
+
60.times do
|
287
|
+
puts 'One second has passed'
|
288
|
+
STDOUT.flush
|
289
|
+
sleep 1
|
290
|
+
end
|
291
|
+
|
292
|
+
'Some result'
|
293
|
+
end
|
294
|
+
|
295
|
+
puts res # => Some result
|
296
|
+
```
|
297
|
+
|
298
|
+
You can specify where to put your cache files explicitly, by passing `cache_dir` parameter which is the "cache" directory inside your current working directory by default.
|
299
|
+
|
300
|
+
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.
|
301
|
+
|
302
|
+
**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.
|
303
|
+
|
304
|
+
### Adapters
|
305
|
+
|
306
|
+
RemoteRuby can use different adapters to execute remote Ruby code. To specify an adapter you want to use, pass an `:adapter` argument to the initializer of `ExecutionContext` or to the `remotely` method.
|
307
|
+
|
308
|
+
#### SSH STDIN adapter
|
309
|
+
|
310
|
+
This adapter uses SSH console client to connect to the remote machine, launches Ruby interpreter there, and feeds the script to the interpreter via STDIN. This is the main and the **default** adapter. It assumes that the SSH client is installed on the client machine, and that the access to the remote host is possible with public-key authenitcation. Password authentication is not supported. To use this adapter, pass `adapter: ::RemoteRuby::SSHStdinAdapter` parameter to the `ExecutionContext` initializer, or do not specify adapter at all.
|
311
|
+
|
312
|
+
##### Parameters
|
313
|
+
|
314
|
+
| Parameter | Type | Required | Default value | Description |
|
315
|
+
| --------- | ---- | ---------| ------------- | ----------- |
|
316
|
+
| server | String | yes | - | Name of the SSH server to connect to |
|
317
|
+
| working_dir | String | no | ~ | Path to the directory on the remote server where the script should be executed |
|
318
|
+
| user | String | no | - | User on the remote host to connect as |
|
319
|
+
| key_file| String | no | - | Path to the private SSH key |
|
320
|
+
|
321
|
+
|
322
|
+
#### Local STDIN adapter
|
323
|
+
|
324
|
+
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.
|
325
|
+
|
326
|
+
|
327
|
+
| Parameter | Type | Required | Default value | Description |
|
328
|
+
| --------- | ---- | ---------| ------------- | ----------- |
|
329
|
+
| working_dir | String | no | . | Path to the directory on the local machine where the script should be executed |
|
330
|
+
|
331
|
+
|
332
|
+
#### Evaluating adapter
|
333
|
+
|
334
|
+
This adapter executes Ruby code in the same process, by running it in an isolated scope. It can optionally change to a specified directory before execution (and change back after completion). There is also an option to run this asynchronously; if enabled, the code will run on a separate thread to mimic SSH connection to a remote machine. Please note, that async feature is experimental, and probably will not work on all platforms. This adapter is intended for testing, and it shows better performance than `LocalStdinAdapter`. To use this adapter, pass `adapter: ::RemoteRuby::EvalAdapter` parameter to the `ExecutionContext` initializer.
|
335
|
+
|
336
|
+
| Parameter | Type | Required | Default value | Description |
|
337
|
+
| --------- | ---- | ---------| ------------- | ----------- |
|
338
|
+
| working_dir | String | no | . | Path to the directory on the local machine where the script should be executed |
|
339
|
+
| async | Boolean | no | false | Enables or disables asynchronous mode of the adapter |
|
340
|
+
|
341
|
+
|
342
|
+
### Rails
|
343
|
+
RemoteRuby can load Rails environment for you, if you want to execute a script in a Rails context. To do this, simply add `rails` parameter to your call:
|
344
|
+
|
345
|
+
```ruby
|
346
|
+
# Rails integration example
|
347
|
+
|
348
|
+
require 'remote_ruby'
|
349
|
+
|
350
|
+
remote_service = ::RemoteRuby::ExecutionContext.new(
|
351
|
+
server: 'rails-server',
|
352
|
+
working_dir: '/var/www/rails_app/www/current',
|
353
|
+
# This specifies ENV['RAILS_ENV'] and can be changed
|
354
|
+
rails: { environment: :production }
|
355
|
+
)
|
356
|
+
|
357
|
+
user_email = 'john_doe@mydomain.com'
|
358
|
+
|
359
|
+
phone = remote_service.execute do
|
360
|
+
user = User.find_by(email: user_email)
|
361
|
+
user.try(:phone)
|
362
|
+
end
|
363
|
+
|
364
|
+
puts phone
|
365
|
+
```
|
366
|
+
|
367
|
+
|
368
|
+
## Contributing
|
369
|
+
|
370
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/nu-hin/remote_ruby.
|
371
|
+
|
372
|
+
## License
|
373
|
+
|
374
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'remote_ruby'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
__marshalled_locals_names__ = []
|
4
|
+
|
5
|
+
# Unmarshalling local variables
|
6
|
+
<% client_locals_base64.each do |name, base64_val| %>
|
7
|
+
<%= name %> = begin
|
8
|
+
val = Marshal.load(Base64.strict_decode64('<%= base64_val %>'))
|
9
|
+
__marshalled_locals_names__ << :<%= name %>
|
10
|
+
val
|
11
|
+
rescue ArgumentError
|
12
|
+
warn("Warning: could not resolve type for '<%=name %>' variable")
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
__return_val__ = begin
|
18
|
+
|
19
|
+
<% if code_headers.any? %>
|
20
|
+
# Start of flavour-added code
|
21
|
+
<%= code_headers.join %>
|
22
|
+
# End of flavour-added code
|
23
|
+
<% end %>
|
24
|
+
|
25
|
+
# Start of client code
|
26
|
+
<%= ruby_code %>
|
27
|
+
# End of client code
|
28
|
+
end
|
29
|
+
|
30
|
+
__marshalled_locals_names__ << :__return_val__
|
31
|
+
|
32
|
+
# Marshalling local variables and result
|
33
|
+
|
34
|
+
$stdout.puts "%%%MARSHAL"
|
35
|
+
|
36
|
+
__marshalled_locals_names__.each do |lv|
|
37
|
+
data = Marshal.dump(eval(lv.to_s))
|
38
|
+
data_length = data.size
|
39
|
+
$stdout.puts "#{lv}:#{data_length}"
|
40
|
+
$stdout.write(data)
|
41
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'digest'
|
3
|
+
require 'erb'
|
4
|
+
|
5
|
+
module RemoteRuby
|
6
|
+
# Receives client Ruby code, locals and their values and creates Ruby code
|
7
|
+
# to be executed on the remote host.
|
8
|
+
class Compiler
|
9
|
+
def initialize(ruby_code, client_locals: {}, flavours: [])
|
10
|
+
@ruby_code = ruby_code
|
11
|
+
@client_locals = client_locals
|
12
|
+
@flavours = flavours
|
13
|
+
end
|
14
|
+
|
15
|
+
def code_hash
|
16
|
+
@code_hash ||= Digest::SHA256.hexdigest(compiled_code)
|
17
|
+
end
|
18
|
+
|
19
|
+
def compiled_code
|
20
|
+
return @compiled_code if @compiled_code
|
21
|
+
template_file =
|
22
|
+
::RemoteRuby.lib_path('remote_ruby/code_templates/compiler/main.rb.erb')
|
23
|
+
template = ERB.new(File.read(template_file))
|
24
|
+
@compiled_code = template.result(binding)
|
25
|
+
end
|
26
|
+
|
27
|
+
def client_locals_base64
|
28
|
+
return @client_locals_base64 if @client_locals_base64
|
29
|
+
@client_locals_base64 = {}
|
30
|
+
|
31
|
+
client_locals.each do |name, data|
|
32
|
+
base64_data = process_local(name, data)
|
33
|
+
next if base64_data.nil?
|
34
|
+
@client_locals_base64[name] = base64_data
|
35
|
+
end
|
36
|
+
|
37
|
+
@client_locals_base64
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
attr_reader :ruby_code, :client_locals, :flavours
|
43
|
+
|
44
|
+
def process_local(name, data)
|
45
|
+
bin_data = Marshal.dump(data)
|
46
|
+
Base64.strict_encode64(bin_data)
|
47
|
+
rescue TypeError => e
|
48
|
+
warn "Cannot send variable '#{name}': #{e.message}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def code_headers
|
52
|
+
flavours.map(&:code_header)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RemoteRuby
|
2
|
+
# An adapter which takes stdout and stderr from files and ignores
|
3
|
+
# all stdin. Only used to read from cache.
|
4
|
+
class CacheAdapter < ConnectionAdapter
|
5
|
+
def initialize(connection_name:, cache_path:)
|
6
|
+
@cache_path = cache_path
|
7
|
+
@connection_name = connection_name
|
8
|
+
end
|
9
|
+
|
10
|
+
def connection_name
|
11
|
+
"[CACHE] #{@connection_name}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def open(_code)
|
15
|
+
stdout = File.open(stdout_file_path, 'r')
|
16
|
+
stderr = File.open(stderr_file_path, 'r')
|
17
|
+
|
18
|
+
yield stdout, stderr
|
19
|
+
ensure
|
20
|
+
stderr.close unless stderr.closed?
|
21
|
+
stdout.close unless stdout.closed?
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :cache_path
|
27
|
+
|
28
|
+
def stdout_file_path
|
29
|
+
fp = "#{cache_path}.stdout"
|
30
|
+
File.exist?(fp) ? fp : File::NULL
|
31
|
+
end
|
32
|
+
|
33
|
+
def stderr_file_path
|
34
|
+
fp = "#{cache_path}.stderr"
|
35
|
+
File.exist?(fp) ? fp : File::NULL
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'remote_ruby/stream_cacher'
|
2
|
+
|
3
|
+
module RemoteRuby
|
4
|
+
# An adapter decorator which extends the adapter passed in to its
|
5
|
+
# initializer to cache stdout and stderr to local filesystem
|
6
|
+
class CachingAdapter < ConnectionAdapter
|
7
|
+
def initialize(cache_path:, adapter:)
|
8
|
+
@cache_path = cache_path
|
9
|
+
@adapter = adapter
|
10
|
+
end
|
11
|
+
|
12
|
+
def connection_name
|
13
|
+
adapter.connection_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def open(code)
|
17
|
+
with_cache do |stdout_cache, stderr_cache|
|
18
|
+
adapter.open(code) do |stdout, stderr|
|
19
|
+
yield ::RemoteRuby::StreamCacher.new(stdout, stdout_cache),
|
20
|
+
::RemoteRuby::StreamCacher.new(stderr, stderr_cache)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :cache_path, :adapter
|
28
|
+
|
29
|
+
def with_cache
|
30
|
+
stderr_cache = File.open(stderr_file_path, 'w')
|
31
|
+
stdout_cache = File.open(stdout_file_path, 'w')
|
32
|
+
|
33
|
+
yield stdout_cache, stderr_cache
|
34
|
+
ensure
|
35
|
+
stdout_cache.close
|
36
|
+
stderr_cache.close
|
37
|
+
end
|
38
|
+
|
39
|
+
def stdout_file_path
|
40
|
+
"#{cache_path}.stdout"
|
41
|
+
end
|
42
|
+
|
43
|
+
def stderr_file_path
|
44
|
+
"#{cache_path}.stderr"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|