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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -2
  3. data/.rubocop.yml +37 -6
  4. data/CHANGELOG.md +64 -0
  5. data/LICENSE.txt +1 -1
  6. data/README.md +348 -81
  7. data/lib/remote_ruby/adapter_builder.rb +75 -0
  8. data/lib/remote_ruby/cache_adapter.rb +41 -0
  9. data/lib/remote_ruby/{connection_adapter/caching_adapter.rb → caching_adapter.rb} +23 -13
  10. data/lib/remote_ruby/code_templates/compiler/main.rb.erb +17 -29
  11. data/lib/remote_ruby/compat_io_reader.rb +36 -0
  12. data/lib/remote_ruby/compat_io_writer.rb +38 -0
  13. data/lib/remote_ruby/compiler.rb +8 -8
  14. data/lib/remote_ruby/connection_adapter.rb +16 -16
  15. data/lib/remote_ruby/execution_context.rb +56 -74
  16. data/lib/remote_ruby/extensions.rb +14 -0
  17. data/lib/remote_ruby/parser_factory.rb +29 -0
  18. data/lib/remote_ruby/plugin.rb +25 -0
  19. data/lib/remote_ruby/{flavour/rails_flavour.rb → rails_plugin.rb} +7 -5
  20. data/lib/remote_ruby/remote_context.rb +52 -0
  21. data/lib/remote_ruby/remote_error.rb +55 -0
  22. data/lib/remote_ruby/source_extractor.rb +2 -12
  23. data/lib/remote_ruby/ssh_adapter.rb +128 -0
  24. data/lib/remote_ruby/stream_prefixer.rb +25 -0
  25. data/lib/remote_ruby/tee_writer.rb +16 -0
  26. data/lib/remote_ruby/text_mode_adapter.rb +63 -0
  27. data/lib/remote_ruby/text_mode_builder.rb +44 -0
  28. data/lib/remote_ruby/tmp_file_adapter.rb +62 -0
  29. data/lib/remote_ruby/version.rb +1 -1
  30. data/lib/remote_ruby.rb +57 -15
  31. metadata +72 -28
  32. data/.github/workflows/main.yml +0 -26
  33. data/.gitignore +0 -17
  34. data/Gemfile +0 -23
  35. data/lib/remote_ruby/connection_adapter/cache_adapter.rb +0 -41
  36. data/lib/remote_ruby/connection_adapter/eval_adapter.rb +0 -96
  37. data/lib/remote_ruby/connection_adapter/local_stdin_adapter.rb +0 -29
  38. data/lib/remote_ruby/connection_adapter/ssh_stdin_adapter.rb +0 -34
  39. data/lib/remote_ruby/connection_adapter/stdin_process_adapter.rb +0 -35
  40. data/lib/remote_ruby/flavour.rb +0 -27
  41. data/lib/remote_ruby/runner.rb +0 -55
  42. data/lib/remote_ruby/stream_cacher.rb +0 -36
  43. data/lib/remote_ruby/unmarshaler.rb +0 -59
  44. data/remote_ruby.gemspec +0 -36
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: remote_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nikita Chernukhin
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-12-16 00:00:00.000000000 Z
10
+ date: 2025-03-06 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: base64
@@ -30,42 +29,84 @@ dependencies:
30
29
  requirements:
31
30
  - - "~>"
32
31
  - !ruby/object:Gem::Version
33
- version: '0.8'
32
+ version: '1.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: logger
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.6'
34
47
  type: :runtime
35
48
  prerelease: false
36
49
  version_requirements: !ruby/object:Gem::Requirement
37
50
  requirements:
38
51
  - - "~>"
39
52
  - !ruby/object:Gem::Version
40
- version: '0.8'
53
+ version: '1.6'
41
54
  - !ruby/object:Gem::Dependency
42
55
  name: method_source
43
56
  requirement: !ruby/object:Gem::Requirement
44
57
  requirements:
45
58
  - - "~>"
46
59
  - !ruby/object:Gem::Version
47
- version: '1.0'
60
+ version: '1.1'
48
61
  type: :runtime
49
62
  prerelease: false
50
63
  version_requirements: !ruby/object:Gem::Requirement
51
64
  requirements:
52
65
  - - "~>"
53
66
  - !ruby/object:Gem::Version
54
- version: '1.0'
67
+ version: '1.1'
68
+ - !ruby/object:Gem::Dependency
69
+ name: net-ssh
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '7.3'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '7.3'
82
+ - !ruby/object:Gem::Dependency
83
+ name: ostruct
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '0.6'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '0.6'
55
96
  - !ruby/object:Gem::Dependency
56
97
  name: parser
57
98
  requirement: !ruby/object:Gem::Requirement
58
99
  requirements:
59
100
  - - "~>"
60
101
  - !ruby/object:Gem::Version
61
- version: '3.0'
102
+ version: '3.3'
62
103
  type: :runtime
63
104
  prerelease: false
64
105
  version_requirements: !ruby/object:Gem::Requirement
65
106
  requirements:
66
107
  - - "~>"
67
108
  - !ruby/object:Gem::Version
68
- version: '3.0'
109
+ version: '3.3'
69
110
  - !ruby/object:Gem::Dependency
70
111
  name: unparser
71
112
  requirement: !ruby/object:Gem::Requirement
@@ -87,39 +128,43 @@ executables: []
87
128
  extensions: []
88
129
  extra_rdoc_files: []
89
130
  files:
90
- - ".github/workflows/main.yml"
91
- - ".gitignore"
92
131
  - ".rspec"
93
132
  - ".rubocop.yml"
94
- - Gemfile
133
+ - CHANGELOG.md
95
134
  - LICENSE.txt
96
135
  - README.md
97
136
  - lib/remote_ruby.rb
137
+ - lib/remote_ruby/adapter_builder.rb
138
+ - lib/remote_ruby/cache_adapter.rb
139
+ - lib/remote_ruby/caching_adapter.rb
98
140
  - lib/remote_ruby/code_templates/compiler/main.rb.erb
141
+ - lib/remote_ruby/compat_io_reader.rb
142
+ - lib/remote_ruby/compat_io_writer.rb
99
143
  - lib/remote_ruby/compiler.rb
100
144
  - lib/remote_ruby/connection_adapter.rb
101
- - lib/remote_ruby/connection_adapter/cache_adapter.rb
102
- - lib/remote_ruby/connection_adapter/caching_adapter.rb
103
- - lib/remote_ruby/connection_adapter/eval_adapter.rb
104
- - lib/remote_ruby/connection_adapter/local_stdin_adapter.rb
105
- - lib/remote_ruby/connection_adapter/ssh_stdin_adapter.rb
106
- - lib/remote_ruby/connection_adapter/stdin_process_adapter.rb
107
145
  - lib/remote_ruby/execution_context.rb
108
- - lib/remote_ruby/flavour.rb
109
- - lib/remote_ruby/flavour/rails_flavour.rb
146
+ - lib/remote_ruby/extensions.rb
110
147
  - lib/remote_ruby/locals_extractor.rb
111
- - lib/remote_ruby/runner.rb
148
+ - lib/remote_ruby/parser_factory.rb
149
+ - lib/remote_ruby/plugin.rb
150
+ - lib/remote_ruby/rails_plugin.rb
151
+ - lib/remote_ruby/remote_context.rb
152
+ - lib/remote_ruby/remote_error.rb
112
153
  - lib/remote_ruby/source_extractor.rb
113
- - lib/remote_ruby/stream_cacher.rb
114
- - lib/remote_ruby/unmarshaler.rb
154
+ - lib/remote_ruby/ssh_adapter.rb
155
+ - lib/remote_ruby/stream_prefixer.rb
156
+ - lib/remote_ruby/tee_writer.rb
157
+ - lib/remote_ruby/text_mode_adapter.rb
158
+ - lib/remote_ruby/text_mode_builder.rb
159
+ - lib/remote_ruby/tmp_file_adapter.rb
115
160
  - lib/remote_ruby/version.rb
116
- - remote_ruby.gemspec
117
161
  homepage: https://github.com/nu-hin/remote_ruby
118
162
  licenses:
119
163
  - MIT
120
164
  metadata:
121
165
  rubygems_mfa_required: 'true'
122
- post_install_message:
166
+ homepage_uri: https://github.com/nu-hin/remote_ruby
167
+ source_code_uri: https://github.com/nu-hin/remote_ruby
123
168
  rdoc_options: []
124
169
  require_paths:
125
170
  - lib
@@ -127,15 +172,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
127
172
  requirements:
128
173
  - - ">="
129
174
  - !ruby/object:Gem::Version
130
- version: '2.6'
175
+ version: '2.7'
131
176
  required_rubygems_version: !ruby/object:Gem::Requirement
132
177
  requirements:
133
178
  - - ">="
134
179
  - !ruby/object:Gem::Version
135
180
  version: '0'
136
181
  requirements: []
137
- rubygems_version: 3.5.16
138
- signing_key:
182
+ rubygems_version: 3.6.2
139
183
  specification_version: 4
140
184
  summary: Execute Ruby code on the remote servers.
141
185
  test_files: []
@@ -1,26 +0,0 @@
1
- name: "Lint & Test"
2
- on: [push, pull_request]
3
- jobs:
4
- rubocop:
5
- runs-on: ubuntu-latest
6
- steps:
7
- - uses: actions/checkout@v2
8
- - uses: ruby/setup-ruby@v1
9
- with:
10
- ruby-version: 3.0.2
11
- bundler-cache: true
12
- - run: bundle exec rubocop
13
- rspec:
14
- strategy:
15
- matrix:
16
- os: [ubuntu-latest, macos-latest]
17
- ruby: [2.6, 2.7, 3.2, head]
18
- runs-on: ${{ matrix.os }}
19
- steps:
20
- - uses: actions/checkout@v2
21
- - uses: ruby/setup-ruby@v1
22
- with:
23
- ruby-version: ${{ matrix.ruby }}
24
- bundler-cache: true
25
- - run: bundle exec rspec
26
- - uses: coverallsapp/github-action@v2
data/.gitignore DELETED
@@ -1,17 +0,0 @@
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/Gemfile DELETED
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
-
7
- gemspec
8
-
9
- group :development do
10
- gem 'rubocop', '~> 1.23'
11
- gem 'yard', '~> 0.9'
12
- end
13
-
14
- group :development, :test do
15
- gem 'byebug', '~> 11.1'
16
- gem 'pry-byebug', '~> 3.9'
17
- gem 'rspec', '~> 3.10'
18
- end
19
-
20
- group :test do
21
- gem 'simplecov', '~> 0.22'
22
- gem 'simplecov-lcov', '~> 0.8'
23
- end
@@ -1,41 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RemoteRuby
4
- # An adapter which takes stdout and stderr from files and ignores
5
- # all stdin. Only used to read from cache.
6
- class CacheAdapter < ConnectionAdapter
7
- def initialize(connection_name:, cache_path:)
8
- super
9
- @cache_path = cache_path
10
- @connection_name = connection_name
11
- end
12
-
13
- def connection_name
14
- "[CACHE] #{@connection_name}"
15
- end
16
-
17
- def open(_code)
18
- stdout = File.open(stdout_file_path, 'r')
19
- stderr = File.open(stderr_file_path, 'r')
20
-
21
- yield stdout, stderr
22
- ensure
23
- stderr.close unless stderr.closed?
24
- stdout.close unless stdout.closed?
25
- end
26
-
27
- private
28
-
29
- attr_reader :cache_path
30
-
31
- def stdout_file_path
32
- fp = "#{cache_path}.stdout"
33
- File.exist?(fp) ? fp : File::NULL
34
- end
35
-
36
- def stderr_file_path
37
- fp = "#{cache_path}.stderr"
38
- File.exist?(fp) ? fp : File::NULL
39
- end
40
- end
41
- end
@@ -1,96 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RemoteRuby
4
- # An adapter to expecute Ruby code in the current process in an isolated
5
- # scope
6
- class EvalAdapter < ConnectionAdapter
7
- attr_reader :async, :working_dir
8
-
9
- def initialize(working_dir: Dir.pwd, async: false)
10
- super
11
- @async = async
12
- @working_dir = working_dir
13
- end
14
-
15
- def connection_name
16
- ''
17
- end
18
-
19
- def open(code, &block)
20
- if async
21
- run_async(code, &block)
22
- else
23
- run_sync(code, &block)
24
- end
25
- end
26
-
27
- private
28
-
29
- def run_code(code)
30
- binder = Object.new
31
-
32
- Dir.chdir(working_dir) do
33
- binder.instance_eval(code)
34
- end
35
- end
36
-
37
- def run_sync(code)
38
- with_stringio do |tmp_stdout, tmp_stderr|
39
- with_tmp_streams(tmp_stdout, tmp_stderr) do
40
- run_code(code)
41
- end
42
-
43
- tmp_stdout.close_write
44
- tmp_stderr.close_write
45
- tmp_stdout.rewind
46
- tmp_stderr.rewind
47
- yield tmp_stdout, tmp_stderr
48
- end
49
- end
50
-
51
- def run_async(code)
52
- with_pipes do |out_read, out_write, err_read, err_write|
53
- Thread.new do
54
- with_tmp_streams(out_write, err_write) do
55
- run_code(code)
56
- end
57
-
58
- out_write.close
59
- err_write.close
60
- end
61
-
62
- yield out_read, err_read
63
- end
64
- end
65
-
66
- def with_pipes
67
- out_read, out_write = IO.pipe
68
- err_read, err_write = IO.pipe
69
- yield out_read, out_write, err_read, err_write
70
- ensure
71
- out_read.close
72
- err_read.close
73
- end
74
-
75
- def with_stringio
76
- out = StringIO.new
77
- err = StringIO.new
78
-
79
- yield out, err
80
- ensure
81
- out.close
82
- err.close
83
- end
84
-
85
- def with_tmp_streams(out, err)
86
- old_stdout = $stdout
87
- old_stderr = $stderr
88
- $stdout = out
89
- $stderr = err
90
- yield
91
- ensure
92
- $stdout = old_stdout
93
- $stderr = old_stderr
94
- end
95
- end
96
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RemoteRuby
4
- # An adapter to expecute Ruby code on the local macine
5
- # inside a specified directory
6
- class LocalStdinAdapter < ::RemoteRuby::StdinProcessAdapter
7
- attr_reader :working_dir, :bundler
8
-
9
- def initialize(working_dir: '.', bundler: false)
10
- super
11
- @working_dir = working_dir
12
- @bundler = bundler
13
- end
14
-
15
- def connection_name
16
- working_dir
17
- end
18
-
19
- private
20
-
21
- def command
22
- if bundler
23
- "cd \"#{working_dir}\" && bundle exec ruby"
24
- else
25
- "cd \"#{working_dir}\" && ruby"
26
- end
27
- end
28
- end
29
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RemoteRuby
4
- # An adapter to execute Ruby code on the remote server via SSH
5
- class SSHStdinAdapter < StdinProcessAdapter
6
- attr_reader :server, :working_dir, :user, :key_file, :bundler
7
-
8
- def initialize(server:, working_dir: '~', user: nil, key_file: nil, bundler: false)
9
- super
10
- @working_dir = working_dir
11
- @server = user.nil? ? server : "#{user}@#{server}"
12
- @user = user
13
- @key_file = key_file
14
- @bundler = bundler
15
- end
16
-
17
- def connection_name
18
- "#{server}:#{working_dir}"
19
- end
20
-
21
- private
22
-
23
- def command
24
- command = 'ssh'
25
- command = "#{command} -i #{key_file}" if key_file
26
-
27
- if bundler
28
- "#{command} #{server} \"cd #{working_dir} && bundle exec ruby\""
29
- else
30
- "#{command} #{server} \"cd #{working_dir} && ruby\""
31
- end
32
- end
33
- end
34
- end
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'open3'
4
-
5
- module RemoteRuby
6
- # Base class for adapters which launch an external process to execute
7
- # Ruby code and send the code to its standard input.
8
- class StdinProcessAdapter < ::RemoteRuby::ConnectionAdapter
9
- include Open3
10
-
11
- def open(code)
12
- result = nil
13
-
14
- popen3(command) do |stdin, stdout, stderr, wait_thr|
15
- stdin.write(code)
16
- stdin.close
17
-
18
- yield stdout, stderr
19
-
20
- result = wait_thr.value
21
- end
22
-
23
- return if result.success?
24
-
25
- raise "Remote connection exited with code #{result}"
26
- end
27
-
28
- protected
29
-
30
- # Command to run an external process. Override in a child class.
31
- def command
32
- raise NotImplementedError
33
- end
34
- end
35
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RemoteRuby
4
- # Base class for Flavours: addons to execution context to insert additonal
5
- # code to the generated remote code.
6
- class Flavour
7
- def self.build_flavours(args = {})
8
- res = []
9
-
10
- {
11
- rails: RemoteRuby::RailsFlavour
12
- }.each do |name, klass|
13
- options = args.delete(name)
14
-
15
- res << klass.new(**options) if options
16
- end
17
-
18
- res
19
- end
20
-
21
- def initialize(**args); end
22
-
23
- def code_header; end
24
- end
25
- end
26
-
27
- require 'remote_ruby/flavour/rails_flavour'
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'colorize'
4
-
5
- require 'remote_ruby/unmarshaler'
6
-
7
- module RemoteRuby
8
- # Runner class is responsible for running a prepared Ruby code with given
9
- # connection adapter, reading output and unmarshalling result and local
10
- # variables values.
11
- class Runner
12
- def initialize(code:, adapter:, out_stream: $stdout, err_stream: $stderr)
13
- @code = code
14
- @adapter = adapter
15
- @out_stream = out_stream
16
- @err_stream = err_stream
17
- end
18
-
19
- def run
20
- locals = nil
21
-
22
- adapter.open(code) do |stdout, stderr|
23
- out_thread = read_stream(stdout, out_stream, :green)
24
- err_thread = read_stream(stderr, err_stream, :red)
25
- [out_thread, err_thread].each(&:join)
26
- locals = out_thread[:locals]
27
- end
28
-
29
- { result: locals[:__return_val__], locals: locals }
30
- end
31
-
32
- private
33
-
34
- attr_reader :code, :adapter, :out_stream, :err_stream
35
-
36
- def read_stream(read_from, write_to, color)
37
- Thread.new do
38
- until read_from.eof?
39
- line = read_from.readline
40
-
41
- if line.start_with?('%%%MARSHAL')
42
- Thread.current[:locals] ||= unmarshal(read_from)
43
- else
44
- write_to.puts "#{adapter.connection_name.send(color)}>\t#{line}"
45
- end
46
- end
47
- end
48
- end
49
-
50
- def unmarshal(stdout)
51
- unmarshaler = RemoteRuby::Unmarshaler.new(stdout)
52
- unmarshaler.unmarshal
53
- end
54
- end
55
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RemoteRuby
4
- # Decorates the source stream and writes to the cache stream as
5
- # the source is being read
6
- class StreamCacher
7
- def initialize(source_stream, cache_stream)
8
- @source_stream = source_stream
9
- @cache_stream = cache_stream
10
- end
11
-
12
- def read(*args)
13
- res = source_stream.read(*args)
14
- cache_stream.write(res)
15
- res
16
- end
17
-
18
- def readline
19
- res = source_stream.readline
20
- cache_stream.write(res)
21
- res
22
- end
23
-
24
- def eof?
25
- source_stream.eof?
26
- end
27
-
28
- def close
29
- source_stream.close
30
- end
31
-
32
- private
33
-
34
- attr_reader :source_stream, :cache_stream
35
- end
36
- end
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RemoteRuby
4
- # Unmarshals variables from given stream
5
- class Unmarshaler
6
- UnmarshalError = Class.new(StandardError)
7
-
8
- def initialize(stream, terminator = nil)
9
- @stream = stream
10
- @terminator = terminator
11
- end
12
-
13
- def unmarshal
14
- res = {}
15
-
16
- until stream.eof?
17
- line = stream.readline
18
-
19
- break if terminator && line == terminator
20
-
21
- var = read_var(line)
22
- res[var.first] = var[1]
23
- end
24
-
25
- res
26
- end
27
-
28
- private
29
-
30
- attr_reader :stream, :terminator
31
-
32
- def read_var(line)
33
- varname, length = read_var_header(line)
34
- data = read_var_data(length)
35
- [varname.to_sym, data]
36
- rescue ArgumentError => e
37
- raise UnmarshalError,
38
- "Could not resolve type for #{varname} variable: #{e.message}"
39
- rescue TypeError
40
- raise UnmarshalError, 'Incorrect data format'
41
- end
42
-
43
- def read_var_header(line)
44
- varname, length = line.split(':')
45
-
46
- raise UnmarshalError, "Incorrect header '#{line}'" \
47
- if varname.nil? || length.nil?
48
-
49
- [varname, length]
50
- end
51
-
52
- # rubocop:disable Security/MarshalLoad
53
- def read_var_data(length)
54
- data = stream.read(length.to_i)
55
- Marshal.load(data)
56
- end
57
- # rubocop:enable Security/MarshalLoad
58
- end
59
- end