class Nomad::Client
Constants
- DEFAULT_HEADERS
The default headers that are sent with every request.
- JSON_PARSE_OPTIONS
The default list of options to use when parsing JSON.
- LOCATION_HEADER
The name of the header used for redirection.
- RESCUED_EXCEPTIONS
- USER_AGENT
The user agent for this client.
Attributes
Public Class Methods
Create a new Client
with the given options. Any options given take precedence over the default options.
@return [Nomad::Client]
# File lib/nomad/client.rb, line 55 def initialize(options = {}) # Use any options given, but fall back to the defaults set on the module Nomad::Configurable.keys.each do |key| value = options.key?(key) ? options[key] : Defaults.public_send(key) instance_variable_set(:"@#{key}", value) end @connection = setup_connection end
Public Instance Methods
A proxy to the {Agent} methods. @return [Agent]
# File lib/nomad/api/agent.rb, line 9 def agent @agent ||= Agent.new(self) end
A proxy to the {Allocation} methods. @return [Allocation]
# File lib/nomad/api/allocation.rb, line 10 def allocation @allocation ||= Allocation.new(self) end
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 {Client#to_query_string}.
If the path is relative, it is merged with the {Defaults.address} attribute. If the path is absolute, it is converted to a URI object and returned.
@param [Symbol] verb
the lowercase HTTP verb (e.g. :+get+)
@param [String] path
the absolute or relative HTTP path (url) to get
@param [Hash] params
the list of params to build the URI with (for GET and DELETE requests)
@return [URI]
# File lib/nomad/client.rb, line 256 def build_uri(verb, path, params = {}) # Add any query string parameters if [:delete, :get].include?(verb) path = [path, to_query_string(params)].compact.join("?") end # Ensure leading slash path = "/" << path if path && path[0] != "/" # Parse the URI return path end
Helper method to get the corresponding {Net::HTTP} class from the given HTTP verb.
@param [#to_s] verb
the HTTP verb to create a class from
@return [Class]
# File lib/nomad/client.rb, line 276 def class_for_request(verb) Net::HTTP.const_get(verb.to_s.capitalize) end
Perform a DELETE request. @see Client#request
# File lib/nomad/client.rb, line 99 def delete(path, params = {}, headers = {}) request(:delete, path, params, headers) end
Raise a response error, extracting as much information from the server's response as possible.
@raise [HTTPError]
@param [HTTP::Message] response
the response object from the request
# File lib/nomad/client.rb, line 318 def error(response) # Use the correct exception class case response when Net::HTTPClientError klass = HTTPClientError when Net::HTTPServerError klass = HTTPServerError else klass = HTTPError end if (response.content_type || '').include?("json") # Attempt to parse the error as JSON begin json = JSON.parse(response.body, JSON_PARSE_OPTIONS) if json[:errors] raise klass.new(address, response, json[:errors]) end rescue JSON::ParserError; end end raise klass.new(address, response, [response.body]) end
A proxy to the {Evaluation} methods. @return [Evaluation]
# File lib/nomad/api/evaluation.rb, line 10 def evaluation @evaluation ||= Evaluation.new(self) end
Perform a GET request. @see Client#request
# File lib/nomad/client.rb, line 75 def get(path, params = {}, headers = {}) request(:get, path, params, headers) end
A proxy to the {Job} methods. @return [Job]
# File lib/nomad/api/job.rb, line 8 def job @job ||= Job.new(self) end
A proxy to the {Node} methods. @return [Node]
# File lib/nomad/api/node.rb, line 8 def node @node ||= Node.new(self) end
A proxy to the {Operator} methods. @return [Operator]
# File lib/nomad/api/operator.rb, line 8 def operator @operator ||= Operator.new(self) end
Perform a PATCH request. @see Client#request
# File lib/nomad/client.rb, line 93 def patch(path, data, headers = {}) request(:patch, path, data, headers) end
Perform a POST request. @see Client#request
# File lib/nomad/client.rb, line 81 def post(path, data = {}, headers = {}) request(:post, path, data, headers) end
Perform a PUT request. @see Client#request
# File lib/nomad/client.rb, line 87 def put(path, data, headers = {}) request(:put, path, data, headers) end
A proxy to the {Region} methods. @return [Region]
# File lib/nomad/api/region.rb, line 8 def region @region ||= Region.new(self) end
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.
@raise [HTTPError]
if the request is not an HTTP 200 OK
@param [Symbol] verb
the lowercase symbol of the HTTP verb (e.g. :get, :delete)
@param [String] path
the absolute or relative path from {Defaults.address} to make the request against
@param [#read, Hash, nil] data
the data to use (varies based on the +verb+)
@param [Hash] headers
the list of headers to use
@return [String, Hash]
the response body
# File lib/nomad/client.rb, line 122 def request(verb, path, data = {}, headers = {}) uri = URI.parse(path) if uri.absolute? new_path, uri.path, uri.fragment = uri.path, "", nil client = self.class.new(options.merge( address: uri.to_s, )) return client.request(verb, new_path, data, headers) end # Build the URI and request object from the given information path = build_uri(verb, path, data) req = class_for_request(verb).new(path) # Get a list of headers headers = DEFAULT_HEADERS.merge(headers) # Add headers headers.each do |key, value| req.add_field(key, value) end # Setup PATCH/POST/PUT if [:patch, :post, :put].include?(verb) if data.respond_to?(:read) req.content_length = data.size req.body_stream = data elsif data.is_a?(Hash) req.form_data = data else req.body = data end end begin response = connection.request(req) case response when Net::HTTPRedirection # On a redirect of a GET or HEAD request, the URL already contains # the data as query string parameters. if [:head, :get].include?(verb) data = {} end request(verb, response[LOCATION_HEADER], data, headers) when Net::HTTPSuccess success(response) else error(response) end rescue *RESCUED_EXCEPTIONS => e raise HTTPConnectionError.new(address, e) end end
Determine if the given options are the same as ours. @return [true, false]
# File lib/nomad/client.rb, line 69 def same_options?(opts) options.hash == opts.hash end
# File lib/nomad/client.rb, line 176 def setup_connection # 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. uri = URI.parse(address) connection = Net::HTTP.new(uri.host, uri.port, proxy_address, proxy_port, proxy_username, proxy_password) # Use a custom open timeout if open_timeout || timeout connection.open_timeout = (open_timeout || timeout).to_i end # Use a custom read timeout if read_timeout || timeout connection.read_timeout = (read_timeout || timeout).to_i end # Also verify peer (this is the default). connection.verify_mode = OpenSSL::SSL::VERIFY_PEER # Apply SSL, if applicable if uri.scheme == "https" # Turn on SSL connection.use_ssl = true # Nomad requires TLS1.2 connection.ssl_version = "TLSv1_2" # Only use secure ciphers connection.ciphers = ssl_ciphers # Custom pem files, no problem! pem = ssl_pem_contents || ssl_pem_file ? File.read(ssl_pem_file) : nil if pem connection.cert = OpenSSL::X509::Certificate.new(pem) connection.key = OpenSSL::PKey::RSA.new(pem, ssl_pem_passphrase) end # Use custom CA cert for verification if ssl_ca_cert connection.ca_file = ssl_ca_cert end # Use custom CA path that contains CA certs if ssl_ca_path connection.ca_path = ssl_ca_path end # Naughty, naughty, naughty! Don't blame me when someone hops in # and executes a MITM attack! if !ssl_verify connection.verify_mode = OpenSSL::SSL::VERIFY_NONE end # Use custom timeout for connecting and verifying via SSL if ssl_timeout || timeout connection.ssl_timeout = (ssl_timeout || timeout).to_i end end return connection end
A proxy to the {Status} methods. @return [Status]
# File lib/nomad/api/status.rb, line 8 def status @status ||= Status.new(self) end
Parse the response object and manipulate the result based on the given Content-Type
header. For now, this method only parses JSON, but it could be expanded in the future to accept other content types.
@param [HTTP::Message] response
the response object from the request
@return [String, Hash]
the parsed response, as an object
# File lib/nomad/client.rb, line 303 def success(response) if response.body && (response.content_type || '').include?("json") JSON.parse(response.body, JSON_PARSE_OPTIONS) else response.body end end
A proxy to the {System} methods. @return [System]
# File lib/nomad/api/system.rb, line 8 def system @system ||= System.new(self) end
Convert the given hash to a list of query string parameters. Each key and value in the hash is URI-escaped for safety.
@param [Hash] hash
the hash to create the query string from
@return [String, nil]
the query string as a string, or +nil+ if there are no params
# File lib/nomad/client.rb, line 288 def to_query_string(hash) hash.map do |key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" end.join('&')[/.+/] end
A proxy to the {Validate} methods. @return [Validate]
# File lib/nomad/api/validate.rb, line 8 def validate @validate ||= Validate.new(self) end
Execute the given block with retries and exponential backoff.
@param [Array<Exception>] rescued
the list of exceptions to rescue
# File lib/nomad/client.rb, line 347 def with_retries(*rescued, &block) options = rescued.last.is_a?(Hash) ? rescued.pop : {} exception = nil retries = 0 max_attempts = options[:attempts] || Defaults::RETRY_ATTEMPTS backoff_base = options[:base] || Defaults::RETRY_BASE backoff_max = options[:max_wait] || Defaults::RETRY_MAX_WAIT begin return yield retries, exception rescue *rescued => e exception = e retries += 1 raise if retries > max_attempts # Calculate the exponential backoff combined with an element of # randomness. backoff = [backoff_base * (2 ** (retries - 1)), backoff_max].min backoff = backoff * (0.5 * (1 + Kernel.rand)) # Ensure we are sleeping at least the minimum interval. backoff = [backoff_base, backoff].max # Exponential backoff. Kernel.sleep(backoff) # Now retry retry end end