chef-infra-api 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|