chef-infra-api 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/lib/chef-api.rb +96 -0
- data/lib/chef-api/aclable.rb +35 -0
- data/lib/chef-api/authentication.rb +300 -0
- data/lib/chef-api/boolean.rb +6 -0
- data/lib/chef-api/configurable.rb +80 -0
- data/lib/chef-api/connection.rb +507 -0
- data/lib/chef-api/defaults.rb +197 -0
- data/lib/chef-api/error_collection.rb +44 -0
- data/lib/chef-api/errors.rb +64 -0
- data/lib/chef-api/multipart.rb +164 -0
- data/lib/chef-api/resource.rb +21 -0
- data/lib/chef-api/resources/base.rb +960 -0
- data/lib/chef-api/resources/client.rb +84 -0
- data/lib/chef-api/resources/collection_proxy.rb +234 -0
- data/lib/chef-api/resources/cookbook.rb +24 -0
- data/lib/chef-api/resources/cookbook_version.rb +23 -0
- data/lib/chef-api/resources/data_bag.rb +136 -0
- data/lib/chef-api/resources/data_bag_item.rb +53 -0
- data/lib/chef-api/resources/environment.rb +16 -0
- data/lib/chef-api/resources/group.rb +16 -0
- data/lib/chef-api/resources/node.rb +20 -0
- data/lib/chef-api/resources/organization.rb +22 -0
- data/lib/chef-api/resources/partial_search.rb +44 -0
- data/lib/chef-api/resources/principal.rb +11 -0
- data/lib/chef-api/resources/role.rb +18 -0
- data/lib/chef-api/resources/search.rb +47 -0
- data/lib/chef-api/resources/user.rb +82 -0
- data/lib/chef-api/schema.rb +150 -0
- data/lib/chef-api/util.rb +119 -0
- data/lib/chef-api/validator.rb +16 -0
- data/lib/chef-api/validators/base.rb +82 -0
- data/lib/chef-api/validators/required.rb +11 -0
- data/lib/chef-api/validators/type.rb +23 -0
- data/lib/chef-api/version.rb +3 -0
- data/templates/errors/abstract_method.erb +5 -0
- data/templates/errors/cannot_regenerate_key.erb +1 -0
- data/templates/errors/chef_api_error.erb +1 -0
- data/templates/errors/file_not_found.erb +1 -0
- data/templates/errors/http_bad_request.erb +3 -0
- data/templates/errors/http_forbidden_request.erb +3 -0
- data/templates/errors/http_gateway_timeout.erb +3 -0
- data/templates/errors/http_method_not_allowed.erb +3 -0
- data/templates/errors/http_not_acceptable.erb +3 -0
- data/templates/errors/http_not_found.erb +3 -0
- data/templates/errors/http_server_unavailable.erb +1 -0
- data/templates/errors/http_unauthorized_request.erb +3 -0
- data/templates/errors/insufficient_file_permissions.erb +1 -0
- data/templates/errors/invalid_resource.erb +1 -0
- data/templates/errors/invalid_validator.erb +1 -0
- data/templates/errors/missing_url_parameter.erb +1 -0
- data/templates/errors/not_a_directory.erb +1 -0
- data/templates/errors/resource_already_exists.erb +1 -0
- data/templates/errors/resource_not_found.erb +1 -0
- data/templates/errors/resource_not_mutable.erb +1 -0
- data/templates/errors/unknown_attribute.erb +1 -0
- metadata +130 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
module ChefAPI
|
2
|
+
#
|
3
|
+
# A re-usable class containing configuration information for the {Connection}.
|
4
|
+
# See {Defaults} for a list of default values.
|
5
|
+
#
|
6
|
+
module Configurable
|
7
|
+
class << self
|
8
|
+
#
|
9
|
+
# The list of configurable keys.
|
10
|
+
#
|
11
|
+
# @return [Array<Symbol>]
|
12
|
+
#
|
13
|
+
def keys
|
14
|
+
@keys ||= [
|
15
|
+
:endpoint,
|
16
|
+
:flavor,
|
17
|
+
:client,
|
18
|
+
:key,
|
19
|
+
:proxy_address,
|
20
|
+
:proxy_password,
|
21
|
+
:proxy_port,
|
22
|
+
:proxy_username,
|
23
|
+
:ssl_pem_file,
|
24
|
+
:ssl_verify,
|
25
|
+
:user_agent,
|
26
|
+
:read_timeout,
|
27
|
+
]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Create one attribute getter and setter for each key.
|
33
|
+
#
|
34
|
+
ChefAPI::Configurable.keys.each do |key|
|
35
|
+
attr_accessor key
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Set the configuration for this config, using a block.
|
40
|
+
#
|
41
|
+
# @example Configure the API endpoint
|
42
|
+
# ChefAPI.configure do |config|
|
43
|
+
# config.endpoint = "http://www.my-ChefAPI-server.com/ChefAPI"
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
def configure
|
47
|
+
yield self
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Reset all configuration options to their default values.
|
52
|
+
#
|
53
|
+
# @example Reset all settings
|
54
|
+
# ChefAPI.reset!
|
55
|
+
#
|
56
|
+
# @return [self]
|
57
|
+
#
|
58
|
+
def reset!
|
59
|
+
ChefAPI::Configurable.keys.each do |key|
|
60
|
+
instance_variable_set(:"@#{key}", Defaults.options[key])
|
61
|
+
end
|
62
|
+
self
|
63
|
+
end
|
64
|
+
alias_method :setup, :reset!
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
#
|
69
|
+
# The list of configurable keys, as an options hash.
|
70
|
+
#
|
71
|
+
# @return [Hash]
|
72
|
+
#
|
73
|
+
def options
|
74
|
+
map = ChefAPI::Configurable.keys.map do |key|
|
75
|
+
[key, instance_variable_get(:"@#{key}")]
|
76
|
+
end
|
77
|
+
Hash[map]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,507 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'openssl'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module ChefAPI
|
7
|
+
#
|
8
|
+
# Connection object for the ChefAPI API.
|
9
|
+
#
|
10
|
+
# @see https://docs.chef.io/api_chef_server.html
|
11
|
+
#
|
12
|
+
class Connection
|
13
|
+
class << self
|
14
|
+
#
|
15
|
+
# @private
|
16
|
+
#
|
17
|
+
# @macro proxy
|
18
|
+
# @method $1
|
19
|
+
# Get the list of $1 for this {Connection}. This method is threadsafe.
|
20
|
+
#
|
21
|
+
# @example Get the $1 from this {Connection} object
|
22
|
+
# connection = ChefAPI::Connection.new('...')
|
23
|
+
# connection.$1 #=> $2(attribute1, attribute2, ...)
|
24
|
+
#
|
25
|
+
# @return [Class<$2>]
|
26
|
+
#
|
27
|
+
def proxy(name, klass)
|
28
|
+
class_eval <<-EOH, __FILE__, __LINE__ + 1
|
29
|
+
def #{name}
|
30
|
+
Thread.current['chefapi.connection'] = self
|
31
|
+
#{klass}
|
32
|
+
end
|
33
|
+
EOH
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
include Logify
|
38
|
+
include ChefAPI::Configurable
|
39
|
+
|
40
|
+
proxy :clients, 'Resource::Client'
|
41
|
+
proxy :cookbooks, 'Resource::Cookbook'
|
42
|
+
proxy :data_bags, 'Resource::DataBag'
|
43
|
+
proxy :data_bag_item, 'Resource::DataBagItem'
|
44
|
+
proxy :environments, 'Resource::Environment'
|
45
|
+
proxy :groups, 'Resource::Group'
|
46
|
+
proxy :nodes, 'Resource::Node'
|
47
|
+
proxy :partial_search, 'Resource::PartialSearch'
|
48
|
+
proxy :principals, 'Resource::Principal'
|
49
|
+
proxy :roles, 'Resource::Role'
|
50
|
+
proxy :search, 'Resource::Search'
|
51
|
+
proxy :users, 'Resource::User'
|
52
|
+
proxy :organizations, 'Resource::Organization'
|
53
|
+
|
54
|
+
#
|
55
|
+
# Create a new ChefAPI Connection with the given options. Any options
|
56
|
+
# given take precedence over the default options.
|
57
|
+
#
|
58
|
+
# @example Create a connection object from a list of options
|
59
|
+
# ChefAPI::Connection.new(
|
60
|
+
# endpoint: 'https://...',
|
61
|
+
# client: 'bacon',
|
62
|
+
# key: '~/.chef/bacon.pem',
|
63
|
+
# )
|
64
|
+
#
|
65
|
+
# @example Create a connection object using a block
|
66
|
+
# ChefAPI::Connection.new do |connection|
|
67
|
+
# connection.endpoint = 'https://...'
|
68
|
+
# connection.client = 'bacon'
|
69
|
+
# connection.key = '~/.chef/bacon.pem'
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# @return [ChefAPI::Connection]
|
73
|
+
#
|
74
|
+
def initialize(options = {})
|
75
|
+
# Use any options given, but fall back to the defaults set on the module
|
76
|
+
ChefAPI::Configurable.keys.each do |key|
|
77
|
+
value = if options[key].nil?
|
78
|
+
ChefAPI.instance_variable_get(:"@#{key}")
|
79
|
+
else
|
80
|
+
options[key]
|
81
|
+
end
|
82
|
+
|
83
|
+
instance_variable_set(:"@#{key}", value)
|
84
|
+
end
|
85
|
+
|
86
|
+
yield self if block_given?
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
# Determine if the given options are the same as ours.
|
91
|
+
#
|
92
|
+
# @return [Boolean]
|
93
|
+
#
|
94
|
+
def same_options?(opts)
|
95
|
+
opts.hash == options.hash
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Make a HTTP GET request
|
100
|
+
#
|
101
|
+
# @param path (see Connection#request)
|
102
|
+
# @param [Hash] params
|
103
|
+
# the list of query params
|
104
|
+
# @param request_options (see Connection#request)
|
105
|
+
#
|
106
|
+
# @raise (see Connection#request)
|
107
|
+
# @return (see Connection#request)
|
108
|
+
#
|
109
|
+
def get(path, params = {}, request_options = {})
|
110
|
+
request(:get, path, params)
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Make a HTTP POST request
|
115
|
+
#
|
116
|
+
# @param path (see Connection#request)
|
117
|
+
# @param [String, #read] data
|
118
|
+
# the body to use for the request
|
119
|
+
# @param [Hash] params
|
120
|
+
# the list of query params
|
121
|
+
# @param request_options (see Connection#request)
|
122
|
+
#
|
123
|
+
# @raise (see Connection#request)
|
124
|
+
# @return (see Connection#request)
|
125
|
+
#
|
126
|
+
def post(path, data, params = {}, request_options = {})
|
127
|
+
request(:post, path, data, params)
|
128
|
+
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# Make a HTTP PUT request
|
132
|
+
#
|
133
|
+
# @param path (see Connection#request)
|
134
|
+
# @param data (see Connection#post)
|
135
|
+
# @param params (see Connection#post)
|
136
|
+
# @param request_options (see Connection#request)
|
137
|
+
#
|
138
|
+
# @raise (see Connection#request)
|
139
|
+
# @return (see Connection#request)
|
140
|
+
#
|
141
|
+
def put(path, data, params = {}, request_options = {})
|
142
|
+
request(:put, path, data, params)
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
# Make a HTTP PATCH request
|
147
|
+
#
|
148
|
+
# @param path (see Connection#request)
|
149
|
+
# @param data (see Connection#post)
|
150
|
+
# @param params (see Connection#post)
|
151
|
+
# @param request_options (see Connection#request)
|
152
|
+
#
|
153
|
+
# @raise (see Connection#request)
|
154
|
+
# @return (see Connection#request)
|
155
|
+
#
|
156
|
+
def patch(path, data, params = {}, request_options = {})
|
157
|
+
request(:patch, path, data, params)
|
158
|
+
end
|
159
|
+
|
160
|
+
#
|
161
|
+
# Make a HTTP DELETE request
|
162
|
+
#
|
163
|
+
# @param path (see Connection#request)
|
164
|
+
# @param params (see Connection#get)
|
165
|
+
# @param request_options (see Connection#request)
|
166
|
+
#
|
167
|
+
# @raise (see Connection#request)
|
168
|
+
# @return (see Connection#request)
|
169
|
+
#
|
170
|
+
def delete(path, params = {}, request_options = {})
|
171
|
+
request(:delete, path, params)
|
172
|
+
end
|
173
|
+
|
174
|
+
#
|
175
|
+
# Make an HTTP request with the given verb, data, params, and headers. If
|
176
|
+
# the response has a return type of JSON, the JSON is automatically parsed
|
177
|
+
# and returned as a hash; otherwise it is returned as a string.
|
178
|
+
#
|
179
|
+
# @raise [Error::HTTPError]
|
180
|
+
# if the request is not an HTTP 200 OK
|
181
|
+
#
|
182
|
+
# @param [Symbol] verb
|
183
|
+
# the lowercase symbol of the HTTP verb (e.g. :get, :delete)
|
184
|
+
# @param [String] path
|
185
|
+
# the absolute or relative path from {Defaults.endpoint} to make the
|
186
|
+
# request against
|
187
|
+
# @param [#read, Hash, nil] data
|
188
|
+
# the data to use (varies based on the +verb+)
|
189
|
+
# @param [Hash] params
|
190
|
+
# the params to use for :patch, :post, :put
|
191
|
+
# @param [Hash] request_options
|
192
|
+
# the list of options/configurables for the actual request
|
193
|
+
#
|
194
|
+
# @option request_options [true, false] :sign (default: +true+)
|
195
|
+
# whether to sign the request using mixlib authentication headers
|
196
|
+
#
|
197
|
+
# @return [String, Hash]
|
198
|
+
# the response body
|
199
|
+
#
|
200
|
+
def request(verb, path, data = {}, params = {}, request_options = {})
|
201
|
+
log.info "#{verb.to_s.upcase} #{path}..."
|
202
|
+
log.debug "Chef flavor: #{flavor.inspect}"
|
203
|
+
|
204
|
+
# Build the URI and request object from the given information
|
205
|
+
if [:delete, :get].include?(verb)
|
206
|
+
uri = build_uri(verb, path, data)
|
207
|
+
else
|
208
|
+
uri = build_uri(verb, path, params)
|
209
|
+
end
|
210
|
+
request = class_for_request(verb).new(uri.request_uri)
|
211
|
+
|
212
|
+
# Add request headers
|
213
|
+
add_request_headers(request)
|
214
|
+
|
215
|
+
# Setup PATCH/POST/PUT
|
216
|
+
if [:patch, :post, :put].include?(verb)
|
217
|
+
if data.respond_to?(:read)
|
218
|
+
log.info "Detected file/io presence"
|
219
|
+
request.body_stream = data
|
220
|
+
elsif data.is_a?(Hash)
|
221
|
+
# If any of the values in the hash are File-like, assume this is a
|
222
|
+
# multi-part post
|
223
|
+
if data.values.any? { |value| value.respond_to?(:read) }
|
224
|
+
log.info "Detected multipart body"
|
225
|
+
|
226
|
+
multipart = Multipart::Body.new(data)
|
227
|
+
|
228
|
+
log.debug "Content-Type: #{multipart.content_type}"
|
229
|
+
log.debug "Content-Length: #{multipart.content_length}"
|
230
|
+
|
231
|
+
request.content_length = multipart.content_length
|
232
|
+
request.content_type = multipart.content_type
|
233
|
+
|
234
|
+
request.body_stream = multipart.stream
|
235
|
+
else
|
236
|
+
log.info "Detected form data"
|
237
|
+
request.form_data = data
|
238
|
+
end
|
239
|
+
else
|
240
|
+
log.info "Detected regular body"
|
241
|
+
request.body = data
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Sign the request
|
246
|
+
if request_options[:sign] == false
|
247
|
+
log.info "Skipping signed header authentication (user requested)..."
|
248
|
+
else
|
249
|
+
add_signing_headers(verb, uri.path, request)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Create the HTTP connection object - since the proxy information defaults
|
253
|
+
# to +nil+, we can just pass it to the initializer method instead of doing
|
254
|
+
# crazy strange conditionals.
|
255
|
+
connection = Net::HTTP.new(uri.host, uri.port,
|
256
|
+
proxy_address, proxy_port, proxy_username, proxy_password)
|
257
|
+
|
258
|
+
# Large or filtered result sets can take a long time to return, so allow
|
259
|
+
# setting a longer read_timeout for our client if we want to make an
|
260
|
+
# expensive request.
|
261
|
+
connection.read_timeout = read_timeout if read_timeout
|
262
|
+
|
263
|
+
# Apply SSL, if applicable
|
264
|
+
if uri.scheme == 'https'
|
265
|
+
# Turn on SSL
|
266
|
+
connection.use_ssl = true
|
267
|
+
|
268
|
+
# Custom pem files, no problem!
|
269
|
+
if ssl_pem_file
|
270
|
+
pem = File.read(ssl_pem_file)
|
271
|
+
connection.cert = OpenSSL::X509::Certificate.new(pem)
|
272
|
+
connection.key = OpenSSL::PKey::RSA.new(pem)
|
273
|
+
connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
274
|
+
end
|
275
|
+
|
276
|
+
# Naughty, naughty, naughty! Don't blame when when someone hops in
|
277
|
+
# and executes a MITM attack!
|
278
|
+
unless ssl_verify
|
279
|
+
log.warn "Disabling SSL verification..."
|
280
|
+
log.warn "Neither ChefAPI nor the maintainers are responsible for " \
|
281
|
+
"damages incurred as a result of disabling SSL verification. " \
|
282
|
+
"Please use this with extreme caution, or consider specifying " \
|
283
|
+
"a custom certificate using `config.ssl_pem_file'."
|
284
|
+
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Create a connection using the block form, which will ensure the socket
|
289
|
+
# is properly closed in the event of an error.
|
290
|
+
connection.start do |http|
|
291
|
+
response = http.request(request)
|
292
|
+
|
293
|
+
log.debug "Raw response:"
|
294
|
+
log.debug response.body
|
295
|
+
|
296
|
+
case response
|
297
|
+
when Net::HTTPRedirection
|
298
|
+
redirect = URI.parse(response['location']).to_s
|
299
|
+
log.debug "Performing HTTP redirect to #{redirect}"
|
300
|
+
request(verb, redirect, data)
|
301
|
+
when Net::HTTPSuccess
|
302
|
+
success(response)
|
303
|
+
else
|
304
|
+
error(response)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
rescue SocketError, Errno::ECONNREFUSED, EOFError
|
308
|
+
log.warn "Unable to reach the Chef Server"
|
309
|
+
raise Error::HTTPServerUnavailable.new
|
310
|
+
end
|
311
|
+
|
312
|
+
#
|
313
|
+
# Construct a URL from the given verb and path. If the request is a GET or
|
314
|
+
# DELETE request, the params are assumed to be query params are are
|
315
|
+
# converted as such using {Connection#to_query_string}.
|
316
|
+
#
|
317
|
+
# If the path is relative, it is merged with the {Defaults.endpoint}
|
318
|
+
# attribute. If the path is absolute, it is converted to a URI object and
|
319
|
+
# returned.
|
320
|
+
#
|
321
|
+
# @param [Symbol] verb
|
322
|
+
# the lowercase HTTP verb (e.g. :+get+)
|
323
|
+
# @param [String] path
|
324
|
+
# the absolute or relative HTTP path (url) to get
|
325
|
+
# @param [Hash] params
|
326
|
+
# the list of params to build the URI with (for GET and DELETE requests)
|
327
|
+
#
|
328
|
+
# @return [URI]
|
329
|
+
#
|
330
|
+
def build_uri(verb, path, params = {})
|
331
|
+
log.info "Building URI..."
|
332
|
+
|
333
|
+
# Add any query string parameters
|
334
|
+
if querystring = to_query_string(params)
|
335
|
+
log.debug "Detected verb deserves a querystring"
|
336
|
+
log.debug "Building querystring using #{params.inspect}"
|
337
|
+
log.debug "Compiled querystring is #{querystring.inspect}"
|
338
|
+
path = [path, querystring].compact.join('?')
|
339
|
+
end
|
340
|
+
|
341
|
+
# Parse the URI
|
342
|
+
uri = URI.parse(path)
|
343
|
+
|
344
|
+
# Don't merge absolute URLs
|
345
|
+
unless uri.absolute?
|
346
|
+
log.debug "Detected URI is relative"
|
347
|
+
log.debug "Appending #{path} to #{endpoint}"
|
348
|
+
uri = URI.parse(File.join(endpoint, path))
|
349
|
+
end
|
350
|
+
|
351
|
+
# Return the URI object
|
352
|
+
uri
|
353
|
+
end
|
354
|
+
|
355
|
+
#
|
356
|
+
# Helper method to get the corresponding +Net::HTTP+ class from the given
|
357
|
+
# HTTP verb.
|
358
|
+
#
|
359
|
+
# @param [#to_s] verb
|
360
|
+
# the HTTP verb to create a class from
|
361
|
+
#
|
362
|
+
# @return [Class]
|
363
|
+
#
|
364
|
+
def class_for_request(verb)
|
365
|
+
Net::HTTP.const_get(verb.to_s.capitalize)
|
366
|
+
end
|
367
|
+
|
368
|
+
#
|
369
|
+
# Convert the given hash to a list of query string parameters. Each key and
|
370
|
+
# value in the hash is URI-escaped for safety.
|
371
|
+
#
|
372
|
+
# @param [Hash] hash
|
373
|
+
# the hash to create the query string from
|
374
|
+
#
|
375
|
+
# @return [String, nil]
|
376
|
+
# the query string as a string, or +nil+ if there are no params
|
377
|
+
#
|
378
|
+
def to_query_string(hash)
|
379
|
+
hash.map do |key, value|
|
380
|
+
"#{URI.escape(key.to_s)}=#{URI.escape(value.to_s)}"
|
381
|
+
end.join('&')[/.+/]
|
382
|
+
end
|
383
|
+
|
384
|
+
private
|
385
|
+
|
386
|
+
#
|
387
|
+
# Parse the response object and manipulate the result based on the given
|
388
|
+
# +Content-Type+ header. For now, this method only parses JSON, but it
|
389
|
+
# could be expanded in the future to accept other content types.
|
390
|
+
#
|
391
|
+
# @param [HTTP::Message] response
|
392
|
+
# the response object from the request
|
393
|
+
#
|
394
|
+
# @return [String, Hash]
|
395
|
+
# the parsed response, as an object
|
396
|
+
#
|
397
|
+
def success(response)
|
398
|
+
log.info "Parsing response as success..."
|
399
|
+
|
400
|
+
case response['Content-Type']
|
401
|
+
when /json/
|
402
|
+
log.debug "Detected response as JSON"
|
403
|
+
log.debug "Parsing response body as JSON"
|
404
|
+
JSON.parse(response.body)
|
405
|
+
else
|
406
|
+
log.debug "Detected response as text/plain"
|
407
|
+
response.body
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
#
|
412
|
+
# Raise a response error, extracting as much information from the server's
|
413
|
+
# response as possible.
|
414
|
+
#
|
415
|
+
# @param [HTTP::Message] response
|
416
|
+
# the response object from the request
|
417
|
+
#
|
418
|
+
def error(response)
|
419
|
+
log.info "Parsing response as error..."
|
420
|
+
|
421
|
+
case response['Content-Type']
|
422
|
+
when /json/
|
423
|
+
log.debug "Detected error response as JSON"
|
424
|
+
log.debug "Parsing error response as JSON"
|
425
|
+
message = JSON.parse(response.body)
|
426
|
+
else
|
427
|
+
log.debug "Detected response as text/plain"
|
428
|
+
message = response.body
|
429
|
+
end
|
430
|
+
|
431
|
+
case response.code.to_i
|
432
|
+
when 400
|
433
|
+
raise Error::HTTPBadRequest.new(message: message)
|
434
|
+
when 401
|
435
|
+
raise Error::HTTPUnauthorizedRequest.new(message: message)
|
436
|
+
when 403
|
437
|
+
raise Error::HTTPForbiddenRequest.new(message: message)
|
438
|
+
when 404
|
439
|
+
raise Error::HTTPNotFound.new(message: message)
|
440
|
+
when 405
|
441
|
+
raise Error::HTTPMethodNotAllowed.new(message: message)
|
442
|
+
when 406
|
443
|
+
raise Error::HTTPNotAcceptable.new(message: message)
|
444
|
+
when 504
|
445
|
+
raise Error::HTTPGatewayTimeout.new(message: message)
|
446
|
+
when 500..600
|
447
|
+
raise Error::HTTPServerUnavailable.new
|
448
|
+
else
|
449
|
+
raise "I got an error #{response.code} that I don't know how to handle!"
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
#
|
454
|
+
# Adds the default headers to the request object.
|
455
|
+
#
|
456
|
+
# @param [Net::HTTP::Request] request
|
457
|
+
#
|
458
|
+
def add_request_headers(request)
|
459
|
+
log.info "Adding request headers..."
|
460
|
+
|
461
|
+
headers = {
|
462
|
+
'Accept' => 'application/json',
|
463
|
+
'Content-Type' => 'application/json',
|
464
|
+
'Connection' => 'keep-alive',
|
465
|
+
'Keep-Alive' => '30',
|
466
|
+
'User-Agent' => user_agent,
|
467
|
+
'X-Chef-Version' => '11.4.0',
|
468
|
+
}
|
469
|
+
|
470
|
+
headers.each do |key, value|
|
471
|
+
log.debug "#{key}: #{value}"
|
472
|
+
request[key] = value
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
#
|
477
|
+
# Create a signed header authentication that can be consumed by
|
478
|
+
# +Mixlib::Authentication+.
|
479
|
+
#
|
480
|
+
# @param [Symbol] verb
|
481
|
+
# the HTTP verb (e.g. +:get+)
|
482
|
+
# @param [String] path
|
483
|
+
# the requested URI path (e.g. +/resources/foo+)
|
484
|
+
# @param [Net::HTTP::Request] request
|
485
|
+
#
|
486
|
+
def add_signing_headers(verb, path, request)
|
487
|
+
log.info "Adding signed header authentication..."
|
488
|
+
|
489
|
+
authentication = Authentication.from_options(
|
490
|
+
user: client,
|
491
|
+
key: key,
|
492
|
+
verb: verb,
|
493
|
+
path: path,
|
494
|
+
body: request.body || request.body_stream,
|
495
|
+
)
|
496
|
+
|
497
|
+
authentication.headers.each do |key, value|
|
498
|
+
log.debug "#{key}: #{value}"
|
499
|
+
request[key] = value
|
500
|
+
end
|
501
|
+
|
502
|
+
if request.body_stream
|
503
|
+
request.body_stream.rewind
|
504
|
+
end
|
505
|
+
end
|
506
|
+
end
|
507
|
+
end
|