puppet 6.10.1 → 6.11.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 (242) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -4
  3. data/Gemfile.lock +20 -12
  4. data/ext/project_data.yaml +3 -2
  5. data/ext/regexp_nodes/regexp_nodes.rb +4 -4
  6. data/ext/windows/service/daemon.rb +33 -8
  7. data/install.rb +6 -6
  8. data/lib/puppet.rb +8 -0
  9. data/lib/puppet/application.rb +1 -1
  10. data/lib/puppet/application/agent.rb +3 -0
  11. data/lib/puppet/application/apply.rb +2 -2
  12. data/lib/puppet/application/describe.rb +3 -9
  13. data/lib/puppet/application/device.rb +3 -0
  14. data/lib/puppet/application/doc.rb +1 -1
  15. data/lib/puppet/application/lookup.rb +1 -1
  16. data/lib/puppet/application/script.rb +2 -2
  17. data/lib/puppet/application/ssl.rb +25 -21
  18. data/lib/puppet/configurer.rb +42 -0
  19. data/lib/puppet/configurer/downloader.rb +2 -6
  20. data/lib/puppet/context/trusted_information.rb +42 -4
  21. data/lib/puppet/defaults.rb +19 -4
  22. data/lib/puppet/face/module/list.rb +5 -5
  23. data/lib/puppet/face/module/search.rb +1 -1
  24. data/lib/puppet/face/module/uninstall.rb +1 -1
  25. data/lib/puppet/face/module/upgrade.rb +1 -1
  26. data/lib/puppet/file_serving/http_metadata.rb +1 -1
  27. data/lib/puppet/file_system.rb +0 -8
  28. data/lib/puppet/file_system/memory_file.rb +1 -1
  29. data/lib/puppet/file_system/posix.rb +3 -2
  30. data/lib/puppet/forge.rb +3 -3
  31. data/lib/puppet/functions.rb +1 -2
  32. data/lib/puppet/gettext/module_translations.rb +1 -1
  33. data/lib/puppet/graph/rb_tree_map.rb +2 -2
  34. data/lib/puppet/graph/simple_graph.rb +4 -3
  35. data/lib/puppet/http.rb +29 -0
  36. data/lib/puppet/http/client.rb +156 -0
  37. data/lib/puppet/http/errors.rb +30 -0
  38. data/lib/puppet/http/redirector.rb +48 -0
  39. data/lib/puppet/http/resolver.rb +5 -0
  40. data/lib/puppet/http/resolver/settings.rb +5 -0
  41. data/lib/puppet/http/resolver/srv.rb +13 -0
  42. data/lib/puppet/http/response.rb +34 -0
  43. data/lib/puppet/http/retry_after_handler.rb +47 -0
  44. data/lib/puppet/http/service.rb +18 -0
  45. data/lib/puppet/http/service/ca.rb +49 -0
  46. data/lib/puppet/http/session.rb +55 -0
  47. data/lib/puppet/indirector/file_bucket_file/file.rb +1 -1
  48. data/lib/puppet/indirector/hiera.rb +2 -0
  49. data/lib/puppet/indirector/request.rb +1 -1
  50. data/lib/puppet/indirector/resource/ral.rb +1 -3
  51. data/lib/puppet/indirector/resource/validator.rb +1 -1
  52. data/lib/puppet/interface.rb +2 -1
  53. data/lib/puppet/interface/documentation.rb +1 -1
  54. data/lib/puppet/loaders.rb +0 -1
  55. data/lib/puppet/metatype/manager.rb +1 -1
  56. data/lib/puppet/module.rb +1 -1
  57. data/lib/puppet/module/task.rb +20 -4
  58. data/lib/puppet/module_tool/applications/installer.rb +1 -1
  59. data/lib/puppet/module_tool/applications/uninstaller.rb +3 -3
  60. data/lib/puppet/module_tool/metadata.rb +1 -1
  61. data/lib/puppet/module_tool/shared_behaviors.rb +4 -4
  62. data/lib/puppet/module_tool/tar/mini.rb +1 -1
  63. data/lib/puppet/network/http.rb +2 -6
  64. data/lib/puppet/network/http/api/indirected_routes.rb +12 -11
  65. data/lib/puppet/network/http/connection.rb +10 -12
  66. data/lib/puppet/network/http/pool.rb +2 -0
  67. data/lib/puppet/network/http/site.rb +5 -1
  68. data/lib/puppet/network/resolver.rb +4 -4
  69. data/lib/puppet/node/environment.rb +4 -2
  70. data/lib/puppet/pal/pal_impl.rb +2 -2
  71. data/lib/puppet/parser/ast.rb +1 -1
  72. data/lib/puppet/parser/ast/resourceparam.rb +1 -1
  73. data/lib/puppet/parser/functions.rb +1 -1
  74. data/lib/puppet/parser/scope.rb +8 -7
  75. data/lib/puppet/pops/evaluator/collectors/catalog_collector.rb +1 -1
  76. data/lib/puppet/pops/evaluator/collectors/exported_collector.rb +1 -1
  77. data/lib/puppet/pops/evaluator/external_syntax_support.rb +3 -2
  78. data/lib/puppet/pops/evaluator/runtime3_support.rb +4 -7
  79. data/lib/puppet/pops/loader/module_loaders.rb +1 -1
  80. data/lib/puppet/pops/loader/task_instantiator.rb +4 -0
  81. data/lib/puppet/pops/loaders.rb +1 -1
  82. data/lib/puppet/pops/lookup/hiera_config.rb +1 -0
  83. data/lib/puppet/pops/lookup/sub_lookup.rb +1 -1
  84. data/lib/puppet/pops/merge_strategy.rb +22 -18
  85. data/lib/puppet/pops/parser/heredoc_support.rb +1 -1
  86. data/lib/puppet/pops/parser/interpolation_support.rb +4 -4
  87. data/lib/puppet/pops/parser/locator.rb +1 -1
  88. data/lib/puppet/pops/parser/pn_parser.rb +17 -16
  89. data/lib/puppet/pops/puppet_stack.rb +52 -48
  90. data/lib/puppet/pops/types/p_sensitive_type.rb +1 -1
  91. data/lib/puppet/pops/types/p_uri_type.rb +1 -1
  92. data/lib/puppet/pops/types/string_converter.rb +10 -10
  93. data/lib/puppet/pops/types/types.rb +3 -3
  94. data/lib/puppet/property.rb +1 -1
  95. data/lib/puppet/property/ensure.rb +1 -1
  96. data/lib/puppet/provider/exec.rb +6 -2
  97. data/lib/puppet/provider/nameservice/directoryservice.rb +1 -1
  98. data/lib/puppet/provider/nameservice/pw.rb +2 -2
  99. data/lib/puppet/provider/package/apt.rb +5 -1
  100. data/lib/puppet/provider/package/dnfmodule.rb +87 -0
  101. data/lib/puppet/provider/package/dpkg.rb +31 -17
  102. data/lib/puppet/provider/package/openbsd.rb +1 -1
  103. data/lib/puppet/provider/package/pip.rb +34 -9
  104. data/lib/puppet/provider/package/portage.rb +1 -1
  105. data/lib/puppet/provider/package/rpm.rb +5 -5
  106. data/lib/puppet/provider/package/windows/package.rb +1 -1
  107. data/lib/puppet/provider/package/yum.rb +1 -1
  108. data/lib/puppet/provider/parsedfile.rb +1 -1
  109. data/lib/puppet/provider/service/daemontools.rb +9 -9
  110. data/lib/puppet/provider/service/openbsd.rb +1 -1
  111. data/lib/puppet/provider/service/rcng.rb +2 -2
  112. data/lib/puppet/provider/service/runit.rb +2 -8
  113. data/lib/puppet/provider/service/systemd.rb +10 -10
  114. data/lib/puppet/provider/user/directoryservice.rb +1 -1
  115. data/lib/puppet/provider/user/user_role_add.rb +1 -1
  116. data/lib/puppet/provider/user/useradd.rb +22 -13
  117. data/lib/puppet/provider/user/windows_adsi.rb +4 -5
  118. data/lib/puppet/reference/indirection.rb +2 -2
  119. data/lib/puppet/reference/metaparameter.rb +1 -3
  120. data/lib/puppet/reference/providers.rb +1 -1
  121. data/lib/puppet/reference/type.rb +3 -9
  122. data/lib/puppet/reports.rb +1 -1
  123. data/lib/puppet/resource.rb +1 -1
  124. data/lib/puppet/resource/catalog.rb +1 -1
  125. data/lib/puppet/rest/errors.rb +1 -0
  126. data/lib/puppet/rest/response.rb +1 -0
  127. data/lib/puppet/rest/route.rb +1 -0
  128. data/lib/puppet/rest/routes.rb +3 -0
  129. data/lib/puppet/runtime.rb +25 -0
  130. data/lib/puppet/settings.rb +3 -3
  131. data/lib/puppet/settings/environment_conf.rb +1 -0
  132. data/lib/puppet/ssl/host.rb +1 -1
  133. data/lib/puppet/ssl/oids.rb +1 -1
  134. data/lib/puppet/ssl/state_machine.rb +23 -15
  135. data/lib/puppet/test/test_helper.rb +1 -1
  136. data/lib/puppet/transaction/report.rb +1 -1
  137. data/lib/puppet/trusted_external.rb +13 -0
  138. data/lib/puppet/type.rb +1 -3
  139. data/lib/puppet/type/exec.rb +7 -3
  140. data/lib/puppet/type/file.rb +1 -2
  141. data/lib/puppet/type/file/source.rb +2 -2
  142. data/lib/puppet/type/package.rb +10 -3
  143. data/lib/puppet/type/schedule.rb +1 -1
  144. data/lib/puppet/type/service.rb +1 -1
  145. data/lib/puppet/util.rb +2 -2
  146. data/lib/puppet/util/command_line/trollop.rb +1 -1
  147. data/lib/puppet/util/http_proxy.rb +2 -10
  148. data/lib/puppet/util/log.rb +2 -2
  149. data/lib/puppet/util/log/destinations.rb +2 -2
  150. data/lib/puppet/util/logging.rb +2 -2
  151. data/lib/puppet/util/metric.rb +2 -2
  152. data/lib/puppet/util/platform.rb +15 -4
  153. data/lib/puppet/util/provider_features.rb +2 -4
  154. data/lib/puppet/util/rdoc.rb +1 -1
  155. data/lib/puppet/util/reference.rb +1 -1
  156. data/lib/puppet/util/resource_template.rb +1 -1
  157. data/lib/puppet/util/selinux.rb +3 -1
  158. data/lib/puppet/util/windows/registry.rb +7 -5
  159. data/lib/puppet/vendor.rb +1 -1
  160. data/lib/puppet/vendor/require_vendored.rb +0 -1
  161. data/lib/puppet/version.rb +1 -1
  162. data/lib/puppet/x509/cert_provider.rb +4 -1
  163. data/locales/puppet.pot +279 -203
  164. data/man/man5/puppet.conf.5 +30 -8
  165. data/man/man8/puppet-agent.8 +4 -1
  166. data/man/man8/puppet-apply.8 +1 -1
  167. data/man/man8/puppet-catalog.8 +1 -1
  168. data/man/man8/puppet-config.8 +1 -1
  169. data/man/man8/puppet-describe.8 +1 -1
  170. data/man/man8/puppet-device.8 +1 -1
  171. data/man/man8/puppet-doc.8 +1 -1
  172. data/man/man8/puppet-epp.8 +1 -1
  173. data/man/man8/puppet-facts.8 +1 -1
  174. data/man/man8/puppet-filebucket.8 +1 -1
  175. data/man/man8/puppet-generate.8 +1 -1
  176. data/man/man8/puppet-help.8 +1 -1
  177. data/man/man8/puppet-key.8 +1 -1
  178. data/man/man8/puppet-lookup.8 +1 -1
  179. data/man/man8/puppet-man.8 +1 -1
  180. data/man/man8/puppet-module.8 +1 -1
  181. data/man/man8/puppet-node.8 +1 -1
  182. data/man/man8/puppet-parser.8 +1 -1
  183. data/man/man8/puppet-plugin.8 +1 -1
  184. data/man/man8/puppet-report.8 +1 -1
  185. data/man/man8/puppet-resource.8 +1 -1
  186. data/man/man8/puppet-script.8 +1 -1
  187. data/man/man8/puppet-ssl.8 +1 -1
  188. data/man/man8/puppet-status.8 +1 -1
  189. data/man/man8/puppet.8 +2 -2
  190. data/spec/fixtures/unit/provider/package/dnfmodule/dnf-module-list-installed.txt +11 -0
  191. data/spec/integration/configurer_spec.rb +52 -0
  192. data/spec/lib/puppet/certificate_factory.rb +2 -2
  193. data/spec/spec_helper.rb +24 -0
  194. data/spec/unit/application/device_spec.rb +6 -0
  195. data/spec/unit/application/ssl_spec.rb +4 -7
  196. data/spec/unit/configurer_spec.rb +1 -0
  197. data/spec/unit/context/trusted_information_spec.rb +41 -2
  198. data/spec/unit/http/client_spec.rb +440 -0
  199. data/spec/unit/http/resolver_spec.rb +45 -0
  200. data/spec/unit/http/service/ca_spec.rb +106 -0
  201. data/spec/unit/http/service_spec.rb +32 -0
  202. data/spec/unit/http/session_spec.rb +102 -0
  203. data/spec/unit/indirector/resource/ral_spec.rb +4 -4
  204. data/spec/unit/network/http/connection_spec.rb +119 -145
  205. data/spec/unit/network/http/site_spec.rb +7 -0
  206. data/spec/unit/parser/scope_spec.rb +10 -0
  207. data/spec/unit/pops/loaders/loaders_spec.rb +13 -2
  208. data/spec/unit/pops/loaders/module_loaders_spec.rb +37 -0
  209. data/spec/unit/provider/exec_spec.rb +209 -0
  210. data/spec/unit/provider/package/dnfmodule_spec.rb +186 -0
  211. data/spec/unit/provider/package/dpkg_spec.rb +238 -78
  212. data/spec/unit/provider/package/pip_spec.rb +51 -6
  213. data/spec/unit/provider/service/daemontools_spec.rb +24 -0
  214. data/spec/unit/provider/service/runit_spec.rb +24 -0
  215. data/spec/unit/provider/service/systemd_spec.rb +25 -25
  216. data/spec/unit/provider/user/useradd_spec.rb +46 -0
  217. data/spec/unit/ssl/host_spec.rb +0 -5
  218. data/spec/unit/ssl/state_machine_spec.rb +16 -10
  219. data/spec/unit/type/exec_spec.rb +6 -12
  220. data/spec/unit/type/file_spec.rb +9 -4
  221. data/spec/unit/type/package_spec.rb +5 -0
  222. data/spec/unit/util/execution_spec.rb +16 -0
  223. data/spec/unit/util/http_proxy_spec.rb +79 -27
  224. data/spec/unit/util/log/destinations_spec.rb +7 -3
  225. metadata +45 -22
  226. data/lib/puppet/pops/loader/null_loader.rb +0 -60
  227. data/lib/puppet/vendor/deep_merge/CHANGELOG +0 -45
  228. data/lib/puppet/vendor/deep_merge/Gemfile +0 -3
  229. data/lib/puppet/vendor/deep_merge/LICENSE +0 -21
  230. data/lib/puppet/vendor/deep_merge/PUPPET_README.md +0 -6
  231. data/lib/puppet/vendor/deep_merge/README.md +0 -113
  232. data/lib/puppet/vendor/deep_merge/Rakefile +0 -19
  233. data/lib/puppet/vendor/deep_merge/deep_merge.gemspec +0 -35
  234. data/lib/puppet/vendor/deep_merge/lib/deep_merge.rb +0 -2
  235. data/lib/puppet/vendor/deep_merge/lib/deep_merge/core.rb +0 -210
  236. data/lib/puppet/vendor/deep_merge/lib/deep_merge/deep_merge_hash.rb +0 -28
  237. data/lib/puppet/vendor/deep_merge/lib/deep_merge/rails_compat.rb +0 -27
  238. data/lib/puppet/vendor/deep_merge/test/test_deep_merge.rb +0 -608
  239. data/lib/puppet/vendor/load_deep_merge.rb +0 -1
  240. data/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/_request_get/should_yield_to_the_block.yml +0 -24
  241. data/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/_request_head/should_yield_to_the_block.yml +0 -24
  242. data/spec/fixtures/vcr/cassettes/Puppet_Network_HTTP_Connection/when_handling_requests/_request_post/should_yield_to_the_block.yml +0 -24
@@ -86,4 +86,11 @@ describe Puppet::Network::HTTP::Site do
86
86
  expect(new_site.host).to eq('host2')
87
87
  expect(new_site.port).to eq(443)
88
88
  end
89
+
90
+ it 'creates a site from a URI' do
91
+ site = create_site('https', 'rubygems.org', 443)
92
+ uri = URI.parse('https://rubygems.org/gems/puppet/')
93
+
94
+ expect(described_class.from_uri(uri)).to eq(site)
95
+ end
89
96
  end
@@ -680,5 +680,15 @@ describe Puppet::Parser::Scope do
680
680
  @scope.setvar("orange", :undef)
681
681
  expect(@scope.to_hash).to eq({'pear' => :green, 'apple' => :red })
682
682
  end
683
+
684
+ it "should not delete values that are :undef in inner scope when include_undef is true" do
685
+ @scope.new_ephemeral true
686
+ @scope.setvar("orange", :tangerine)
687
+ @scope.setvar("pear", :green)
688
+ @scope.new_ephemeral true
689
+ @scope.setvar("apple", :red)
690
+ @scope.setvar("orange", :undef)
691
+ expect(@scope.to_hash(true, true)).to eq({'pear' => :green, 'apple' => :red, 'orange' => :undef })
692
+ end
683
693
  end
684
694
  end
@@ -52,6 +52,10 @@ describe 'loaders' do
52
52
 
53
53
  # Loaders caches the puppet_system_loader, must reset between tests
54
54
 
55
+ before :each do
56
+ allow(File).to receive(:read).and_call_original
57
+ end
58
+
55
59
  context 'when loading pp resource types using auto loading' do
56
60
  let(:pp_resources) { config_dir('pp_resources') }
57
61
  let(:environments) { Puppet::Environments::Directories.new(my_fixture_dir, []) }
@@ -100,6 +104,11 @@ describe 'loaders' do
100
104
  expect(loaders.puppet_cache_loader()).to be_a(Puppet::Pops::Loader::ModuleLoaders::LibRootedFileBased)
101
105
  end
102
106
 
107
+ it 'creates a cached_puppet loader that can load version 4 functions, version 3 functions, and data types, in that order' do
108
+ loaders = Puppet::Pops::Loaders.new(empty_test_env, true)
109
+ expect(loaders.puppet_cache_loader.loadables).to eq([:func_4x, :func_3x, :datatype])
110
+ end
111
+
103
112
  it 'does not create a cached_puppet loader when for_agent is the default false value' do
104
113
  loaders = Puppet::Pops::Loaders.new(empty_test_env)
105
114
  expect(loaders.puppet_cache_loader()).to be(nil)
@@ -450,7 +459,9 @@ describe 'loaders' do
450
459
  end
451
460
 
452
461
  it "a function with syntax error has helpful error message" do
453
- expect { loader.load_typed(typed_name(:function, 'func_with_syntax_error')) }.to raise_error(/syntax error, unexpected keyword_end/)
462
+ expect {
463
+ loader.load_typed(typed_name(:function, 'func_with_syntax_error'))
464
+ }.to raise_error(/syntax error, unexpected (keyword_)?end/)
454
465
  end
455
466
  end
456
467
 
@@ -487,7 +498,7 @@ describe 'loaders' do
487
498
  end
488
499
 
489
500
  it "to self inside function body is reported as an error" do
490
- expect {
501
+ expect {
491
502
  f = loader.load_typed(typed_name(:function, 'bad_func_load4')).value
492
503
  f.call(scope)
493
504
  }.to raise_error(/Illegal method definition.*'bad_func_load4_illegal_method'/)
@@ -163,6 +163,43 @@ describe 'FileBased module loader' do
163
163
  expect(module_loader.load_typed(typed_name(:task, 'testmodule::baz'))).not_to be_nil
164
164
  expect { module_loader.load_typed(typed_name(:task, 'testmodule::qux')) }.to raise_error(/No source besides task metadata was found/)
165
165
  end
166
+
167
+ it 'raises and error when `parameters` is not a hash' do
168
+ metadata = { 'parameters' => 'foo' }
169
+ module_dir = dir_containing('testmodule', 'tasks' => {'foo.py' => '', 'foo.json' => metadata.to_json})
170
+
171
+ module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, loaders, 'testmodule', module_dir)
172
+ expect{module_loader.load_typed(typed_name(:task, 'testmodule::foo'))}
173
+ .to raise_error(Puppet::ParseError, /Failed to load metadata for task testmodule::foo: 'parameters' must be a hash/)
174
+ end
175
+
176
+ it 'raises and error when `implementations` `requirements` key is not an array' do
177
+ metadata = { 'implementations' => { 'name' => 'foo.py', 'requirements' => 'foo'} }
178
+ module_dir = dir_containing('testmodule', 'tasks' => {'foo.py' => '', 'foo.json' => metadata.to_json})
179
+
180
+ module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, loaders, 'testmodule', module_dir)
181
+ expect{module_loader.load_typed(typed_name(:task, 'testmodule::foo'))}
182
+ .to raise_error(Puppet::Module::Task::InvalidMetadata,
183
+ /Task metadata for task testmodule::foo does not specify implementations as an array/)
184
+ end
185
+
186
+ it 'raises and error when top-level `files` is not an array' do
187
+ metadata = { 'files' => 'foo' }
188
+ module_dir = dir_containing('testmodule', 'tasks' => {'foo.py' => '', 'foo.json' => metadata.to_json})
189
+
190
+ module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, loaders, 'testmodule', module_dir)
191
+ expect{module_loader.load_typed(typed_name(:task, 'testmodule::foo'))}
192
+ .to raise_error(Puppet::Module::Task::InvalidMetadata, /The 'files' task metadata expects an array, got foo/)
193
+ end
194
+
195
+ it 'raises and error when `files` nested in `interpreters` is not an array' do
196
+ metadata = { 'implementations' => [{'name' => 'foo.py', 'files' => 'foo'}] }
197
+ module_dir = dir_containing('testmodule', 'tasks' => {'foo.py' => '', 'foo.json' => metadata.to_json})
198
+
199
+ module_loader = Puppet::Pops::Loader::ModuleLoaders.module_loader_from(static_loader, loaders, 'testmodule', module_dir)
200
+ expect{module_loader.load_typed(typed_name(:task, 'testmodule::foo'))}
201
+ .to raise_error(Puppet::Module::Task::InvalidMetadata, /The 'files' task metadata expects an array, got foo/)
202
+ end
166
203
  end
167
204
 
168
205
  def typed_name(type, name)
@@ -1,7 +1,12 @@
1
1
  require 'spec_helper'
2
2
  require 'puppet/provider/exec'
3
+ require 'puppet_spec/compiler'
4
+ require 'puppet_spec/files'
3
5
 
4
6
  describe Puppet::Provider::Exec do
7
+ include PuppetSpec::Compiler
8
+ include PuppetSpec::Files
9
+
5
10
  describe "#extractexe" do
6
11
  it "should return the first element of an array" do
7
12
  expect(subject.extractexe(['one', 'two'])).to eq('one')
@@ -31,4 +36,208 @@ describe Puppet::Provider::Exec do
31
36
  end
32
37
  end
33
38
  end
39
+
40
+ context "when handling sensitive data" do
41
+ before :each do
42
+ Puppet[:log_level] = 'debug'
43
+ end
44
+
45
+ let(:supersecret) { 'supersecret' }
46
+ let(:path) do
47
+ if Puppet::Util::Platform.windows?
48
+ # The `apply_compiled_manifest` helper doesn't add the `path` fact, so
49
+ # we can't reference that in our manifest. Windows PATHs can contain
50
+ # double quotes and trailing backslashes, which confuse HEREDOC
51
+ # interpolation below. So sanitize it:
52
+ ENV['PATH'].split(File::PATH_SEPARATOR).map do |dir|
53
+ dir.gsub(/"/, '\"').gsub(/\\$/, '')
54
+ end.join(File::PATH_SEPARATOR)
55
+ else
56
+ ENV['PATH']
57
+ end
58
+ end
59
+
60
+ def ruby_exit_0
61
+ "ruby -e 'exit 0'"
62
+ end
63
+
64
+ def echo_from_ruby_exit_0(message)
65
+ # Escape double quotes due to HEREDOC interpolation below
66
+ "ruby -e 'puts \"#{message}\"; exit 0'".gsub(/"/, '\"')
67
+ end
68
+
69
+ def echo_from_ruby_exit_1(message)
70
+ # Escape double quotes due to HEREDOC interpolation below
71
+ "ruby -e 'puts \"#{message}\"; exit 1'".gsub(/"/, '\"')
72
+ end
73
+
74
+ context "when validating the command" do
75
+ it "redacts the arguments if the command is relative" do
76
+ expect {
77
+ apply_compiled_manifest(<<-MANIFEST)
78
+ exec { 'echo':
79
+ command => Sensitive.new('echo #{supersecret}')
80
+ }
81
+ MANIFEST
82
+ }.to raise_error do |err|
83
+ expect(err).to be_a(Puppet::Error)
84
+ expect(err.message).to match(/'echo' is not qualified and no path was specified. Please qualify the command or specify a path./)
85
+ expect(err.message).to_not match(/#{supersecret}/)
86
+ end
87
+ end
88
+
89
+ it "redacts the arguments if the command is a directory" do
90
+ dir = tmpdir('exec')
91
+ apply_compiled_manifest(<<-MANIFEST)
92
+ exec { 'echo':
93
+ command => Sensitive.new('#{dir} #{supersecret}'),
94
+ }
95
+ MANIFEST
96
+ expect(@logs).to include(an_object_having_attributes(level: :err, message: /'#{dir}' is a directory, not a file/))
97
+ expect(@logs).to_not include(an_object_having_attributes(message: /#{supersecret}/))
98
+ end
99
+
100
+ it "redacts the arguments if the command isn't executable" do
101
+ file = tmpfile('exec')
102
+ Puppet::FileSystem.touch(file)
103
+ Puppet::FileSystem.chmod(0644, file)
104
+
105
+ apply_compiled_manifest(<<-MANIFEST)
106
+ exec { 'echo':
107
+ command => Sensitive.new('#{file} #{supersecret}'),
108
+ }
109
+ MANIFEST
110
+ # Execute permission works differently on Windows, but execute will fail since the
111
+ # file doesn't have a valid extension and isn't a valid executable. The raised error
112
+ # will be Errno::EIO, which is not useful. The Windows execute code needs to raise
113
+ # Puppet::Util::Windows::Error so the Win32 error message is preserved.
114
+ pending("PUP-3561 Needs to raise a meaningful Puppet::Error") if Puppet::Util::Platform.windows?
115
+ expect(@logs).to include(an_object_having_attributes(level: :err, message: /'#{file}' is not executable/))
116
+ expect(@logs).to_not include(an_object_having_attributes(message: /#{supersecret}/))
117
+ end
118
+
119
+ it "redacts the arguments if the relative command cannot be resolved using the path parameter" do
120
+ file = File.basename(tmpfile('exec'))
121
+ dir = tmpdir('exec')
122
+
123
+ apply_compiled_manifest(<<-MANIFEST)
124
+ exec { 'echo':
125
+ command => Sensitive.new('#{file} #{supersecret}'),
126
+ path => "#{dir}",
127
+ }
128
+ MANIFEST
129
+ expect(@logs).to include(an_object_having_attributes(level: :err, message: /Could not find command '#{file}'/))
130
+ expect(@logs).to_not include(an_object_having_attributes(message: /#{supersecret}/))
131
+ end
132
+ end
133
+
134
+ it "redacts the command on success", unless: Puppet::Util::Platform.jruby? do
135
+ command = echo_from_ruby_exit_0(supersecret)
136
+
137
+ apply_compiled_manifest(<<-MANIFEST)
138
+ exec { 'true':
139
+ command => Sensitive.new("#{command}"),
140
+ path => "#{path}",
141
+ }
142
+ MANIFEST
143
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Executing '[redacted]'", source: /Exec\[true\]/))
144
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Executing: '[redacted]'", source: "Puppet"))
145
+ expect(@logs).to include(an_object_having_attributes(level: :notice, message: "executed successfully"))
146
+ expect(@logs).to_not include(an_object_having_attributes(message: /#{supersecret}/))
147
+ end
148
+
149
+ it "redacts the command on failure", unless: Puppet::Util::Platform.jruby? do
150
+ command = echo_from_ruby_exit_1(supersecret)
151
+
152
+ apply_compiled_manifest(<<-MANIFEST)
153
+ exec { 'false':
154
+ command => Sensitive.new("#{command}"),
155
+ path => "#{path}",
156
+ }
157
+ MANIFEST
158
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Executing '[redacted]'", source: /Exec\[false\]/))
159
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Executing: '[redacted]'", source: "Puppet"))
160
+ expect(@logs).to include(an_object_having_attributes(level: :err, message: "[command redacted] returned 1 instead of one of [0]"))
161
+ expect(@logs).to_not include(an_object_having_attributes(message: /#{supersecret}/))
162
+ end
163
+
164
+ context "when handling checks", unless: Puppet::Util::Platform.jruby? do
165
+ let(:onlyifsecret) { "onlyifsecret" }
166
+ let(:unlesssecret) { "unlesssecret" }
167
+
168
+ it "redacts command and onlyif outputs" do
169
+ onlyif = echo_from_ruby_exit_0(onlyifsecret)
170
+
171
+ apply_compiled_manifest(<<-MANIFEST)
172
+ exec { 'true':
173
+ command => Sensitive.new("#{ruby_exit_0}"),
174
+ onlyif => Sensitive.new("#{onlyif}"),
175
+ path => "#{path}",
176
+ }
177
+ MANIFEST
178
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Executing check '[redacted]'"))
179
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Executing '[redacted]'", source: /Exec\[true\]/))
180
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Executing: '[redacted]'", source: "Puppet"))
181
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "[output redacted]"))
182
+ expect(@logs).to include(an_object_having_attributes(level: :notice, message: "executed successfully"))
183
+ expect(@logs).to_not include(an_object_having_attributes(message: /#{onlyifsecret}/))
184
+ end
185
+
186
+ it "redacts the command that would have been executed but didn't due to onlyif" do
187
+ command = echo_from_ruby_exit_0(supersecret)
188
+ onlyif = echo_from_ruby_exit_1(onlyifsecret)
189
+
190
+ apply_compiled_manifest(<<-MANIFEST)
191
+ exec { 'true':
192
+ command => Sensitive.new("#{command}"),
193
+ onlyif => Sensitive.new("#{onlyif}"),
194
+ path => "#{path}",
195
+ }
196
+ MANIFEST
197
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Executing check '[redacted]'"))
198
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Executing: '[redacted]'", source: "Puppet"))
199
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "[output redacted]"))
200
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "'[command redacted]' won't be executed because of failed check 'onlyif'"))
201
+ expect(@logs).to_not include(an_object_having_attributes(message: /#{supersecret}/))
202
+ expect(@logs).to_not include(an_object_having_attributes(message: /#{onlyifsecret}/))
203
+ end
204
+
205
+ it "redacts command and unless outputs" do
206
+ unlesscmd = echo_from_ruby_exit_1(unlesssecret)
207
+
208
+ apply_compiled_manifest(<<-MANIFEST)
209
+ exec { 'true':
210
+ command => Sensitive.new("#{ruby_exit_0}"),
211
+ unless => Sensitive.new("#{unlesscmd}"),
212
+ path => "#{path}",
213
+ }
214
+ MANIFEST
215
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Executing check '[redacted]'"))
216
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Executing '[redacted]'", source: /Exec\[true\]/))
217
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Executing: '[redacted]'", source: "Puppet"))
218
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "[output redacted]"))
219
+ expect(@logs).to include(an_object_having_attributes(level: :notice, message: "executed successfully"))
220
+ expect(@logs).to_not include(an_object_having_attributes(message: /#{unlesssecret}/))
221
+ end
222
+
223
+ it "redacts the command that would have been executed but didn't due to unless" do
224
+ command = echo_from_ruby_exit_0(supersecret)
225
+ unlesscmd = echo_from_ruby_exit_0(unlesssecret)
226
+
227
+ apply_compiled_manifest(<<-MANIFEST)
228
+ exec { 'true':
229
+ command => Sensitive.new("#{command}"),
230
+ unless => Sensitive.new("#{unlesscmd}"),
231
+ path => "#{path}",
232
+ }
233
+ MANIFEST
234
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Executing check '[redacted]'"))
235
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "Executing: '[redacted]'", source: "Puppet"))
236
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "[output redacted]"))
237
+ expect(@logs).to include(an_object_having_attributes(level: :debug, message: "'[command redacted]' won't be executed because of failed check 'unless'"))
238
+ expect(@logs).to_not include(an_object_having_attributes(message: /#{supersecret}/))
239
+ expect(@logs).to_not include(an_object_having_attributes(message: /#{unlesssecret}/))
240
+ end
241
+ end
242
+ end
34
243
  end
@@ -0,0 +1,186 @@
1
+ require 'spec_helper'
2
+
3
+ describe Puppet::Type.type(:package).provider(:dnfmodule) do
4
+ include PuppetSpec::Fixtures
5
+
6
+ let(:dnf_version) do
7
+ <<-DNF_OUTPUT
8
+ 4.0.9
9
+ Installed: dnf-0:4.0.9.2-5.el8.noarch at Wed 29 May 2019 07:05:05 AM GMT
10
+ Built : Red Hat, Inc. <http://bugzilla.redhat.com/bugzilla> at Thu 14 Feb 2019 12:04:07 PM GMT
11
+
12
+ Installed: rpm-0:4.14.2-9.el8.x86_64 at Wed 29 May 2019 07:04:33 AM GMT
13
+ Built : Red Hat, Inc. <http://bugzilla.redhat.com/bugzilla> at Thu 20 Dec 2018 01:30:03 PM GMT
14
+ DNF_OUTPUT
15
+ end
16
+
17
+ let(:execute_options) do
18
+ {:failonfail => true, :combine => true, :custom_environment => {}}
19
+ end
20
+
21
+ let(:packages) { File.read(my_fixture("dnf-module-list-installed.txt")) }
22
+ let(:dnf_path) { '/usr/bin/dnf' }
23
+
24
+ before(:each) { allow(Puppet::Util).to receive(:which).with('/usr/bin/dnf').and_return(dnf_path) }
25
+
26
+ it "should have lower specificity" do
27
+ allow(Facter).to receive(:value).with(:osfamily).and_return(:redhat)
28
+ allow(Facter).to receive(:value).with(:operatingsystem).and_return(:redhat)
29
+ allow(Facter).to receive(:value).with(:operatingsystemmajrelease).and_return('8')
30
+ expect(described_class.specificity).to be < 200
31
+ end
32
+
33
+ describe "should be an opt-in provider" do
34
+ Array(4..8).each do |ver|
35
+ it "should not be default for redhat #{ver}" do
36
+ allow(Facter).to receive(:value).with(:operatingsystem).and_return('redhat')
37
+ allow(Facter).to receive(:value).with(:osfamily).and_return('redhat')
38
+ allow(Facter).to receive(:value).with(:operatingsystemmajrelease).and_return(ver.to_s)
39
+ expect(described_class).not_to be_default
40
+ end
41
+ end
42
+ end
43
+
44
+ describe "handling dnf versions" do
45
+ before(:each) do
46
+ expect(Puppet::Type::Package::ProviderDnfmodule).to receive(:execute)
47
+ .with(["/usr/bin/dnf", "--version"])
48
+ .and_return(dnf_version).at_most(:once)
49
+ expect(Puppet::Util::Execution).to receive(:execute)
50
+ .with(["/usr/bin/dnf", "--version"], execute_options)
51
+ .and_return(Puppet::Util::Execution::ProcessOutput.new(dnf_version, 0))
52
+ end
53
+
54
+ describe "with a supported dnf version" do
55
+ it "correctly parses the version" do
56
+ expect(described_class.current_version).to eq('4.0.9')
57
+ end
58
+ end
59
+
60
+ describe "with an unsupported dnf version" do
61
+ let(:dnf_version) do
62
+ <<-DNF_OUTPUT
63
+ 2.7.5
64
+ Installed: dnf-0:2.7.5-12.fc28.noarch at Mon 13 Aug 2018 11:05:27 PM GMT
65
+ Built : Fedora Project at Wed 18 Apr 2018 02:29:51 PM GMT
66
+
67
+ Installed: rpm-0:4.14.1-7.fc28.x86_64 at Mon 13 Aug 2018 11:05:25 PM GMT
68
+ Built : Fedora Project at Mon 19 Feb 2018 09:29:01 AM GMT
69
+ DNF_OUTPUT
70
+ end
71
+
72
+ before(:each) { described_class.instance_variable_set("@current_version", nil) }
73
+
74
+ it "correctly parses the version" do
75
+ expect(described_class.current_version).to eq('2.7.5')
76
+ end
77
+
78
+ it "raises an error when attempting prefetch" do
79
+ expect { described_class.prefetch('anything') }.to raise_error(Puppet::Error, "Modules are not supported on DNF versions lower than 3.0.1")
80
+ end
81
+ end
82
+ end
83
+
84
+ describe "when installing a module" do
85
+ let(:name) { 'baz' }
86
+
87
+ let(:resource) do
88
+ Puppet::Type.type(:package).new(
89
+ :name => name,
90
+ :provider => 'dnfmodule',
91
+ )
92
+ end
93
+
94
+ let(:provider) do
95
+ provider = described_class.new
96
+ provider.resource = resource
97
+ provider
98
+ end
99
+
100
+ describe 'provider features' do
101
+ it { is_expected.to be_versionable }
102
+ it { is_expected.to be_installable }
103
+ it { is_expected.to be_uninstallable }
104
+ end
105
+
106
+ context "when installing a new module" do
107
+ before do
108
+ provider.instance_variable_get('@property_hash')[:ensure] = :absent
109
+ end
110
+
111
+ it "should not reset the module stream when package is absent" do
112
+ resource[:ensure] = :present
113
+ expect(provider).not_to receive(:uninstall)
114
+ expect(provider).to receive(:execute)
115
+ provider.install
116
+ end
117
+
118
+ it "should not reset the module stream when package is purged" do
119
+ provider.instance_variable_get('@property_hash')[:ensure] = :purged
120
+ resource[:ensure] = :present
121
+ expect(provider).not_to receive(:uninstall)
122
+ expect(provider).to receive(:execute)
123
+ provider.install
124
+ end
125
+
126
+ it "should install the default stream and flavor" do
127
+ resource[:ensure] = :present
128
+ expect(provider).to receive(:execute).with(array_including('baz'))
129
+ provider.install
130
+ end
131
+
132
+ it "should install a specific stream" do
133
+ resource[:ensure] = '9.6'
134
+ expect(provider).to receive(:execute).with(array_including('baz:9.6'))
135
+ provider.install
136
+ end
137
+
138
+ it "should install a specific flavor" do
139
+ resource[:ensure] = :present
140
+ resource[:flavor] = 'minimal'
141
+ expect(provider).to receive(:execute).with(array_including('baz/minimal'))
142
+ provider.install
143
+ end
144
+
145
+ it "should install a specific flavor and stream" do
146
+ resource[:ensure] = '9.6'
147
+ resource[:flavor] = 'minimal'
148
+ expect(provider).to receive(:execute).with(array_including('baz:9.6/minimal'))
149
+ provider.install
150
+ end
151
+ end
152
+
153
+ context "when ensuring a specific version on top of another stream" do
154
+ before do
155
+ provider.instance_variable_get('@property_hash')[:ensure] = '9.6'
156
+ end
157
+
158
+ it "should remove existing packages and reset the module stream before installing" do
159
+ resource[:ensure] = '10'
160
+ expect(provider).to receive(:execute).thrice.with(array_including(/remove|reset|install/))
161
+ provider.install
162
+ end
163
+ end
164
+ end
165
+
166
+ context "parsing the output of module list --installed" do
167
+ before { allow(described_class).to receive(:command).with(:dnf).and_return(dnf_path) }
168
+
169
+ it "returns an array of installed modules" do
170
+ allow(Puppet::Util::Execution).to receive(:execute)
171
+ .with("/usr/bin/dnf module list --installed -d 0 -e 1")
172
+ .and_return(packages)
173
+
174
+ installed_packages = described_class.instances.map { |package| package.properties }
175
+ expected_packages = [{name: "gimp", ensure: "2.8", flavor: "devel", :provider => :dnfmodule},
176
+ {name: "mariadb", ensure: "10.3", flavor: "client", :provider => :dnfmodule},
177
+ {name: "nodejs", ensure: "10", flavor: "minimal", :provider => :dnfmodule},
178
+ {name: "perl", ensure: "5.26", flavor: "minimal", :provider => :dnfmodule},
179
+ {name: "postgresql", ensure: "10", flavor: "server", :provider => :dnfmodule},
180
+ {name: "rust-toolset", ensure: "rhel8", flavor: "common", :provider => :dnfmodule},
181
+ {name: "subversion", ensure: "1.10", flavor: "server", :provider => :dnfmodule}]
182
+
183
+ expect(installed_packages).to eql(expected_packages)
184
+ end
185
+ end
186
+ end