defog 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .rspec
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in defog.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Ronen Barzel
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,124 @@
1
+ = defog
2
+
3
+ Defog wraps the [fog](https://rubygems.org/gems/fog) gem (specifically,
4
+ [Fog::Storage](http://fog.io/1.3.1/storage/)), providing access to files
5
+ stored in the cloud via proxy files on the local file system.
6
+ A proxy file can be
7
+ * Read-only: A local cached copy of a cloud file.
8
+ * Write-only: A local file that will be uploaded to the cloud.
9
+ * Read-Write: A local file that mirrors a cloud file, propogating changes back to the cloud.
10
+
11
+ Defog thus lets you use ordinary programmatic tools to access and
12
+ manipulate your cloud data. Thanks to the magic of
13
+ [fog](https://rubygems.org/gems/fog) it works across cloud providers, and
14
+ it also works with the local file system as a "provider" so that you can,
15
+ e.g. use the local file system for development and the cloud for
16
+ production.
17
+
18
+ == Usage Summary
19
+
20
+ Full Rdoc is available at http://rubydoc.info/gems/defog
21
+
22
+ === Create proxy connection
23
+
24
+ Connect to the remote storage by creating a <code>Defog::Proxy</code>
25
+ object, which proxies files in a specific remote location, e.g.:
26
+
27
+ defog = Defog::Proxy.new(:provider => :AWS,
28
+ :aws_access_key_id => "yourid",
29
+ :aws_secret_access_key => "yoursecret",
30
+ :region => "optional-s3-region",
31
+ :bucket => "s3-bucket-name")
32
+
33
+ defog = Defog::Proxy.new(:provider => :Local,
34
+ :directory => "/path/to/directory")
35
+
36
+ === Proxy a file
37
+
38
+ Proxy a remote file by creating a <code>Defog::File</code> object:
39
+
40
+ file = defog.file("key/of/file", mode)
41
+ # ... access file ...
42
+ file.close
43
+
44
+ defog.file("key/of/file", mode) do |file|
45
+ # ... access file ...
46
+ end
47
+
48
+ <code>mode</code> can be "r", "r+", "w", "w+", "a", or "a+" with the usual
49
+ semantics. Closing the file object (explicitly or implicitly at the end of
50
+ the block) synchronizes the local proxy with the remote storage and (by
51
+ default) deletes the local proxy file.
52
+
53
+ The <code>Defog::File</code> class inherits from <code>::File</code>. So
54
+ you can use it directly for I/O operations, such as
55
+
56
+ defog.file("key", "r") do |file|
57
+ file.readlines
58
+ end
59
+
60
+ You can also access the proxy file via its path, allowing things such as
61
+
62
+ defog.file("image100x100.jpg", "w") do |file|
63
+ system("convert souce.png -scale 100x100 #{file.path}")
64
+ end
65
+
66
+ (Note that the proxy file path has the same file extension as the cloud key string.)
67
+
68
+
69
+ To suppress updating the remote storage, delete the local proxy file before
70
+ closing (e.g. via <code>File.unlink(file.path)</code>) or pass
71
+ <code>:synchronize => false</code> to the <code>#close</code> method.
72
+
73
+
74
+ === Persistence
75
+
76
+ By default, the local proxy files are deleted when closed. However, it is
77
+ possible to persist the local proxy, so that it if the remote is accessed
78
+ again-- whether in the same program execution or at a later time or by a
79
+ different program--the data will not need to be transferred again. Do so
80
+ via
81
+
82
+ file = defog.file("key/of/file", mode, :persist => true)
83
+
84
+ or
85
+
86
+ file.close(:persist => true) # overrides setting at runtime
87
+
88
+ When opening a file whose local proxy has been persisted, Defog checks to see if
89
+ the local proxy is out of date and if so replaces it.
90
+
91
+ Currently, Defog does not natively support a way to explictly delete the
92
+ locally persisted file (other than opening and closing it again without
93
+ :persist => true). But it's fair game to delete it outside of Defog, such
94
+ as via a cron job that cleans out old files.
95
+
96
+ === Local Proxy File Location
97
+
98
+ Local proxy files are stored by default in
99
+
100
+ #{tmproot}/defog/#{provider}/#{location}/#{key}
101
+
102
+ where <code>tmproot</code> is <code>Rails.root+"tmp"</code> if Rails is
103
+ defined, otherwise <code>Dir.tmpdir()</code>. For AWS,
104
+ <code>location</code> is the bucket name, and for Local it's the
105
+ local_root directory path with slashes replaced with dashes
106
+
107
+ See the documentation for configuring other locations.
108
+
109
+ == Installation
110
+
111
+ Gemfile:
112
+ gem 'defog'
113
+
114
+ == Compatibility
115
+
116
+ Defog has (so far) been tested using MRI 1.9.3 and
117
+ [fog](https://rubygems.org/gems/fog) storage providers :Local and :AWS
118
+
119
+ == History
120
+
121
+ == Copyright
122
+
123
+ Released under the MIT License. See LICENSE for details.
124
+
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec) do |spec|
6
+ spec.rspec_opts = '-Ispec'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+ Rake::RDocTask.new do |rdoc|
11
+ require File.dirname(__FILE__) + '/lib/defog/version'
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = "defog #{Defog::VERSION}"
14
+ rdoc.rdoc_files.include('README*')
15
+ rdoc.rdoc_files.include('lib/**/*.rb')
16
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/defog/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["ronen barzel"]
6
+ gem.email = ["ronen@barzel.org"]
7
+ gem.description = %q{Wrapper to fog gem, proxying access to cloud files as local files.}
8
+ gem.summary = %q{Wrapper to fog gem, proxying access to cloud files as local files. Access can be read-only (local cache), write-only (upload), or read-write (mirror)}
9
+ gem.homepage = "http://github.com/ronen/defog"
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "defog"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Defog::VERSION
17
+
18
+ gem.add_dependency 'fog'
19
+ gem.add_dependency 'hash_keyword_args'
20
+
21
+ gem.add_development_dependency 'rake'
22
+ gem.add_development_dependency 'rspec'
23
+ gem.add_development_dependency 'simplecov'
24
+ gem.add_development_dependency 'simplecov-gem-adapter'
25
+ end
@@ -0,0 +1,5 @@
1
+ require 'defog/error'
2
+ require 'defog/file'
3
+ require 'defog/fog_wrapper'
4
+ require 'defog/proxy'
5
+ require 'defog/version'
@@ -0,0 +1,8 @@
1
+ module Defog
2
+ class Error < RuntimeError
3
+
4
+ class NoCloudFile < Error
5
+ end
6
+ end
7
+
8
+ end
@@ -0,0 +1,83 @@
1
+ module Defog
2
+ # Create a Defog::File proxy instance via Defog::Proxy#file, such as
3
+ #
4
+ # defog = Defog::Proxy.new(:provider => :AWS, :aws_access_key_id => access_key, ...)
5
+ #
6
+ # defog.file("key/to/my/file", "w") do |file|
7
+ # # ... access the proxy file ...
8
+ # end
9
+ #
10
+ # or
11
+ #
12
+ # file = defog.file("key/to/my/file", "w")
13
+ # # ... access the proxy file ...
14
+ # file.close
15
+ #
16
+ # Defog::File inherits from ::File, so you can act on the proxy file using
17
+ # ordinary IO methods, such as
18
+ #
19
+ # defog.file("key", "r") do |file|
20
+ # file.readlines
21
+ # end
22
+ #
23
+ # You can also access the proxy file via its path, allowing things such
24
+ # as
25
+ # defog.file("image100x100.jpg", "w") do |file|
26
+ # system("convert souce.png -scale 100x100 #{file.path}")
27
+ # end
28
+ #
29
+ # (Note that the proxy file path has the same file extension as the cloud key string.)
30
+ #
31
+ # Upon closing the proxy file, in normal use the cloud storage gets synchronized and
32
+ # the proxy deleted. See File#close for more details.
33
+ class File < ::File
34
+ def self.get(opts={}, &block) #:nodoc:
35
+ opts = opts.keyword_args(:proxy => :required, :key => :required, :mode => :required, :persist => :optional)
36
+
37
+ proxy_path = opts[:proxy_path] = Pathname.new("#{opts.proxy.proxy_root}/#{opts.key}").expand_path
38
+ proxy_path.dirname.mkpath
39
+
40
+ case opts.mode
41
+ when "r" then
42
+ opts.proxy.fog_wrapper.get_file(opts.key, proxy_path)
43
+ when "w", "w+" then
44
+ opts[:upload] = true
45
+ when "r+", "a", "a+" then
46
+ opts.proxy.fog_wrapper.get_file(opts.key, proxy_path)
47
+ opts[:upload] = true
48
+ else
49
+ raise ArgumentError, "Invalid mode #{opts.mode.inspect}"
50
+ end
51
+
52
+ self.open(opts, &block)
53
+ end
54
+
55
+ def initialize(opts={}, &block) #:nodoc:
56
+ @defog = opts.keyword_args(:proxy => :required, :mode => :required, :key => :required, :proxy_path => :required, :upload => :optional, :persist => :optional)
57
+ super(@defog.proxy_path, @defog.mode, &block)
58
+ end
59
+
60
+ # Closes the proxy file and, in the common case, synchronizes the cloud storage
61
+ # then deletes the proxy file.
62
+ #
63
+ # Synchronization can be suppressed by passing the option
64
+ # :synchronize => false
65
+ # Synchronization will also be implicitly suppressed if the proxy file
66
+ # was deleted before this call, e.g., via <code>::File.unlink(file.path)</code>.
67
+ #
68
+ #
69
+ # Whether the proxy file gets deleted vs persisted after the close can
70
+ # be set by passing the option
71
+ # :persist => true or false
72
+ # (This will override the setting of <code>:persist</code> passed to Proxy#file)
73
+ #
74
+ def close(opts={})
75
+ opts = opts.keyword_args(:persist => @defog.persist, :synchronize => true)
76
+ super()
77
+ if @defog.proxy_path.exist?
78
+ @defog.proxy.fog_wrapper.put_file(@defog.key, @defog.proxy_path) if @defog.upload and opts.synchronize
79
+ @defog.proxy_path.unlink unless opts.persist
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,73 @@
1
+ require "fog"
2
+ require "hash_keyword_args"
3
+ require "pathname"
4
+
5
+ module Defog #:nodoc: all
6
+ class FogWrapper #:nodoc: all
7
+
8
+ attr_reader :location
9
+ attr_reader :fog_directory
10
+
11
+ def self.connect(opts={})
12
+ opts = opts.keyword_args(:provider => :required, :OTHERS => :optional)
13
+ provider = opts.delete(:provider)
14
+ klass = begin
15
+ self.const_get(provider.to_s.capitalize, false)
16
+ rescue NameError
17
+ raise ArgumentError, "#{provider.inspect} is not a supported fog storage provider"
18
+ end
19
+ klass.new(opts)
20
+ end
21
+
22
+ def get_file(key, path)
23
+ raise Error::NoCloudFile, "No such file in #{provider} #{location}: #{key}" unless fog_head(key)
24
+ return if path.exist? and Digest::MD5.hexdigest(path.read) == get_md5(key)
25
+ path.open("w") do |f|
26
+ f.write(fog_head(key).body)
27
+ end
28
+ end
29
+
30
+ def put_file(key, path)
31
+ fog_directory.files.create(:key => key, :body => path.open)
32
+ end
33
+
34
+ def fog_head(key)
35
+ fog_directory.files.head(key)
36
+ end
37
+
38
+ class Local < FogWrapper
39
+ def provider ; :local ; end
40
+
41
+ def initialize(opts={})
42
+ opts = opts.keyword_args(:local_root => :required)
43
+ directory = Pathname.new(opts.local_root).realpath
44
+ @location = directory.to_s.gsub(%r{/},'_')
45
+ @fog_connection = Fog::Storage.new(:provider => provider, :local_root => directory)
46
+ @fog_directory = @fog_connection.directories.get('.')
47
+ end
48
+
49
+ def get_md5(key)
50
+ Digest::MD5.hexdigest(fog_head(key).body)
51
+ end
52
+
53
+ end
54
+
55
+ class Aws < FogWrapper
56
+ def provider ; :AWS ; end
57
+
58
+ def initialize(opts={})
59
+ opts = opts.keyword_args(:aws_access_key_id => :required, :aws_secret_access_key => :required, :region => :optional, :bucket => :required)
60
+ @location = opts.delete(:bucket)
61
+ @fog_connection = Fog::Storage.new(opts.merge(:provider => provider))
62
+ @fog_connection.directories.create :key => @location unless @fog_connection.directories.map(&:key).include? @location
63
+ @fog_directory = @fog_connection.directories.get(@location)
64
+ end
65
+
66
+ def get_md5(key)
67
+ fog_head(key).content_md5
68
+ end
69
+
70
+ end
71
+ end
72
+ end
73
+
@@ -0,0 +1,85 @@
1
+ require "hash_keyword_args"
2
+ require "tmpdir"
3
+ require "pathname"
4
+
5
+ module Defog
6
+ class Proxy
7
+
8
+ attr_reader :proxy_root #
9
+ attr_reader :fog_wrapper # :nodoc:
10
+
11
+ # Opens a <code>Fog</code> cloud storage connection to map to a corresponding proxy
12
+ # directory. Use via, e.g.,
13
+ #
14
+ # Defog::Proxy.new(:provider => :AWS, :aws_access_key_id => access_key, ...)
15
+ #
16
+ # The <code>:provider</code> and its corresponding options must be
17
+ # specified as per <code>Fog::Storage.new</code>. Currently, only
18
+ # <code>:local</code> and <code>:AWS</code> are supported. When using
19
+ # <code>:AWS</code>, an additional option <code>:bucket</code> must be
20
+ # specified; all files proxied by this instance must be in a single
21
+ # bucket.
22
+ #
23
+ # By default, each proxy's root directory is placed in a reasonable
24
+ # safe place, under <code>Rails.root/tmp</code> if Rails is defined
25
+ # otherwise under <code>Dir.tmpdir</code>. (More details: within that
26
+ # directory, the root directory is disambiguated by #provider and
27
+ # #location, so that multiple Defog::Proxy instances can be
28
+ # created without collision.)
29
+ #
30
+ # The upshot is that if you have no special constraints you don't need
31
+ # to worry about it. But if you do care, you can specify the option:
32
+ # :proxy_root => "/root/for/this/proxy/files"
33
+ #
34
+ def initialize(opts={})
35
+ opts = opts.keyword_args(:provider => :required, :proxy_root => :optional, :OTHERS => :optional)
36
+
37
+ @proxy_root = Pathname.new(opts.delete(:proxy_root)) if opts.proxy_root
38
+
39
+ @fog_wrapper = FogWrapper.connect(opts)
40
+
41
+ @proxy_root ||= case
42
+ when defined?(Rails) then Rails.root
43
+ else Pathname.new(Dir.tmpdir)
44
+ end + "defog" + provider.to_s + location
45
+
46
+ end
47
+
48
+ # Returns the provider for this proxy. I.e., <code>:local</code> or
49
+ # <code>:AWS</code>
50
+ def provider
51
+ @fog_wrapper.provider
52
+ end
53
+
54
+ # Returns a 'location' handle to use in the default proxy root path,
55
+ # to disambiguate it from other proxies with the same provider. For
56
+ # :AWS it's the bucket name, for :Local it's derived from the local
57
+ # root path.
58
+ def location
59
+ @fog_wrapper.location
60
+ end
61
+
62
+ # Proxy a remote cloud file. Returns a Defog::File object, which is a
63
+ # specialization of ::File.
64
+ #
65
+ # <code>key</code> is the cloud storage key for the file.
66
+ #
67
+ # <code>mode</code> can be "r", "r+", "w", "w+", "a", or "a+" with the
68
+ # usual semantics.
69
+ #
70
+ # Like ::File.open, if called with a block yields the file object to
71
+ # the block and ensures the file will be closed when leaving the block.
72
+ #
73
+ # Normally the proxy file is synchronized and then deleted upon close.
74
+ # Pass
75
+ # :persist => true
76
+ # to maintain the file after closing. See File#close for more
77
+ # details.
78
+ #
79
+ def file(key, mode, opts={}, &block)
80
+ opts = opts.keyword_args(:persist)
81
+ File.get(opts.merge(:proxy => self, :key => key, :mode => mode), &block)
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,3 @@
1
+ module Defog
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1 @@
1
+ tmp/
@@ -0,0 +1,257 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples "get proxy" do
4
+ it "should create proxy if remote exists" do
5
+ create_remote("hello")
6
+ file = @proxy.file(key, @mode)
7
+ File.exist?(file.path).should be_true
8
+ end
9
+
10
+ it "should raise error if remote doesn't exist" do
11
+ expect { @proxy.file("nonesuch", @mode) }.should raise_error(Defog::Error::NoCloudFile)
12
+ end
13
+
14
+ it "should overwrite existing proxy if it's not valid " do
15
+ create_remote("hello")
16
+ create_proxy("goodbye")
17
+ proxy_path.read.should == "goodbye"
18
+ @proxy.file(key, @mode)
19
+ proxy_path.read.should == "hello"
20
+ end
21
+
22
+ it "should use existing proxy if it's valid" do
23
+ create_remote("hello")
24
+ create_proxy("hello")
25
+ Pathname.any_instance.should_not_receive(:open).with("w")
26
+ @proxy.file(key, @mode)
27
+
28
+ # doublecheck that should_not_receive was the right
29
+ # thing to test. will it be received for an invalid proxy?
30
+ create_proxy("goodbye")
31
+ Pathname.any_instance.should_receive(:open).with("w")
32
+ @proxy.file(key, @mode)
33
+ end
34
+ end
35
+
36
+ shared_examples "read" do
37
+ it "should correctly read" do
38
+ create_remote("read me")
39
+ @proxy.file(key, @mode) do |file|
40
+ file.rewind
41
+ file.read.should == "read me"
42
+ end
43
+ end
44
+ end
45
+
46
+ shared_examples "read after write" do
47
+ it "should correctly read after write" do
48
+ @proxy.file(key, @mode) do |file|
49
+ file.write "read me"
50
+ file.rewind
51
+ file.read.should == "read me"
52
+ end
53
+ end
54
+ end
55
+
56
+ shared_examples "write" do
57
+ it "should correctly write" do
58
+ create_remote("dummy")
59
+ @proxy.file(key, @mode, :persist => true) do |file|
60
+ file.write "write me"
61
+ end
62
+ proxy_path.read.should =~ /write me$/
63
+ end
64
+ end
65
+
66
+ shared_examples "append" do
67
+ it "should correctly append" do
68
+ create_remote("hello")
69
+ @proxy.file(key, @mode, :persist => true) do |file|
70
+ file.write "goodbye"
71
+ end
72
+ proxy_path.read.should == "hellogoodbye"
73
+ end
74
+ end
75
+
76
+ shared_examples "create" do
77
+
78
+ it "should create remote" do
79
+ file = @proxy.file(key, @mode)
80
+ create_proxy("upload me")
81
+ file.close
82
+ remote_body.should == "upload me"
83
+ end
84
+
85
+ it "should not create remote if proxy is deleted" do
86
+ @proxy.file(key, @mode) do |file|
87
+ file.write("ignore me")
88
+ proxy_path.unlink
89
+ end
90
+ expect {remote_body}.should raise_error
91
+ end
92
+
93
+ it "should not create remote if :synchronize => false" do
94
+ file = @proxy.file(key, @mode)
95
+ create_proxy("ignore me")
96
+ file.close(:synchronize => false)
97
+ expect {remote_body}.should raise_error
98
+ end
99
+
100
+ end
101
+
102
+ shared_examples "update" do
103
+
104
+ it "should overwrite remote" do
105
+ create_remote("overwrite me")
106
+ remote_body.should == "overwrite me"
107
+ file = @proxy.file(key, @mode)
108
+ create_proxy("upload me")
109
+ file.close
110
+ remote_body.should == "upload me"
111
+ end
112
+
113
+ it "should not overwrite remote if proxy is deleted" do
114
+ create_remote("keep me")
115
+ @proxy.file(key, @mode) do |file|
116
+ file.write("ignore me")
117
+ proxy_path.unlink
118
+ end
119
+ remote_body.should == "keep me"
120
+ end
121
+
122
+ it "should not overwrite remote if :synchronize => false" do
123
+ create_remote("keep me")
124
+ file = @proxy.file(key, @mode)
125
+ create_proxy("ignore me")
126
+ file.close(:synchronize => false)
127
+ remote_body.should == "keep me"
128
+ end
129
+
130
+ end
131
+
132
+ shared_examples "persistence" do
133
+ it "should delete proxy on close" do
134
+ create_remote("whatever")
135
+ file = @proxy.file(key, @mode)
136
+ proxy_path.should be_exist
137
+ file.close
138
+ proxy_path.should_not be_exist
139
+ end
140
+
141
+ it "should delete proxy on close (block form)" do
142
+ create_remote("whatever")
143
+ @proxy.file(key, @mode) do |file|
144
+ proxy_path.should be_exist
145
+ end
146
+ proxy_path.should_not be_exist
147
+ end
148
+
149
+ it "should not delete proxy if persisting" do
150
+ create_remote("whatever")
151
+ @proxy.file(key, @mode, :persist => true) do |file|
152
+ proxy_path.should be_exist
153
+ end
154
+ proxy_path.should be_exist
155
+ end
156
+
157
+ it "close should override persist true" do
158
+ create_remote("whatever")
159
+ file = @proxy.file(key, @mode)
160
+ proxy_path.should be_exist
161
+ file.close(:persist => true)
162
+ proxy_path.should be_exist
163
+ end
164
+
165
+ it "close should override persist false" do
166
+ create_remote("whatever")
167
+ file = @proxy.file(key, @mode, :persist => true)
168
+ proxy_path.should be_exist
169
+ file.close(:persist => false)
170
+ proxy_path.should_not be_exist
171
+ end
172
+
173
+ end
174
+
175
+
176
+ shared_examples "a proxy file" do |proxyargs|
177
+
178
+ before(:all) do
179
+ @proxy = Defog::Proxy.new(proxyargs)
180
+ end
181
+
182
+ %W[r r+ w w+ a a+].each do |mode|
183
+
184
+ context "mode #{mode.inspect}" do
185
+ before(:each) do
186
+ @mode = mode
187
+ end
188
+ it_should_behave_like "get proxy" if mode =~ %r{[ra]}
189
+ it_should_behave_like "read" if mode == "r" or mode == "a+"
190
+ it_should_behave_like "write" if mode =~ %r{[wa+]}
191
+ it_should_behave_like "read after write" if mode == "w+"
192
+ it_should_behave_like "append" if mode =~ %r{a}
193
+ it_should_behave_like "create" if mode =~ %r{wa}
194
+ it_should_behave_like "update" if mode =~ %r{[wa+]}
195
+ it_should_behave_like "persistence"
196
+ end
197
+ end
198
+
199
+ it "should raise error on bad mode" do
200
+ expect { @proxy.file(key, "xyz") }.should raise_error(ArgumentError)
201
+ end
202
+
203
+
204
+ end
205
+
206
+ describe "Defog::Proxy::File" do
207
+
208
+ context "Local" do
209
+
210
+ before(:all) do
211
+ Fog.unmock!
212
+ end
213
+
214
+ args = {:provider => :local, :local_root => LOCAL_CLOUD_PATH, :proxy_root => PROXY_BASE_PATH + "local" }
215
+
216
+ it_should_behave_like "a proxy file", args
217
+
218
+ end
219
+
220
+ context "AWS" do
221
+
222
+ before(:all) do
223
+ Fog.mock!
224
+ end
225
+
226
+ args = {:provider => :AWS, :aws_access_key_id => "dummyid", :aws_secret_access_key => "dummysecret", :region => "eu-west-1", :bucket => "tester", :proxy_root => PROXY_BASE_PATH + "AWS"}
227
+
228
+ it_should_behave_like "a proxy file", args
229
+
230
+ end
231
+
232
+ def key
233
+ example.metadata[:full_description].gsub(/\+/,'plus').gsub(/\W/,'-') + "/filename"
234
+ end
235
+
236
+ def create_remote(body)
237
+ @proxy.fog_wrapper.fog_directory.files.create(:key => key, :body => body)
238
+ end
239
+
240
+ def proxy_path
241
+ Pathname.new("#{@proxy.proxy_root}/#{key}").expand_path
242
+ end
243
+
244
+ def create_proxy(body)
245
+ path = proxy_path
246
+ path.dirname.mkpath
247
+ path.open("w") do |f|
248
+ f.write(body)
249
+ end
250
+ end
251
+
252
+ def remote_body
253
+ @proxy.fog_wrapper.fog_directory.files.get(key).body
254
+ end
255
+
256
+
257
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples "a proxy" do |args|
4
+
5
+ it "should default proxy root to tmpdir/defog" do
6
+ proxy = Defog::Proxy.new(args)
7
+ proxy.proxy_root.should == Pathname.new(Dir.tmpdir) + "defog" + proxy.provider.to_s + proxy.location
8
+ end
9
+
10
+ it "should default proxy root to Rails.root" do
11
+ begin
12
+ Kernel.const_set("Rails", Struct.new(:root).new("/dummy/rails/app/"))
13
+ proxy = Defog::Proxy.new(args)
14
+ proxy.proxy_root.should == Rails.root + "defog" + proxy.provider.to_s + proxy.location
15
+ ensure
16
+ Kernel.send :remove_const, "Rails"
17
+ end
18
+ end
19
+
20
+ it "should accept proxy root parameter" do
21
+ path = Pathname.new("/a/random/path")
22
+ proxy = Defog::Proxy.new(args.merge(:proxy_root => path))
23
+ proxy.proxy_root.should == path
24
+ end
25
+
26
+
27
+ end
28
+
29
+ describe Defog::Proxy do
30
+
31
+ context "Local" do
32
+ before(:all) do
33
+ Fog.unmock!
34
+ end
35
+
36
+ args = {:provider => :local, :local_root => LOCAL_CLOUD_PATH}
37
+
38
+ it_should_behave_like "a proxy", args
39
+
40
+ it "should use the deslashed local_root as the location" do
41
+ Defog::Proxy.new(args).location.should == LOCAL_CLOUD_PATH.to_s.gsub(%r{/},"_")
42
+ end
43
+
44
+ end
45
+
46
+ context "AWS" do
47
+ before(:all) do
48
+ Fog.mock!
49
+ end
50
+
51
+ args = {:provider => :AWS, :aws_access_key_id => "dummyid", :aws_secret_access_key => "dummysecret", :region => "eu-west-1", :bucket => "tester"}
52
+ it_should_behave_like "a proxy", args
53
+
54
+ it "should use the bucket name as the location" do
55
+ Defog::Proxy.new(args).location.should == args[:bucket]
56
+ end
57
+ end
58
+
59
+ it "should raise error on bad provider" do
60
+ expect { Defog::Proxy.new(:provider => :nonesuch) }.should raise_error(ArgumentError)
61
+ end
62
+
63
+ end
@@ -0,0 +1,26 @@
1
+ if RUBY_VERSION > "1.9"
2
+ require 'simplecov'
3
+ require 'simplecov-gem-adapter'
4
+ SimpleCov.start 'gem'
5
+ end
6
+
7
+ require 'rspec'
8
+ require 'defog'
9
+
10
+ # Requires supporting files with custom matchers and macros, etc,
11
+ # in ./support/ and its subdirectories.
12
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
13
+
14
+ RSpec.configure do |config|
15
+
16
+ end
17
+
18
+ RSPEC_TMP_PATH = Pathname.new(__FILE__).dirname + "tmp"
19
+ PROXY_BASE_PATH = RSPEC_TMP_PATH + "proxy"
20
+ LOCAL_CLOUD_PATH = RSPEC_TMP_PATH + "cloud"
21
+
22
+ [PROXY_BASE_PATH, LOCAL_CLOUD_PATH].each do |path|
23
+ path.rmtree if path.exist?
24
+ path.mkpath
25
+ end
26
+
File without changes
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: defog
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - ronen barzel
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: fog
16
+ requirement: &70247567066900 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70247567066900
25
+ - !ruby/object:Gem::Dependency
26
+ name: hash_keyword_args
27
+ requirement: &70247567066480 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70247567066480
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ requirement: &70247567066040 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70247567066040
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ requirement: &70247567065600 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70247567065600
58
+ - !ruby/object:Gem::Dependency
59
+ name: simplecov
60
+ requirement: &70247567065000 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70247567065000
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov-gem-adapter
71
+ requirement: &70247567064460 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70247567064460
80
+ description: Wrapper to fog gem, proxying access to cloud files as local files.
81
+ email:
82
+ - ronen@barzel.org
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files: []
86
+ files:
87
+ - .gitignore
88
+ - Gemfile
89
+ - LICENSE
90
+ - README.rdoc
91
+ - Rakefile
92
+ - defog.gemspec
93
+ - lib/defog.rb
94
+ - lib/defog/error.rb
95
+ - lib/defog/file.rb
96
+ - lib/defog/fog_wrapper.rb
97
+ - lib/defog/proxy.rb
98
+ - lib/defog/version.rb
99
+ - spec/.gitignore
100
+ - spec/file_spec.rb
101
+ - spec/proxy_spec.rb
102
+ - spec/spec_helper.rb
103
+ - spec/support/connections.rb
104
+ homepage: http://github.com/ronen/defog
105
+ licenses: []
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 1.8.10
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: Wrapper to fog gem, proxying access to cloud files as local files. Access
128
+ can be read-only (local cache), write-only (upload), or read-write (mirror)
129
+ test_files:
130
+ - spec/file_spec.rb
131
+ - spec/proxy_spec.rb
132
+ - spec/spec_helper.rb
133
+ - spec/support/connections.rb
134
+ has_rdoc: