defog 0.0.1

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.
@@ -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: