puppet 6.15.0 → 6.16.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puppet might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CODEOWNERS +2 -7
- data/Gemfile.lock +17 -14
- data/lib/puppet.rb +32 -8
- data/lib/puppet/agent.rb +18 -4
- data/lib/puppet/application/agent.rb +1 -2
- data/lib/puppet/application/device.rb +1 -1
- data/lib/puppet/application/plugin.rb +1 -0
- data/lib/puppet/application/ssl.rb +1 -1
- data/lib/puppet/configurer.rb +2 -2
- data/lib/puppet/context/trusted_information.rb +14 -8
- data/lib/puppet/daemon.rb +13 -27
- data/lib/puppet/defaults.rb +19 -0
- data/lib/puppet/face/facts.rb +1 -1
- data/lib/puppet/face/help.rb +29 -3
- data/lib/puppet/face/module/search.rb +5 -0
- data/lib/puppet/face/plugin.rb +1 -1
- data/lib/puppet/file_serving/http_metadata.rb +1 -1
- data/lib/puppet/file_system/uniquefile.rb +4 -0
- data/lib/puppet/forge/repository.rb +7 -6
- data/lib/puppet/functions/filter.rb +1 -0
- data/lib/puppet/http/client.rb +22 -11
- data/lib/puppet/http/external_client.rb +0 -6
- data/lib/puppet/indirector/file_content/http.rb +5 -0
- data/lib/puppet/indirector/file_metadata/http.rb +4 -4
- data/lib/puppet/indirector/rest.rb +7 -1
- data/lib/puppet/network/http/compression.rb +7 -0
- data/lib/puppet/network/http/connection.rb +2 -0
- data/lib/puppet/network/http/connection_adapter.rb +182 -0
- data/lib/puppet/network/http/nocache_pool.rb +1 -0
- data/lib/puppet/network/http_pool.rb +2 -2
- data/lib/puppet/pal/catalog_compiler.rb +5 -0
- data/lib/puppet/pal/pal_impl.rb +4 -1
- data/lib/puppet/parser/compiler.rb +28 -25
- data/lib/puppet/parser/functions/filter.rb +1 -0
- data/lib/puppet/provider/package/aix.rb +17 -2
- data/lib/puppet/provider/package/apt.rb +4 -1
- data/lib/puppet/provider/package/dnfmodule.rb +24 -4
- data/lib/puppet/provider/package/pip.rb +60 -37
- data/lib/puppet/provider/package/portage.rb +2 -2
- data/lib/puppet/provider/package/yum.rb +7 -0
- data/lib/puppet/provider/package/zypper.rb +59 -1
- data/lib/puppet/provider/service/systemd.rb +21 -4
- data/lib/puppet/provider/user/useradd.rb +5 -1
- data/lib/puppet/reports/http.rb +5 -3
- data/lib/puppet/runtime.rb +25 -2
- data/lib/puppet/ssl/state_machine.rb +33 -8
- data/lib/puppet/ssl/verifier_adapter.rb +9 -1
- data/lib/puppet/test/test_helper.rb +1 -1
- data/lib/puppet/type/file/source.rb +1 -1
- data/lib/puppet/type/package.rb +16 -1
- data/lib/puppet/type/service.rb +6 -8
- data/lib/puppet/type/user.rb +1 -7
- data/lib/puppet/util/autoload.rb +1 -18
- data/lib/puppet/util/log/destinations.rb +1 -10
- data/lib/puppet/util/package/version/range.rb +4 -1
- data/lib/puppet/util/package/version/range/eq.rb +14 -0
- data/lib/puppet/version.rb +1 -1
- data/locales/puppet.pot +191 -111
- data/man/man5/puppet.conf.5 +21 -2
- data/man/man8/puppet-agent.8 +1 -1
- data/man/man8/puppet-apply.8 +1 -1
- data/man/man8/puppet-catalog.8 +1 -1
- data/man/man8/puppet-config.8 +1 -1
- data/man/man8/puppet-describe.8 +1 -1
- data/man/man8/puppet-device.8 +1 -1
- data/man/man8/puppet-doc.8 +1 -1
- data/man/man8/puppet-epp.8 +1 -1
- data/man/man8/puppet-facts.8 +1 -1
- data/man/man8/puppet-filebucket.8 +1 -1
- data/man/man8/puppet-generate.8 +1 -1
- data/man/man8/puppet-help.8 +6 -3
- data/man/man8/puppet-key.8 +1 -1
- data/man/man8/puppet-lookup.8 +1 -1
- data/man/man8/puppet-man.8 +1 -1
- data/man/man8/puppet-module.8 +4 -1
- data/man/man8/puppet-node.8 +1 -1
- data/man/man8/puppet-parser.8 +1 -1
- data/man/man8/puppet-plugin.8 +1 -1
- data/man/man8/puppet-report.8 +1 -1
- data/man/man8/puppet-resource.8 +1 -1
- data/man/man8/puppet-script.8 +1 -1
- data/man/man8/puppet-ssl.8 +1 -1
- data/man/man8/puppet-status.8 +1 -1
- data/man/man8/puppet.8 +2 -2
- data/spec/fixtures/unit/provider/package/dnfmodule/{dnf-module-list-enabled.txt → dnf-module-list.txt} +6 -0
- data/spec/fixtures/unit/provider/package/zypper/zypper-search-uninstalled.out +13 -0
- data/spec/integration/application/agent_spec.rb +66 -1
- data/spec/integration/application/plugin_spec.rb +23 -0
- data/spec/integration/http/client_spec.rb +6 -1
- data/spec/integration/network/http_pool_spec.rb +56 -0
- data/spec/integration/util/windows/adsi_spec.rb +5 -0
- data/spec/lib/puppet_spec/https.rb +6 -0
- data/spec/unit/agent_spec.rb +47 -1
- data/spec/unit/application/agent_spec.rb +4 -4
- data/spec/unit/context/trusted_information_spec.rb +17 -0
- data/spec/unit/daemon_spec.rb +5 -64
- data/spec/unit/face/module/search_spec.rb +17 -0
- data/spec/unit/file_system/uniquefile_spec.rb +11 -0
- data/spec/unit/http/client_spec.rb +10 -10
- data/spec/unit/http/external_client_spec.rb +9 -9
- data/spec/unit/indirector/catalog/compiler_spec.rb +1 -0
- data/spec/unit/indirector/file_metadata/http_spec.rb +167 -0
- data/spec/unit/indirector/file_metadata/rest_spec.rb +15 -14
- data/spec/unit/indirector/rest_spec.rb +13 -0
- data/spec/unit/network/http/connection_spec.rb +542 -190
- data/spec/unit/network/http/nocache_pool_spec.rb +22 -0
- data/spec/unit/network/http_pool_spec.rb +63 -57
- data/spec/unit/network/http_spec.rb +1 -1
- data/spec/unit/provider/package/aix_spec.rb +29 -0
- data/spec/unit/provider/package/dnfmodule_spec.rb +25 -5
- data/spec/unit/provider/package/pip_spec.rb +42 -16
- data/spec/unit/provider/package/portage_spec.rb +5 -0
- data/spec/unit/provider/package/yum_spec.rb +16 -8
- data/spec/unit/provider/package/zypper_spec.rb +84 -0
- data/spec/unit/provider/service/init_spec.rb +1 -0
- data/spec/unit/provider/service/openbsd_spec.rb +9 -0
- data/spec/unit/provider/service/openwrt_spec.rb +1 -0
- data/spec/unit/provider/service/redhat_spec.rb +9 -0
- data/spec/unit/provider/service/systemd_spec.rb +84 -13
- data/spec/unit/provider/user/useradd_spec.rb +8 -0
- data/spec/unit/puppet_pal_catalog_spec.rb +43 -0
- data/spec/unit/puppet_spec.rb +33 -0
- data/spec/unit/reports/http_spec.rb +1 -1
- data/spec/unit/ssl/state_machine_spec.rb +52 -8
- data/spec/unit/type/service_spec.rb +9 -8
- data/spec/unit/type/user_spec.rb +1 -1
- data/spec/unit/util/autoload_spec.rb +2 -1
- data/spec/unit/util/log/destinations_spec.rb +1 -29
- data/spec/unit/util/package/version/range_spec.rb +22 -1
- data/tasks/manpages.rake +5 -35
- metadata +10 -4
@@ -164,38 +164,38 @@ describe Puppet::HTTP::ExternalClient do
|
|
164
164
|
it "submits credentials for GET requests" do
|
165
165
|
stub_request(:get, uri).with(basic_auth: credentials)
|
166
166
|
|
167
|
-
client.get(uri, options: {user: 'user', password: 'pass'})
|
167
|
+
client.get(uri, options: {basic_auth: {user: 'user', password: 'pass'}})
|
168
168
|
end
|
169
169
|
|
170
170
|
it "submits credentials for POST requests" do
|
171
171
|
stub_request(:post, uri).with(basic_auth: credentials)
|
172
172
|
|
173
|
-
client.post(uri, "", options: {content_type: 'text/plain', user: 'user', password: 'pass'})
|
173
|
+
client.post(uri, "", options: {content_type: 'text/plain', basic_auth: {user: 'user', password: 'pass'}})
|
174
174
|
end
|
175
175
|
|
176
176
|
it "returns response containing access denied" do
|
177
177
|
stub_request(:get, uri).with(basic_auth: credentials).to_return(status: [403, "Ye Shall Not Pass"])
|
178
178
|
|
179
|
-
response = client.get(uri, options: {user: 'user', password: 'pass'})
|
179
|
+
response = client.get(uri, options: { basic_auth: {user: 'user', password: 'pass'}})
|
180
180
|
expect(response.code).to eq(403)
|
181
181
|
expect(response.reason).to eq("Ye Shall Not Pass")
|
182
182
|
expect(response).to_not be_success
|
183
183
|
end
|
184
184
|
|
185
|
-
it '
|
185
|
+
it 'includes basic auth if user is nil' do
|
186
186
|
stub_request(:get, uri).with do |req|
|
187
|
-
expect(req.headers).
|
187
|
+
expect(req.headers).to include('Authorization')
|
188
188
|
end
|
189
189
|
|
190
|
-
client.get(uri, options: {user: nil, password: 'pass'})
|
190
|
+
client.get(uri, options: {basic_auth: {user: nil, password: 'pass'}})
|
191
191
|
end
|
192
192
|
|
193
|
-
it '
|
193
|
+
it 'includes basic auth if password is nil' do
|
194
194
|
stub_request(:get, uri).with do |req|
|
195
|
-
expect(req.headers).
|
195
|
+
expect(req.headers).to include('Authorization')
|
196
196
|
end
|
197
197
|
|
198
|
-
client.get(uri, options: {user: 'user', password: nil})
|
198
|
+
client.get(uri, options: {basic_auth: {user: 'user', password: nil}})
|
199
199
|
end
|
200
200
|
end
|
201
201
|
end
|
@@ -477,6 +477,7 @@ describe Puppet::Resource::Catalog::Compiler do
|
|
477
477
|
end
|
478
478
|
|
479
479
|
it "should add 'pe_serverversion' when PE" do
|
480
|
+
allow(File).to receive(:readable?).and_call_original
|
480
481
|
allow(File).to receive(:readable?).with(pe_version_file).and_return(true)
|
481
482
|
allow(File).to receive(:zero?).with(pe_version_file).and_return(false)
|
482
483
|
allow(File).to receive(:read).and_call_original
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'puppet/indirector/file_metadata'
|
4
|
+
require 'puppet/indirector/file_metadata/http'
|
5
|
+
|
6
|
+
describe Puppet::Indirector::FileMetadata::Http do
|
7
|
+
DEFAULT_HEADERS = {
|
8
|
+
"Cache-Control" => "private, max-age=0",
|
9
|
+
"Connection" => "close",
|
10
|
+
"Content-Encoding" => "gzip",
|
11
|
+
"Content-Type" => "text/html; charset=ISO-8859-1",
|
12
|
+
"Date" => "Fri, 01 May 2020 17:16:00 GMT",
|
13
|
+
"Expires" => "-1",
|
14
|
+
"Server" => "gws"
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
let(:certname) { 'ziggy' }
|
18
|
+
# The model is Puppet:FileServing::Metadata
|
19
|
+
let(:model) { described_class.model }
|
20
|
+
# The http terminus creates instances of HttpMetadata which subclass Metadata
|
21
|
+
let(:metadata) { Puppet::FileServing::HttpMetadata.new(key) }
|
22
|
+
let(:key) { "https://example.com/path/to/file" }
|
23
|
+
# Digest::MD5.base64digest("") => "1B2M2Y8AsgTpgAmY7PhCfg=="
|
24
|
+
let(:content_md5) { {"Content-MD5" => "1B2M2Y8AsgTpgAmY7PhCfg=="} }
|
25
|
+
let(:last_modified) { {"Last-Modified" => "Wed, 01 Jan 2020 08:00:00 GMT"} }
|
26
|
+
|
27
|
+
before :each do
|
28
|
+
described_class.indirection.terminus_class = :http
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when finding" do
|
32
|
+
it "returns http file metadata" do
|
33
|
+
stub_request(:head, key)
|
34
|
+
.to_return(status: 200, headers: DEFAULT_HEADERS)
|
35
|
+
|
36
|
+
result = model.indirection.find(key)
|
37
|
+
expect(result.ftype).to eq('file')
|
38
|
+
expect(result.path).to eq('/dev/null')
|
39
|
+
expect(result.relative_path).to be_nil
|
40
|
+
expect(result.destination).to be_nil
|
41
|
+
expect(result.checksum).to match(%r{mtime})
|
42
|
+
expect(result.owner).to be_nil
|
43
|
+
expect(result.group).to be_nil
|
44
|
+
expect(result.mode).to be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
it "reports an md5 checksum if present in the response" do
|
48
|
+
stub_request(:head, key)
|
49
|
+
.to_return(status: 200, headers: DEFAULT_HEADERS.merge(content_md5))
|
50
|
+
|
51
|
+
result = model.indirection.find(key)
|
52
|
+
expect(result.checksum_type).to eq(:md5)
|
53
|
+
expect(result.checksum).to eq("{md5}d41d8cd98f00b204e9800998ecf8427e")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "reports an mtime checksum if present in the response" do
|
57
|
+
stub_request(:head, key)
|
58
|
+
.to_return(status: 200, headers: DEFAULT_HEADERS.merge(last_modified))
|
59
|
+
|
60
|
+
result = model.indirection.find(key)
|
61
|
+
expect(result.checksum_type).to eq(:mtime)
|
62
|
+
expect(result.checksum).to eq("{mtime}2020-01-01 08:00:00 UTC")
|
63
|
+
end
|
64
|
+
|
65
|
+
it "prefers md5" do
|
66
|
+
stub_request(:head, key)
|
67
|
+
.to_return(status: 200, headers: DEFAULT_HEADERS.merge(content_md5).merge(last_modified))
|
68
|
+
|
69
|
+
result = model.indirection.find(key)
|
70
|
+
expect(result.checksum_type).to eq(:md5)
|
71
|
+
expect(result.checksum).to eq("{md5}d41d8cd98f00b204e9800998ecf8427e")
|
72
|
+
end
|
73
|
+
|
74
|
+
it "prefers mtime when explicitly requested" do
|
75
|
+
stub_request(:head, key)
|
76
|
+
.to_return(status: 200, headers: DEFAULT_HEADERS.merge(content_md5).merge(last_modified))
|
77
|
+
|
78
|
+
result = model.indirection.find(key, checksum_type: :mtime)
|
79
|
+
expect(result.checksum_type).to eq(:mtime)
|
80
|
+
expect(result.checksum).to eq("{mtime}2020-01-01 08:00:00 UTC")
|
81
|
+
end
|
82
|
+
|
83
|
+
it "leniently parses base64" do
|
84
|
+
# Content-MD5 header is missing '==' padding
|
85
|
+
stub_request(:head, key)
|
86
|
+
.to_return(status: 200, headers: DEFAULT_HEADERS.merge("Content-MD5" => "1B2M2Y8AsgTpgAmY7PhCfg"))
|
87
|
+
|
88
|
+
result = model.indirection.find(key)
|
89
|
+
expect(result.checksum_type).to eq(:md5)
|
90
|
+
expect(result.checksum).to eq("{md5}d41d8cd98f00b204e9800998ecf8427e")
|
91
|
+
end
|
92
|
+
|
93
|
+
it "URL encodes special characters" do
|
94
|
+
pending("HTTP terminus doesn't encode the URI before parsing")
|
95
|
+
|
96
|
+
stub_request(:head, %r{/path%20to%20file})
|
97
|
+
|
98
|
+
model.indirection.find('https://example.com/path to file')
|
99
|
+
end
|
100
|
+
|
101
|
+
it "sends query parameters" do
|
102
|
+
stub_request(:head, key).with(query: {'a' => 'b'})
|
103
|
+
|
104
|
+
model.indirection.find("#{key}?a=b")
|
105
|
+
end
|
106
|
+
|
107
|
+
it "returns nil if the content doesn't exist" do
|
108
|
+
stub_request(:head, key).to_return(status: 404)
|
109
|
+
|
110
|
+
expect(model.indirection.find(key)).to be_nil
|
111
|
+
end
|
112
|
+
|
113
|
+
it "returns nil if fail_on_404" do
|
114
|
+
stub_request(:head, key).to_return(status: 404)
|
115
|
+
|
116
|
+
expect(model.indirection.find(key, fail_on_404: true)).to be_nil
|
117
|
+
end
|
118
|
+
|
119
|
+
it "returns nil on HTTP 500" do
|
120
|
+
stub_request(:head, key).to_return(status: 500)
|
121
|
+
|
122
|
+
# this is kind of strange, but it does allow puppet to try
|
123
|
+
# multiple `source => ["URL1", "URL2"]` and use the first
|
124
|
+
# one based on sourceselect
|
125
|
+
expect(model.indirection.find(key)).to be_nil
|
126
|
+
end
|
127
|
+
|
128
|
+
it "accepts all content types" do
|
129
|
+
stub_request(:head, key).with(headers: {'Accept' => '*/*'})
|
130
|
+
|
131
|
+
model.indirection.find(key)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "sets puppet user-agent" do
|
135
|
+
stub_request(:head, key).with(headers: {'User-Agent' => Puppet[:http_user_agent]})
|
136
|
+
|
137
|
+
model.indirection.find(key)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "tries to persist the connection" do
|
141
|
+
# HTTP/1.1 defaults to persistent connections, so check for
|
142
|
+
# the header's absence
|
143
|
+
stub_request(:head, key).with do |request|
|
144
|
+
expect(request.headers).to_not include('Connection')
|
145
|
+
end
|
146
|
+
|
147
|
+
model.indirection.find(key)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "follows redirects" do
|
151
|
+
new_url = "https://example.com/different/path"
|
152
|
+
redirect = { status: 200, headers: { 'Location' => new_url }, body: ""}
|
153
|
+
stub_request(:head, key).to_return(redirect)
|
154
|
+
stub_request(:head, new_url)
|
155
|
+
|
156
|
+
model.indirection.find(key)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context "when searching" do
|
161
|
+
it "raises an error" do
|
162
|
+
expect {
|
163
|
+
model.indirection.search(key)
|
164
|
+
}.to raise_error(Puppet::Error, 'cannot lookup multiple files')
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -6,6 +6,7 @@ require 'puppet/indirector/file_metadata/rest'
|
|
6
6
|
describe Puppet::Indirector::FileMetadata::Rest do
|
7
7
|
let(:certname) { 'ziggy' }
|
8
8
|
let(:formatter) { Puppet::Network::FormatHandler.format(:json) }
|
9
|
+
let(:model) { described_class.model }
|
9
10
|
|
10
11
|
before :each do
|
11
12
|
described_class.indirection.terminus_class = :rest
|
@@ -18,13 +19,13 @@ describe Puppet::Indirector::FileMetadata::Rest do
|
|
18
19
|
context "when finding" do
|
19
20
|
let(:uri) { %r{/puppet/v3/file_metadata/:mount/path/to/file} }
|
20
21
|
let(:key) { "puppet:///:mount/path/to/file" }
|
21
|
-
let(:metadata) {
|
22
|
+
let(:metadata) { model.new('/path/to/file') }
|
22
23
|
|
23
24
|
it "returns file metadata" do
|
24
25
|
stub_request(:get, uri)
|
25
26
|
.to_return(status: 200, **metadata_response(metadata))
|
26
27
|
|
27
|
-
result =
|
28
|
+
result = model.indirection.find(key)
|
28
29
|
expect(result.path).to eq('/path/to/file')
|
29
30
|
end
|
30
31
|
|
@@ -32,20 +33,20 @@ describe Puppet::Indirector::FileMetadata::Rest do
|
|
32
33
|
stub_request(:get, %r{/puppet/v3/file_metadata/:mount/path%20to%20file})
|
33
34
|
.to_return(status: 200, **metadata_response(metadata))
|
34
35
|
|
35
|
-
|
36
|
+
model.indirection.find('puppet:///:mount/path to file')
|
36
37
|
end
|
37
38
|
|
38
39
|
it "returns nil if the content doesn't exist" do
|
39
40
|
stub_request(:get, uri).to_return(status: 404)
|
40
41
|
|
41
|
-
expect(
|
42
|
+
expect(model.indirection.find(key)).to be_nil
|
42
43
|
end
|
43
44
|
|
44
45
|
it "raises if fail_on_404 is true" do
|
45
46
|
stub_request(:get, uri).to_return(status: 404, headers: { 'Content-Type' => 'application/json'}, body: "{}")
|
46
47
|
|
47
48
|
expect {
|
48
|
-
|
49
|
+
model.indirection.find(key, fail_on_404: true)
|
49
50
|
}.to raise_error(Puppet::Error, %r{Find /puppet/v3/file_metadata/:mount/path/to/file resulted in 404 with the message: {}})
|
50
51
|
end
|
51
52
|
|
@@ -53,7 +54,7 @@ describe Puppet::Indirector::FileMetadata::Rest do
|
|
53
54
|
stub_request(:get, uri).to_return(status: 500, headers: { 'Content-Type' => 'application/json'}, body: "{}")
|
54
55
|
|
55
56
|
expect {
|
56
|
-
|
57
|
+
model.indirection.find(key)
|
57
58
|
}.to raise_error(Net::HTTPError, %r{Error 500 on SERVER: })
|
58
59
|
end
|
59
60
|
|
@@ -61,20 +62,20 @@ describe Puppet::Indirector::FileMetadata::Rest do
|
|
61
62
|
stub_request(:get, %r{https://example.com:8140/puppet/v3/file_metadata/:mount/path/to/file})
|
62
63
|
.to_return(status: 200, **metadata_response(metadata))
|
63
64
|
|
64
|
-
|
65
|
+
model.indirection.find("puppet://example.com:8140/:mount/path/to/file")
|
65
66
|
end
|
66
67
|
end
|
67
68
|
|
68
69
|
context "when searching" do
|
69
70
|
let(:uri) { %r{/puppet/v3/file_metadatas/:mount/path/to/dir} }
|
70
71
|
let(:key) { "puppet:///:mount/path/to/dir" }
|
71
|
-
let(:metadatas) { [
|
72
|
+
let(:metadatas) { [model.new('/path/to/dir')] }
|
72
73
|
|
73
74
|
it "returns an array of file metadata" do
|
74
75
|
stub_request(:get, uri)
|
75
76
|
.to_return(status: 200, **metadata_response(metadatas))
|
76
77
|
|
77
|
-
result =
|
78
|
+
result = model.indirection.search(key)
|
78
79
|
expect(result.first.path).to eq('/path/to/dir')
|
79
80
|
end
|
80
81
|
|
@@ -82,26 +83,26 @@ describe Puppet::Indirector::FileMetadata::Rest do
|
|
82
83
|
stub_request(:get, %r{/puppet/v3/file_metadatas/:mount/path%20to%20dir})
|
83
84
|
.to_return(status: 200, **metadata_response(metadatas))
|
84
85
|
|
85
|
-
|
86
|
+
model.indirection.search('puppet:///:mount/path to dir')
|
86
87
|
end
|
87
88
|
|
88
89
|
it "returns an empty array if the metadata doesn't exist" do
|
89
90
|
stub_request(:get, uri).to_return(status: 404)
|
90
91
|
|
91
|
-
expect(
|
92
|
+
expect(model.indirection.search(key)).to eq([])
|
92
93
|
end
|
93
94
|
|
94
95
|
it "returns an empty array if the metadata doesn't exist and fail_on_404 is true" do
|
95
96
|
stub_request(:get, uri).to_return(status: 404, headers: { 'Content-Type' => 'application/json'}, body: "{}")
|
96
97
|
|
97
|
-
expect(
|
98
|
+
expect(model.indirection.search(key, fail_on_404: true)).to eq([])
|
98
99
|
end
|
99
100
|
|
100
101
|
it "raises an error on HTTP 500" do
|
101
102
|
stub_request(:get, uri).to_return(status: 500, headers: { 'Content-Type' => 'application/json'}, body: "{}")
|
102
103
|
|
103
104
|
expect {
|
104
|
-
|
105
|
+
model.indirection.search(key)
|
105
106
|
}.to raise_error(Net::HTTPError, %r{Error 500 on SERVER: })
|
106
107
|
end
|
107
108
|
|
@@ -109,7 +110,7 @@ describe Puppet::Indirector::FileMetadata::Rest do
|
|
109
110
|
stub_request(:get, %r{https://example.com:8140/puppet/v3/file_metadatas/:mount/path/to/dir})
|
110
111
|
.to_return(status: 200, **metadata_response(metadatas))
|
111
112
|
|
112
|
-
|
113
|
+
model.indirection.search("puppet://example.com:8140/:mount/path/to/dir")
|
113
114
|
end
|
114
115
|
end
|
115
116
|
|
@@ -45,6 +45,19 @@ shared_examples_for "a REST terminus method" do |terminus_method|
|
|
45
45
|
|
46
46
|
expect(Puppet[:preferred_serialization_format]).to eq("pson")
|
47
47
|
end
|
48
|
+
|
49
|
+
it "logs a deprecation warning" do
|
50
|
+
terminus.send(terminus_method, request)
|
51
|
+
|
52
|
+
expect(@logs).to include(an_object_having_attributes(level: :warning, message: /Puppet::Indirector::Rest##{terminus_method} is deprecated. Use Puppet::HTTP::Client instead./))
|
53
|
+
end
|
54
|
+
|
55
|
+
it "omits the warning when deprecations are disabled" do
|
56
|
+
Puppet[:disable_warnings] = 'deprecations'
|
57
|
+
terminus.send(terminus_method, request)
|
58
|
+
|
59
|
+
expect(@logs).to eq([])
|
60
|
+
end
|
48
61
|
end
|
49
62
|
|
50
63
|
HTTP_ERROR_CODES.each do |code|
|
@@ -1,296 +1,648 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'puppet/network/http/connection'
|
3
|
+
require 'puppet/network/http/connection_adapter'
|
3
4
|
require 'puppet/test_ca'
|
4
5
|
|
5
6
|
describe Puppet::Network::HTTP::Connection do
|
6
|
-
let
|
7
|
-
let
|
8
|
-
let
|
7
|
+
let(:host) { "me.example.com" }
|
8
|
+
let(:port) { 8140 }
|
9
|
+
let(:path) { '/foo' }
|
10
|
+
let(:url) { "https://#{host}:#{port}#{path}" }
|
9
11
|
|
10
|
-
|
12
|
+
shared_examples_for "an HTTP connection" do |klass, legacy_api|
|
13
|
+
subject { klass.new(host, port, :verify => Puppet::SSL::Validator.no_validator) }
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
15
|
+
context "when providing HTTP connections" do
|
16
|
+
context "when initializing http instances" do
|
17
|
+
it "should return an http instance created with the passed host and port" do
|
18
|
+
conn = klass.new(host, port, :verify => Puppet::SSL::Validator.no_validator)
|
16
19
|
|
17
|
-
|
18
|
-
|
20
|
+
expect(conn.address).to eq(host)
|
21
|
+
expect(conn.port).to eq(port)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should enable ssl on the http instance by default" do
|
25
|
+
conn = klass.new(host, port, :verify => Puppet::SSL::Validator.no_validator)
|
26
|
+
|
27
|
+
expect(conn).to be_use_ssl
|
28
|
+
end
|
29
|
+
|
30
|
+
it "can disable ssl using an option and ignore the verify" do
|
31
|
+
conn = klass.new(host, port, :use_ssl => false)
|
32
|
+
|
33
|
+
expect(conn).to_not be_use_ssl
|
34
|
+
end
|
35
|
+
|
36
|
+
it "can enable ssl using an option" do
|
37
|
+
conn = klass.new(host, port, :use_ssl => true, :verify => Puppet::SSL::Validator.no_validator)
|
38
|
+
|
39
|
+
expect(conn).to be_use_ssl
|
40
|
+
end
|
41
|
+
|
42
|
+
it "ignores the ':verify' option when ssl is disabled" do
|
43
|
+
conn = klass.new(host, port, :use_ssl => false, :verify => Puppet::SSL::Validator.no_validator)
|
44
|
+
|
45
|
+
expect(conn.verifier).to be_nil
|
46
|
+
end
|
47
|
+
|
48
|
+
it "wraps the validator in an adapter" do
|
49
|
+
conn = klass.new(host, port, :verify => Puppet::SSL::Validator.no_validator)
|
50
|
+
|
51
|
+
expect(conn.verifier).to be_a_kind_of(Puppet::SSL::VerifierAdapter)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should raise Puppet::Error when invalid options are specified" do
|
55
|
+
expect { klass.new(host, port, :invalid_option => nil) }.to raise_error(Puppet::Error, 'Unrecognized option(s): :invalid_option')
|
56
|
+
end
|
57
|
+
|
58
|
+
it "accepts a verifier" do
|
59
|
+
verifier = Puppet::SSL::Verifier.new(host, double('ssl_context'))
|
60
|
+
conn = klass.new(host, port, :use_ssl => true, :verifier => verifier)
|
61
|
+
|
62
|
+
expect(conn.verifier).to eq(verifier)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "raises if the wrong verifier class is specified" do
|
66
|
+
expect {
|
67
|
+
klass.new(host, port, :verifier => Puppet::SSL::Validator.default_validator)
|
68
|
+
}.to raise_error(ArgumentError,
|
69
|
+
"Expected an instance of Puppet::SSL::Verifier but was passed a Puppet::SSL::Validator::DefaultValidator")
|
70
|
+
end
|
19
71
|
end
|
72
|
+
end
|
20
73
|
|
21
|
-
|
22
|
-
|
74
|
+
context "for streaming GET requests" do
|
75
|
+
it 'yields the response' do
|
76
|
+
stub_request(:get, url)
|
23
77
|
|
24
|
-
expect
|
78
|
+
expect { |b|
|
79
|
+
subject.request_get('/foo', {}, &b)
|
80
|
+
}.to yield_with_args(Net::HTTPResponse)
|
25
81
|
end
|
26
82
|
|
27
|
-
it "
|
28
|
-
|
83
|
+
it "stringifies keys and encodes values in the query" do
|
84
|
+
stub_request(:get, url).with(query: "foo=bar%3Dbaz")
|
29
85
|
|
30
|
-
|
86
|
+
subject.request_get("#{path}?foo=bar=baz") { |_| }
|
31
87
|
end
|
32
88
|
|
33
|
-
it "
|
34
|
-
|
89
|
+
it "merges custom headers with default ones" do
|
90
|
+
stub_request(:get, url).with(headers: { 'X-Foo' => 'Bar', 'User-Agent' => /./ })
|
35
91
|
|
36
|
-
|
92
|
+
subject.request_get(path, {'X-Foo' => 'Bar'}) { |_| }
|
37
93
|
end
|
38
94
|
|
39
|
-
it "
|
40
|
-
|
95
|
+
it "returns the response" do
|
96
|
+
stub_request(:get, url)
|
41
97
|
|
42
|
-
|
98
|
+
response = subject.request_get(path) { |_| }
|
99
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
100
|
+
expect(response.code).to eq("200")
|
43
101
|
end
|
44
102
|
|
45
|
-
it "
|
46
|
-
|
103
|
+
it "accepts a URL string as the path" do
|
104
|
+
stub_request(:get, url)
|
47
105
|
|
48
|
-
|
106
|
+
response = subject.request_get(url) { |_| }
|
107
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
49
108
|
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context "for streaming head requests" do
|
112
|
+
it 'yields the response when request_head is called' do
|
113
|
+
stub_request(:head, url)
|
50
114
|
|
51
|
-
|
52
|
-
|
115
|
+
expect { |b|
|
116
|
+
subject.request_head('/foo', {}, &b)
|
117
|
+
}.to yield_with_args(Net::HTTPResponse)
|
53
118
|
end
|
54
119
|
|
55
|
-
it "
|
56
|
-
|
57
|
-
conn = Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => true, :verifier => verifier)
|
120
|
+
it "stringifies keys and encodes values in the query" do
|
121
|
+
stub_request(:head, url).with(query: "foo=bar%3Dbaz")
|
58
122
|
|
59
|
-
|
123
|
+
subject.request_head("#{path}?foo=bar=baz") { |_| }
|
60
124
|
end
|
61
125
|
|
62
|
-
it "
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
"Expected an instance of Puppet::SSL::Verifier but was passed a Puppet::SSL::Validator::DefaultValidator")
|
126
|
+
it "merges custom headers with default ones" do
|
127
|
+
stub_request(:head, url).with(headers: { 'X-Foo' => 'Bar', 'User-Agent' => /./ })
|
128
|
+
|
129
|
+
subject.request_head(path, {'X-Foo' => 'Bar'}) { |_| }
|
67
130
|
end
|
68
|
-
end
|
69
|
-
end
|
70
131
|
|
71
|
-
|
72
|
-
|
73
|
-
stub_request(:get, url)
|
132
|
+
it "returns the response" do
|
133
|
+
stub_request(:head, url)
|
74
134
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
135
|
+
response = subject.request_head(path) { |_| }
|
136
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
137
|
+
expect(response.code).to eq("200")
|
138
|
+
end
|
79
139
|
|
80
|
-
|
81
|
-
|
140
|
+
it "accepts a URL string as the path" do
|
141
|
+
stub_request(:head, url)
|
82
142
|
|
83
|
-
|
84
|
-
|
85
|
-
|
143
|
+
response = subject.request_head(url) { |_| }
|
144
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
145
|
+
end
|
86
146
|
end
|
87
147
|
|
88
|
-
|
89
|
-
|
148
|
+
context "for streaming post requests" do
|
149
|
+
it 'yields the response when request_post is called' do
|
150
|
+
stub_request(:post, url)
|
90
151
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
end
|
152
|
+
expect { |b|
|
153
|
+
subject.request_post('/foo', "param: value", &b)
|
154
|
+
}.to yield_with_args(Net::HTTPResponse)
|
155
|
+
end
|
96
156
|
|
97
|
-
|
98
|
-
|
99
|
-
options[:use_ssl] = false
|
100
|
-
options[:verify] = Puppet::SSL::Validator.no_validator
|
101
|
-
Puppet::Network::HTTP::Connection.new(host, port, options)
|
102
|
-
end
|
157
|
+
it "stringifies keys and encodes values in the query" do
|
158
|
+
stub_request(:post, url).with(query: "foo=bar%3Dbaz")
|
103
159
|
|
104
|
-
|
105
|
-
|
106
|
-
end
|
160
|
+
subject.request_post("#{path}?foo=bar=baz", "") { |_| }
|
161
|
+
end
|
107
162
|
|
108
|
-
|
109
|
-
|
110
|
-
stub_request(:get, "http://me.example.com:8140/bar").to_return(status: 200)
|
163
|
+
it "merges custom headers with default ones" do
|
164
|
+
stub_request(:post, url).with(headers: { 'X-Foo' => 'Bar', 'User-Agent' => /./ })
|
111
165
|
|
112
|
-
|
113
|
-
|
166
|
+
subject.request_post(path, "", {'X-Foo' => 'Bar'}) { |_| }
|
167
|
+
end
|
114
168
|
|
115
|
-
|
116
|
-
|
117
|
-
conn.get('/')
|
118
|
-
}.to raise_error(Puppet::Network::HTTP::RedirectionLimitExceededException)
|
119
|
-
end
|
169
|
+
it "returns the response" do
|
170
|
+
stub_request(:post, url)
|
120
171
|
|
121
|
-
|
122
|
-
|
172
|
+
response = subject.request_post(path, "") { |_| }
|
173
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
174
|
+
expect(response.code).to eq("200")
|
175
|
+
end
|
176
|
+
|
177
|
+
it "accepts a URL string as the path" do
|
178
|
+
stub_request(:post, url)
|
123
179
|
|
124
|
-
|
125
|
-
|
180
|
+
response = subject.request_post(url, "") { |_| }
|
181
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
182
|
+
end
|
126
183
|
end
|
127
184
|
|
128
|
-
|
129
|
-
|
130
|
-
|
185
|
+
context "for GET requests" do
|
186
|
+
it "includes default HTTP headers" do
|
187
|
+
stub_request(:get, url).with(headers: {'User-Agent' => /./})
|
131
188
|
|
132
|
-
|
133
|
-
|
134
|
-
end
|
189
|
+
subject.get(path)
|
190
|
+
end
|
135
191
|
|
136
|
-
|
137
|
-
|
138
|
-
stub_request(:get, "http://me.example.com:8140/foo").to_return(redirect_to("http://me.example.com:8140/bar"))
|
139
|
-
stub_request(:get, "http://me.example.com:8140/bar").to_return(redirect_to("http://me.example.com:8140/baz"))
|
140
|
-
stub_request(:get, "http://me.example.com:8140/baz").to_return(redirect_to("http://me.example.com:8140/qux"))
|
192
|
+
it "stringifies keys and encodes values in the query" do
|
193
|
+
stub_request(:get, url).with(query: "foo=bar%3Dbaz")
|
141
194
|
|
142
|
-
|
143
|
-
|
144
|
-
end
|
145
|
-
end
|
195
|
+
subject.get("#{path}?foo=bar=baz")
|
196
|
+
end
|
146
197
|
|
147
|
-
|
148
|
-
|
149
|
-
stub_request(:get, url)
|
150
|
-
.to_return(status: [503, 'Service Unavailable'], headers: {'Retry-After' => datetime}).then
|
151
|
-
.to_return(status: 200)
|
152
|
-
end
|
198
|
+
it "merges custom headers with default ones" do
|
199
|
+
stub_request(:get, url).with(headers: { 'X-Foo' => 'Bar', 'User-Agent' => /./ })
|
153
200
|
|
154
|
-
|
155
|
-
|
201
|
+
subject.get(path, {'X-Foo' => 'Bar'})
|
202
|
+
end
|
156
203
|
|
157
|
-
|
158
|
-
|
159
|
-
|
204
|
+
it "returns the response" do
|
205
|
+
stub_request(:get, url)
|
206
|
+
|
207
|
+
response = subject.get(path)
|
208
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
209
|
+
expect(response.code).to eq("200")
|
210
|
+
end
|
211
|
+
|
212
|
+
it "returns the entire response body" do
|
213
|
+
stub_request(:get, url).to_return(body: "abc")
|
214
|
+
|
215
|
+
response = subject.get(path)
|
216
|
+
expect(response.body).to eq("abc")
|
217
|
+
end
|
160
218
|
|
161
|
-
|
162
|
-
|
219
|
+
it "accepts a URL string as the path" do
|
220
|
+
stub_request(:get, url)
|
163
221
|
|
164
|
-
|
165
|
-
|
222
|
+
response = subject.get(url)
|
223
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
224
|
+
end
|
166
225
|
end
|
167
226
|
|
168
|
-
|
169
|
-
|
227
|
+
context "for HEAD requests" do
|
228
|
+
it "includes default HTTP headers" do
|
229
|
+
stub_request(:head, url).with(headers: {'User-Agent' => /./})
|
170
230
|
|
171
|
-
|
172
|
-
|
173
|
-
allow(http1).to receive(:started?).and_return(true)
|
231
|
+
subject.head(path)
|
232
|
+
end
|
174
233
|
|
175
|
-
|
176
|
-
|
177
|
-
allow(http1).to receive(:started?).and_return(true)
|
234
|
+
it "stringifies keys and encodes values in the query" do
|
235
|
+
stub_request(:head, url).with(query: "foo=bar%3Dbaz")
|
178
236
|
|
179
|
-
|
180
|
-
|
181
|
-
allow(pool).to receive(:with_connection).and_yield(http1).and_yield(http2)
|
237
|
+
subject.head("#{path}?foo=bar=baz")
|
238
|
+
end
|
182
239
|
|
183
|
-
|
184
|
-
|
240
|
+
it "merges custom headers with default ones" do
|
241
|
+
stub_request(:head, url).with(headers: { 'X-Foo' => 'Bar', 'User-Agent' => /./ })
|
185
242
|
|
186
|
-
|
187
|
-
|
243
|
+
subject.head(path, {'X-Foo' => 'Bar'})
|
244
|
+
end
|
245
|
+
|
246
|
+
it "returns the response" do
|
247
|
+
stub_request(:head, url)
|
188
248
|
|
189
|
-
|
190
|
-
|
249
|
+
response = subject.head(path)
|
250
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
251
|
+
expect(response.code).to eq("200")
|
252
|
+
end
|
191
253
|
|
192
|
-
|
254
|
+
it "accepts a URL string as the path" do
|
255
|
+
stub_request(:head, url)
|
193
256
|
|
194
|
-
|
195
|
-
|
257
|
+
response = subject.head(url)
|
258
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
259
|
+
end
|
196
260
|
end
|
197
261
|
|
198
|
-
|
199
|
-
|
262
|
+
context "for PUT requests" do
|
263
|
+
it "includes default HTTP headers" do
|
264
|
+
stub_request(:put, url).with(headers: {'User-Agent' => /./})
|
200
265
|
|
201
|
-
|
202
|
-
|
266
|
+
subject.put(path, "", {'Content-Type' => 'text/plain'})
|
267
|
+
end
|
203
268
|
|
204
|
-
|
269
|
+
it "stringifies keys and encodes values in the query" do
|
270
|
+
stub_request(:put, url).with(query: "foo=bar%3Dbaz")
|
205
271
|
|
206
|
-
|
207
|
-
|
208
|
-
end
|
272
|
+
subject.put("#{path}?foo=bar=baz", "")
|
273
|
+
end
|
209
274
|
|
210
|
-
|
211
|
-
|
275
|
+
it "includes custom headers" do
|
276
|
+
stub_request(:put, url).with(headers: { 'X-Foo' => 'Bar' })
|
212
277
|
|
213
|
-
|
278
|
+
subject.put(path, "", {'X-Foo' => 'Bar', 'Content-Type' => 'text/plain'})
|
279
|
+
end
|
214
280
|
|
215
|
-
|
281
|
+
it "returns the response" do
|
282
|
+
stub_request(:put, url)
|
216
283
|
|
217
|
-
|
218
|
-
|
284
|
+
response = subject.put(path, "", {'Content-Type' => 'text/plain'})
|
285
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
286
|
+
expect(response.code).to eq("200")
|
287
|
+
end
|
288
|
+
|
289
|
+
it "sets content-type for the body" do
|
290
|
+
stub_request(:put, url).with(headers: {"Content-Type" => "text/plain"})
|
219
291
|
|
220
|
-
|
221
|
-
|
292
|
+
subject.put(path, "hello", {'Content-Type' => 'text/plain'})
|
293
|
+
end
|
222
294
|
|
223
|
-
|
295
|
+
it 'sends an empty body' do
|
296
|
+
stub_request(:put, url).with(body: '')
|
224
297
|
|
225
|
-
|
226
|
-
|
227
|
-
|
298
|
+
subject.put(path, nil)
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'defaults content-type to application/x-www-form-urlencoded' do
|
302
|
+
skip("Net::HTTP sends a default content-type header, but it's not visible to webmock") if legacy_api
|
228
303
|
|
229
|
-
|
230
|
-
let(:auth) { { :user => 'user', :password => 'password' } }
|
231
|
-
let(:creds) { [ 'user', 'password'] }
|
304
|
+
stub_request(:put, url).with(headers: {'Content-Type' => 'application/x-www-form-urlencoded'})
|
232
305
|
|
233
|
-
|
234
|
-
|
306
|
+
subject.put(path, '')
|
307
|
+
end
|
308
|
+
|
309
|
+
it "accepts a URL string as the path" do
|
310
|
+
stub_request(:put, url)
|
235
311
|
|
236
|
-
|
312
|
+
response = subject.put(url, '')
|
313
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
314
|
+
end
|
237
315
|
end
|
238
316
|
|
239
|
-
|
240
|
-
|
317
|
+
context "for POST requests" do
|
318
|
+
it "includes default HTTP headers" do
|
319
|
+
stub_request(:post, url).with(headers: {'User-Agent' => /./})
|
320
|
+
|
321
|
+
subject.post(path, "", {'Content-Type' => 'text/plain'})
|
322
|
+
end
|
323
|
+
|
324
|
+
it "stringifies keys and encodes values in the query" do
|
325
|
+
stub_request(:post, url).with(query: "foo=bar%3Dbaz")
|
326
|
+
|
327
|
+
subject.post("#{path}?foo=bar=baz", "", {'Content-Type' => 'text/plain'})
|
328
|
+
end
|
329
|
+
|
330
|
+
it "includes custom headers" do
|
331
|
+
stub_request(:post, url).with(headers: { 'X-Foo' => 'Bar' })
|
332
|
+
|
333
|
+
subject.post(path, "", {'X-Foo' => 'Bar', 'Content-Type' => 'text/plain'})
|
334
|
+
end
|
335
|
+
|
336
|
+
it "returns the response" do
|
337
|
+
stub_request(:post, url)
|
338
|
+
|
339
|
+
response = subject.post(path, "", {'Content-Type' => 'text/plain'})
|
340
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
341
|
+
expect(response.code).to eq("200")
|
342
|
+
end
|
343
|
+
|
344
|
+
it "sets content-type for the body" do
|
345
|
+
stub_request(:post, url).with(headers: {"Content-Type" => "text/plain"})
|
346
|
+
|
347
|
+
subject.post(path, "hello", {'Content-Type' => 'text/plain'})
|
348
|
+
end
|
349
|
+
|
350
|
+
it 'sends an empty body' do
|
351
|
+
stub_request(:post, url).with(body: '')
|
352
|
+
|
353
|
+
subject.post(path, nil)
|
354
|
+
end
|
355
|
+
|
356
|
+
it 'defaults content-type to application/x-www-form-urlencoded' do
|
357
|
+
skip("Net::HTTP sends a default content-type header, but it's not visible to webmock") if legacy_api
|
358
|
+
|
359
|
+
stub_request(:post, url).with(headers: {'Content-Type' => 'application/x-www-form-urlencoded'})
|
360
|
+
|
361
|
+
subject.post(path, "")
|
362
|
+
end
|
241
363
|
|
242
|
-
|
364
|
+
it "accepts a URL string as the path" do
|
365
|
+
stub_request(:post, url)
|
366
|
+
|
367
|
+
response = subject.post(url, '')
|
368
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
369
|
+
end
|
243
370
|
end
|
244
371
|
|
245
|
-
|
246
|
-
|
372
|
+
context "for DELETE requests" do
|
373
|
+
it "includes default HTTP headers" do
|
374
|
+
stub_request(:delete, url).with(headers: {'User-Agent' => /./})
|
375
|
+
|
376
|
+
subject.delete(path)
|
377
|
+
end
|
378
|
+
|
379
|
+
it "merges custom headers with default ones" do
|
380
|
+
stub_request(:delete, url).with(headers: { 'X-Foo' => 'Bar', 'User-Agent' => /./ })
|
381
|
+
|
382
|
+
subject.delete(path, {'X-Foo' => 'Bar'})
|
383
|
+
end
|
384
|
+
|
385
|
+
it "stringifies keys and encodes values in the query" do
|
386
|
+
stub_request(:delete, url).with(query: "foo=bar%3Dbaz")
|
387
|
+
|
388
|
+
subject.delete("#{path}?foo=bar=baz")
|
389
|
+
end
|
390
|
+
|
391
|
+
it "returns the response" do
|
392
|
+
stub_request(:delete, url)
|
393
|
+
|
394
|
+
response = subject.delete(path)
|
395
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
396
|
+
expect(response.code).to eq("200")
|
397
|
+
end
|
398
|
+
|
399
|
+
it "returns the entire response body" do
|
400
|
+
stub_request(:delete, url).to_return(body: "abc")
|
401
|
+
|
402
|
+
expect(subject.delete(path).body).to eq("abc")
|
403
|
+
end
|
404
|
+
|
405
|
+
it "accepts a URL string as the path" do
|
406
|
+
stub_request(:delete, url)
|
247
407
|
|
248
|
-
|
408
|
+
response = subject.delete(url)
|
409
|
+
expect(response).to be_an_instance_of(Net::HTTPOK)
|
410
|
+
end
|
249
411
|
end
|
250
412
|
|
251
|
-
|
252
|
-
|
413
|
+
context "when response is a redirect" do
|
414
|
+
subject { klass }
|
415
|
+
|
416
|
+
def create_connection(options = {})
|
417
|
+
options[:use_ssl] = false
|
418
|
+
options[:verify] = Puppet::SSL::Validator.no_validator
|
419
|
+
subject.new(host, port, options)
|
420
|
+
end
|
421
|
+
|
422
|
+
def redirect_to(url)
|
423
|
+
{ status: 302, headers: { 'Location' => url } }
|
424
|
+
end
|
425
|
+
|
426
|
+
it "should follow the redirect to the final resource location" do
|
427
|
+
stub_request(:get, "http://me.example.com:8140/foo").to_return(redirect_to("http://me.example.com:8140/bar"))
|
428
|
+
stub_request(:get, "http://me.example.com:8140/bar").to_return(status: 200)
|
429
|
+
|
430
|
+
create_connection.get('/foo')
|
431
|
+
end
|
432
|
+
|
433
|
+
def expects_limit_exceeded(conn)
|
434
|
+
expect {
|
435
|
+
conn.get('/')
|
436
|
+
}.to raise_error(Puppet::Network::HTTP::RedirectionLimitExceededException)
|
437
|
+
end
|
438
|
+
|
439
|
+
it "should not follow any redirects when the limit is 0" do
|
440
|
+
stub_request(:get, "http://me.example.com:8140/").to_return(redirect_to("http://me.example.com:8140/foo"))
|
441
|
+
|
442
|
+
conn = create_connection(:redirect_limit => 0)
|
443
|
+
expects_limit_exceeded(conn)
|
444
|
+
end
|
445
|
+
|
446
|
+
it "should follow the redirect once" do
|
447
|
+
stub_request(:get, "http://me.example.com:8140/").to_return(redirect_to("http://me.example.com:8140/foo"))
|
448
|
+
stub_request(:get, "http://me.example.com:8140/foo").to_return(redirect_to("http://me.example.com:8140/bar"))
|
449
|
+
|
450
|
+
conn = create_connection(:redirect_limit => 1)
|
451
|
+
expects_limit_exceeded(conn)
|
452
|
+
end
|
453
|
+
|
454
|
+
it "should raise an exception when the redirect limit is exceeded" do
|
455
|
+
stub_request(:get, "http://me.example.com:8140/").to_return(redirect_to("http://me.example.com:8140/foo"))
|
456
|
+
stub_request(:get, "http://me.example.com:8140/foo").to_return(redirect_to("http://me.example.com:8140/bar"))
|
457
|
+
stub_request(:get, "http://me.example.com:8140/bar").to_return(redirect_to("http://me.example.com:8140/baz"))
|
458
|
+
stub_request(:get, "http://me.example.com:8140/baz").to_return(redirect_to("http://me.example.com:8140/qux"))
|
459
|
+
|
460
|
+
conn = create_connection(:redirect_limit => 3)
|
461
|
+
expects_limit_exceeded(conn)
|
462
|
+
end
|
253
463
|
|
254
|
-
|
464
|
+
it 'raises an exception when the location header is missing' do
|
465
|
+
stub_request(:get, "http://me.example.com:8140/").to_return(status: 302)
|
466
|
+
|
467
|
+
if legacy_api
|
468
|
+
expect {
|
469
|
+
create_connection.get('/')
|
470
|
+
}.to raise_error(URI::InvalidURIError, /bad URI/)
|
471
|
+
else
|
472
|
+
expect {
|
473
|
+
create_connection.get('/')
|
474
|
+
}.to raise_error(Puppet::HTTP::ProtocolError, /Location response header is missing/)
|
475
|
+
end
|
476
|
+
end
|
255
477
|
end
|
256
478
|
|
257
|
-
|
258
|
-
|
479
|
+
context "when response indicates an overloaded server" do
|
480
|
+
def retry_after(datetime)
|
481
|
+
stub_request(:get, url)
|
482
|
+
.to_return(status: [503, 'Service Unavailable'], headers: {'Retry-After' => datetime}).then
|
483
|
+
.to_return(status: 200)
|
484
|
+
end
|
485
|
+
|
486
|
+
it "should return a 503 response if Retry-After is not set" do
|
487
|
+
stub_request(:get, url).to_return(status: [503, 'Service Unavailable'])
|
488
|
+
|
489
|
+
result = subject.get('/foo')
|
490
|
+
expect(result.code).to eq("503")
|
491
|
+
end
|
492
|
+
|
493
|
+
it "should return a 503 response if Retry-After is not convertible to an Integer or RFC 2822 Date" do
|
494
|
+
retry_after('foo')
|
495
|
+
|
496
|
+
if legacy_api
|
497
|
+
result = subject.get('/foo')
|
498
|
+
expect(result.code).to eq("503")
|
499
|
+
else
|
500
|
+
expect {
|
501
|
+
subject.get('/foo')
|
502
|
+
}.to raise_error(Puppet::HTTP::ProtocolError, /Failed to parse Retry-After header 'foo'/)
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
it "should close the connection before sleeping" do
|
507
|
+
retry_after('42')
|
508
|
+
|
509
|
+
http1 = Net::HTTP.new(host, port)
|
510
|
+
http1.use_ssl = true
|
511
|
+
allow(http1).to receive(:started?).and_return(true)
|
512
|
+
|
513
|
+
http2 = Net::HTTP.new(host, port)
|
514
|
+
http2.use_ssl = true
|
515
|
+
allow(http1).to receive(:started?).and_return(true)
|
516
|
+
|
517
|
+
# The "with_connection" method is required to yield started connections
|
518
|
+
pool = if legacy_api
|
519
|
+
Puppet.lookup(:http_pool)
|
520
|
+
else
|
521
|
+
Puppet.runtime[:http].pool
|
522
|
+
end
|
259
523
|
|
260
|
-
|
524
|
+
allow(pool).to receive(:with_connection).and_yield(http1).and_yield(http2)
|
525
|
+
|
526
|
+
expect(http1).to receive(:finish).ordered
|
527
|
+
expect(::Kernel).to receive(:sleep).with(42).ordered
|
528
|
+
|
529
|
+
subject.get('/foo')
|
530
|
+
end
|
531
|
+
|
532
|
+
it "should sleep and retry if Retry-After is an Integer" do
|
533
|
+
retry_after('42')
|
534
|
+
|
535
|
+
expect(::Kernel).to receive(:sleep).with(42)
|
536
|
+
|
537
|
+
result = subject.get('/foo')
|
538
|
+
expect(result.code).to eq("200")
|
539
|
+
end
|
540
|
+
|
541
|
+
it "should sleep and retry if Retry-After is an RFC 2822 Date" do
|
542
|
+
retry_after('Wed, 13 Apr 2005 15:18:05 GMT')
|
543
|
+
|
544
|
+
now = DateTime.new(2005, 4, 13, 8, 17, 5, '-07:00')
|
545
|
+
allow(DateTime).to receive(:now).and_return(now)
|
546
|
+
|
547
|
+
expect(::Kernel).to receive(:sleep).with(60)
|
548
|
+
|
549
|
+
result = subject.get('/foo')
|
550
|
+
expect(result.code).to eq("200")
|
551
|
+
end
|
552
|
+
|
553
|
+
it "should sleep for no more than the Puppet runinterval" do
|
554
|
+
retry_after('60')
|
555
|
+
|
556
|
+
Puppet[:runinterval] = 30
|
557
|
+
|
558
|
+
expect(::Kernel).to receive(:sleep).with(30)
|
559
|
+
|
560
|
+
subject.get('/foo')
|
561
|
+
end
|
562
|
+
|
563
|
+
it "should sleep for 0 seconds if the RFC 2822 date has past" do
|
564
|
+
retry_after('Wed, 13 Apr 2005 15:18:05 GMT')
|
565
|
+
|
566
|
+
expect(::Kernel).to receive(:sleep).with(0)
|
567
|
+
|
568
|
+
subject.get('/foo')
|
569
|
+
end
|
261
570
|
end
|
262
|
-
end
|
263
571
|
|
264
|
-
|
265
|
-
|
266
|
-
|
572
|
+
context "basic auth" do
|
573
|
+
let(:auth) { { :user => 'user', :password => 'password' } }
|
574
|
+
let(:creds) { [ 'user', 'password'] }
|
267
575
|
|
268
|
-
|
269
|
-
|
576
|
+
it "is allowed in get requests" do
|
577
|
+
stub_request(:get, url).with(basic_auth: creds)
|
270
578
|
|
271
|
-
|
272
|
-
|
273
|
-
generic_error = Net::HTTPError.new('generic error', double("response"))
|
274
|
-
stub_request(:get, url).to_raise(generic_error)
|
579
|
+
subject.get('/foo', nil, :basic_auth => auth)
|
580
|
+
end
|
275
581
|
|
276
|
-
|
277
|
-
|
582
|
+
it "is allowed in post requests" do
|
583
|
+
stub_request(:post, url).with(basic_auth: creds)
|
584
|
+
|
585
|
+
subject.post('/foo', 'data', nil, :basic_auth => auth)
|
586
|
+
end
|
587
|
+
|
588
|
+
it "is allowed in head requests" do
|
589
|
+
stub_request(:head, url).with(basic_auth: creds)
|
590
|
+
|
591
|
+
subject.head('/foo', nil, :basic_auth => auth)
|
592
|
+
end
|
593
|
+
|
594
|
+
it "is allowed in delete requests" do
|
595
|
+
stub_request(:delete, url).with(basic_auth: creds)
|
596
|
+
|
597
|
+
subject.delete('/foo', nil, :basic_auth => auth)
|
598
|
+
end
|
599
|
+
|
600
|
+
it "is allowed in put requests" do
|
601
|
+
stub_request(:put, url).with(basic_auth: creds)
|
602
|
+
|
603
|
+
subject.put('/foo', 'data', nil, :basic_auth => auth)
|
604
|
+
end
|
278
605
|
end
|
279
606
|
|
280
|
-
it "
|
281
|
-
|
282
|
-
stub_request(:get, url).
|
607
|
+
it "sets HTTP User-Agent header" do
|
608
|
+
puppet_ua = "Puppet/#{Puppet.version} Ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_PLATFORM})"
|
609
|
+
stub_request(:get, url).with(headers: { 'User-Agent' => puppet_ua })
|
283
610
|
|
284
|
-
|
285
|
-
expect { subject.get('/foo') }.to raise_error(timeout_error)
|
611
|
+
subject.get('/foo')
|
286
612
|
end
|
287
613
|
|
288
|
-
|
289
|
-
|
290
|
-
|
614
|
+
describe 'connection request errors' do
|
615
|
+
it "logs and raises generic http errors" do
|
616
|
+
generic_error = Net::HTTPError.new('generic error', double("response"))
|
617
|
+
stub_request(:get, url).to_raise(generic_error)
|
291
618
|
|
292
|
-
|
293
|
-
|
619
|
+
expect(Puppet).to receive(:log_exception).with(anything, /^.*failed.*: generic error$/)
|
620
|
+
expect { subject.get('/foo') }.to raise_error(generic_error)
|
621
|
+
end
|
622
|
+
|
623
|
+
it "logs and raises timeout errors" do
|
624
|
+
timeout_error = Net::OpenTimeout.new
|
625
|
+
stub_request(:get, url).to_raise(timeout_error)
|
626
|
+
|
627
|
+
expect(Puppet).to receive(:log_exception).with(anything, /^.*timed out .*after .* seconds/)
|
628
|
+
expect { subject.get('/foo') }.to raise_error(timeout_error)
|
629
|
+
end
|
630
|
+
|
631
|
+
it "logs and raises eof errors" do
|
632
|
+
eof_error = EOFError
|
633
|
+
stub_request(:get, url).to_raise(eof_error)
|
634
|
+
|
635
|
+
expect(Puppet).to receive(:log_exception).with(anything, /^.*interrupted after .* seconds$/)
|
636
|
+
expect { subject.get('/foo') }.to raise_error(eof_error)
|
637
|
+
end
|
294
638
|
end
|
295
639
|
end
|
640
|
+
|
641
|
+
describe Puppet::Network::HTTP::Connection do
|
642
|
+
it_behaves_like "an HTTP connection", described_class, true
|
643
|
+
end
|
644
|
+
|
645
|
+
describe Puppet::Network::HTTP::ConnectionAdapter do
|
646
|
+
it_behaves_like "an HTTP connection", described_class, false
|
647
|
+
end
|
296
648
|
end
|