filbunke 2.0.9 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c42ae52d1bc54da6a935b95b029a6bf1797d6d34
4
- data.tar.gz: 1435482387aa797f0a0780aa65d79a3ecacd17a3
3
+ metadata.gz: bdf27520bab96738e5a12465ccd526ee4d3d837a
4
+ data.tar.gz: dbb247721d46a2b8aafe2d5942120850b304fd3a
5
5
  SHA512:
6
- metadata.gz: 4b268216e4e4db244c77d734f935507061631f4ac568ce6e9009e991837af4dd25fd9dd1bd205895c438c9380005b10b1c7c9aa6694e49ad63bbe3b15743762f
7
- data.tar.gz: fbae049f4a7e51a0635348d1923fa6b0bb7b46bd672f982317614fdd51c8e96f882922cb28eebca12300c8b036d4dd78f7458cb5491b426a80430241abbf9f9b
6
+ metadata.gz: 6754b1ac1376a1f491d7159fe09ab95b474637b144d2ab09fdbea87fd724234e2cf9ee5d535a6678cfbcca585dd681f186df7c193cceb6e0480d3f154536825c
7
+ data.tar.gz: 17084f8cc91464c6a9a2d1a47f5b2df0c732bc338bbd877f2795af9e16d44c80c1aea2d0d38304adee1bd5e8c06dce0d8503518f720da45253e4dea1123c3ee9
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
- Gemfile.lock
1
+ Gemfile.lock
2
+ pkg/
data/Gemfile CHANGED
@@ -1,3 +1,7 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
- gem 'jeweler'
3
+
4
+ group :test do
5
+ gem 'shoulda'
6
+ gem 'test-unit'
7
+ end
data/Rakefile CHANGED
@@ -3,6 +3,13 @@ require 'rake'
3
3
 
4
4
  begin
5
5
  require 'jeweler'
6
+ required_dependencies = {
7
+ "json" => "1.8.3",
8
+ "typhoeus" => "1.0.1",
9
+ "open4" => "1.3.4",
10
+ "mime-types" => "2.6.2",
11
+ "parallel" => "1.6.1"
12
+ }
6
13
  Jeweler::Tasks.new do |gem|
7
14
  gem.name = "filbunke"
8
15
  gem.summary = %Q{Filbunke client}
@@ -10,29 +17,33 @@ begin
10
17
  gem.email = "technical@deltaprojects.com"
11
18
  gem.homepage = "https://rubygems.org/gems/filbunke"
12
19
  gem.authors = ["Wouter de Bie", "Bjorn Sperber", "Karl Ravn", "Magnus Spangdal"]
13
- gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
20
+ gem.add_development_dependency "shoulda", "~> 0"
14
21
  gem.files.exclude 'pkg'
15
22
  gem.executables = ['filbunked']
16
- gem.add_dependency 'json', '= 1.8.3'
17
- gem.add_dependency 'typhoeus', '= 0.7.3'
18
- gem.add_dependency 'open4', '= 1.3.4'
19
- gem.add_dependency 'mime-types', '= 2.6.2'
20
- gem.add_dependency 'parallel', '= 1.6.1'
23
+ required_dependencies.each do |name, version|
24
+ gem.add_dependency "#{name}", "= #{version}"
25
+ end
21
26
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
22
27
  end
23
28
  Jeweler::GemcutterTasks.new
24
29
  rescue LoadError => e
25
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
30
+ puts "Jeweler (or a dependency) not available. Install it with: \n\tgem install jeweler \nadditional dependencies;"
31
+ required_dependencies.each do |name,version|
32
+ puts "\tgem install #{name} -v #{version}"
33
+ end
26
34
  raise e
27
35
  end
28
36
 
37
+
29
38
  require 'rake/testtask'
30
39
  Rake::TestTask.new(:test) do |test|
31
40
  test.libs << 'lib' << 'test'
32
41
  test.pattern = 'test/**/test_*.rb'
33
42
  test.verbose = true
43
+ test.warning = true
34
44
  end
35
45
 
46
+
36
47
  begin
37
48
  require 'rcov/rcovtask'
38
49
  Rcov::RcovTask.new do |test|
@@ -46,6 +57,10 @@ rescue LoadError
46
57
  end
47
58
  end
48
59
 
60
+ task :check_dependencies do
61
+
62
+ end
63
+
49
64
  task :test => :check_dependencies
50
65
 
51
66
  task :default => :test
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.9
1
+ 2.1.0
data/filbunke.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: filbunke 2.0.9 ruby lib
5
+ # stub: filbunke 2.1.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "filbunke"
9
- s.version = "2.0.9"
9
+ s.version = "2.1.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Wouter de Bie", "Bjorn Sperber", "Karl Ravn", "Magnus Spangdal"]
14
- s.date = "2015-11-05"
14
+ s.date = "2016-03-05"
15
15
  s.description = "Filbunke client and library"
16
16
  s.email = "technical@deltaprojects.com"
17
17
  s.executables = ["filbunked"]
@@ -42,31 +42,31 @@ Gem::Specification.new do |s|
42
42
  "test/test_filbunke.rb"
43
43
  ]
44
44
  s.homepage = "https://rubygems.org/gems/filbunke"
45
- s.rubygems_version = "2.5.0"
45
+ s.rubygems_version = "2.4.5.1"
46
46
  s.summary = "Filbunke client"
47
47
 
48
48
  if s.respond_to? :specification_version then
49
49
  s.specification_version = 4
50
50
 
51
51
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
52
- s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
52
+ s.add_development_dependency(%q<shoulda>, ["~> 0"])
53
53
  s.add_runtime_dependency(%q<json>, ["= 1.8.3"])
54
- s.add_runtime_dependency(%q<typhoeus>, ["= 0.7.3"])
54
+ s.add_runtime_dependency(%q<typhoeus>, ["= 1.0.1"])
55
55
  s.add_runtime_dependency(%q<open4>, ["= 1.3.4"])
56
56
  s.add_runtime_dependency(%q<mime-types>, ["= 2.6.2"])
57
57
  s.add_runtime_dependency(%q<parallel>, ["= 1.6.1"])
58
58
  else
59
- s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
59
+ s.add_dependency(%q<shoulda>, ["~> 0"])
60
60
  s.add_dependency(%q<json>, ["= 1.8.3"])
61
- s.add_dependency(%q<typhoeus>, ["= 0.7.3"])
61
+ s.add_dependency(%q<typhoeus>, ["= 1.0.1"])
62
62
  s.add_dependency(%q<open4>, ["= 1.3.4"])
63
63
  s.add_dependency(%q<mime-types>, ["= 2.6.2"])
64
64
  s.add_dependency(%q<parallel>, ["= 1.6.1"])
65
65
  end
66
66
  else
67
- s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
67
+ s.add_dependency(%q<shoulda>, ["~> 0"])
68
68
  s.add_dependency(%q<json>, ["= 1.8.3"])
69
- s.add_dependency(%q<typhoeus>, ["= 0.7.3"])
69
+ s.add_dependency(%q<typhoeus>, ["= 1.0.1"])
70
70
  s.add_dependency(%q<open4>, ["= 1.3.4"])
71
71
  s.add_dependency(%q<mime-types>, ["= 2.6.2"])
72
72
  s.add_dependency(%q<parallel>, ["= 1.6.1"])
@@ -10,7 +10,6 @@ module Filbunke
10
10
  URL_KEY = 'url'
11
11
  FROM_CHECKPOINT_KEY = 'from_checkpoint'
12
12
  HASH_KEY = 'hash'
13
- URI_UNSAFE_CHARACTERS = '/[^.:\/\w-]/'
14
13
 
15
14
 
16
15
  def initialize(repository, logger, callbacks = [], failed_request_log_file_name = nil)
@@ -26,18 +25,17 @@ module Filbunke
26
25
  def with_updated_files(last_checkpoint)
27
26
  updates = get_updated_file_list(last_checkpoint)
28
27
  updated_files = updates["files"] || []
29
- failure = false
30
-
31
- new_checkpoint = updates["checkpoint"]
32
-
33
- @logger.info "Updating repository: #{@repository.name}: #{updated_files.size} files. Checkpoint: #{last_checkpoint} ==> #{new_checkpoint}" if updated_files.size > 0
28
+ new_checkpoint = updates["checkpoint"] || 0
29
+ if updated_files.empty?
30
+ return new_checkpoint
31
+ end
32
+ @logger.info "Updating repository: #{@repository.name}: #{updated_files.size} files. Checkpoint: #{last_checkpoint} ==> #{new_checkpoint}"
34
33
 
35
34
  @async_requests = []
36
-
37
35
  callbacks_on_update = []
38
36
  callbacks_on_no_change = []
39
37
  callbacks_on_delete = []
40
-
38
+ has_update_file_failure = false
41
39
  updated_files.each do |raw_file|
42
40
  file = File.new(raw_file)
43
41
  local_file_path = ::File.join(@repository.local_path, file.path)
@@ -50,42 +48,57 @@ module Filbunke
50
48
  yield file
51
49
  callbacks_on_update << OpenStruct.new({ :file => file, :local_file_path => local_file_path })
52
50
  else
53
- @logger.error "Unable to get file #{file.url} ==> #{file.path}!"
54
- failure = true
51
+ @logger.error "Unable to fetch file #{file.url} ==> #{file.path}!"
52
+ has_update_file_failure = true
53
+ break;
55
54
  end
56
-
55
+
57
56
  else
58
57
  @logger.debug "File exists with correct hash: #{local_file_path}"
59
58
  callbacks_on_no_change << OpenStruct.new({:file => file, :local_file_path => local_file_path})
60
59
  end
61
60
  end
62
61
  end
63
- @hydra.run
64
62
 
65
- pfailure = failure || @async_requests.any? do |request|
66
- @logger.warn "request did not handle response: #{request.inspect}" if request.response.nil? || request.response.code != 200
67
- request.response.nil? || request.response.code != 200
63
+ if has_update_file_failure
64
+ @logger.error "FAILED to fetch files for #{@repository.name} last_checkpoint = #{last_checkpoint}"
65
+ return last_checkpoint
68
66
  end
67
+ @logger.info "Done setting up async requests for #{@repository.name}, starting fetch..."
69
68
 
70
- if pfailure == false
71
- @logger.info "Done fetching files for #{@repository.name}, processing callbacks..."
72
- begin
73
- run_callbacks_delete(callbacks_on_delete)
74
- run_callbacks(callbacks_on_update)
75
- run_callbacks_no_change(callbacks_on_no_change)
76
-
77
- new_checkpoint || last_checkpoint
78
- rescue RuntimeError, SystemCallError, StandardError => e
79
- msg = ["Callbacks failed to run; #{e.class} - #{e.message}", *e.backtrace].join("\n\t")
80
- @logger.error "FAILED to update files for #{@repository.name} last_checkpoint = #{last_checkpoint}; #{msg}"
81
- last_checkpoint
69
+ has_fetch_files_failure = begin
70
+ @hydra.run
71
+ @async_requests.any? do |request|
72
+ @logger.warn "request did not handle response: #{request.inspect}" if request.response.nil? || request.response.code != 200
73
+ request.response.nil? || request.response.code != 200
82
74
  end
83
- else
75
+ rescue RuntimeError, SystemCallError, StandardError => e
76
+ msg = ["#{e.class} - #{e.message}", *e.backtrace].join("\n\t")
77
+ @logger.error "FAILED to fetch files for #{@repository.name} last_checkpoint = #{last_checkpoint}; #{msg}"
78
+ true
79
+ end
80
+
81
+ if has_fetch_files_failure
84
82
  @logger.error "FAILED to update files for #{@repository.name} last_checkpoint = #{last_checkpoint}"
83
+ return last_checkpoint
84
+ end
85
+
86
+ @logger.info "Done fetching files for #{@repository.name}, processing callbacks..."
87
+ new_or_last_checkpoint = begin
88
+ run_callbacks_delete(callbacks_on_delete)
89
+ run_callbacks(callbacks_on_update)
90
+ run_callbacks_no_change(callbacks_on_no_change)
91
+
92
+ new_checkpoint || last_checkpoint
93
+ rescue RuntimeError, SystemCallError, StandardError => e
94
+ msg = ["#{e.class} - #{e.message}", *e.backtrace].join("\n\t")
95
+ @logger.error "FAILED to process callbacks for #{@repository.name} last_checkpoint = #{last_checkpoint}; #{msg}"
85
96
  last_checkpoint
86
97
  end
98
+
99
+ new_or_last_checkpoint
87
100
  end
88
-
101
+
89
102
  def update_files!(last_checkpoint)
90
103
  with_updated_files(last_checkpoint) {}
91
104
  end
@@ -138,7 +151,7 @@ module Filbunke
138
151
  end
139
152
 
140
153
  def last_checkpoint
141
- last_checkpoint_http = Net::HTTP.new(@repository.host, @repository.port)
154
+ last_checkpoint_http = Net::HTTP.new(@repository.host, @repository.port)
142
155
  last_checkpoint_http.start do |http|
143
156
  last_checkpoint_path = "/#{UPDATES_ACTION}/#{@repository.name}/#{LAST_CHECKPOINT_ACTION}"
144
157
  request = Net::HTTP::Get.new(last_checkpoint_path)
@@ -149,7 +162,7 @@ module Filbunke
149
162
  return response.body.chomp.to_i
150
163
  end
151
164
  end
152
-
165
+
153
166
  private
154
167
 
155
168
  def log_failed_request(failed_request_command, e)
@@ -161,7 +174,7 @@ module Filbunke
161
174
  end
162
175
 
163
176
  def update_file!(file, local_file_path)
164
-
177
+
165
178
  if file.url =~ /^http:\/\//
166
179
  update_http_file!(file, local_file_path)
167
180
  elsif (file.url =~ /^hdfs:\/\//)
@@ -204,6 +217,7 @@ module Filbunke
204
217
  updates_http.read_timeout = 300 # default is 60 seconds
205
218
  updates_http.start do |http|
206
219
  updates_path = "/#{UPDATES_ACTION}/#{@repository.name}?#{FROM_CHECKPOINT_KEY}=#{last_checkpoint}"
220
+ updates_path = "#{updates_path}&batch_size=#{@repository.batch_size}" if @repository.batch_size > 0
207
221
  begin
208
222
  @logger.info "Fetching updated file list from #{updates_path}"
209
223
  request = Net::HTTP::Get.new(updates_path)
@@ -228,38 +242,59 @@ module Filbunke
228
242
  def update_http_file!(file, local_file_path)
229
243
  begin
230
244
  async_request = if @repository.user
231
- Typhoeus::Request.new(URI.encode(file.url, URI_UNSAFE_CHARACTERS), :followlocation => true, :username => @repository.user, :password => @repository.pass)
245
+ Typhoeus::Request.new(
246
+ URI.escape(file.url),
247
+ :followlocation => true,
248
+ :username => @repository.user,
249
+ :password => @repository.pass
250
+ )
232
251
  else
233
- Typhoeus::Request.new(URI.encode(file.url, URI_UNSAFE_CHARACTERS), :followlocation => true)
252
+ Typhoeus::Request.new(
253
+ URI.escape(file.url),
254
+ :followlocation => true
255
+ )
256
+ end
257
+
258
+ downloaded_file = nil
259
+ async_request.on_headers do |response|
260
+ if response.code != 200
261
+ raise "Downloading file #{response.effective_url} failed with status code #{response.code} --- #{response.inspect}"
262
+ end
263
+ ::FileUtils.mkdir_p(::File.dirname(local_file_path))
264
+ downloaded_file = ::File.new("#{local_file_path}.tmp", "wb")
265
+ @logger.debug("Updating file #{local_file_path}")
266
+ end
267
+
268
+
269
+ async_request.on_body do |chunk, response|
270
+ downloaded_file.write(chunk) if response.code == 200
234
271
  end
272
+
235
273
  async_request.on_complete do |response|
236
- success = false
237
- begin
238
- success = response.code.to_i == 200
239
- if success
240
- write_file!(local_file_path, response.body)
274
+ unless downloaded_file.nil?
275
+ downloaded_file.close unless downloaded_file.closed?
276
+ if response.code == 200
277
+ ::FileUtils.mv "#{local_file_path}.tmp", local_file_path
241
278
  else
242
- body_if_error = response.code >= 500 ? ", body = #{response.body}" : ""
243
- @logger.warn "Failed to update file #{file.url}, got status code = #{response.code}#{body_if_error}"
279
+ ::FileUtils.rm "#{local_file_path}.tmp" if ::File.exist? "#{local_file_path}.tmp"
244
280
  end
245
- rescue SystemCallError, StandardError => e
246
- msg = ["#{e.class} - #{e.message}", *e.backtrace].join("\n\t")
247
- @logger.error "Failed to update file #{file.url}: #{msg}"
248
281
  end
249
- # return the async_request.handled_response value here
250
- success
282
+ true
251
283
  end
252
284
  @hydra.queue async_request
253
285
  @async_requests << async_request
254
- rescue StandardError => e
286
+ true
287
+ rescue RuntimeError, SystemCallError, StandardError => e
255
288
  msg = ["#{e.class} - #{e.message}", *e.backtrace].join("\n\t")
256
289
  @logger.error "Failed to update file #{file.url}: #{msg}"
257
- return false
290
+ unless downloaded_file.nil?
291
+ downloaded_file.close unless downloaded_file.closed?
292
+ ::FileUtils.rm "#{local_file_path}.tmp" if ::File.exist? "#{local_file_path}.tmp"
293
+ end
294
+ false
258
295
  end
259
-
260
- return true
261
296
  end
262
-
297
+
263
298
  def update_hdfs_file!(file, local_file_path)
264
299
  begin
265
300
  ::FileUtils.mkdir_p(::File.dirname(local_file_path))
@@ -268,10 +303,10 @@ module Filbunke
268
303
  url.gsub!(/hdfs:\/\/([^\/]*)(.*)/, "hdfs://\\2")
269
304
  hdfs_cmd = "#{@repository.hadoop_binary} dfs -copyToLocal #{url} #{local_file_path}.tmp"
270
305
  #@logger.debug "Trying to update #{local_file_path} with '#{hdfs_cmd}'"
271
-
306
+
272
307
  pid, stdin, stdout, stderr = Open4::popen4 hdfs_cmd
273
308
  ignored, status = Process::waitpid2 pid
274
-
309
+
275
310
  if status.exitstatus == 0 then
276
311
  begin
277
312
  ::FileUtils.mv "#{local_file_path}.tmp", local_file_path
@@ -280,7 +315,7 @@ module Filbunke
280
315
  msg = ["#{e.class} - #{e.message}", *e.backtrace].join("\n\t")
281
316
  @logger.error "Failed to move hdfs file #{file.url}: #{msg}"
282
317
  return false
283
- end
318
+ end
284
319
  else
285
320
  @logger.error "Failed to update hdfs file #{file.url}! Unable to execute #{hdfs_cmd}"
286
321
  return false
@@ -292,30 +327,11 @@ module Filbunke
292
327
  end
293
328
  end
294
329
 
295
- def write_file!(file_path, contents)
296
- ::FileUtils.mkdir_p(::File.dirname(file_path))
297
- @logger.debug("Updating: #{file_path}")
298
- begin
299
- ::File.open("#{file_path}.tmp", 'w') do |file|
300
- file.write(contents)
301
- file.close
302
- end
303
- ::FileUtils.mv "#{file_path}.tmp", file_path
304
- return true
305
- rescue StandardError => e
306
- msg = ["#{e.class} - #{e.message}", *e.backtrace].join("\n\t")
307
- @logger.error "Failed to move file #{file_path}: #{msg}"
308
- return false
309
- end
310
- end
311
-
312
330
  def delete_file!(file_path)
313
331
  if ::File.exists?(file_path) then
314
332
  @logger.debug("Deleting: #{file_path}")
315
333
  ::File.delete(file_path)
316
334
  end
317
335
  end
318
-
319
336
  end
320
337
  end
321
-
@@ -15,6 +15,7 @@ module Filbunke
15
15
  @logger.log("Initializing repository: #{repository_name}")
16
16
  @clients << begin
17
17
  repository_config["run_every"] = repository_config.fetch("run_every", @config.fetch("run_every", 10))
18
+ repository_config["batch_size"] = repository_config.fetch("batch_size", @config.fetch("batch_size", 0))
18
19
  repository = Repository.new(repository_config)
19
20
  callbacks = []
20
21
  repository_config["callbacks"].each do |callback_name, callback_config|
@@ -11,7 +11,8 @@ module Filbunke
11
11
  :pass,
12
12
  :hadoop_binary,
13
13
  :run_every,
14
- :hydra_concurrency
14
+ :hydra_concurrency,
15
+ :batch_size
15
16
 
16
17
  def initialize(repository_config)
17
18
  @name = repository_config["filbunke_server_repository"]
@@ -25,6 +26,7 @@ module Filbunke
25
26
  @hadoop_binary = repository_config["hadoop_binary"]
26
27
  @run_every = repository_config.fetch("run_every", 10).to_i
27
28
  @hydra_concurrency = repository_config.fetch("hydra_concurrency", 100).to_i
29
+ @batch_size = repository_config.fetch("batch_size", 0).to_i
28
30
  end
29
31
 
30
32
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: filbunke
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.9
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wouter de Bie
@@ -11,20 +11,20 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2015-11-05 00:00:00.000000000 Z
14
+ date: 2016-03-05 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
- name: thoughtbot-shoulda
17
+ name: shoulda
18
18
  requirement: !ruby/object:Gem::Requirement
19
19
  requirements:
20
- - - ">="
20
+ - - "~>"
21
21
  - !ruby/object:Gem::Version
22
22
  version: '0'
23
23
  type: :development
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - ">="
27
+ - - "~>"
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
30
  - !ruby/object:Gem::Dependency
@@ -47,14 +47,14 @@ dependencies:
47
47
  requirements:
48
48
  - - '='
49
49
  - !ruby/object:Gem::Version
50
- version: 0.7.3
50
+ version: 1.0.1
51
51
  type: :runtime
52
52
  prerelease: false
53
53
  version_requirements: !ruby/object:Gem::Requirement
54
54
  requirements:
55
55
  - - '='
56
56
  - !ruby/object:Gem::Version
57
- version: 0.7.3
57
+ version: 1.0.1
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: open4
60
60
  requirement: !ruby/object:Gem::Requirement
@@ -147,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
147
  version: '0'
148
148
  requirements: []
149
149
  rubyforge_project:
150
- rubygems_version: 2.5.0
150
+ rubygems_version: 2.4.5.1
151
151
  signing_key:
152
152
  specification_version: 4
153
153
  summary: Filbunke client