vos 0.0.4 → 0.1.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.
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}"}