activestorage-openstack 1.4.1 → 1.5.2

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: f68dff9e3fe5b903629fd687a0597303d5e8d4d0d89e9854cbb6c7e0804e95ae
4
- data.tar.gz: ea0b21d646385009d55e8a663d3ebf2621757078fa381f84c234dbbfba646d98
3
+ metadata.gz: 43f1f0f286ce5f4af4fbaf238a64ed7a1bce8cd2913308704f06240b85d0857a
4
+ data.tar.gz: 26bec142d446126347874a9aa08a35d4bf2a7d9658ae91c86772dc8848e52043
5
5
  SHA512:
6
- metadata.gz: e94402316fc53986b02519dd98c4c7b1bc2df8704f63366816c19a231ee7d4ecf79fd798f0fce62b4b0e6b9b464b79ee0a260d19669228cab04b96009dc03030
7
- data.tar.gz: ac46642d66e201967f32a89f6b22b28d89c6f627f42e6896c52f2c2028881b9e9c40256edad2b0d3d5a5566e407ba8af46c5aa2558127d19fabe14320ce14358
6
+ metadata.gz: 14a8c709f352ee499ae0897c60d4fecd2c4f3e127a3c8c942a47698170f20f883a2a5ce0d1f4d0f82baaee19eea2bf75c4360c5c3a88c58a6a3f7c0a65e81786
7
+ data.tar.gz: 7955689218deaff559a3ab46e6357206c7b6d0fc87d23a05c805e0613d73c52b06fbdfd8e2aca4c6ae16b62c07e085ce5955310aefc5e9399ef9859050212225
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.1'
3
+ VERSION = '1.5.2'
4
4
  end
5
5
  end
@@ -1,166 +1,191 @@
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)
6
+ class Service
7
+ # ActiveStorage provider for OpenStack
8
+ class OpenStackService < Service
9
+ attr_reader :settings, :container
9
10
 
10
- @client = Fog::OpenStack::Storage.new(settings)
11
- @container = Fog::OpenStack.escape(container)
12
- end
11
+ def initialize(container:, credentials:, public: false, connection_options: {})
12
+ super()
13
+ @settings = credentials.reverse_merge(connection_options: connection_options)
13
14
 
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
15
+ @public = public
16
+ @container = Fog::OpenStack.escape(container)
17
+ end
22
18
 
23
- begin
24
- client.put_object(container, key, io, params)
25
- rescue Excon::Error::UnprocessableEntity
26
- raise ActiveStorage::IntegrityError
19
+ def upload(key, io, checksum: nil, disposition: nil, content_type: nil, filename: nil, **)
20
+ instrument :upload, key: key, checksum: checksum do
21
+ params = { 'Content-Type' => content_type || guess_content_type(io) }
22
+ params['ETag'] = convert_base64digest_to_hexdigest(checksum) if checksum
23
+ if disposition && filename
24
+ params['Content-Disposition'] =
25
+ content_disposition_with(type: disposition, filename: filename)
26
+ end
27
+
28
+ begin
29
+ client.put_object(container, key, io, params)
30
+ rescue Excon::Error::UnprocessableEntity
31
+ raise ActiveStorage::IntegrityError
32
+ end
27
33
  end
28
34
  end
29
- end
30
35
 
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
36
+ def download(key, &block)
37
+ if block_given?
38
+ instrument :streaming_download, key: key do
39
+ object_for(key, &block)
40
+ end
41
+ else
42
+ instrument :download, key: key do
43
+ object_for(key).body
44
+ end
39
45
  end
46
+ rescue Fog::OpenStack::Storage::NotFound
47
+ raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
40
48
  end
41
- rescue Fog::OpenStack::Storage::NotFound
42
- raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
43
- end
44
49
 
45
- def download_chunk(key, range)
46
- instrument :download_chunk, key: key, range: range do
47
- chunk_buffer = []
50
+ def download_chunk(key, range)
51
+ instrument :download_chunk, key: key, range: range do
52
+ chunk_buffer = []
48
53
 
49
- object_for(key) do |chunk|
50
- chunk_buffer << chunk
51
- end
54
+ object_for(key) do |chunk|
55
+ chunk_buffer << chunk
56
+ end
52
57
 
53
- chunk_buffer.join[range]
54
- rescue Fog::OpenStack::Storage::NotFound
55
- raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
58
+ chunk_buffer.join[range]
59
+ rescue Fog::OpenStack::Storage::NotFound
60
+ raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
61
+ end
56
62
  end
57
- end
58
63
 
59
- def delete(key)
60
- instrument :delete, key: key do
61
- client.delete_object(container, key)
62
- rescue Fog::OpenStack::Storage::NotFound
63
- false
64
+ def delete(key)
65
+ instrument :delete, key: key do
66
+ client.delete_object(container, key)
67
+ rescue Fog::OpenStack::Storage::NotFound
68
+ false
69
+ end
64
70
  end
65
- end
66
71
 
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)
72
+ def delete_prefixed(prefix)
73
+ instrument :delete, prefix: prefix do
74
+ directory = client.directories.get(container)
75
+ filtered_files = client.files(directory: directory, prefix: prefix)
76
+ filtered_files = filtered_files.map(&:key)
72
77
 
73
- client.delete_multiple_objects(container, filtered_files)
78
+ client.delete_multiple_objects(container, filtered_files)
79
+ end
74
80
  end
75
- end
76
81
 
77
- def exist?(key)
78
- instrument :exist, key: key do |payload|
79
- answer = object_for(key)
80
- payload[:exist] = answer.present?
81
- rescue Fog::OpenStack::Storage::NotFound
82
- payload[:exist] = false
82
+ def exist?(key)
83
+ instrument :exist, key: key do |payload|
84
+ answer = head_for(key)
85
+ payload[:exist] = answer.present?
86
+ rescue Fog::OpenStack::Storage::NotFound
87
+ payload[:exist] = false
88
+ end
83
89
  end
84
- end
85
90
 
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(
91
+ def url(key, **options)
92
+ if ActiveStorage.version < Gem::Version.new('6.1-alpha')
93
+ instrument :url, key: key do |payload|
94
+ private_url(key, **options).tap do |generated_url|
95
+ payload[:url] = generated_url
96
+ end
97
+ end
98
+ else
99
+ super
100
+ end
101
+ end
102
+
103
+ def url_for_direct_upload(key, expires_in:, filename: nil, **)
104
+ instrument :url, key: key do |payload|
105
+ expire_at = unix_timestamp_expires_at(expires_in)
106
+ generated_url = client.create_temp_url(
91
107
  container,
92
108
  key,
93
109
  expire_at,
94
- filename: filename
110
+ 'PUT',
111
+ port: 443,
112
+ **(filename ? { filename: ActiveStorage::Filename.wrap(filename).to_s } : {}),
113
+ scheme: 'https'
95
114
  )
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
115
 
101
- generated_url
116
+ payload[:url] = generated_url
117
+
118
+ generated_url
119
+ end
102
120
  end
103
- end
104
121
 
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')
122
+ def headers_for_direct_upload(_key, content_type:, checksum:, **)
123
+ {
124
+ 'Content-Type' => content_type,
125
+ 'ETag' => convert_base64digest_to_hexdigest(checksum)
126
+ }
127
+ end
114
128
 
115
- payload[:url] = generated_url
129
+ def update_metadata(key, content_type:, disposition: nil, filename: nil, **)
130
+ instrument :update_metadata, key: key, content_type: content_type, disposition: disposition do
131
+ params = { 'Content-Type' => content_type }
132
+ if disposition && filename
133
+ params['Content-Disposition'] =
134
+ content_disposition_with(
135
+ type: disposition,
136
+ filename: ActiveStorage::Filename.wrap(filename)
137
+ )
138
+ end
139
+ client.post_object(container, key, params)
140
+ true
141
+ rescue Fog::OpenStack::Storage::NotFound
142
+ raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
143
+ end
144
+ end
116
145
 
117
- generated_url
146
+ private
147
+
148
+ def client
149
+ @client ||= Fog::OpenStack::Storage.new(settings)
118
150
  end
119
- end
120
151
 
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
152
+ def head_for(key)
153
+ client.head_object(container, key)
154
+ end
127
155
 
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)
156
+ def object_for(key, &block)
157
+ client.get_object(container, key, &block)
141
158
  end
142
- end
143
159
 
144
- private
160
+ # ActiveStorage sends a `Digest::MD5.base64digest` checksum
161
+ # OpenStack expects a `Digest::MD5.hexdigest` ETag
162
+ def convert_base64digest_to_hexdigest(base64digest)
163
+ base64digest.unpack1('m0').unpack1('H*')
164
+ end
145
165
 
146
- def object_for(key, &block)
147
- client.get_object(container, key, &block)
148
- end
166
+ def unix_timestamp_expires_at(seconds_from_now)
167
+ Time.current.advance(seconds: seconds_from_now).to_i
168
+ end
149
169
 
150
- # ActiveStorage sends a `Digest::MD5.base64digest` checksum
151
- # OpenStack expects a `Digest::MD5.hexdigest` ETag
152
- def convert_base64digest_to_hexdigest(base64digest)
153
- base64digest.unpack1('m0').unpack1('H*')
154
- end
170
+ def guess_content_type(io)
171
+ Marcel::MimeType.for io,
172
+ name: io.try(:original_filename),
173
+ declared_type: io.try(:content_type)
174
+ end
155
175
 
156
- def unix_timestamp_expires_at(seconds_from_now)
157
- Time.current.advance(seconds: seconds_from_now).to_i
158
- end
176
+ def private_url(key, expires_in:, filename:, disposition:, **)
177
+ expire_at = unix_timestamp_expires_at(expires_in)
178
+ generated_url = client.get_object_https_url(container, key, expire_at, filename: filename.sanitized)
179
+ generated_url += '&inline' if disposition.to_s != 'attachment'
180
+ # unfortunately OpenStack Swift cannot overwrite the content type of an object via a temp url
181
+ # so we just ignore the content_type argument here
159
182
 
160
- def guess_content_type(io)
161
- Marcel::MimeType.for io,
162
- name: io.try(:original_filename),
163
- declared_type: io.try(:content_type)
183
+ generated_url
184
+ end
185
+
186
+ def public_url(key, **)
187
+ client.public_url(container, key)
188
+ end
164
189
  end
165
190
  end
166
191
  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.1
4
+ version: 1.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chedli Bourguiba
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-03 00:00:00.000000000 Z
11
+ date: 2021-09-17 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
@@ -127,7 +127,7 @@ homepage: https://github.com/chaadow/activestorage-openstack
127
127
  licenses:
128
128
  - MIT
129
129
  metadata: {}
130
- post_install_message:
130
+ post_install_message:
131
131
  rdoc_options: []
132
132
  require_paths:
133
133
  - lib
@@ -142,8 +142,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
142
142
  - !ruby/object:Gem::Version
143
143
  version: 1.8.11
144
144
  requirements: []
145
- rubygems_version: 3.1.2
146
- signing_key:
145
+ rubygems_version: 3.1.3
146
+ signing_key:
147
147
  specification_version: 4
148
148
  summary: ActiveStorage wrapper for OpenStack Storage
149
149
  test_files: []