vos 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -2,8 +2,8 @@ require 'rake_ext'
2
2
 
3
3
  project(
4
4
  name: "vos",
5
- version: "0.0.4",
6
- summary: "Tiny wrapper over Net::SSH/SFTP + small rake addon for cluster configuration management",
5
+ version: "0.1.0",
6
+ summary: "Virtual Operating System",
7
7
 
8
8
  author: "Alexey Petrushin",
9
9
  homepage: "http://github.com/alexeypetrushin/vos"
@@ -0,0 +1,31 @@
1
+ module Vos
2
+ class Box
3
+ module Marks
4
+ def mark key
5
+ ensure_mark_requrements!
6
+ file("#{marks_dir}/#{key}").create!
7
+ end
8
+
9
+ def has_mark? key
10
+ ensure_mark_requrements!
11
+ entry["#{marks_dir}/#{key}"].exist?
12
+ end
13
+
14
+ def clear_marks
15
+ bash "rm -r #{marks_dir}"
16
+ end
17
+
18
+ protected
19
+ def marks_dir
20
+ home "/.marks"
21
+ end
22
+
23
+ def ensure_mark_requrements!
24
+ unless @ensure_mark_requrements
25
+ self.dir(marks_dir).create
26
+ @ensure_mark_requrements = true
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,87 @@
1
+ module Vos
2
+ class Box
3
+ module Shell
4
+ def bash cmd, *args
5
+ self['/'].bash cmd, *args
6
+ end
7
+
8
+ def bash_without_path cmd, *args
9
+ check = args.shift if args.first.is_a?(Regexp)
10
+ options = args.last || {}
11
+
12
+ cmd = env cmd
13
+ code, stdout_and_stderr = open{driver.bash cmd}
14
+
15
+ unless code == 0
16
+ puts stdout_and_stderr
17
+ raise "can't execute '#{cmd}'!"
18
+ end
19
+
20
+ if check and (stdout_and_stderr !~ check)
21
+ puts stdout_and_stderr
22
+ raise "output not match with #{check.inspect}!"
23
+ end
24
+
25
+ stdout_and_stderr
26
+ end
27
+
28
+ def exec cmd
29
+ open{driver.exec(env(cmd))}
30
+ end
31
+
32
+ attr_writer :env
33
+ def env command_or_env_variables = nil, &block
34
+ @env ||= default_env
35
+
36
+ if block
37
+ before = env.clone
38
+ begin
39
+ if variables = command_or_env_variables
40
+ raise 'invalid arguments' unless variables.is_a? Hash
41
+ @env.merge! variables
42
+ end
43
+ block.call
44
+ ensure
45
+ @env = before
46
+ end
47
+ else
48
+ if command_or_env_variables == nil
49
+ @env
50
+ elsif command_or_env_variables.is_a? String
51
+ cmd = command_or_env_variables
52
+ env_str = env.to_a.collect{|k, v| "#{k}=#{v}"}.join(' ')
53
+ wrap_cmd env_str, cmd
54
+ elsif command_or_env_variables.is_a? Hash
55
+ variables = command_or_env_variables
56
+ @env.merge! variables
57
+ else
58
+ raise 'invalid arguments'
59
+ end
60
+ end
61
+ end
62
+ def default_env
63
+ {}
64
+ end
65
+ def wrap_cmd env_str, cmd
66
+ %(#{env_str}#{' && ' unless env_str.empty?}#{cmd})
67
+ end
68
+
69
+
70
+ def home path = nil
71
+ open{@home = bash('cd ~; pwd').gsub("\n", '')} unless @home
72
+ path ? self[@home][path] : self[@home]
73
+ end
74
+
75
+ # def generate_tmp_dir_name
76
+ # open do
77
+ # driver.generate_tmp_dir_name
78
+ # end
79
+ # end
80
+
81
+ # def inspect
82
+ # "<Box: #{options[:host]}>"
83
+ # end
84
+ # alias_method :to_s, :inspect
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,43 @@
1
+ module Vos
2
+ class Box
3
+ module Vfs
4
+ def open_fs &block
5
+ open &block
6
+ end
7
+
8
+ def to_entry
9
+ '/'.to_entry_on(self)
10
+ end
11
+
12
+ def to_file
13
+ to_entry.file
14
+ end
15
+
16
+ def to_dir
17
+ to_entry
18
+ end
19
+
20
+ # def [] path
21
+ # to_entry[path]
22
+ # end
23
+ # alias_method :/, :[]
24
+
25
+ %w(dir file entry entries [] / tmp).each do |m|
26
+ script = <<-RUBY
27
+ def #{m} *a, &b
28
+ to_entry.#{m} *a, &b
29
+ end
30
+ RUBY
31
+ eval script, binding, __FILE__, __LINE__
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ module Vfs
38
+ class Dir
39
+ def bash cmd, *args
40
+ storage.bash_without_path "cd #{path} && #{cmd}", *args
41
+ end
42
+ end
43
+ end
data/lib/vos/box.rb ADDED
@@ -0,0 +1,45 @@
1
+ module Vos
2
+ class Box
3
+ include Shell, Marks, Vfs
4
+
5
+ attr_accessor :options
6
+
7
+ def initialize options = {}
8
+ @options = options
9
+ options[:host] ||= 'localhost'
10
+ end
11
+
12
+
13
+ #
14
+ # driver
15
+ #
16
+ def driver
17
+ unless @driver
18
+ klass = options[:host] == 'localhost' ? Drivers::Local : Drivers::Ssh
19
+ @driver = klass.new options
20
+ end
21
+ @driver
22
+ end
23
+
24
+ def open &block
25
+ driver.open &block
26
+ end
27
+ def close
28
+ driver.close
29
+ end
30
+
31
+
32
+ #
33
+ # Micelaneous
34
+ #
35
+ def inspect
36
+ host = options[:host]
37
+ if host == 'localhost'
38
+ ''
39
+ else
40
+ host
41
+ end
42
+ end
43
+ alias_method :to_s, :inspect
44
+ end
45
+ end
@@ -1,4 +1,4 @@
1
- module Rsh
1
+ module Vos
2
2
  module Drivers
3
3
  class Abstract
4
4
  attr_reader :options
@@ -6,10 +6,6 @@ module Rsh
6
6
  def initialize options = {}
7
7
  @options = options
8
8
  end
9
-
10
- def generate_tmp_dir_name
11
- "/tmp/ssh_tmp_dir_#{rand(10**6)}"
12
- end
13
9
  end
14
10
  end
15
11
  end
@@ -0,0 +1,38 @@
1
+ require 'vfs/storages/local'
2
+
3
+ module Vos
4
+ module Drivers
5
+ class Local < Abstract
6
+ #
7
+ # Vfs
8
+ #
9
+ include Vfs::Storages::Local::LocalVfsHelper
10
+ def open &block
11
+ block.call self if block
12
+ end
13
+ alias_method :open_fs, :open
14
+ def close; end
15
+
16
+
17
+ #
18
+ # Shell
19
+ #
20
+ def exec command
21
+ code, stdout, stderr = Open3.popen3 command do |stdin, stdout, stderr, waitth|
22
+ [waitth.value.to_i, stdout.read, stderr.read]
23
+ end
24
+
25
+ return code, stdout, stderr
26
+ end
27
+
28
+
29
+ def bash command
30
+ code, stdout_and_stderr = Open3.popen2e command do |stdin, stdout_and_stderr, wait_thread|
31
+ [wait_thread.value.to_i, stdout_and_stderr.read]
32
+ end
33
+
34
+ return code, stdout_and_stderr
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,15 @@
1
+ shared_examples_for 'vos driver' do
2
+ describe "shell" do
3
+ it 'exec' do
4
+ @driver.open do |d|
5
+ d.exec("echo 'ok'").should == [0, "ok\n", ""]
6
+ end
7
+ end
8
+
9
+ it 'bash' do
10
+ @driver.open do |d|
11
+ d.bash("echo 'ok'").should == [0, "ok\n"]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,256 @@
1
+ require 'net/ssh'
2
+ require 'net/sftp'
3
+
4
+ module Vos
5
+ module Drivers
6
+ class Ssh < Abstract
7
+ module VfsStorage
8
+ #
9
+ # Attributes
10
+ #
11
+ def attributes path
12
+
13
+ stat = sftp.stat! fix_path(path)
14
+ attrs = {}
15
+ attrs[:file] = stat.file?
16
+ attrs[:dir] = stat.directory?
17
+ # stat.symlink?
18
+ attrs
19
+ rescue Net::SFTP::StatusException
20
+ {}
21
+ end
22
+
23
+ def set_attributes path, attrs
24
+ raise 'not supported'
25
+ end
26
+
27
+ #
28
+ # File
29
+ #
30
+ def read_file path, &block
31
+ sftp.file.open fix_path(path), 'r' do |is|
32
+ while buff = is.gets
33
+ block.call buff
34
+ end
35
+ end
36
+ end
37
+
38
+ def write_file path, append, &block
39
+ # there's no support for :append in Net::SFTP, so we just mimic it
40
+ if append
41
+ attrs = attributes(path)
42
+ data = if attrs
43
+ if attrs[:file]
44
+ os = ""
45
+ read_file(path){|buff| os << buff}
46
+ delete_file path
47
+ os
48
+ else
49
+ raise "can't append to dir!"
50
+ end
51
+ else
52
+ ''
53
+ end
54
+ write_file path, false do |writer|
55
+ writer.call data
56
+ block.call writer
57
+ end
58
+ else
59
+ sftp.file.open fix_path(path), 'w' do |os|
60
+ writer = -> buff {os.write buff}
61
+ block.call writer
62
+ end
63
+ end
64
+ end
65
+
66
+ def delete_file remote_file_path
67
+ sftp.remove! fix_path(remote_file_path)
68
+ end
69
+
70
+ # def move_file path
71
+ # raise 'not supported'
72
+ # end
73
+
74
+
75
+ #
76
+ # Dir
77
+ #
78
+ def create_dir path
79
+ sftp.mkdir! path
80
+ end
81
+
82
+ def delete_dir path
83
+ exec "rm -r #{path}"
84
+ end
85
+
86
+ def each path, &block
87
+ sftp.dir.foreach path do |stat|
88
+ next if stat.name == '.' or stat.name == '..'
89
+ if stat.directory?
90
+ block.call stat.name, :dir
91
+ else
92
+ block.call stat.name, :file
93
+ end
94
+ end
95
+ end
96
+
97
+ def efficient_dir_copy from, to
98
+ from.storage.open_fs do |from_fs|
99
+ to.storage.open_fs do |to_fs|
100
+ if from_fs.local?
101
+ sftp.upload! from.path, fix_path(to.path)
102
+ true
103
+ elsif to_fs.local?
104
+ sftp.download! fix_path(to.path), from.path, :recursive => true
105
+ true
106
+ else
107
+ false
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ # def move_dir path
114
+ # raise 'not supported'
115
+ # end
116
+
117
+
118
+ #
119
+ # Special
120
+ #
121
+ def tmp &block
122
+ tmp_dir = "/tmp/vfs_#{rand(10**3)}"
123
+ if block
124
+ begin
125
+ create_dir tmp_dir
126
+ block.call tmp_dir
127
+ ensure
128
+ delete_dir tmp_dir
129
+ end
130
+ else
131
+ create_dir tmp_dir
132
+ tmp_dir
133
+ end
134
+ end
135
+
136
+ def local?; false end
137
+ end
138
+
139
+ def initialize options = {}
140
+ super
141
+ raise "ssh options not provided!" unless options[:ssh]
142
+ raise "invalid ssh options!" unless options[:ssh].is_a?(Hash)
143
+ end
144
+
145
+
146
+ #
147
+ # Establishing SSH channel
148
+ #
149
+ def open &block
150
+ if block
151
+ if @ssh
152
+ block.call self
153
+ else
154
+ begin
155
+ open
156
+ block.call self
157
+ ensure
158
+ close
159
+ end
160
+ end
161
+ else
162
+ unless @ssh
163
+ ssh_options = self.options[:ssh].clone
164
+ host = options[:host] || raise('host not provided!')
165
+ user = ssh_options.delete(:user) || raise('user not provied!')
166
+ @ssh = Net::SSH.start(host, user, ssh_options)
167
+ @sftp = @ssh.sftp.connect
168
+ end
169
+ end
170
+ end
171
+
172
+ def close
173
+ if @ssh
174
+ @ssh.close
175
+ # @sftp.close not needed
176
+ @ssh, @sftp = nil
177
+ end
178
+ end
179
+
180
+ def to_s; options[:host] end
181
+
182
+
183
+ #
184
+ # Vfs
185
+ #
186
+ include VfsStorage
187
+ alias_method :open_fs, :open
188
+
189
+
190
+ #
191
+ # Shell
192
+ #
193
+ def exec command
194
+ # somehow net-ssh doesn't executes ~/.profile, so we need to execute it manually
195
+ # command = ". ~/.profile && #{command}"
196
+
197
+ stdout, stderr, code, signal = hacked_exec! ssh, command
198
+
199
+ return code, stdout, stderr
200
+ end
201
+
202
+ def bash command
203
+ # somehow net-ssh doesn't executes ~/.profile, so we need to execute it manually
204
+ # command = ". ~/.profile && #{command}"
205
+
206
+ stdout_and_stderr, stderr, code, signal = hacked_exec! ssh, command, true
207
+
208
+ return code, stdout_and_stderr
209
+ end
210
+
211
+
212
+ protected
213
+ attr_accessor :ssh, :sftp
214
+
215
+ def fix_path path
216
+ path.sub(/^\~/, home)
217
+ end
218
+
219
+ def home
220
+ unless @home
221
+ command = 'cd ~; pwd'
222
+ code, stdout, stderr = exec command
223
+ raise "can't execute '#{command}'!" unless code == 0
224
+ @home = stdout.gsub("\n", '')
225
+ end
226
+ @home
227
+ end
228
+
229
+ # taken from here http://stackoverflow.com/questions/3386233/how-to-get-exit-status-with-rubys-netssh-library/3386375#3386375
230
+ def hacked_exec!(ssh, command, merge_stdout_and_stderr = false, &block)
231
+ stdout_data = ""
232
+ stderr_data = ""
233
+ exit_code = nil
234
+ exit_signal = nil
235
+
236
+ channel = ssh.open_channel do |channel|
237
+ channel.exec(command) do |ch, success|
238
+ raise "could not execute command: #{command.inspect}" unless success
239
+
240
+ channel.on_data{|ch2, data| stdout_data << data}
241
+ channel.on_extended_data do |ch2, type, data|
242
+ stdout_data << data if merge_stdout_and_stderr
243
+ stderr_data << data
244
+ end
245
+ channel.on_request("exit-status"){|ch,data| exit_code = data.read_long}
246
+ channel.on_request("exit-signal"){|ch, data| exit_signal = data.read_long}
247
+ end
248
+ end
249
+
250
+ channel.wait
251
+
252
+ [stdout_data, stderr_data, exit_code, exit_signal]
253
+ end
254
+ end
255
+ end
256
+ end
data/lib/vos/gems.rb ADDED
@@ -0,0 +1,6 @@
1
+ # gem 'net-ssh'
2
+ # gem 'net-sftp'
3
+
4
+ if respond_to? :fake_gem
5
+ fake_gem 'vfs'
6
+ end
@@ -0,0 +1,40 @@
1
+ module Vos
2
+ module Helpers
3
+ module Ubuntu
4
+ def default_env
5
+ {:DEBIAN_FRONTEND => 'noninteractive'}
6
+ end
7
+ def wrap_cmd env_str, cmd
8
+ %(. #{env_file.path} && #{env_str}#{' && ' unless env_str.empty?}#{cmd})
9
+ end
10
+
11
+ def env_file
12
+ file '/etc/profile' ## file '/etc/environment'
13
+ end
14
+
15
+ def append_to_environment file, reload = true
16
+ raise "#{file} must be an Entry" unless file.is_a? Vfs::Entry
17
+
18
+ env_ext = dir '/etc/profile_ext'
19
+
20
+ remote_file = env_ext[file.name]
21
+ file.copy_to! remote_file
22
+
23
+ require_clause = "source #{remote_file.path}"
24
+ env_file.append "\n#{require_clause}\n" unless env_file.content.include? require_clause
25
+
26
+ reload_env if reload
27
+ end
28
+
29
+ def reload_env
30
+ bash ". #{env_file.path}"
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ module Vfs
37
+ class File
38
+
39
+ end
40
+ end
@@ -2,6 +2,8 @@ require 'fileutils'
2
2
  require 'net/ssh'
3
3
  require 'net/sftp'
4
4
 
5
+ require 'vfs'
6
+
5
7
  # class File
6
8
  # class << self
7
9
  # def ensure_dir_exist directory, options = {}
@@ -23,7 +25,7 @@ require 'net/sftp'
23
25
  # FileUtils.cp_r from, to
24
26
  # end
25
27
  #
26
- # def remove_dir dir
28
+ # def delete_dir dir
27
29
  # FileUtils.rm_r dir
28
30
  # end
29
31
  # end
@@ -1,11 +1,9 @@
1
1
  raise 'ruby 1.9.2 or higher required!' if RUBY_VERSION < '1.9.2'
2
2
 
3
- require 'rsh/gems'
3
+ require 'vos/gems'
4
4
 
5
5
  require 'open3'
6
6
 
7
- require 'net/ssh'
8
- require 'net/sftp'
9
7
 
10
8
  %w(
11
9
  support
@@ -14,6 +12,10 @@ require 'net/sftp'
14
12
  drivers/local
15
13
  drivers/ssh
16
14
 
17
- box/marks
15
+ box/shell
16
+ box/marks
17
+ box/vfs
18
18
  box
19
- ).each{|f| require "rsh/#{f}"}
19
+
20
+ helpers/ubuntu
21
+ ).each{|f| require "vos/#{f}"}