dragonfly-fog_data_store 0.0.2 → 0.0.4

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