dragonfly-fog_data_store 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1ecde557abb73476a4f9fdeca96dddf3ff674e28
4
- data.tar.gz: b77a20a8b7ec1c7fdd5cd7c863f274962fc68755
3
+ metadata.gz: 89d9745c0df7a506740f738e49d72069aadab2ca
4
+ data.tar.gz: 9fa161c005f635fa3b65e5072a96a87ee6cf0f3d
5
5
  SHA512:
6
- metadata.gz: 8c4d6662d03df0e686e036fea8e473cf04c7878f2a5b00fe2ef58eeda61b3208211b0edd5bf0fe95ac38372e737415ace9ef175b6a88edd12f9478175999ba33
7
- data.tar.gz: 1c8198aca12706c3795e197352a27b3717f1040e2a492b63ca73b0c887f03b1e13184fcba0846e14a6b2deb352f68ccd1d794ba37e39111b022d9a95f2fba84e
6
+ metadata.gz: c2753d6f7a7d578aa1c8415c2ca934f86d9f75645b19b6b8857b51deb0363d497e22557daaa3489da58296869c24c5b925c84d39e41e964123f7682181e3eff8
7
+ data.tar.gz: ad9d802aff75030fd9deecfcdf94efb4213433b22cd541896a76e3f9681a4f4ad39befe2ef31d1afc42f7cb136e942ac63bb44c1e4450de91b3e252a7e0ab0bc
data/.gitignore CHANGED
@@ -15,5 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
- .fog_spec.yml
19
- .DS_Store
18
+ .s3_spec.yml
19
+
data/Gemfile CHANGED
@@ -1,2 +1,2 @@
1
- source 'http://rubygems.org'
1
+ source 'https://rubygems.org'
2
2
  gemspec
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 Jimmy Hsu
1
+ Copyright (c) 2015 Jimmy Hsu
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,7 +1,49 @@
1
1
  # Dragonfly::FogDataStore
2
2
 
3
- This is shamelessly stolen from https://github.com/markevans/dragonfly-s3_data_store.
3
+ This is shamelessly stolen from
4
+ https://github.com/markevans/dragonfly-s3_data_store.
5
+
6
+ ## Gemfile
7
+
8
+ ```ruby
9
+ gem 'dragonfly-fog_data_store'
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ Configuration (remember the require)
15
+
16
+ ```ruby
17
+ require 'dragonfly/fog_data_store'
18
+
19
+ Dragonfly.app.configure do
20
+ # ...
21
+
22
+ datastore :fog,
23
+ container: 'my-container',
24
+ username: 'blahblahblah',
25
+ api_key: 'blublublublu',
26
+ region: 'ord'
27
+
28
+ # ...
29
+ end
30
+ ```
31
+
32
+ ### Available configuration options
33
+
34
+ ```ruby
35
+ :container
36
+ :username
37
+ :api_key
38
+ :region # See http://www.rackspace.com/knowledge_center/article/about-regions for options
39
+ :storage_headers # defaults to {}, can be overridden per-write - see below
40
+ ```
41
+
42
+ ### Serving directly from S3
43
+
44
+ You can get the S3 url using
45
+
46
+ ```ruby
47
+ my_model.attachment.url
48
+ ```
4
49
 
5
- There are failures in the specs arround mocking access to rackspace cloud
6
- storage.
7
- As such it's not meant for general consumption yet.
data/Rakefile CHANGED
@@ -1,2 +1 @@
1
1
  require "bundler/gem_tasks"
2
- require 'bundler'
@@ -6,10 +6,10 @@ require 'dragonfly/fog_data_store/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "dragonfly-fog_data_store"
8
8
  spec.version = Dragonfly::FogDataStore::VERSION
9
- spec.authors = ["jimmy"]
9
+ spec.authors = ["Jimmy Hsu"]
10
10
  spec.email = ["irregular.profit@gmail.com"]
11
- spec.description = %q{fog data store for Dragonfly}
12
- spec.summary = %q{Data store for storing Dragonfly content (e.g. images) on fog}
11
+ spec.description = %q{Racksapce data store for Dragonfly}
12
+ spec.summary = %q{Data store for storing Dragonfly content Rackspace through Fog}
13
13
  spec.homepage = "https://github.com/irregularprofit/dragonfly-fog_data_store"
14
14
  spec.license = "MIT"
15
15
 
@@ -19,6 +19,6 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_runtime_dependency "dragonfly", "~> 1.0"
22
- spec.add_runtime_dependency "fog", '~> 1.28.0'
23
- spec.add_development_dependency "rspec", "~> 2.0"
22
+ spec.add_runtime_dependency "fog", "~> 1.28.0"
23
+ spec.add_development_dependency "rspec", "~> 3.2"
24
24
  end
@@ -9,21 +9,25 @@ module Dragonfly
9
9
  # Exceptions
10
10
  class NotConfigured < RuntimeError; end
11
11
 
12
- def initialize(opts={})
13
- @bucket_name = opts[:bucket_name]
14
- @rackspace_region = opts[:rackspace_region]
15
- @rackspace_api_key = opts[:rackspace_api_key]
16
- @rackspace_username = opts[:rackspace_username]
12
+ REGIONS = [:dfw, :ord, :iad, :lon, :syd, :hkg]
17
13
 
18
- @url_scheme = opts[:url_scheme] || 'http'
19
- @url_host = opts[:url_host]
14
+ def initialize(opts={})
15
+ @container = opts[:container]
16
+ @username = opts[:username]
17
+ @api_key = opts[:api_key]
18
+ @region = opts[:region]
19
+ @storage_headers = opts[:storage_headers] || {}
20
+
21
+ @url_scheme = opts[:url_scheme] || 'http'
22
+ @url_host = opts[:url_host]
20
23
  end
21
24
 
22
- attr_accessor :bucket_name, :rackspace_api_key, :rackspace_username, :url_scheme, :url_host, :rackspace_region
25
+ attr_accessor :container, :username, :api_key, :region,
26
+ :url_scheme, :url_host, :storage_headers
23
27
 
24
28
  def write(content, opts={})
25
29
  ensure_configured
26
- ensure_bucket_initialized
30
+ ensure_container_initialized
27
31
 
28
32
  headers = {'Content-Type' => content.mime_type}
29
33
  headers.merge!(opts[:headers]) if opts[:headers]
@@ -31,7 +35,7 @@ module Dragonfly
31
35
 
32
36
  rescuing_socket_errors do
33
37
  content.file do |f|
34
- storage.put_object(bucket_name, uid, f, headers)
38
+ storage.put_object(container, full_path(uid), f, full_storage_headers(headers, content.meta))
35
39
  end
36
40
  end
37
41
 
@@ -40,43 +44,40 @@ module Dragonfly
40
44
 
41
45
  def read(uid)
42
46
  ensure_configured
43
- response = rescuing_socket_errors{ storage.get_object(bucket_name, uid) }
44
- [response.body, response.headers]
47
+
48
+ response = rescuing_socket_errors{ storage.get_object(container, full_path(uid)) }
49
+ [response.body, headers_to_meta(response.headers)]
50
+ rescue Fog::Storage::Rackspace::NotFound
51
+ nil
45
52
  rescue Excon::Errors::NotFound => e
46
53
  nil
47
54
  end
48
55
 
49
56
  def destroy(uid)
50
- rescuing_socket_errors{ storage.delete_object(bucket_name, uid) }
57
+ rescuing_socket_errors{ storage.delete_object(container, full_path(uid)) }
58
+ rescue Fog::Storage::Rackspace::NotFound
59
+ nil
51
60
  rescue Excon::Errors::NotFound, Excon::Errors::Conflict => e
52
61
  Dragonfly.warn("#{self.class.name} destroy error: #{e}")
53
62
  end
54
63
 
55
- def url_for(uid, opts={})
56
- if opts && opts[:expires]
57
- storage.get_object_https_url(bucket_name, uid, opts[:expires])
58
- else
59
- scheme = opts[:scheme] || url_scheme
60
- host = opts[:host] || url_host
61
- "#{scheme}://#{host}/#{uid}"
62
- end
63
- end
64
-
65
64
  def storage
66
65
  @storage ||= begin
67
66
  storage = Fog::Storage.new({
68
67
  provider: 'Rackspace',
69
- rackspace_region: rackspace_region,
70
- rackspace_api_key: rackspace_api_key,
71
- rackspace_username: rackspace_username
72
- }.reject {|name, option| option.nil?})
68
+ rackspace_username: username,
69
+ rackspace_api_key: api_key,
70
+ rackspace_region: region
71
+ })
73
72
  storage
74
73
  end
75
74
  end
76
75
 
77
- def bucket_exists?
78
- #rescuing_socket_errors{ storage.get_bucket_location(bucket_name) }
76
+ def container_exists?
77
+ rescuing_socket_errors{ storage.get_container(container) }
79
78
  true
79
+ rescue Fog::Storage::Rackspace::NotFound
80
+ nil
80
81
  rescue Excon::Errors::NotFound => e
81
82
  false
82
83
  end
@@ -85,24 +86,49 @@ module Dragonfly
85
86
 
86
87
  def ensure_configured
87
88
  unless @configured
88
- [:bucket_name, :rackspace_api_key, :rackspace_username, :rackspace_region].each do |attr|
89
+ [:container, :username, :api_key, :container].each do |attr|
89
90
  raise NotConfigured, "You need to configure #{self.class.name} with #{attr}" if send(attr).nil?
90
91
  end
91
92
  @configured = true
92
93
  end
93
94
  end
94
95
 
95
- def ensure_bucket_initialized
96
- unless @bucket_initialized
97
- rescuing_socket_errors{ storage.put_bucket(bucket_name, 'LocationConstraint' => rackspace_region) } unless bucket_exists?
98
- @bucket_initialized = true
96
+ def ensure_container_initialized
97
+ unless @container_initialized
98
+ rescuing_socket_errors{ storage.put_container(container) } unless container_exists?
99
+ @container_initialized = true
99
100
  end
100
101
  end
101
102
 
103
+ def get_region
104
+ reg = region || :ord
105
+ raise "Invalid region #{reg} - should be one of #{REGIONS.join(', ')}" unless REGIONS.include?(reg)
106
+ reg
107
+ end
108
+
102
109
  def generate_uid(name)
103
110
  "#{Time.now.strftime '%Y/%m/%d/%H/%M/%S'}/#{rand(1000)}/#{name.gsub(/[^\w.]+/, '_')}"
104
111
  end
105
112
 
113
+ def full_path(uid)
114
+ File.join *[uid].compact
115
+ end
116
+
117
+ def full_storage_headers(headers, meta)
118
+ storage_headers.merge(meta_to_headers(meta)).merge(headers)
119
+ end
120
+
121
+ def headers_to_meta(headers)
122
+ json = headers['X-Object-Meta']
123
+ if json && !json.empty?
124
+ Serializer.json_decode(json)
125
+ end
126
+ end
127
+
128
+ def meta_to_headers(meta)
129
+ {'X-Object-Meta' => Serializer.json_encode(meta)}
130
+ end
131
+
106
132
  def rescuing_socket_errors(&block)
107
133
  yield
108
134
  rescue Excon::Errors::SocketError => e
@@ -112,4 +138,3 @@ module Dragonfly
112
138
 
113
139
  end
114
140
  end
115
-
@@ -1,5 +1,5 @@
1
1
  module Dragonfly
2
2
  class FogDataStore
3
- VERSION = "0.0.2"
3
+ VERSION = "0.0.4"
4
4
  end
5
5
  end
@@ -17,39 +17,18 @@ describe Dragonfly::FogDataStore do
17
17
  else
18
18
  enabled = false
19
19
  end
20
-
21
- if enabled
22
-
23
- # Make sure it's a new bucket name
24
- BUCKET_NAME = "dragonfly-test-#{Time.now.to_i.to_s(36)}"
25
- REGION = :ord
26
-
27
- before(:each) do
28
- @data_store = Dragonfly::FogDataStore.new(
29
- bucket_name: BUCKET_NAME,
30
- provider: 'Rackspace',
31
- rackspace_api_key: KEY,
32
- rackspace_username: SECRET,
33
- rackspace_region: REGION
34
- )
35
- end
36
-
37
- else
38
-
39
- BUCKET_NAME = 'test-bucket'
40
- REGION = :ord
41
-
42
- before(:each) do
43
- Fog.mock!
44
- @data_store = Dragonfly::FogDataStore.new(
45
- bucket_name: BUCKET_NAME,
46
- provider: 'Rackspace',
47
- rackspace_api_key: 'XXXXXXXXX',
48
- rackspace_username: 'XXXXXXXXX',
49
- rackspace_region: REGION
50
- )
51
- end
52
-
20
+ CONTAINER = 'test-container'
21
+
22
+ before(:each) do
23
+ Fog.mock!
24
+ @data_store = Dragonfly::FogDataStore.new(
25
+ provider: 'Rackspace',
26
+ username: 'XXXXXXXXX',
27
+ api_key: 'XXXXXXXXX',
28
+ region: 'ord',
29
+ secret: 'super-secret-key-no-one-can-guess',
30
+ container: CONTAINER
31
+ )
53
32
  end
54
33
 
55
34
  it_should_behave_like 'data_store'
@@ -63,7 +42,7 @@ describe Dragonfly::FogDataStore do
63
42
  app.configure do
64
43
  datastore :fog
65
44
  end
66
- app.datastore.should be_a(Dragonfly::FogDataStore)
45
+ expect(app.datastore).to be_a(Dragonfly::FogDataStore)
67
46
  end
68
47
  end
69
48
 
@@ -71,97 +50,121 @@ describe Dragonfly::FogDataStore do
71
50
  it "should use the name from the content if set" do
72
51
  content.name = 'doobie.doo'
73
52
  uid = @data_store.write(content)
74
- uid.should =~ /doobie\.doo$/
53
+ expect(uid).to match(/doobie\.doo$/)
75
54
  new_content.update(*@data_store.read(uid))
76
- new_content.data.should == 'eggheads'
55
+ expect(new_content.data).to eq('eggheads')
77
56
  end
78
57
 
79
58
  it "should work ok with files with funny names" do
80
59
  content.name = "A Picture with many spaces in its name (at 20:00 pm).png"
81
60
  uid = @data_store.write(content)
82
- uid.should =~ /A_Picture_with_many_spaces_in_its_name_at_20_00_pm_\.png$/
61
+ expect(uid).to match(/A_Picture_with_many_spaces_in_its_name_at_20_00_pm_\.png$/)
83
62
  new_content.update(*@data_store.read(uid))
84
- new_content.data.should == 'eggheads'
63
+ expect(new_content.data).to eq('eggheads')
85
64
  end
86
65
 
87
66
  it "should allow for setting the path manually" do
88
67
  uid = @data_store.write(content, path: 'hello/there')
89
- uid.should == 'hello/there'
68
+ expect(uid).to eq('hello/there')
90
69
  new_content.update(*@data_store.read(uid))
91
- new_content.data.should == 'eggheads'
70
+ expect(new_content.data).to eq('eggheads')
92
71
  end
93
72
 
94
- if enabled # Fog.mock! doesn't act consistently here
95
- it "should reset the connection and try again if Fog throws a socket EOFError" do
96
- @data_store.storage.should_receive(:put_object).exactly(:once).and_raise(Excon::Errors::SocketError.new(EOFError.new))
97
- @data_store.storage.should_receive(:put_object).with(BUCKET_NAME, anything, anything, hash_including)
98
- @data_store.write(content)
99
- end
100
-
101
- it "should just let it raise if Fog throws a socket EOFError again" do
102
- @data_store.storage.should_receive(:put_object).and_raise(Excon::Errors::SocketError.new(EOFError.new))
103
- @data_store.storage.should_receive(:put_object).and_raise(Excon::Errors::SocketError.new(EOFError.new))
104
- expect{
105
- @data_store.write(content)
106
- }.to raise_error(Excon::Errors::SocketError)
107
- end
108
- end
109
73
  end
110
74
 
111
75
  describe "not configuring stuff properly" do
112
- it "should require a bucket name on write" do
113
- @data_store.bucket_name = nil
114
- proc{ @data_store.write(content) }.should raise_error(Dragonfly::FogDataStore::NotConfigured)
76
+ it "should require a container name on write" do
77
+ @data_store.container = nil
78
+ expect{ @data_store.write(content) }.to raise_error(Dragonfly::FogDataStore::NotConfigured)
115
79
  end
116
80
 
117
- it "should require an rackspace_api_key on write" do
118
- @data_store.rackspace_api_key = nil
119
- proc{ @data_store.write(content) }.should raise_error(Dragonfly::FogDataStore::NotConfigured)
81
+ it "should require an username on write" do
82
+ @data_store.username = nil
83
+ expect{ @data_store.write(content) }.to raise_error(Dragonfly::FogDataStore::NotConfigured)
120
84
  end
121
85
 
122
86
  it "should require a secret access key on write" do
123
- @data_store.rackspace_username = nil
124
- proc{ @data_store.write(content) }.should raise_error(Dragonfly::FogDataStore::NotConfigured)
87
+ @data_store.api_key = nil
88
+ expect{ @data_store.write(content) }.to raise_error(Dragonfly::FogDataStore::NotConfigured)
125
89
  end
126
90
 
127
- it "should require a bucket name on read" do
128
- @data_store.bucket_name = nil
129
- proc{ @data_store.read('asdf') }.should raise_error(Dragonfly::FogDataStore::NotConfigured)
91
+ it "should require a container name on read" do
92
+ @data_store.container = nil
93
+ expect{ @data_store.read('asdf') }.to raise_error(Dragonfly::FogDataStore::NotConfigured)
130
94
  end
131
95
 
132
- it "should require an rackspace_api_key on read" do
133
- @data_store.rackspace_api_key = nil
134
- proc{ @data_store.read('asdf') }.should raise_error(Dragonfly::FogDataStore::NotConfigured)
96
+ it "should require an username on read" do
97
+ @data_store.username = nil
98
+ expect{ @data_store.read('asdf') }.to raise_error(Dragonfly::FogDataStore::NotConfigured)
135
99
  end
136
100
 
137
101
  it "should require a secret access key on read" do
138
- @data_store.rackspace_username = nil
139
- proc{ @data_store.read('asdf') }.should raise_error(Dragonfly::FogDataStore::NotConfigured)
102
+ @data_store.api_key = nil
103
+ expect{ @data_store.read('asdf') }.to raise_error(Dragonfly::FogDataStore::NotConfigured)
140
104
  end
105
+ end
141
106
 
142
- if !enabled #this will fail since the specs are not running on an ec2 instance with an iam role defined
143
- it 'should allow missing secret key and access key on write if iam profiles are allowed' do
144
- # This is slightly brittle but it's annoying waiting for fog doing stuff
145
- @data_store.storage.stub(get_bucket_location: nil, put_object: nil)
146
-
147
- @data_store.rackspace_username = nil
148
- @data_store.rackspace_api_key = nil
149
- expect{ @data_store.write(content) }.not_to raise_error
150
- end
107
+ describe "autocreating the container" do
108
+ it "should create the container on write if it doesn't exist" do
109
+ @data_store.container = "dragonfly-test-blah-blah-#{rand(100000000)}"
110
+ @data_store.write(content)
151
111
  end
152
112
 
113
+ it "should not try to create the container on read if it doesn't exist" do
114
+ @data_store.container = "dragonfly-test-blah-blah-#{rand(100000000)}"
115
+ expect(@data_store.send(:storage)).to_not receive(:put_container)
116
+ expect(@data_store.read("gungle")).to be_nil
117
+ end
153
118
  end
154
119
 
155
- describe "autocreating the bucket" do
156
- it "should create the bucket on write if it doesn't exist" do
157
- @data_store.bucket_name = "dragonfly-test-blah-blah-#{rand(100000000)}"
120
+ describe "headers" do
121
+ before(:each) do
122
+ @data_store.storage_headers = {'x-fog-foo' => 'biscuithead'}
123
+ end
124
+
125
+ it "should allow configuring globally" do
126
+ expect(@data_store.storage).to receive(:put_object).with(CONTAINER, anything, anything,
127
+ hash_including('x-fog-foo' => 'biscuithead')
128
+ )
129
+ @data_store.write(content)
130
+ end
131
+
132
+ it "should allow adding per-store" do
133
+ expect(@data_store.storage).to receive(:put_object).with(CONTAINER, anything, anything,
134
+ hash_including('x-fog-foo' => 'biscuithead', 'hello' => 'there')
135
+ )
136
+ @data_store.write(content, headers: {'hello' => 'there'})
137
+ end
138
+
139
+ it "should let the per-store one take precedence" do
140
+ expect(@data_store.storage).to receive(:put_object).with(CONTAINER, anything, anything,
141
+ hash_including('x-fog-foo' => 'override!')
142
+ )
143
+ @data_store.write(content, headers: {'x-fog-foo' => 'override!'})
144
+ end
145
+
146
+ it "should write setting the content type" do
147
+ expect(@data_store.storage).to receive(:put_object) do |_, __, ___, headers|
148
+ expect(headers['Content-Type']).to eq('image/png')
149
+ end
150
+ content.name = 'egg.png'
158
151
  @data_store.write(content)
159
152
  end
160
153
 
161
- it "should not try to create the bucket on read if it doesn't exist" do
162
- @data_store.bucket_name = "dragonfly-test-blah-blah-#{rand(100000000)}"
163
- @data_store.send(:storage).should_not_receive(:put_bucket)
164
- @data_store.read("gungle").should be_nil
154
+ it "allow overriding the content type" do
155
+ expect(@data_store.storage).to receive(:put_object) do |_, __, ___, headers|
156
+ expect(headers['Content-Type']).to eq('text/plain')
157
+ end
158
+ content.name = 'egg.png'
159
+ @data_store.write(content, headers: {'Content-Type' => 'text/plain'})
160
+ end
161
+ end
162
+
163
+ describe "meta" do
164
+ it "uses the X-Object-Meta header for meta" do
165
+ uid = @data_store.write(content, headers: {'X-Object-Meta' => Dragonfly::Serializer.json_encode({potato: 44})})
166
+ c, meta = @data_store.read(uid)
167
+ expect(meta['potato']).to eq(44)
165
168
  end
166
169
  end
167
170
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dragonfly-fog_data_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
- - jimmy
7
+ - Jimmy Hsu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-26 00:00:00.000000000 Z
11
+ date: 2015-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dragonfly
@@ -44,15 +44,15 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '2.0'
47
+ version: '3.2'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '2.0'
55
- description: fog data store for Dragonfly
54
+ version: '3.2'
55
+ description: Racksapce data store for Dragonfly
56
56
  email:
57
57
  - irregular.profit@gmail.com
58
58
  executables: []
@@ -92,7 +92,7 @@ rubyforge_project:
92
92
  rubygems_version: 2.4.6
93
93
  signing_key:
94
94
  specification_version: 4
95
- summary: Data store for storing Dragonfly content (e.g. images) on fog
95
+ summary: Data store for storing Dragonfly content Rackspace through Fog
96
96
  test_files:
97
97
  - spec/fog_data_store_spec.rb
98
98
  - spec/spec_helper.rb