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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.rdoc +124 -0
- data/Rakefile +16 -0
- data/defog.gemspec +25 -0
- data/lib/defog.rb +5 -0
- data/lib/defog/error.rb +8 -0
- data/lib/defog/file.rb +83 -0
- data/lib/defog/fog_wrapper.rb +73 -0
- data/lib/defog/proxy.rb +85 -0
- data/lib/defog/version.rb +3 -0
- data/spec/.gitignore +1 -0
- data/spec/file_spec.rb +257 -0
- data/spec/proxy_spec.rb +63 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/connections.rb +0 -0
- metadata +134 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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.
|
data/README.rdoc
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
@@ -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
|
data/defog.gemspec
ADDED
@@ -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
|
data/lib/defog.rb
ADDED
data/lib/defog/error.rb
ADDED
data/lib/defog/file.rb
ADDED
@@ -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
|
+
|
data/lib/defog/proxy.rb
ADDED
@@ -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
|
data/spec/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
tmp/
|
data/spec/file_spec.rb
ADDED
@@ -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
|
data/spec/proxy_spec.rb
ADDED
@@ -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
|
data/spec/spec_helper.rb
ADDED
@@ -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:
|