gcs 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cf9d720d07e17816a3153871d3270c62cedc6636
4
+ data.tar.gz: 14d98afb77a2604726d50af1495f8567001c8387
5
+ SHA512:
6
+ metadata.gz: a6c852593cfdbebe19960b7e2a98cefaa688c849587e58d64ab90aad7ceefff7dc587d2397672bf3dba8eb64f6f69fbebe4c78b98f0e2bc0c7662337c4311054
7
+ data.tar.gz: d70fa373f3fd6e57fa2560befab7f1e70fd57253f58ba3087820227381fb93ba6a47b4eb7078036db1029a32255a88be1ce0c6cb9bfa0036f9c306ed7c6e7ca0
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ /config/*.json
16
+ /vendor/bundle
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,8 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.1
5
+ - 2.3.4
6
+ before_install:
7
+ - if [ "${TRAVIS_SECURE_ENV_VARS}" = true ]; then openssl aes-256-cbc -K $encrypted_22290c477736_key -iv $encrypted_22290c477736_iv -in config/service_account.json.enc -out config/service_account.json -d; fi
8
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in gcs.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2017 Groovenauts, Inc.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,69 @@
1
+ # gcs-ruby
2
+
3
+ [![Build Status](https://travis-ci.org/groovenauts/gcs-ruby.svg?branch=master)](https://travis-ci.org/groovenauts/gcs-ruby)
4
+
5
+ Groovenauts' wrapper library for Google Cloud Storage with [google-api-ruby-client](https://github.com/google/google-api-ruby-client).
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'gcs'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install gcs
22
+
23
+ ## Usage
24
+
25
+ ```
26
+ gcs = Gcs.new(email_address, private_key)
27
+
28
+ # get bucket list
29
+ gcs.bucket(project_id)
30
+
31
+ # get object list
32
+ gcs.list_objects("myBucket", prefix: "path/to/subdir", max_results: 1000)
33
+
34
+ # get object metadata
35
+ get_object "myBucket", "myObject"
36
+ get_object "gs://myBucket/myObject"
37
+
38
+ # download object content
39
+ io = StringIO.new
40
+ get_object "myBucket", "myObject", download_dest: io
41
+ get_object "gs://myBucket/myObject", download_dest: io
42
+
43
+ # delete object
44
+ gcs.delete_object("myBucket", "myObject")
45
+ gcs.delete_object("gs://myBucket/myObject")
46
+
47
+ # create new object
48
+ io = StringIO.new("Hello, World!\")
49
+ gcs.insert_object("myBucket", "myObject", io)
50
+
51
+ # copy recursively
52
+ gcs.copy_tree("gs://myBucket1/src", "gs://myBucket2/dest")
53
+
54
+ # delete recursively
55
+ gcs.remove_tree("gs://myBucket/dir")
56
+
57
+ # read object content with size limitation
58
+ gcs.read_partial("gs://myBucket/myObject", limit: 1024) # => read first part of object at least 1024 bytes.
59
+ gcs.read_partial("myBucket", "myObjet", limit: 1024)
60
+ gcs.read_partial("gs://myBucket/myObject", limit: 1024, trim_after_last_delimiter: "\n") #=> remove substr after last "\n"
61
+
62
+ # initiate resumable upload
63
+ # return session URL to upload object content by PUT method requests.
64
+ # see https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload
65
+ # origin_domain keyword arg was for CORS setting.
66
+ gcs.initiate_resumable_upload("myBucket", "myObject", content_type: "text/plain", origin_domain: "http://example.com")
67
+ gcs.initiate_resumable_upload("gs://myBucket/myObject", content_type: "text/plain", origin_domain: "http://example.com")
68
+ ```
69
+
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "gcs"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ service_account_json = File.expand_path("../../config/service_account.json", __FILE__)
14
+ if File.readable?(service_account_json)
15
+ json = JSON.parse(File.read(service_account_json))
16
+ @client = Gcs.new(json["client_email"], OpenSSL::PKey::RSA.new(json["private_key"]))
17
+ end
18
+
19
+ require "pry"
20
+ if @client
21
+ Pry.start(@client)
22
+ else
23
+ Pry.start
24
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'gcs/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "gcs"
8
+ spec.version = Gcs::VERSION
9
+ spec.authors = ["Groovenauts, Inc."]
10
+ spec.email = ["tech@groovenauts.jp"]
11
+
12
+ spec.summary = %q{Groovenauts' wrapper library for Google Cloud Storage with google-api-ruby-client}
13
+ spec.description = %q{Groovenauts' wrapper library for Google Cloud Storage with google-api-ruby-client}
14
+ spec.homepage = "https://github.com/groovenauts/gcs-ruby"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.13"
24
+ spec.add_development_dependency "pry"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rake-compiler"
27
+ spec.add_development_dependency "rspec", "~> 3.0"
28
+ spec.add_dependency "google-api-client"
29
+ end
@@ -0,0 +1,234 @@
1
+ # coding: utf-8
2
+
3
+ require "net/http"
4
+ require "cgi"
5
+ require "json"
6
+
7
+ require "gcs/version"
8
+ require "google/apis/storage_v1"
9
+
10
+ class Gcs
11
+ include Google::Apis::StorageV1
12
+ def initialize(email_address = nil, private_key = nil, scope: "cloud-platform")
13
+ @api = Google::Apis::StorageV1::StorageService.new
14
+ scope_url = "https://www.googleapis.com/auth/#{scope}"
15
+ if email_address and private_key
16
+ auth = Signet::OAuth2::Client.new(
17
+ token_credential_uri: "https://accounts.google.com/o/oauth2/token",
18
+ audience: "https://accounts.google.com/o/oauth2/token",
19
+ scope: scope_url,
20
+ issuer: email_address,
21
+ signing_key: private_key)
22
+ else
23
+ auth = Google::Auth.get_application_default([scope_url])
24
+ end
25
+ auth.fetch_access_token!
26
+ @api.authorization = auth
27
+ end
28
+
29
+ def buckets(project_id)
30
+ @api.list_buckets(project_id, max_results: 1000).items || []
31
+ end
32
+
33
+ def bucket(name)
34
+ @api.get_bucket(name)
35
+ rescue Google::Apis::ClientError
36
+ if $!.status_code == 404
37
+ return nil
38
+ else
39
+ raise
40
+ end
41
+ end
42
+
43
+ def insert_bucket(project_id, name, storage_class: "STANDARD", acl: nil, default_object_acl: nil, location: nil)
44
+ b = Bucket.new(
45
+ name: name,
46
+ storage_class: storage_class
47
+ )
48
+ b.location = location if location
49
+ b.acl = acl if acl
50
+ b.default_object_acl = default_object_acl if default_object_acl
51
+ @api.insert_bucket(project_id, b)
52
+ end
53
+
54
+ def delete_bucket(name)
55
+ @api.delete_bucket(name)
56
+ rescue Google::Apis::ClientError
57
+ if $!.status_code == 404
58
+ return nil
59
+ else
60
+ raise
61
+ end
62
+ end
63
+
64
+ def self.ensure_bucket_object(bucket, object=nil)
65
+ if object.nil? and bucket.start_with?("gs://")
66
+ bucket = bucket.sub(%r{\Ags://}, "")
67
+ bucket, object = bucket.split("/", 2)
68
+ end
69
+ return [bucket, object]
70
+ end
71
+
72
+ def _ensure_bucket_object(bucket, object=nil)
73
+ self.class.ensure_bucket_object(bucket, object)
74
+ end
75
+
76
+ def get_object(bucket, object=nil, download_dest: nil)
77
+ bucket, object = _ensure_bucket_object(bucket, object)
78
+ begin
79
+ obj = @api.get_object(bucket, object)
80
+ if download_dest
81
+ @api.get_object(bucket, object, generation: obj.generation, download_dest: download_dest)
82
+ end
83
+ obj
84
+ rescue Google::Apis::ClientError
85
+ if $!.status_code == 404
86
+ return nil
87
+ else
88
+ raise
89
+ end
90
+ end
91
+ end
92
+
93
+ def read_partial(bucket, object=nil, limit: 1024*1024, trim_after_last_delimiter: nil, &blk)
94
+ bucket, object = _ensure_bucket_object(bucket, object)
95
+ uri = URI("https://www.googleapis.com/download/storage/v1/b/#{CGI.escape(bucket)}/o/#{CGI.escape(object).gsub("+", "%20")}?alt=media")
96
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
97
+ req = Net::HTTP::Get.new(uri.request_uri)
98
+ req["Authorization"] = "Bearer #{@api.authorization.access_token}"
99
+ http.request(req) do |res|
100
+ case res
101
+ when Net::HTTPSuccess
102
+ if blk
103
+ res.read_body(&blk)
104
+ return res
105
+ else
106
+ total = "".force_encoding(Encoding::ASCII_8BIT)
107
+ res.read_body do |part|
108
+ total << part
109
+ if total.bytesize > limit
110
+ break
111
+ end
112
+ end
113
+ if trim_after_last_delimiter
114
+ i = total.rindex(trim_after_last_delimiter.force_encoding(Encoding::ASCII_8BIT))
115
+ if i.nil?
116
+ # If no delimiter was found, return empty string.
117
+ # This is because caller expect not to incomplete line. (ex: Newline Delimited JSON)
118
+ i = -1
119
+ end
120
+ total[(i+1)..-1] = ""
121
+ end
122
+ return total
123
+ end
124
+ when Net::HTTPNotFound
125
+ return nil
126
+ else
127
+ raise "Gcs.read_partial failed with HTTP status #{res.code}: #{res.body}"
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ def list_objects(bucket, delimiter: "/", prefix: "", page_token: nil, max_results: nil)
134
+ @api.list_objects(bucket, delimiter: delimiter, prefix: prefix, page_token: page_token, max_results: max_results)
135
+ end
136
+
137
+ def delete_object(bucket, object=nil, if_generation_match: nil)
138
+ bucket, object = _ensure_bucket_object(bucket, object)
139
+ @api.delete_object(bucket, object, if_generation_match: if_generation_match)
140
+ end
141
+
142
+ # @param [String] bucket
143
+ # @param [String] object name
144
+ # @param [String|IO] source
145
+ # @param [String] content_type
146
+ # @param [String] content_encoding
147
+ #
148
+ # @return [Google::Apis::StorageV1::Object]
149
+ def insert_object(bucket, name, source, content_type: nil, content_encoding: nil, if_generation_match: nil)
150
+ bucket, name = _ensure_bucket_object(bucket, name)
151
+ obj = Google::Apis::StorageV1::Object.new(name: name)
152
+ @api.insert_object(bucket, obj, content_encoding: content_encoding, upload_source: source, content_type: content_type,
153
+ if_generation_match: if_generation_match)
154
+ end
155
+
156
+ def rewrite(src_bucket, src_object, dest_bucket, dest_object, if_generation_match: nil)
157
+ r = @api.rewrite_object(src_bucket, src_object, dest_bucket, dest_object, if_generation_match: if_generation_match)
158
+ until r.done
159
+ r = @api.rewrite_object(src_bucket, src_object, dest_bucket, dest_object, rewrite_token: r.rewrite_token, if_generation_match: if_generation_match)
160
+ end
161
+ r
162
+ end
163
+
164
+ def copy_tree(src, dest)
165
+ src_bucket, src_path = self.class.ensure_bucket_object(src)
166
+ dest_bucket, dest_path = self.class.ensure_bucket_object(dest)
167
+ src_path = src_path + "/" unless src_path[-1] == "/"
168
+ dest_path = dest_path + "/" unless dest_path[-1] == "/"
169
+ res = list_objects(src_bucket, prefix: src_path)
170
+ (res.items || []).each do |o|
171
+ next if o.name[-1] == "/"
172
+ dest_obj_name = dest_path + o.name.sub(/\A#{Regexp.escape(src_path)}/, "")
173
+ self.rewrite(src_bucket, o.name, dest_bucket, dest_obj_name)
174
+ end
175
+ (res.prefixes || []).each do |p|
176
+ copy_tree("gs://#{src_bucket}/#{p}", "gs://#{dest_bucket}/#{dest_path}#{p.sub(/\A#{Regexp.escape(src_path)}/, "")}")
177
+ end
178
+ end
179
+
180
+ def copy_object(src, dest)
181
+ src_bucket, src_path = self.class.ensure_bucket_object(src)
182
+ dest_bucket, dest_path = self.class.ensure_bucket_object(dest)
183
+ self.rewrite(src_bucket, src_path, dest_bucket, dest_path)
184
+ end
185
+
186
+ def remove_tree(gcs_url)
187
+ bucket, path = self.class.ensure_bucket_object(gcs_url)
188
+ if path.size > 0 and path[-1] != "/"
189
+ path = path + "/"
190
+ end
191
+ next_page_token = nil
192
+ loop do
193
+ begin
194
+ res = list_objects(bucket, prefix: path, delimiter: nil, page_token: next_page_token)
195
+ rescue Google::Apis::ClientError
196
+ if $!.status_code == 404
197
+ return nil
198
+ else
199
+ raise
200
+ end
201
+ end
202
+
203
+ # batch request あたりの API 呼び出しの量は API の種類によって異なり
204
+ # Cloud Storage JSON API のドキュメントでは 100 となってるけど1000でもいけたので1000に変更
205
+ # ref. https://cloud.google.com/storage/docs/json_api/v1/how-tos/batch
206
+ (res.items || []).each_slice(1000) do |objs|
207
+ @api.batch do
208
+ objs.each do |o|
209
+ @api.delete_object(bucket, o.name) {|_, err| raise err if err and (not(err.respond_to?(:status_code)) or (err.status_code != 404))}
210
+ end
211
+ end
212
+ end
213
+ break unless res.next_page_token
214
+ next_page_token = res.next_page_token
215
+ end
216
+ end
217
+
218
+ def initiate_resumable_upload(bucket, object=nil, content_type: "application/octet-stream", origin_domain: nil)
219
+ bucket, object = self.class.ensure_bucket_object(bucket, object)
220
+ uri = URI("https://www.googleapis.com/upload/storage/v1/b/#{CGI.escape(bucket)}/o?uploadType=resumable")
221
+ http = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
222
+ req = Net::HTTP::Post.new(uri.request_uri)
223
+ req["content-type"] = "application/json; charset=UTF-8"
224
+ req["Authorization"] = "Bearer #{@api.authorization.access_token}"
225
+ req["X-Upload-Content-Type"] = content_type
226
+ if origin_domain
227
+ req["Origin"] = origin_domain
228
+ end
229
+ req.body = JSON.generate({ "name" => object })
230
+ res = http.request(req)
231
+ return res["location"]
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,3 @@
1
+ class Gcs
2
+ VERSION = "0.1.1"
3
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gcs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Groovenauts, Inc.
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-12-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.13'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '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: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake-compiler
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: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: google-api-client
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Groovenauts' wrapper library for Google Cloud Storage with google-api-ruby-client
98
+ email:
99
+ - tech@groovenauts.jp
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - ".travis.yml"
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - bin/console
112
+ - bin/setup
113
+ - config/service_account.json.enc
114
+ - gcs.gemspec
115
+ - lib/gcs.rb
116
+ - lib/gcs/version.rb
117
+ homepage: https://github.com/groovenauts/gcs-ruby
118
+ licenses: []
119
+ metadata: {}
120
+ post_install_message:
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubyforge_project:
136
+ rubygems_version: 2.6.13
137
+ signing_key:
138
+ specification_version: 4
139
+ summary: Groovenauts' wrapper library for Google Cloud Storage with google-api-ruby-client
140
+ test_files: []