rssh 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ require 'rake_ext'
2
+
3
+ project(
4
+ name: "rssh",
5
+ version: "0.0.1",
6
+ summary: "ruby ssh and io",
7
+
8
+ author: "Alexey Petrushin",
9
+ homepage: "http://github.com/alexeypetrushin/rssh"
10
+ )
@@ -0,0 +1,11 @@
1
+ class Ssh
2
+ attr_reader :config
3
+
4
+
5
+ def setstat(path, options)
6
+ remote do |sh, sftp|
7
+ sftp.setstat! path, options
8
+ end
9
+ end
10
+
11
+ end
@@ -0,0 +1,18 @@
1
+ raise 'ruby 1.9.2 or higher required!' if RUBY_VERSION < '1.9.2'
2
+
3
+ require 'rsh/gems'
4
+
5
+ require 'open3'
6
+
7
+ require 'net/ssh'
8
+ require 'net/sftp'
9
+
10
+ %w(
11
+ support
12
+
13
+ drivers/abstract
14
+ drivers/local
15
+ drivers/ssh
16
+
17
+ box
18
+ ).each{|f| require "rsh/#{f}"}
@@ -0,0 +1,145 @@
1
+ module Rsh
2
+ class Box
3
+ attr_accessor :options
4
+
5
+ def initialize options = {}
6
+ @options = options
7
+ options[:host] ||= 'localhost'
8
+ end
9
+
10
+ def driver
11
+ unless @driver
12
+ klass = options[:host] == 'localhost' ? Drivers::Local : Drivers::Ssh
13
+ @driver = klass.new options
14
+ end
15
+ @driver
16
+ end
17
+
18
+ def local_driver
19
+ @local_driver ||= Drivers::Local.new
20
+ end
21
+
22
+ def bulk &b
23
+ driver.bulk &b
24
+ end
25
+
26
+ def upload_file from_local_path, to_remote_path, options = {}
27
+ bulk do
28
+ raise "file '#{from_local_path}' not exists!" unless local_driver.file_exist? from_local_path
29
+ if driver.file_exist?(to_remote_path)
30
+ if options[:override]
31
+ driver.remove_file to_remote_path
32
+ else
33
+ raise "file '#{to_remote_path}' already exists!"
34
+ end
35
+ end
36
+ driver.upload_file from_local_path, to_remote_path
37
+ end
38
+ end
39
+
40
+ def download_file from_remote_path, to_local_path, options = {}
41
+ bulk do
42
+ raise "file #{from_remote_path} not exists!" unless driver.file_exist?(from_remote_path)
43
+ if local_driver.file_exist? to_local_path
44
+ if options[:override]
45
+ local_driver.remove_file to_local_path
46
+ else
47
+ raise "file #{to_local_path} already exists!"
48
+ end
49
+ end
50
+ driver.download_file from_remote_path, to_local_path
51
+ end
52
+ end
53
+
54
+ def remove_file remote_file_path
55
+ bulk do
56
+ raise "file #{remote_file_path} not exists!" unless driver.file_exist? remote_file_path
57
+ driver.remove_file remote_file_path
58
+ end
59
+ end
60
+
61
+ def exist? remote_path
62
+ driver.exist? remote_path
63
+ end
64
+
65
+ def file_exist? remote_file_path
66
+ driver.file_exist? remote_file_path
67
+ end
68
+
69
+ def remove_directory remote_directory_path
70
+ bulk do
71
+ raise "directory #{remote_directory_path} not exists!" unless driver.directory_exist? remote_directory_path
72
+ driver.remove_directory remote_directory_path
73
+ end
74
+ end
75
+
76
+ def create_directory remote_path, options = {}
77
+ bulk do
78
+ if driver.directory_exist?(remote_path)
79
+ if options[:override]
80
+ driver.remove_directory remote_path
81
+ else
82
+ raise "directory '#{remote_path}' already exists!"
83
+ end
84
+ end
85
+ driver.create_directory remote_path
86
+ end
87
+ end
88
+
89
+ def directory_exist? remote_path
90
+ driver.directory_exist? remote_path
91
+ end
92
+
93
+ def upload_directory from_local_path, to_remote_path, options = {}
94
+ bulk do
95
+ raise "directory '#{from_local_path}' not exists!" unless local_driver.directory_exist? from_local_path
96
+ if driver.directory_exist?(to_remote_path)
97
+ if options[:override]
98
+ driver.remove_directory to_remote_path
99
+ else
100
+ raise "directory '#{to_remote_path}' already exists!"
101
+ end
102
+ end
103
+ driver.upload_directory from_local_path, to_remote_path
104
+ end
105
+ end
106
+
107
+ def download_directory from_remote_path, to_local_path, options = {}
108
+ bulk do
109
+ raise "directory #{from_remote_path} not exists!" unless driver.directory_exist?(from_remote_path)
110
+ if local_driver.directory_exist? to_local_path
111
+ if options[:override]
112
+ local_driver.remove_directory to_local_path
113
+ else
114
+ raise "directory #{to_local_path} already exists!"
115
+ end
116
+ end
117
+ driver.download_directory from_remote_path, to_local_path
118
+ end
119
+ end
120
+
121
+ def with_tmp_dir &block
122
+ bulk do
123
+ tmp_dir = driver.generate_tmp_dir_name
124
+ begin
125
+ remove_directory tmp_dir if directory_exist? tmp_dir
126
+ create_directory tmp_dir
127
+ block.call
128
+ ensure
129
+ remove_directory tmp_dir if directory_exist? tmp_dir
130
+ end
131
+ end
132
+ end
133
+
134
+ def bash cmd
135
+ code, stdout, stderr = driver.exec cmd
136
+ raise "can't execute '#{cmd}'!" unless code == 0
137
+ return stdout, stderr
138
+ end
139
+
140
+ protected
141
+ def method_missing m, *a, &b
142
+ driver.send m, *a, &b
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,19 @@
1
+ module Rsh
2
+ module Drivers
3
+ class Abstract
4
+ attr_reader :options
5
+
6
+ def initialize options = {}
7
+ @options = options
8
+ end
9
+
10
+ def bulk &b
11
+ b.call
12
+ end
13
+
14
+ def generate_tmp_dir_name
15
+ "/tmp/ssh_tmp_dir_#{rand(10**6)}"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,52 @@
1
+ module Rsh
2
+ module Drivers
3
+ class Local < Abstract
4
+ def bulk &b
5
+ b.call
6
+ end
7
+
8
+ def upload_file from_local_path, to_remote_path
9
+ FileUtils.copy from_local_path, to_remote_path
10
+ end
11
+
12
+ def download_file from_remote_path, to_local_path
13
+ FileUtils.copy from_remote_path, to_local_path
14
+ end
15
+
16
+ def exist? remote_file_path
17
+ File.exist? remote_file_path
18
+ end
19
+
20
+ alias_method :directory_exist?, :exist?
21
+ alias_method :file_exist?, :exist?
22
+
23
+ def remove_file remote_file_path
24
+ File.delete remote_file_path
25
+ end
26
+
27
+ def create_directory path
28
+ Dir.mkdir path
29
+ end
30
+
31
+ def remove_directory path
32
+ FileUtils.rm_r path
33
+ end
34
+
35
+ def upload_directory from_local_path, to_remote_path
36
+ FileUtils.cp_r from_local_path, to_remote_path
37
+ end
38
+
39
+ def download_directory from_remote_path, to_local_path
40
+ FileUtils.cp_r from_remote_path, to_local_path
41
+ end
42
+
43
+ def exec command
44
+ code, stdout, stderr = Open3.popen3 command do |stdin, stdout, stderr, waitth|
45
+ [waitth.value.to_i, stdout.read, stderr.read]
46
+ end
47
+
48
+ return code, stdout, stderr
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,145 @@
1
+ module Rsh
2
+ module Drivers
3
+ class Ssh < Abstract
4
+ def initialize options = {}
5
+ super
6
+ raise "ssh options not provided!" unless options[:ssh]
7
+ raise "invalid ssh options!" unless options[:ssh].is_a?(Hash)
8
+ end
9
+
10
+ def upload_file from_local_path, to_remote_path
11
+ remote do |ssh, sftp|
12
+ sftp.upload! from_local_path, to_remote_path
13
+ end
14
+ end
15
+
16
+ def download_file from_remote_path, to_local_path
17
+ File.open to_local_path, "w" do |out|
18
+ remote do |ssh, sftp|
19
+ sftp.download! from_remote_path, out #, :recursive => true
20
+ end
21
+ end
22
+ end
23
+
24
+ def exist? remote_file_path
25
+ remote do |ssh, sftp|
26
+ begin
27
+ fattrs = sftp.stat! remote_file_path
28
+ fattrs.directory? or fattrs.file? or fattrs.symlink?
29
+ rescue Net::SFTP::StatusException
30
+ false
31
+ end
32
+ end
33
+ end
34
+ alias_method :directory_exist?, :exist?
35
+ alias_method :file_exist?, :exist?
36
+
37
+ def remove_file remote_file_path
38
+ remote do |ssh, sftp|
39
+ sftp.remove! remote_file_path
40
+ end
41
+ end
42
+
43
+ def exec command
44
+ remote do |ssh, sftp|
45
+ # somehow net-ssh doesn't executes ~/.profile, so we need to execute it manually
46
+ # command = ". ~/.profile && #{command}"
47
+
48
+ stdout, stderr, code, signal = hacked_exec! ssh, command
49
+
50
+ return code, stdout, stderr
51
+ end
52
+ end
53
+
54
+ def open_connection
55
+ ssh_options = self.options[:ssh].clone
56
+ host = options[:host] || raise('host not provided!')
57
+ user = ssh_options.delete(:user) || raise('user not provied!')
58
+ @ssh = Net::SSH.start(host, user, ssh_options)
59
+ @sftp = @ssh.sftp.connect
60
+ end
61
+
62
+ def close_connection
63
+ @ssh.close
64
+ # @sftp.close not needed
65
+ @ssh, @sftp = nil
66
+ end
67
+
68
+ def bulk &block
69
+ remote &block
70
+ end
71
+
72
+ def create_directory path
73
+ remote do |ssh, sftp|
74
+ sftp.mkdir! path
75
+ # exec "mkdir #{path}"
76
+ end
77
+ end
78
+
79
+ def remove_directory path
80
+ exec "rm -r #{path}"
81
+ end
82
+
83
+ def upload_directory from_local_path, to_remote_path
84
+ remote do |ssh, sftp|
85
+ sftp.upload! from_local_path, to_remote_path
86
+ end
87
+ end
88
+
89
+ def download_directory from_remote_path, to_local_path
90
+ remote do |ssh, sftp|
91
+ sftp.download! from_remote_path, to_local_path, :recursive => true
92
+ end
93
+ end
94
+
95
+ protected
96
+ # taken from here http://stackoverflow.com/questions/3386233/how-to-get-exit-status-with-rubys-netssh-library/3386375#3386375
97
+ def hacked_exec!(ssh, command, &block)
98
+ stdout_data = ""
99
+ stderr_data = ""
100
+ exit_code = nil
101
+ exit_signal = nil
102
+
103
+ channel = ssh.open_channel do |channel|
104
+ channel.exec(command) do |ch, success|
105
+ raise "could not execute command: #{command.inspect}" unless success
106
+
107
+ channel.on_data{|ch2, data| stdout_data << data}
108
+ channel.on_extended_data{|ch2, type, data| stderr_data << data}
109
+ channel.on_request("exit-status"){|ch,data| exit_code = data.read_long}
110
+ channel.on_request("exit-signal"){|ch, data| exit_signal = data.read_long}
111
+ end
112
+ end
113
+
114
+ channel.wait
115
+
116
+ [stdout_data, stderr_data, exit_code, exit_signal]
117
+ end
118
+
119
+ def remote(&block)
120
+ if @ssh
121
+ block.call @ssh, @sftp
122
+ else
123
+ # Rails.logger.info "Connecting to remote Hadoop #{options[:user]}@#{options[:host]}"
124
+ begin
125
+ open_connection
126
+ block.call @ssh, @sftp
127
+ ensure
128
+ close_connection
129
+ end
130
+
131
+ # Net::SSH.start options[:host], options[:user], :config => true do |ssh|
132
+ # ssh.sftp.connect do |sftp|
133
+ # begin
134
+ # @ssh, @sftp = ssh, sftp
135
+ # block.call @ssh, @sftp
136
+ # ensure
137
+ # @ssh, @sftp = nil
138
+ # end
139
+ # end
140
+ # end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,2 @@
1
+ gem 'net-ssh'
2
+ gem 'net-sftp'
@@ -0,0 +1,30 @@
1
+ require 'fileutils'
2
+ require 'net/ssh'
3
+ require 'net/sftp'
4
+
5
+ # class File
6
+ # class << self
7
+ # def ensure_dir_exist directory, options = {}
8
+ # options = options.symbolize_keys
9
+ # clean = options.delete :clean
10
+ # raise "unsupported options #{options.keys}!" unless options.empty?
11
+ #
12
+ # FileUtils.rm_r directory, :force => true if clean and File.exist?(directory)
13
+ # FileUtils.mkdir_p directory
14
+ # end
15
+ #
16
+ # def copy_dir_content from, to
17
+ # Dir.glob "#{from}/*" do |item|
18
+ # FileUtils.cp_r item, to
19
+ # end
20
+ # end
21
+ #
22
+ # def copy_dir from, to
23
+ # FileUtils.cp_r from, to
24
+ # end
25
+ #
26
+ # def remove_dir dir
27
+ # FileUtils.rm_r dir
28
+ # end
29
+ # end
30
+ # end
@@ -0,0 +1,4 @@
1
+ TODO
2
+ - introduce Entity/Dir/File (the same as in Rush)
3
+ - allow to move files between any Boxes, not only between local and remote.
4
+ - add support for moving dirs.
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for 'abstract driver' do
4
+ dir = "#{File.dirname __FILE__}/abstract_driver"
5
+ with_tmp_spec_dir dir, before: :each
6
+
7
+ describe "io" do
8
+ before :each do
9
+ @local_dir = spec_dir
10
+ @remote_dir = @driver.generate_tmp_dir_name
11
+
12
+ @driver.remove_directory @remote_dir if @driver.directory_exist? @remote_dir
13
+ @driver.create_directory @remote_dir
14
+ end
15
+
16
+ after :each do
17
+ @driver.remove_directory @remote_dir if @driver.directory_exist? @remote_dir
18
+ end
19
+
20
+ describe "files" do
21
+ before :each do
22
+ @local_file = "#{@local_dir}/local_file"
23
+ @check_file = "#{@local_dir}/check_file"
24
+ @remote_file = "#{@remote_dir}/remote_file"
25
+ end
26
+
27
+ it "file_exist?" do
28
+ @driver.file_exist?(@remote_file).should be_false
29
+ @driver.upload_file(@local_file, @remote_file)
30
+ @driver.file_exist?(@remote_file).should be_true
31
+ end
32
+
33
+ it "upload & download file" do
34
+ @driver.upload_file(@local_file, @remote_file)
35
+ @driver.file_exist?(@remote_file).should be_true
36
+
37
+ @driver.download_file(@remote_file, @check_file)
38
+ File.read(@local_file).should == File.read(@check_file)
39
+ end
40
+
41
+ it "remove_file" do
42
+ @driver.upload_file(@local_file, @remote_file)
43
+ @driver.file_exist?(@remote_file).should be_true
44
+ @driver.remove_file(@remote_file)
45
+ @driver.file_exist?(@remote_file).should be_false
46
+ end
47
+ end
48
+
49
+ describe 'directories' do
50
+ before :each do
51
+ @from_local, @remote_path, @to_local = "#{@local_dir}/dir", "#{@remote_dir}/upload", "#{@local_dir}/download"
52
+ end
53
+
54
+ it "directory_exist?, create_directory, remove_directory" do
55
+ dir = "#{@remote_dir}/some_dir"
56
+ @driver.directory_exist?(dir).should be_false
57
+ @driver.create_directory(dir)
58
+ @driver.directory_exist?(dir).should be_true
59
+ @driver.remove_directory(dir)
60
+ @driver.directory_exist?(dir).should be_false
61
+ end
62
+
63
+ it "upload_directory & download_directory" do
64
+ upload_path_check = "#{@remote_path}/dir2/file"
65
+ @driver.file_exist?(upload_path_check).should be_false
66
+ @driver.upload_directory(@from_local, @remote_path)
67
+ @driver.file_exist?(upload_path_check).should be_true
68
+
69
+ download_path_check = "#{@to_local}/dir2/file"
70
+ File.exist?(download_path_check).should be_false
71
+ @driver.download_directory(@remote_path, @to_local)
72
+ File.exist?(download_path_check).should be_true
73
+ end
74
+ end
75
+ end
76
+
77
+ describe "shell" do
78
+ it 'exec' do
79
+ @driver.exec("echo 'ok'").should == [0, "ok\n", ""]
80
+ end
81
+ end
82
+ end
File without changes
@@ -0,0 +1 @@
1
+ some content
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rsh::Box do
4
+ with_tmp_spec_dir before: :each
5
+
6
+ before :each do
7
+ @box = Rsh::Box.new
8
+
9
+ @local_dir = spec_dir
10
+ @remote_dir = @box.generate_tmp_dir_name
11
+
12
+ @box.remove_directory @remote_dir if @box.directory_exist? @remote_dir
13
+ @box.create_directory @remote_dir
14
+ end
15
+
16
+ after :each do
17
+ @box.remove_directory @remote_dir if @box.directory_exist? @remote_dir
18
+ end
19
+
20
+ describe "io" do
21
+ describe "files" do
22
+ before :each do
23
+ @local_file = "#{@local_dir}/local_file"
24
+ @check_file = "#{@local_dir}/check_file"
25
+ @remote_file = "#{@remote_dir}/remote_file"
26
+ end
27
+
28
+ it "file_exist?" do
29
+ @box.file_exist?(@remote_file).should be_false
30
+ @box.upload_file(@local_file, @remote_file)
31
+ @box.file_exist?(@remote_file).should be_true
32
+ end
33
+
34
+ it "upload_file" do
35
+ @box.upload_file(@local_file, @remote_file)
36
+ @box.file_exist?(@remote_file).should be_true
37
+
38
+ lambda{@box.upload_file(@local_file, @remote_file)}.should raise_error(/exists/)
39
+
40
+ # upload with override
41
+ @box.upload_file(@local_file, @remote_file, override: true)
42
+ @box.file_exist?(@remote_file).should be_true
43
+ end
44
+
45
+ it "download_file" do
46
+ lambda{@box.download_file(@remote_file, @check_file)}.should raise_error(/not exists/)
47
+ @box.upload_file(@local_file, @remote_file)
48
+ @box.download_file(@remote_file, @check_file)
49
+ File.read(@local_file).should == File.read(@check_file)
50
+ end
51
+
52
+ it "remove_file" do
53
+ lambda{@box.remove_file(@remote_file)}.should raise_error(/not exists/)
54
+ @box.upload_file(@local_file, @remote_file)
55
+ @box.file_exist?(@remote_file).should be_true
56
+ @box.remove_file(@remote_file)
57
+ @box.file_exist?(@remote_file).should be_false
58
+ end
59
+ end
60
+
61
+ describe 'directories' do
62
+ before :each do
63
+ @from_local, @remote_path, @to_local = "#{@local_dir}/dir", "#{@remote_dir}/upload", "#{@local_dir}/download"
64
+ end
65
+
66
+ it "directory_exist?" do
67
+ @box.file_exist?(@remote_path).should be_false
68
+ @box.upload_directory(@from_local, @remote_path)
69
+ @box.file_exist?(@remote_path).should be_true
70
+ end
71
+
72
+ it "upload_directory" do
73
+ @box.upload_directory(@from_local, @remote_path)
74
+ @box.directory_exist?(@remote_path).should be_true
75
+
76
+ lambda{@box.upload_directory(@from_local, @remote_path)}.should raise_error(/exists/)
77
+
78
+ # upload with override
79
+ @box.upload_directory(@from_local, @remote_path, override: true)
80
+ @box.directory_exist?(@remote_path).should be_true
81
+ end
82
+
83
+ it "download_directory" do
84
+ lambda{@box.download_directory(@remote_path, @to_local)}.should raise_error(/not exists/)
85
+ @box.upload_directory(@from_local, @remote_path)
86
+ @box.download_directory(@remote_path, @to_local)
87
+ File.exist?("#{@to_local}/dir2/file").should be_true
88
+ end
89
+
90
+ it "remove_directory" do
91
+ lambda{@box.remove_directory(@remote_path)}.should raise_error(/not exists/)
92
+ @box.upload_directory(@from_local, @remote_path)
93
+ @box.directory_exist?(@remote_path).should be_true
94
+ @box.remove_directory(@remote_path)
95
+ @box.directory_exist?(@remote_path).should be_false
96
+ end
97
+ end
98
+ end
99
+
100
+ describe "shell" do
101
+ it 'exec' do
102
+ @box.bash("echo 'ok'").should == ["ok\n", ""]
103
+ end
104
+ end
105
+ end
File without changes
@@ -0,0 +1 @@
1
+ some content
@@ -0,0 +1,4 @@
1
+ :ssh:
2
+ :host: some_host.com
3
+ :user: some_user
4
+ :password: some_password
@@ -0,0 +1,5 @@
1
+ :remote_driver:
2
+ :host: universal.4ire.net
3
+ :ssh:
4
+ :user: root
5
+ :password: privateprivate
@@ -0,0 +1,9 @@
1
+ require 'abstract_driver'
2
+
3
+ describe Rsh::Drivers::Local do
4
+ it_should_behave_like "abstract driver"
5
+
6
+ before :each do
7
+ @driver = Rsh::Drivers::Local.new
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ require 'rspec_ext'
2
+ require 'rsh'
3
+
4
+ rspec do
5
+ def config
6
+ dir = File.dirname __FILE__
7
+ config_file_path = "#{dir}/config.yml"
8
+ raise "no config '#{config_file_path}'!" unless File.exist? config_file_path
9
+ @config ||= YAML.load_file config_file_path
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ require 'abstract_driver'
2
+ require 'yaml'
3
+
4
+ describe Rsh::Drivers::Ssh do
5
+ it_should_behave_like "abstract driver"
6
+
7
+ before :each do
8
+ @driver = Rsh::Drivers::Ssh.new config[:remote_driver]
9
+ @driver.open_connection
10
+ end
11
+
12
+ after :each do
13
+ @driver.close_connection
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rssh
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Alexey Petrushin
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-01-30 00:00:00 +03:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description:
22
+ email:
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - Rakefile
31
+ - readme.md
32
+ - lib/old/ssh.rb
33
+ - lib/rsh/box.rb
34
+ - lib/rsh/drivers/abstract.rb
35
+ - lib/rsh/drivers/local.rb
36
+ - lib/rsh/drivers/ssh.rb
37
+ - lib/rsh/gems.rb
38
+ - lib/rsh/support.rb
39
+ - lib/rsh.rb
40
+ - spec/abstract_driver/dir/dir2/file
41
+ - spec/abstract_driver/local_file
42
+ - spec/abstract_driver.rb
43
+ - spec/box_spec/dir/dir2/file
44
+ - spec/box_spec/local_file
45
+ - spec/box_spec.rb
46
+ - spec/config.example.yml
47
+ - spec/config.yml
48
+ - spec/local_driver_spec.rb
49
+ - spec/spec_helper.rb
50
+ - spec/ssh_driver_spec.rb
51
+ has_rdoc: true
52
+ homepage: http://github.com/alexeypetrushin/rssh
53
+ licenses: []
54
+
55
+ post_install_message:
56
+ rdoc_options: []
57
+
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.3.7
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: ruby ssh and io
83
+ test_files: []
84
+