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)


308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/chef-api/connection.rb', line 308

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)


342
343
344
# File 'lib/chef-api/connection.rb', line 342

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 = {}) ⇒ 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

Returns:

  • (String, Hash)

    the response body

Raises:



162
163
164
# File 'lib/chef-api/connection.rb', line 162

def delete(path, params = {})
  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 = {}) ⇒ 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

Returns:

  • (String, Hash)

    the response body

Raises:



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

def get(path, params = {})
  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 = {}) ⇒ 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

Returns:

  • (String, Hash)

    the response body

Raises:



149
150
151
# File 'lib/chef-api/connection.rb', line 149

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

#post(path, data, params = {}) ⇒ 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

Returns:

  • (String, Hash)

    the response body

Raises:



121
122
123
# File 'lib/chef-api/connection.rb', line 121

def post(path, data, params = {})
  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 = {}) ⇒ 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

Returns:

  • (String, Hash)

    the response body

Raises:



135
136
137
# File 'lib/chef-api/connection.rb', line 135

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

#request(verb, path, data = {}, params = {}) ⇒ 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

Returns:

  • (String, Hash)

    the response body

Raises:



187
188
189
190
191
192
193
194
195
196
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
# File 'lib/chef-api/connection.rb', line 187

def request(verb, path, data = {}, params = {})
  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
  add_signing_headers(verb, uri.path, request)

  # 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 " \
        "damanges 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



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

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'