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
@@ -170,6 +170,12 @@ describe Puppet::Application::Device do
170
170
 
171
171
  device.handle_facts(true)
172
172
  end
173
+
174
+ it "should register ssl OIDs" do
175
+ expect(Puppet::SSL::Oids).to receive(:register_puppet_oids)
176
+
177
+ device.setup
178
+ end
173
179
  end
174
180
 
175
181
  describe "during setup" do
@@ -1,6 +1,5 @@
1
1
  require 'spec_helper'
2
2
  require 'puppet/application/ssl'
3
- require 'webmock/rspec'
4
3
  require 'openssl'
5
4
  require 'puppet/test_ca'
6
5
 
@@ -23,11 +22,6 @@ describe Puppet::Application::Ssl, unless: Puppet::Util::Platform.jruby? do
23
22
  end
24
23
 
25
24
  before do
26
- WebMock.disable_net_connect!
27
-
28
- allow_any_instance_of(Net::HTTP).to receive(:start)
29
- allow_any_instance_of(Net::HTTP).to receive(:finish)
30
-
31
25
  Puppet.settings.use(:main)
32
26
  Puppet[:certname] = name
33
27
  Puppet[:vardir] = tmpdir("ssl_testing")
@@ -126,8 +120,11 @@ describe Puppet::Application::Ssl, unless: Puppet::Util::Platform.jruby? do
126
120
  end
127
121
 
128
122
  it 'registers OIDs' do
123
+ stub_request(:put, %r{puppet-ca/v1/certificate_request/#{name}}).to_return(status: 200)
124
+ stub_request(:get, %r{puppet-ca/v1/certificate/#{name}}).to_return(status: 404)
125
+
129
126
  expect(Puppet::SSL::Oids).to receive(:register_puppet_oids)
130
- expects_command_to_fail(%r{Failed to submit certificate request})
127
+ expects_command_to_pass(%r{Submitted certificate request for '#{name}' to https://.*})
131
128
  end
132
129
 
133
130
  it 'submits the CSR and saves it locally' do
@@ -749,6 +749,7 @@ describe Puppet::Configurer do
749
749
  end
750
750
 
751
751
  it "should not make a node request" do
752
+ expects_new_catalog_only(@catalog)
752
753
  expect(Puppet::Node.indirection).not_to receive(:find)
753
754
 
754
755
  @agent.run
@@ -30,6 +30,27 @@ describe Puppet::Context::TrustedInformation, :unless => RUBY_PLATFORM == 'java'
30
30
  cert
31
31
  end
32
32
 
33
+ let(:external_data) {
34
+ {
35
+ 'string' => 'a',
36
+ 'integer' => 1,
37
+ 'boolean' => true,
38
+ 'hash' => { 'one' => 'two' },
39
+ 'array' => ['b', 2, {}]
40
+ }
41
+ }
42
+
43
+ def allow_external_trusted_data(certname, data)
44
+ Puppet[:trusted_external_command] = '/usr/bin/generate_data.sh'
45
+ allow(Puppet::Util::Execution).to receive(:execute).with(['/usr/bin/generate_data.sh', certname], anything).and_return(JSON.dump(data))
46
+ end
47
+
48
+ it "defaults external to an empty hash" do
49
+ trusted = Puppet::Context::TrustedInformation.new(false, 'ignored', nil)
50
+
51
+ expect(trusted.external).to eq({})
52
+ end
53
+
33
54
  context "when remote" do
34
55
  it "has no cert information when it isn't authenticated" do
35
56
  trusted = Puppet::Context::TrustedInformation.remote(false, 'ignored', nil)
@@ -61,6 +82,14 @@ describe Puppet::Context::TrustedInformation, :unless => RUBY_PLATFORM == 'java'
61
82
  expect(trusted.certname).to eq('cert name')
62
83
  expect(trusted.extensions).to eq({})
63
84
  end
85
+
86
+ it 'contains external trusted data' do
87
+ allow_external_trusted_data('cert name', external_data)
88
+
89
+ trusted = Puppet::Context::TrustedInformation.remote(true, 'cert name', nil)
90
+
91
+ expect(trusted.external).to eq(external_data)
92
+ end
64
93
  end
65
94
 
66
95
  context "when local" do
@@ -85,6 +114,14 @@ describe Puppet::Context::TrustedInformation, :unless => RUBY_PLATFORM == 'java'
85
114
  expect(trusted.hostname).to be_nil
86
115
  expect(trusted.domain).to be_nil
87
116
  end
117
+
118
+ it 'contains external trusted data' do
119
+ allow_external_trusted_data('cert name', external_data)
120
+
121
+ trusted = Puppet::Context::TrustedInformation.remote(true, 'cert name', nil)
122
+
123
+ expect(trusted.external).to eq(external_data)
124
+ end
88
125
  end
89
126
 
90
127
  it "converts itself to a hash" do
@@ -98,7 +135,8 @@ describe Puppet::Context::TrustedInformation, :unless => RUBY_PLATFORM == 'java'
98
135
  '1.3.6.1.4.1.34380.1.2.2' => 'more CSR specific info',
99
136
  },
100
137
  'hostname' => 'cert name',
101
- 'domain' => nil
138
+ 'domain' => nil,
139
+ 'external' => {},
102
140
  })
103
141
  end
104
142
 
@@ -113,7 +151,8 @@ describe Puppet::Context::TrustedInformation, :unless => RUBY_PLATFORM == 'java'
113
151
  '1.3.6.1.4.1.34380.1.2.2' => 'more CSR specific info',
114
152
  },
115
153
  'hostname' => 'hostname',
116
- 'domain' => 'domain.long'
154
+ 'domain' => 'domain.long',
155
+ 'external' => {},
117
156
  })
118
157
  end
119
158
 
@@ -0,0 +1,440 @@
1
+ require 'spec_helper'
2
+ require 'webmock/rspec'
3
+ require 'puppet/http'
4
+
5
+ describe Puppet::HTTP::Client do
6
+ let(:uri) { URI.parse('https://www.example.com') }
7
+ let(:client) { described_class.new }
8
+ let(:credentials) { ['user', 'pass'] }
9
+
10
+ it 'creates unique sessions' do
11
+ expect(client.create_session).to_not eq(client.create_session)
12
+ end
13
+
14
+ context "when connecting" do
15
+ it 'connects to HTTP URLs' do
16
+ uri = URI.parse('http://www.example.com')
17
+
18
+ client.connect(uri) do |http|
19
+ expect(http.address).to eq('www.example.com')
20
+ expect(http.port).to eq(80)
21
+ expect(http).to_not be_use_ssl
22
+ end
23
+ end
24
+
25
+ it 'connects to HTTPS URLs' do
26
+ client.connect(uri) do |http|
27
+ expect(http.address).to eq('www.example.com')
28
+ expect(http.port).to eq(443)
29
+ expect(http).to be_use_ssl
30
+ end
31
+ end
32
+
33
+ it 'raises ConnectionError if the connection is refused' do
34
+ allow_any_instance_of(Net::HTTP).to receive(:start).and_raise(Errno::ECONNREFUSED)
35
+
36
+ expect {
37
+ client.connect(uri)
38
+ }.to raise_error(Puppet::HTTP::ConnectionError, %r{Failed to connect to https://www.example.com:})
39
+ end
40
+ end
41
+
42
+ context 'after connecting' do
43
+ def expect_http_error(cause, expected_message)
44
+ expect {
45
+ client.connect(uri) do |_|
46
+ raise cause, 'whoops'
47
+ end
48
+ }.to raise_error(Puppet::HTTP::HTTPError, expected_message)
49
+ end
50
+
51
+ it 're-raises HTTPError' do
52
+ expect_http_error(Puppet::HTTP::HTTPError, 'whoops')
53
+ end
54
+
55
+ it 'raises HTTPError if connection is interrupted while reading' do
56
+ expect_http_error(EOFError, %r{Request to https://www.example.com interrupted after .* seconds})
57
+ end
58
+
59
+ it 'raises HTTPError if connection times out' do
60
+ expect_http_error(Net::ReadTimeout, %r{Request to https://www.example.com timed out after .* seconds})
61
+ end
62
+
63
+ it 'raises HTTPError if connection fails' do
64
+ expect_http_error(ArgumentError, %r{Request to https://www.example.com failed after .* seconds})
65
+ end
66
+ end
67
+
68
+ context "when closing" do
69
+ it "closes all connections in the pool" do
70
+ pool = double('pool')
71
+ expect(pool).to receive(:close)
72
+
73
+ client = described_class.new(pool: pool)
74
+ client.close
75
+ end
76
+ end
77
+
78
+ context "for GET requests" do
79
+ it "includes default HTTP headers" do
80
+ stub_request(:get, uri).with(headers: {'X-Puppet-Version' => /./, 'User-Agent' => /./})
81
+
82
+ client.get(uri)
83
+ end
84
+
85
+ it "stringifies keys and encodes values in the query" do
86
+ stub_request(:get, uri).with(query: "foo=bar%3Dbaz")
87
+
88
+ client.get(uri, params: {:foo => "bar=baz"})
89
+ end
90
+
91
+ it "merges custom headers with default ones" do
92
+ stub_request(:get, uri).with(headers: { 'X-Foo' => 'Bar', 'X-Puppet-Version' => /./, 'User-Agent' => /./ })
93
+
94
+ client.get(uri, headers: {'X-Foo' => 'Bar'})
95
+ end
96
+
97
+ it "returns the response" do
98
+ stub_request(:get, uri)
99
+
100
+ response = client.get(uri)
101
+ expect(response).to be_an_instance_of(Puppet::HTTP::Response)
102
+ expect(response).to be_success
103
+ expect(response.code).to eq(200)
104
+ end
105
+
106
+ it "returns the entire response body" do
107
+ stub_request(:get, uri).to_return(body: "abc")
108
+
109
+ expect(client.get(uri).body).to eq("abc")
110
+ end
111
+
112
+ it "streams the response body when a block is given" do
113
+ stub_request(:get, uri).to_return(body: "abc")
114
+
115
+ io = StringIO.new
116
+ client.get(uri) do |response|
117
+ response.read_body do |data|
118
+ io.write(data)
119
+ end
120
+ end
121
+
122
+ expect(io.string).to eq("abc")
123
+ end
124
+ end
125
+
126
+ context "for PUT requests" do
127
+ it "includes default HTTP headers" do
128
+ stub_request(:put, uri).with(headers: {'X-Puppet-Version' => /./, 'User-Agent' => /./})
129
+
130
+ client.put(uri, content_type: 'text/plain', body: "")
131
+ end
132
+
133
+ it "stringifies keys and encodes values in the query" do
134
+ stub_request(:put, "https://www.example.com").with(query: "foo=bar%3Dbaz")
135
+
136
+ client.put(uri, params: {:foo => "bar=baz"}, content_type: 'text/plain', body: "")
137
+ end
138
+
139
+ it "includes custom headers" do
140
+ stub_request(:put, "https://www.example.com").with(headers: { 'X-Foo' => 'Bar' })
141
+
142
+ client.put(uri, headers: {'X-Foo' => 'Bar'}, content_type: 'text/plain', body: "")
143
+ end
144
+
145
+ it "returns the response" do
146
+ stub_request(:put, uri)
147
+
148
+ response = client.put(uri, content_type: 'text/plain', body: "")
149
+ expect(response).to be_an_instance_of(Puppet::HTTP::Response)
150
+ expect(response).to be_success
151
+ expect(response.code).to eq(200)
152
+ end
153
+
154
+ it "sets content-length and content-type for the body" do
155
+ stub_request(:put, uri).with(headers: {"Content-Length" => "5", "Content-Type" => "text/plain"})
156
+
157
+ client.put(uri, content_type: 'text/plain', body: "hello")
158
+ end
159
+ end
160
+
161
+ context "Basic Auth" do
162
+ it "submits credentials for GET requests" do
163
+ stub_request(:get, uri).with(basic_auth: credentials)
164
+
165
+ client.get(uri, user: 'user', password: 'pass')
166
+ end
167
+
168
+ it "submits credentials for PUT requests" do
169
+ stub_request(:put, uri).with(basic_auth: credentials)
170
+
171
+ client.put(uri, content_type: 'text/plain', body: "hello", user: 'user', password: 'pass')
172
+ end
173
+
174
+ it "returns response containing access denied" do
175
+ stub_request(:get, uri).with(basic_auth: credentials).to_return(status: [403, "Ye Shall Not Pass"])
176
+
177
+ response = client.get(uri, user: 'user', password: 'pass')
178
+ expect(response.code).to eq(403)
179
+ expect(response.reason).to eq("Ye Shall Not Pass")
180
+ expect(response).to_not be_success
181
+ end
182
+
183
+ it 'omits basic auth if user is nil' do
184
+ stub_request(:get, uri).with do |req|
185
+ expect(req.headers).to_not include('Authorization')
186
+ end
187
+
188
+ client.get(uri, user: nil, password: 'pass')
189
+ end
190
+
191
+ it 'omits basic auth if password is nil' do
192
+ stub_request(:get, uri).with do |req|
193
+ expect(req.headers).to_not include('Authorization')
194
+ end
195
+
196
+ client.get(uri, user: 'user', password: nil)
197
+ end
198
+ end
199
+
200
+ context "when redirecting" do
201
+ let(:start_url) { URI("https://www.example.com:8140/foo") }
202
+ let(:bar_url) { "https://www.example.com:8140/bar" }
203
+ let(:baz_url) { "https://www.example.com:8140/baz" }
204
+ let(:other_host) { "https://other.example.com:8140/qux" }
205
+
206
+ def redirect_to(status: 302, url:)
207
+ { status: status, headers: { 'Location' => url }, body: "Redirected to #{url}" }
208
+ end
209
+
210
+ it "preserves GET method" do
211
+ stub_request(:get, start_url).to_return(redirect_to(url: bar_url))
212
+ stub_request(:get, bar_url).to_return(status: 200)
213
+
214
+ response = client.get(start_url)
215
+ expect(response).to be_success
216
+ end
217
+
218
+ it "preserves PUT method" do
219
+ stub_request(:put, start_url).to_return(redirect_to(url: bar_url))
220
+ stub_request(:put, bar_url).to_return(status: 200)
221
+
222
+ response = client.put(start_url, body: "", content_type: 'text/plain')
223
+ expect(response).to be_success
224
+ end
225
+
226
+ it "preserves query parameters" do
227
+ query = { 'debug' => true }
228
+ stub_request(:get, start_url).with(query: query).to_return(redirect_to(url: bar_url))
229
+ stub_request(:get, bar_url).with(query: query).to_return(status: 200)
230
+
231
+ response = client.get(start_url, params: query)
232
+ expect(response).to be_success
233
+ end
234
+
235
+ it "preserves custom and default headers when redirecting" do
236
+ headers = { 'X-Foo' => 'Bar', 'X-Puppet-Version' => Puppet.version }
237
+ stub_request(:get, start_url).with(headers: headers).to_return(redirect_to(url: bar_url))
238
+ stub_request(:get, bar_url).with(headers: headers).to_return(status: 200)
239
+
240
+ response = client.get(start_url, headers: headers)
241
+ expect(response).to be_success
242
+ end
243
+
244
+ it "preserves basic authorization" do
245
+ stub_request(:get, start_url).with(basic_auth: credentials).to_return(redirect_to(url: bar_url))
246
+ stub_request(:get, bar_url).with(basic_auth: credentials).to_return(status: 200)
247
+
248
+ client.get(start_url, user: 'user', password: 'pass')
249
+ end
250
+
251
+ it "redirects given a relative location" do
252
+ relative_url = "/people.html"
253
+ stub_request(:get, start_url).to_return(redirect_to(url: relative_url))
254
+ stub_request(:get, "https://www.example.com:8140/people.html").to_return(status: 200)
255
+
256
+ response = client.get(start_url)
257
+ expect(response).to be_success
258
+ end
259
+
260
+ it "preserves query parameters given a relative location" do
261
+ relative_url = "/people.html"
262
+ query = { 'debug' => true }
263
+ stub_request(:get, start_url).with(query: query).to_return(redirect_to(url: relative_url))
264
+ stub_request(:get, "https://www.example.com:8140/people.html").with(query: query).to_return(status: 200)
265
+
266
+ response = client.get(start_url, params: query)
267
+ expect(response).to be_success
268
+ end
269
+
270
+ it "preserves request body for each request" do
271
+ data = 'some data'
272
+ stub_request(:put, start_url).with(body: data).to_return(redirect_to(url: bar_url))
273
+ stub_request(:put, bar_url).with(body: data).to_return(status: 200)
274
+
275
+ response = client.put(start_url, body: data, content_type: 'text/plain')
276
+ expect(response).to be_success
277
+ end
278
+
279
+ it "returns the body from the final response" do
280
+ stub_request(:get, start_url).to_return(redirect_to(url: bar_url))
281
+ stub_request(:get, bar_url).to_return(status: 200, body: 'followed')
282
+
283
+ response = client.get(start_url)
284
+ expect(response.body).to eq('followed')
285
+ end
286
+
287
+ [301, 307].each do |code|
288
+ it "also redirects on #{code}" do
289
+ stub_request(:get, start_url).to_return(redirect_to(status: code, url: bar_url))
290
+ stub_request(:get, bar_url).to_return(status: 200)
291
+
292
+ response = client.get(start_url)
293
+ expect(response).to be_success
294
+ end
295
+ end
296
+
297
+ [303, 308].each do |code|
298
+ it "returns an error on #{code}" do
299
+ stub_request(:get, start_url).to_return(redirect_to(status: code, url: bar_url))
300
+
301
+ response = client.get(start_url)
302
+ expect(response.code).to eq(code)
303
+ expect(response).to_not be_success
304
+ end
305
+ end
306
+
307
+ it "raises an error if the Location header is missing" do
308
+ stub_request(:get, start_url).to_return(status: 302)
309
+
310
+ expect {
311
+ client.get(start_url)
312
+ }.to raise_error(Puppet::HTTP::ProtocolError, "Location response header is missing")
313
+ end
314
+
315
+ it "raises an error if the Location header is invalid" do
316
+ stub_request(:get, start_url).to_return(redirect_to(status: 302, url: 'http://foo"bar'))
317
+
318
+ expect {
319
+ client.get(start_url)
320
+ }.to raise_error(Puppet::HTTP::ProtocolError, /Location URI is invalid/)
321
+ end
322
+
323
+ it "raises an error if limit is 0 and we're asked to follow" do
324
+ stub_request(:get, start_url).to_return(redirect_to(url: bar_url))
325
+
326
+ client = described_class.new(redirect_limit: 0)
327
+ expect {
328
+ client.get(start_url)
329
+ }.to raise_error(Puppet::HTTP::TooManyRedirects, %r{Too many HTTP redirections for https://www.example.com:8140})
330
+ end
331
+
332
+ it "raises an error if asked to follow redirects more times than the limit" do
333
+ stub_request(:get, start_url).to_return(redirect_to(url: bar_url))
334
+ stub_request(:get, bar_url).to_return(redirect_to(url: baz_url))
335
+
336
+ client = described_class.new(redirect_limit: 1)
337
+ expect {
338
+ client.get(start_url)
339
+ }.to raise_error(Puppet::HTTP::TooManyRedirects, %r{Too many HTTP redirections for https://www.example.com:8140})
340
+ end
341
+
342
+ it "follows multiple redirects if equal to or less than the redirect limit" do
343
+ stub_request(:get, start_url).to_return(redirect_to(url: bar_url))
344
+ stub_request(:get, bar_url).to_return(redirect_to(url: baz_url))
345
+ stub_request(:get, baz_url).to_return(status: 200)
346
+
347
+ client = described_class.new(redirect_limit: 2)
348
+ response = client.get(start_url)
349
+ expect(response).to be_success
350
+ end
351
+
352
+ it "redirects to a different host" do
353
+ stub_request(:get, start_url).to_return(redirect_to(url: other_host))
354
+ stub_request(:get, other_host).to_return(status: 200)
355
+
356
+ response = client.get(start_url)
357
+ expect(response).to be_success
358
+ end
359
+
360
+ it "redirects from http to https" do
361
+ http = URI("http://example.com/foo")
362
+ https = URI("https://example.com/bar")
363
+
364
+ stub_request(:get, http).to_return(redirect_to(url: https))
365
+ stub_request(:get, https).to_return(status: 200)
366
+
367
+ response = client.get(http)
368
+ expect(response).to be_success
369
+ end
370
+
371
+ it "redirects from https to http" do
372
+ http = URI("http://example.com/foo")
373
+ https = URI("https://example.com/bar")
374
+
375
+ stub_request(:get, https).to_return(redirect_to(url: http))
376
+ stub_request(:get, http).to_return(status: 200)
377
+
378
+ response = client.get(https)
379
+ expect(response).to be_success
380
+ end
381
+ end
382
+
383
+ context "when response indicates an overloaded server" do
384
+ def retry_after(datetime)
385
+ stub_request(:get, uri)
386
+ .to_return(status: [503, 'Service Unavailable'], headers: {'Retry-After' => datetime}).then
387
+ .to_return(status: 200)
388
+ end
389
+
390
+ it "returns a 503 response if Retry-After is not set" do
391
+ stub_request(:get, uri).to_return(status: [503, 'Service Unavailable'])
392
+
393
+ expect(client.get(uri).code).to eq(503)
394
+ end
395
+
396
+ it "raises if Retry-After is not convertible to an Integer or RFC 2822 Date" do
397
+ stub_request(:get, uri).to_return(status: [503, 'Service Unavailable'], headers: {'Retry-After' => 'foo'})
398
+
399
+ expect {
400
+ client.get(uri)
401
+ }.to raise_error(Puppet::HTTP::ProtocolError, /Failed to parse Retry-After header 'foo' as an integer or RFC 2822 date/)
402
+ end
403
+
404
+ it "should sleep and retry if Retry-After is an Integer" do
405
+ retry_after('42')
406
+
407
+ expect(::Kernel).to receive(:sleep).with(42)
408
+
409
+ client.get(uri)
410
+ end
411
+
412
+ it "should sleep and retry if Retry-After is an RFC 2822 Date" do
413
+ retry_after('Wed, 13 Apr 2005 15:18:05 GMT')
414
+
415
+ now = DateTime.new(2005, 4, 13, 8, 17, 5, '-07:00')
416
+ allow(DateTime).to receive(:now).and_return(now)
417
+
418
+ expect(::Kernel).to receive(:sleep).with(60)
419
+
420
+ client.get(uri)
421
+ end
422
+
423
+ it "should sleep for no more than the Puppet runinterval" do
424
+ retry_after('60')
425
+ Puppet[:runinterval] = 30
426
+
427
+ expect(::Kernel).to receive(:sleep).with(30)
428
+
429
+ client.get(uri)
430
+ end
431
+
432
+ it "should sleep for 0 seconds if the RFC 2822 date has past" do
433
+ retry_after('Wed, 13 Apr 2005 15:18:05 GMT')
434
+
435
+ expect(::Kernel).to receive(:sleep).with(0)
436
+
437
+ client.get(uri)
438
+ end
439
+ end
440
+ end