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
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RemoteRuby
|
4
|
+
# Builds connection adapter based on the provided parameters.
|
5
|
+
# Can wrap the adapter in caching and text mode adapters.
|
6
|
+
class AdapterBuilder
|
7
|
+
attr_reader :adapter_params, :use_cache, :save_cache, :text_mode
|
8
|
+
|
9
|
+
def initialize(adapter_klass: nil, use_cache: false, save_cache: false, **params)
|
10
|
+
@adapter_klass = adapter_klass
|
11
|
+
@use_cache = use_cache
|
12
|
+
@save_cache = save_cache
|
13
|
+
@adapter_params = params
|
14
|
+
|
15
|
+
RemoteRuby.ensure_cache_dir if save_cache
|
16
|
+
end
|
17
|
+
|
18
|
+
def adapter_klass
|
19
|
+
return @adapter_klass if @adapter_klass
|
20
|
+
|
21
|
+
if adapter_params[:host]
|
22
|
+
::RemoteRuby::SSHAdapter
|
23
|
+
else
|
24
|
+
::RemoteRuby::TmpFileAdapter
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def build(code_hash)
|
29
|
+
res = adapter_klass.new(**adapter_params)
|
30
|
+
|
31
|
+
cache_mode = use_cache && cache_exists?(code_hash)
|
32
|
+
|
33
|
+
if cache_mode
|
34
|
+
cache_adapter(code_hash, res.connection_name)
|
35
|
+
elsif save_cache
|
36
|
+
caching_adapter(res, code_hash)
|
37
|
+
else
|
38
|
+
res
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def cache_adapter(code_hash, connection_name)
|
43
|
+
::RemoteRuby::CacheAdapter.new(
|
44
|
+
cache_path: cache_path(code_hash),
|
45
|
+
connection_name: connection_name
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
def caching_adapter(adapter, code_hash)
|
50
|
+
::RemoteRuby::CachingAdapter.new(
|
51
|
+
adapter: adapter,
|
52
|
+
cache_path: cache_path(code_hash)
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def context_hash(code_hash)
|
57
|
+
Digest::MD5.hexdigest(
|
58
|
+
self.class.name +
|
59
|
+
adapter_klass.name.to_s +
|
60
|
+
adapter_params.to_s +
|
61
|
+
code_hash
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
def cache_path(code_hash)
|
66
|
+
hsh = context_hash(code_hash)
|
67
|
+
File.join(RemoteRuby.cache_dir, hsh)
|
68
|
+
end
|
69
|
+
|
70
|
+
def cache_exists?(code_hash)
|
71
|
+
hsh = cache_path(code_hash)
|
72
|
+
File.exist?("#{hsh}.stdout") || File.exist?("#{hsh}.stderr") || File.exist?("#{hsh}.result")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RemoteRuby
|
4
|
+
# An adapter which takes stdout, stderr and result streams
|
5
|
+
# from files and ignores all stdin. Only used to read from cache.
|
6
|
+
class CacheAdapter < ConnectionAdapter
|
7
|
+
attr_reader :connection_name
|
8
|
+
|
9
|
+
def initialize(cache_path:, connection_name:)
|
10
|
+
super
|
11
|
+
@cache_path = cache_path
|
12
|
+
@connection_name = connection_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def open(_code, _stdin, stdout, stderr)
|
16
|
+
IO.copy_stream(stdout_file_path, stdout)
|
17
|
+
IO.copy_stream(stderr_file_path, stderr)
|
18
|
+
|
19
|
+
File.binread(result_file_path) if result_file_path
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :cache_path
|
25
|
+
|
26
|
+
def stdout_file_path
|
27
|
+
fp = "#{cache_path}.stdout"
|
28
|
+
File.exist?(fp) ? fp : File::NULL
|
29
|
+
end
|
30
|
+
|
31
|
+
def stderr_file_path
|
32
|
+
fp = "#{cache_path}.stderr"
|
33
|
+
File.exist?(fp) ? fp : File::NULL
|
34
|
+
end
|
35
|
+
|
36
|
+
def result_file_path
|
37
|
+
fp = "#{cache_path}.result"
|
38
|
+
File.exist?(fp) ? fp : File::NULL
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'remote_ruby/
|
3
|
+
require 'remote_ruby/tee_writer'
|
4
4
|
|
5
5
|
module RemoteRuby
|
6
6
|
# An adapter decorator which extends the adapter passed in to its
|
@@ -12,17 +12,21 @@ module RemoteRuby
|
|
12
12
|
@adapter = adapter
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
15
|
+
def open(code, stdin, stdout, stderr)
|
16
|
+
res = nil
|
17
|
+
with_cache do |stdout_cache, stderr_cache, result_cache|
|
18
|
+
tee_out = ::RemoteRuby::TeeWriter.new(stdout, stdout_cache)
|
19
|
+
tee_err = ::RemoteRuby::TeeWriter.new(stderr, stderr_cache)
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
adapter.open(code) do |stdout, stderr|
|
22
|
-
yield ::RemoteRuby::StreamCacher.new(stdout, stdout_cache),
|
23
|
-
::RemoteRuby::StreamCacher.new(stderr, stderr_cache)
|
24
|
-
end
|
21
|
+
res = adapter.open(code, stdin, tee_out, tee_err)
|
22
|
+
result_cache.write(res)
|
25
23
|
end
|
24
|
+
|
25
|
+
res
|
26
|
+
end
|
27
|
+
|
28
|
+
def connection_name
|
29
|
+
adapter.connection_name
|
26
30
|
end
|
27
31
|
|
28
32
|
private
|
@@ -30,13 +34,15 @@ module RemoteRuby
|
|
30
34
|
attr_reader :cache_path, :adapter
|
31
35
|
|
32
36
|
def with_cache
|
33
|
-
stderr_cache = File.open(stderr_file_path, '
|
34
|
-
stdout_cache = File.open(stdout_file_path, '
|
37
|
+
stderr_cache = File.open(stderr_file_path, 'wb')
|
38
|
+
stdout_cache = File.open(stdout_file_path, 'wb')
|
39
|
+
result_cache = File.open(result_file_path, 'wb')
|
35
40
|
|
36
|
-
yield stdout_cache, stderr_cache
|
41
|
+
yield stdout_cache, stderr_cache, result_cache
|
37
42
|
ensure
|
38
43
|
stdout_cache.close
|
39
44
|
stderr_cache.close
|
45
|
+
result_cache.close
|
40
46
|
end
|
41
47
|
|
42
48
|
def stdout_file_path
|
@@ -46,5 +52,9 @@ module RemoteRuby
|
|
46
52
|
def stderr_file_path
|
47
53
|
"#{cache_path}.stderr"
|
48
54
|
end
|
55
|
+
|
56
|
+
def result_file_path
|
57
|
+
"#{cache_path}.result"
|
58
|
+
end
|
49
59
|
end
|
50
60
|
end
|
@@ -1,44 +1,32 @@
|
|
1
1
|
# Set up Bundler if Gemfile is present
|
2
2
|
require 'bundler/setup' if File.exist?('Gemfile')
|
3
3
|
|
4
|
-
|
4
|
+
<%= inline_file('remote_context.rb') %>
|
5
5
|
|
6
|
-
|
6
|
+
__context__ = RemoteRuby::RemoteContext.new(__FILE__)
|
7
7
|
|
8
8
|
# Unmarshalling local variables
|
9
9
|
<% client_locals_base64.each do |name, base64_val| %>
|
10
|
-
<%= name %> =
|
11
|
-
val = Marshal.load(Base64.strict_decode64('<%= base64_val %>'))
|
12
|
-
__marshalled_locals_names__ << :<%= name %>
|
13
|
-
val
|
14
|
-
rescue ArgumentError
|
15
|
-
warn("Warning: could not resolve type for '<%=name %>' variable")
|
16
|
-
nil
|
17
|
-
end
|
10
|
+
<%= name %> = __context__.unmarshal(:<%= name %>, '<%= base64_val %>')
|
18
11
|
<% end %>
|
19
12
|
|
20
|
-
|
13
|
+
$stdout.sync = true
|
14
|
+
$stderr.sync = true
|
21
15
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
16
|
+
__context__.execute do
|
17
|
+
<% if plugins.any? %>
|
18
|
+
# Start of plugin-added code
|
19
|
+
<%plugins.each do |pl| %>
|
20
|
+
# <%= pl.class.name %>
|
21
|
+
<%= pl.code_header.gsub(/^/,' ') %>
|
22
|
+
# End of <%= pl.class.name %>
|
23
|
+
<%end%>
|
24
|
+
# End of plugin-added code
|
27
25
|
|
26
|
+
<% end %>
|
28
27
|
# Start of client code
|
29
|
-
<%= ruby_code %>
|
28
|
+
<%= ruby_code.gsub(/^/, ' ') %>
|
30
29
|
# End of client code
|
31
30
|
end
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
# Marshalling local variables and result
|
36
|
-
|
37
|
-
$stdout.puts "%%%MARSHAL"
|
38
|
-
|
39
|
-
__marshalled_locals_names__.each do |lv|
|
40
|
-
data = Marshal.dump(eval(lv.to_s))
|
41
|
-
data_length = data.size
|
42
|
-
$stdout.puts "#{lv}:#{data_length}"
|
43
|
-
$stdout.write(data)
|
44
|
-
end
|
32
|
+
File.binwrite(__FILE__, __context__.dump)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RemoteRuby
|
4
|
+
# Wraps any object that responds to #readpartial with a readable IO object
|
5
|
+
class CompatIOReader
|
6
|
+
attr_reader :readable
|
7
|
+
|
8
|
+
def initialize(io)
|
9
|
+
if io.is_a?(IO)
|
10
|
+
@readable = io
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
raise 'Object must respond to #readpartial' unless io.respond_to?(:readpartial)
|
15
|
+
|
16
|
+
@readable, @writeable = IO.pipe
|
17
|
+
@thread = start(io)
|
18
|
+
end
|
19
|
+
|
20
|
+
def join
|
21
|
+
return unless @writeable
|
22
|
+
|
23
|
+
@writeable.close
|
24
|
+
@thread.join
|
25
|
+
@readable.close
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def start(io)
|
31
|
+
Thread.new do
|
32
|
+
IO.copy_stream(io, @writeable)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RemoteRuby
|
4
|
+
# Wraps any object that responds to #write with a writeable IO object
|
5
|
+
class CompatIOWriter
|
6
|
+
attr_reader :writeable
|
7
|
+
|
8
|
+
def initialize(io)
|
9
|
+
if io.is_a?(IO)
|
10
|
+
@writeable = io
|
11
|
+
return
|
12
|
+
end
|
13
|
+
|
14
|
+
raise 'Object must respond to #write' unless io.respond_to?(:write)
|
15
|
+
|
16
|
+
@readable, @writeable = IO.pipe
|
17
|
+
@thread = start(io)
|
18
|
+
end
|
19
|
+
|
20
|
+
def join
|
21
|
+
return unless @readable
|
22
|
+
|
23
|
+
@readable.close
|
24
|
+
@thread.join
|
25
|
+
@writeable.close
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def start(io)
|
31
|
+
Thread.new do
|
32
|
+
IO.copy_stream(@readable, io)
|
33
|
+
rescue IOError
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/remote_ruby/compiler.rb
CHANGED
@@ -8,10 +8,10 @@ module RemoteRuby
|
|
8
8
|
# Receives client Ruby code, locals and their values and creates Ruby code
|
9
9
|
# to be executed on the remote host.
|
10
10
|
class Compiler
|
11
|
-
def initialize(ruby_code, client_locals: {},
|
11
|
+
def initialize(ruby_code, client_locals: {}, plugins: [])
|
12
12
|
@ruby_code = ruby_code
|
13
13
|
@client_locals = client_locals
|
14
|
-
@
|
14
|
+
@plugins = plugins
|
15
15
|
end
|
16
16
|
|
17
17
|
def code_hash
|
@@ -23,10 +23,14 @@ module RemoteRuby
|
|
23
23
|
|
24
24
|
template_file =
|
25
25
|
::RemoteRuby.lib_path('remote_ruby/code_templates/compiler/main.rb.erb')
|
26
|
-
template = ERB.new(File.read(template_file))
|
26
|
+
template = ERB.new(File.read(template_file), trim_mode: '<>')
|
27
27
|
@compiled_code = template.result(binding)
|
28
28
|
end
|
29
29
|
|
30
|
+
def inline_file(fname)
|
31
|
+
File.read(::RemoteRuby.lib_path('remote_ruby', fname))
|
32
|
+
end
|
33
|
+
|
30
34
|
def client_locals_base64
|
31
35
|
return @client_locals_base64 if @client_locals_base64
|
32
36
|
|
@@ -44,7 +48,7 @@ module RemoteRuby
|
|
44
48
|
|
45
49
|
private
|
46
50
|
|
47
|
-
attr_reader :ruby_code, :client_locals, :
|
51
|
+
attr_reader :ruby_code, :client_locals, :plugins
|
48
52
|
|
49
53
|
def process_local(name, data)
|
50
54
|
bin_data = Marshal.dump(data)
|
@@ -52,9 +56,5 @@ module RemoteRuby
|
|
52
56
|
rescue TypeError => e
|
53
57
|
warn "Cannot send variable '#{name}': #{e.message}"
|
54
58
|
end
|
55
|
-
|
56
|
-
def code_headers
|
57
|
-
flavours.map(&:code_header)
|
58
|
-
end
|
59
59
|
end
|
60
60
|
end
|
@@ -1,30 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module RemoteRuby
|
4
|
-
# Base class for
|
4
|
+
# Base class for connection adapters.
|
5
5
|
class ConnectionAdapter
|
6
6
|
# Initializers of adapters should receive only keyword arguments.
|
7
7
|
# May be overriden in a child class.
|
8
8
|
def initialize(**args); end
|
9
9
|
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
|
14
|
-
|
10
|
+
# Override in child class.
|
11
|
+
# Accepts compiled Ruby code as string,
|
12
|
+
# readable IO for stdin, writable IO for stdout and stderr.
|
13
|
+
# Should return a binary result stream.
|
14
|
+
def open(_code, _stdin, _stdout, _stderr)
|
15
|
+
# :nocov:
|
16
|
+
raise NotImplementedError
|
17
|
+
# :nocov:
|
15
18
|
end
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
def open(_code)
|
20
|
-
raise NotImplementedError
|
20
|
+
def connection_name
|
21
|
+
"#{self.class.name} "
|
21
22
|
end
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
25
|
-
require 'remote_ruby/
|
26
|
-
require 'remote_ruby/
|
27
|
-
require 'remote_ruby/
|
28
|
-
require 'remote_ruby/
|
29
|
-
require 'remote_ruby/
|
30
|
-
require 'remote_ruby/connection_adapter/caching_adapter'
|
26
|
+
require 'remote_ruby/cache_adapter'
|
27
|
+
require 'remote_ruby/caching_adapter'
|
28
|
+
require 'remote_ruby/ssh_adapter'
|
29
|
+
require 'remote_ruby/tmp_file_adapter'
|
30
|
+
require 'remote_ruby/text_mode_adapter'
|
@@ -7,53 +7,65 @@ require 'remote_ruby/compiler'
|
|
7
7
|
require 'remote_ruby/connection_adapter'
|
8
8
|
require 'remote_ruby/locals_extractor'
|
9
9
|
require 'remote_ruby/source_extractor'
|
10
|
-
require 'remote_ruby/
|
11
|
-
require 'remote_ruby/
|
10
|
+
require 'remote_ruby/plugin'
|
11
|
+
require 'remote_ruby/remote_context'
|
12
|
+
require 'remote_ruby/remote_error'
|
13
|
+
require 'remote_ruby/adapter_builder'
|
14
|
+
require 'remote_ruby/text_mode_builder'
|
12
15
|
|
13
16
|
module RemoteRuby
|
14
17
|
# This class is responsible for executing blocks on the remote host with the
|
15
18
|
# specified adapters. This is the entrypoint to RemoteRuby logic.
|
16
19
|
class ExecutionContext
|
17
20
|
def initialize(**params)
|
18
|
-
|
19
|
-
|
20
|
-
@
|
21
|
-
@
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
FileUtils.mkdir_p(@cache_dir)
|
21
|
+
add_plugins(params)
|
22
|
+
configure_streams(params)
|
23
|
+
@dump_code = params.delete(:dump_code) || false
|
24
|
+
@text_mode_builder = RemoteRuby::TextModeBuilder.new(
|
25
|
+
out_tty: out_stream.tty?,
|
26
|
+
err_tty: err_stream.tty?,
|
27
|
+
params: params
|
28
|
+
)
|
29
|
+
@adapter_builder = RemoteRuby::AdapterBuilder.new(**params)
|
28
30
|
end
|
29
31
|
|
30
32
|
def execute(locals = nil, &block)
|
31
33
|
source = code_source(block)
|
32
34
|
locals ||= extract_locals(block)
|
33
35
|
|
34
|
-
|
36
|
+
compiler = build_compiler(source, locals)
|
37
|
+
context = execute_code(compiler)
|
38
|
+
|
39
|
+
assign_locals(locals.keys, context.locals, block)
|
35
40
|
|
36
|
-
|
41
|
+
if context.error?
|
42
|
+
raise RemoteRuby::RemoteError.new(
|
43
|
+
compiler.compiled_code,
|
44
|
+
context,
|
45
|
+
code_path(compiler.code_hash)
|
46
|
+
)
|
47
|
+
end
|
37
48
|
|
38
|
-
result
|
49
|
+
context.result
|
39
50
|
end
|
40
51
|
|
41
52
|
private
|
42
53
|
|
43
|
-
attr_reader :
|
44
|
-
:out_stream, :err_stream, :flavours
|
54
|
+
attr_reader :dump_code, :in_stream, :out_stream, :err_stream, :plugins, :adapter_builder, :text_mode_builder
|
45
55
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
56
|
+
def add_plugins(params)
|
57
|
+
@plugins = ::RemoteRuby::Plugin.build_plugins(params)
|
58
|
+
end
|
49
59
|
|
50
|
-
|
51
|
-
|
60
|
+
def configure_streams(params)
|
61
|
+
@in_stream = params.delete(:in_stream) || $stdin
|
62
|
+
@out_stream = params.delete(:out_stream) || $stdout
|
63
|
+
@err_stream = params.delete(:err_stream) || $stderr
|
52
64
|
end
|
53
65
|
|
54
66
|
def extract_locals(block)
|
55
67
|
extractor =
|
56
|
-
::RemoteRuby::LocalsExtractor.new(block, ignore_types:
|
68
|
+
::RemoteRuby::LocalsExtractor.new(block, ignore_types: RemoteRuby.ignored_types)
|
57
69
|
extractor.locals
|
58
70
|
end
|
59
71
|
|
@@ -62,74 +74,44 @@ module RemoteRuby
|
|
62
74
|
source_extractor.extract(&block)
|
63
75
|
end
|
64
76
|
|
65
|
-
def
|
66
|
-
Digest::MD5.hexdigest(
|
67
|
-
self.class.name +
|
68
|
-
adapter_klass.name.to_s +
|
69
|
-
params.to_s +
|
70
|
-
code_hash
|
71
|
-
)
|
72
|
-
end
|
73
|
-
|
74
|
-
def cache_path(code_hash)
|
75
|
-
hsh = context_hash(code_hash)
|
76
|
-
File.join(cache_dir, hsh)
|
77
|
-
end
|
78
|
-
|
79
|
-
def cache_exists?(code_hash)
|
80
|
-
hsh = cache_path(code_hash)
|
81
|
-
File.exist?("#{hsh}.stdout") || File.exist?("#{hsh}.stderr")
|
82
|
-
end
|
83
|
-
|
84
|
-
def compiler(ruby_code, client_locals)
|
77
|
+
def build_compiler(ruby_code, client_locals)
|
85
78
|
RemoteRuby::Compiler.new(
|
86
79
|
ruby_code,
|
87
80
|
client_locals: client_locals,
|
88
|
-
|
81
|
+
plugins: plugins
|
89
82
|
)
|
90
83
|
end
|
91
84
|
|
92
|
-
def execute_code(
|
93
|
-
compiler
|
85
|
+
def execute_code(compiler)
|
86
|
+
write_code(compiler.code_hash, compiler.compiled_code)
|
94
87
|
|
95
|
-
|
96
|
-
|
97
|
-
adapter: adapter(compiler.code_hash),
|
98
|
-
out_stream: out_stream,
|
99
|
-
err_stream: err_stream
|
100
|
-
)
|
88
|
+
adapter = adapter_builder.build(compiler.code_hash)
|
89
|
+
adapter = text_mode_builder.build(adapter)
|
101
90
|
|
102
|
-
|
91
|
+
# rubocop:disable Security/MarshalLoad
|
92
|
+
Marshal.load(adapter.open(compiler.compiled_code, in_stream, out_stream, err_stream))
|
93
|
+
# rubocop:enable Security/MarshalLoad
|
103
94
|
end
|
104
95
|
|
105
|
-
def
|
106
|
-
|
96
|
+
def assign_locals(local_names, values, block)
|
97
|
+
local_names.each do |local|
|
98
|
+
next unless values.key?(local)
|
107
99
|
|
108
|
-
|
109
|
-
cache_adapter(actual_adapter, code_hash)
|
110
|
-
elsif save_cache
|
111
|
-
caching_adapter(actual_adapter, code_hash)
|
112
|
-
else
|
113
|
-
actual_adapter
|
100
|
+
block.binding.local_variable_set(local, values[local])
|
114
101
|
end
|
115
102
|
end
|
116
103
|
|
117
|
-
def
|
118
|
-
|
119
|
-
connection_name: adapter.connection_name,
|
120
|
-
cache_path: cache_path(code_hash)
|
121
|
-
)
|
122
|
-
end
|
104
|
+
def code_path(code_hash)
|
105
|
+
return nil unless dump_code
|
123
106
|
|
124
|
-
|
125
|
-
::RemoteRuby::CachingAdapter.new(
|
126
|
-
adapter: adapter,
|
127
|
-
cache_path: cache_path(code_hash)
|
128
|
-
)
|
107
|
+
File.join(RemoteRuby.code_dir, "#{code_hash}.rb")
|
129
108
|
end
|
130
109
|
|
131
|
-
def
|
132
|
-
|
110
|
+
def write_code(code_hash, code)
|
111
|
+
return unless dump_code
|
112
|
+
|
113
|
+
RemoteRuby.ensure_code_dir
|
114
|
+
File.write(code_path(code_hash), code)
|
133
115
|
end
|
134
116
|
end
|
135
117
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'remote_ruby/execution_context'
|
4
|
+
|
5
|
+
module RemoteRuby
|
6
|
+
# Module to include in the global scope to provide the `remotely` method
|
7
|
+
module Extensions
|
8
|
+
def remotely(args = {}, &block)
|
9
|
+
locals = args.delete(:locals)
|
10
|
+
execution_context = ::RemoteRuby::ExecutionContext.new(**args)
|
11
|
+
execution_context.execute(locals, &block)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RemoteRuby
|
4
|
+
# Serves to dynamically require the parser gem and configure it.
|
5
|
+
# This is done in a separate module to have a possibility
|
6
|
+
# to suppress parser gem warnings about Ruby compatibility.
|
7
|
+
module ParserFactory
|
8
|
+
def self.require_parser
|
9
|
+
begin
|
10
|
+
prev = $VERBOSE
|
11
|
+
$VERBOSE = nil if RemoteRuby.suppress_parser_warnings
|
12
|
+
require 'parser/current'
|
13
|
+
require 'unparser'
|
14
|
+
ensure
|
15
|
+
$VERBOSE = prev
|
16
|
+
end
|
17
|
+
|
18
|
+
# Opt-in to most recent AST format
|
19
|
+
Parser::Builders::Default.emit_lambda = true
|
20
|
+
Parser::Builders::Default.emit_procarg0 = true
|
21
|
+
Parser::Builders::Default.emit_encoding = true
|
22
|
+
Parser::Builders::Default.emit_index = true
|
23
|
+
Parser::Builders::Default.emit_arg_inside_procarg0 = true
|
24
|
+
Parser::Builders::Default.emit_forward_arg = true
|
25
|
+
Parser::Builders::Default.emit_kwargs = true
|
26
|
+
Parser::Builders::Default.emit_match_pattern = true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RemoteRuby
|
4
|
+
# Base class for Plugins to execution context to insert additonal
|
5
|
+
# code to the generated remote code.
|
6
|
+
class Plugin
|
7
|
+
class << self
|
8
|
+
def build_plugins(args = {})
|
9
|
+
res = []
|
10
|
+
|
11
|
+
RemoteRuby.plugins.each do |name, klass|
|
12
|
+
options = args.delete(name)
|
13
|
+
|
14
|
+
res << klass.new(**options) if options
|
15
|
+
end
|
16
|
+
|
17
|
+
res
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(**args); end
|
22
|
+
|
23
|
+
def code_header; end
|
24
|
+
end
|
25
|
+
end
|