mongoid 7.2.1 → 7.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/README.md +1 -1
  5. data/lib/mongoid.rb +1 -0
  6. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +1 -1
  7. data/lib/mongoid/association/proxy.rb +1 -1
  8. data/lib/mongoid/association/referenced/has_many/enumerable.rb +1 -1
  9. data/lib/mongoid/association/referenced/has_many/proxy.rb +1 -1
  10. data/lib/mongoid/attributes.rb +8 -1
  11. data/lib/mongoid/criteria.rb +1 -1
  12. data/lib/mongoid/errors/mongoid_error.rb +1 -1
  13. data/lib/mongoid/interceptable.rb +1 -1
  14. data/lib/mongoid/matcher.rb +19 -43
  15. data/lib/mongoid/matcher/elem_match.rb +2 -1
  16. data/lib/mongoid/matcher/expression.rb +5 -14
  17. data/lib/mongoid/matcher/field_expression.rb +4 -5
  18. data/lib/mongoid/reloadable.rb +5 -0
  19. data/lib/mongoid/validatable/associated.rb +1 -1
  20. data/lib/mongoid/validatable/presence.rb +3 -3
  21. data/lib/mongoid/validatable/uniqueness.rb +1 -1
  22. data/lib/mongoid/version.rb +1 -1
  23. data/lib/rails/generators/mongoid/config/config_generator.rb +8 -1
  24. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +1 -1
  25. data/spec/integration/app_spec.rb +139 -82
  26. data/spec/integration/document_spec.rb +21 -0
  27. data/spec/integration/matcher_operator_data/elem_match.yml +46 -0
  28. data/spec/integration/matcher_operator_data/implicit_traversal.yml +96 -0
  29. data/spec/lite_spec_helper.rb +2 -3
  30. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +17 -4
  31. data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +17 -0
  32. data/spec/mongoid/attributes_spec.rb +241 -0
  33. data/spec/mongoid/clients/options_spec.rb +2 -0
  34. data/spec/mongoid/contextual/atomic_spec.rb +17 -4
  35. data/spec/mongoid/criteria_spec.rb +4 -0
  36. data/spec/mongoid/document_fields_spec.rb +26 -0
  37. data/spec/mongoid/document_query_spec.rb +51 -0
  38. data/spec/mongoid/errors/mongoid_error_spec.rb +20 -8
  39. data/spec/mongoid/matcher/extract_attribute_data/numeric_keys.yml +104 -0
  40. data/spec/mongoid/matcher/extract_attribute_data/traversal.yml +68 -88
  41. data/spec/mongoid/matcher/extract_attribute_spec.rb +3 -13
  42. data/spec/mongoid/persistable/settable_spec.rb +30 -0
  43. data/spec/mongoid/persistable_spec.rb +2 -2
  44. data/spec/shared/bin/get-mongodb-download-url +17 -0
  45. data/spec/shared/bin/s3-copy +45 -0
  46. data/spec/shared/bin/s3-upload +69 -0
  47. data/spec/shared/lib/mrss/cluster_config.rb +19 -4
  48. data/spec/shared/lib/mrss/constraints.rb +46 -8
  49. data/spec/shared/lib/mrss/docker_runner.rb +10 -1
  50. data/spec/shared/lib/mrss/lite_constraints.rb +16 -0
  51. data/spec/shared/lib/mrss/server_version_registry.rb +79 -33
  52. data/spec/shared/lib/mrss/spec_organizer.rb +32 -2
  53. data/spec/shared/lib/mrss/utils.rb +15 -0
  54. data/spec/shared/share/Dockerfile.erb +122 -29
  55. data/spec/shared/share/haproxy-1.conf +16 -0
  56. data/spec/shared/share/haproxy-2.conf +17 -0
  57. data/spec/shared/shlib/server.sh +58 -11
  58. data/spec/shared/shlib/set_env.sh +4 -1
  59. data/spec/spec_helper.rb +1 -1
  60. data/spec/support/models/address.rb +4 -0
  61. data/spec/support/models/mop.rb +10 -0
  62. data/spec/support/models/person.rb +9 -0
  63. data/spec/support/spec_config.rb +8 -0
  64. metadata +555 -527
  65. metadata.gz.sig +0 -0
@@ -25,20 +25,10 @@ describe 'Matcher.extract_attribute' do
25
25
  Mongoid::Matcher.extract_attribute(document, key)
26
26
  end
27
27
 
28
- let(:expected_exists) { spec['exists'] }
29
- let(:expected_value) { spec['value'] }
30
- let(:expected_expanded) { spec['expanded'] }
28
+ let(:expected) { spec.fetch('result') }
31
29
 
32
- it 'has the expected exists flag' do
33
- actual[0].should == expected_exists
34
- end
35
-
36
- it 'has the expected value' do
37
- actual[1].should == expected_value
38
- end
39
-
40
- it 'has the expected expanded flag' do
41
- actual[2].should == expected_expanded
30
+ it 'has the expected result' do
31
+ actual.should == expected
42
32
  end
43
33
  end
44
34
  end
@@ -512,4 +512,34 @@ describe Mongoid::Persistable::Settable do
512
512
  end
513
513
  end
514
514
  end
515
+
516
+ context "when the field being set was projected out" do
517
+ let(:full_agent) do
518
+ Agent.create!(title: "Double-Oh Eight")
519
+ end
520
+
521
+ let(:agent) do
522
+ Agent.where(_id: full_agent.id).only(:dob).first
523
+ end
524
+
525
+ context 'field exists in database' do
526
+ it "raises MissingAttributeError" do
527
+ lambda do
528
+ agent.set(title: '008')
529
+ end.should raise_error(ActiveModel::MissingAttributeError)
530
+
531
+ expect(agent.reload.title).to eq 'Double-Oh Eight'
532
+ end
533
+ end
534
+
535
+ context 'field does not exist in database' do
536
+ it "raises MissingAttributeError" do
537
+ lambda do
538
+ agent.set(number: '008')
539
+ end.should raise_error(ActiveModel::MissingAttributeError)
540
+
541
+ expect(agent.reload.read_attribute(:number)).to be nil
542
+ end
543
+ end
544
+ end
515
545
  end
@@ -178,8 +178,8 @@ describe Mongoid::Persistable do
178
178
 
179
179
  before do
180
180
  class Band
181
- def my_updates(*args)
182
- atomically(*args) do |d|
181
+ def my_updates(**args)
182
+ atomically(**args) do |d|
183
183
  d.set(name: "Placebo")
184
184
  d.unset(:origin)
185
185
  end
@@ -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
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
1
4
  # ClusterConfig requires ClientRegistry class provided by the host project.
2
5
 
3
6
  require 'singleton'
@@ -12,6 +15,11 @@ module Mrss
12
15
  @single_server
13
16
  end
14
17
 
18
+ def sharded_ish?
19
+ determine_cluster_config
20
+ @topology == :sharded || @topology == :load_balanced
21
+ end
22
+
15
23
  def replica_set_name
16
24
  determine_cluster_config
17
25
  @replica_set_name
@@ -45,7 +53,7 @@ module Mrss
45
53
  raise "Deployment server version not known - check that connection to deployment succeeded"
46
54
  end
47
55
 
48
- if server_version >= '3.4' && topology != :sharded
56
+ if server_version >= '3.4' && !sharded_ish?
49
57
  fcv
50
58
  else
51
59
  if short_server_version == '4.1'
@@ -82,6 +90,11 @@ module Mrss
82
90
  @primary_description
83
91
  end
84
92
 
93
+ def server_parameters
94
+ determine_cluster_config
95
+ @server_parameters
96
+ end
97
+
85
98
  # Try running a command on the admin database to see if the mongod was
86
99
  # started with auth.
87
100
  def auth_enabled?
@@ -107,7 +120,7 @@ module Mrss
107
120
  :mmapv1
108
121
  else
109
122
  client = ClientRegistry.instance.global_client('root_authorized')
110
- if topology == :sharded
123
+ if sharded_ish?
111
124
  shards = client.use(:admin).command(listShards: 1).first
112
125
  if shards['shards'].empty?
113
126
  raise 'Shards are empty'
@@ -196,8 +209,10 @@ module Mrss
196
209
  @server_version = build_info['version']
197
210
  @enterprise = build_info['modules'] && build_info['modules'].include?('enterprise')
198
211
 
199
- if @topology != :sharded && short_server_version >= '3.4'
200
- rv = client.use(:admin).command(getParameter: 1, featureCompatibilityVersion: 1).first['featureCompatibilityVersion']
212
+ @server_parameters = client.use(:admin).command(getParameter: '*').first
213
+
214
+ if !sharded_ish? && short_server_version >= '3.4'
215
+ rv = @server_parameters['featureCompatibilityVersion']
201
216
  @fcv = rv['version'] || rv
202
217
  end
203
218
  end
@@ -52,7 +52,7 @@ module Mrss
52
52
  end
53
53
 
54
54
  def require_topology(*topologies)
55
- invalid_topologies = topologies - [:single, :replica_set, :sharded]
55
+ invalid_topologies = topologies - [:single, :replica_set, :sharded, :load_balanced]
56
56
 
57
57
  unless invalid_topologies.empty?
58
58
  raise ArgumentError, "Invalid topologies requested: #{invalid_topologies.join(', ')}"
@@ -82,7 +82,7 @@ module Mrss
82
82
  unless ClusterConfig.instance.server_version >= '4.0'
83
83
  skip 'Transactions tests in a replica set topology require server 4.0+'
84
84
  end
85
- when :sharded
85
+ when :sharded, :load_balanced
86
86
  unless ClusterConfig.instance.server_version >= '4.2'
87
87
  skip 'Transactions tests in a sharded cluster topology require server 4.2+'
88
88
  end
@@ -113,6 +113,14 @@ module Mrss
113
113
  end
114
114
  end
115
115
 
116
+ def require_retry_writes
117
+ before(:all) do
118
+ unless SpecConfig.instance.retry_writes?
119
+ skip "Retry writes is disabled"
120
+ end
121
+ end
122
+ end
123
+
116
124
  def require_no_retry_writes
117
125
  before(:all) do
118
126
  if SpecConfig.instance.retry_writes?
@@ -172,8 +180,8 @@ module Mrss
172
180
  skip "Zstd compression is enabled"
173
181
  end
174
182
  end
175
- end
176
-
183
+ end
184
+
177
185
  def require_no_compression
178
186
  before(:all) do
179
187
  if SpecConfig.instance.compressors
@@ -241,7 +249,9 @@ module Mrss
241
249
  # (mongos 4.0+ overrides the write concern)
242
250
  def require_set_write_concern
243
251
  before(:all) do
244
- if ClusterConfig.instance.topology == :sharded && ClusterConfig.instance.short_server_version >= '4.0'
252
+ if %i(sharded load_balanced).include?(ClusterConfig.instance.topology) &&
253
+ ClusterConfig.instance.short_server_version >= '4.0'
254
+ then
245
255
  skip "mongos 4.0+ overrides write concern"
246
256
  end
247
257
  end
@@ -265,7 +275,9 @@ module Mrss
265
275
 
266
276
  def require_wired_tiger
267
277
  before(:all) do
268
- if ClusterConfig.instance.storage_engine != :wired_tiger
278
+ # Storage detection fails for serverless instances. However, it is safe to
279
+ # assume that a serverless instance uses WiredTiger Storage Engine.
280
+ if !SpecConfig.instance.serverless? && ClusterConfig.instance.storage_engine != :wired_tiger
269
281
  skip 'Test requires WiredTiger storage engine'
270
282
  end
271
283
  end
@@ -274,7 +286,9 @@ module Mrss
274
286
  def require_wired_tiger_on_36
275
287
  before(:all) do
276
288
  if ClusterConfig.instance.short_server_version >= '3.6'
277
- if ClusterConfig.instance.storage_engine != :wired_tiger
289
+ # Storage detection fails for serverless instances. However, it is safe to
290
+ # assume that a serverless instance uses WiredTiger Storage Engine.
291
+ if !SpecConfig.instance.serverless? && ClusterConfig.instance.storage_engine != :wired_tiger
278
292
  skip 'Test requires WiredTiger storage engine on 3.6+ servers'
279
293
  end
280
294
  end
@@ -283,7 +297,7 @@ module Mrss
283
297
 
284
298
  def require_mmapv1
285
299
  before(:all) do
286
- if ClusterConfig.instance.storage_engine != :mmapv1
300
+ if SpecConfig.instance.serverless? || ClusterConfig.instance.storage_engine != :mmapv1
287
301
  skip 'Test requires MMAPv1 storage engine'
288
302
  end
289
303
  end
@@ -326,5 +340,29 @@ module Mrss
326
340
  end
327
341
  end
328
342
  end
343
+
344
+ def require_required_api_version
345
+ before(:all) do
346
+ unless ENV['API_VERSION_REQUIRED'] == '1'
347
+ skip 'Set API_VERSION_REQUIRED=1 to run this test'
348
+ end
349
+ end
350
+ end
351
+
352
+ def require_no_required_api_version
353
+ before(:all) do
354
+ if ENV['API_VERSION_REQUIRED'] == '1'
355
+ skip 'Cannot have API_VERSION_REQUIRED=1 to run this test'
356
+ end
357
+ end
358
+ end
359
+
360
+ def require_unix_socket
361
+ before(:all) do
362
+ if ENV['TOPOLOGY'] == 'load-balanced'
363
+ skip 'Load balancer does not listen on Unix sockets'
364
+ end
365
+ end
366
+ end
329
367
  end
330
368
  end
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
1
4
  require 'optparse'
2
5
  require 'erb'
3
6
  autoload :Dotenv, 'dotenv'
@@ -108,7 +111,7 @@ module Mrss
108
111
  '.'])
109
112
  end
110
113
 
111
- BASE_TEST_COMMAND = %w(docker run -i --tmpfs /tmpfs:exec).freeze
114
+ BASE_TEST_COMMAND = %w(docker run --rm -i --tmpfs /tmpfs:exec).freeze
112
115
 
113
116
  def run_tests
114
117
  run_command(BASE_TEST_COMMAND + tty_arg + extra_env + [image_tag] +
@@ -176,9 +179,11 @@ module Mrss
176
179
  BASE_IMAGES = {
177
180
  'debian81' => 'debian:jessie',
178
181
  'debian92' => 'debian:stretch',
182
+ 'debian10' => 'debian:buster',
179
183
  'ubuntu1404' => 'ubuntu:trusty',
180
184
  'ubuntu1604' => 'ubuntu:xenial',
181
185
  'ubuntu1804' => 'ubuntu:bionic',
186
+ 'ubuntu2004' => 'ubuntu:focal',
182
187
  'rhel62' => 'centos:6',
183
188
  'rhel70' => 'centos:7',
184
189
  }.freeze
@@ -195,6 +200,10 @@ module Mrss
195
200
  ruby == 'ruby-head'
196
201
  end
197
202
 
203
+ def system_ruby?
204
+ %w(1 true yes).include?(@env['SYSTEM_RUBY']&.downcase)
205
+ end
206
+
198
207
  def server_version
199
208
  @env['MONGODB_VERSION']
200
209
  end
@@ -171,5 +171,21 @@ module Mrss
171
171
  end
172
172
  end
173
173
  end
174
+
175
+ def require_active_support
176
+ before(:all) do
177
+ if !SpecConfig.instance.active_support?
178
+ skip 'This test requires ActiveSupport; set WITH_ACTIVE_SUPPORT=1 in environment'
179
+ end
180
+ end
181
+ end
182
+
183
+ def no_active_support
184
+ before(:all) do
185
+ if SpecConfig.instance.active_support?
186
+ skip 'This test requires no ActiveSupport; unset WITH_ACTIVE_SUPPORT in environment'
187
+ end
188
+ end
189
+ end
174
190
  end
175
191
  end
@@ -1,8 +1,23 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+
1
4
  autoload :JSON, 'json'
2
5
  require 'open-uri'
3
6
 
4
7
  module Mrss
5
8
  class ServerVersionRegistry
9
+ class Error < StandardError
10
+ end
11
+
12
+ class UnknownVersion < Error
13
+ end
14
+
15
+ class MissingDownloadUrl < Error
16
+ end
17
+
18
+ class BrokenDownloadUrl < Error
19
+ end
20
+
6
21
  def initialize(desired_version, arch)
7
22
  @desired_version, @arch = desired_version, arch
8
23
  end
@@ -11,39 +26,16 @@ module Mrss
11
26
 
12
27
  def download_url
13
28
  @download_url ||= begin
14
- info = JSON.load(uri_open('http://downloads.mongodb.org/current.json').read)
15
- version = info['versions'].detect do |version|
16
- version['version'].start_with?(desired_version) &&
17
- !version['version'].include?('-') &&
18
- # Sometimes the download situation is borked and there is a release
19
- # with no downloads... skip those.
20
- !version['downloads'].empty?
21
- end
22
- # Allow RC releases if there isn't a GA release.
23
- version ||= info['versions'].detect do |version|
24
- version['version'].start_with?(desired_version) &&
25
- # Sometimes the download situation is borked and there is a release
26
- # with no downloads... skip those.
27
- !version['downloads'].empty?
29
+ version, version_ok = detect_version(current_catalog)
30
+ if version.nil?
31
+ version, full_version_ok = detect_version(full_catalog)
32
+ version_ok ||= full_version_ok
28
33
  end
29
34
  if version.nil?
30
- info = JSON.load(URI.parse('http://downloads.mongodb.org/full.json').open.read)
31
- versions = info['versions'].select do |version|
32
- version['version'].start_with?(desired_version) &&
33
- !version['downloads'].empty?
34
- end
35
- # Get rid of rc, beta etc. versions if there is a GA release.
36
- if versions.any? { |version| !version.include?('-') }
37
- versions.delete_if do |version|
38
- version['version'].include?('-')
39
- end
40
- end
41
- # Versions are ordered with newest first, take the first one i.e. the most
42
- # recent one.
43
- version = versions.first
44
- if version.nil?
45
- STDERR.puts "Error: no version #{desired_version}"
46
- exit 2
35
+ if version_ok
36
+ raise MissingDownloadUrl, "No downloads for version #{desired_version}"
37
+ else
38
+ raise UnknownVersion, "No version #{desired_version}"
47
39
  end
48
40
  end
49
41
  dl = version['downloads'].detect do |dl|
@@ -51,13 +43,31 @@ module Mrss
51
43
  dl['arch'] == 'x86_64'
52
44
  end
53
45
  unless dl
54
- STDERR.puts "Error: no download for #{arch} for #{version['version']}"
55
- exit 2
46
+ raise MissingDownloadUrl, "No download for #{arch} for #{version['version']}"
56
47
  end
57
48
  url = dl['archive']['url']
58
49
  end
50
+ rescue MissingDownloadUrl
51
+ if %w(4.7 4.7.0).include?(desired_version)
52
+ # 4.7.0 has no advertised downloads but it is downloadable and
53
+ # we do need it. Dirty hack below.
54
+ registry = self.class.new('4.4.3', arch)
55
+ registry.download_url.sub('4.4.3', '4.7.0').tap do |url|
56
+ # Sanity check - ensure the URL we hacked up is a valid one
57
+ io = uri_open(url)
58
+ begin
59
+ io.read(1)
60
+ ensure
61
+ io.close
62
+ end
63
+ end
64
+ else
65
+ raise
66
+ end
59
67
  end
60
68
 
69
+ private
70
+
61
71
  def uri_open(*args)
62
72
  if RUBY_VERSION < '2.5'
63
73
  open(*args)
@@ -65,5 +75,41 @@ module Mrss
65
75
  URI.open(*args)
66
76
  end
67
77
  end
78
+
79
+ def detect_version(catalog)
80
+ candidate_versions = catalog['versions'].select do |version|
81
+ version['version'].start_with?(desired_version) &&
82
+ !version['version'].include?('-')
83
+ end
84
+ version_ok = !candidate_versions.empty?
85
+ # Sometimes the download situation is borked and there is a release
86
+ # with no downloads... skip those.
87
+ version = candidate_versions.detect do |version|
88
+ !version['downloads'].empty?
89
+ end
90
+ # Allow RC releases if there isn't a GA release.
91
+ if version.nil?
92
+ candidate_versions = catalog['versions'].select do |version|
93
+ version['version'].start_with?(desired_version)
94
+ end
95
+ version_ok ||= !candidate_versions.empty?
96
+ version = candidate_versions.detect do |version|
97
+ !version['downloads'].empty?
98
+ end
99
+ end
100
+ [version, version_ok]
101
+ end
102
+
103
+ def current_catalog
104
+ @current_catalog ||= begin
105
+ JSON.load(uri_open('http://downloads.mongodb.org/current.json').read)
106
+ end
107
+ end
108
+
109
+ def full_catalog
110
+ @full_catalog ||= begin
111
+ JSON.load(uri_open('http://downloads.mongodb.org/full.json').read)
112
+ end
113
+ end
68
114
  end
69
115
  end