puppet 6.15.0 → 6.16.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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/CODEOWNERS +2 -7
  3. data/Gemfile.lock +17 -14
  4. data/lib/puppet.rb +32 -8
  5. data/lib/puppet/agent.rb +18 -4
  6. data/lib/puppet/application/agent.rb +1 -2
  7. data/lib/puppet/application/device.rb +1 -1
  8. data/lib/puppet/application/plugin.rb +1 -0
  9. data/lib/puppet/application/ssl.rb +1 -1
  10. data/lib/puppet/configurer.rb +2 -2
  11. data/lib/puppet/context/trusted_information.rb +14 -8
  12. data/lib/puppet/daemon.rb +13 -27
  13. data/lib/puppet/defaults.rb +19 -0
  14. data/lib/puppet/face/facts.rb +1 -1
  15. data/lib/puppet/face/help.rb +29 -3
  16. data/lib/puppet/face/module/search.rb +5 -0
  17. data/lib/puppet/face/plugin.rb +1 -1
  18. data/lib/puppet/file_serving/http_metadata.rb +1 -1
  19. data/lib/puppet/file_system/uniquefile.rb +4 -0
  20. data/lib/puppet/forge/repository.rb +7 -6
  21. data/lib/puppet/functions/filter.rb +1 -0
  22. data/lib/puppet/http/client.rb +22 -11
  23. data/lib/puppet/http/external_client.rb +0 -6
  24. data/lib/puppet/indirector/file_content/http.rb +5 -0
  25. data/lib/puppet/indirector/file_metadata/http.rb +4 -4
  26. data/lib/puppet/indirector/rest.rb +7 -1
  27. data/lib/puppet/network/http/compression.rb +7 -0
  28. data/lib/puppet/network/http/connection.rb +2 -0
  29. data/lib/puppet/network/http/connection_adapter.rb +182 -0
  30. data/lib/puppet/network/http/nocache_pool.rb +1 -0
  31. data/lib/puppet/network/http_pool.rb +2 -2
  32. data/lib/puppet/pal/catalog_compiler.rb +5 -0
  33. data/lib/puppet/pal/pal_impl.rb +4 -1
  34. data/lib/puppet/parser/compiler.rb +28 -25
  35. data/lib/puppet/parser/functions/filter.rb +1 -0
  36. data/lib/puppet/provider/package/aix.rb +17 -2
  37. data/lib/puppet/provider/package/apt.rb +4 -1
  38. data/lib/puppet/provider/package/dnfmodule.rb +24 -4
  39. data/lib/puppet/provider/package/pip.rb +60 -37
  40. data/lib/puppet/provider/package/portage.rb +2 -2
  41. data/lib/puppet/provider/package/yum.rb +7 -0
  42. data/lib/puppet/provider/package/zypper.rb +59 -1
  43. data/lib/puppet/provider/service/systemd.rb +21 -4
  44. data/lib/puppet/provider/user/useradd.rb +5 -1
  45. data/lib/puppet/reports/http.rb +5 -3
  46. data/lib/puppet/runtime.rb +25 -2
  47. data/lib/puppet/ssl/state_machine.rb +33 -8
  48. data/lib/puppet/ssl/verifier_adapter.rb +9 -1
  49. data/lib/puppet/test/test_helper.rb +1 -1
  50. data/lib/puppet/type/file/source.rb +1 -1
  51. data/lib/puppet/type/package.rb +16 -1
  52. data/lib/puppet/type/service.rb +6 -8
  53. data/lib/puppet/type/user.rb +1 -7
  54. data/lib/puppet/util/autoload.rb +1 -18
  55. data/lib/puppet/util/log/destinations.rb +1 -10
  56. data/lib/puppet/util/package/version/range.rb +4 -1
  57. data/lib/puppet/util/package/version/range/eq.rb +14 -0
  58. data/lib/puppet/version.rb +1 -1
  59. data/locales/puppet.pot +191 -111
  60. data/man/man5/puppet.conf.5 +21 -2
  61. data/man/man8/puppet-agent.8 +1 -1
  62. data/man/man8/puppet-apply.8 +1 -1
  63. data/man/man8/puppet-catalog.8 +1 -1
  64. data/man/man8/puppet-config.8 +1 -1
  65. data/man/man8/puppet-describe.8 +1 -1
  66. data/man/man8/puppet-device.8 +1 -1
  67. data/man/man8/puppet-doc.8 +1 -1
  68. data/man/man8/puppet-epp.8 +1 -1
  69. data/man/man8/puppet-facts.8 +1 -1
  70. data/man/man8/puppet-filebucket.8 +1 -1
  71. data/man/man8/puppet-generate.8 +1 -1
  72. data/man/man8/puppet-help.8 +6 -3
  73. data/man/man8/puppet-key.8 +1 -1
  74. data/man/man8/puppet-lookup.8 +1 -1
  75. data/man/man8/puppet-man.8 +1 -1
  76. data/man/man8/puppet-module.8 +4 -1
  77. data/man/man8/puppet-node.8 +1 -1
  78. data/man/man8/puppet-parser.8 +1 -1
  79. data/man/man8/puppet-plugin.8 +1 -1
  80. data/man/man8/puppet-report.8 +1 -1
  81. data/man/man8/puppet-resource.8 +1 -1
  82. data/man/man8/puppet-script.8 +1 -1
  83. data/man/man8/puppet-ssl.8 +1 -1
  84. data/man/man8/puppet-status.8 +1 -1
  85. data/man/man8/puppet.8 +2 -2
  86. data/spec/fixtures/unit/provider/package/dnfmodule/{dnf-module-list-enabled.txt → dnf-module-list.txt} +6 -0
  87. data/spec/fixtures/unit/provider/package/zypper/zypper-search-uninstalled.out +13 -0
  88. data/spec/integration/application/agent_spec.rb +66 -1
  89. data/spec/integration/application/plugin_spec.rb +23 -0
  90. data/spec/integration/http/client_spec.rb +6 -1
  91. data/spec/integration/network/http_pool_spec.rb +56 -0
  92. data/spec/integration/util/windows/adsi_spec.rb +5 -0
  93. data/spec/lib/puppet_spec/https.rb +6 -0
  94. data/spec/unit/agent_spec.rb +47 -1
  95. data/spec/unit/application/agent_spec.rb +4 -4
  96. data/spec/unit/context/trusted_information_spec.rb +17 -0
  97. data/spec/unit/daemon_spec.rb +5 -64
  98. data/spec/unit/face/module/search_spec.rb +17 -0
  99. data/spec/unit/file_system/uniquefile_spec.rb +11 -0
  100. data/spec/unit/http/client_spec.rb +10 -10
  101. data/spec/unit/http/external_client_spec.rb +9 -9
  102. data/spec/unit/indirector/catalog/compiler_spec.rb +1 -0
  103. data/spec/unit/indirector/file_metadata/http_spec.rb +167 -0
  104. data/spec/unit/indirector/file_metadata/rest_spec.rb +15 -14
  105. data/spec/unit/indirector/rest_spec.rb +13 -0
  106. data/spec/unit/network/http/connection_spec.rb +542 -190
  107. data/spec/unit/network/http/nocache_pool_spec.rb +22 -0
  108. data/spec/unit/network/http_pool_spec.rb +63 -57
  109. data/spec/unit/network/http_spec.rb +1 -1
  110. data/spec/unit/provider/package/aix_spec.rb +29 -0
  111. data/spec/unit/provider/package/dnfmodule_spec.rb +25 -5
  112. data/spec/unit/provider/package/pip_spec.rb +42 -16
  113. data/spec/unit/provider/package/portage_spec.rb +5 -0
  114. data/spec/unit/provider/package/yum_spec.rb +16 -8
  115. data/spec/unit/provider/package/zypper_spec.rb +84 -0
  116. data/spec/unit/provider/service/init_spec.rb +1 -0
  117. data/spec/unit/provider/service/openbsd_spec.rb +9 -0
  118. data/spec/unit/provider/service/openwrt_spec.rb +1 -0
  119. data/spec/unit/provider/service/redhat_spec.rb +9 -0
  120. data/spec/unit/provider/service/systemd_spec.rb +84 -13
  121. data/spec/unit/provider/user/useradd_spec.rb +8 -0
  122. data/spec/unit/puppet_pal_catalog_spec.rb +43 -0
  123. data/spec/unit/puppet_spec.rb +33 -0
  124. data/spec/unit/reports/http_spec.rb +1 -1
  125. data/spec/unit/ssl/state_machine_spec.rb +52 -8
  126. data/spec/unit/type/service_spec.rb +9 -8
  127. data/spec/unit/type/user_spec.rb +1 -1
  128. data/spec/unit/util/autoload_spec.rb +2 -1
  129. data/spec/unit/util/log/destinations_spec.rb +1 -29
  130. data/spec/unit/util/package/version/range_spec.rb +22 -1
  131. data/tasks/manpages.rake +5 -35
  132. metadata +10 -4
@@ -0,0 +1,182 @@
1
+ class Puppet::Network::HTTP::ConnectionAdapter < Puppet::Network::HTTP::Connection
2
+ def initialize(host, port, options = {})
3
+ super(host, port, options)
4
+
5
+ @client = Puppet.runtime[:http]
6
+ end
7
+
8
+ def get(path, headers = {}, options = {})
9
+ headers ||= {}
10
+ options[:ssl_context] ||= resolve_ssl_context
11
+ options[:redirect_limit] ||= @redirect_limit
12
+
13
+ with_error_handling do
14
+ resp = @client.get(to_url(path), headers: headers, options: options)
15
+ resp.nethttp
16
+ end
17
+ end
18
+
19
+ def post(path, data, headers = nil, options = {})
20
+ headers ||= {}
21
+ headers['Content-Type'] ||= "application/x-www-form-urlencoded"
22
+ data ||= ''
23
+ options[:ssl_context] ||= resolve_ssl_context
24
+ options[:redirect_limit] ||= @redirect_limit
25
+
26
+ with_error_handling do
27
+ resp = @client.post(to_url(path), data, headers: headers, options: options)
28
+ resp.nethttp
29
+ end
30
+ end
31
+
32
+ def head(path, headers = {}, options = {})
33
+ headers ||= {}
34
+ options[:ssl_context] ||= resolve_ssl_context
35
+ options[:redirect_limit] ||= @redirect_limit
36
+
37
+ with_error_handling do
38
+ resp = @client.head(to_url(path), headers: headers, options: options)
39
+ resp.nethttp
40
+ end
41
+ end
42
+
43
+ def delete(path, headers = {'Depth' => 'Infinity'}, options = {})
44
+ headers ||= {}
45
+ options[:ssl_context] ||= resolve_ssl_context
46
+ options[:redirect_limit] ||= @redirect_limit
47
+
48
+ with_error_handling do
49
+ resp = @client.delete(to_url(path), headers: headers, options: options)
50
+ resp.nethttp
51
+ end
52
+ end
53
+
54
+ def put(path, data, headers = nil, options = {})
55
+ headers ||= {}
56
+ headers['Content-Type'] ||= "application/x-www-form-urlencoded"
57
+ data ||= ''
58
+ options[:ssl_context] ||= resolve_ssl_context
59
+ options[:redirect_limit] ||= @redirect_limit
60
+
61
+ with_error_handling do
62
+ resp = @client.put(to_url(path), data, headers: headers, options: options)
63
+ resp.nethttp
64
+ end
65
+ end
66
+
67
+ def request_get(*args, &block)
68
+ path, headers = *args
69
+ headers ||= {}
70
+ options = {
71
+ ssl_context: resolve_ssl_context,
72
+ redirect_limit: @redirect_limit
73
+ }
74
+
75
+ resp = @client.get(to_url(path), headers: headers, options: options) do |response|
76
+ yield response.nethttp if block_given?
77
+ end
78
+ resp.nethttp
79
+ end
80
+
81
+ def request_head(*args, &block)
82
+ path, headers = *args
83
+ headers ||= {}
84
+ options = {
85
+ ssl_context: resolve_ssl_context,
86
+ redirect_limit: @redirect_limit
87
+ }
88
+
89
+ response = @client.head(to_url(path), headers: headers, options: options)
90
+ yield response.nethttp if block_given?
91
+ response.nethttp
92
+ end
93
+
94
+ def request_post(*args, &block)
95
+ path, data, headers = *args
96
+ headers ||= {}
97
+ headers['Content-Type'] ||= "application/x-www-form-urlencoded"
98
+ options = {
99
+ ssl_context: resolve_ssl_context,
100
+ redirect_limit: @redirect_limit
101
+ }
102
+
103
+ resp = @client.post(to_url(path), data, headers: headers, options: options) do |response|
104
+ yield response.nethttp if block_given?
105
+ end
106
+ resp.nethttp
107
+ end
108
+
109
+ private
110
+
111
+ # The old Connection class ignores the ssl_context on the Puppet stack,
112
+ # and always loads certs/keys based on what is currently in the filesystem.
113
+ # If the files are missing, it would attempt to bootstrap the certs/keys
114
+ # while in the process of making a network request, due to the call to
115
+ # Puppet.lookup(:ssl_host) in Puppet::SSL::Validator::DefaultValidator#setup_connection.
116
+ # This class doesn't preserve the boostrap behavior because that is handled
117
+ # outside of this class, and can only be triggered by running `puppet ssl` or
118
+ # `puppet agent`.
119
+ def resolve_ssl_context
120
+ # don't need an ssl context for http connections
121
+ return nil unless @site.use_ssl?
122
+
123
+ # if our verifier has an ssl_context, use that
124
+ ctx = @verifier.ssl_context
125
+ return ctx if ctx
126
+
127
+ # load available certs
128
+ cert = Puppet::X509::CertProvider.new
129
+ ssl = Puppet::SSL::SSLProvider.new
130
+ begin
131
+ password = cert.load_private_key_password
132
+ ssl.load_context(certname: Puppet[:certname], password: password)
133
+ rescue Puppet::SSL::SSLError => e
134
+ Puppet.log_exception(e)
135
+
136
+ # if we don't have cacerts, then create a root context that doesn't
137
+ # trust anything. The old code used to fallback to VERIFY_NONE,
138
+ # which we don't want to emulate.
139
+ ssl.create_root_context(cacerts: [])
140
+ end
141
+ end
142
+
143
+ def to_url(path)
144
+ if path =~ /^https?:\/\//
145
+ # The old Connection class accepts a URL as the request path, and sends
146
+ # it in "absolute-form" in the request line, e.g. GET https://puppet:8140/.
147
+ # See https://httpwg.org/specs/rfc7230.html#absolute-form. It just so happens
148
+ # to work because HTTP 1.1 servers are required to accept absolute-form even
149
+ # though clients are only supposed to send them to proxies, so the proxy knows
150
+ # what upstream server to CONNECT to. This method creates a URL using the
151
+ # scheme/host/port that the connection was created with, and appends the path
152
+ # portion of the absolute-form. The resulting request will use "origin-form"
153
+ # as it should have done all along.
154
+ url = URI(path)
155
+ URI("#{@site.addr}/#{normalize_path(url.path)}")
156
+ else
157
+ URI("#{@site.addr}/#{Puppet::Util.uri_encode(normalize_path(path))}")
158
+ end
159
+ end
160
+
161
+ def normalize_path(path)
162
+ if path[0] == '/'
163
+ path[1..-1]
164
+ else
165
+ path
166
+ end
167
+ end
168
+
169
+ def with_error_handling(&block)
170
+ yield
171
+ rescue Puppet::HTTP::TooManyRedirects => e
172
+ raise Puppet::Network::HTTP::RedirectionLimitExceededException.new(_("Too many HTTP redirections for %{host}:%{port}") % { host: @host, port: @port }, e)
173
+ rescue Puppet::HTTP::HTTPError => e
174
+ Puppet.log_exception(e, e.message)
175
+ case e.cause
176
+ when Net::OpenTimeout, Net::ReadTimeout, Net::HTTPError, EOFError
177
+ raise e.cause
178
+ else
179
+ raise e
180
+ end
181
+ end
182
+ end
@@ -3,6 +3,7 @@
3
3
  # @api private
4
4
  class Puppet::Network::HTTP::NoCachePool < Puppet::Network::HTTP::BasePool
5
5
  def initialize(factory = Puppet::Network::HTTP::Factory.new)
6
+ Puppet.deprecation_warning(_('Puppet::Network::HTTP::NoCachePool is deprecated.'))
6
7
  @factory = factory
7
8
  end
8
9
 
@@ -1,4 +1,5 @@
1
1
  require 'puppet/network/http/connection'
2
+ require 'puppet/network/http/connection_adapter'
2
3
  require 'puppet/util/platform'
3
4
 
4
5
  module Puppet::Network; end
@@ -12,14 +13,13 @@ module Puppet::Network; end
12
13
  #
13
14
  module Puppet::Network::HttpPool
14
15
 
15
- @http_client_class = Puppet::Network::HTTP::Connection
16
+ @http_client_class = Puppet::Network::HTTP::ConnectionAdapter
16
17
 
17
18
  def self.http_client_class
18
19
  @http_client_class
19
20
  end
20
21
  def self.http_client_class=(klass)
21
22
  @http_client_class = klass
22
- Puppet.runtime['http'] = Puppet::HTTP::ExternalClient.new(klass)
23
23
  end
24
24
 
25
25
  # Retrieve a connection for the given host and port.
@@ -97,6 +97,11 @@ module Pal
97
97
  internal_compiler.evaluate_additions
98
98
  end
99
99
 
100
+ # Attempts to evaluate AST for node defnintions https://puppet.com/docs/puppet/latest/lang_node_definitions.html
101
+ # if there are any.
102
+ def evaluate_ast_node
103
+ internal_compiler.evaluate_ast_node
104
+ end
100
105
  end
101
106
 
102
107
  end
@@ -424,7 +424,6 @@ module Pal
424
424
  begin
425
425
  node.sanitize()
426
426
  compiler = create_internal_compiler(internal_compiler_class, node)
427
- add_variables(compiler.topscope, pal_variables)
428
427
 
429
428
  case internal_compiler_class
430
429
  when :script
@@ -440,6 +439,10 @@ module Pal
440
439
  # TRANSLATORS: Do not translate, symbolic name
441
440
  Puppet.override(overrides, "PAL::with_#{internal_compiler_class}_compiler") do
442
441
  compiler.compile do | compiler_yield |
442
+ # In case the varaibles passed to the compiler are PCore types defined in modules, they
443
+ # need to be deserialized and added from within the this scope, so that loaders are
444
+ # available during deserizlization.
445
+ add_variables(compiler.topscope, Puppet::Pops::Serialization::FromDataConverter.convert(pal_variables))
443
446
  # wrap the internal compiler to prevent it from leaking in the PAL API
444
447
  if block_given?
445
448
  yield(pal_compiler)
@@ -348,6 +348,34 @@ class Puppet::Parser::Compiler
348
348
  end
349
349
  end
350
350
 
351
+
352
+ # If ast nodes are enabled, then see if we can find and evaluate one.
353
+ #
354
+ # @api private
355
+ def evaluate_ast_node
356
+ krt = environment.known_resource_types
357
+ return unless krt.nodes? #ast_nodes?
358
+
359
+ # Now see if we can find the node.
360
+ astnode = nil
361
+ @node.names.each do |name|
362
+ astnode = krt.node(name.to_s.downcase)
363
+ break if astnode
364
+ end
365
+
366
+ unless (astnode ||= krt.node("default"))
367
+ raise Puppet::ParseError, _("Could not find node statement with name 'default' or '%{names}'") % { names: node.names.join(", ") }
368
+ end
369
+
370
+ # Create a resource to model this node, and then add it to the list
371
+ # of resources.
372
+ resource = astnode.ensure_in_catalog(topscope)
373
+
374
+ resource.evaluate
375
+
376
+ @node_scope = topscope.class_scope(astnode)
377
+ end
378
+
351
379
  # Evaluates each specified class in turn. If there are any classes that
352
380
  # can't be found, an error is raised. This method really just creates resource objects
353
381
  # that point back to the classes, and then the resources are themselves
@@ -486,31 +514,6 @@ class Puppet::Parser::Compiler
486
514
  krt.capability_mappings.clear # No longer needed
487
515
  end
488
516
 
489
- # If ast nodes are enabled, then see if we can find and evaluate one.
490
- def evaluate_ast_node
491
- krt = environment.known_resource_types
492
- return unless krt.nodes? #ast_nodes?
493
-
494
- # Now see if we can find the node.
495
- astnode = nil
496
- @node.names.each do |name|
497
- astnode = krt.node(name.to_s.downcase)
498
- break if astnode
499
- end
500
-
501
- unless (astnode ||= krt.node("default"))
502
- raise Puppet::ParseError, _("Could not find node statement with name 'default' or '%{names}'") % { names: node.names.join(", ") }
503
- end
504
-
505
- # Create a resource to model this node, and then add it to the list
506
- # of resources.
507
- resource = astnode.ensure_in_catalog(topscope)
508
-
509
- resource.evaluate
510
-
511
- @node_scope = topscope.class_scope(astnode)
512
- end
513
-
514
517
  # Evaluate our collections and return true if anything returned an object.
515
518
  # The 'true' is used to continue a loop, so it's important.
516
519
  def evaluate_collections
@@ -43,6 +43,7 @@ as an array in the form `[key, value]` and returns a hash containing the results
43
43
  $data = { "orange" => 0, "blueberry" => 1, "raspberry" => 2 }
44
44
  $filtered_data = $data.filter |$items| { $items[0] =~ /berry$/ }
45
45
  # $filtered_data = {blueberry => 1, raspberry => 2}
46
+ ~~~
46
47
 
47
48
  When the first argument is an array and the lambda has two parameters, Puppet passes the
48
49
  array's indexes (enumerated from 0) in the first parameter and its values in the second
@@ -29,6 +29,15 @@ Puppet::Type.type(:package).provide :aix, :parent => Puppet::Provider::Package d
29
29
 
30
30
  attr_accessor :latest_info
31
31
 
32
+ STATE_CODE = {
33
+ 'A' => :applied,
34
+ 'B' => :broken,
35
+ 'C' => :committed,
36
+ 'E' => :efix_locked,
37
+ 'O' => :obsolete,
38
+ '?' => :inconsistent,
39
+ }.freeze
40
+
32
41
  def self.srclistcmd(source)
33
42
  [ command(:installp), "-L", "-d", source ]
34
43
  end
@@ -97,6 +106,11 @@ Puppet::Type.type(:package).provide :aix, :parent => Puppet::Provider::Package d
97
106
  if output =~ /^#{Regexp.escape(@resource[:name])}\s+.*\s+Already superseded by.*$/
98
107
  self.fail _("aix package provider is unable to downgrade packages")
99
108
  end
109
+
110
+ pkg_info = query
111
+ if pkg_info && [:broken, :inconsistent].include?(pkg_info[:status])
112
+ self.fail _("Package '%{name}' is in a %{status} state and requires manual intervention") % { name: @resource[:name], status: pkg_info[:status] }
113
+ end
100
114
  end
101
115
 
102
116
  def self.pkglist(hash = {})
@@ -108,8 +122,9 @@ Puppet::Type.type(:package).provide :aix, :parent => Puppet::Provider::Package d
108
122
  end
109
123
 
110
124
  begin
111
- list = execute(cmd).scan(/^[^#][^:]*:([^:]*):([^:]*)/).collect { |n,e|
112
- { :name => n, :ensure => e, :provider => self.name }
125
+ list = execute(cmd).scan(/^[^#][^:]*:([^:]*):([^:]*):[^:]*:[^:]*:([^:])/).collect { |n,e,s|
126
+ e = :absent if [:broken, :inconsistent].include?(STATE_CODE[s])
127
+ { :name => n, :ensure => e, :status => STATE_CODE[s], :provider => self.name }
113
128
  }
114
129
  rescue Puppet::ExecutionFailure => detail
115
130
  if hash[:pkgname]
@@ -77,7 +77,10 @@ Puppet::Type.type(:package).provide :apt, :parent => :dpkg, :source => :dpkg do
77
77
  if should.is_a?(String)
78
78
  begin
79
79
  should_range = VersionRange.parse(should, DebianVersion)
80
- should = best_version(should_range)
80
+
81
+ unless should_range.is_a?(VersionRange::Eq)
82
+ should = best_version(should_range)
83
+ end
81
84
  rescue VersionRange::ValidationFailure, DebianVersion::ValidationFailure
82
85
  Puppet.debug("Cannot parse #{should} as a debian version range, falling through")
83
86
  end
@@ -12,7 +12,7 @@ require 'puppet/provider/package'
12
12
 
13
13
  Puppet::Type.type(:package).provide :dnfmodule, :parent => :dnf do
14
14
 
15
- has_feature :installable, :uninstallable, :versionable, :supports_flavors
15
+ has_feature :installable, :uninstallable, :versionable, :supports_flavors, :disableable
16
16
  #has_feature :upgradeable
17
17
  # it's not (yet) feasible to make this upgradeable since module streams don't
18
18
  # always have matching version types (i.e. idm has streams DL1 and client,
@@ -34,10 +34,10 @@ Puppet::Type.type(:package).provide :dnfmodule, :parent => :dnf do
34
34
 
35
35
  def self.instances
36
36
  packages = []
37
- cmd = "#{command(:dnf)} module list --enabled -d 0 -e #{error_level}"
37
+ cmd = "#{command(:dnf)} module list -d 0 -e #{error_level}"
38
38
  execute(cmd).each_line do |line|
39
39
  # select only lines with actual packages since DNF clutters the output
40
- next unless line =~ /\[[ei]\][, ]/
40
+ next unless line =~ /\[[eix]\][, ]/
41
41
  line.gsub!(/\[d\]/, '') # we don't care about the default flag
42
42
 
43
43
  flavor = if line.include?('[i]')
@@ -48,7 +48,11 @@ Puppet::Type.type(:package).provide :dnfmodule, :parent => :dnf do
48
48
 
49
49
  packages << new(
50
50
  name: line.split[0],
51
- ensure: line.split[1],
51
+ ensure: if line.include?('[x]')
52
+ :disabled
53
+ else
54
+ line.split[1]
55
+ end,
52
56
  flavor: flavor,
53
57
  provider: name
54
58
  )
@@ -98,6 +102,18 @@ Puppet::Type.type(:package).provide :dnfmodule, :parent => :dnf do
98
102
  end
99
103
  end
100
104
 
105
+ # should only get here when @resource[ensure] is :disabled
106
+ def insync?(is)
107
+ if resource[:ensure] == :disabled
108
+ # in sync only if package is already disabled
109
+ pkg = self.class.instances.find do |package|
110
+ @resource[:name] == package.name && package.properties[:ensure] == :disabled
111
+ end
112
+ return true if pkg
113
+ end
114
+ return false
115
+ end
116
+
101
117
  def enable(args = @resource[:name])
102
118
  execute([command(:dnf), 'module', 'enable', '-d', '0', '-e', self.class.error_level, '-y', args])
103
119
  end
@@ -107,6 +123,10 @@ Puppet::Type.type(:package).provide :dnfmodule, :parent => :dnf do
107
123
  reset # reset module to the default stream
108
124
  end
109
125
 
126
+ def disable(args = @resource[:name])
127
+ execute([command(:dnf), 'module', 'disable', '-d', '0', '-e', self.class.error_level, '-y', args])
128
+ end
129
+
110
130
  def reset
111
131
  execute([command(:dnf), 'module', 'reset', '-d', '0', '-e', self.class.error_level, '-y', @resource[:name]])
112
132
  end
@@ -79,7 +79,7 @@ Puppet::Type.type(:package).provide :pip, :parent => ::Puppet::Provider::Package
79
79
  command_options << '--all'
80
80
  end
81
81
 
82
- execpipe [command, command_options] do |process|
82
+ execpipe [quote(command), command_options] do |process|
83
83
  process.collect do |line|
84
84
  pkg = parse(line)
85
85
  next unless pkg
@@ -158,7 +158,7 @@ Puppet::Type.type(:package).provide :pip, :parent => ::Puppet::Provider::Package
158
158
  command = resource_or_provider_command
159
159
  self.class.validate_command(command)
160
160
 
161
- command_and_options = [command, 'install', "#{@resource[:name]}==versionplease"]
161
+ command_and_options = [self.class.quote(command), 'install', "#{@resource[:name]}==versionplease"]
162
162
  command_and_options << install_options if @resource[:install_options]
163
163
  execpipe command_and_options do |process|
164
164
  process.collect do |line|
@@ -179,7 +179,7 @@ Puppet::Type.type(:package).provide :pip, :parent => ::Puppet::Provider::Package
179
179
  self.class.validate_command(command)
180
180
 
181
181
  Dir.mktmpdir("puppet_pip") do |dir|
182
- command_and_options = [command, 'install', "#{@resource[:name]}", '-d', "#{dir}", '-v']
182
+ command_and_options = [self.class.quote(command), 'install', "#{@resource[:name]}", '-d', "#{dir}", '-v']
183
183
  command_and_options << install_options if @resource[:install_options]
184
184
  execpipe command_and_options do |process|
185
185
  process.collect do |line|
@@ -211,51 +211,73 @@ Puppet::Type.type(:package).provide :pip, :parent => ::Puppet::Provider::Package
211
211
  should_range
212
212
  end
213
213
 
214
- # Install a package. The ensure parameter may specify installed,
215
- # latest, a version number, or, in conjunction with the source
216
- # parameter, an SCM revision. In that case, the source parameter
217
- # gives the fully-qualified URL to the repository.
218
- def install
219
- command = resource_or_provider_command
220
- self.class.validate_command(command)
221
-
214
+ def get_install_command_options()
222
215
  should = @resource[:ensure]
223
216
  command_options = %w{install -q}
224
- command_options += install_options if @resource[:install_options]
217
+ command_options += install_options if @resource[:install_options]
218
+
225
219
  if @resource[:source]
226
220
  if String === should
227
221
  command_options << "#{@resource[:source]}@#{should}#egg=#{@resource[:name]}"
228
222
  else
229
223
  command_options << "#{@resource[:source]}#egg=#{@resource[:name]}"
230
224
  end
231
- else
232
- case should
233
- when :latest
234
- command_options << "--upgrade" << @resource[:name]
235
- when String
236
- begin
237
- should_range = PIP_VERSION_RANGE.parse(should, PIP_VERSION)
238
- should = best_version(should_range)
239
-
240
- unless should == should_range
241
- command_options << "#{@resource[:name]}==#{should}"
242
- else
243
- # when no suitable version for the given range was found, let pip handle
244
- if should.is_a?(PIP_VERSION_RANGE::MinMax)
245
- command_options << "#{@resource[:name]} #{should.split.join(',')}"
246
- else
247
- command_options << "#{@resource[:name]} #{should}"
248
- end
249
- end
250
- rescue PIP_VERSION_RANGE::ValidationFailure, PIP_VERSION::ValidationFailure
251
- Puppet.debug("Cannot parse #{should} as a pip version range, falling through.")
252
- command_options << "#{@resource[:name]}==#{should}"
253
- end
225
+
226
+ return command_options
227
+ end
228
+
229
+ if should == :latest
230
+ command_options << "--upgrade" << @resource[:name]
231
+
232
+ return command_options
233
+ end
234
+
235
+ unless String === should
236
+ command_options << @resource[:name]
237
+
238
+ return command_options
239
+ end
240
+
241
+ begin
242
+ should_range = PIP_VERSION_RANGE.parse(should, PIP_VERSION)
243
+ rescue PIP_VERSION_RANGE::ValidationFailure, PIP_VERSION::ValidationFailure
244
+ Puppet.debug("Cannot parse #{should} as a pip version range, falling through.")
245
+ command_options << "#{@resource[:name]}==#{should}"
246
+
247
+ return command_options
248
+ end
249
+
250
+ if should_range.is_a?(PIP_VERSION_RANGE::Eq)
251
+ command_options << "#{@resource[:name]}==#{should}"
252
+
253
+ return command_options
254
+ end
255
+
256
+ should = best_version(should_range)
257
+
258
+ if should == should_range
259
+ # when no suitable version for the given range was found, let pip handle
260
+ if should.is_a?(PIP_VERSION_RANGE::MinMax)
261
+ command_options << "#{@resource[:name]} #{should.split.join(',')}"
254
262
  else
255
- command_options << @resource[:name]
263
+ command_options << "#{@resource[:name]} #{should}"
256
264
  end
265
+ else
266
+ command_options << "#{@resource[:name]}==#{should}"
257
267
  end
258
268
 
269
+ command_options
270
+ end
271
+
272
+ # Install a package. The ensure parameter may specify installed,
273
+ # latest, a version number, or, in conjunction with the source
274
+ # parameter, an SCM revision. In that case, the source parameter
275
+ # gives the fully-qualified URL to the repository.
276
+ def install
277
+ command = resource_or_provider_command
278
+ self.class.validate_command(command)
279
+
280
+ command_options = get_install_command_options
259
281
  execute([command, command_options])
260
282
  end
261
283
 
@@ -298,6 +320,8 @@ Puppet::Type.type(:package).provide :pip, :parent => ::Puppet::Provider::Package
298
320
  should_range.include?(is_version)
299
321
  end
300
322
 
323
+ # Quoting is required if the path to the pip command contains spaces.
324
+ # Required for execpipe() but not execute(), as execute() already does this.
301
325
  def self.quote(path)
302
326
  if path.include?(" ")
303
327
  "\"#{path}\""
@@ -305,5 +329,4 @@ Puppet::Type.type(:package).provide :pip, :parent => ::Puppet::Provider::Package
305
329
  path
306
330
  end
307
331
  end
308
- private_class_method :quote
309
332
  end