shrine-google_cloud_storage 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []