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:



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

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)


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 277

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

  # Add any query string parameters
  if [:delete, :get].include?(verb)
    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
  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)


313
314
315
# File 'lib/chef-api/connection.rb', line 313

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:



156
157
158
# File 'lib/chef-api/connection.rb', line 156

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:



103
104
105
# File 'lib/chef-api/connection.rb', line 103

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'

#patch(path, data) ⇒ 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

Returns:

  • (String, Hash)

    the response body

Raises:



143
144
145
# File 'lib/chef-api/connection.rb', line 143

def patch(path, data)
  request(:patch, path, data)
end

#post(path, data) ⇒ String, Hash

Make a HTTP POST request

Parameters:

  • data (String, #read)

    the body to use for the request

  • path (String)

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

Returns:

  • (String, Hash)

    the response body

Raises:



117
118
119
# File 'lib/chef-api/connection.rb', line 117

def post(path, data)
  request(:post, path, data)
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:



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

proxy :principals,   'Resource::Principal'

#put(path, data) ⇒ 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

Returns:

  • (String, Hash)

    the response body

Raises:



130
131
132
# File 'lib/chef-api/connection.rb', line 130

def put(path, data)
  request(:put, path, data)
end

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

Returns:

  • (String, Hash)

    the response body

Raises:



179
180
181
182
183
184
185
186
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
# File 'lib/chef-api/connection.rb', line 179

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

  # Build the URI and request object from the given information
  uri = build_uri(verb, path, data)
  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)
      request.body_stream = data
    elsif data.is_a?(Hash)
      request.form_data = data
    else
      request.body = data
    end
  end

  # Sign the request
  add_signing_headers(verb, uri, request, parsed_key)

  # 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:



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

proxy :roles,        'Resource::Role'

#same_options?(opts) ⇒ Boolean

Determine if the given options are the same as ours.

Returns:



89
90
91
# File 'lib/chef-api/connection.rb', line 89

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

#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



327
328
329
330
331
# File 'lib/chef-api/connection.rb', line 327

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:



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

proxy :users,        'Resource::User'