stacker_bee 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.rubocop.yml +7 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +112 -0
- data/Rakefile +6 -0
- data/bin/stacker_bee +97 -0
- data/config.default.yml +3 -0
- data/config/4.2.json +67126 -0
- data/lib/stacker_bee.rb +16 -0
- data/lib/stacker_bee/api.rb +44 -0
- data/lib/stacker_bee/body_parser.rb +23 -0
- data/lib/stacker_bee/client.rb +105 -0
- data/lib/stacker_bee/configuration.rb +6 -0
- data/lib/stacker_bee/connection.rb +31 -0
- data/lib/stacker_bee/middleware/logger.rb +48 -0
- data/lib/stacker_bee/middleware/signed_query.rb +29 -0
- data/lib/stacker_bee/rash.rb +95 -0
- data/lib/stacker_bee/request.rb +41 -0
- data/lib/stacker_bee/request_error.rb +39 -0
- data/lib/stacker_bee/response.rb +29 -0
- data/lib/stacker_bee/utilities.rb +18 -0
- data/lib/stacker_bee/version.rb +3 -0
- data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/.yml +33 -0
- data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/a_nil_request_parameter/properly_executes_the_request.yml +33 -0
- data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/a_request_parameter_with_an_Array/.yml +35 -0
- data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/a_request_parameter_with_and_empty_string/properly_executes_the_request.yml +33 -0
- data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/containing_an_error/.yml +37 -0
- data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/containing_an_error/should_log_response_as_error.yml +37 -0
- data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/first/.yml +33 -0
- data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/first_item/.yml +33 -0
- data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/first_item/_account_type_/.yml +33 -0
- data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/first_item/_accounttype_/.yml +33 -0
- data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/should_log_request.yml +33 -0
- data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/should_not_log_response_as_error.yml +33 -0
- data/spec/cassettes/A_response_to_a_request_sent_to_the_CloudStack_API/space_character_in_a_request_parameter/properly_signs_the_request.yml +32 -0
- data/spec/fixtures/4.2.json +67126 -0
- data/spec/fixtures/simple.json +871 -0
- data/spec/integration/request_spec.rb +116 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/units/stacker_bee/api_spec.rb +24 -0
- data/spec/units/stacker_bee/client_spec.rb +181 -0
- data/spec/units/stacker_bee/configuration_spec.rb +7 -0
- data/spec/units/stacker_bee/connection_spec.rb +45 -0
- data/spec/units/stacker_bee/middleware/logger_spec.rb +55 -0
- data/spec/units/stacker_bee/rash_spec.rb +87 -0
- data/spec/units/stacker_bee/request_error_spec.rb +44 -0
- data/spec/units/stacker_bee/request_spec.rb +49 -0
- data/spec/units/stacker_bee/response_spec.rb +79 -0
- data/spec/units/stacker_bee/utilities_spec.rb +25 -0
- data/spec/units/stacker_bee_spec.rb +6 -0
- data/stacker_bee.gemspec +30 -0
- metadata +225 -0
    
        data/lib/stacker_bee.rb
    ADDED
    
    | @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            require_stacker_bee = if defined?(require_relative)
         | 
| 2 | 
            +
                                    lambda do |path|
         | 
| 3 | 
            +
                                      require_relative path
         | 
| 4 | 
            +
                                    end
         | 
| 5 | 
            +
                                  else # for 1.8.7
         | 
| 6 | 
            +
                                    lambda do |path|
         | 
| 7 | 
            +
                                      require "stacker_bee/#{path}"
         | 
| 8 | 
            +
                                    end
         | 
| 9 | 
            +
                                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            %w(
         | 
| 12 | 
            +
              version
         | 
| 13 | 
            +
              client
         | 
| 14 | 
            +
            ).each do |file_name|
         | 
| 15 | 
            +
              require_stacker_bee["stacker_bee/#{file_name}"]
         | 
| 16 | 
            +
            end
         | 
| @@ -0,0 +1,44 @@ | |
| 1 | 
            +
            require "multi_json"
         | 
| 2 | 
            +
            require "stacker_bee/utilities"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module StackerBee
         | 
| 5 | 
            +
              class API
         | 
| 6 | 
            +
                include Utilities
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                attr_accessor :api_path
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def initialize(attrs = {})
         | 
| 11 | 
            +
                  attrs.each_pair do |key, value|
         | 
| 12 | 
            +
                    setter = "#{key}="
         | 
| 13 | 
            +
                    send(setter, value)
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def [](key)
         | 
| 18 | 
            +
                  endpoints[uncase(key)]
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def key?(key)
         | 
| 22 | 
            +
                  endpoints.key? uncase(key)
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                protected
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def endpoints
         | 
| 28 | 
            +
                  @endpoints ||= read_endpoints
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def read_endpoints
         | 
| 32 | 
            +
                  return unless api_path
         | 
| 33 | 
            +
                  json     = File.read(api_path)
         | 
| 34 | 
            +
                  response = MultiJson.load(json)
         | 
| 35 | 
            +
                  apis_by_endpoint(response)
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def apis_by_endpoint(response)
         | 
| 39 | 
            +
                  response["listapisresponse"]["api"].each_with_object({}) do |api, memo|
         | 
| 40 | 
            +
                    memo[uncase(api["name"])] = api
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
            end
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            require "multi_json"
         | 
| 2 | 
            +
            require "stacker_bee/rash"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module StackerBee
         | 
| 5 | 
            +
              module BodyParser
         | 
| 6 | 
            +
                attr_reader :body
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def body=(raw_response)
         | 
| 9 | 
            +
                  @body = parse(raw_response.body)
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def parse(json)
         | 
| 13 | 
            +
                  parsed = MultiJson.load(json)
         | 
| 14 | 
            +
                  fail "Cannot determine response key in #{parsed.keys}" if parsed.size > 1
         | 
| 15 | 
            +
                  case value = parsed.values.first
         | 
| 16 | 
            +
                  when Hash  then Rash.new(value)
         | 
| 17 | 
            +
                  when Array then value.map { |item| Rash.new(item) }
         | 
| 18 | 
            +
                  else
         | 
| 19 | 
            +
                    value
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -0,0 +1,105 @@ | |
| 1 | 
            +
            require "forwardable"
         | 
| 2 | 
            +
            require "stacker_bee/configuration"
         | 
| 3 | 
            +
            require "stacker_bee/api"
         | 
| 4 | 
            +
            require "stacker_bee/connection"
         | 
| 5 | 
            +
            require "stacker_bee/request"
         | 
| 6 | 
            +
            require "stacker_bee/response"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            module StackerBee
         | 
| 9 | 
            +
              class Client
         | 
| 10 | 
            +
                DEFAULT_API_PATH = File.join(
         | 
| 11 | 
            +
                  File.dirname(__FILE__), '../../config/4.2.json'
         | 
| 12 | 
            +
                )
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                extend Forwardable
         | 
| 15 | 
            +
                def_delegators :configuration,
         | 
| 16 | 
            +
                               :logger,
         | 
| 17 | 
            +
                               :logger=,
         | 
| 18 | 
            +
                               :url,
         | 
| 19 | 
            +
                               :url=,
         | 
| 20 | 
            +
                               :api_key,
         | 
| 21 | 
            +
                               :api_key=,
         | 
| 22 | 
            +
                               :secret_key,
         | 
| 23 | 
            +
                               :secret_key=
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                class << self
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def reset!
         | 
| 28 | 
            +
                    @api, @api_path, @default_config = nil
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def default_config
         | 
| 32 | 
            +
                    @default_config ||= {
         | 
| 33 | 
            +
                      allow_empty_string_params: false
         | 
| 34 | 
            +
                    }
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  def configuration=(config_hash)
         | 
| 38 | 
            +
                    default_config.merge!(config_hash)
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def api_path
         | 
| 42 | 
            +
                    @api_path ||= DEFAULT_API_PATH
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  def api_path=(new_api_path)
         | 
| 46 | 
            +
                    return @api_path if @api_path == new_api_path
         | 
| 47 | 
            +
                    @api = nil
         | 
| 48 | 
            +
                    @api_path = new_api_path
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  def api
         | 
| 52 | 
            +
                    @api ||= API.new(api_path: api_path)
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def initialize(config = {})
         | 
| 57 | 
            +
                  self.configuration = config
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                def configuration=(config)
         | 
| 61 | 
            +
                  @configuration = configuration_with_defaults(config)
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                def configuration
         | 
| 65 | 
            +
                  @configuration ||= configuration_with_defaults
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def request(endpoint_name, params = {})
         | 
| 69 | 
            +
                  request      = Request.new(endpoint_for(endpoint_name), api_key, params)
         | 
| 70 | 
            +
                  request.allow_empty_string_params =
         | 
| 71 | 
            +
                    configuration.allow_empty_string_params
         | 
| 72 | 
            +
                  raw_response = connection.get(request)
         | 
| 73 | 
            +
                  Response.new(raw_response)
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                def endpoint_for(name)
         | 
| 77 | 
            +
                  api = self.class.api[name]
         | 
| 78 | 
            +
                  api && api["name"]
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def method_missing(name, *args, &block)
         | 
| 82 | 
            +
                  endpoint = endpoint_for(name)
         | 
| 83 | 
            +
                  if endpoint
         | 
| 84 | 
            +
                    request(endpoint, *args, &block)
         | 
| 85 | 
            +
                  else
         | 
| 86 | 
            +
                    super
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                def respond_to?(name, include_private = false)
         | 
| 91 | 
            +
                  self.class.api.key?(name) || super
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                protected
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                def connection
         | 
| 97 | 
            +
                  @connection ||= Connection.new(configuration)
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                def configuration_with_defaults(config = {})
         | 
| 101 | 
            +
                  config_hash = self.class.default_config.merge(config)
         | 
| 102 | 
            +
                  Configuration.new(config_hash)
         | 
| 103 | 
            +
                end
         | 
| 104 | 
            +
              end
         | 
| 105 | 
            +
            end
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            require "faraday"
         | 
| 2 | 
            +
            require "uri"
         | 
| 3 | 
            +
            require "stacker_bee/middleware/signed_query"
         | 
| 4 | 
            +
            require "stacker_bee/middleware/logger"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module StackerBee
         | 
| 7 | 
            +
              class ConnectionError < Exception
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              class Connection
         | 
| 11 | 
            +
                attr_accessor :configuration
         | 
| 12 | 
            +
                def initialize(configuration)
         | 
| 13 | 
            +
                  @configuration = configuration
         | 
| 14 | 
            +
                  uri      = URI.parse(self.configuration.url)
         | 
| 15 | 
            +
                  @path    = uri.path
         | 
| 16 | 
            +
                  uri.path = ''
         | 
| 17 | 
            +
                  fail ConnectionError, "no protocol specified" unless uri.scheme
         | 
| 18 | 
            +
                  @faraday = Faraday.new(url: uri.to_s) do |faraday|
         | 
| 19 | 
            +
                    faraday.use      Middleware::SignedQuery, self.configuration.secret_key
         | 
| 20 | 
            +
                    faraday.use      Middleware::Logger, self.configuration.logger
         | 
| 21 | 
            +
                    faraday.adapter  Faraday.default_adapter  # Net::HTTP
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def get(request)
         | 
| 26 | 
            +
                  @faraday.get(@path, request.query_params)
         | 
| 27 | 
            +
                rescue Faraday::Error::ConnectionFailed
         | 
| 28 | 
            +
                  raise ConnectionError, "Failed to connect to #{configuration.url}"
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            require 'forwardable'
         | 
| 2 | 
            +
            require 'logger'
         | 
| 3 | 
            +
            require 'pp'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module StackerBee
         | 
| 6 | 
            +
              module Middleware
         | 
| 7 | 
            +
                class Logger < Faraday::Response::Middleware
         | 
| 8 | 
            +
                  extend Forwardable
         | 
| 9 | 
            +
                  PROGNAME = "StackerBee"
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  attr_accessor :logger
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def initialize(app, _logger = nil)
         | 
| 14 | 
            +
                    super(app)
         | 
| 15 | 
            +
                    self.logger = _logger
         | 
| 16 | 
            +
                    logger.progname ||= PROGNAME
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def logger
         | 
| 20 | 
            +
                    @logger ||= ::Logger.new($stdout)
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def_delegators :logger, :debug, :info, :warn, :error, :fatal
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def call(env)
         | 
| 26 | 
            +
                    log_request(env)
         | 
| 27 | 
            +
                    super
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def on_complete(env)
         | 
| 31 | 
            +
                    log_response(env)
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def log_request(env)
         | 
| 35 | 
            +
                    info  "#{env[:method]} #{env[:url]}"
         | 
| 36 | 
            +
                    debug env[:request_headers].pretty_inspect
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  def log_response(env)
         | 
| 40 | 
            +
                    status_message = "Status: #{env[:status]}"
         | 
| 41 | 
            +
                    env[:status] < 400 ? info(status_message) : error(status_message)
         | 
| 42 | 
            +
                    debug env[:response_headers].pretty_inspect
         | 
| 43 | 
            +
                    debug env[:body]
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
| @@ -0,0 +1,29 @@ | |
| 1 | 
            +
            require "faraday"
         | 
| 2 | 
            +
            require "base64"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module StackerBee
         | 
| 5 | 
            +
              module Middleware
         | 
| 6 | 
            +
                class SignedQuery < Faraday::Middleware
         | 
| 7 | 
            +
                  def initialize(app, key)
         | 
| 8 | 
            +
                    @key = key
         | 
| 9 | 
            +
                    fail "Key cannot be nil" unless @key
         | 
| 10 | 
            +
                    super(app)
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def call(env)
         | 
| 14 | 
            +
                    sign_uri(env[:url])
         | 
| 15 | 
            +
                    @app.call(env)
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def sign_uri(uri)
         | 
| 19 | 
            +
                    downcased  = uri.query.downcase
         | 
| 20 | 
            +
                    nonplussed = downcased.gsub('+', '%20')
         | 
| 21 | 
            +
                    signed     = OpenSSL::HMAC.digest 'sha1', @key, nonplussed
         | 
| 22 | 
            +
                    encoded    = Base64.encode64(signed).chomp
         | 
| 23 | 
            +
                    escaped    = CGI.escape(encoded)
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    uri.query << "&signature=#{escaped}"
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| @@ -0,0 +1,95 @@ | |
| 1 | 
            +
            require 'forwardable'
         | 
| 2 | 
            +
            require "stacker_bee/utilities"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module StackerBee
         | 
| 5 | 
            +
              class Rash
         | 
| 6 | 
            +
                extend Forwardable
         | 
| 7 | 
            +
                include Utilities
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def_delegators :@hash, *[
         | 
| 10 | 
            +
                  :default, :default_proc, :each_value, :empty?, :has_value?, :hash,
         | 
| 11 | 
            +
                  :length, :size, :value?, :values, :assoc, :each, :each_key, :each_pair,
         | 
| 12 | 
            +
                  :flatten, :invert, :keys, :key, :merge, :rassoc, :to_a, :to_h, :to_hash
         | 
| 13 | 
            +
                ]
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def initialize(hash = {})
         | 
| 16 | 
            +
                  @hash = {}
         | 
| 17 | 
            +
                  hash.each_pair do |key, value|
         | 
| 18 | 
            +
                    @hash[convert_key(key)] = convert_value(value)
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                  @hash.freeze
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def ==(other)
         | 
| 24 | 
            +
                  case other
         | 
| 25 | 
            +
                  when Rash
         | 
| 26 | 
            +
                    super || @hash == other.to_hash
         | 
| 27 | 
            +
                  when Hash
         | 
| 28 | 
            +
                    self == Rash.new(other)
         | 
| 29 | 
            +
                  else
         | 
| 30 | 
            +
                    super
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def select(*args, &block)
         | 
| 35 | 
            +
                  Rash.new @hash.select(*args, &block)
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def reject(*args, &block)
         | 
| 39 | 
            +
                  Rash.new @hash.reject(*args, &block)
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                def values_at(*keys)
         | 
| 43 | 
            +
                  @hash.values_at(*keys.map { |key| convert_key(key) })
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def fetch(key, *args, &block)
         | 
| 47 | 
            +
                  @hash.fetch(convert_key(key), *args, &block)
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def [](key)
         | 
| 51 | 
            +
                  @hash[convert_key(key)]
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                def key?(key)
         | 
| 55 | 
            +
                  @hash.key?(convert_key(key))
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
                alias_method :include?, :key?
         | 
| 58 | 
            +
                alias_method :has_key?, :key?
         | 
| 59 | 
            +
                alias_method :member?,  :key?
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def to_hash
         | 
| 62 | 
            +
                  self.class.deep_dup(@hash)
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                def inspect
         | 
| 66 | 
            +
                  "#<#{self.class} #{@hash}>"
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
                alias_method :to_s, :inspect
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                protected
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                def self.deep_dup(hash)
         | 
| 73 | 
            +
                  hash.dup.tap do |duplicate|
         | 
| 74 | 
            +
                    duplicate.each_pair do |key, value|
         | 
| 75 | 
            +
                      duplicate[key] = deep_dup(value) if value.is_a?(Hash)
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
                  end
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                def convert_key(key)
         | 
| 81 | 
            +
                  key.kind_of?(Numeric) ? key : uncase(key)
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                def convert_value(value)
         | 
| 85 | 
            +
                  case value
         | 
| 86 | 
            +
                  when Hash
         | 
| 87 | 
            +
                    Rash.new(value)
         | 
| 88 | 
            +
                  when Array
         | 
| 89 | 
            +
                    value.map { |item| convert_value(item) }
         | 
| 90 | 
            +
                  else
         | 
| 91 | 
            +
                    value
         | 
| 92 | 
            +
                  end
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
              end
         | 
| 95 | 
            +
            end
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            require "stacker_bee/utilities"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module StackerBee
         | 
| 4 | 
            +
              class Request
         | 
| 5 | 
            +
                include Utilities
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                RESPONSE_TYPE = "json"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                attr_accessor :params
         | 
| 10 | 
            +
                attr_writer   :allow_empty_string_params
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def initialize(endpoint, api_key, params = {})
         | 
| 13 | 
            +
                  params[:api_key]  = api_key
         | 
| 14 | 
            +
                  params[:command]  = endpoint
         | 
| 15 | 
            +
                  params[:response] = RESPONSE_TYPE
         | 
| 16 | 
            +
                  self.params = params
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def query_params
         | 
| 20 | 
            +
                  params
         | 
| 21 | 
            +
                    .reject { |key, val| val.nil? }
         | 
| 22 | 
            +
                    .reject { |key, val| !allow_empty_string_params && val == '' }
         | 
| 23 | 
            +
                    .sort
         | 
| 24 | 
            +
                    .map { |(key, val)| [cloud_stack_key(key), cloud_stack_value(val)] }
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def allow_empty_string_params
         | 
| 28 | 
            +
                  @allow_empty_string_params ||= false
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                private
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def cloud_stack_key(key)
         | 
| 34 | 
            +
                  camel_case(key, true)
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def cloud_stack_value(value)
         | 
| 38 | 
            +
                  value.respond_to?(:join) ? value.join(',') : value
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         |