shrine-google_cloud_storage 0.1.0
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 +7 -0
- data/README.md +63 -0
- data/lib/shrine/storage/google_cloud_storage.rb +175 -0
- data/shrine-google_cloud_storage.gemspec +22 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 900ee63b1a59397ff36fe30add0c7efbca93e07f
|
4
|
+
data.tar.gz: 8fcf629cbd6f125ad18c30c5d135f4b39d20c771
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d8e8f6e0b56adf09b010edfcaedac63769a9e6c5d3d096e7e78cfd6f71faee1e1774604e847d7360f29b365b80ac41d8d20f55cd0ec464adc1e4b8c31261dad9
|
7
|
+
data.tar.gz: 58f5b6adabf36e897ab6f475aa589744ff4aaf5031d961296a2407d17c41770220bb9ef2a0e65f79f98db9ffc04c40a4be9e011ddd8ef4426fec3439561355b7
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Shrine::Storage::GoogleCloudStorage
|
2
|
+
|
3
|
+
Provides [Google Cloud Storage] (GCS) storage for [Shrine].
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem "shrine-google_cloud_storage"
|
9
|
+
```
|
10
|
+
|
11
|
+
## Authentication
|
12
|
+
|
13
|
+
The GCS plugin uses Google's [Application Default Credentials]. Please check
|
14
|
+
documentation for the various ways to provide credentials.
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
```rb
|
19
|
+
require "shrine/storage/gcs"
|
20
|
+
|
21
|
+
Shrine.storages = {
|
22
|
+
cache: Shrine::Storage::GoogleCloudStorage.new(bucket: "cache"),
|
23
|
+
store: Shrine::Storage::GoogleCloudStorage.new(bucket: "store"),
|
24
|
+
}
|
25
|
+
```
|
26
|
+
|
27
|
+
You can set a predefined ACL on created objects, as well as custom headers using the `object_options` parameter:
|
28
|
+
|
29
|
+
```rb
|
30
|
+
Shrine::Storage::GoogleCloudStorage.new(
|
31
|
+
bucket: "store",
|
32
|
+
default_acl: 'publicRead',
|
33
|
+
object_options: {
|
34
|
+
cache_control: 'public, max-age: 7200'
|
35
|
+
},
|
36
|
+
)
|
37
|
+
```
|
38
|
+
|
39
|
+
|
40
|
+
## Contributing
|
41
|
+
|
42
|
+
Firstly you need to create an `.env` file with a dedicated GCS bucket:
|
43
|
+
|
44
|
+
```sh
|
45
|
+
# .env
|
46
|
+
GCS_BUCKET="..."
|
47
|
+
```
|
48
|
+
|
49
|
+
Warning: all content of the bucket is cleared between tests, create a new one only for this usage!
|
50
|
+
|
51
|
+
Afterwards you can run the tests:
|
52
|
+
|
53
|
+
```sh
|
54
|
+
$ bundle exec rake test
|
55
|
+
```
|
56
|
+
|
57
|
+
## License
|
58
|
+
|
59
|
+
[MIT](http://opensource.org/licenses/MIT)
|
60
|
+
|
61
|
+
[Google Cloud Storage]: https://cloud.google.com/storage/
|
62
|
+
[Shrine]: https://github.com/janko-m/shrine
|
63
|
+
[Application Default Credentials]: https://developers.google.com/identity/protocols/application-default-credentials
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require "shrine"
|
2
|
+
require "googleauth"
|
3
|
+
require "google/apis/storage_v1"
|
4
|
+
|
5
|
+
class Shrine
|
6
|
+
module Storage
|
7
|
+
class GoogleCloudStorage
|
8
|
+
attr_reader :bucket, :prefix, :host
|
9
|
+
|
10
|
+
def initialize(bucket:, prefix: nil, host: nil, default_acl: nil, object_options: {})
|
11
|
+
@bucket = bucket
|
12
|
+
@prefix = prefix
|
13
|
+
@host = host
|
14
|
+
@default_acl = default_acl
|
15
|
+
@object_options = object_options
|
16
|
+
end
|
17
|
+
|
18
|
+
def upload(io, id, shrine_metadata: {}, **_options)
|
19
|
+
# uploads `io` to the location `id`
|
20
|
+
|
21
|
+
object = Google::Apis::StorageV1::Object.new @object_options.merge(bucket: @bucket, name: object_name(id))
|
22
|
+
|
23
|
+
if copyable?(io)
|
24
|
+
storage_api.copy_object(
|
25
|
+
io.storage.bucket,
|
26
|
+
io.storage.object_name(io.id),
|
27
|
+
@bucket,
|
28
|
+
object_name(id),
|
29
|
+
object,
|
30
|
+
destination_predefined_acl: @default_acl,
|
31
|
+
)
|
32
|
+
else
|
33
|
+
storage_api.insert_object(
|
34
|
+
@bucket,
|
35
|
+
object,
|
36
|
+
content_type: shrine_metadata["mime_type"],
|
37
|
+
upload_source: io.to_io,
|
38
|
+
options: { uploadType: 'multipart' },
|
39
|
+
predefined_acl: @default_acl,
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def url(id, **_options)
|
45
|
+
# URL to the remote file, accepts options for customizing the URL
|
46
|
+
host = @host || "storage.googleapis.com/#{@bucket}"
|
47
|
+
|
48
|
+
"https://#{host}/#{object_name(id)}"
|
49
|
+
end
|
50
|
+
|
51
|
+
def download(id)
|
52
|
+
tempfile = Tempfile.new(["googlestorage", File.extname(id)], binmode: true)
|
53
|
+
storage_api.get_object(@bucket, object_name(id), download_dest: tempfile)
|
54
|
+
tempfile.tap(&:open)
|
55
|
+
end
|
56
|
+
|
57
|
+
def open(id)
|
58
|
+
# returns the remote file as an IO-like object
|
59
|
+
io = storage_api.get_object(@bucket, object_name(id), download_dest: StringIO.new)
|
60
|
+
io.rewind
|
61
|
+
io
|
62
|
+
end
|
63
|
+
|
64
|
+
def exists?(id)
|
65
|
+
# checks if the file exists on the storage
|
66
|
+
storage_api.get_object(@bucket, object_name(id)) do |_, err|
|
67
|
+
if err
|
68
|
+
if err.status_code == 404
|
69
|
+
false
|
70
|
+
else
|
71
|
+
raise err
|
72
|
+
end
|
73
|
+
else
|
74
|
+
true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def delete(id)
|
80
|
+
# deletes the file from the storage
|
81
|
+
storage_api.delete_object(@bucket, object_name(id))
|
82
|
+
|
83
|
+
rescue Google::Apis::ClientError => e
|
84
|
+
# The object does not exist, Shrine expects us to be ok
|
85
|
+
return true if e.status_code == 404
|
86
|
+
|
87
|
+
raise e
|
88
|
+
end
|
89
|
+
|
90
|
+
def multi_delete(ids)
|
91
|
+
batch_delete(ids.map { |i| object_name(i) })
|
92
|
+
end
|
93
|
+
|
94
|
+
def clear!
|
95
|
+
ids = []
|
96
|
+
|
97
|
+
storage_api.fetch_all do |token, s|
|
98
|
+
prefix = "#{@prefix}/" if @prefix
|
99
|
+
s.list_objects(
|
100
|
+
@bucket,
|
101
|
+
prefix: prefix,
|
102
|
+
fields: "items/name",
|
103
|
+
page_token: token,
|
104
|
+
)
|
105
|
+
end.each do |object|
|
106
|
+
ids << object.name
|
107
|
+
|
108
|
+
if ids.size >= 100
|
109
|
+
# Batches are limited to 100, so we execute it and reset the ids
|
110
|
+
batch_delete(ids)
|
111
|
+
ids = []
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# We delete the remaining ones
|
116
|
+
batch_delete(ids) unless ids.empty?
|
117
|
+
end
|
118
|
+
|
119
|
+
def presign(id, **options)
|
120
|
+
method = options[:method] || "GET"
|
121
|
+
content_md5 = options[:content_md5] || ""
|
122
|
+
content_type = options[:content_type] || ""
|
123
|
+
expires = (Time.now.utc + (options[:expires] || 300)).to_i
|
124
|
+
headers = nil
|
125
|
+
path = "/#{@bucket}/" + object_name(id)
|
126
|
+
|
127
|
+
to_sign = [method, content_md5, content_type, expires, headers, path].compact.join("\n")
|
128
|
+
|
129
|
+
signing_key = options[:signing_key]
|
130
|
+
signing_key = OpenSSL::PKey::RSA.new(signing_key) unless signing_key.respond_to?(:sign)
|
131
|
+
signature = Base64.strict_encode64(signing_key.sign(OpenSSL::Digest::SHA256.new, to_sign)).delete("\n")
|
132
|
+
|
133
|
+
signed_url = "https://storage.googleapis.com#{path}?GoogleAccessId=#{options[:issuer]}" \
|
134
|
+
"&Expires=#{expires}&Signature=#{CGI.escape(signature)}"
|
135
|
+
|
136
|
+
OpenStruct.new(
|
137
|
+
url: signed_url,
|
138
|
+
fields: {},
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
def object_name(id)
|
143
|
+
@prefix ? "#{@prefix}/#{id}" : id
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
def copyable?(io)
|
149
|
+
io.is_a?(UploadedFile) &&
|
150
|
+
io.storage.is_a?(Storage::GoogleCloudStorage)
|
151
|
+
# TODO: add a check for the credentials
|
152
|
+
end
|
153
|
+
|
154
|
+
def batch_delete(object_names)
|
155
|
+
storage_api.batch do |storage|
|
156
|
+
object_names.each do |name|
|
157
|
+
storage.delete_object(@bucket, name)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def storage_api
|
163
|
+
if !@storage_api || @storage_api.authorization.expired?
|
164
|
+
service = Google::Apis::StorageV1::StorageService.new
|
165
|
+
scopes = ['https://www.googleapis.com/auth/devstorage.read_write']
|
166
|
+
authorization = Google::Auth.get_application_default(scopes)
|
167
|
+
authorization.fetch_access_token!
|
168
|
+
service.authorization = authorization
|
169
|
+
@storage_api = service
|
170
|
+
end
|
171
|
+
@storage_api
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = "shrine-google_cloud_storage"
|
3
|
+
gem.version = "0.1.0"
|
4
|
+
|
5
|
+
gem.required_ruby_version = ">= 2.1"
|
6
|
+
|
7
|
+
gem.summary = "Provides Google Cloud Storage storage for Shrine."
|
8
|
+
gem.homepage = "https://github.com/renchap/shrine-google_cloud_storage"
|
9
|
+
gem.authors = ["Renaud Chaput"]
|
10
|
+
gem.email = ["renchap@gmail.com"]
|
11
|
+
gem.license = "MIT"
|
12
|
+
|
13
|
+
gem.files = Dir["README.md", "LICENSE.txt", "lib/**/*.rb", "shrine-google_cloud_storage.gemspec"]
|
14
|
+
gem.require_path = "lib"
|
15
|
+
|
16
|
+
gem.add_dependency "shrine", "~> 2.0"
|
17
|
+
gem.add_dependency "google-api-client", "~> 0.11.0"
|
18
|
+
|
19
|
+
gem.add_development_dependency "rake"
|
20
|
+
gem.add_development_dependency "minitest"
|
21
|
+
gem.add_development_dependency "dotenv"
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shrine-google_cloud_storage
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Renaud Chaput
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-05-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: shrine
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: google-api-client
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.11.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.11.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: dotenv
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- renchap@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- README.md
|
91
|
+
- lib/shrine/storage/google_cloud_storage.rb
|
92
|
+
- shrine-google_cloud_storage.gemspec
|
93
|
+
homepage: https://github.com/renchap/shrine-google_cloud_storage
|
94
|
+
licenses:
|
95
|
+
- MIT
|
96
|
+
metadata: {}
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '2.1'
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 2.5.2
|
114
|
+
signing_key:
|
115
|
+
specification_version: 4
|
116
|
+
summary: Provides Google Cloud Storage storage for Shrine.
|
117
|
+
test_files: []
|