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