puppet 6.14.0 → 6.15.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

Files changed (195) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +15 -15
  3. data/ext/windows/service/daemon.rb +3 -3
  4. data/lib/puppet.rb +1 -1
  5. data/lib/puppet/agent.rb +2 -10
  6. data/lib/puppet/application/agent.rb +2 -1
  7. data/lib/puppet/application/filebucket.rb +5 -14
  8. data/lib/puppet/application/ssl.rb +2 -2
  9. data/lib/puppet/configurer.rb +7 -3
  10. data/lib/puppet/configurer/plugin_handler.rb +1 -1
  11. data/lib/puppet/defaults.rb +22 -2
  12. data/lib/puppet/environments.rb +4 -5
  13. data/lib/puppet/face/plugin.rb +1 -1
  14. data/lib/puppet/file_system/file_impl.rb +13 -9
  15. data/lib/puppet/forge/repository.rb +1 -1
  16. data/lib/puppet/functions/call.rb +1 -1
  17. data/lib/puppet/functions/reduce.rb +2 -4
  18. data/lib/puppet/http.rb +2 -0
  19. data/lib/puppet/http/client.rb +191 -52
  20. data/lib/puppet/http/external_client.rb +96 -0
  21. data/lib/puppet/http/redirector.rb +34 -0
  22. data/lib/puppet/http/resolver.rb +46 -3
  23. data/lib/puppet/http/resolver/server_list.rb +75 -15
  24. data/lib/puppet/http/resolver/settings.rb +22 -2
  25. data/lib/puppet/http/resolver/srv.rb +28 -2
  26. data/lib/puppet/http/response.rb +63 -1
  27. data/lib/puppet/http/retry_after_handler.rb +39 -0
  28. data/lib/puppet/http/service.rb +67 -1
  29. data/lib/puppet/http/service/ca.rb +71 -9
  30. data/lib/puppet/http/service/compiler.rb +213 -11
  31. data/lib/puppet/http/service/file_server.rb +105 -4
  32. data/lib/puppet/http/service/report.rb +36 -3
  33. data/lib/puppet/http/session.rb +59 -8
  34. data/lib/puppet/indirector/catalog/rest.rb +2 -1
  35. data/lib/puppet/indirector/facts/rest.rb +2 -1
  36. data/lib/puppet/indirector/file_bucket_file/rest.rb +48 -0
  37. data/lib/puppet/indirector/file_metadata/rest.rb +4 -2
  38. data/lib/puppet/indirector/node/rest.rb +2 -1
  39. data/lib/puppet/indirector/report/yaml.rb +23 -0
  40. data/lib/puppet/indirector/status/rest.rb +2 -1
  41. data/lib/puppet/metatype/manager.rb +80 -80
  42. data/lib/puppet/network/http/base_pool.rb +6 -1
  43. data/lib/puppet/network/http/pool.rb +2 -4
  44. data/lib/puppet/network/http_pool.rb +1 -0
  45. data/lib/puppet/node/environment.rb +11 -1
  46. data/lib/puppet/pal/pal_impl.rb +1 -29
  47. data/lib/puppet/parser/compiler.rb +14 -7
  48. data/lib/puppet/parser/functions.rb +18 -13
  49. data/lib/puppet/pops/loaders.rb +7 -5
  50. data/lib/puppet/provider/group/windows_adsi.rb +3 -3
  51. data/lib/puppet/provider/package/apt.rb +61 -1
  52. data/lib/puppet/provider/package/dnfmodule.rb +39 -12
  53. data/lib/puppet/provider/package/gem.rb +41 -7
  54. data/lib/puppet/provider/package/pacman.rb +2 -5
  55. data/lib/puppet/provider/package/pip.rb +105 -33
  56. data/lib/puppet/provider/package/pip3.rb +0 -2
  57. data/lib/puppet/provider/package/pkgdmg.rb +1 -1
  58. data/lib/puppet/provider/package/pkgng.rb +16 -4
  59. data/lib/puppet/provider/package/puppet_gem.rb +6 -2
  60. data/lib/puppet/provider/package/rpm.rb +6 -213
  61. data/lib/puppet/provider/package/yum.rb +92 -19
  62. data/lib/puppet/provider/service/systemd.rb +2 -1
  63. data/lib/puppet/reports/http.rb +13 -11
  64. data/lib/puppet/resource/type_collection.rb +20 -16
  65. data/lib/puppet/ssl.rb +1 -0
  66. data/lib/puppet/ssl/host.rb +4 -4
  67. data/lib/puppet/ssl/oids.rb +1 -0
  68. data/lib/puppet/ssl/state_machine.rb +50 -33
  69. data/lib/puppet/transaction/report.rb +2 -2
  70. data/lib/puppet/type.rb +6 -1
  71. data/lib/puppet/type/file/source.rb +4 -2
  72. data/lib/puppet/type/package.rb +25 -2
  73. data/lib/puppet/type/user.rb +0 -19
  74. data/lib/puppet/util/at_fork.rb +1 -1
  75. data/lib/puppet/util/autoload.rb +3 -0
  76. data/lib/puppet/util/instance_loader.rb +14 -10
  77. data/lib/puppet/util/package/version/debian.rb +175 -0
  78. data/lib/puppet/util/package/version/gem.rb +15 -0
  79. data/lib/puppet/util/package/version/pip.rb +167 -0
  80. data/lib/puppet/util/package/version/range.rb +50 -0
  81. data/lib/puppet/util/package/version/range/gt.rb +14 -0
  82. data/lib/puppet/util/package/version/range/gt_eq.rb +14 -0
  83. data/lib/puppet/util/package/version/range/lt.rb +14 -0
  84. data/lib/puppet/util/package/version/range/lt_eq.rb +14 -0
  85. data/lib/puppet/util/package/version/range/min_max.rb +21 -0
  86. data/lib/puppet/util/package/version/range/simple.rb +11 -0
  87. data/lib/puppet/util/package/version/rpm.rb +73 -0
  88. data/lib/puppet/util/pidlock.rb +13 -7
  89. data/lib/puppet/util/platform.rb +5 -0
  90. data/lib/puppet/util/rpm_compare.rb +193 -0
  91. data/lib/puppet/util/windows/adsi.rb +2 -2
  92. data/lib/puppet/util/windows/process.rb +15 -14
  93. data/lib/puppet/util/windows/security.rb +1 -0
  94. data/lib/puppet/util/windows/sid.rb +3 -3
  95. data/lib/puppet/version.rb +1 -1
  96. data/locales/puppet.pot +207 -201
  97. data/man/man5/puppet.conf.5 +11 -3
  98. data/man/man8/puppet-agent.8 +1 -1
  99. data/man/man8/puppet-apply.8 +1 -1
  100. data/man/man8/puppet-catalog.8 +1 -1
  101. data/man/man8/puppet-config.8 +1 -1
  102. data/man/man8/puppet-describe.8 +1 -1
  103. data/man/man8/puppet-device.8 +1 -1
  104. data/man/man8/puppet-doc.8 +1 -1
  105. data/man/man8/puppet-epp.8 +1 -1
  106. data/man/man8/puppet-facts.8 +1 -1
  107. data/man/man8/puppet-filebucket.8 +1 -1
  108. data/man/man8/puppet-generate.8 +1 -1
  109. data/man/man8/puppet-help.8 +1 -1
  110. data/man/man8/puppet-key.8 +1 -1
  111. data/man/man8/puppet-lookup.8 +1 -1
  112. data/man/man8/puppet-man.8 +1 -1
  113. data/man/man8/puppet-module.8 +1 -1
  114. data/man/man8/puppet-node.8 +1 -1
  115. data/man/man8/puppet-parser.8 +1 -1
  116. data/man/man8/puppet-plugin.8 +1 -1
  117. data/man/man8/puppet-report.8 +1 -1
  118. data/man/man8/puppet-resource.8 +1 -1
  119. data/man/man8/puppet-script.8 +1 -1
  120. data/man/man8/puppet-ssl.8 +1 -1
  121. data/man/man8/puppet-status.8 +1 -1
  122. data/man/man8/puppet.8 +2 -2
  123. data/spec/fixtures/ssl/unknown-127.0.0.1-key.pem +67 -0
  124. data/spec/fixtures/ssl/unknown-127.0.0.1.pem +48 -0
  125. data/spec/fixtures/ssl/unknown-ca-key.pem +67 -0
  126. data/spec/fixtures/ssl/unknown-ca.pem +59 -0
  127. data/spec/fixtures/unit/provider/package/dnfmodule/{dnf-module-list-installed.txt → dnf-module-list-enabled.txt} +2 -0
  128. data/spec/fixtures/unit/provider/package/pkgng/pkg.version +2 -0
  129. data/spec/fixtures/unit/provider/package/yum/yum-check-update-subscription-manager.txt +9 -0
  130. data/spec/fixtures/unit/provider/service/systemd/list_unit_files_services +9 -0
  131. data/spec/integration/application/agent_spec.rb +329 -0
  132. data/spec/integration/application/apply_spec.rb +132 -3
  133. data/spec/integration/application/filebucket_spec.rb +190 -0
  134. data/spec/integration/application/plugin_spec.rb +50 -0
  135. data/spec/integration/http/client_spec.rb +34 -40
  136. data/spec/integration/indirector/report/yaml.rb +83 -0
  137. data/spec/integration/module_tool/forge_spec.rb +2 -15
  138. data/spec/integration/network/http_pool_spec.rb +11 -19
  139. data/spec/integration/node/environment_spec.rb +15 -0
  140. data/spec/integration/util/windows/adsi_spec.rb +1 -1
  141. data/spec/lib/puppet/test_ca.rb +2 -2
  142. data/spec/lib/puppet_spec/https.rb +10 -7
  143. data/spec/lib/puppet_spec/puppetserver.rb +119 -0
  144. data/spec/shared_contexts/https.rb +29 -0
  145. data/spec/unit/agent_spec.rb +33 -25
  146. data/spec/unit/application/agent_spec.rb +5 -1
  147. data/spec/unit/application/device_spec.rb +2 -2
  148. data/spec/unit/application/filebucket_spec.rb +22 -2
  149. data/spec/unit/configurer_spec.rb +1 -1
  150. data/spec/unit/defaults_spec.rb +24 -1
  151. data/spec/unit/environments_spec.rb +8 -0
  152. data/spec/unit/file_system_spec.rb +10 -0
  153. data/spec/unit/http/client_spec.rb +105 -46
  154. data/spec/unit/http/external_client_spec.rb +201 -0
  155. data/spec/unit/http/resolver_spec.rb +20 -0
  156. data/spec/unit/http/service/ca_spec.rb +25 -2
  157. data/spec/unit/http/service/compiler_spec.rb +184 -6
  158. data/spec/unit/http/service/file_server_spec.rb +35 -3
  159. data/spec/unit/http/service/report_spec.rb +3 -1
  160. data/spec/unit/http/service_spec.rb +3 -3
  161. data/spec/unit/http/session_spec.rb +56 -7
  162. data/spec/unit/indirector/file_bucket_file/rest_spec.rb +82 -2
  163. data/spec/unit/network/http/pool_spec.rb +3 -3
  164. data/spec/unit/node/environment_spec.rb +16 -0
  165. data/spec/unit/provider/group/windows_adsi_spec.rb +43 -10
  166. data/spec/unit/provider/package/apt_spec.rb +30 -0
  167. data/spec/unit/provider/package/dnfmodule_spec.rb +33 -14
  168. data/spec/unit/provider/package/gem_spec.rb +40 -0
  169. data/spec/unit/provider/package/pacman_spec.rb +6 -21
  170. data/spec/unit/provider/package/pip_spec.rb +26 -3
  171. data/spec/unit/provider/package/pkgdmg_spec.rb +1 -1
  172. data/spec/unit/provider/package/pkgng_spec.rb +38 -0
  173. data/spec/unit/provider/package/puppet_gem_spec.rb +8 -0
  174. data/spec/unit/provider/package/rpm_spec.rb +0 -212
  175. data/spec/unit/provider/package/yum_spec.rb +235 -1
  176. data/spec/unit/provider/service/systemd_spec.rb +10 -1
  177. data/spec/unit/provider/user/windows_adsi_spec.rb +3 -3
  178. data/spec/unit/puppet_pal_2pec.rb +0 -29
  179. data/spec/unit/reports/http_spec.rb +70 -52
  180. data/spec/unit/ssl/host_spec.rb +4 -2
  181. data/spec/unit/ssl/oids_spec.rb +1 -0
  182. data/spec/unit/ssl/state_machine_spec.rb +38 -6
  183. data/spec/unit/transaction/report_spec.rb +4 -0
  184. data/spec/unit/util/at_fork_spec.rb +2 -2
  185. data/spec/unit/util/package/version/debian_spec.rb +83 -0
  186. data/spec/unit/util/package/version/pip_spec.rb +464 -0
  187. data/spec/unit/util/package/version/range_spec.rb +154 -0
  188. data/spec/unit/util/package/version/rpm_spec.rb +121 -0
  189. data/spec/unit/util/pidlock_spec.rb +83 -47
  190. data/spec/unit/util/rpm_compare_spec.rb +196 -0
  191. data/spec/unit/util/windows/adsi_spec.rb +4 -4
  192. data/spec/unit/util/windows/sid_spec.rb +2 -2
  193. data/tasks/generate_cert_fixtures.rake +15 -1
  194. metadata +51 -6
  195. data/spec/integration/faces/plugin_spec.rb +0 -63
@@ -2,6 +2,7 @@ require 'pathname'
2
2
  require 'puppet/util/rubygems'
3
3
  require 'puppet/util/warnings'
4
4
  require 'puppet/pops/adaptable'
5
+ require 'puppet/concurrent/synchronized'
5
6
 
6
7
  # An adapter that ties the module_directories cache to the environment where the modules are parsed. This
7
8
  # adapter ensures that the life-cycle of this cache doesn't exceed the life-cycle of the environment.
@@ -13,6 +14,8 @@ end
13
14
 
14
15
  # Autoload paths, either based on names or all at once.
15
16
  class Puppet::Util::Autoload
17
+ include Puppet::Concurrent::Synchronized
18
+
16
19
  @loaded = {}
17
20
 
18
21
  class << self
@@ -1,5 +1,6 @@
1
1
  require 'puppet/util/autoload'
2
2
  require 'puppet/util'
3
+ require 'puppet/concurrent/lock'
3
4
 
4
5
  # A module that can easily autoload things for us. Uses an instance
5
6
  # of Puppet::Util::Autoload
@@ -18,6 +19,7 @@ module Puppet::Util::InstanceLoader
18
19
  type = type.intern
19
20
  @instances[type] = {}
20
21
  @autoloaders[type] = Puppet::Util::Autoload.new(self, path)
22
+ @instance_loader_lock = Puppet::Concurrent::Lock.new
21
23
 
22
24
  # Now define our new simple methods
23
25
  unless respond_to?(type)
@@ -44,19 +46,21 @@ module Puppet::Util::InstanceLoader
44
46
 
45
47
  # Retrieve an already-loaded instance, or attempt to load our instance.
46
48
  def loaded_instance(type, name)
47
- name = name.intern
48
- instances = instance_hash(type)
49
- return nil unless instances
50
- unless instances.include? name
51
- if instance_loader(type).load(name, Puppet.lookup(:current_environment))
52
- unless instances.include? name
53
- Puppet.warning(_("Loaded %{type} file for %{name} but %{type} was not defined") % { type: type, name: name })
49
+ @instance_loader_lock.synchronize do
50
+ name = name.intern
51
+ instances = instance_hash(type)
52
+ return nil unless instances
53
+ unless instances.include? name
54
+ if instance_loader(type).load(name, Puppet.lookup(:current_environment))
55
+ unless instances.include? name
56
+ Puppet.warning(_("Loaded %{type} file for %{name} but %{type} was not defined") % { type: type, name: name })
57
+ return nil
58
+ end
59
+ else
54
60
  return nil
55
61
  end
56
- else
57
- return nil
58
62
  end
63
+ instances[name]
59
64
  end
60
- instances[name]
61
65
  end
62
66
  end
@@ -0,0 +1,175 @@
1
+ module Puppet::Util::Package::Version
2
+ class Debian < Numeric
3
+ include Comparable
4
+
5
+ # Version string matching regexes
6
+ REGEX_EPOCH = '(?:([0-9]+):)?'
7
+ # alphanumerics and the characters . + - ~ , starts with a digit, ~ only of debian_revision is present
8
+ REGEX_UPSTREAM_VERSION = '([\.\+~0-9a-zA-Z-]+?)'
9
+ #alphanumerics and the characters + . ~
10
+ REGEX_DEBIAN_REVISION = '(?:-([\.\+~0-9a-zA-Z]*))?'
11
+
12
+ REGEX_FULL = REGEX_EPOCH + REGEX_UPSTREAM_VERSION + REGEX_DEBIAN_REVISION.freeze
13
+ REGEX_FULL_RX = /\A#{REGEX_FULL}\Z/
14
+
15
+ class ValidationFailure < ArgumentError; end
16
+
17
+ def self.parse(ver)
18
+ raise ValidationFailure, "Unable to parse '#{ver}' as a string" unless ver.is_a?(String)
19
+
20
+ match, epoch, upstream_version, debian_revision = *ver.match(REGEX_FULL_RX)
21
+
22
+ raise ValidationFailure, "Unable to parse '#{ver}' as a debian version identifier" unless match
23
+
24
+ new(epoch.to_i, upstream_version, debian_revision).freeze
25
+ end
26
+
27
+ def to_s
28
+ s = @upstream_version
29
+ s = "#{@epoch}:#{s}" if @epoch != 0
30
+ s = "#{s}-#{@debian_revision}" if @debian_revision
31
+ s
32
+ end
33
+ alias inspect to_s
34
+
35
+ def eql?(other)
36
+ other.is_a?(self.class) &&
37
+ @epoch.eql?(other.epoch) &&
38
+ @upstream_version.eql?(other.upstream_version) &&
39
+ @debian_revision.eql?(other.debian_revision)
40
+ end
41
+ alias == eql?
42
+
43
+ def <=>(other)
44
+ return nil unless other.is_a?(self.class)
45
+ cmp = @epoch <=> other.epoch
46
+ if cmp == 0
47
+ cmp = compare_upstream_version(other)
48
+ if cmp == 0
49
+ cmp = compare_debian_revision(other)
50
+ end
51
+ end
52
+ cmp
53
+ end
54
+
55
+ attr_reader :epoch, :upstream_version, :debian_revision
56
+
57
+ private
58
+
59
+ def initialize(epoch, upstream_version, debian_revision)
60
+ @epoch = epoch
61
+ @upstream_version = upstream_version
62
+ @debian_revision = debian_revision
63
+ end
64
+
65
+ def compare_upstream_version(other)
66
+ mine = @upstream_version
67
+ yours = other.upstream_version
68
+ compare_debian_versions(mine, yours)
69
+ end
70
+
71
+ def compare_debian_revision(other)
72
+ mine = @debian_revision
73
+ yours = other.debian_revision
74
+ compare_debian_versions(mine, yours)
75
+ end
76
+
77
+ def compare_debian_versions(mine, yours)
78
+ # First the initial part of each string consisting entirely of non-digit characters is determined.
79
+ # These two parts (one of which may be empty) are compared lexically. If a difference is found it is
80
+ # returned. The lexical comparison is a comparison of ASCII values modified so that all the letters
81
+ # sort earlier than all the non-letters and so that a tilde sorts before anything, even the end of a
82
+ # part. For example, the following parts are in sorted order from earliest to latest: ~~, ~~a, ~, the
83
+ # empty part, a.
84
+ #
85
+ # Then the initial part of the remainder of each string which consists entirely of digit characters
86
+ # is determined. The numerical values of these two parts are compared, and any difference found is
87
+ # returned as the result of the comparison. For these purposes an empty string (which can only occur
88
+ # at the end of one or both version strings being compared) counts as zero.
89
+ #
90
+ # These two steps (comparing and removing initial non-digit strings and initial digit strings) are
91
+ # repeated until a difference is found or both strings are exhausted.
92
+
93
+ mine_index = 0
94
+ yours_index = 0
95
+ cmp = 0
96
+ mine ||= ''
97
+ yours ||= ''
98
+ while mine_index < mine.length && yours_index < yours.length && cmp == 0
99
+ #handle ~
100
+ _mymatch, mytilde = *match_tildes(mine.slice(mine_index..-1))
101
+ mytilde ||= ''
102
+
103
+ _yoursmatch, yourstilde = *match_tildes(yours.slice(yours_index..-1))
104
+ yourstilde ||= ''
105
+
106
+ cmp = -1 * (mytilde.length <=> yourstilde.length)
107
+ mine_index += mytilde.length
108
+ yours_index += yourstilde.length
109
+
110
+ if cmp == 0 # handle letters
111
+
112
+ _mymatch, myletters = *match_letters(mine.slice(mine_index..-1))
113
+ myletters ||= ''
114
+
115
+ _yoursmatch, yoursletters = *match_letters(yours.slice(yours_index..-1))
116
+ yoursletters ||= ''
117
+
118
+ cmp = myletters <=> yoursletters
119
+ mine_index += myletters.length
120
+ yours_index += yoursletters.length
121
+
122
+ if cmp == 0 # handle nonletters except tilde
123
+ _mymatch, mynon_letters = *match_non_letters(mine.slice(mine_index..-1))
124
+ mynon_letters ||= ''
125
+
126
+ _yoursmatch, yoursnon_letters = *match_non_letters(yours.slice(yours_index..-1))
127
+ yoursnon_letters ||= ''
128
+
129
+ cmp = mynon_letters <=> yoursnon_letters
130
+ mine_index += mynon_letters.length
131
+ yours_index += yoursnon_letters.length
132
+
133
+ if cmp == 0 # handle digits
134
+ _mymatch, mydigits = *match_digits(mine.slice(mine_index..-1))
135
+ mydigits ||= ''
136
+
137
+ _yoursmatch, yoursdigits = *match_digits(yours.slice(yours_index..-1))
138
+ yoursdigits ||= ''
139
+
140
+ cmp = mydigits.to_i <=> yoursdigits.to_i
141
+ mine_index += mydigits.length
142
+ yours_index += yoursdigits.length
143
+ end
144
+ end
145
+ end
146
+ end
147
+ if cmp == 0
148
+ if mine_index < mine.length && match_tildes(mine[mine_index])
149
+ cmp = -1
150
+ elsif yours_index < yours.length && match_tildes(yours[yours_index])
151
+ cmp = 1
152
+ else
153
+ cmp = mine.length <=> yours.length
154
+ end
155
+ end
156
+ cmp
157
+ end
158
+
159
+ def match_digits(a)
160
+ a.match(/^([0-9]+)/)
161
+ end
162
+
163
+ def match_non_letters(a)
164
+ a.match(/^([\.\+-]+)/)
165
+ end
166
+
167
+ def match_tildes(a)
168
+ a.match(/^(~+)/)
169
+ end
170
+
171
+ def match_letters(a)
172
+ a.match(/^([A-Za-z]+)/)
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,15 @@
1
+ module Puppet::Util::Package::Version
2
+ class Gem < ::Gem::Version
3
+ def self.parse(version)
4
+ raise ValidationFailure, version unless version.is_a? String
5
+ raise ValidationFailure, version unless version =~ ANCHORED_VERSION_PATTERN
6
+ new(version)
7
+ end
8
+
9
+ class ValidationFailure < ArgumentError
10
+ def initialize(version)
11
+ super("#{version} is not a valid ruby gem version.")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,167 @@
1
+ module Puppet::Util::Package::Version
2
+ class Pip
3
+ include Comparable
4
+
5
+ VERSION_PATTERN = "
6
+ v?
7
+ (?:
8
+ (?:(?<epoch>[0-9]+)!)? # epoch
9
+ (?<release>[0-9]+(?:\\.[0-9]+)*) # release segment
10
+ (?<pre> # pre-release
11
+ [-_\\.]?
12
+ (?<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
13
+ [-_\\.]?
14
+ (?<pre_n>[0-9]+)?
15
+ )?
16
+ (?<post> # post release
17
+ (?:-(?<post_n1>[0-9]+))
18
+ |
19
+ (?:
20
+ [-_\\.]?
21
+ (?<post_l>post|rev|r)
22
+ [-_\\.]?
23
+ (?<post_n2>[0-9]+)?
24
+ )
25
+ )?
26
+ (?<dev> # dev release
27
+ [-_\\.]?
28
+ (?<dev_l>dev)
29
+ [-_\\.]?
30
+ (?<dev_n>[0-9]+)?
31
+ )?
32
+ )
33
+ (?:\\+(?<local>[a-z0-9]+(?:[-_\\.][a-z0-9]+)*))? # local version
34
+ ".freeze
35
+
36
+ def self.parse(version)
37
+ raise ValidationFailure, version.to_s unless version.is_a? String
38
+
39
+ matched = version.match(Regexp.new(("^\\s*") + VERSION_PATTERN + ("\\s*$"), Regexp::EXTENDED | Regexp::MULTILINE | Regexp::IGNORECASE))
40
+ raise ValidationFailure, version unless matched
41
+
42
+ new(matched)
43
+ end
44
+
45
+ def self.compare(version_a, version_b)
46
+ version_a = parse(version_a) unless version_a.is_a?(self)
47
+ version_b = parse(version_b) unless version_b.is_a?(self)
48
+
49
+ version_a <=> version_b
50
+ end
51
+
52
+ def to_s
53
+ parts = []
54
+
55
+ parts.push("#{@epoch_data}!") if @epoch_data && @epoch_data != 0
56
+ parts.push(@release_data.join(".")) if @release_data
57
+ parts.push(@pre_data.join) if @pre_data
58
+ parts.push(".post#{@post_data[1]}") if @post_data
59
+ parts.push(".dev#{@dev_data[1]}") if @dev_data
60
+ parts.push("+#{@local_data.join(".")}") if @local_data
61
+
62
+ parts.join
63
+ end
64
+ alias inspect to_s
65
+
66
+ def eql?(other)
67
+ other.is_a?(self.class) && key.eql?(other.key)
68
+ end
69
+ alias == eql?
70
+
71
+ def <=>(other)
72
+ raise ValidationFailure, other.to_s unless other.is_a?(self.class)
73
+ compare(key, other.key)
74
+ end
75
+
76
+ attr_reader :key
77
+
78
+ private
79
+
80
+ def initialize(matched)
81
+ @epoch_data = matched[:epoch].to_i
82
+ @release_data = matched[:release].split('.').map(&:to_i) if matched[:release]
83
+ @pre_data = parse_letter_version(matched[:pre_l], matched[:pre_n]) if matched[:pre_l] || matched[:pre_n]
84
+ @post_data = parse_letter_version(matched[:post_l], matched[:post_n1] || matched[:post_n2]) if matched[:post_l] || matched[:post_n1] || matched[:post_n2]
85
+ @dev_data = parse_letter_version(matched[:dev_l], matched[:dev_n]) if matched[:dev_l] || matched[:dev_n]
86
+ @local_data = parse_local_version(matched[:local]) if matched[:local]
87
+
88
+ @key = compose_key(@epoch_data, @release_data, @pre_data, @post_data, @dev_data, @local_data)
89
+ end
90
+
91
+ def parse_letter_version(letter, number)
92
+ if letter
93
+ number = 0 if !number
94
+ letter.downcase!
95
+
96
+ if letter == "alpha"
97
+ letter = "a"
98
+ elsif letter == "beta"
99
+ letter = "b"
100
+ elsif ["c", "pre", "preview"].include?(letter)
101
+ letter = "rc"
102
+ elsif ["rev", "r"].include?(letter)
103
+ letter = "post"
104
+ end
105
+
106
+ return [letter, number.to_i]
107
+ end
108
+
109
+ ["post", number.to_i] if !letter && number
110
+ end
111
+
112
+ def parse_local_version(local_version)
113
+ local_version.split(/[\\._-]/).map{|part| part =~ /[0-9]+/ && part !~ /[a-zA-Z]+/ ? part.to_i : part.downcase} if local_version
114
+ end
115
+
116
+ def compose_key(epoch, release, pre, post, dev, local)
117
+ release_key = release.reverse
118
+ release_key.each_with_index do |element, index|
119
+ break unless element == 0
120
+ release_key.delete_at(index) unless release_key.at(index + 1) != 0
121
+ end
122
+ release_key.reverse!
123
+
124
+ if !pre && !post && dev
125
+ pre_key = -Float::INFINITY
126
+ else
127
+ pre_key = pre || Float::INFINITY
128
+ end
129
+
130
+ post_key = post || -Float::INFINITY
131
+
132
+ dev_key = dev || Float::INFINITY
133
+
134
+ if !local
135
+ local_key = [[-Float::INFINITY, ""]]
136
+ else
137
+ local_key = local.map{|i| (i.is_a? Integer) ? [i, ""] : [-Float::INFINITY, i]}
138
+ end
139
+
140
+ [epoch, release_key, pre_key, post_key, dev_key, local_key]
141
+ end
142
+
143
+ def compare(this, other)
144
+ if (this.is_a? Array) && (other.is_a? Array)
145
+ this << -Float::INFINITY if this.length < other.length
146
+ other << -Float::INFINITY if this.length > other.length
147
+
148
+ this.each_with_index do |element, index|
149
+ return compare(element, other.at(index)) if element != other.at(index)
150
+ end
151
+ elsif (this.is_a? Array) && !(other.is_a? Array)
152
+ raise Puppet::Error, 'Cannot compare #{this} (Array) with #{other} (#{other.class}). Only ±Float::INFINITY accepted.' unless other.abs == Float::INFINITY
153
+ return other == -Float::INFINITY ? 1 : -1
154
+ elsif !(this.is_a? Array) && (other.is_a? Array)
155
+ raise Puppet::Error, 'Cannot compare #{this} (#{this.class}) with #{other} (Array). Only ±Float::INFINITY accepted.' unless this.abs == Float::INFINITY
156
+ return this == -Float::INFINITY ? -1 : 1
157
+ end
158
+ this <=> other
159
+ end
160
+
161
+ class ValidationFailure < ArgumentError
162
+ def initialize(version)
163
+ super("#{version} is not a valid python package version. Please refer to https://www.python.org/dev/peps/pep-0440/.")
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,50 @@
1
+ require 'puppet/util/package/version/range/lt'
2
+ require 'puppet/util/package/version/range/lt_eq'
3
+ require 'puppet/util/package/version/range/gt'
4
+ require 'puppet/util/package/version/range/gt_eq'
5
+ require 'puppet/util/package/version/range/min_max'
6
+
7
+ module Puppet::Util::Package::Version
8
+ class Range
9
+ class ValidationFailure < ArgumentError; end
10
+ # Parses a version range string into a comparable {Range} instance.
11
+ #
12
+ # Currently parsed version range string may take any of the following
13
+ # forms:
14
+ #
15
+ # * Regular Version strings
16
+ # * ex. `"1.0.0"`, `"1.2.3-pre"`
17
+ # * Inequalities
18
+ # * ex. `">1.0.0"`, `"<3.2.0"`, `">=4.0.0"`
19
+ # * Range Intersections (min is always first)
20
+ # * ex. `">1.0.0 <=2.3.0"`
21
+ #
22
+ RANGE_SPLIT = /\s+/
23
+ FULL_REGEX = /\A((?:[<>=])+)(.+)\Z/
24
+
25
+ # @param range_string [String] the version range string to parse
26
+ # @param version_class [Version] a version class implementing comparison operators and parse method
27
+ # @return [Range] a new {Range} instance
28
+ # @api public
29
+ def self.parse(range_string, version_class)
30
+ raise ValidationFailure, "Unable to parse '#{range_string}' as a string" unless range_string.is_a?(String)
31
+ simples = range_string.split(RANGE_SPLIT).map do |simple|
32
+ match, operator, version = *simple.match(FULL_REGEX)
33
+ raise ValidationFailure, "Unable to parse '#{simple}' as a version range identifier" unless match
34
+ case operator
35
+ when '>'
36
+ Gt.new(version_class::parse(version))
37
+ when '>='
38
+ GtEq.new(version_class::parse(version))
39
+ when '<'
40
+ Lt.new(version_class::parse(version))
41
+ when '<='
42
+ LtEq.new(version_class::parse(version))
43
+ else
44
+ raise ValidationFailure, "Operator '#{operator}' is not implemented"
45
+ end
46
+ end
47
+ simples.size == 1 ? simples[0] : MinMax.new(simples[0], simples[1])
48
+ end
49
+ end
50
+ end