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 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: []