Class: ChefAPI::Connection

Inherits:
Object
  • Object
show all
Includes:
Configurable, Logger
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 Logger

included, level, level=, logger_for

Methods included from Configurable

#configure, keys, #reset!

Constructor Details

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

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



48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/chef-api/connection.rb', line 48

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
end

Class Method Details

.proxy(name, klass) ⇒ Object



30
31
32
33
34
35
36
# File 'lib/chef-api/connection.rb', line 30

def proxy(name, klass)
  class_eval "    def \#{name}\n      @\#{name} ||= ChefAPI::Proxy.new(self, \#{klass})\n    end\n  EOH\nend\n", __FILE__, __LINE__ + 1

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)


252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/chef-api/connection.rb', line 252

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

  # Add any query string parameters
  if [:delete, :get].include?(verb)
    log.debug "     Detected verb deserves a querystring"
    log.debug "     Building querystring using #{params.inspect}"
    path = [path, to_query_string(params)].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 #{endpoint} to #{path}"
    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)


285
286
287
# File 'lib/chef-api/connection.rb', line 285

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

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



133
134
135
# File 'lib/chef-api/connection.rb', line 133

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

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



80
81
82
# File 'lib/chef-api/connection.rb', line 80

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

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



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

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:



94
95
96
# File 'lib/chef-api/connection.rb', line 94

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

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



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

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)

  • headers (Hash)

    the list of headers to use

Returns:

  • (String, Hash)

    the response body

Raises:



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
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
# File 'lib/chef-api/connection.rb', line 158

def request(verb, path, data = {})
  log.info "===> #{verb.to_s.upcase} #{path}..."

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

    case response
    when Net::HTTPRedirection
      redirect = URI.parse(response['location'])
      log.debug "===> Performing HTTP redirect to #{redirect}"
      request(verb, redirect, params)
    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

#same_options?(opts) ⇒ Boolean

Determine if the given options are the same as ours.

Returns:



66
67
68
# File 'lib/chef-api/connection.rb', line 66

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



299
300
301
302
303
# File 'lib/chef-api/connection.rb', line 299

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