activestorage-openstack 1.4.2 → 1.5.0

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
  SHA256:
3
- metadata.gz: cce4c99f0a5a3c5cbdef328ae3633196657a04a8c9ba801fe82904b1f0283abf
4
- data.tar.gz: 2cf3bcee0842a884a11ae611e7c39cd0a059e690cb2f9872e647999274b68490
3
+ metadata.gz: dbc6dd562ea11df43aac0d26b9f0d4c0aca0b8b9eef91cbcd7c570db7b9e9a35
4
+ data.tar.gz: f8320f16bdfc5bcaeb9d1b1f9683c32836fd53d9c336c5f88c25a441b4050a98
5
5
  SHA512:
6
- metadata.gz: 9a467d3b91dd8c2be7cad76e02f223b43bb4ccbe82b26b19459fb9388f7c21e2e5d31ce051ab325d225159c377157fb96d3701d58b2e0b870edeb4aea5ea2660
7
- data.tar.gz: a4ee1f33629ee2ad84ccba999290f27c495a412e867242a97ff5952e2dde22baab641ad1600db2afa47b2aa671f6a22917a79cd576168cd38bf349b246923788
6
+ metadata.gz: 8e45d6474289076b867a5bc64d98f7a0b7e802330884fc3da5154c9a973736130d7a926b01e2ed4387fd6e02bb1d0c2cac1ab00a65ba9381abc84938fe501b92
7
+ data.tar.gz: cfb46a535ec45c8f137c59e8c6f1c18b68a184156c68fb79d1abe86839b03ceaf8b40f8fc9a8c17d07db12f7edfc234823bb81c128651cbb8e5a74f14d257c58
data/README.md CHANGED
@@ -5,8 +5,6 @@ This rails plugin wraps the OpenStack Swift provider as an Active Storage servic
5
5
  [![Build Status](https://travis-ci.com/chaadow/activestorage-openstack.svg?branch=master)](https://travis-ci.com/chaadow/activestorage-openstack)
6
6
  [![Maintainability](https://api.codeclimate.com/v1/badges/4c070c101f86a579516f/maintainability)](https://codeclimate.com/github/chaadow/activestorage-openstack/maintainability)
7
7
  [![Test Coverage](https://api.codeclimate.com/v1/badges/4c070c101f86a579516f/test_coverage)](https://codeclimate.com/github/chaadow/activestorage-openstack/test_coverage)
8
-
9
-
10
8
  Starting from version `0.4`, this gem enforces version `>= 0.2.2` of `fog-openstack` which introduces breaking changes to the configuration keys in `config/storage.yml`. Please read the [MIGRATING from `0.1.x` to `0.2.x`](#migrating-from-fog-openstack-01x-to-02x) section
11
9
 
12
10
  **This gem currently supports `fog-openstack` version `~ 1.0`**
@@ -29,11 +27,15 @@ $ gem install activestorage-openstack
29
27
  ```
30
28
 
31
29
  ## Usage
32
- in `config/storage.yml`, in your Rails app, create an entry with the following keys:
30
+ in `config/storage.yml`, in your Rails app, you can create as many entries as
31
+ you wish. Here is an example with rails 6.1 new support for public containers
32
+
33
33
  ```yaml
34
- dev_openstack:
34
+
35
+ # Here you can have the common authentication credentials and config
36
+ # by defining a YAML anchor
37
+ default_config: &default_config
35
38
  service: OpenStack
36
- container: <container name> # Container name for your OpenStack provider
37
39
  credentials:
38
40
  openstack_auth_url: <auth url>
39
41
  openstack_username: <username>
@@ -42,19 +44,32 @@ dev_openstack:
42
44
  openstack_temp_url_key: <temp url key> # Mandatory, instructions below
43
45
  connection_options: # optional
44
46
  chunk_size: 2097152 # 2MBs - 1MB is the default
47
+
48
+ # starting from rails 6.1, you can have a public container generating public
49
+ # URLs
50
+ public_openstack:
51
+ <<: *default_config # we include the anchor defined above
52
+ public: true # important ; to tell rails that this is a public container
53
+ container: <container name> # Container name for your public OpenStack provider
54
+
55
+ # this config will generate signed/expired URLs (aka. private URLs)
56
+ private_openstack:
57
+ <<: *default_config # we include the anchor defined above
58
+ public: false # Optional in this case, because false is the default value
59
+ container: <container name> # Container name for your private OpenStack provider
45
60
  ```
46
61
 
47
- You can create as many entries as you would like for your different environments. For instance: `dev_openstack` for development, `test_openstack` for test environment, and `prod_openstack` for production. This way you can choose the appropriate container for each scenario.
62
+ You can create as many entries as you would like for your different environments. For instance: `public_openstack` for development, `test_openstack` for test environment, and `prod_openstack` for production. This way you can choose the appropriate container for each scenario.
48
63
 
49
64
  Then register the provider in your `config/{environment}.rb` (`config/development.rb`/`config/test.rb`/`config/production.rb`)
50
65
 
51
- For example, for the `dev_openstack` entry above, change the `config` variable in `config/development.rb` like the following:
66
+ For example, for the `public_openstack` entry above, change the `config` variable in `config/development.rb` like the following:
52
67
  ```ruby
53
68
  # Store uploaded files on the local file system (see config/storage.yml for options)
54
- config.active_storage.service = :dev_openstack
69
+ config.active_storage.service = :public_openstack
55
70
  ```
56
71
 
57
- ## Migrating from fog-openstack `0.1.x` to `0.2.x`
72
+ ## Migrating from fog-openstack `~> 0.1.x` to `>= 0.2.x`
58
73
 
59
74
  1- From your configuration file (`config/storage.yml`) change the `openstack_auth_uri` from :
60
75
  ```yaml
@@ -89,9 +104,16 @@ The next version of this plugin, will add a rails generator, or expose a method
89
104
 
90
105
  ## `ActiveStorage::Openstack`'s Content-Type handling
91
106
 
92
- OpenStack Swift handles the Content-Type of an object differently from other object storage services. You cannot overwrite the Content-Type via a temp URL. This gem will try very hard to set the right Content-Type for an object at object creation (either via server upload or direct upload) but this is wrong in some edge cases (e.g. you use direct upload and the browser provides a wrong mime type).
93
-
94
- For these edge cases `ActiveStorage::Blob::Identifiable` downloads the first 4K of a file, identifies the content type and saves the result in the database. For `ActiveStorage::Openstack` we also need to update the Content-Type of the object. This is done automatically with a little monkey patch.
107
+ OpenStack Swift handles the Content-Type of an object differently from other
108
+ object storage services.
109
+ You cannot overwrite the Content-Type via a temp URL. This gem will try very
110
+ hard to set the right Content-Type for an object at
111
+ object creation (either via server upload or direct upload) but this can be
112
+ wrong in some edge cases (e.g. you use direct upload and the browser provides
113
+ a wrong mime type).
114
+ Thankfully, by implementing the rails hook `#update_metadata`
115
+ this will update the object in your container by setting the new content type
116
+ after it's been uploaded.
95
117
 
96
118
  ## Testing
97
119
  First, run `bundle` to install the gem dependencies (both development and production)
@@ -1,5 +1,5 @@
1
1
  module ActiveStorage
2
2
  module Openstack
3
- VERSION = '1.4.2'
3
+ VERSION = '1.5.0'
4
4
  end
5
5
  end
@@ -1,170 +1,188 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fog/openstack'
2
4
 
3
5
  module ActiveStorage
4
- class Service::OpenStackService < Service
5
- attr_reader :client, :container
6
-
7
- def initialize(container:, credentials:, connection_options: {})
8
- settings = credentials.reverse_merge(connection_options: connection_options)
9
-
10
- @client = Fog::OpenStack::Storage.new(settings)
11
- @container = Fog::OpenStack.escape(container)
12
- end
13
-
14
- def upload(key, io, checksum: nil, disposition: nil, content_type: nil, filename: nil, **)
15
- instrument :upload, key: key, checksum: checksum do
16
- params = { 'Content-Type' => content_type || guess_content_type(io) }
17
- params['ETag'] = convert_base64digest_to_hexdigest(checksum) if checksum
18
- if disposition && filename
19
- params['Content-Disposition'] =
20
- content_disposition_with(type: disposition, filename: filename)
21
- end
6
+ class Service
7
+ # ActiveStorage provider for OpenStack
8
+ class OpenStackService < Service
9
+ attr_reader :client, :container
10
+
11
+ def initialize(container:, credentials:, public: false, connection_options: {})
12
+ super()
13
+ settings = credentials.reverse_merge(connection_options: connection_options)
14
+
15
+ @public = public
16
+ @client = Fog::OpenStack::Storage.new(settings)
17
+ @container = Fog::OpenStack.escape(container)
18
+ end
22
19
 
23
- begin
24
- client.put_object(container, key, io, params)
25
- rescue Excon::Error::UnprocessableEntity
26
- raise ActiveStorage::IntegrityError
20
+ def upload(key, io, checksum: nil, disposition: nil, content_type: nil, filename: nil, **)
21
+ instrument :upload, key: key, checksum: checksum do
22
+ params = { 'Content-Type' => content_type || guess_content_type(io) }
23
+ params['ETag'] = convert_base64digest_to_hexdigest(checksum) if checksum
24
+ if disposition && filename
25
+ params['Content-Disposition'] =
26
+ content_disposition_with(type: disposition, filename: filename)
27
+ end
28
+
29
+ begin
30
+ client.put_object(container, key, io, params)
31
+ rescue Excon::Error::UnprocessableEntity
32
+ raise ActiveStorage::IntegrityError
33
+ end
27
34
  end
28
35
  end
29
- end
30
36
 
31
- def download(key, &block)
32
- if block_given?
33
- instrument :streaming_download, key: key do
34
- object_for(key, &block)
35
- end
36
- else
37
- instrument :download, key: key do
38
- object_for(key).body
37
+ def download(key, &block)
38
+ if block_given?
39
+ instrument :streaming_download, key: key do
40
+ object_for(key, &block)
41
+ end
42
+ else
43
+ instrument :download, key: key do
44
+ object_for(key).body
45
+ end
39
46
  end
47
+ rescue Fog::OpenStack::Storage::NotFound
48
+ raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
40
49
  end
41
- rescue Fog::OpenStack::Storage::NotFound
42
- raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
43
- end
44
50
 
45
- def download_chunk(key, range)
46
- instrument :download_chunk, key: key, range: range do
47
- chunk_buffer = []
51
+ def download_chunk(key, range)
52
+ instrument :download_chunk, key: key, range: range do
53
+ chunk_buffer = []
48
54
 
49
- object_for(key) do |chunk|
50
- chunk_buffer << chunk
51
- end
55
+ object_for(key) do |chunk|
56
+ chunk_buffer << chunk
57
+ end
52
58
 
53
- chunk_buffer.join[range]
54
- rescue Fog::OpenStack::Storage::NotFound
55
- raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
59
+ chunk_buffer.join[range]
60
+ rescue Fog::OpenStack::Storage::NotFound
61
+ raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
62
+ end
56
63
  end
57
- end
58
64
 
59
- def delete(key)
60
- instrument :delete, key: key do
61
- client.delete_object(container, key)
62
- rescue Fog::OpenStack::Storage::NotFound
63
- false
65
+ def delete(key)
66
+ instrument :delete, key: key do
67
+ client.delete_object(container, key)
68
+ rescue Fog::OpenStack::Storage::NotFound
69
+ false
70
+ end
64
71
  end
65
- end
66
72
 
67
- def delete_prefixed(prefix)
68
- instrument :delete, prefix: prefix do
69
- directory = client.directories.get(container)
70
- filtered_files = client.files(directory: directory, prefix: prefix)
71
- filtered_files = filtered_files.map(&:key)
73
+ def delete_prefixed(prefix)
74
+ instrument :delete, prefix: prefix do
75
+ directory = client.directories.get(container)
76
+ filtered_files = client.files(directory: directory, prefix: prefix)
77
+ filtered_files = filtered_files.map(&:key)
72
78
 
73
- client.delete_multiple_objects(container, filtered_files)
79
+ client.delete_multiple_objects(container, filtered_files)
80
+ end
74
81
  end
75
- end
76
82
 
77
- def exist?(key)
78
- instrument :exist, key: key do |payload|
79
- answer = head_for(key)
80
- payload[:exist] = answer.present?
81
- rescue Fog::OpenStack::Storage::NotFound
82
- payload[:exist] = false
83
+ def exist?(key)
84
+ instrument :exist, key: key do |payload|
85
+ answer = head_for(key)
86
+ payload[:exist] = answer.present?
87
+ rescue Fog::OpenStack::Storage::NotFound
88
+ payload[:exist] = false
89
+ end
83
90
  end
84
- end
85
91
 
86
- def url(key, expires_in:, disposition:, filename:, **)
87
- instrument :url, key: key do |payload|
88
- expire_at = unix_timestamp_expires_at(expires_in)
89
- generated_url =
90
- client.get_object_https_url(
92
+ def url(key, **options)
93
+ if ActiveStorage.version < Gem::Version.new('6.1-alpha')
94
+ instrument :url, key: key do |payload|
95
+ private_url(key, **options).tap do |generated_url|
96
+ payload[:url] = generated_url
97
+ end
98
+ end
99
+ else
100
+ super
101
+ end
102
+ end
103
+
104
+ def url_for_direct_upload(key, expires_in:, filename:, **)
105
+ instrument :url, key: key do |payload|
106
+ expire_at = unix_timestamp_expires_at(expires_in)
107
+ generated_url = client.create_temp_url(
91
108
  container,
92
109
  key,
93
110
  expire_at,
94
- filename: filename
111
+ 'PUT',
112
+ port: 443,
113
+ filename: ActiveStorage::Filename.wrap(filename).sanitized.to_s,
114
+ scheme: 'https'
95
115
  )
96
- generated_url += '&inline' if disposition.to_s != 'attachment'
97
- # unfortunately OpenStack Swift cannot overwrite the content type of an object via a temp url
98
- # so we just ignore the content_type argument here
99
- payload[:url] = generated_url
100
116
 
101
- generated_url
102
- end
103
- end
117
+ payload[:url] = generated_url
104
118
 
105
- def url_for_direct_upload(key, expires_in:, **)
106
- instrument :url, key: key do |payload|
107
- expire_at = unix_timestamp_expires_at(expires_in)
108
- generated_url = client.create_temp_url(container,
109
- key,
110
- expire_at,
111
- 'PUT',
112
- port: 443,
113
- scheme: 'https')
119
+ generated_url
120
+ end
121
+ end
114
122
 
115
- payload[:url] = generated_url
123
+ def headers_for_direct_upload(_key, content_type:, checksum:, **)
124
+ {
125
+ 'Content-Type' => content_type,
126
+ 'ETag' => convert_base64digest_to_hexdigest(checksum)
127
+ }
128
+ end
116
129
 
117
- generated_url
130
+ def update_metadata(key, content_type:, disposition: nil, filename: nil, **)
131
+ instrument :update_metadata, key: key, content_type: content_type, disposition: disposition do
132
+ params = { 'Content-Type' => content_type }
133
+ if disposition && filename
134
+ params['Content-Disposition'] =
135
+ content_disposition_with(
136
+ type: disposition,
137
+ filename: ActiveStorage::Filename.wrap(filename)
138
+ )
139
+ end
140
+ client.post_object(container, key, params)
141
+ true
142
+ rescue Fog::OpenStack::Storage::NotFound
143
+ raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
144
+ end
118
145
  end
119
- end
120
146
 
121
- def headers_for_direct_upload(_key, content_type:, checksum:, **)
122
- {
123
- 'Content-Type' => content_type,
124
- 'ETag' => convert_base64digest_to_hexdigest(checksum)
125
- }
126
- end
147
+ private
127
148
 
128
- def update_metadata(key, content_type:, disposition: nil, filename: nil, **)
129
- instrument :update_metadata, key: key, content_type: content_type, disposition: disposition do
130
- params = { 'Content-Type' => content_type }
131
- if disposition && filename
132
- params['Content-Disposition'] =
133
- content_disposition_with(type: disposition, filename: ActiveStorage::Filename.wrap(filename))
134
- end
135
- client.post_object(container,
136
- key,
137
- params)
138
- true
139
- rescue Fog::OpenStack::Storage::NotFound
140
- raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
149
+ def head_for(key)
150
+ client.head_object(container, key)
141
151
  end
142
- end
143
152
 
144
- private
153
+ def object_for(key, &block)
154
+ client.get_object(container, key, &block)
155
+ end
145
156
 
146
- def head_for(key)
147
- client.head_object(container, key)
148
- end
157
+ # ActiveStorage sends a `Digest::MD5.base64digest` checksum
158
+ # OpenStack expects a `Digest::MD5.hexdigest` ETag
159
+ def convert_base64digest_to_hexdigest(base64digest)
160
+ base64digest.unpack1('m0').unpack1('H*')
161
+ end
149
162
 
150
- def object_for(key, &block)
151
- client.get_object(container, key, &block)
152
- end
163
+ def unix_timestamp_expires_at(seconds_from_now)
164
+ Time.current.advance(seconds: seconds_from_now).to_i
165
+ end
153
166
 
154
- # ActiveStorage sends a `Digest::MD5.base64digest` checksum
155
- # OpenStack expects a `Digest::MD5.hexdigest` ETag
156
- def convert_base64digest_to_hexdigest(base64digest)
157
- base64digest.unpack1('m0').unpack1('H*')
158
- end
167
+ def guess_content_type(io)
168
+ Marcel::MimeType.for io,
169
+ name: io.try(:original_filename),
170
+ declared_type: io.try(:content_type)
171
+ end
159
172
 
160
- def unix_timestamp_expires_at(seconds_from_now)
161
- Time.current.advance(seconds: seconds_from_now).to_i
162
- end
173
+ def private_url(key, expires_in:, filename:, disposition:, **)
174
+ expire_at = unix_timestamp_expires_at(expires_in)
175
+ generated_url = client.get_object_https_url(container, key, expire_at, filename: filename.sanitized)
176
+ generated_url += '&inline' if disposition.to_s != 'attachment'
177
+ # unfortunately OpenStack Swift cannot overwrite the content type of an object via a temp url
178
+ # so we just ignore the content_type argument here
163
179
 
164
- def guess_content_type(io)
165
- Marcel::MimeType.for io,
166
- name: io.try(:original_filename),
167
- declared_type: io.try(:content_type)
180
+ generated_url
181
+ end
182
+
183
+ def public_url(key, **)
184
+ client.public_url(container, key)
185
+ end
168
186
  end
169
187
  end
170
188
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activestorage-openstack
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chedli Bourguiba
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-11-11 00:00:00.000000000 Z
11
+ date: 2020-12-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fog-openstack
@@ -39,33 +39,33 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: mime-types
42
+ name: rails
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: 5.2.2
48
48
  type: :runtime
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: '0'
54
+ version: 5.2.2
55
55
  - !ruby/object:Gem::Dependency
56
- name: rails
56
+ name: appraisal
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 5.2.2
62
- type: :runtime
61
+ version: '0'
62
+ type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 5.2.2
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: sqlite3
71
71
  requirement: !ruby/object:Gem::Requirement