mongo 2.20.1 → 2.20.2

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +9 -9
  3. data/lib/mongo/socket/ssl.rb +13 -4
  4. data/lib/mongo/version.rb +4 -15
  5. data/spec/integration/ocsp_verifier_spec.rb +30 -99
  6. data/spec/integration/reconnect_spec.rb +8 -2
  7. data/spec/integration/srv_monitoring_spec.rb +2 -3
  8. data/spec/integration/srv_spec.rb +0 -4
  9. data/spec/shared/CANDIDATE.md +28 -0
  10. data/spec/shared/LICENSE +20 -0
  11. data/spec/shared/bin/get-mongodb-download-url +17 -0
  12. data/spec/shared/bin/s3-copy +45 -0
  13. data/spec/shared/bin/s3-upload +69 -0
  14. data/spec/shared/lib/mrss/child_process_helper.rb +80 -0
  15. data/spec/shared/lib/mrss/cluster_config.rb +231 -0
  16. data/spec/shared/lib/mrss/constraints.rb +378 -0
  17. data/spec/shared/lib/mrss/docker_runner.rb +298 -0
  18. data/spec/shared/lib/mrss/eg_config_utils.rb +51 -0
  19. data/spec/shared/lib/mrss/event_subscriber.rb +210 -0
  20. data/spec/shared/lib/mrss/lite_constraints.rb +238 -0
  21. data/spec/shared/lib/mrss/release/candidate.rb +281 -0
  22. data/spec/shared/lib/mrss/release/product_data.rb +144 -0
  23. data/spec/shared/lib/mrss/server_version_registry.rb +113 -0
  24. data/spec/shared/lib/mrss/session_registry.rb +69 -0
  25. data/spec/shared/lib/mrss/session_registry_legacy.rb +60 -0
  26. data/spec/shared/lib/mrss/spec_organizer.rb +179 -0
  27. data/spec/shared/lib/mrss/utils.rb +37 -0
  28. data/spec/shared/lib/tasks/candidate.rake +64 -0
  29. data/spec/shared/share/Dockerfile.erb +251 -0
  30. data/spec/shared/share/haproxy-1.conf +16 -0
  31. data/spec/shared/share/haproxy-2.conf +17 -0
  32. data/spec/shared/shlib/config.sh +27 -0
  33. data/spec/shared/shlib/distro.sh +84 -0
  34. data/spec/shared/shlib/server.sh +423 -0
  35. data/spec/shared/shlib/set_env.sh +110 -0
  36. data/spec/support/common_shortcuts.rb +19 -37
  37. data/spec/support/constraints.rb +10 -0
  38. metadata +64 -15
  39. data/spec/support/dns.rb +0 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a641cd6128fd246b0ac9999f00167631548206573a22989968f2a7a132bc0ae
4
- data.tar.gz: d5526d1ca34219ac644191f1e2c7177877c70063cc5a97992379e8e86065d0ca
3
+ metadata.gz: 8ccd08d09b5ee60c5da82105ce4bb165f1324e475e9f476f956d08a677a503b9
4
+ data.tar.gz: d055b854eb8f822e37ca7002bef5e3625c6ddf7052c06113c0568b0ca275eae6
5
5
  SHA512:
6
- metadata.gz: 4c23fd9abff5c29517b2cf0c0094bedfbbe667a96f2494b36e6465d8bbfbe113cd08f05e72d15b6db17ef35a211c4aaa040c20dc99eb65417c3c3690b7712c59
7
- data.tar.gz: 54ffbc332168455337aa4de863db0ca179084a35c271e9f21dbfe2425556bd00e02b2fd46a5c2581aa15d5e9a11b84d9a0c231889774adb129f2ad38b997f4f4
6
+ metadata.gz: 291abbb2324d615f2061a7f924e31c7020c7da0541ff5a5b115a9c3edb7b4a74a921ff3e226a50bb6c74fb9f8f79f8343f14ea17f701b6796fde2dc774f0c915
7
+ data.tar.gz: 792ed0dd04ed391c9b644fa13e65d4282182cac3e54af1f1a4830a20d6ed30c6f6eecef0c0e8d3cbab0c02bef907065a70568fa44b346bb8d947cc02a6a5abb6
data/Rakefile CHANGED
@@ -4,6 +4,10 @@
4
4
  require 'bundler'
5
5
  require 'rspec/core/rake_task'
6
6
 
7
+ if File.exist?('./spec/shared/lib/tasks/candidate.rake')
8
+ load 'spec/shared/lib/tasks/candidate.rake'
9
+ end
10
+
7
11
  ROOT = File.expand_path(File.join(File.dirname(__FILE__)))
8
12
 
9
13
  $: << File.join(ROOT, 'spec/shared/lib')
@@ -34,16 +38,12 @@ end
34
38
 
35
39
  task :default => ['spec:prepare', :spec]
36
40
 
37
- # stands in for the Bundler-provided `build` task, which builds the
38
- # gem for this project. Our release process builds the gems in a
39
- # particular way, in a GitHub action. This task is just to help remind
40
- # developers of that fact.
41
+ desc 'Build the gem'
41
42
  task :build do
42
- abort <<~WARNING
43
- `rake build` does nothing in this project. The gem must be built via
44
- the `Driver Release` action on GitHub, which is triggered manually when
45
- a new release is ready.
46
- WARNING
43
+ command = %w[ gem build ]
44
+ command << "--output=#{ENV['GEM_FILE_NAME']}" if ENV['GEM_FILE_NAME']
45
+ command << (ENV['GEMSPEC'] || 'mongo.gemspec')
46
+ system(*command)
47
47
  end
48
48
 
49
49
  # `rake version` is used by the deployment system so get the release version
@@ -23,6 +23,7 @@ module Mongo
23
23
  # @since 2.0.0
24
24
  class SSL < Socket
25
25
  include OpenSSL
26
+ include Loggable
26
27
 
27
28
  # Initializes a new TLS socket.
28
29
  #
@@ -363,12 +364,15 @@ module Mongo
363
364
  end
364
365
 
365
366
  def verify_ocsp_endpoint!(socket)
366
- unless verify_ocsp_endpoint?
367
- return
368
- end
367
+ return unless verify_ocsp_endpoint?
369
368
 
370
369
  cert = socket.peer_cert
371
- ca_cert = socket.peer_cert_chain.last
370
+ ca_cert = find_issuer(cert, socket.peer_cert_chain)
371
+
372
+ unless ca_cert
373
+ log_warn("TLS certificate of '#{host_name}' could not be definitively verified via OCSP: issuer certificate not found in the chain.")
374
+ return
375
+ end
372
376
 
373
377
  verifier = OcspVerifier.new(@host_name, cert, ca_cert, context.cert_store,
374
378
  **Utils.shallow_symbolize_keys(options))
@@ -411,6 +415,11 @@ module Mongo
411
415
  end
412
416
  end
413
417
  end
418
+
419
+ # Find the issuer certificate in the chain.
420
+ def find_issuer(cert, cert_chain)
421
+ cert_chain.find { |c| c.subject == cert.issuer }
422
+ end
414
423
  end
415
424
  end
416
425
  end
data/lib/mongo/version.rb CHANGED
@@ -1,20 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (C) 2014-2020 MongoDB Inc.
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
-
17
3
  module Mongo
18
4
  # The current version of the driver.
19
- VERSION = '2.20.1'
5
+ #
6
+ # Note that this file is automatically updated via `rake candidate:create`.
7
+ # Manual changes to this file will be overwritten by that rake task.
8
+ VERSION = '2.20.2'
20
9
  end
@@ -9,6 +9,21 @@ describe Mongo::Socket::OcspVerifier do
9
9
  with_openssl_debug
10
10
  retry_test sleep: 5
11
11
 
12
+ def self.with_ocsp_responder(port = 8100, path = '/', &setup)
13
+ around do |example|
14
+ server = WEBrick::HTTPServer.new(Port: port)
15
+ server.mount_proc path, &setup
16
+ Thread.new { server.start }
17
+ begin
18
+ example.run
19
+ ensure
20
+ server.shutdown
21
+ end
22
+
23
+ ::Utils.wait_for_port_free(port, 5)
24
+ end
25
+ end
26
+
12
27
  shared_examples 'verifies' do
13
28
  context 'mri' do
14
29
  fails_on_jruby
@@ -173,21 +188,10 @@ describe Mongo::Socket::OcspVerifier do
173
188
 
174
189
  context 'one time' do
175
190
 
176
- around do |example|
177
- server = WEBrick::HTTPServer.new(Port: 8100)
178
- server.mount_proc '/' do |req, res|
179
- res.status = 303
180
- res['locAtion'] = "http://localhost:8101#{req.path}"
181
- res.body = "See http://localhost:8101#{req.path}"
182
- end
183
- Thread.new { server.start }
184
- begin
185
- example.run
186
- ensure
187
- server.shutdown
188
- end
189
-
190
- ::Utils.wait_for_port_free(8100, 5)
191
+ with_ocsp_responder do |req, res|
192
+ res.status = 303
193
+ res['locAtion'] = "http://localhost:8101#{req.path}"
194
+ res.body = "See http://localhost:8101#{req.path}"
191
195
  end
192
196
 
193
197
  include_context 'verifier', algorithm: algorithm
@@ -248,21 +252,10 @@ describe Mongo::Socket::OcspVerifier do
248
252
  port: 8101,
249
253
  )
250
254
 
251
- around do |example|
252
- server = WEBrick::HTTPServer.new(Port: 8100)
253
- server.mount_proc '/' do |req, res|
254
- res.status = 303
255
- res['locAtion'] = req.path
256
- res.body = "See #{req.path} indefinitely"
257
- end
258
- Thread.new { server.start }
259
- begin
260
- example.run
261
- ensure
262
- server.shutdown
263
- end
264
-
265
- ::Utils.wait_for_port_free(8100, 5)
255
+ with_ocsp_responder do |req, res|
256
+ res.status = 303
257
+ res['locAtion'] = req.path
258
+ res.body = "See #{req.path} indefinitely"
266
259
  end
267
260
 
268
261
  include_context 'verifier', algorithm: algorithm
@@ -274,85 +267,23 @@ describe Mongo::Socket::OcspVerifier do
274
267
 
275
268
  include_context 'verifier', algorithm: 'rsa'
276
269
 
277
- context '40x / 50x' do
278
- around do |example|
279
- server = WEBrick::HTTPServer.new(Port: 8100)
280
- server.mount_proc '/' do |req, res|
270
+ [400, 404, 500, 503].each do |code|
271
+ context "code #{code}" do
272
+ with_ocsp_responder do |req, res|
281
273
  res.status = code
282
274
  res.body = "HTTP #{code}"
283
275
  end
284
- Thread.new { server.start }
285
- begin
286
- example.run
287
- ensure
288
- server.shutdown
289
- end
290
-
291
- ::Utils.wait_for_port_free(8100, 5)
292
- end
293
-
294
- [400, 404, 500, 503].each do |_code|
295
- context "code #{_code}" do
296
- let(:code) { _code }
297
- include_examples 'does not verify'
298
- end
299
- end
300
- end
301
-
302
- context '204' do
303
- around do |example|
304
- server = WEBrick::HTTPServer.new(Port: 8100)
305
- server.mount_proc '/' do |req, res|
306
- res.status = 204
307
- end
308
- Thread.new { server.start }
309
- begin
310
- example.run
311
- ensure
312
- server.shutdown
313
- end
314
276
 
315
- ::Utils.wait_for_port_free(8100, 5)
316
- end
317
-
318
- context "code 204" do
319
- let(:code) { 204 }
320
277
  include_examples 'does not verify'
321
278
  end
322
279
  end
323
- end
324
-
325
- context 'responder URI has no path' do
326
- require_external_connectivity
327
-
328
- # https://github.com/jruby/jruby-openssl/issues/210
329
- fails_on_jruby
330
280
 
331
- include_context 'basic verifier'
332
-
333
- # The fake certificates all have paths in them for use with the ocsp mock.
334
- # Use real certificates retrieved from Atlas for this test as they don't
335
- # have a path in the OCSP URI (which the test also asserts).
336
- # Note that these certificates expire in 3 months and need to be replaced
337
- # with a more permanent solution.
338
- # Use the spec/support/certificates/retrieve-atlas-cert script to retrieve
339
- # current certificates from Atlas.
340
- let(:cert_path) { File.join(File.dirname(__FILE__), '../support/certificates/atlas-ocsp.crt') }
341
- let(:ca_cert_path) { File.join(File.dirname(__FILE__), '../support/certificates/atlas-ocsp-ca.crt') }
342
- let(:cert_store) do
343
- OpenSSL::X509::Store.new.tap do |store|
344
- store.set_default_paths
281
+ context 'code 204' do
282
+ with_ocsp_responder do |req, res|
283
+ res.status = 204
345
284
  end
346
- end
347
-
348
- before do
349
- verifier.ocsp_uris.length.should > 0
350
- URI.parse(verifier.ocsp_uris.first).path.should == ''
351
- end
352
285
 
353
- it 'verifies' do
354
- # TODO This test will fail if the certificate expires
355
- expect(verifier.verify).to be(true), "If atlas-ocsp certificates have expired, run spec/support/certificates/retrieve-atlas-cert to get a new ones"
286
+ include_examples 'does not verify'
356
287
  end
357
288
  end
358
289
  end
@@ -111,6 +111,10 @@ describe 'Client after reconnect' do
111
111
  # thread.kill should've similarly failed, but it doesn't.
112
112
  fails_on_jruby
113
113
 
114
+ # odd failures related to async on ruby <= 3.1, I suspect something
115
+ # with how fibers worked in those versions.
116
+ minimum_mri_version '3.2.0'
117
+
114
118
  it 'recreates SRV monitor' do
115
119
  wait_for_discovery
116
120
 
@@ -151,6 +155,10 @@ describe 'Client after reconnect' do
151
155
  # NotImplementedError: recvmsg_nonblock is not implemented
152
156
  fails_on_jruby
153
157
 
158
+ # odd failures related to async on ruby <= 3.1, I suspect something
159
+ # with how fibers worked in those versions.
160
+ minimum_mri_version '3.2.0'
161
+
154
162
  let(:uri) do
155
163
  "mongodb+srv://test-fake.test.build.10gen.cc/"
156
164
  end
@@ -181,8 +189,6 @@ describe 'Client after reconnect' do
181
189
  end
182
190
 
183
191
  around do |example|
184
- require 'support/dns'
185
-
186
192
  rules = [
187
193
  ['_mongodb._tcp.test-fake.test.build.10gen.cc', :srv,
188
194
  [0, 0, 2799, 'localhost.test.build.10gen.cc'],
@@ -76,9 +76,8 @@ describe 'SRV Monitoring' do
76
76
  # NotImplementedError: recvmsg_nonblock is not implemented
77
77
  fails_on_jruby
78
78
 
79
- before(:all) do
80
- require 'support/dns'
81
- end
79
+ # mock dns implementation doesn't play nice with 2.7, etc.
80
+ minimum_mri_version '3.0.0'
82
81
 
83
82
  around do |example|
84
83
  # Speed up the tests by listening on the fake ports we are using.
@@ -12,10 +12,6 @@ describe 'SRV lookup' do
12
12
  # NotImplementedError: recvmsg_nonblock is not implemented
13
13
  fails_on_jruby
14
14
 
15
- before(:all) do
16
- require 'support/dns'
17
- end
18
-
19
15
  let(:uri) do
20
16
  "mongodb+srv://test-fake.test.build.10gen.cc/?tls=#{SpecConfig.instance.ssl?}&tlsInsecure=true"
21
17
  end
@@ -0,0 +1,28 @@
1
+ # Candidate Tasks
2
+
3
+ When using the `candidate` rake tasks, you must make sure:
4
+
5
+ 1. You are using at least `git` version 2.49.0.
6
+ 2. You have the `gh` CLI tool installed.
7
+ 3. You are logged into `gh` with an account that has collaborator access to the repository.
8
+ 4. You have run `gh repo set-default` from the root of your local checkout to set the default repository to the canonical MongoDB repo.
9
+ 5. The `origin` remote for your local checkout is set to your own fork.
10
+ 6. The `upstream` remote for your local checkout is set to the canonical
11
+ MongoDB repo.
12
+
13
+ Once configured, you can use the following commands:
14
+
15
+ 1. `rake candidate:prs` - This will list all pull requests that will be included in the next release. Any with `[?]` are unlabelled (or are not labelled with a recognized label). Otherwise, `[b]` means `bug`, `[f]` means `feature`, and `[x]` means `bcbreak`.
16
+ 2. `rake candidate:preview` - This will generate and display the release notes for the next release, based on the associated pull requests.
17
+ 3. `rake candidate:create` - This will create a new PR against the default repository, using the generated release notes as the description. The new PR will be given the `release-candidate` label.
18
+
19
+ Then, after the release candidate PR is approved and merged, the release process will automatically bundle, sign, and release the new version.
20
+
21
+ Once you've merged the PR, you can switch to the "Actions" tab for the repository on GitHub and look for the "Release" workflow (might be named differently), which should have triggered automatically. You can monitor the progress of the release there. If there are any problems, the workflow is generally safe to re-run after you've addressed them.
22
+
23
+ Things to do after the release succeeds:
24
+
25
+ 1. Copy the release notes from the PR and create a new release announcement on the forums (https://www.mongodb.com/community/forums/c/announcements/driver-releases/110).
26
+ 2. If the release was not automatically announced in #ruby, copy a link to the GitHub release or MongoDB forum post there.
27
+ 3. Close the release in Jira.
28
+
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2020 MongoDB, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ desired_version, arch = ARGV
4
+ if arch.nil?
5
+ STDERR.puts "Usage: get-mongodb-download-url desired-version arch"
6
+ exit 1
7
+ end
8
+
9
+ $: << File.join(File.dirname(__FILE__), '../lib')
10
+ require 'mrss/server_version_registry'
11
+
12
+ begin
13
+ puts Mrss::ServerVersionRegistry.new(desired_version, arch).download_url
14
+ rescue Mrss::ServerVersionRegistry::Error => exc
15
+ STDERR.puts "Error: #{exc}"
16
+ exit 2
17
+ end
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'aws-sdk-s3'
5
+
6
+ options = {}
7
+ OptionParser.new do |opts|
8
+ opts.banner = "Usage: s3-copy options"
9
+
10
+ opts.on("-r", "--region=REGION", "AWS region to use (default us-east-1)") do |v|
11
+ options[:region] = v
12
+ end
13
+
14
+ opts.on("-p", "--param=KEY=VALUE", "Specify parameter for new files") do |v|
15
+ options[:params] ||= {}
16
+ k, v = v.split('=', 2)
17
+ options[:params][k.to_sym] = v
18
+ end
19
+
20
+ opts.on("-f", "--from=BUCKET:PATH", "Bucket name and key (or path) to copy from") do |v|
21
+ options[:from] = v
22
+ end
23
+
24
+ opts.on("-t", "--to=BUCKET:PATH", "Bucket name and key (or path) to write to (may be specified more than once)") do |v|
25
+ options[:to] ||= []
26
+ options[:to] << v
27
+ end
28
+ end.parse!
29
+
30
+ ENV['AWS_REGION'] ||= options[:region] || 'us-east-1'
31
+
32
+ bucket, key = options.fetch(:from).split(':', 2)
33
+
34
+ s3 = Aws::S3::Client.new
35
+
36
+ options.fetch(:to).each do |dest|
37
+ STDERR.puts "Copying to #{dest}"
38
+ dbucket, dkey = dest.split(':', 2)
39
+ s3.copy_object(
40
+ bucket: dbucket,
41
+ key: dkey,
42
+ copy_source: "/#{bucket}/#{key}",
43
+ **options[:params] || {},
44
+ )
45
+ end
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'aws-sdk-s3'
5
+
6
+ options = {}
7
+ OptionParser.new do |opts|
8
+ opts.banner = "Usage: s3-upload options"
9
+
10
+ opts.on("-r", "--region=REGION", "AWS region to use (default us-east-1)") do |v|
11
+ options[:region] = v
12
+ end
13
+
14
+ opts.on("-p", "--param=KEY=VALUE", "Specify parameter for S3 upload") do |v|
15
+ options[:params] ||= {}
16
+ k, v = v.split('=', 2)
17
+ options[:params][k.to_sym] = v
18
+ end
19
+
20
+ opts.on("-f", "--file=PATH", "Path to the file to upload, - to upload standard input") do |v|
21
+ options[:file] = v
22
+ end
23
+
24
+ opts.on("-w", "--write=BUCKET:PATH", "Bucket name and key (or path) to upload to") do |v|
25
+ options[:write] = v
26
+ end
27
+
28
+ opts.on("-c", "--copy=BUCKET:PATH", "Bucket name and key (or path) to copy to (may be specified more than once)") do |v|
29
+ options[:copy] ||= []
30
+ options[:copy] << v
31
+ end
32
+ end.parse!
33
+
34
+ ENV['AWS_REGION'] ||= options[:region] || 'us-east-1'
35
+
36
+ def upload(f, options)
37
+ s3 = Aws::S3::Client.new
38
+ write = options.fetch(:write)
39
+ STDERR.puts "Writing #{write}"
40
+ bucket, key = write.split(':', 2)
41
+ s3.put_object(
42
+ body: f.read,
43
+ bucket: bucket,
44
+ key: key,
45
+ **options[:params] || {},
46
+ )
47
+ if copy = options[:copy]
48
+ copy.each do |dest|
49
+ STDERR.puts "Copying to #{dest}"
50
+ dbucket, dkey = dest.split(':', 2)
51
+ s3.copy_object(
52
+ bucket: dbucket,
53
+ key: dkey,
54
+ copy_source: "/#{bucket}/#{key}",
55
+ **options[:params] || {},
56
+ )
57
+ end
58
+ end
59
+ end
60
+
61
+ if options[:file] == '-'
62
+ upload(STDIN, options)
63
+ elsif options[:file]
64
+ File.open(options[:file]) do |f|
65
+ upload(f, options)
66
+ end
67
+ else
68
+ upload(STDIN, options)
69
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
4
+ autoload :ChildProcess, 'childprocess'
5
+ autoload :Tempfile, 'tempfile'
6
+
7
+ module Mrss
8
+ module ChildProcessHelper
9
+ class SpawnError < StandardError; end
10
+
11
+ module_function def call(cmd, env: nil, cwd: nil)
12
+ process = ChildProcess.new(*cmd)
13
+ process.io.inherit!
14
+ if cwd
15
+ process.cwd = cwd
16
+ end
17
+ if env
18
+ env.each do |k, v|
19
+ process.environment[k.to_s] = v
20
+ end
21
+ end
22
+ process.start
23
+ process.wait
24
+ process
25
+ end
26
+
27
+ module_function def check_call(cmd, env: nil, cwd: nil)
28
+ process = call(cmd, env: env, cwd: cwd)
29
+ unless process.exit_code == 0
30
+ raise SpawnError, "Failed to execute: #{cmd}"
31
+ end
32
+ end
33
+
34
+ module_function def get_output(cmd, env: nil, cwd: nil)
35
+ process = ChildProcess.new(*cmd)
36
+ process.io.inherit!
37
+ if cwd
38
+ process.cwd = cwd
39
+ end
40
+ if env
41
+ env.each do |k, v|
42
+ process.environment[k.to_s] = v
43
+ end
44
+ end
45
+
46
+ output = ''
47
+ r, w = IO.pipe
48
+
49
+ begin
50
+ process.io.stdout = w
51
+ process.start
52
+ w.close
53
+
54
+ thread = Thread.new do
55
+ begin
56
+ loop do
57
+ output << r.readpartial(16384)
58
+ end
59
+ rescue EOFError
60
+ end
61
+ end
62
+
63
+ process.wait
64
+ thread.join
65
+ ensure
66
+ r.close
67
+ end
68
+
69
+ [process, output]
70
+ end
71
+
72
+ module_function def check_output(*args)
73
+ process, output = get_output(*args)
74
+ unless process.exit_code == 0
75
+ raise SpawnError,"Failed to execute: #{args}"
76
+ end
77
+ output
78
+ end
79
+ end
80
+ end