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

connection[R]

Public Class Methods

new(options = {}) click to toggle source

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

agent() click to toggle source

A proxy to the {Agent} methods. @return [Agent]

# File lib/nomad/api/agent.rb, line 9
def agent
  @agent ||= Agent.new(self)
end
allocation() click to toggle source

A proxy to the {Allocation} methods. @return [Allocation]

# File lib/nomad/api/allocation.rb, line 10
def allocation
  @allocation ||= Allocation.new(self)
end
build_uri(verb, path, params = {}) click to toggle source

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
class_for_request(verb) click to toggle source

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
delete(path, params = {}, headers = {}) click to toggle source

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
error(response) click to toggle source

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
evaluation() click to toggle source

A proxy to the {Evaluation} methods. @return [Evaluation]

# File lib/nomad/api/evaluation.rb, line 10
def evaluation
  @evaluation ||= Evaluation.new(self)
end
get(path, params = {}, headers = {}) click to toggle source

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
job() click to toggle source

A proxy to the {Job} methods. @return [Job]

# File lib/nomad/api/job.rb, line 8
def job
  @job ||= Job.new(self)
end
node() click to toggle source

A proxy to the {Node} methods. @return [Node]

# File lib/nomad/api/node.rb, line 8
def node
  @node ||= Node.new(self)
end
operator() click to toggle source

A proxy to the {Operator} methods. @return [Operator]

# File lib/nomad/api/operator.rb, line 8
def operator
  @operator ||= Operator.new(self)
end
patch(path, data, headers = {}) click to toggle source

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
post(path, data = {}, headers = {}) click to toggle source

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
put(path, data, headers = {}) click to toggle source

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
region() click to toggle source

A proxy to the {Region} methods. @return [Region]

# File lib/nomad/api/region.rb, line 8
def region
  @region ||= Region.new(self)
end
request(verb, path, data = {}, headers = {}) click to toggle source

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
same_options?(opts) click to toggle source

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
setup_connection() click to toggle source
# 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
status() click to toggle source

A proxy to the {Status} methods. @return [Status]

# File lib/nomad/api/status.rb, line 8
def status
  @status ||= Status.new(self)
end
success(response) click to toggle source

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
system() click to toggle source

A proxy to the {System} methods. @return [System]

# File lib/nomad/api/system.rb, line 8
def system
  @system ||= System.new(self)
end
to_query_string(hash) click to toggle source

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
validate() click to toggle source

A proxy to the {Validate} methods. @return [Validate]

# File lib/nomad/api/validate.rb, line 8
def validate
  @validate ||= Validate.new(self)
end
with_retries(*rescued) { |retries, exception| ... } click to toggle source

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