activestorage-openstack 1.2.0 → 1.5.1
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/railtie.rb +0 -12
- data/lib/active_storage/openstack/version.rb +1 -1
- data/lib/active_storage/service/open_stack_service.rb +141 -112
- metadata +10 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b319aff1bce7b83e7b28c029ace3190bc918ad2c7524b2f8afa55fd5dc60c80
|
4
|
+
data.tar.gz: bcec102f0f8e18f4745adf0199576758dfe5e121d532203e6f48e4fa5d2b8b5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4d0d5d3e79cdecf6518a6be8190f64d207d945aca6ee57f51879d535b2a5efdf28c617af4df398448a1230ce94e93740bd70fb344e07146786b672b33c976c4
|
7
|
+
data.tar.gz: 04c6e56a3b7c23b0bf731c9aed9b2398cccea8cd221784261a66d8f6164f771e3396992e56d100a1e0743ad8f822e1100b7196eaec34fcaa0166de85378add02
|
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,18 +1,6 @@
|
|
1
1
|
module ActiveStorage
|
2
2
|
module Openstack
|
3
3
|
class Railtie < ::Rails::Railtie
|
4
|
-
initializer "active_storage_openstack.blob" do
|
5
|
-
ActiveSupport.on_load(:active_storage_blob) do |klass|
|
6
|
-
klass.after_commit do |blob|
|
7
|
-
# overwrite content type if identification run and
|
8
|
-
# the service responds to change_content_type
|
9
|
-
if blob.identified? && !blob.content_type.blank? &&
|
10
|
-
blob.service.respond_to?(:change_content_type)
|
11
|
-
blob.service.change_content_type(blob.key, blob.content_type)
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
4
|
end
|
17
5
|
end
|
18
6
|
end
|
@@ -1,159 +1,188 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fog/openstack'
|
2
4
|
|
3
5
|
module ActiveStorage
|
4
|
-
class Service
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
params['ETag'] = convert_base64digest_to_hexdigest(checksum) if checksum
|
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
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
23
34
|
end
|
24
35
|
end
|
25
|
-
end
|
26
36
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
35
46
|
end
|
47
|
+
rescue Fog::OpenStack::Storage::NotFound
|
48
|
+
raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
|
36
49
|
end
|
37
|
-
rescue Fog::OpenStack::Storage::NotFound
|
38
|
-
raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
|
39
|
-
end
|
40
50
|
|
41
|
-
|
42
|
-
|
43
|
-
|
51
|
+
def download_chunk(key, range)
|
52
|
+
instrument :download_chunk, key: key, range: range do
|
53
|
+
chunk_buffer = []
|
44
54
|
|
45
|
-
|
46
|
-
|
47
|
-
|
55
|
+
object_for(key) do |chunk|
|
56
|
+
chunk_buffer << chunk
|
57
|
+
end
|
48
58
|
|
49
|
-
|
50
|
-
|
51
|
-
|
59
|
+
chunk_buffer.join[range]
|
60
|
+
rescue Fog::OpenStack::Storage::NotFound
|
61
|
+
raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
|
62
|
+
end
|
52
63
|
end
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
begin
|
64
|
+
|
65
|
+
def delete(key)
|
66
|
+
instrument :delete, key: key do
|
57
67
|
client.delete_object(container, key)
|
58
68
|
rescue Fog::OpenStack::Storage::NotFound
|
59
69
|
false
|
60
70
|
end
|
61
71
|
end
|
62
|
-
end
|
63
72
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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)
|
69
78
|
|
70
|
-
|
79
|
+
client.delete_multiple_objects(container, filtered_files)
|
80
|
+
end
|
71
81
|
end
|
72
|
-
end
|
73
82
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
answer = object_for(key)
|
83
|
+
def exist?(key)
|
84
|
+
instrument :exist, key: key do |payload|
|
85
|
+
answer = head_for(key)
|
78
86
|
payload[:exist] = answer.present?
|
79
87
|
rescue Fog::OpenStack::Storage::NotFound
|
80
88
|
payload[:exist] = false
|
81
89
|
end
|
82
90
|
end
|
83
|
-
end
|
84
91
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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: nil, **)
|
105
|
+
instrument :url, key: key do |payload|
|
106
|
+
expire_at = unix_timestamp_expires_at(expires_in)
|
107
|
+
generated_url = client.create_temp_url(
|
90
108
|
container,
|
91
109
|
key,
|
92
110
|
expire_at,
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
payload[:url] = generated_url
|
111
|
+
'PUT',
|
112
|
+
port: 443,
|
113
|
+
**(filename ? { filename: ActiveStorage::Filename.wrap(filename).to_s } : {}),
|
114
|
+
scheme: 'https'
|
115
|
+
)
|
99
116
|
|
100
|
-
|
117
|
+
payload[:url] = generated_url
|
118
|
+
|
119
|
+
generated_url
|
120
|
+
end
|
101
121
|
end
|
102
|
-
end
|
103
122
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
129
|
+
|
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
|
145
|
+
end
|
113
146
|
|
114
|
-
|
147
|
+
private
|
115
148
|
|
116
|
-
|
149
|
+
def head_for(key)
|
150
|
+
client.head_object(container, key)
|
117
151
|
end
|
118
|
-
end
|
119
152
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
'ETag' => convert_base64digest_to_hexdigest(checksum)
|
124
|
-
}
|
125
|
-
end
|
153
|
+
def object_for(key, &block)
|
154
|
+
client.get_object(container, key, &block)
|
155
|
+
end
|
126
156
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
true
|
133
|
-
rescue Fog::OpenStack::Storage::NotFound
|
134
|
-
raise ActiveStorage::FileNotFoundError if defined?(ActiveStorage::FileNotFoundError)
|
135
|
-
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
|
136
162
|
|
137
|
-
|
163
|
+
def unix_timestamp_expires_at(seconds_from_now)
|
164
|
+
Time.current.advance(seconds: seconds_from_now).to_i
|
165
|
+
end
|
138
166
|
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
142
172
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
148
179
|
|
149
|
-
|
150
|
-
|
151
|
-
end
|
180
|
+
generated_url
|
181
|
+
end
|
152
182
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
declared_type: io.try(:content_type)
|
183
|
+
def public_url(key, **)
|
184
|
+
client.public_url(container, key)
|
185
|
+
end
|
157
186
|
end
|
158
187
|
end
|
159
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
|
+
version: 1.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chedli Bourguiba
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-24 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
|
@@ -142,7 +142,7 @@ 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.
|
145
|
+
rubygems_version: 3.1.3
|
146
146
|
signing_key:
|
147
147
|
specification_version: 4
|
148
148
|
summary: ActiveStorage wrapper for OpenStack Storage
|