Class: ChefAPI::Connection

Inherits:
Object
  • Object
show all
Includes:
Configurable, Logify
Defined in:
lib/chef-api/connection.rb

Overview

Connection object for the ChefAPI API.

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Configurable

#configure, keys, #reset!

Constructor Details

#initialize(options = {}) {|_self| ... } ⇒ ChefAPI::Connection

Create a new ChefAPI Connection with the given options. Any options given take precedence over the default options.

Examples:

Create a connection object from a list of options

ChefAPI::Connection.new(
  endpoint: 'https://...',
  client:   'bacon',
  key:      '~/.chef/bacon.pem',
)

Create a connection object using a block

ChefAPI::Connection.new do |connection|
  connection.endpoint = 'https://...'
  connection.client   = 'bacon'
  connection.key      = '~/.chef/bacon.pem'
end

Yields:

  • (_self)

Yield Parameters:


71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/chef-api/connection.rb', line 71

def initialize(options = {})
  # Use any options given, but fall back to the defaults set on the module
  ChefAPI::Configurable.keys.each do |key|
    value = if options[key].nil?
      ChefAPI.instance_variable_get(:"@#{key}")
    else
      options[key]
    end

    instance_variable_set(:"@#{key}", value)
  end

  yield self if block_given?
end

Class Method Details

.proxy(name, klass) ⇒ Object


27
28
29
30
31
32
33
34
# File 'lib/chef-api/connection.rb', line 27

def proxy(name, klass)
  class_eval <<-EOH, __FILE__, __LINE__ + 1
    def #{name}
      Thread.current['chefapi.connection'] = self
      #{klass}
    end
  EOH
end

Instance Method Details

#build_uri(verb, path, params = {}) ⇒ URI

Construct a URL from the given verb and path. If the request is a GET or DELETE request, the params are assumed to be query params are are converted as such using #to_query_string.

If the path is relative, it is merged with the Defaults.endpoint attribute. If the path is absolute, it is converted to a URI object and returned.

Parameters:

  • verb (Symbol)

    the lowercase HTTP verb (e.g. :get)

  • path (String)

    the absolute or relative HTTP path (url) to get

  • params (Hash) (defaults to: {})

    the list of params to build the URI with (for GET and DELETE requests)

Returns:

  • (URI)

322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/chef-api/connection.rb', line 322

def build_uri(verb, path, params = {})
  log.info  "Building URI..."

  # Add any query string parameters
  if querystring = to_query_string(params)
    log.debug "Detected verb deserves a querystring"
    log.debug "Building querystring using #{params.inspect}"
    log.debug "Compiled querystring is #{querystring.inspect}"
    path = [path, querystring].compact.join('?')
  end

  # Parse the URI
  uri = URI.parse(path)

  # Don't merge absolute URLs
  unless uri.absolute?
    log.debug "Detected URI is relative"
    log.debug "Appending #{path} to #{endpoint}"
    uri = URI.parse(File.join(endpoint, path))
  end

  # Return the URI object
  uri
end

#class_for_request(verb) ⇒ Class

Helper method to get the corresponding Net::HTTP class from the given HTTP verb.

Parameters:

  • verb (#to_s)

    the HTTP verb to create a class from

Returns:

  • (Class)

356
357
358
# File 'lib/chef-api/connection.rb', line 356

def class_for_request(verb)
  Net::HTTP.const_get(verb.to_s.capitalize)
end

#clientsClass<Resource::Client>

Get the list of clients for this ChefAPI::Connection. This method is threadsafe.

Examples:

Get the clients from this ChefAPI::Connection object

connection = ChefAPI::Connection.new('...')
connection.clients #=> Resource::Client(attribute1, attribute2, ...)

Returns:


40
# File 'lib/chef-api/connection.rb', line 40

proxy :clients,        'Resource::Client'

#cookbooksClass<Resource::Cookbook>

Get the list of cookbooks for this ChefAPI::Connection. This method is threadsafe.

Examples:

Get the cookbooks from this ChefAPI::Connection object

connection = ChefAPI::Connection.new('...')
connection.cookbooks #=> Resource::Cookbook(attribute1, attribute2, ...)

Returns:


41
# File 'lib/chef-api/connection.rb', line 41

proxy :cookbooks,      'Resource::Cookbook'

#data_bagsClass<Resource::DataBag>

Get the list of data_bags for this ChefAPI::Connection. This method is threadsafe.

Examples:

Get the data_bags from this ChefAPI::Connection object

connection = ChefAPI::Connection.new('...')
connection.data_bags #=> Resource::DataBag(attribute1, attribute2, ...)

Returns:


42
# File 'lib/chef-api/connection.rb', line 42

proxy :data_bags,      'Resource::DataBag'

#delete(path, params = {}, request_options = {}) ⇒ String, Hash

Make a HTTP DELETE request

Parameters:

  • path (String)

    the absolute or relative path from Defaults.endpoint to make the request against

  • params (Hash) (defaults to: {})

    the list of query params

  • request_options (Hash) (defaults to: {})

    the list of options/configurables for the actual request

Returns:

  • (String, Hash)

    the response body

Raises:


167
168
169
# File 'lib/chef-api/connection.rb', line 167

def delete(path, params = {}, request_options = {})
  request(:delete, path, params)
end

#environmentsClass<Resource::Environment>

Get the list of environments for this ChefAPI::Connection. This method is threadsafe.

Examples:

Get the environments from this ChefAPI::Connection object

connection = ChefAPI::Connection.new('...')
connection.environments #=> Resource::Environment(attribute1, attribute2, ...)

Returns:


43
# File 'lib/chef-api/connection.rb', line 43

proxy :environments,   'Resource::Environment'

#get(path, params = {}, request_options = {}) ⇒ String, Hash

Make a HTTP GET request

Parameters:

  • params (Hash) (defaults to: {})

    the list of query params

  • path (String)

    the absolute or relative path from Defaults.endpoint to make the request against

  • request_options (Hash) (defaults to: {})

    the list of options/configurables for the actual request

Returns:

  • (String, Hash)

    the response body

Raises:


106
107
108
# File 'lib/chef-api/connection.rb', line 106

def get(path, params = {}, request_options = {})
  request(:get, path, params)
end

#nodesClass<Resource::Node>

Get the list of nodes for this ChefAPI::Connection. This method is threadsafe.

Examples:

Get the nodes from this ChefAPI::Connection object

connection = ChefAPI::Connection.new('...')
connection.nodes #=> Resource::Node(attribute1, attribute2, ...)

Returns:


44
# File 'lib/chef-api/connection.rb', line 44

proxy :nodes,          'Resource::Node'

#partial_searchClass<Resource::PartialSearch>

Get the list of partial_search for this ChefAPI::Connection. This method is threadsafe.

Examples:

Get the partial_search from this ChefAPI::Connection object

connection = ChefAPI::Connection.new('...')
connection.partial_search #=> Resource::PartialSearch(attribute1, attribute2, ...)

Returns:


45
# File 'lib/chef-api/connection.rb', line 45

proxy :partial_search, 'Resource::PartialSearch'

#patch(path, data, params = {}, request_options = {}) ⇒ String, Hash

Make a HTTP PATCH request

Parameters:

  • path (String)

    the absolute or relative path from Defaults.endpoint to make the request against

  • data (String, #read)

    the body to use for the request

  • params (Hash) (defaults to: {})

    the list of query params

  • request_options (Hash) (defaults to: {})

    the list of options/configurables for the actual request

Returns:

  • (String, Hash)

    the response body

Raises:


153
154
155
# File 'lib/chef-api/connection.rb', line 153

def patch(path, data, params = {}, request_options = {})
  request(:patch, path, data, params)
end

#post(path, data, params = {}, request_options = {}) ⇒ String, Hash

Make a HTTP POST request

Parameters:

  • data (String, #read)

    the body to use for the request

  • params (Hash) (defaults to: {})

    the list of query params

  • path (String)

    the absolute or relative path from Defaults.endpoint to make the request against

  • request_options (Hash) (defaults to: {})

    the list of options/configurables for the actual request

Returns:

  • (String, Hash)

    the response body

Raises:


123
124
125
# File 'lib/chef-api/connection.rb', line 123

def post(path, data, params = {}, request_options = {})
  request(:post, path, data, params)
end

#principalsClass<Resource::Principal>

Get the list of principals for this ChefAPI::Connection. This method is threadsafe.

Examples:

Get the principals from this ChefAPI::Connection object

connection = ChefAPI::Connection.new('...')
connection.principals #=> Resource::Principal(attribute1, attribute2, ...)

Returns:


46
# File 'lib/chef-api/connection.rb', line 46

proxy :principals,     'Resource::Principal'

#put(path, data, params = {}, request_options = {}) ⇒ String, Hash

Make a HTTP PUT request

Parameters:

  • path (String)

    the absolute or relative path from Defaults.endpoint to make the request against

  • data (String, #read)

    the body to use for the request

  • params (Hash) (defaults to: {})

    the list of query params

  • request_options (Hash) (defaults to: {})

    the list of options/configurables for the actual request

Returns:

  • (String, Hash)

    the response body

Raises:


138
139
140
# File 'lib/chef-api/connection.rb', line 138

def put(path, data, params = {}, request_options = {})
  request(:put, path, data, params)
end

#request(verb, path, data = {}, params = {}, request_options = {}) ⇒ String, Hash

Make an HTTP request with the given verb, data, params, and headers. If the response has a return type of JSON, the JSON is automatically parsed and returned as a hash; otherwise it is returned as a string.

Parameters:

  • verb (Symbol)

    the lowercase symbol of the HTTP verb (e.g. :get, :delete)

  • path (String)

    the absolute or relative path from Defaults.endpoint to make the request against

  • data (#read, Hash, nil) (defaults to: {})

    the data to use (varies based on the verb)

  • params (Hash) (defaults to: {})

    the params to use for :patch, :post, :put

  • request_options (Hash) (defaults to: {})

    the list of options/configurables for the actual request

Options Hash (request_options):

  • :sign (true, false) — default: default: +true+

    whether to sign the request using mixlib authentication headers

Returns:

  • (String, Hash)

    the response body

Raises:


197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/chef-api/connection.rb', line 197

def request(verb, path, data = {}, params = {}, request_options = {})
  log.info  "#{verb.to_s.upcase} #{path}..."
  log.debug "Chef flavor: #{flavor.inspect}"

  # Build the URI and request object from the given information
  if [:delete, :get].include?(verb)
    uri = build_uri(verb, path, data)
  else
    uri = build_uri(verb, path, params)
  end
  request = class_for_request(verb).new(uri.request_uri)

  # Add request headers
  add_request_headers(request)

  # Setup PATCH/POST/PUT
  if [:patch, :post, :put].include?(verb)
    if data.respond_to?(:read)
      log.info "Detected file/io presence"
      request.body_stream = data
    elsif data.is_a?(Hash)
      # If any of the values in the hash are File-like, assume this is a
      # multi-part post
      if data.values.any? { |value| value.respond_to?(:read) }
        log.info "Detected multipart body"

        multipart = Multipart::Body.new(data)

        log.debug "Content-Type: #{multipart.content_type}"
        log.debug "Content-Length: #{multipart.content_length}"

        request.content_length = multipart.content_length
        request.content_type   = multipart.content_type

        request.body_stream    = multipart.stream
      else
        log.info "Detected form data"
        request.form_data = data
      end
    else
      log.info "Detected regular body"
      request.body = data
    end
  end

  # Sign the request
  if request_options[:sign] == false
    log.info "Skipping signed header authentication (user requested)..."
  else
    add_signing_headers(verb, uri.path, request)
  end

  # Create the HTTP connection object - since the proxy information defaults
  # to +nil+, we can just pass it to the initializer method instead of doing
  # crazy strange conditionals.
  connection = Net::HTTP.new(uri.host, uri.port,
    proxy_address, proxy_port, proxy_username, proxy_password)

  # Apply SSL, if applicable
  if uri.scheme == 'https'
    # Turn on SSL
    connection.use_ssl = true

    # Custom pem files, no problem!
    if ssl_pem_file
      pem = File.read(ssl_pem_file)
      connection.cert = OpenSSL::X509::Certificate.new(pem)
      connection.key = OpenSSL::PKey::RSA.new(pem)
      connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
    end

    # Naughty, naughty, naughty! Don't blame when when someone hops in
    # and executes a MITM attack!
    unless ssl_verify
      log.warn "Disabling SSL verification..."
      log.warn "Neither ChefAPI nor the maintainers are responsible for " \
        "damages incurred as a result of disabling SSL verification. " \
        "Please use this with extreme caution, or consider specifying " \
        "a custom certificate using `config.ssl_pem_file'."
      connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
    end
  end

  # Create a connection using the block form, which will ensure the socket
  # is properly closed in the event of an error.
  connection.start do |http|
    response = http.request(request)

    log.debug "Raw response:"
    log.debug response.body

    case response
    when Net::HTTPRedirection
      redirect = URI.parse(response['location']).to_s
      log.debug "Performing HTTP redirect to #{redirect}"
      request(verb, redirect, data)
    when Net::HTTPSuccess
      success(response)
    else
      error(response)
    end
  end
rescue SocketError, Errno::ECONNREFUSED, EOFError
  log.warn "Unable to reach the Chef Server"
  raise Error::HTTPServerUnavailable.new
end

#rolesClass<Resource::Role>

Get the list of roles for this ChefAPI::Connection. This method is threadsafe.

Examples:

Get the roles from this ChefAPI::Connection object

connection = ChefAPI::Connection.new('...')
connection.roles #=> Resource::Role(attribute1, attribute2, ...)

Returns:


47
# File 'lib/chef-api/connection.rb', line 47

proxy :roles,          'Resource::Role'

#same_options?(opts) ⇒ Boolean

Determine if the given options are the same as ours.

Returns:


91
92
93
# File 'lib/chef-api/connection.rb', line 91

def same_options?(opts)
  opts.hash == options.hash
end

#searchClass<Resource::Search>

Get the list of search for this ChefAPI::Connection. This method is threadsafe.

Examples:

Get the search from this ChefAPI::Connection object

connection = ChefAPI::Connection.new('...')
connection.search #=> Resource::Search(attribute1, attribute2, ...)

Returns:


48
# File 'lib/chef-api/connection.rb', line 48

proxy :search,         'Resource::Search'

#to_query_string(hash) ⇒ String?

Convert the given hash to a list of query string parameters. Each key and value in the hash is URI-escaped for safety.

Parameters:

  • hash (Hash)

    the hash to create the query string from

Returns:

  • (String, nil)

    the query string as a string, or nil if there are no params


370
371
372
373
374
# File 'lib/chef-api/connection.rb', line 370

def to_query_string(hash)
  hash.map do |key, value|
    "#{URI.escape(key.to_s)}=#{URI.escape(value.to_s)}"
  end.join('&')[/.+/]
end

#usersClass<Resource::User>

Get the list of users for this ChefAPI::Connection. This method is threadsafe.

Examples:

Get the users from this ChefAPI::Connection object

connection = ChefAPI::Connection.new('...')
connection.users #=> Resource::User(attribute1, attribute2, ...)

Returns:


49
# File 'lib/chef-api/connection.rb', line 49

proxy :users,          'Resource::User'