activestorage-openstack 1.4.1 → 1.5.2
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 +4 -4
- data/README.md +34 -12
- data/lib/active_storage/openstack/version.rb +1 -1
- data/lib/active_storage/service/open_stack_service.rb +147 -122
- metadata +13 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 43f1f0f286ce5f4af4fbaf238a64ed7a1bce8cd2913308704f06240b85d0857a
|
4
|
+
data.tar.gz: 26bec142d446126347874a9aa08a35d4bf2a7d9658ae91c86772dc8848e52043
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
[](https://travis-ci.com/chaadow/activestorage-openstack)
|
6
6
|
[](https://codeclimate.com/github/chaadow/activestorage-openstack/maintainability)
|
7
7
|
[](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,
|
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
|
-
|
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: `
|
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 `
|
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 = :
|
69
|
+
config.active_storage.service = :public_openstack
|
55
70
|
```
|
56
71
|
|
57
|
-
## Migrating from fog-openstack
|
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
|
93
|
-
|
94
|
-
|
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,166 +1,191 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fog/openstack'
|
2
4
|
|
3
5
|
module ActiveStorage
|
4
|
-
class Service
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
11
|
+
def initialize(container:, credentials:, public: false, connection_options: {})
|
12
|
+
super()
|
13
|
+
@settings = credentials.reverse_merge(connection_options: connection_options)
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
50
|
+
def download_chunk(key, range)
|
51
|
+
instrument :download_chunk, key: key, range: range do
|
52
|
+
chunk_buffer = []
|
48
53
|
|
49
|
-
|
50
|
-
|
51
|
-
|
54
|
+
object_for(key) do |chunk|
|
55
|
+
chunk_buffer << chunk
|
56
|
+
end
|
52
57
|
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
78
|
+
client.delete_multiple_objects(container, filtered_files)
|
79
|
+
end
|
74
80
|
end
|
75
|
-
end
|
76
81
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
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
|
-
|
116
|
+
payload[:url] = generated_url
|
117
|
+
|
118
|
+
generated_url
|
119
|
+
end
|
102
120
|
end
|
103
|
-
end
|
104
121
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
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
|
-
|
146
|
+
private
|
147
|
+
|
148
|
+
def client
|
149
|
+
@client ||= Fog::OpenStack::Storage.new(settings)
|
118
150
|
end
|
119
|
-
end
|
120
151
|
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
129
|
-
|
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
|
-
|
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
|
-
|
147
|
-
|
148
|
-
|
166
|
+
def unix_timestamp_expires_at(seconds_from_now)
|
167
|
+
Time.current.advance(seconds: seconds_from_now).to_i
|
168
|
+
end
|
149
169
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
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
|
-
|
157
|
-
|
158
|
-
|
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
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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
|
+
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:
|
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:
|
42
|
+
name: rails
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
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:
|
54
|
+
version: 5.2.2
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: appraisal
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
62
|
-
type: :
|
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:
|
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.
|
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: []
|