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/readme.md CHANGED
@@ -1,21 +1,122 @@
1
- # Rsh - Tiny wrapper over Net::SSH/SFTP
1
+ # Vfs - Virtual File System
2
2
 
3
- Because they are too hard to use and have terrible API design.
3
+ Handy and simple abstraction over any storage that can represent concept of File and Directory (or at least part of it).
4
+ The Vfs for File System Storages is the same as ActiveRecord is for Relational Databases.
4
5
 
5
- box = Rsh::Box.new host: 'webapp.com', ssh: {user: 'root', password: 'secret'}
6
+ Currently, there are following implementations available:
7
+
8
+ - local file system
9
+ - remote file system (over ssh)
10
+
11
+ ## Goals
12
+
13
+ - **handy, simple and clean** API.
14
+ - **high performance** - the same as by using low-level storage API, there should be no extra calls **.
15
+ - same API for different storages (Local FS, SSH, Hadoop, or any other , ...).
16
+ - should work **simultaneously with different storages**.
17
+ - small codebase, easy to extend by others.
18
+ - simple storage-driver implementation, easy add new storage types (Hadoop DFS, LDAP, Document Oriented DB, In-Memory, ...).
19
+
20
+ ** all methods should have the same performance as native system calls, except for :move and :rename. Right now they are implemented
21
+ ASAP by using copy+destroy approach, will be fixed as soon as I'll have free time to do it.
22
+
23
+ ## Installation
24
+
25
+ $ gem install vfs
26
+ $ gem install vos
27
+
28
+ ## Code samples:
29
+ gem 'vfs' # Virtual File System
30
+ require 'vfs'
31
+
32
+ gem 'vos' # Virtual Operating System
33
+ require 'vos'
34
+
35
+
36
+ # Connections, let's deploy our 'cool_app' project from our local box to remote server
37
+ server = Vfs::Box.new(host: 'cool_app.com', ssh: {user: 'me', password: 'secret'})
38
+ me = '~'.to_dir
39
+
40
+ cool_app = server['apps/cool_app']
41
+ projects = me['projects']
42
+
43
+
44
+ # Working with dirs, copying dir from any source to any destination (local/remote/custom_storage_type)
45
+ projects['cool_app'].copy_to cool_app
46
+
47
+
48
+ # Working with files
49
+ dbc = cool_app.file('config/database.yml') # <= the 'config' dir not exist yet
50
+ dbc.write("user: root\npassword: secret") # <= now the 'database.yml' and parent 'config' has been created
51
+ dbc.content =~ /database/ # => false, we forgot to add the database
52
+ dbc.append("\ndatabase: mysql") # let's do it
53
+
54
+ dbc.update do |content| # and add host info
55
+ content + "\nhost: cool_app.com "
56
+ end
57
+
58
+ projects['cool_app/config/database.yml']. # or just overwrite it with our local dev version
59
+ copy_to! dbc
60
+
61
+ # there are also streaming support (read/write/append), please go to specs for docs
6
62
 
7
- box.upload_directory '/my_project', '/apps/my_project'
8
- box.bash 'nohup /apps/my_project/server_start'
9
-
10
- Honestly my wrapper also not very good. I would like to make API looks like the ['rush'][rush] gem (made by Adam Wiggins)
11
- but it requires a lots of time, maybe I'll do it later.
12
- So, for now it's just a small wrapper to do ssh/io operations not so painfull.
13
63
 
64
+ # Checks
65
+ cool_app['config'].exist? # => true
66
+ cool_app.dir('config').exist? # => true
67
+ cool_app.file('config').exist? # => false
68
+
69
+ cool_app['config'].dir? # => true
70
+ cool_app['config'].file? # => false
71
+
72
+
73
+ # Navigation
74
+ config = cool_app['config']
75
+ config.parent # => </apps/cool_app>
76
+ config['../..'] # => </>
77
+ config['../..'].dir? # => true
78
+
79
+ cool_app.entries # => list of dirs and files, also support &block
80
+ cool_app.files # => list of files, also support &block
81
+ cool_app.dirs # => list of dirs, also support &block
82
+
83
+
84
+ # For more please go to specs (create/update/move/copy/destroy/...)
85
+
86
+ ## Integration with [Vos][vos] (Virtual Operating System)
87
+
88
+ server['apps/cool_app'].bash 'rails production'
89
+
90
+ For more details please go to [Vos][vos] project page.
91
+
92
+ # Why?
93
+
94
+ To easy my work: with local FS, remote FS (cluster management, deployment automation), and some specific systems like Hadoop DFS.
95
+
96
+ Because the API of standard File/Dir/FileUtils classes are just terrible. And there's the reason for it - the goal of thouse tools
97
+ is to provide 1-to-1 clone of underlying OS API, instead of provididing handy tool.
98
+
99
+ And if you want to use remote FS - things are getting even worse and more complicated (Net::SSH & Net::SFTP use a little
100
+ different API than local FS, and you has to remember all thouse little quirks).
101
+
14
102
  ## TODO
15
103
 
16
- - remove ssh.remote and use only open/close
17
- - introduce Entity/Dir/File (the same as in Rush)
18
- - allow to move files between any Boxes, not only between local and remote.
19
- - add support for moving dirs.
104
+ ### v 0.1 (all done)
105
+
106
+ - bash & basic support for
107
+ - File.append
108
+ - list of entries/files/dirs
109
+ - support for efficient copy for Local and SSH storages
110
+
111
+ ### v 0.2 (not started)
112
+
113
+ - efficient (not copy/destroy) versions of move_to, rename
114
+ - glob search for directories: Dir['**/*.yml']
115
+ - access via attributes and helpers for unix chmod
116
+ - add storages: remote FS over HTTP.
117
+
118
+ ### future
119
+
120
+ - add storages: Hadoop DFS, MongoDB, Amazon S3
20
121
 
21
- [rush]: http://github.com/adamwiggins/rush
122
+ [vfs]: http://github.com/alexeypetrushin/vfs
data/spec/box_spec.rb CHANGED
@@ -1,109 +1,56 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Rsh::Box do
4
- with_tmp_spec_dir before: :each
5
-
3
+ describe Vos::Box do
6
4
  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
5
+ @box = Vos::Box.new
6
+ @box.stub :puts
18
7
  end
19
8
 
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
9
+ describe 'vfs integration' do
10
+ it 'smoke test' do
11
+ @box['/'].exist?.should be_true
59
12
  end
60
13
 
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
14
+ it 'vfs integration' do
15
+ @box['/'].bash("echo 'ok'").should == "ok\n"
97
16
  end
98
17
  end
99
18
 
100
19
  describe "shell" do
101
20
  it 'bash' do
102
21
  @box.bash("echo 'ok'").should == "ok\n"
103
- end
22
+ end
23
+
24
+ it 'bash working dir should be /' do
25
+ @box.bash('pwd').should == "/\n"
26
+ end
27
+
28
+ it 'check with regex' do
29
+ @box.bash "echo 'ok'", /ok/
30
+ -> {@box.bash "echo 'ok'", /no/}.should raise_error(/not match/)
31
+ end
104
32
 
105
33
  it "exec" do
106
34
  @box.exec("echo 'ok'").should == [0, "ok\n", ""]
107
35
  end
36
+
37
+ it 'home' do
38
+ @box.home.should_not be_nil
39
+ end
40
+
41
+ it 'env' do
42
+ @box.env.should == {}
43
+ @box.env = {a: 'b'}
44
+
45
+ @box.env c: 'd' do
46
+ @box.env.should == {a: 'b', c: 'd'}
47
+ end
48
+ @box.env.should == {a: 'b'}
49
+
50
+ @box.env(c: 'd')
51
+ @box.env.should == {a: 'b', c: 'd'}
52
+
53
+ @box.env('ls').should == "a=b c=d && ls"
54
+ end
108
55
  end
109
56
  end
@@ -0,0 +1,10 @@
1
+ require 'drivers/spec_helper'
2
+
3
+ describe Vos::Drivers::Local do
4
+ it_should_behave_like "vos driver"
5
+ it_should_behave_like "vfs storage"
6
+
7
+ before :each do
8
+ @storage = @driver = Vos::Drivers::Local.new
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+ require 'yaml'
3
+
4
+ require 'vfs/storages/specification'
5
+ require 'vos/drivers/specification'
@@ -0,0 +1,15 @@
1
+ require 'drivers/spec_helper'
2
+
3
+ describe Vos::Drivers::Ssh do
4
+ it_should_behave_like "vos driver"
5
+ it_should_behave_like "vfs storage"
6
+
7
+ before :all do
8
+ @storage = @driver = Vos::Drivers::Ssh.new(config[:remote_driver])
9
+ @driver.open
10
+ end
11
+
12
+ after :all do
13
+ @driver.close
14
+ end
15
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'rspec_ext'
2
- require 'rsh'
2
+ require 'ruby_ext'
3
+ require 'vos'
3
4
 
4
5
  rspec do
5
6
  def config
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
+ - 1
7
8
  - 0
8
- - 4
9
- version: 0.0.4
9
+ version: 0.1.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Alexey Petrushin
@@ -14,10 +14,22 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-02-02 00:00:00 +03:00
17
+ date: 2011-02-08 00:00:00 +03:00
18
18
  default_executable:
19
- dependencies: []
20
-
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: vfs
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
21
33
  description:
22
34
  email:
23
35
  executables: []
@@ -30,25 +42,25 @@ files:
30
42
  - Rakefile
31
43
  - readme.md
32
44
  - lib/old/ssh.rb
33
- - lib/rsh/box/marks.rb
34
- - lib/rsh/box.rb
35
- - lib/rsh/drivers/abstract.rb
36
- - lib/rsh/drivers/local.rb
37
- - lib/rsh/drivers/ssh.rb
38
- - lib/rsh/gems.rb
39
- - lib/rsh/support.rb
40
- - lib/rsh.rb
41
- - spec/abstract_driver/dir/dir2/file
42
- - spec/abstract_driver/local_file
43
- - spec/abstract_driver.rb
44
- - spec/box_spec/dir/dir2/file
45
- - spec/box_spec/local_file
45
+ - lib/vos/box/marks.rb
46
+ - lib/vos/box/shell.rb
47
+ - lib/vos/box/vfs.rb
48
+ - lib/vos/box.rb
49
+ - lib/vos/drivers/abstract.rb
50
+ - lib/vos/drivers/local.rb
51
+ - lib/vos/drivers/specification.rb
52
+ - lib/vos/drivers/ssh.rb
53
+ - lib/vos/gems.rb
54
+ - lib/vos/helpers/ubuntu.rb
55
+ - lib/vos/support.rb
56
+ - lib/vos.rb
46
57
  - spec/box_spec.rb
47
58
  - spec/config.example.yml
48
59
  - spec/config.yml
49
- - spec/local_driver_spec.rb
60
+ - spec/drivers/local_spec.rb
61
+ - spec/drivers/spec_helper.rb
62
+ - spec/drivers/ssh_spec.rb
50
63
  - spec/spec_helper.rb
51
- - spec/ssh_driver_spec.rb
52
64
  has_rdoc: true
53
65
  homepage: http://github.com/alexeypetrushin/vos
54
66
  licenses: []
@@ -80,6 +92,6 @@ rubyforge_project:
80
92
  rubygems_version: 1.3.7
81
93
  signing_key:
82
94
  specification_version: 3
83
- summary: Tiny wrapper over Net::SSH/SFTP + small rake addon for cluster configuration management
95
+ summary: Virtual Operating System
84
96
  test_files: []
85
97
 
data/lib/rsh/box/marks.rb DELETED
@@ -1,29 +0,0 @@
1
- module Rsh
2
- module Marks
3
- def mark key
4
- ensure_mark_requrements!
5
- bash "touch #{marks_dir}/#{key}"
6
- end
7
-
8
- def has_mark? key
9
- ensure_mark_requrements!
10
- file_exist? "#{marks_dir}/#{key}"
11
- end
12
-
13
- def clear_marks
14
- bash "rm -r #{marks_dir}"
15
- end
16
-
17
- protected
18
- def marks_dir
19
- home "/.marks"
20
- end
21
-
22
- def ensure_mark_requrements!
23
- unless @ensure_mark_requrements
24
- create_directory marks_dir unless directory_exist? marks_dir
25
- @ensure_mark_requrements = true
26
- end
27
- end
28
- end
29
- end
data/lib/rsh/box.rb DELETED
@@ -1,182 +0,0 @@
1
- module Rsh
2
- class Box
3
- include Marks
4
-
5
- attr_accessor :options
6
-
7
- def initialize options = {}
8
- @options = options
9
- options[:host] ||= 'localhost'
10
- end
11
-
12
- def driver
13
- unless @driver
14
- klass = options[:host] == 'localhost' ? Drivers::Local : Drivers::Ssh
15
- @driver = klass.new options
16
- end
17
- @driver
18
- end
19
-
20
- def local_driver
21
- @local_driver ||= Drivers::Local.new
22
- end
23
-
24
- def bulk &b
25
- driver.bulk &b
26
- end
27
-
28
- def upload_file from_local_path, to_remote_path, options = {}
29
- bulk do
30
- raise "file '#{from_local_path}' not exists!" unless local_driver.file_exist? from_local_path
31
- if driver.file_exist?(to_remote_path)
32
- if options[:override]
33
- driver.remove_file to_remote_path
34
- else
35
- raise "file '#{to_remote_path}' already exists!"
36
- end
37
- end
38
- driver.upload_file from_local_path, to_remote_path
39
- end
40
- end
41
-
42
- def download_file from_remote_path, to_local_path, options = {}
43
- bulk do
44
- raise "file #{from_remote_path} not exists!" unless driver.file_exist?(from_remote_path)
45
- if local_driver.file_exist? to_local_path
46
- if options[:override]
47
- local_driver.remove_file to_local_path
48
- else
49
- raise "file #{to_local_path} already exists!"
50
- end
51
- end
52
- driver.download_file from_remote_path, to_local_path
53
- end
54
- end
55
-
56
- def remove_file remote_file_path, options = {}
57
- bulk do
58
- if driver.file_exist? remote_file_path
59
- driver.remove_file remote_file_path
60
- else
61
- raise "file #{remote_file_path} not exists!" unless options[:silent]
62
- end
63
- end
64
- end
65
-
66
- def exist? remote_path
67
- driver.exist? remote_path
68
- end
69
-
70
- def file_exist? remote_file_path
71
- driver.file_exist? remote_file_path
72
- end
73
-
74
- def remove_directory remote_directory_path, options = {}
75
- bulk do
76
- if driver.directory_exist? remote_directory_path
77
- driver.remove_directory remote_directory_path
78
- else
79
- raise "directory #{remote_directory_path} not exists!" unless options[:silent]
80
- end
81
- end
82
- end
83
-
84
- def create_directory remote_path, options = {}
85
- bulk do
86
- if driver.directory_exist?(remote_path)
87
- if options[:override]
88
- driver.remove_directory remote_path
89
- driver.create_directory remote_path
90
- elsif options[:silent]
91
- # do nothing
92
- else
93
- raise "directory '#{remote_path}' already exists!"
94
- end
95
- else
96
- driver.create_directory remote_path
97
- end
98
- end
99
- end
100
-
101
- def directory_exist? remote_path
102
- driver.directory_exist? remote_path
103
- end
104
-
105
- def upload_directory from_local_path, to_remote_path, options = {}
106
- bulk do
107
- raise "directory '#{from_local_path}' not exists!" unless local_driver.directory_exist? from_local_path
108
- if driver.directory_exist?(to_remote_path)
109
- if options[:override]
110
- driver.remove_directory to_remote_path
111
- else
112
- raise "directory '#{to_remote_path}' already exists!"
113
- end
114
- end
115
- driver.upload_directory from_local_path, to_remote_path
116
- end
117
- end
118
-
119
- def download_directory from_remote_path, to_local_path, options = {}
120
- bulk do
121
- raise "directory #{from_remote_path} not exists!" unless driver.directory_exist?(from_remote_path)
122
- if local_driver.directory_exist? to_local_path
123
- if options[:override]
124
- local_driver.remove_directory to_local_path
125
- else
126
- raise "directory #{to_local_path} already exists!"
127
- end
128
- end
129
- driver.download_directory from_remote_path, to_local_path
130
- end
131
- end
132
-
133
- def with_tmp_dir &block
134
- bulk do
135
- tmp_dir = driver.generate_tmp_dir_name
136
- begin
137
- remove_directory tmp_dir if directory_exist? tmp_dir
138
- create_directory tmp_dir
139
- block.call
140
- ensure
141
- remove_directory tmp_dir if directory_exist? tmp_dir
142
- end
143
- end
144
- end
145
-
146
- def bash cmd, options = {}
147
- ignore_stderr = options.delete :ignore_stderr
148
- raise "invalid options :#{options.keys.join(', :')}" unless options.empty?
149
-
150
- code, stdout, stderr = exec cmd
151
- unless code == 0
152
- puts stdout
153
- puts stderr
154
- raise "can't execute '#{cmd}'!"
155
- end
156
- unless stderr.empty? or ignore_stderr
157
- puts stderr
158
- raise "stderr not empty for '#{cmd}'!"
159
- end
160
- stdout + stderr
161
- end
162
-
163
- def exec cmd
164
- driver.exec cmd
165
- end
166
-
167
- def home path = nil
168
- @home ||= bash('cd ~; pwd').gsub("\n", '')
169
- "#{@home}#{path}"
170
- end
171
-
172
- def inspect
173
- "<Box: #{options[:host]}>"
174
- end
175
- alias_method :to_s, :inspect
176
-
177
- protected
178
- def method_missing m, *a, &b
179
- driver.send m, *a, &b
180
- end
181
- end
182
- end
@@ -1,48 +0,0 @@
1
- module Rsh
2
- module Drivers
3
- class Local < Abstract
4
- def upload_file from_local_path, to_remote_path
5
- FileUtils.copy from_local_path, to_remote_path
6
- end
7
-
8
- def download_file from_remote_path, to_local_path
9
- FileUtils.copy from_remote_path, to_local_path
10
- end
11
-
12
- def exist? remote_file_path
13
- File.exist? remote_file_path
14
- end
15
-
16
- alias_method :directory_exist?, :exist?
17
- alias_method :file_exist?, :exist?
18
-
19
- def remove_file remote_file_path
20
- File.delete remote_file_path
21
- end
22
-
23
- def create_directory path
24
- Dir.mkdir path
25
- end
26
-
27
- def remove_directory path
28
- FileUtils.rm_r path
29
- end
30
-
31
- def upload_directory from_local_path, to_remote_path
32
- FileUtils.cp_r from_local_path, to_remote_path
33
- end
34
-
35
- def download_directory from_remote_path, to_local_path
36
- FileUtils.cp_r from_remote_path, to_local_path
37
- end
38
-
39
- def exec command
40
- code, stdout, stderr = Open3.popen3 command do |stdin, stdout, stderr, waitth|
41
- [waitth.value.to_i, stdout.read, stderr.read]
42
- end
43
-
44
- return code, stdout, stderr
45
- end
46
- end
47
- end
48
- end