chef-api 0.2.0
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/.gitignore +20 -0
- data/.travis.yml +14 -0
- data/Gemfile +12 -0
- data/LICENSE +201 -0
- data/README.md +264 -0
- data/Rakefile +1 -0
- data/chef-api.gemspec +25 -0
- data/lib/chef-api/boolean.rb +6 -0
- data/lib/chef-api/configurable.rb +78 -0
- data/lib/chef-api/connection.rb +466 -0
- data/lib/chef-api/defaults.rb +130 -0
- data/lib/chef-api/error_collection.rb +44 -0
- data/lib/chef-api/errors.rb +35 -0
- data/lib/chef-api/logger.rb +160 -0
- data/lib/chef-api/proxy.rb +72 -0
- data/lib/chef-api/resource.rb +16 -0
- data/lib/chef-api/resources/base.rb +951 -0
- data/lib/chef-api/resources/client.rb +85 -0
- data/lib/chef-api/resources/collection_proxy.rb +217 -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 +35 -0
- data/lib/chef-api/resources/environment.rb +16 -0
- data/lib/chef-api/resources/node.rb +17 -0
- data/lib/chef-api/resources/principal.rb +11 -0
- data/lib/chef-api/resources/role.rb +16 -0
- data/lib/chef-api/resources/user.rb +11 -0
- data/lib/chef-api/schema.rb +112 -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/lib/chef-api.rb +76 -0
- data/locales/en.yml +89 -0
- data/spec/integration/resources/client_spec.rb +8 -0
- data/spec/integration/resources/environment_spec.rb +8 -0
- data/spec/integration/resources/node_spec.rb +8 -0
- data/spec/integration/resources/role_spec.rb +8 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/support/chef_server.rb +115 -0
- data/spec/support/shared/chef_api_resource.rb +91 -0
- data/spec/unit/resources/base_spec.rb +47 -0
- data/spec/unit/resources/client_spec.rb +69 -0
- metadata +128 -0
@@ -0,0 +1,466 @@
|
|
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 http://docs.opscode.com/api_chef_server.html
|
11
|
+
#
|
12
|
+
class Connection
|
13
|
+
class << self
|
14
|
+
#
|
15
|
+
# @private
|
16
|
+
#
|
17
|
+
# @macro proxy
|
18
|
+
# @method $1
|
19
|
+
# Get a proxied collection for +$1+. The proxy automatically injects
|
20
|
+
# the current connection into the $2, providing a very Rubyesque way
|
21
|
+
# for handling multiple connection objects.
|
22
|
+
#
|
23
|
+
# @example Get the $1 from the connection object
|
24
|
+
# connection = ChefAPI::Connection.new('...')
|
25
|
+
# connection.$1 #=> $2 (with the connection object pre-populated)
|
26
|
+
#
|
27
|
+
# @return [ChefAPI::Proxy<$2>]
|
28
|
+
# a collection proxy for the $2
|
29
|
+
#
|
30
|
+
def proxy(name, klass)
|
31
|
+
class_eval <<-EOH, __FILE__, __LINE__ + 1
|
32
|
+
def #{name}
|
33
|
+
@#{name} ||= ChefAPI::Proxy.new(self, #{klass})
|
34
|
+
end
|
35
|
+
EOH
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
include ChefAPI::Configurable
|
40
|
+
include ChefAPI::Logger
|
41
|
+
|
42
|
+
#
|
43
|
+
# Create a new ChefAPI Connection with the given options. Any options
|
44
|
+
# given take precedence over the default options.
|
45
|
+
#
|
46
|
+
# @return [ChefAPI::Connection]
|
47
|
+
#
|
48
|
+
def initialize(options = {})
|
49
|
+
# Use any options given, but fall back to the defaults set on the module
|
50
|
+
ChefAPI::Configurable.keys.each do |key|
|
51
|
+
value = if options[key].nil?
|
52
|
+
ChefAPI.instance_variable_get(:"@#{key}")
|
53
|
+
else
|
54
|
+
options[key]
|
55
|
+
end
|
56
|
+
|
57
|
+
instance_variable_set(:"@#{key}", value)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Determine if the given options are the same as ours.
|
63
|
+
#
|
64
|
+
# @return [Boolean]
|
65
|
+
#
|
66
|
+
def same_options?(opts)
|
67
|
+
opts.hash == options.hash
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Make a HTTP GET request
|
72
|
+
#
|
73
|
+
# @param path (see Connection#request)
|
74
|
+
# @param [Hash] params
|
75
|
+
# the list of query params
|
76
|
+
#
|
77
|
+
# @raise (see Connection#request)
|
78
|
+
# @return (see Connection#request)
|
79
|
+
#
|
80
|
+
def get(path, params = {})
|
81
|
+
request(:get, path, params)
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Make a HTTP POST request
|
86
|
+
#
|
87
|
+
# @param path (see Connection#request)
|
88
|
+
# @param [String, #read] data
|
89
|
+
# the body to use for the request
|
90
|
+
#
|
91
|
+
# @raise (see Connection#request)
|
92
|
+
# @return (see Connection#request)
|
93
|
+
#
|
94
|
+
def post(path, data)
|
95
|
+
request(:post, path, data)
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Make a HTTP PUT request
|
100
|
+
#
|
101
|
+
# @param path (see Connection#request)
|
102
|
+
# @param data (see Connection#post)
|
103
|
+
#
|
104
|
+
# @raise (see Connection#request)
|
105
|
+
# @return (see Connection#request)
|
106
|
+
#
|
107
|
+
def put(path, data)
|
108
|
+
request(:put, path, data)
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# Make a HTTP PATCH request
|
113
|
+
#
|
114
|
+
# @param path (see Connection#request)
|
115
|
+
# @param data (see Connection#post)
|
116
|
+
#
|
117
|
+
# @raise (see Connection#request)
|
118
|
+
# @return (see Connection#request)
|
119
|
+
#
|
120
|
+
def patch(path, data)
|
121
|
+
request(:patch, path, data)
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Make a HTTP DELETE request
|
126
|
+
#
|
127
|
+
# @param path (see Connection#request)
|
128
|
+
# @param params (see Connection#get)
|
129
|
+
#
|
130
|
+
# @raise (see Connection#request)
|
131
|
+
# @return (see Connection#request)
|
132
|
+
#
|
133
|
+
def delete(path, params = {})
|
134
|
+
request(:delete, path, params)
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# Make an HTTP request with the given verb, data, params, and headers. If
|
139
|
+
# the response has a return type of JSON, the JSON is automatically parsed
|
140
|
+
# and returned as a hash; otherwise it is returned as a string.
|
141
|
+
#
|
142
|
+
# @raise [Error::HTTPError]
|
143
|
+
# if the request is not an HTTP 200 OK
|
144
|
+
#
|
145
|
+
# @param [Symbol] verb
|
146
|
+
# the lowercase symbol of the HTTP verb (e.g. :get, :delete)
|
147
|
+
# @param [String] path
|
148
|
+
# the absolute or relative path from {Defaults.endpoint} to make the
|
149
|
+
# request against
|
150
|
+
# @param [#read, Hash, nil] data
|
151
|
+
# the data to use (varies based on the +verb+)
|
152
|
+
# @param [Hash] headers
|
153
|
+
# the list of headers to use
|
154
|
+
#
|
155
|
+
# @return [String, Hash]
|
156
|
+
# the response body
|
157
|
+
#
|
158
|
+
def request(verb, path, data = {})
|
159
|
+
log.info "===> #{verb.to_s.upcase} #{path}..."
|
160
|
+
|
161
|
+
# Build the URI and request object from the given information
|
162
|
+
uri = build_uri(verb, path, data)
|
163
|
+
request = class_for_request(verb).new(uri.request_uri)
|
164
|
+
|
165
|
+
# Add request headers
|
166
|
+
add_request_headers(request)
|
167
|
+
|
168
|
+
# Setup PATCH/POST/PUT
|
169
|
+
if [:patch, :post, :put].include?(verb)
|
170
|
+
if data.respond_to?(:read)
|
171
|
+
request.body_stream = data
|
172
|
+
elsif data.is_a?(Hash)
|
173
|
+
request.form_data = data
|
174
|
+
else
|
175
|
+
request.body = data
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Sign the request
|
180
|
+
add_signing_headers(verb, uri, request, parsed_key)
|
181
|
+
|
182
|
+
# Create the HTTP connection object - since the proxy information defaults
|
183
|
+
# to +nil+, we can just pass it to the initializer method instead of doing
|
184
|
+
# crazy strange conditionals.
|
185
|
+
connection = Net::HTTP.new(uri.host, uri.port,
|
186
|
+
proxy_address, proxy_port, proxy_username, proxy_password)
|
187
|
+
|
188
|
+
# Apply SSL, if applicable
|
189
|
+
if uri.scheme == 'https'
|
190
|
+
# Turn on SSL
|
191
|
+
connection.use_ssl = true
|
192
|
+
|
193
|
+
# Custom pem files, no problem!
|
194
|
+
if ssl_pem_file
|
195
|
+
pem = File.read(ssl_pem_file)
|
196
|
+
connection.cert = OpenSSL::X509::Certificate.new(pem)
|
197
|
+
connection.key = OpenSSL::PKey::RSA.new(pem)
|
198
|
+
connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
199
|
+
end
|
200
|
+
|
201
|
+
# Naughty, naughty, naughty! Don't blame when when someone hops in
|
202
|
+
# and executes a MITM attack!
|
203
|
+
unless ssl_verify
|
204
|
+
log.warn "===> Disabling SSL verification..."
|
205
|
+
log.warn "Neither ChefAPI nor the maintainers are responsible for " \
|
206
|
+
"damanges incurred as a result of disabling SSL verification. " \
|
207
|
+
"Please use this with extreme caution, or consider specifying " \
|
208
|
+
"a custom certificate using `config.ssl_pem_file'."
|
209
|
+
connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Create a connection using the block form, which will ensure the socket
|
214
|
+
# is properly closed in the event of an error.
|
215
|
+
connection.start do |http|
|
216
|
+
response = http.request(request)
|
217
|
+
|
218
|
+
case response
|
219
|
+
when Net::HTTPRedirection
|
220
|
+
redirect = URI.parse(response['location'])
|
221
|
+
log.debug "===> Performing HTTP redirect to #{redirect}"
|
222
|
+
request(verb, redirect, params)
|
223
|
+
when Net::HTTPSuccess
|
224
|
+
success(response)
|
225
|
+
else
|
226
|
+
error(response)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
rescue SocketError, Errno::ECONNREFUSED, EOFError
|
230
|
+
log.warn " Unable to reach the Chef Server"
|
231
|
+
raise Error::HTTPServerUnavailable.new
|
232
|
+
end
|
233
|
+
|
234
|
+
#
|
235
|
+
# Construct a URL from the given verb and path. If the request is a GET or
|
236
|
+
# DELETE request, the params are assumed to be query params are are
|
237
|
+
# converted as such using {Connection#to_query_string}.
|
238
|
+
#
|
239
|
+
# If the path is relative, it is merged with the {Defaults.endpoint}
|
240
|
+
# attribute. If the path is absolute, it is converted to a URI object and
|
241
|
+
# returned.
|
242
|
+
#
|
243
|
+
# @param [Symbol] verb
|
244
|
+
# the lowercase HTTP verb (e.g. :+get+)
|
245
|
+
# @param [String] path
|
246
|
+
# the absolute or relative HTTP path (url) to get
|
247
|
+
# @param [Hash] params
|
248
|
+
# the list of params to build the URI with (for GET and DELETE requests)
|
249
|
+
#
|
250
|
+
# @return [URI]
|
251
|
+
#
|
252
|
+
def build_uri(verb, path, params = {})
|
253
|
+
log.info "===> Building URI..."
|
254
|
+
|
255
|
+
# Add any query string parameters
|
256
|
+
if [:delete, :get].include?(verb)
|
257
|
+
log.debug " Detected verb deserves a querystring"
|
258
|
+
log.debug " Building querystring using #{params.inspect}"
|
259
|
+
path = [path, to_query_string(params)].compact.join('?')
|
260
|
+
end
|
261
|
+
|
262
|
+
# Parse the URI
|
263
|
+
uri = URI.parse(path)
|
264
|
+
|
265
|
+
# Don't merge absolute URLs
|
266
|
+
unless uri.absolute?
|
267
|
+
log.debug " Detected URI is relative"
|
268
|
+
log.debug " Appending #{endpoint} to #{path}"
|
269
|
+
uri = URI.parse(File.join(endpoint, path))
|
270
|
+
end
|
271
|
+
|
272
|
+
# Return the URI object
|
273
|
+
uri
|
274
|
+
end
|
275
|
+
|
276
|
+
#
|
277
|
+
# Helper method to get the corresponding {Net::HTTP} class from the given
|
278
|
+
# HTTP verb.
|
279
|
+
#
|
280
|
+
# @param [#to_s] verb
|
281
|
+
# the HTTP verb to create a class from
|
282
|
+
#
|
283
|
+
# @return [Class]
|
284
|
+
#
|
285
|
+
def class_for_request(verb)
|
286
|
+
Net::HTTP.const_get(verb.to_s.capitalize)
|
287
|
+
end
|
288
|
+
|
289
|
+
#
|
290
|
+
# Convert the given hash to a list of query string parameters. Each key and
|
291
|
+
# value in the hash is URI-escaped for safety.
|
292
|
+
#
|
293
|
+
# @param [Hash] hash
|
294
|
+
# the hash to create the query string from
|
295
|
+
#
|
296
|
+
# @return [String, nil]
|
297
|
+
# the query string as a string, or +nil+ if there are no params
|
298
|
+
#
|
299
|
+
def to_query_string(hash)
|
300
|
+
hash.map do |key, value|
|
301
|
+
"#{URI.escape(key.to_s)}=#{URI.escape(value.to_s)}"
|
302
|
+
end.join('&')[/.+/]
|
303
|
+
end
|
304
|
+
|
305
|
+
private
|
306
|
+
|
307
|
+
#
|
308
|
+
# Parse the given private key. Users can specify the private key as:
|
309
|
+
#
|
310
|
+
# - the path to the key on disk
|
311
|
+
# - the raw string key
|
312
|
+
# - an +OpenSSL::PKey::RSA object+
|
313
|
+
#
|
314
|
+
# Any other implementations are not supported and will likely explode.
|
315
|
+
#
|
316
|
+
# @todo
|
317
|
+
# Handle errors when the file cannot be read due to insufficient
|
318
|
+
# permissions
|
319
|
+
#
|
320
|
+
# @return [OpenSSL::PKey::RSA]
|
321
|
+
# the RSA private key as an OpenSSL object
|
322
|
+
#
|
323
|
+
def parsed_key
|
324
|
+
return @parsed_key if @parsed_key
|
325
|
+
|
326
|
+
log.info "===> Parsing private key..."
|
327
|
+
|
328
|
+
if key.nil?
|
329
|
+
log.warn " No private key given!"
|
330
|
+
raise 'No private key given!'
|
331
|
+
end
|
332
|
+
|
333
|
+
if key.is_a?(OpenSSL::PKey::RSA)
|
334
|
+
log.debug " Detected private key is an OpenSSL Ruby object"
|
335
|
+
@parsed_key = key
|
336
|
+
end
|
337
|
+
|
338
|
+
if key =~ /(.+)\.pem$/ || File.exists?(key)
|
339
|
+
log.debug " Detected private key is the path to a file"
|
340
|
+
contents = File.read(File.expand_path(key))
|
341
|
+
@parsed_key = OpenSSL::PKey::RSA.new(contents)
|
342
|
+
else
|
343
|
+
log.debug " Detected private key was the literal string key"
|
344
|
+
@parsed_key = OpenSSL::PKey::RSA.new(key)
|
345
|
+
end
|
346
|
+
|
347
|
+
@parsed_key
|
348
|
+
end
|
349
|
+
|
350
|
+
#
|
351
|
+
# Parse the response object and manipulate the result based on the given
|
352
|
+
# +Content-Type+ header. For now, this method only parses JSON, but it
|
353
|
+
# could be expanded in the future to accept other content types.
|
354
|
+
#
|
355
|
+
# @param [HTTP::Message] response
|
356
|
+
# the response object from the request
|
357
|
+
#
|
358
|
+
# @return [String, Hash]
|
359
|
+
# the parsed response, as an object
|
360
|
+
#
|
361
|
+
def success(response)
|
362
|
+
log.info "===> Parsing response as success..."
|
363
|
+
|
364
|
+
case response['Content-Type']
|
365
|
+
when 'application/json'
|
366
|
+
log.debug " Detected response as JSON"
|
367
|
+
log.debug " Parsing response body as JSON"
|
368
|
+
JSON.parse(response.body)
|
369
|
+
else
|
370
|
+
log.debug " Detected response as text/plain"
|
371
|
+
response.body
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
#
|
376
|
+
# Raise a response error, extracting as much information from the server's
|
377
|
+
# response as possible.
|
378
|
+
#
|
379
|
+
# @param [HTTP::Message] response
|
380
|
+
# the response object from the request
|
381
|
+
#
|
382
|
+
def error(response)
|
383
|
+
log.info "===> Parsing response as error..."
|
384
|
+
|
385
|
+
case response['Content-Type']
|
386
|
+
when 'application/json'
|
387
|
+
log.debug " Detected error response as JSON"
|
388
|
+
log.debug " Parsing error response as JSON"
|
389
|
+
message = JSON.parse(response.body)['error'].first
|
390
|
+
else
|
391
|
+
log.debug " Detected response as text/plain"
|
392
|
+
message = response.body
|
393
|
+
end
|
394
|
+
|
395
|
+
case response.code.to_i
|
396
|
+
when 400
|
397
|
+
raise Error::HTTPBadRequest.new(message: message)
|
398
|
+
when 401
|
399
|
+
raise Error::HTTPUnauthorizedRequest.new(message: message)
|
400
|
+
when 403
|
401
|
+
raise Error::HTTPForbiddenRequest.new(message: message)
|
402
|
+
when 404
|
403
|
+
raise Error::HTTPNotFound.new(message: message)
|
404
|
+
when 405
|
405
|
+
raise Error::HTTPMethodNotAllowed.new(message: message)
|
406
|
+
when 406
|
407
|
+
raise Error::HTTPNotAcceptable.new(message: message)
|
408
|
+
when 500..600
|
409
|
+
raise Error::HTTPServerUnavailable.new
|
410
|
+
else
|
411
|
+
raise "I got an error #{response.code} that I don't know how to handle!"
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
#
|
416
|
+
# Adds the default headers to the request object.
|
417
|
+
#
|
418
|
+
# @param [Net::HTTP::Request] request
|
419
|
+
#
|
420
|
+
def add_request_headers(request)
|
421
|
+
log.info "===> Adding request headers..."
|
422
|
+
|
423
|
+
headers = {
|
424
|
+
'Accept' => 'application/json',
|
425
|
+
'Content-Type' => 'application/json',
|
426
|
+
'Connection' => 'keep-alive',
|
427
|
+
'Keep-Alive' => '30',
|
428
|
+
'User-Agent' => user_agent,
|
429
|
+
'X-Chef-Version' => '11.4.0',
|
430
|
+
}
|
431
|
+
|
432
|
+
headers.each do |key, value|
|
433
|
+
log.debug " #{key}: #{value}"
|
434
|
+
request[key] = value
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
#
|
439
|
+
# Use mixlib-auth to create a signed header auth.
|
440
|
+
#
|
441
|
+
# @param [Net::HTTP::Request] request
|
442
|
+
#
|
443
|
+
def add_signing_headers(verb, uri, request, key)
|
444
|
+
log.info "===> Adding signed header authentication..."
|
445
|
+
|
446
|
+
unless defined?(Mixlib::Authentication)
|
447
|
+
require 'mixlib/authentication/signedheaderauth'
|
448
|
+
end
|
449
|
+
|
450
|
+
headers = Mixlib::Authentication::SignedHeaderAuth.signing_object(
|
451
|
+
:http_method => verb,
|
452
|
+
:body => request.body || '',
|
453
|
+
:host => "#{uri.host}:#{uri.port}",
|
454
|
+
:path => request.path,
|
455
|
+
:timestamp => Time.now.utc.iso8601,
|
456
|
+
:user_id => client,
|
457
|
+
:file => '',
|
458
|
+
).sign(key)
|
459
|
+
|
460
|
+
headers.each do |key, value|
|
461
|
+
log.debug " #{key}: #{value}"
|
462
|
+
request[key] = value
|
463
|
+
end
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'chef-api/version'
|
2
|
+
|
3
|
+
module ChefAPI
|
4
|
+
module Defaults
|
5
|
+
# Default API endpoint
|
6
|
+
ENDPOINT = 'http://localhost:4000/'.freeze
|
7
|
+
|
8
|
+
# Default User Agent header string
|
9
|
+
USER_AGENT = "ChefAPI Ruby Gem #{ChefAPI::VERSION}".freeze
|
10
|
+
|
11
|
+
class << self
|
12
|
+
#
|
13
|
+
# The list of calculated default options for the configuration.
|
14
|
+
#
|
15
|
+
# @return [Hash]
|
16
|
+
#
|
17
|
+
def options
|
18
|
+
Hash[Configurable.keys.map { |key| [key, send(key)] }]
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# The endpoint where the Chef Server lives. This is equivalent to the
|
23
|
+
# +chef_server_url+ in Chef terminology. If you are using Enterprise
|
24
|
+
# Hosted Chef or Enterprise Chef on premise, this endpoint includes your
|
25
|
+
# organization name, such as:
|
26
|
+
#
|
27
|
+
# https://api.opscode.com/organizations/NAME
|
28
|
+
#
|
29
|
+
# If you are running Open Source Chef Server or Chef Zero, this is just
|
30
|
+
# the URL to your Chef Server instance, such as:
|
31
|
+
#
|
32
|
+
# http://chef.example.com/
|
33
|
+
#
|
34
|
+
# @return [String]
|
35
|
+
#
|
36
|
+
def endpoint
|
37
|
+
ENV['CHEF_API_ENDPOINT'] || ENDPOINT
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# The User Agent header to send along.
|
42
|
+
#
|
43
|
+
# @return [String]
|
44
|
+
#
|
45
|
+
def user_agent
|
46
|
+
ENV['CHEF_API_USER_AGENT'] || USER_AGENT
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# The name of the Chef API client. This is the equivalent of
|
51
|
+
# +client_name+ in Chef terminology. In most cases, this is your Chef
|
52
|
+
# username.
|
53
|
+
#
|
54
|
+
# @return [String, nil]
|
55
|
+
#
|
56
|
+
def client
|
57
|
+
ENV['CHEF_API_CLIENT']
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# The private key to authentication against the Chef Server. This is
|
62
|
+
# equivalent to the +client_key+ in Chef terminology. This value can
|
63
|
+
# be the client key in plain text or a path to the key on disk.
|
64
|
+
#
|
65
|
+
# @return [String, nil]
|
66
|
+
#
|
67
|
+
def key
|
68
|
+
ENV['CHEF_API_KEY']
|
69
|
+
end
|
70
|
+
#
|
71
|
+
# The HTTP Proxy server address as a string
|
72
|
+
#
|
73
|
+
# @return [String, nil]
|
74
|
+
#
|
75
|
+
def proxy_address
|
76
|
+
ENV['CHEF_API_PROXY_ADDRESS']
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# The HTTP Proxy user password as a string
|
81
|
+
#
|
82
|
+
# @return [String, nil]
|
83
|
+
#
|
84
|
+
def proxy_password
|
85
|
+
ENV['CHEF_API_PROXY_PASSWORD']
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# The HTTP Proxy server port as a string
|
90
|
+
#
|
91
|
+
# @return [String, nil]
|
92
|
+
#
|
93
|
+
def proxy_port
|
94
|
+
ENV['CHEF_API_PROXY_PORT']
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# The HTTP Proxy server username as a string
|
99
|
+
#
|
100
|
+
# @return [String, nil]
|
101
|
+
#
|
102
|
+
def proxy_username
|
103
|
+
ENV['CHEF_API_PROXY_USERNAME']
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
# The path to a pem file on disk for use with a custom SSL verification
|
108
|
+
#
|
109
|
+
# @return [String, nil]
|
110
|
+
#
|
111
|
+
def ssl_pem_file
|
112
|
+
ENV['CHEF_API_SSL_PEM_FILE']
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# Verify SSL requests (default: true)
|
117
|
+
#
|
118
|
+
# @return [true, false]
|
119
|
+
#
|
120
|
+
def ssl_verify
|
121
|
+
if ENV['CHEF_API_SSL_VERIFY'].nil?
|
122
|
+
true
|
123
|
+
else
|
124
|
+
%w[t y].include?(ENV['CHEF_API_SSL_VERIFY'].downcase[0])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module ChefAPI
|
2
|
+
#
|
3
|
+
# Private internal class for managing the error collection.
|
4
|
+
#
|
5
|
+
class ErrorCollection < Hash
|
6
|
+
#
|
7
|
+
# The default proc for the hash needs to be an empty Array.
|
8
|
+
#
|
9
|
+
# @return [Proc]
|
10
|
+
#
|
11
|
+
def initialize
|
12
|
+
super { |h, k| h[k] = [] }
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Add a new error to the hash.
|
17
|
+
#
|
18
|
+
# @param [Symbol] key
|
19
|
+
# the attribute key
|
20
|
+
# @param [String] error
|
21
|
+
# the error message to push
|
22
|
+
#
|
23
|
+
# @return [self]
|
24
|
+
#
|
25
|
+
def add(key, error)
|
26
|
+
self[key].push(error)
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Output the full messages for each error. This is useful for displaying
|
32
|
+
# information about validation to the user when something goes wrong.
|
33
|
+
#
|
34
|
+
# @return [Array<String>]
|
35
|
+
#
|
36
|
+
def full_messages
|
37
|
+
self.map do |key, errors|
|
38
|
+
errors.map do |error|
|
39
|
+
"`#{key}' #{error}"
|
40
|
+
end
|
41
|
+
end.flatten
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module ChefAPI
|
2
|
+
module Error
|
3
|
+
class ChefAPIError < StandardError
|
4
|
+
def initialize(options = {})
|
5
|
+
class_name = self.class.to_s.split('::').last
|
6
|
+
error_key = Util.underscore(class_name)
|
7
|
+
|
8
|
+
super I18n.t("chef_api.errors.#{error_key}", options)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class AbstractMethod < ChefAPIError; end
|
13
|
+
class CannotRegenerateKey < ChefAPIError; end
|
14
|
+
class FileNotFound < ChefAPIError; end
|
15
|
+
|
16
|
+
class HTTPError < ChefAPIError; end
|
17
|
+
class HTTPBadRequest < HTTPError; end
|
18
|
+
class HTTPForbiddenRequest < HTTPError; end
|
19
|
+
class HTTPNotAcceptable < HTTPError; end
|
20
|
+
class HTTPNotFound < HTTPError; end
|
21
|
+
class HTTPMethodNotAllowed < HTTPError; end
|
22
|
+
class HTTPServerUnavailable < HTTPError; end
|
23
|
+
|
24
|
+
class HTTPUnauthorizedRequest < ChefAPIError; end
|
25
|
+
class InsufficientFilePermissions < ChefAPIError; end
|
26
|
+
class InvalidResource < ChefAPIError; end
|
27
|
+
class InvalidValidator < ChefAPIError; end
|
28
|
+
class MissingURLParameter < ChefAPIError; end
|
29
|
+
class NotADirectory < ChefAPIError; end
|
30
|
+
class ResourceAlreadyExists < ChefAPIError; end
|
31
|
+
class ResourceNotFound < ChefAPIError; end
|
32
|
+
class ResourceNotMutable < ChefAPIError; end
|
33
|
+
class UnknownAttribute < ChefAPIError; end
|
34
|
+
end
|
35
|
+
end
|