remote_ruby 0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /cache/
10
+ /tmp/
11
+ /tags
12
+ /spec/integration/config.yml
13
+
14
+ # rspec failure tracking
15
+ .rspec_status
16
+
17
+ .gem
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --require 'spec_helper'
2
+ --format documentation
3
+ --color
4
+ --tag ~type:integration
data/.rubocop.yml ADDED
@@ -0,0 +1,3 @@
1
+ Metrics/BlockLength:
2
+ ExcludedMethods: ['describe', 'context', 'shared_context']
3
+
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1
4
+ - 2.2
5
+ - 2.3
6
+ - 2.4
7
+ - 2.5
8
+ script:
9
+ - bundle exec rspec
10
+ os:
11
+ - linux
12
+ - osx
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,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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