puppet-library 0.2.0 → 0.3.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,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6c1ea499735a82bacf4051bd1b4ea24f2003d613
4
- data.tar.gz: ad4e79023760adaf7bc96becfa61d9403f2c60be
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZDc4NjNkZDQyZTlhNWMzZWIwZDhmMTM1MzY1ZTg4NzUyNWQyYzY2OQ==
5
+ data.tar.gz: !binary |-
6
+ MzA4N2JiOGU0MTkzMTBmZGUyYzhhOTFiNWQxZGViOGU0YTY5ODk3OA==
5
7
  SHA512:
6
- metadata.gz: ebe0288bdc97d3df601dc7f1d523d49d3884662af2b756aac2a7634219175a7b7f6035b3a3ffd0bafcfd87f0c9c3c5eb847ff12409a9aca7daf4de24da4b1141
7
- data.tar.gz: 81466a2f9550252a75f7f6d98d941df618eb0b611a52a2d7df0fb30e85b825dcdd0399ed3876889189417e07e14b650cb219f31d0cdb2e33b8d22cf597ce32e5
8
+ metadata.gz: !binary |-
9
+ NWUzMTQ5MDM2ZWEwOWY4MzcwYTI1NGRkNjJiNWI3ZDJiN2I0OTg5MGMwMWEz
10
+ MmY0ZmRhNTQ4ZWI5YWQ2ZWUxMGUxNDQ0YjhjYzMzMmJmMTBjNWRmMTlkM2M3
11
+ M2Y4YTU0NzQwOThjM2E0YTU2MzIzYWM2M2U4YmFmNDkyYzBkMDY=
12
+ data.tar.gz: !binary |-
13
+ MDBmNzBkZDYzZWZkZWU5YWE0ODhkZDNmMDQ5ZmRkODMxNjJjYTQ4Y2Q2M2U3
14
+ Njc3M2E3Zjk1NGE4MGM2ZWJmMDllMThiM2IwMDE3M2EzYjAxNDg2OTNlOGM0
15
+ ZGYyNGNhNjJlOWI5MTRlY2QzMjk0YzY5NmU1ZmFlNTNjNTg0ZDc=
data/.travis.yml CHANGED
@@ -16,13 +16,16 @@
16
16
 
17
17
  language: ruby
18
18
  before_install:
19
- - gem update --system 2.1.11
19
+ - gem update --system ${RUBYGEMS_VERSION}
20
20
  - gem --version
21
21
  rvm:
22
22
  - 1.8.7
23
23
  - 1.9.3
24
24
  - 2.0.0
25
25
  - 2.1.0
26
- matrix:
27
- allow_failures:
28
- - rvm: 2.1.0
26
+ env:
27
+ - RUBYGEMS_VERSION=2.1.11
28
+ - RUBYGEMS_VERSION=1.8.24
29
+ #matrix:
30
+ # allow_failures:
31
+ # - rvm: 2.1.0
data/CHANGELOG.yml CHANGED
@@ -28,4 +28,8 @@
28
28
  changes:
29
29
  - Support more specific module version queries using 'version' parameter
30
30
  - Fixed bug on older versions of RubyGems
31
+ - tag: v0.3.0
32
+ changes:
33
+ - Added module search
34
+ - Improved performance of proxy
31
35
 
@@ -15,29 +15,11 @@
15
15
  # You should have received a copy of the GNU General Public License
16
16
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
 
18
- module PuppetLibrary::ModuleRepo
19
- class Multi
20
- def repos
21
- @repos ||= []
22
- end
18
+ notification :terminal_notifier
23
19
 
24
- def add_repo(repo)
25
- repos << repo
26
- end
27
-
28
- def get_module(author, name, version)
29
- repos.each do |repo|
30
- mod = repo.get_module(author, name, version)
31
- return mod unless mod.nil?
32
- end
33
- return nil
34
- end
35
-
36
- def get_metadata(author, name)
37
- metadata_list = repos.inject([]) do |metadata_list, repo|
38
- metadata_list + repo.get_metadata(author, name)
39
- end
40
- metadata_list.unique_by { |metadata| metadata["version"] }
41
- end
42
- end
20
+ guard 'rspec', :version => 2 do
21
+ watch(%r{^spec/.+_spec\.rb$})
22
+ watch(%r{^lib/puppet_library/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
23
+ watch('spec/spec_helper.rb') { "spec" }
43
24
  end
25
+
data/README.md CHANGED
@@ -74,6 +74,21 @@ See all options
74
74
 
75
75
  $ puppet-library --help
76
76
 
77
+ ## Back-Ends
78
+
79
+ Puppet Library contains built-in support for:
80
+ - serving packaged (`.tar.gz`) modules from a directory (or directories) of your choosing
81
+ - proxying a remote forge (or forges)
82
+ - serving modules from a combination of the above
83
+
84
+ ## Compatibility with other tools
85
+
86
+ Puppet Library currently supports:
87
+ - search with Puppet (`puppet module search apache`)
88
+ - dependency resolution and installation with Puppet (`puppet module install puppetlabs/apache`)
89
+ - dependency resolution and installation with [librarian-puppet](http://librarian-puppet.com)
90
+ - installation with [r10k](https://github.com/adrienthebo/r10k)
91
+
77
92
  ## Contributing
78
93
 
79
94
  1. Fork it
data/Rakefile CHANGED
@@ -19,11 +19,25 @@ require 'bundler/gem_tasks'
19
19
  require 'rspec/core/rake_task'
20
20
  require 'coveralls/rake/task'
21
21
 
22
- if RUBY_VERSION.start_with? "1.8"
23
- # The integration test doesn't work on Ruby 1.8.
24
- DEFAULT_TEST_TASK = :spec
25
- else
22
+ class String
23
+ def green
24
+ "\e[32m#{self}\e[0m"
25
+ end
26
+
27
+ def red
28
+ "\e[31m#{self}\e[0m"
29
+ end
30
+ end
31
+
32
+ def ruby_version_supports_integration_test?(version = RUBY_VERSION)
33
+ # The integration test doesn't work on Ruby 1.8, and Puppet doesn't work on 2.1
34
+ ! /^(1\.8|2\.1)/.match(version)
35
+ end
36
+
37
+ if ruby_version_supports_integration_test?
26
38
  DEFAULT_TEST_TASK = :test
39
+ else
40
+ DEFAULT_TEST_TASK = :spec
27
41
  end
28
42
 
29
43
  Coveralls::RakeTask.new
@@ -31,6 +45,11 @@ Coveralls::RakeTask.new
31
45
  desc "Run the specs"
32
46
  RSpec::Core::RakeTask.new(:spec)
33
47
 
48
+ desc "Run the integration tests"
49
+ RSpec::Core::RakeTask.new(:integration_test) do |rspec|
50
+ rspec.pattern = "test/**/*_integration_test.rb"
51
+ end
52
+
34
53
  desc "Run all the tests"
35
54
  RSpec::Core::RakeTask.new(:test) do |rspec|
36
55
  rspec.pattern = "{spec,test}/**/*_{spec,integration_test}.rb"
@@ -40,7 +59,43 @@ task :default => [DEFAULT_TEST_TASK, 'coveralls:push']
40
59
 
41
60
  desc "Check it works on all local rubies"
42
61
  task :verify do
43
- system "rvm all do rake test"
62
+ versions = %w[1.8 1.9 2.0 2.1]
63
+ puts "\nRunning Specs".green
64
+ spec_results = versions.map do |ruby_version|
65
+ puts "\n- Ruby #{ruby_version}".green
66
+ system "rvm #{ruby_version} do rake spec"
67
+ end
68
+
69
+ puts "\nRunning Integration Tests".green
70
+ integration_test_results = versions.map do |ruby_version|
71
+ puts "\n- Ruby #{ruby_version}".green
72
+ system "rvm #{ruby_version} do rake integration_test"
73
+ end
74
+
75
+ puts "\nResults:\n".green
76
+ results = spec_results.zip(integration_test_results)
77
+ puts "+---------+-------+-------+"
78
+ puts "| Version | Specs | Tests |"
79
+ puts "+---------+-------+-------+"
80
+ versions.zip(results).each do |(version, (spec_result, integration_test_result))|
81
+ v = version
82
+ s = spec_result ? "pass".green : "fail".red
83
+ i = integration_test_result ? "pass".green : "fail".red
84
+ puts "| #{v} | #{s} | #{i} |"
85
+ end
86
+ puts "+---------+-------+-------+"
87
+
88
+ versions.zip(results).each do |(version, (spec_result, integration_test_result))|
89
+ unless spec_result
90
+ fail "Specs failed with Ruby #{version}"
91
+ end
92
+
93
+ if ruby_version_supports_integration_test?(version)
94
+ unless integration_test_result
95
+ fail "Integration tests failed with Ruby #{version}"
96
+ end
97
+ end
98
+ end
44
99
  end
45
100
 
46
101
  desc "Check all files for license headers"
data/config.ru CHANGED
@@ -19,8 +19,8 @@ require 'rubygems'
19
19
  require 'puppet_library'
20
20
 
21
21
  server = PuppetLibrary::Server.set_up do |library|
22
- library.module_repo PuppetLibrary::ModuleRepo::Proxy.new("http://forge.puppetlabs.com")
23
- library.module_repo PuppetLibrary::ModuleRepo::Directory.new("/var/lib/modules")
22
+ library.forge PuppetLibrary::Forge::Proxy.new("http://forge.puppetlabs.com")
23
+ library.forge PuppetLibrary::Forge::Directory.new("/var/lib/modules")
24
24
  end
25
25
 
26
26
  run server
@@ -0,0 +1,183 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # Puppet Library
3
+ # Copyright (C) 2014 drrb
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'puppet_library/forge/search_result'
19
+ require 'puppet_library/util'
20
+
21
+ module PuppetLibrary::Forge
22
+
23
+ class Abstract
24
+ def initialize(module_repo)
25
+ @repo = module_repo
26
+ end
27
+
28
+ def search_modules(query)
29
+ search = Search.new(query)
30
+
31
+ search_results = retrieve_all_metadata.select do |result|
32
+ search.matches? result
33
+ end.sort_by do |result|
34
+ result.version
35
+ end.reverse.map do |result|
36
+ result.to_search_result
37
+ end
38
+
39
+ SearchResult.merge_by_full_name(search_results)
40
+ end
41
+
42
+ def get_module_metadata(author, name)
43
+ modules = retrieve_metadata(author, name)
44
+
45
+ raise ModuleNotFound if modules.empty?
46
+
47
+ module_infos = modules.map { |m| m.to_info }
48
+ module_infos.deep_merge
49
+ end
50
+
51
+ def get_module_metadata_with_dependencies(author, name, version)
52
+ raise ModuleNotFound if retrieve_metadata(author, name).empty?
53
+
54
+ full_name = "#{author}/#{name}"
55
+ versions = collect_dependencies_versions(full_name)
56
+ return versions if version.nil?
57
+
58
+ versions[full_name] = versions[full_name].select do |v|
59
+ Gem::Dependency.new(name, version).match?(name, v["version"])
60
+ end
61
+
62
+ dependencies = versions[full_name].map do |v|
63
+ v["dependencies"].map {|(name, spec)| name}
64
+ end.flatten
65
+ versions = Hash[versions.select do |name, info|
66
+ name == full_name || dependencies.include?(name)
67
+ end]
68
+ return versions
69
+ end
70
+
71
+ def collect_dependencies_versions(module_full_name, metadata = {})
72
+ author, module_name = module_full_name.split "/"
73
+ module_versions = retrieve_metadata(author, module_name)
74
+ metadata[module_full_name] = module_versions.map {|v| v.to_version }
75
+
76
+ dependencies = module_versions.map {|v| v.dependency_names }.flatten
77
+ dependencies.each do |dependency|
78
+ collect_dependencies_versions(dependency, metadata) unless metadata.include? dependency
79
+ end
80
+ return metadata
81
+ end
82
+
83
+ def get_module_buffer(author, name, version)
84
+ @repo.get_module(author, name, version) or raise ModuleNotFound
85
+ end
86
+
87
+ def retrieve_metadata(author, module_name)
88
+ @repo.get_metadata(author, module_name).map {|metadata| ModuleMetadata.new(metadata)}
89
+ end
90
+
91
+ def retrieve_all_metadata
92
+ @repo.get_all_metadata.map {|metadata| ModuleMetadata.new(metadata)}
93
+ end
94
+ end
95
+
96
+ class Search
97
+ def initialize(query)
98
+ @query = query
99
+ end
100
+
101
+ def matches?(metadata)
102
+ return true if @query.nil?
103
+ return true if metadata.name.include? @query
104
+ return true if metadata.author.include? @query
105
+ return false
106
+ end
107
+ end
108
+
109
+ class ModuleMetadata
110
+ def initialize(metadata)
111
+ @metadata = metadata
112
+ end
113
+
114
+ def author
115
+ @metadata["name"][/^[^-]+/]
116
+ end
117
+
118
+ def name
119
+ @metadata["name"].sub(/^[^-]+-/, "")
120
+ end
121
+
122
+ def full_name
123
+ @metadata["name"].sub("-", "/")
124
+ end
125
+
126
+ def version
127
+ @metadata["version"]
128
+ end
129
+
130
+ def dependencies
131
+ @metadata["dependencies"]
132
+ end
133
+
134
+ def summary
135
+ @metadata["summary"]
136
+ end
137
+
138
+ def description
139
+ @metadata["description"]
140
+ end
141
+
142
+ def project_page
143
+ @metadata["project_page"]
144
+ end
145
+
146
+ def dependency_names
147
+ dependencies.map {|d| d["name"]}
148
+ end
149
+
150
+ def to_info
151
+ {
152
+ "author" => author,
153
+ "full_name" => full_name,
154
+ "name" => name,
155
+ "desc" => description,
156
+ "releases" => [ { "version" => version } ]
157
+ }
158
+ end
159
+
160
+ def to_version
161
+ {
162
+ "file" => "/modules/#{author}-#{name}-#{version}.tar.gz",
163
+ "version" => version,
164
+ "dependencies" => dependencies.map do |dependency|
165
+ [ dependency["name"], dependency["version_requirement"] ]
166
+ end
167
+ }
168
+ end
169
+
170
+ def to_search_result
171
+ {
172
+ "author" => author,
173
+ "full_name" => full_name,
174
+ "name" => name,
175
+ "desc" => summary,
176
+ "project_url" => project_page,
177
+ "releases" => [{ "version" => version}],
178
+ "version" => version,
179
+ "tag_list" => [author, name]
180
+ }
181
+ end
182
+ end
183
+ end
@@ -17,10 +17,12 @@
17
17
  require 'json'
18
18
  require 'rubygems/package'
19
19
  require 'zlib'
20
+ require 'puppet_library/forge/abstract'
20
21
 
21
- module PuppetLibrary::ModuleRepo
22
- class Directory
22
+ module PuppetLibrary::Forge
23
+ class Directory < PuppetLibrary::Forge::Abstract
23
24
  def initialize(module_dir)
25
+ super(self)
24
26
  @module_dir = module_dir
25
27
  end
26
28
 
@@ -34,7 +36,7 @@ module PuppetLibrary::ModuleRepo
34
36
  end
35
37
  end
36
38
 
37
- def get_metadata(author, module_name)
39
+ def get_metadata(author = "*", module_name = "")
38
40
  Dir["#{@module_dir}/#{author}-#{module_name}*"].map do |module_path|
39
41
  tar = Gem::Package::TarReader.new(Zlib::GzipReader.open(module_path))
40
42
  tar.rewind
@@ -42,5 +44,9 @@ module PuppetLibrary::ModuleRepo
42
44
  JSON.parse(metadata_file.read)
43
45
  end
44
46
  end
47
+
48
+ def get_all_metadata
49
+ get_metadata
50
+ end
45
51
  end
46
52
  end
@@ -0,0 +1,81 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # Puppet Library
3
+ # Copyright (C) 2014 drrb
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'puppet_library/forge/search_result'
19
+
20
+ module PuppetLibrary::Forge
21
+ class Multi
22
+ def add_forge(forge)
23
+ forges << forge
24
+ end
25
+
26
+ def search_modules(query)
27
+ all_results = forges.map do |forge|
28
+ forge.search_modules(query)
29
+ end.flatten
30
+
31
+ SearchResult.merge_by_full_name(all_results)
32
+ end
33
+
34
+ def get_module_buffer(author, name, version)
35
+ forges.each do |forge|
36
+ begin
37
+ return forge.get_module_buffer(author, name, version)
38
+ rescue ModuleNotFound
39
+ # Try the next one
40
+ end
41
+ end
42
+ raise ModuleNotFound
43
+ end
44
+
45
+ def get_module_metadata(author, name)
46
+ metadata_list = forges.inject([]) do |metadata_list, forge|
47
+ begin
48
+ metadata_list << forge.get_module_metadata(author, name)
49
+ rescue ModuleNotFound
50
+ metadata_list
51
+ end
52
+ end
53
+ raise ModuleNotFound if metadata_list.empty?
54
+ metadata_list.deep_merge.tap do |metadata|
55
+ metadata["releases"] = metadata["releases"].unique_by { |release| release["version"] }
56
+ end
57
+ end
58
+
59
+ def get_module_metadata_with_dependencies(author, name, version)
60
+ metadata_list = []
61
+ forges.each do |forge|
62
+ begin
63
+ metadata_list << forge.get_module_metadata_with_dependencies(author, name, version)
64
+ rescue ModuleNotFound
65
+ # Try the next one
66
+ end
67
+ end
68
+ raise ModuleNotFound if metadata_list.empty?
69
+ metadata_list.deep_merge.tap do |metadata|
70
+ metadata.each do |module_name, releases|
71
+ metadata[module_name] = releases.unique_by { |release| release["version"] }
72
+ end
73
+ end
74
+ end
75
+
76
+ private
77
+ def forges
78
+ @forges ||= []
79
+ end
80
+ end
81
+ end
@@ -20,8 +20,7 @@ require 'puppet_library/http/cache/in_memory'
20
20
  require 'puppet_library/http/cache/noop'
21
21
  require 'puppet_library/http/url'
22
22
 
23
- module PuppetLibrary::ModuleRepo
24
- #include PuppetLibrary::Http
23
+ module PuppetLibrary::Forge
25
24
  class Proxy
26
25
  def initialize(url,
27
26
  query_cache = PuppetLibrary::Http::Cache::InMemory.new,
@@ -33,29 +32,37 @@ module PuppetLibrary::ModuleRepo
33
32
  @download_cache = download_cache
34
33
  end
35
34
 
36
- def get_module(author, name, version)
37
- version_info = get_module_version(author, name, version)
38
- if version_info.nil?
39
- nil
40
- else
35
+ def search_modules(query)
36
+ query_parameter = query.nil? ? "" : "?q=#{query}"
37
+ results = get("/modules.json#{query_parameter}")
38
+ JSON.parse results
39
+ end
40
+
41
+ def get_module_buffer(author, name, version)
42
+ begin
43
+ version_info = get_module_version(author, name, version)
44
+ raise ModuleNotFound if version_info.nil?
41
45
  download_file(version_info["file"])
46
+ rescue OpenURI::HTTPError
47
+ raise ModuleNotFound
42
48
  end
43
49
  end
44
50
 
45
- def get_metadata(author, name)
46
- module_versions = get_module_versions(author, name)
47
- module_versions.map do |version_info|
48
- {
49
- "name" => "#{author}-#{name}",
50
- "author" => author,
51
- "version" => version_info["version"],
52
- "dependencies" => version_info["dependencies"].map do |(dep_name, dep_spec)|
53
- { "name" => dep_name, "version_requirement" => dep_spec }
54
- end
55
- }
51
+ def get_module_metadata(author, name)
52
+ begin
53
+ response = get("/#{author}/#{name}.json")
54
+ JSON.parse(response)
55
+ rescue OpenURI::HTTPError
56
+ raise ModuleNotFound
57
+ end
58
+ end
59
+
60
+ def get_module_metadata_with_dependencies(author, name, version)
61
+ begin
62
+ look_up_releases(author, name, version)
63
+ rescue OpenURI::HTTPError
64
+ raise ModuleNotFound
56
65
  end
57
- rescue OpenURI::HTTPError => http_error
58
- return []
59
66
  end
60
67
 
61
68
  private
@@ -71,8 +78,10 @@ module PuppetLibrary::ModuleRepo
71
78
  versions["#{author}/#{name}"]
72
79
  end
73
80
 
74
- def look_up_releases(author, name)
75
- response = get("/api/v1/releases.json?module=#{author}/#{name}")
81
+ def look_up_releases(author, name, version = nil)
82
+ version_query = version ? "&version=#{version}" : ""
83
+ url = "/api/v1/releases.json?module=#{author}/#{name}#{version_query}"
84
+ response = get(url)
76
85
  JSON.parse(response)
77
86
  end
78
87
 
@@ -93,3 +102,4 @@ module PuppetLibrary::ModuleRepo
93
102
  end
94
103
  end
95
104
  end
105
+
@@ -0,0 +1,50 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # Puppet Library
3
+ # Copyright (C) 2014 drrb
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'puppet_library/util'
19
+
20
+ module PuppetLibrary::Forge::SearchResult
21
+ def self.merge_by_full_name(results)
22
+ results_by_module = results.group_by do |result|
23
+ result["full_name"]
24
+ end
25
+
26
+ results_by_module.values.map do |module_results|
27
+ combine_search_results(module_results)
28
+ end.flatten
29
+ end
30
+
31
+ def self.combine_search_results(search_results)
32
+ highest_version, tags, releases = search_results.inject([nil, [], []]) do |(highest_version, tags, releases), result|
33
+ [
34
+ max_version(highest_version, result["version"]),
35
+ tags + (result["tag_list"] || []),
36
+ releases + (result["releases"] || [])
37
+ ]
38
+ end
39
+
40
+ combined_result = search_results.first.tap do |result|
41
+ result["version"] = highest_version
42
+ result["tag_list"] = tags.uniq
43
+ result["releases"] = releases.uniq.version_sort_by {|r| r["version"]}.reverse
44
+ end
45
+ end
46
+
47
+ def self.max_version(left, right)
48
+ [Gem::Version.new(left), Gem::Version.new(right)].max.version
49
+ end
50
+ end