tumugi-plugin-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: a54e93d2ae132b0372b36c6f3d1974c1fbb7ef92
4
+ data.tar.gz: 6c1c6aa1fe96b655ab3f8f9b49176ef34dc9858f
5
+ SHA512:
6
+ metadata.gz: 83efe8fafc377ac90a8fc103bcee1195fb80296330a50b8a28d6c029037e19b1b29f0e15337a8443313f6fa643311ccc42a9b9747bf4308bc183bb454bacd4ed
7
+ data.tar.gz: f2ce008443eaf65118d94650698ec6ccf4b2ebf07d6079ae11a2ac7d28da8992cb683dae22cb20f6558dcc7603a41e0d2b1843a804f7bb2545e8e164aac13a71
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .env
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ cache: bundler
3
+ rvm:
4
+ - 2.1.10
5
+ - 2.2.5
6
+ - 2.3.1
7
+ - ruby-head
8
+ - jruby-9.0.5.0
9
+ before_install:
10
+ - gem install bundler
11
+ matrix:
12
+ allow_failures:
13
+ - rvm: ruby-head
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tumugi-plugin-gcs.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,87 @@
1
+ [![Build Status](https://travis-ci.org/tumugi/tumugi-plugin-google_cloud_storage.svg?branch=master)](https://travis-ci.org/tumugi/tumugi-plugin-google_cloud_storage) [![Code Climate](https://codeclimate.com/github/tumugi/tumugi-plugin-google_cloud_storage/badges/gpa.svg)](https://codeclimate.com/github/tumugi/tumugi-plugin-google_cloud_storage) [![Coverage Status](https://coveralls.io/repos/github/tumugi/tumugi-plugin-google_cloud_storage/badge.svg?branch=master)](https://coveralls.io/github/tumugi/tumugi-plugin-google_cloud_storage?branch=master) [![Gem Version](https://badge.fury.io/rb/tumugi-plugin-google_cloud_storage.svg)](https://badge.fury.io/rb/tumugi-plugin-google_cloud_storage)
2
+
3
+ # tumugi-plugin-google_cloud_storage
4
+
5
+ [tumugi](https://github.com/tumugi/tumugi) plugin for Google Cloud Storage.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'tumugi-plugin-google_cloud_storage'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```sh
18
+ $ bundle
19
+ ```
20
+
21
+ Or install it yourself as:
22
+
23
+ ```sh
24
+ $ gem install tumugi-plugin-google_cloud_storage
25
+ ```
26
+
27
+ ## Component
28
+
29
+ ### Tumugi::Plugin::GoogleCloudStorageFileTarget
30
+
31
+ This target represent file or directory on Googl Cloud Storage.
32
+ This target has 2 parameters, `bucket` and `key`.
33
+
34
+ Tumugi workflow file using this target is like this:
35
+
36
+ ```rb
37
+ task :task1 do
38
+ param :bucket, type: :string, auto_bind: true, required: true
39
+ param :day, type: :time, auto_bind: true, required: true
40
+ output do
41
+ target(:google_cloud_storage_file, bucket: bucket, key: "test_#{day.strftime('%Y%m%d')}.txt")
42
+ end
43
+ run do
44
+ log 'task1#run'
45
+ output.open('w') {|f| f.puts('done') }
46
+ end
47
+ end
48
+ ```
49
+
50
+ ### Config Section
51
+
52
+ tumugi-plugin-google_cloud_storage provide config section named "google_cloud_storage" which can specified Google Cloud Storage autenticaion info.
53
+
54
+ #### Authenticate by client_email and private_key
55
+
56
+ ```rb
57
+ Tumugi.config do |config|
58
+ config.section("google_cloud_storage") do |section|
59
+ section.project_id = "xxx"
60
+ section.client_email = "yyy@yyy.iam.gserviceaccount.com"
61
+ section.private_key = "zzz"
62
+ end
63
+ end
64
+ ```
65
+
66
+ #### Authenticate by JSON key file
67
+
68
+ ```rb
69
+ Tumugi.configure do |config|
70
+ config.section("google_cloud_storage") do |section|
71
+ section.private_key_file = "/path/to/key.json"
72
+ end
73
+ end
74
+ ```
75
+
76
+ ## Development
77
+
78
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
79
+
80
+ ## Contributing
81
+
82
+ Bug reports and pull requests are welcome on GitHub at https://github.com/tumugi/ttumugi-plugin-google_cloud_storage
83
+
84
+ ## License
85
+
86
+ The gem is available as open source under the terms of the [Apache License
87
+ Version 2.0](http://www.apache.org/licenses/).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "tumugi/plugin/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
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -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,11 @@
1
+ task :task1 do
2
+ param :bucket, type: :string, auto_bind: true, required: true
3
+ param :day, type: :time, auto_bind: true, required: true
4
+ output do
5
+ target(:google_cloud_storage_file, bucket: bucket, key: "test_#{day.strftime('%Y%m%d')}.txt")
6
+ end
7
+ run do
8
+ log 'task1#run'
9
+ output.open('w') {|f| f.puts('done') }
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ Tumugi.configure do |config|
2
+ config.section('google_cloud_storage') do |section|
3
+ section.project_id = ENV["PROJECT_ID"]
4
+ section.client_email = ENV["CLIENT_EMAIL"]
5
+ section.private_key = ENV["PRIVATE_KEY"].gsub(/\\n/, "\n")
6
+ end
7
+ end
@@ -0,0 +1,18 @@
1
+ require 'tumugi/atomic_file'
2
+
3
+ module Tumugi
4
+ module Plugin
5
+ module GoogleCloudStorage
6
+ class AtomicFile < Tumugi::AtomicFile
7
+ def initialize(path, client)
8
+ super(path)
9
+ @client = client
10
+ end
11
+
12
+ def move_to_final_destination(temp_file)
13
+ @client.upload(temp_file, path)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,299 @@
1
+ require 'uri'
2
+ require 'json'
3
+ require 'google/apis/storage_v1'
4
+ require 'tumugi/file_system'
5
+
6
+ module Tumugi
7
+ module Plugin
8
+ module GoogleCloudStorage
9
+ class FileSystem < Tumugi::FileSystem
10
+ attr_reader :client
11
+
12
+ def initialize(config)
13
+ @client = create_client(config)
14
+ end
15
+
16
+ #######################################################################
17
+ # FileSystem interfaces
18
+ #######################################################################
19
+
20
+ def exist?(path)
21
+ bucket, key = path_to_bucket_and_key(path)
22
+ if obj_exist?(bucket, key)
23
+ true
24
+ else
25
+ directory?(path)
26
+ end
27
+ rescue
28
+ process_error($!)
29
+ end
30
+
31
+ def remove(path, recursive: true)
32
+ bucket, key = path_to_bucket_and_key(path)
33
+ raise Tumugi::FileSystemError.new("Cannot delete root of bucket at path '#{path}'") if root?(key)
34
+
35
+ if obj_exist?(bucket, key)
36
+ @client.delete_object(bucket, key)
37
+ wait_until { !obj_exist?(bucket, key) }
38
+ true
39
+ elsif directory?(path)
40
+ raise Tumugi::FileSystemError.new("Path '#{path}' is a directory. Must use recursive delete") if !recursive
41
+
42
+ objs = entries(path).map(&:name)
43
+ @client.batch do |client|
44
+ objs.each do |obj|
45
+ client.delete_object(bucket, obj)
46
+ end
47
+ end
48
+ wait_until { !directory?(path) }
49
+ true
50
+ else
51
+ false
52
+ end
53
+ rescue
54
+ process_error($!)
55
+ end
56
+
57
+ def mkdir(path, parents: true, raise_if_exist: false)
58
+ if exist?(path)
59
+ if raise_if_exist
60
+ raise Tumugi::FileAlreadyExistError.new("Path #{path} is already exist")
61
+ elsif !directory?(path)
62
+ raise Tumugi::NotADirectoryError.new("Path #{path} is not a directory")
63
+ end
64
+ false
65
+ else
66
+ put_string("", add_path_delimiter(path))
67
+ true
68
+ end
69
+ rescue
70
+ process_error($!)
71
+ end
72
+
73
+ def directory?(path)
74
+ bucket, key = path_to_bucket_and_key(path)
75
+ if root?(key)
76
+ bucket_exist?(bucket)
77
+ else
78
+ obj = add_path_delimiter(key)
79
+ if obj_exist?(bucket, obj)
80
+ true
81
+ else
82
+ # Any objects with this prefix
83
+ objects = @client.list_objects(bucket, prefix: obj, max_results: 20)
84
+ !!(objects.items && objects.items.size > 0)
85
+ end
86
+ end
87
+ rescue
88
+ process_error($!)
89
+ end
90
+
91
+ def entries(path)
92
+ bucket, key = path_to_bucket_and_key(path)
93
+ obj = add_path_delimiter(key)
94
+ results = []
95
+ next_page_token = ''
96
+
97
+ until next_page_token.nil?
98
+ objects = @client.list_objects(bucket, prefix: obj, page_token: next_page_token)
99
+ if objects && objects.items
100
+ results.concat(objects.items)
101
+ next_page_token = objects.next_page_token
102
+ else
103
+ next_page_token = nil
104
+ end
105
+ end
106
+ results
107
+ rescue
108
+ process_error($!)
109
+ end
110
+
111
+ def move(src_path, dest_path, raise_if_exist: false)
112
+ copy(src_path, dest_path, raise_if_exist: raise_if_exist)
113
+ remove(src_path)
114
+ end
115
+
116
+ #######################################################################
117
+ # Specific methods
118
+ #######################################################################
119
+
120
+ def upload(media, path, content_type: nil)
121
+ bucket, key = path_to_bucket_and_key(path)
122
+ obj = Google::Apis::StorageV1::Object.new(bucket: bucket, name: key)
123
+ @client.insert_object(bucket, obj, upload_source: media, content_type: content_type)
124
+ wait_until { obj_exist?(bucket, key) }
125
+ rescue
126
+ process_error($!)
127
+ end
128
+
129
+ def download(path, download_path: nil, mode: 'r', &block)
130
+ bucket, key = path_to_bucket_and_key(path)
131
+ if download_path.nil?
132
+ download_path = Tempfile.new('tumugi_gcs_file_system').path
133
+ end
134
+ @client.get_object(bucket, key, download_dest: download_path)
135
+ wait_until { File.exist?(download_path) }
136
+
137
+ if block_given?
138
+ File.open(download_path, mode, &block)
139
+ else
140
+ File.open(download_path, mode)
141
+ end
142
+ rescue
143
+ process_error($!)
144
+ end
145
+
146
+ def put_string(contents, path, content_type: 'text/plain')
147
+ media = StringIO.new(contents)
148
+ upload(media, path, content_type: content_type)
149
+ end
150
+
151
+ def copy(src_path, dest_path, raise_if_exist: false)
152
+ if raise_if_exist && exist?(dest_path)
153
+ raise Tumugi::FileAlreadyExistError.new("Path #{dest_path} is already exist")
154
+ end
155
+
156
+ src_bucket, src_key = path_to_bucket_and_key(src_path)
157
+ dest_bucket, dest_key = path_to_bucket_and_key(dest_path)
158
+
159
+ if directory?(src_path)
160
+ src_prefix = add_path_delimiter(src_key)
161
+ dest_prefix = add_path_delimiter(dest_key)
162
+
163
+ src_path = add_path_delimiter(src_path)
164
+ copied_objs = []
165
+ entries(src_path).each do |entry|
166
+ suffix = entry.name[src_prefix.length..-1]
167
+ @client.copy_object(src_bucket, src_prefix + suffix,
168
+ dest_bucket, dest_prefix + suffix)
169
+ copied_objs << (dest_prefix + suffix)
170
+ end
171
+ wait_until { copied_objs.all? {|obj| obj_exist?(dest_bucket, obj)} }
172
+ else
173
+ @client.copy_object(src_bucket, src_key, dest_bucket, dest_key)
174
+ wait_until { obj_exist?(dest_bucket, dest_key) }
175
+ end
176
+ rescue
177
+ process_error($!)
178
+ end
179
+
180
+ def path_to_bucket_and_key(path)
181
+ uri = URI.parse(path)
182
+ raise Tumugi::FileSystemError.new("URI scheme must be 'gs' but '#{uri.scheme}'") unless uri.scheme == 'gs'
183
+ [ uri.host, uri.path[1..-1] ]
184
+ end
185
+
186
+ def create_bucket(bucket)
187
+ unless bucket_exist?(bucket)
188
+ b = Google::Apis::StorageV1::Bucket.new(name: bucket)
189
+ @client.insert_bucket(@project_id, b)
190
+ true
191
+ else
192
+ false
193
+ end
194
+ rescue
195
+ process_error($!)
196
+ end
197
+
198
+ def remove_bucket(bucket)
199
+ if bucket_exist?(bucket)
200
+ @client.delete_bucket(bucket)
201
+ true
202
+ else
203
+ false
204
+ end
205
+ rescue
206
+ process_error($!)
207
+ end
208
+
209
+ def bucket_exist?(bucket)
210
+ @client.get_bucket(bucket)
211
+ true
212
+ rescue => e
213
+ return false if e.status_code == 404
214
+ process_error(e)
215
+ end
216
+
217
+ private
218
+
219
+ def obj_exist?(bucket, key)
220
+ @client.get_object(bucket, key)
221
+ true
222
+ rescue => e
223
+ return false if e.status_code == 404
224
+ process_error(e)
225
+ end
226
+
227
+ def root?(key)
228
+ key.nil? || key == ''
229
+ end
230
+
231
+ def add_path_delimiter(key)
232
+ if key.end_with?('/')
233
+ key
234
+ else
235
+ "#{key}/"
236
+ end
237
+ end
238
+
239
+ def create_client(config)
240
+ if config.private_key_file.nil?
241
+ @project_id = config.project_id
242
+ client_email = config.client_email
243
+ private_key = config.private_key
244
+ else
245
+ json = JSON.parse(File.read(config.private_key_file))
246
+ @project_id = json['project_id']
247
+ client_email = json['client_email']
248
+ private_key = json['private_key']
249
+ end
250
+
251
+ # https://cloud.google.com/storage/docs/authentication
252
+ scope = "https://www.googleapis.com/auth/devstorage.read_write"
253
+
254
+ if client_email and private_key
255
+ auth = Signet::OAuth2::Client.new(
256
+ token_credential_uri: "https://accounts.google.com/o/oauth2/token",
257
+ audience: "https://accounts.google.com/o/oauth2/token",
258
+ scope: scope,
259
+ issuer: client_email,
260
+ signing_key: OpenSSL::PKey.read(private_key))
261
+ # MEMO: signet-0.6.1 depend on Farady.default_connection
262
+ Faraday.default_connection.options.timeout = 60
263
+ auth.fetch_access_token!
264
+ else
265
+ auth = Google::Auth.get_application_default([scope])
266
+ auth.fetch_access_token!
267
+ end
268
+
269
+ client = Google::Apis::StorageV1::StorageService.new
270
+ client.authorization = auth
271
+ client
272
+ end
273
+
274
+ def wait_until(&block)
275
+ while not block.call
276
+ sleep 1
277
+ end
278
+ end
279
+
280
+ def process_error(err)
281
+ if err.respond_to?(:body)
282
+ begin
283
+ jobj = JSON.parse(err.body)
284
+ error = jobj["error"]
285
+ reason = error["errors"].map{|e| e["reason"]}.join(",")
286
+ errors = error["errors"].map{|e| e["message"] }.join("\n")
287
+ rescue JSON::ParserError
288
+ reason = err.status_code.to_s
289
+ errors = "HTTP Status: #{err.status_code}\nHeaders: #{err.header.inspect}\nBody:\n#{err.body}"
290
+ end
291
+ raise Tumugi::FileSystemError.new(errors, reason)
292
+ else
293
+ raise err
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,41 @@
1
+ require 'tumugi/config'
2
+ require 'tumugi/plugin'
3
+ require 'tumugi/plugin/file_system_target'
4
+ require 'tumugi/plugin/google_cloud_storage/atomic_file'
5
+ require 'tumugi/plugin/google_cloud_storage/file_system'
6
+
7
+ module Tumugi
8
+ module Plugin
9
+ class GoogleCloudStorageFileTarget < Tumugi::Plugin::FileSystemTarget
10
+ Tumugi::Plugin.register_target('google_cloud_storage_file', self)
11
+ Tumugi::Config.register_section('google_cloud_storage', :project_id, :client_email, :private_key, :private_key_file)
12
+
13
+ attr_reader :bucket, :key, :path
14
+
15
+ def initialize(bucket:, key:)
16
+ @bucket = bucket
17
+ @key = key
18
+ @path = "gs://#{File.join(bucket, key)}"
19
+ log "bucket='#{bucket}, key='#{key}'"
20
+ end
21
+
22
+ def fs
23
+ @fs ||= Tumugi::Plugin::GoogleCloudStorage::FileSystem.new(Tumugi.config.section('google_cloud_storage'))
24
+ end
25
+
26
+ def open(mode="r", &block)
27
+ if mode.include? 'r'
28
+ fs.download(path, mode: mode, &block)
29
+ elsif mode.include? 'w'
30
+ Tumugi::Plugin::GoogleCloudStorage::AtomicFile.new(path, fs).open(&block)
31
+ else
32
+ raise Tumugi::TumugiError.new('Invalid mode: #{mode}')
33
+ end
34
+ end
35
+
36
+ def to_s
37
+ path
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "tumugi-plugin-google_cloud_storage"
7
+ spec.version = "0.1.0"
8
+ spec.authors = ["Kazuyuki Honda"]
9
+ spec.email = ["hakobera@gmail.com"]
10
+
11
+ spec.summary = "Tumugi plugin for Google Cloud Storage"
12
+ spec.homepage = "https://github.com/tumugi/tumugi-plugin-google_cloud_storage"
13
+ spec.license = "Apache License Version 2.0"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.required_ruby_version = '>= 2.1'
21
+
22
+ spec.add_runtime_dependency "tumugi", "~> 0.5.1"
23
+ spec.add_runtime_dependency "google-api-client", "~> 0.9.3"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.11"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "test-unit", "~> 3.1"
28
+ spec.add_development_dependency "test-unit-rr"
29
+ spec.add_development_dependency "coveralls"
30
+ spec.add_development_dependency "github_changelog_generator"
31
+ end
metadata ADDED
@@ -0,0 +1,169 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tumugi-plugin-google_cloud_storage
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kazuyuki Honda
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-05-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tumugi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.5.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.5.1
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.9.3
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.9.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.11'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.11'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: test-unit
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.1'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: test-unit-rr
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: coveralls
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: github_changelog_generator
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description:
126
+ email:
127
+ - hakobera@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - ".travis.yml"
134
+ - Gemfile
135
+ - README.md
136
+ - Rakefile
137
+ - bin/console
138
+ - bin/setup
139
+ - examples/example.rb
140
+ - examples/tumugi_config_example.rb
141
+ - lib/tumugi/plugin/google_cloud_storage/atomic_file.rb
142
+ - lib/tumugi/plugin/google_cloud_storage/file_system.rb
143
+ - lib/tumugi/plugin/target/google_cloud_storage_file.rb
144
+ - tumugi-plugin-google_cloud_storage.gemspec
145
+ homepage: https://github.com/tumugi/tumugi-plugin-google_cloud_storage
146
+ licenses:
147
+ - Apache License Version 2.0
148
+ metadata: {}
149
+ post_install_message:
150
+ rdoc_options: []
151
+ require_paths:
152
+ - lib
153
+ required_ruby_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '2.1'
158
+ required_rubygems_version: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
163
+ requirements: []
164
+ rubyforge_project:
165
+ rubygems_version: 2.5.1
166
+ signing_key:
167
+ specification_version: 4
168
+ summary: Tumugi plugin for Google Cloud Storage
169
+ test_files: []