puppet 6.13.0 → 6.14.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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +7 -13
  3. data/Gemfile.lock +6 -6
  4. data/README.md +15 -22
  5. data/lib/puppet.rb +1 -1
  6. data/lib/puppet/application/agent.rb +9 -11
  7. data/lib/puppet/application/describe.rb +7 -5
  8. data/lib/puppet/application/device.rb +2 -2
  9. data/lib/puppet/application/filebucket.rb +14 -1
  10. data/lib/puppet/application/ssl.rb +1 -1
  11. data/lib/puppet/configurer.rb +30 -41
  12. data/lib/puppet/configurer/plugin_handler.rb +10 -1
  13. data/lib/puppet/defaults.rb +7 -1
  14. data/lib/puppet/face/plugin.rb +1 -1
  15. data/lib/puppet/functions/eyaml_lookup_key.rb +13 -8
  16. data/lib/puppet/http.rb +1 -0
  17. data/lib/puppet/http/client.rb +69 -34
  18. data/lib/puppet/http/resolver/server_list.rb +2 -2
  19. data/lib/puppet/http/resolver/settings.rb +1 -1
  20. data/lib/puppet/http/resolver/srv.rb +1 -1
  21. data/lib/puppet/http/response.rb +6 -1
  22. data/lib/puppet/http/service.rb +30 -11
  23. data/lib/puppet/http/service/ca.rb +8 -8
  24. data/lib/puppet/http/service/compiler.rb +41 -10
  25. data/lib/puppet/http/service/file_server.rb +40 -20
  26. data/lib/puppet/http/service/report.rb +12 -15
  27. data/lib/puppet/http/session.rb +39 -1
  28. data/lib/puppet/indirector/catalog/rest.rb +33 -0
  29. data/lib/puppet/indirector/facts/rest.rb +41 -0
  30. data/lib/puppet/indirector/file_content/rest.rb +30 -0
  31. data/lib/puppet/indirector/file_metadata/rest.rb +50 -0
  32. data/lib/puppet/indirector/node/rest.rb +23 -0
  33. data/lib/puppet/indirector/report/rest.rb +19 -0
  34. data/lib/puppet/indirector/rest.rb +6 -0
  35. data/lib/puppet/indirector/status/rest.rb +17 -0
  36. data/lib/puppet/loaders.rb +6 -0
  37. data/lib/puppet/network/http/base_pool.rb +1 -1
  38. data/lib/puppet/network/http/pool.rb +6 -1
  39. data/lib/puppet/provider/group/groupadd.rb +9 -4
  40. data/lib/puppet/runtime.rb +8 -1
  41. data/lib/puppet/settings.rb +2 -0
  42. data/lib/puppet/settings/http_extra_headers_setting.rb +25 -0
  43. data/lib/puppet/ssl/state_machine.rb +4 -0
  44. data/lib/puppet/test/test_helper.rb +3 -1
  45. data/lib/puppet/type/file.rb +13 -0
  46. data/lib/puppet/type/file/source.rb +47 -58
  47. data/lib/puppet/version.rb +1 -1
  48. data/locales/puppet.pot +167 -160
  49. data/man/man5/puppet.conf.5 +11 -3
  50. data/man/man8/puppet-agent.8 +6 -6
  51. data/man/man8/puppet-apply.8 +1 -1
  52. data/man/man8/puppet-catalog.8 +1 -1
  53. data/man/man8/puppet-config.8 +1 -1
  54. data/man/man8/puppet-describe.8 +1 -1
  55. data/man/man8/puppet-device.8 +2 -2
  56. data/man/man8/puppet-doc.8 +1 -1
  57. data/man/man8/puppet-epp.8 +1 -1
  58. data/man/man8/puppet-facts.8 +1 -1
  59. data/man/man8/puppet-filebucket.8 +17 -2
  60. data/man/man8/puppet-generate.8 +1 -1
  61. data/man/man8/puppet-help.8 +1 -1
  62. data/man/man8/puppet-key.8 +1 -1
  63. data/man/man8/puppet-lookup.8 +1 -1
  64. data/man/man8/puppet-man.8 +1 -1
  65. data/man/man8/puppet-module.8 +1 -1
  66. data/man/man8/puppet-node.8 +1 -1
  67. data/man/man8/puppet-parser.8 +1 -1
  68. data/man/man8/puppet-plugin.8 +1 -1
  69. data/man/man8/puppet-report.8 +1 -1
  70. data/man/man8/puppet-resource.8 +1 -1
  71. data/man/man8/puppet-script.8 +1 -1
  72. data/man/man8/puppet-ssl.8 +2 -2
  73. data/man/man8/puppet-status.8 +1 -1
  74. data/man/man8/puppet.8 +2 -2
  75. data/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_md5/should_fetch_if_not_on_the_local_disk.yml +1 -67
  76. data/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_md5/should_not_update_if_content_on_disk_is_up-to-date.yml +1 -69
  77. data/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_md5/should_update_if_content_differs_on_disk.yml +1 -69
  78. data/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/should_fetch_if_mtime_is_older_on_disk.yml +1 -67
  79. data/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/should_fetch_if_no_header_specified.yml +1 -65
  80. data/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/should_fetch_if_not_on_the_local_disk.yml +1 -67
  81. data/spec/fixtures/vcr/cassettes/Puppet_Type_File/when_sourcing/from_http/using_mtime/should_not_update_if_mtime_is_newer_on_disk.yml +1 -67
  82. data/spec/integration/faces/plugin_spec.rb +3 -1
  83. data/spec/integration/http/client_spec.rb +11 -0
  84. data/spec/integration/network/http_pool_spec.rb +9 -1
  85. data/spec/unit/application/describe_spec.rb +88 -50
  86. data/spec/unit/configurer/plugin_handler_spec.rb +36 -19
  87. data/spec/unit/configurer_spec.rb +16 -14
  88. data/spec/unit/face/plugin_spec.rb +12 -10
  89. data/spec/unit/functions/lookup_spec.rb +13 -0
  90. data/spec/unit/http/client_spec.rb +172 -1
  91. data/spec/unit/http/resolver_spec.rb +14 -2
  92. data/spec/unit/http/response_spec.rb +69 -0
  93. data/spec/unit/http/service/ca_spec.rb +28 -9
  94. data/spec/unit/http/service/compiler_spec.rb +151 -24
  95. data/spec/unit/http/service/file_server_spec.rb +65 -8
  96. data/spec/unit/http/service/report_spec.rb +17 -8
  97. data/spec/unit/http/service_spec.rb +92 -3
  98. data/spec/unit/http/session_spec.rb +104 -1
  99. data/spec/unit/indirector/catalog/rest_spec.rb +59 -2
  100. data/spec/unit/indirector/facts/rest_spec.rb +79 -24
  101. data/spec/unit/indirector/file_content/rest_spec.rb +53 -2
  102. data/spec/unit/indirector/file_metadata/rest_spec.rb +109 -2
  103. data/spec/unit/indirector/node/rest_spec.rb +57 -2
  104. data/spec/unit/indirector/report/rest_spec.rb +58 -51
  105. data/spec/unit/indirector/resource/ral_spec.rb +7 -8
  106. data/spec/unit/indirector/status/rest_spec.rb +43 -2
  107. data/spec/unit/network/http/pool_spec.rb +57 -11
  108. data/spec/unit/provider/group/groupadd_spec.rb +22 -8
  109. data/spec/unit/settings/autosign_setting_spec.rb +1 -1
  110. data/spec/unit/settings/http_extra_headers_spec.rb +64 -0
  111. data/spec/unit/ssl/state_machine_spec.rb +10 -0
  112. data/spec/unit/transaction_spec.rb +0 -2
  113. data/spec/unit/type/file/ensure_spec.rb +1 -2
  114. data/spec/unit/type/file/source_spec.rb +86 -35
  115. data/spec/unit/util/at_fork_spec.rb +1 -0
  116. data/spec/unit/util/pidlock_spec.rb +36 -24
  117. metadata +7 -3
  118. data/COMMITTERS.md +0 -244
@@ -3082,6 +3082,19 @@ describe "The lookup function" do
3082
3082
 
3083
3083
  let(:env_data) { data_files }
3084
3084
 
3085
+ context 'with unencryptable eyaml' do
3086
+ let(:data_files) do
3087
+ {
3088
+ 'common.eyaml' => <<-YAML.unindent
3089
+ key_with_invalid_eyaml: ENC[PKCS7,INVALID]
3090
+ YAML
3091
+ }
3092
+ end
3093
+
3094
+ it 'fails and reports error with ENC value, key being looked up and filename' do
3095
+ expect { lookup('key_with_invalid_eyaml') }.to raise_error(Puppet::DataBinding::LookupError, /hiera-eyaml backend error decrypting ENC\[PKCS7,INVALID\] when looking up key_with_invalid_eyaml in .*common\.eyaml\. Error was/)
3096
+ end
3097
+ end
3085
3098
  context 'and a module using eyaml with different options' do
3086
3099
 
3087
3100
  let(:private_module_key) do
@@ -4,7 +4,10 @@ require 'puppet/http'
4
4
 
5
5
  describe Puppet::HTTP::Client do
6
6
  let(:uri) { URI.parse('https://www.example.com') }
7
- let(:client) { described_class.new }
7
+ let(:pool) { Puppet::Network::HTTP::Pool.new }
8
+ let(:puppet_context) { Puppet::SSL::SSLContext.new }
9
+ let(:system_context) { Puppet::SSL::SSLContext.new }
10
+ let(:client) { described_class.new(pool: pool, ssl_context: puppet_context, system_ssl_context: system_context) }
8
11
  let(:credentials) { ['user', 'pass'] }
9
12
 
10
13
  it 'creates unique sessions' do
@@ -45,6 +48,38 @@ describe Puppet::HTTP::Client do
45
48
  client.connect(uri)
46
49
  }.to raise_error(Puppet::HTTP::ConnectionError, %r{^Request to https://www.example.com timed out connect operation after .* seconds})
47
50
  end
51
+
52
+ it 'connects using the default ssl context' do
53
+ expect(pool).to receive(:with_connection) do |_, verifier|
54
+ expect(verifier.ssl_context).to equal(puppet_context)
55
+ end
56
+
57
+ client.connect(uri)
58
+ end
59
+
60
+ it 'connects using a specified ssl context' do
61
+ other_context = Puppet::SSL::SSLContext.new
62
+
63
+ expect(pool).to receive(:with_connection) do |_, verifier|
64
+ expect(verifier.ssl_context).to equal(other_context)
65
+ end
66
+
67
+ client.connect(uri, ssl_context: other_context)
68
+ end
69
+
70
+ it 'connects using the system store' do
71
+ expect(pool).to receive(:with_connection) do |_, verifier|
72
+ expect(verifier.ssl_context).to equal(system_context)
73
+ end
74
+
75
+ client.connect(uri, include_system_store: true)
76
+ end
77
+
78
+ it 'raises an HTTPError if both are specified' do
79
+ expect {
80
+ client.connect(uri, ssl_context: puppet_context, include_system_store: true)
81
+ }.to raise_error(Puppet::HTTP::HTTPError, /The ssl_context and include_system_store parameters are mutually exclusive/)
82
+ end
48
83
  end
49
84
 
50
85
  context 'after connecting' do
@@ -138,6 +173,28 @@ describe Puppet::HTTP::Client do
138
173
 
139
174
  expect(io.string).to eq("abc")
140
175
  end
176
+
177
+ context 'when connecting' do
178
+ it 'uses a specified ssl context' do
179
+ stub_request(:get, uri).to_return(body: "abc")
180
+
181
+ other_context = Puppet::SSL::SSLContext.new
182
+
183
+ client.get(uri, ssl_context: other_context)
184
+ end
185
+
186
+ it 'uses the system store' do
187
+ stub_request(:get, uri).to_return(body: "abc")
188
+
189
+ client.get(uri, include_system_store: true)
190
+ end
191
+
192
+ it 'raises an HTTPError if both are specified' do
193
+ expect {
194
+ client.get(uri, ssl_context: puppet_context, include_system_store: true)
195
+ }.to raise_error(Puppet::HTTP::HTTPError, /The ssl_context and include_system_store parameters are mutually exclusive/)
196
+ end
197
+ end
141
198
  end
142
199
 
143
200
  context "for HEAD requests" do
@@ -173,6 +230,28 @@ describe Puppet::HTTP::Client do
173
230
 
174
231
  expect(client.head(uri).body).to eq("abc")
175
232
  end
233
+
234
+ context 'when connecting' do
235
+ it 'uses a specified ssl context' do
236
+ stub_request(:head, uri)
237
+
238
+ other_context = Puppet::SSL::SSLContext.new
239
+
240
+ client.head(uri, ssl_context: other_context)
241
+ end
242
+
243
+ it 'uses the system store' do
244
+ stub_request(:head, uri)
245
+
246
+ client.head(uri, include_system_store: true)
247
+ end
248
+
249
+ it 'raises an HTTPError if both are specified' do
250
+ expect {
251
+ client.head(uri, ssl_context: puppet_context, include_system_store: true)
252
+ }.to raise_error(Puppet::HTTP::HTTPError, /The ssl_context and include_system_store parameters are mutually exclusive/)
253
+ end
254
+ end
176
255
  end
177
256
 
178
257
  context "for PUT requests" do
@@ -211,6 +290,28 @@ describe Puppet::HTTP::Client do
211
290
 
212
291
  client.put(uri, content_type: 'text/plain', body: "hello")
213
292
  end
293
+
294
+ context 'when connecting' do
295
+ it 'uses a specified ssl context' do
296
+ stub_request(:put, uri)
297
+
298
+ other_context = Puppet::SSL::SSLContext.new
299
+
300
+ client.put(uri, content_type: 'text/plain', body: "", ssl_context: other_context)
301
+ end
302
+
303
+ it 'uses the system store' do
304
+ stub_request(:put, uri)
305
+
306
+ client.put(uri, content_type: 'text/plain', body: "", include_system_store: true)
307
+ end
308
+
309
+ it 'raises an HTTPError if both are specified' do
310
+ expect {
311
+ client.put(uri, content_type: 'text/plain', body: "", ssl_context: puppet_context, include_system_store: true)
312
+ }.to raise_error(Puppet::HTTP::HTTPError, /The ssl_context and include_system_store parameters are mutually exclusive/)
313
+ end
314
+ end
214
315
  end
215
316
 
216
317
  context "for POST requests" do
@@ -259,6 +360,28 @@ describe Puppet::HTTP::Client do
259
360
 
260
361
  expect(io.string).to eq("abc")
261
362
  end
363
+
364
+ context 'when connecting' do
365
+ it 'uses a specified ssl context' do
366
+ stub_request(:post, uri)
367
+
368
+ other_context = Puppet::SSL::SSLContext.new
369
+
370
+ client.post(uri, content_type: 'text/plain', body: "", ssl_context: other_context)
371
+ end
372
+
373
+ it 'uses the system store' do
374
+ stub_request(:post, uri)
375
+
376
+ client.post(uri, content_type: 'text/plain', body: "", include_system_store: true)
377
+ end
378
+
379
+ it 'raises an HTTPError if both are specified' do
380
+ expect {
381
+ client.post(uri, content_type: 'text/plain', body: "", ssl_context: puppet_context, include_system_store: true)
382
+ }.to raise_error(Puppet::HTTP::HTTPError, /The ssl_context and include_system_store parameters are mutually exclusive/)
383
+ end
384
+ end
262
385
  end
263
386
 
264
387
  context "for DELETE requests" do
@@ -294,6 +417,28 @@ describe Puppet::HTTP::Client do
294
417
 
295
418
  expect(client.delete(uri).body).to eq("abc")
296
419
  end
420
+
421
+ context 'when connecting' do
422
+ it 'uses a specified ssl context' do
423
+ stub_request(:delete, uri)
424
+
425
+ other_context = Puppet::SSL::SSLContext.new
426
+
427
+ client.delete(uri, ssl_context: other_context)
428
+ end
429
+
430
+ it 'uses the system store' do
431
+ stub_request(:delete, uri)
432
+
433
+ client.delete(uri, include_system_store: true)
434
+ end
435
+
436
+ it 'raises an HTTPError if both are specified' do
437
+ expect {
438
+ client.delete(uri, ssl_context: puppet_context, include_system_store: true)
439
+ }.to raise_error(Puppet::HTTP::HTTPError, /The ssl_context and include_system_store parameters are mutually exclusive/)
440
+ end
441
+ end
297
442
  end
298
443
 
299
444
  context "Basic Auth" do
@@ -539,6 +684,32 @@ describe Puppet::HTTP::Client do
539
684
  }.to raise_error(Puppet::HTTP::ProtocolError, /Failed to parse Retry-After header 'foo' as an integer or RFC 2822 date/)
540
685
  end
541
686
 
687
+ it "should close the connection before sleeping" do
688
+ retry_after('42')
689
+
690
+ site = Puppet::Network::HTTP::Site.from_uri(uri)
691
+
692
+ http1 = Net::HTTP.new(site.host, site.port)
693
+ http1.use_ssl = true
694
+ allow(http1).to receive(:started?).and_return(true)
695
+
696
+ http2 = Net::HTTP.new(site.host, site.port)
697
+ http2.use_ssl = true
698
+ allow(http2).to receive(:started?).and_return(true)
699
+
700
+
701
+ pool = Puppet::Network::HTTP::Pool.new()
702
+ client = Puppet::HTTP::Client.new(pool: pool)
703
+
704
+ # The "with_connection" method is required to yield started connections
705
+ allow(pool).to receive(:with_connection).and_yield(http1).and_yield(http2)
706
+
707
+ expect(http1).to receive(:finish).ordered
708
+ expect(::Kernel).to receive(:sleep).with(42).ordered
709
+
710
+ client.get(uri)
711
+ end
712
+
542
713
  it "should sleep and retry if Retry-After is an Integer" do
543
714
  retry_after('42')
544
715
 
@@ -22,10 +22,11 @@ describe Puppet::HTTP::Resolver do
22
22
  end
23
23
 
24
24
  context 'when resolving using server_list' do
25
+ let(:subject) { Puppet::HTTP::Resolver::ServerList.new(client, server_list_setting: Puppet.settings.setting(:server_list), default_port: 8142, services: Puppet::HTTP::Service::SERVICE_NAMES) }
26
+
25
27
  before :each do
26
28
  Puppet[:server_list] = 'ca.example.com:8141,apple.example.com'
27
29
  end
28
- let(:subject) { Puppet::HTTP::Resolver::ServerList.new(client, server_list_setting: Puppet.settings.setting(:server_list), default_port: 8142, services: [:puppet, :ca]) }
29
30
 
30
31
  it 'returns a service based on the current server_list setting' do
31
32
  stub_request(:get, "https://ca.example.com:8141/status/v1/simple/master").to_return(status: 200)
@@ -43,7 +44,18 @@ describe Puppet::HTTP::Resolver do
43
44
  expect(service.url.to_s).to eq("https://ca.example.com:8141/puppet-ca/v1")
44
45
  end
45
46
 
46
- it 'falls fails if no servers in server_list are accessible' do
47
+ it 'logs unsuccessful HTTP 500 responses' do
48
+ Puppet[:log_level] = "debug"
49
+
50
+ stub_request(:get, "https://ca.example.com:8141/status/v1/simple/master").to_return(status: [500, 'Internal Server Error'])
51
+ stub_request(:get, "https://apple.example.com:8142/status/v1/simple/master").to_return(status: 200)
52
+
53
+ subject.resolve(session, :ca)
54
+
55
+ expect(@logs.map(&:message)).to include(/Puppet server ca.example.com:8141 is unavailable: 500 Internal Server Error/)
56
+ end
57
+
58
+ it 'fails if no servers in server_list are accessible' do
47
59
  stub_request(:get, "https://ca.example.com:8141/status/v1/simple/master").to_return(status: 503)
48
60
  stub_request(:get, "https://apple.example.com:8142/status/v1/simple/master").to_return(status: 503)
49
61
 
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+ require 'puppet/http'
3
+
4
+ describe Puppet::HTTP::Response do
5
+ let(:uri) { URI.parse('https://www.example.com') }
6
+ let(:client) { Puppet::HTTP::Client.new }
7
+
8
+ it "returns the request URL" do
9
+ stub_request(:get, uri)
10
+
11
+ response = client.get(uri)
12
+ expect(response.url).to eq(uri)
13
+ end
14
+
15
+ it "returns the HTTP code" do
16
+ stub_request(:get, uri)
17
+
18
+ response = client.get(uri)
19
+ expect(response.code).to eq(200)
20
+ end
21
+
22
+ it "returns the HTTP reason string" do
23
+ stub_request(:get, uri).to_return(status: [418, "I'm a teapot"])
24
+
25
+ response = client.get(uri)
26
+ expect(response.reason).to eq("I'm a teapot")
27
+ end
28
+
29
+ it "returns the response body" do
30
+ stub_request(:get, uri).to_return(status: 200, body: "I'm the body")
31
+
32
+ response = client.get(uri)
33
+ expect(response.body).to eq("I'm the body")
34
+ end
35
+
36
+ it "streams the response body" do
37
+ stub_request(:get, uri).to_return(status: 200, body: "I'm the streaming body")
38
+
39
+ content = StringIO.new
40
+ client.get(uri) do |response|
41
+ response.read_body do |data|
42
+ content << data
43
+ end
44
+ end
45
+ expect(content.string).to eq("I'm the streaming body")
46
+ end
47
+
48
+ it "raises if a block isn't given when streaming" do
49
+ stub_request(:get, uri).to_return(status: 200, body: "")
50
+
51
+ expect {
52
+ client.get(uri) do |response|
53
+ response.read_body
54
+ end
55
+ }.to raise_error(Puppet::HTTP::HTTPError, %r{Request to https://www.example.com failed after .* seconds: A block is required})
56
+ end
57
+
58
+ it "returns success for all 2xx codes" do
59
+ stub_request(:get, uri).to_return(status: 202)
60
+
61
+ expect(client.get(uri)).to be_success
62
+ end
63
+
64
+ it "returns a header value" do
65
+ stub_request(:get, uri).to_return(status: 200, headers: { 'Content-Encoding' => 'gzip' })
66
+
67
+ expect(client.get(uri)['Content-Encoding']).to eq('gzip')
68
+ end
69
+ end
@@ -5,6 +5,7 @@ require 'puppet/http'
5
5
  describe Puppet::HTTP::Service::Ca do
6
6
  let(:ssl_context) { Puppet::SSL::SSLContext.new }
7
7
  let(:client) { Puppet::HTTP::Client.new(ssl_context: ssl_context) }
8
+ let(:session) { Puppet::HTTP::Session.new(client, []) }
8
9
  let(:subject) { client.create_session.route_to(:ca) }
9
10
 
10
11
  before :each do
@@ -23,15 +24,6 @@ describe Puppet::HTTP::Service::Ca do
23
24
 
24
25
  subject.get_certificate('ca')
25
26
  end
26
-
27
-
28
- it 'includes the X-Puppet-Profiling header when Puppet[:profile] is true' do
29
- stub_request(:get, uri).with(headers: {'X-Puppet-Version' => /./, 'User-Agent' => /./, 'X-Puppet-Profiling' => 'true'})
30
-
31
- Puppet[:profile] = true
32
-
33
- subject.get_certificate('ca')
34
- end
35
27
  end
36
28
 
37
29
  context 'when routing to the CA service' do
@@ -64,6 +56,15 @@ describe Puppet::HTTP::Service::Ca do
64
56
  let(:pem) { cert.to_pem }
65
57
  let(:url) { "https://www.example.com/puppet-ca/v1/certificate/ca" }
66
58
 
59
+ it 'includes headers set via the :http_extra_headers and :profile settings' do
60
+ stub_request(:get, url).with(headers: {'Example-Header' => 'real-thing', 'another' => 'thing', 'X-Puppet-Profiling' => 'true'})
61
+
62
+ Puppet[:http_extra_headers] = 'Example-Header:real-thing,another:thing'
63
+ Puppet[:profile] = true
64
+
65
+ subject.get_certificate('ca')
66
+ end
67
+
67
68
  it 'gets a certificate from the "certificate" endpoint' do
68
69
  stub_request(:get, url).to_return(body: pem)
69
70
 
@@ -94,6 +95,15 @@ describe Puppet::HTTP::Service::Ca do
94
95
  let(:pem) { crl.to_pem }
95
96
  let(:url) { "https://www.example.com/puppet-ca/v1/certificate_revocation_list/ca" }
96
97
 
98
+ it 'includes headers set via the :http_extra_headers and :profile settings' do
99
+ stub_request(:get, url).with(headers: {'Example-Header' => 'real-thing', 'another' => 'thing', 'X-Puppet-Profiling' => 'true'})
100
+
101
+ Puppet[:http_extra_headers] = 'Example-Header:real-thing,another:thing'
102
+ Puppet[:profile] = true
103
+
104
+ subject.get_certificate_revocation_list
105
+ end
106
+
97
107
  it 'gets a CRL from "certificate_revocation_list" endpoint' do
98
108
  stub_request(:get, url).to_return(body: pem)
99
109
 
@@ -136,6 +146,15 @@ describe Puppet::HTTP::Service::Ca do
136
146
  let(:pem) { request.to_pem }
137
147
  let(:url) { "https://www.example.com/puppet-ca/v1/certificate_request/infinity" }
138
148
 
149
+ it 'includes headers set via the :http_extra_headers and :profile settings' do
150
+ stub_request(:put, url).with(headers: {'Example-Header' => 'real-thing', 'another' => 'thing', 'X-Puppet-Profiling' => 'true'})
151
+
152
+ Puppet[:http_extra_headers] = 'Example-Header:real-thing,another:thing'
153
+ Puppet[:profile] = true
154
+
155
+ subject.put_certificate_request('infinity', request)
156
+ end
157
+
139
158
  it 'submits a CSR to the "certificate_request" endpoint' do
140
159
  stub_request(:put, url).with(body: pem, headers: { 'Content-Type' => 'text/plain' })
141
160
 
@@ -12,6 +12,7 @@ describe Puppet::HTTP::Service::Compiler do
12
12
  let(:node) { Puppet::Node.new(certname) }
13
13
  let(:facts) { Puppet::Node::Facts.new(certname) }
14
14
  let(:catalog) { Puppet::Resource::Catalog.new(certname) }
15
+ let(:status) { Puppet::Status.new }
15
16
  let(:formatter) { Puppet::Network::FormatHandler.format(:json) }
16
17
 
17
18
  before :each do
@@ -30,16 +31,7 @@ describe Puppet::HTTP::Service::Compiler do
30
31
  expect(request.headers).to_not include('X-Puppet-Profiling')
31
32
  end.to_return(body: formatter.render(catalog), headers: {'Content-Type' => formatter.mime })
32
33
 
33
- subject.get_catalog(certname, environment: environment, facts: facts)
34
- end
35
-
36
- it 'includes the X-Puppet-Profiling header in requests when Puppet[:profile] is true' do
37
- stub_request(:post, uri).with(headers: {'X-Puppet-Version' => /./, 'User-Agent' => /./, 'X-Puppet-Profiling' => 'true'})
38
- .to_return(body: formatter.render(catalog), headers: {'Content-Type' => formatter.mime })
39
-
40
- Puppet[:profile] = true
41
-
42
- subject.get_catalog(certname, environment: environment, facts: facts)
34
+ subject.post_catalog(certname, environment: environment, facts: facts)
43
35
  end
44
36
  end
45
37
 
@@ -51,7 +43,7 @@ describe Puppet::HTTP::Service::Compiler do
51
43
  stub_request(:post, "https://compiler2.example.com:8141/puppet/v3/catalog/ziggy?environment=testing")
52
44
  .to_return(body: formatter.render(catalog), headers: {'Content-Type' => formatter.mime })
53
45
 
54
- subject.get_catalog(certname, environment: environment, facts: facts)
46
+ subject.post_catalog(certname, environment: environment, facts: facts)
55
47
  end
56
48
  end
57
49
 
@@ -59,12 +51,22 @@ describe Puppet::HTTP::Service::Compiler do
59
51
  let(:uri) { %r{/puppet/v3/catalog/ziggy} }
60
52
  let(:catalog_response) { { body: formatter.render(catalog), headers: {'Content-Type' => formatter.mime } } }
61
53
 
54
+ it 'includes puppet headers set via the :http_extra_headers and :profile settings' do
55
+ stub_request(:post, uri).with(headers: {'Example-Header' => 'real-thing', 'another' => 'thing', 'X-Puppet-Profiling' => 'true'}).
56
+ to_return(body: formatter.render(catalog), headers: {'Content-Type' => formatter.mime })
57
+
58
+ Puppet[:http_extra_headers] = 'Example-Header:real-thing,another:thing'
59
+ Puppet[:profile] = true
60
+
61
+ subject.post_catalog(certname, environment: environment, facts: facts)
62
+ end
63
+
62
64
  it 'submits facts as application/json by default' do
63
65
  stub_request(:post, uri)
64
66
  .with(body: hash_including("facts_format" => /application\/json/))
65
67
  .to_return(**catalog_response)
66
68
 
67
- subject.get_catalog(certname, environment: environment, facts: facts)
69
+ subject.post_catalog(certname, environment: environment, facts: facts)
68
70
  end
69
71
 
70
72
  it 'submits facts as pson if set as the preferred format' do
@@ -74,7 +76,7 @@ describe Puppet::HTTP::Service::Compiler do
74
76
  .with(body: hash_including("facts_format" => /pson/))
75
77
  .to_return(**catalog_response)
76
78
 
77
- subject.get_catalog(certname, environment: environment, facts: facts)
79
+ subject.post_catalog(certname, environment: environment, facts: facts)
78
80
  end
79
81
 
80
82
  it 'includes environment as a query parameter AND in the POST body' do
@@ -83,7 +85,7 @@ describe Puppet::HTTP::Service::Compiler do
83
85
  body: hash_including("environment" => 'outerspace'))
84
86
  .to_return(**catalog_response)
85
87
 
86
- subject.get_catalog(certname, environment: 'outerspace', facts: facts)
88
+ subject.post_catalog(certname, environment: 'outerspace', facts: facts)
87
89
  end
88
90
 
89
91
  it 'includes configured_environment' do
@@ -91,7 +93,7 @@ describe Puppet::HTTP::Service::Compiler do
91
93
  .with(body: hash_including("configured_environment" => 'agent_specified'))
92
94
  .to_return(**catalog_response)
93
95
 
94
- subject.get_catalog(certname, environment: 'production', facts: facts, configured_environment: 'agent_specified')
96
+ subject.post_catalog(certname, environment: 'production', facts: facts, configured_environment: 'agent_specified')
95
97
  end
96
98
 
97
99
  it 'includes transaction_uuid' do
@@ -101,7 +103,7 @@ describe Puppet::HTTP::Service::Compiler do
101
103
  .with(body: hash_including("transaction_uuid" => uuid))
102
104
  .to_return(**catalog_response)
103
105
 
104
- subject.get_catalog(certname, environment: 'production', facts: facts, transaction_uuid: uuid)
106
+ subject.post_catalog(certname, environment: 'production', facts: facts, transaction_uuid: uuid)
105
107
  end
106
108
 
107
109
  it 'includes job_uuid' do
@@ -111,7 +113,7 @@ describe Puppet::HTTP::Service::Compiler do
111
113
  .with(body: hash_including("job_uuid" => uuid))
112
114
  .to_return(**catalog_response)
113
115
 
114
- subject.get_catalog(certname, environment: 'production', facts: facts, job_uuid: uuid)
116
+ subject.post_catalog(certname, environment: 'production', facts: facts, job_uuid: uuid)
115
117
  end
116
118
 
117
119
  it 'includes static_catalog' do
@@ -119,7 +121,7 @@ describe Puppet::HTTP::Service::Compiler do
119
121
  .with(body: hash_including("static_catalog" => "false"))
120
122
  .to_return(**catalog_response)
121
123
 
122
- subject.get_catalog(certname, environment: 'production', facts: facts, static_catalog: false)
124
+ subject.post_catalog(certname, environment: 'production', facts: facts, static_catalog: false)
123
125
  end
124
126
 
125
127
  it 'includes dot-separated list of checksum_types' do
@@ -127,14 +129,14 @@ describe Puppet::HTTP::Service::Compiler do
127
129
  .with(body: hash_including("checksum_type" => "sha256.sha384"))
128
130
  .to_return(**catalog_response)
129
131
 
130
- subject.get_catalog(certname, environment: 'production', facts: facts, checksum_type: %w[sha256 sha384])
132
+ subject.post_catalog(certname, environment: 'production', facts: facts, checksum_type: %w[sha256 sha384])
131
133
  end
132
134
 
133
135
  it 'returns a deserialized catalog' do
134
136
  stub_request(:post, uri)
135
137
  .to_return(**catalog_response)
136
138
 
137
- cat = subject.get_catalog(certname, environment: 'production', facts: facts)
139
+ cat = subject.post_catalog(certname, environment: 'production', facts: facts)
138
140
  expect(cat).to be_a(Puppet::Resource::Catalog)
139
141
  expect(cat.name).to eq(certname)
140
142
  end
@@ -144,7 +146,7 @@ describe Puppet::HTTP::Service::Compiler do
144
146
  .to_return(status: [500, "Server Error"])
145
147
 
146
148
  expect {
147
- subject.get_catalog(certname, environment: 'production', facts: facts)
149
+ subject.post_catalog(certname, environment: 'production', facts: facts)
148
150
  }.to raise_error do |err|
149
151
  expect(err).to be_an_instance_of(Puppet::HTTP::ResponseError)
150
152
  expect(err.message).to eq('Server Error')
@@ -157,7 +159,7 @@ describe Puppet::HTTP::Service::Compiler do
157
159
  .to_return(body: "content-type is missing")
158
160
 
159
161
  expect {
160
- subject.get_catalog(certname, environment: 'production', facts: facts)
162
+ subject.post_catalog(certname, environment: 'production', facts: facts)
161
163
  }.to raise_error(Puppet::HTTP::ProtocolError, /No content type in http response; cannot parse/)
162
164
  end
163
165
 
@@ -166,7 +168,7 @@ describe Puppet::HTTP::Service::Compiler do
166
168
  .to_return(body: "this isn't valid JSON", headers: {'Content-Type' => 'application/json'})
167
169
 
168
170
  expect {
169
- subject.get_catalog(certname, environment: 'production', facts: facts)
171
+ subject.post_catalog(certname, environment: 'production', facts: facts)
170
172
  }.to raise_error(Puppet::HTTP::SerializationError, /Failed to deserialize Puppet::Resource::Catalog from json/)
171
173
  end
172
174
 
@@ -194,7 +196,7 @@ describe Puppet::HTTP::Service::Compiler do
194
196
  .with(body: hash_including("facts" => /#{test_fact[:encoded]}/))
195
197
  .to_return(**catalog_response)
196
198
 
197
- subject.get_catalog(certname, environment: environment, facts: facts)
199
+ subject.post_catalog(certname, environment: environment, facts: facts)
198
200
  end
199
201
  end
200
202
  end
@@ -204,6 +206,16 @@ describe Puppet::HTTP::Service::Compiler do
204
206
  let(:uri) { %r{/puppet/v3/node/ziggy} }
205
207
  let(:node_response) { { body: formatter.render(node), headers: {'Content-Type' => formatter.mime } } }
206
208
 
209
+ it 'includes custom headers set via the :http_extra_headers and :profile settings' do
210
+ stub_request(:get, uri).with(headers: {'Example-Header' => 'real-thing', 'another' => 'thing', 'X-Puppet-Profiling' => 'true'}).
211
+ to_return(**node_response)
212
+
213
+ Puppet[:http_extra_headers] = 'Example-Header:real-thing,another:thing'
214
+ Puppet[:profile] = true
215
+
216
+ subject.get_node(certname, environment: 'production')
217
+ end
218
+
207
219
  it 'includes environment' do
208
220
  stub_request(:get, uri)
209
221
  .with(query: hash_including("environment" => "outerspace"))
@@ -271,9 +283,71 @@ describe Puppet::HTTP::Service::Compiler do
271
283
  end
272
284
  end
273
285
 
286
+ context 'when getting facts' do
287
+ let(:uri) { %r{/puppet/v3/facts/ziggy} }
288
+ let(:facts_response) { { body: formatter.render(facts), headers: {'Content-Type' => formatter.mime } } }
289
+
290
+ it 'includes environment' do
291
+ stub_request(:get, uri)
292
+ .with(query: hash_including("environment" => "outerspace"))
293
+ .to_return(**facts_response)
294
+
295
+ subject.get_facts(certname, environment: 'outerspace')
296
+ end
297
+
298
+ it 'returns a deserialized facts object' do
299
+ stub_request(:get, uri)
300
+ .to_return(**facts_response)
301
+
302
+ n = subject.get_facts(certname, environment: 'production')
303
+ expect(n).to be_a(Puppet::Node::Facts)
304
+ expect(n.name).to eq(certname)
305
+ end
306
+
307
+ it 'raises a response error if unsuccessful' do
308
+ stub_request(:get, uri)
309
+ .to_return(status: [500, "Server Error"])
310
+
311
+ expect {
312
+ subject.get_facts(certname, environment: 'production')
313
+ }.to raise_error do |err|
314
+ expect(err).to be_an_instance_of(Puppet::HTTP::ResponseError)
315
+ expect(err.message).to eq('Server Error')
316
+ expect(err.response.code).to eq(500)
317
+ end
318
+ end
319
+
320
+ it 'raises a protocol error if the content-type header is missing' do
321
+ stub_request(:get, uri)
322
+ .to_return(body: "content-type is missing")
323
+
324
+ expect {
325
+ subject.get_facts(certname, environment: 'production')
326
+ }.to raise_error(Puppet::HTTP::ProtocolError, /No content type in http response; cannot parse/)
327
+ end
328
+
329
+ it 'raises a serialization error if the content is invalid' do
330
+ stub_request(:get, uri)
331
+ .to_return(body: "this isn't valid JSON", headers: {'Content-Type' => 'application/json'})
332
+
333
+ expect {
334
+ subject.get_facts(certname, environment: 'production')
335
+ }.to raise_error(Puppet::HTTP::SerializationError, /Failed to deserialize Puppet::Node::Facts from json/)
336
+ end
337
+ end
338
+
274
339
  context 'when putting facts' do
275
340
  let(:uri) { %r{/puppet/v3/facts/ziggy} }
276
341
 
342
+ it 'includes custom headers set the :http_extra_headers and :profile settings' do
343
+ stub_request(:put, uri).with(headers: {'Example-Header' => 'real-thing', 'another' => 'thing', 'X-Puppet-Profiling' => 'true'})
344
+
345
+ Puppet[:http_extra_headers] = 'Example-Header:real-thing,another:thing'
346
+ Puppet[:profile] = true
347
+
348
+ subject.put_facts(certname, environment: environment, facts: facts)
349
+ end
350
+
277
351
  it 'serializes facts in the body' do
278
352
  facts = Puppet::Node::Facts.new(certname, { 'domain' => 'zork'})
279
353
  Puppet::Node::Facts.indirection.save(facts)
@@ -319,4 +393,57 @@ describe Puppet::HTTP::Service::Compiler do
319
393
  }.to raise_error(Puppet::HTTP::SerializationError, /Failed to serialize Puppet::Node::Facts to json: "\\xE2" from ASCII-8BIT to UTF-8/)
320
394
  end
321
395
  end
396
+
397
+ context 'when getting status' do
398
+ let(:uri) { %r{/puppet/v3/status/ziggy} }
399
+ let(:status_response) { { body: formatter.render(status), headers: {'Content-Type' => formatter.mime } } }
400
+
401
+ it 'always sends production' do
402
+ stub_request(:get, uri)
403
+ .with(query: hash_including("environment" => "production"))
404
+ .to_return(**status_response)
405
+
406
+ subject.get_status(certname)
407
+ end
408
+
409
+ it 'returns a deserialized status' do
410
+ stub_request(:get, uri)
411
+ .to_return(**status_response)
412
+
413
+ s = subject.get_status(certname)
414
+ expect(s).to be_a(Puppet::Status)
415
+ expect(s.status).to eq("is_alive" => true)
416
+ end
417
+
418
+ it 'raises a response error if unsuccessful' do
419
+ stub_request(:get, uri)
420
+ .to_return(status: [500, "Server Error"])
421
+
422
+ expect {
423
+ subject.get_status(certname)
424
+ }.to raise_error do |err|
425
+ expect(err).to be_an_instance_of(Puppet::HTTP::ResponseError)
426
+ expect(err.message).to eq('Server Error')
427
+ expect(err.response.code).to eq(500)
428
+ end
429
+ end
430
+
431
+ it 'raises a protocol error if the content-type header is missing' do
432
+ stub_request(:get, uri)
433
+ .to_return(body: "content-type is missing")
434
+
435
+ expect {
436
+ subject.get_status(certname)
437
+ }.to raise_error(Puppet::HTTP::ProtocolError, /No content type in http response; cannot parse/)
438
+ end
439
+
440
+ it 'raises a serialization error if the content is invalid' do
441
+ stub_request(:get, uri)
442
+ .to_return(body: "this isn't valid JSON", headers: {'Content-Type' => 'application/json'})
443
+
444
+ expect {
445
+ subject.get_status(certname)
446
+ }.to raise_error(Puppet::HTTP::SerializationError, /Failed to deserialize Puppet::Status from json/)
447
+ end
448
+ end
322
449
  end