From 35964c89aa36d882560e265fe42354c67f76ab24 Mon Sep 17 00:00:00 2001 From: Kevin Olbrich Date: Thu, 12 Dec 2013 17:16:38 +0000 Subject: [PATCH] Basic analytics support including a class for joyent 'modules'. Note that these are named 'JoyentModule' instead of 'Module' to avoid obvious namespace problems. --- lib/fog/joyent.rb | 1 + lib/fog/joyent/analytics.rb | 210 ++++++++++++++++++ .../joyent/models/analytics/joyent_module.rb | 13 ++ .../joyent/models/analytics/joyent_modules.rb | 0 .../requests/analytics/describe_analytics.rb | 0 5 files changed, 224 insertions(+) create mode 100644 lib/fog/joyent/analytics.rb create mode 100644 lib/fog/joyent/models/analytics/joyent_module.rb create mode 100644 lib/fog/joyent/models/analytics/joyent_modules.rb create mode 100644 lib/fog/joyent/requests/analytics/describe_analytics.rb diff --git a/lib/fog/joyent.rb b/lib/fog/joyent.rb index c9efb3715..616b37cb9 100644 --- a/lib/fog/joyent.rb +++ b/lib/fog/joyent.rb @@ -4,6 +4,7 @@ module Fog module Joyent extend Fog::Provider + service(:analytics, 'joyent/analytics', 'Analytics') service(:compute, 'joyent/compute', 'Compute') end diff --git a/lib/fog/joyent/analytics.rb b/lib/fog/joyent/analytics.rb new file mode 100644 index 000000000..d8d1548d3 --- /dev/null +++ b/lib/fog/joyent/analytics.rb @@ -0,0 +1,210 @@ +require 'fog/joyent' +require 'fog/joyent/errors' + +module Fog + module Joyent + class Analytics < Fog::Service + requires :joyent_username + + recognizes :joyent_password + recognizes :joyent_url + + recognizes :joyent_keyname + recognizes :joyent_keyfile + recognizes :joyent_keydata + recognizes :joyent_keyphrase + recognizes :joyent_version + request_path 'fog/joyent/requests/analytics' + + request :describe_analytics + + + model_path 'fog/joyent/models/analytics' + + collection :joyent_modules + model :joyent_module + + class Mock + def self.data + @data ||= Hash.new do |hash, key| + hash[key] = {} + end + end + + def data + self.class.data + end + + def initialize(options = {}) + @joyent_username = options[:joyent_username] || Fog.credentials[:joyent_username] + @joyent_password = options[:joyent_password] || Fog.credentials[:joyent_password] + end + + def request(opts) + raise "Not Implemented" + end + end # Mock + + class Real + def initialize(options = {}) + + @connection_options = options[:connection_options] || {} + @persistent = options[:persistent] || false + + @joyent_url = options[:joyent_url] || 'https://us-sw-1.api.joyentcloud.com' + @joyent_version = options[:joyent_version] || '~7' + @joyent_username = options[:joyent_username] + + unless @joyent_username + raise ArgumentError, "options[:joyent_username] required" + end + + if options[:joyent_keyname] + @joyent_keyname = options[:joyent_keyname] + @joyent_keyphrase = options[:joyent_keyphrase] + @key_manager = Net::SSH::Authentication::KeyManager.new(nil, { + :keys_only => true, + :passphrase => @joyent_keyphrase + }) + @header_method = method(:header_for_signature_auth) + + if options[:joyent_keyfile] + if File.exists?(options[:joyent_keyfile]) + @joyent_keyfile = options[:joyent_keyfile] + @key_manager.add(@joyent_keyfile) + else + raise ArgumentError, "options[:joyent_keyfile] provided does not exist." + end + elsif options[:joyent_keydata] + if options[:joyent_keydata].to_s.empty? + raise ArgumentError, 'options[:joyent_keydata] must not be blank' + else + @joyent_keydata = options[:joyent_keydata] + @key_manager.add_key_data(@joyent_keydata) + end + end + elsif options[:joyent_password] + @joyent_password = options[:joyent_password] + @header_method = method(:header_for_basic_auth) + else + raise ArgumentError, "Must provide either a joyent_password or joyent_keyname and joyent_keyfile pair" + end + + @connection = Fog::Connection.new( + @joyent_url, + @persistent, + @connection_options + ) + end + + def request(opts = {}) + opts[:headers] = { + "X-Api-Version" => @joyent_version, + "Content-Type" => "application/json", + "Accept" => "application/json" + }.merge(opts[:headers] || {}).merge(@header_method.call) + + if opts[:body] + opts[:body] = Fog::JSON.encode(opts[:body]) + end + + + response = @connection.request(opts) + if response.headers["Content-Type"] == "application/json" + response.body = json_decode(response.body) + end + + response + rescue Excon::Errors::HTTPStatusError => e + raise_if_error!(e.request, e.response) + end + + private + + def json_decode(body) + parsed = Fog::JSON.decode(body) + decode_time_attrs(parsed) + end + + def header_for_basic_auth + { + "Authorization" => "Basic #{Base64.encode64("#{@joyent_username}:#{@joyent_password}").delete("\r\n")}" + } + end + + def header_for_signature_auth + date = Time.now.utc.httpdate + + # Force KeyManager to load the key(s) + @key_manager.each_identity {} + + key = @key_manager.known_identities.keys.first + + sig = if key.kind_of? OpenSSL::PKey::RSA + @key_manager.sign(key, date)[15..-1] + else + key = OpenSSL::PKey::DSA.new(File.read(@joyent_keyfile), @joyent_keyphrase) + key.sign('sha1', date) + end + + key_id = "/#{@joyent_username}/keys/#{@joyent_keyname}" + key_type = key.class.to_s.split('::').last.downcase.to_sym + + unless [:rsa, :dsa].include? key_type + raise Joyent::Errors::Unauthorized.new('Invalid key type -- only rsa or dsa key is supported') + end + + signature = Base64.encode64(sig).delete("\r\n") + + { + "Date" => date, + "Authorization" => "Signature keyId=\"#{key_id}\",algorithm=\"#{key_type}-sha1\" #{signature}" + } + rescue Net::SSH::Authentication::KeyManagerError => e + raise Joyent::Errors::Unauthorized.new('SSH Signing Error: :#{e.message}', e) + end + + def decode_time_attrs(obj) + if obj.kind_of?(Hash) + obj["created"] = Time.parse(obj["created"]) unless obj["created"].nil? or obj["created"] == '' + obj["updated"] = Time.parse(obj["updated"]) unless obj["updated"].nil? or obj["updated"] == '' + elsif obj.kind_of?(Array) + obj.map do |o| + decode_time_attrs(o) + end + end + + obj + end + + def raise_if_error!(request, response) + case response.status + when 401 then + raise Fog::Compute::Joyent::Errors::Unauthorized.new('Invalid credentials were used', request, response) + when 403 then + raise Fog::Compute::Joyent::Errors::Forbidden.new('No permissions to the specified resource', request, response) + when 404 then + raise Fog::Compute::Joyent::Errors::NotFound.new('Requested resource was not found', request, response) + when 405 then + raise Fog::Compute::Joyent::Errors::MethodNotAllowed.new('Method not supported for the given resource', request, response) + when 406 then + raise Fog::Compute::Joyent::Errors::NotAcceptable.new('Try sending a different Accept header', request, response) + when 409 then + raise Fog::Compute::Joyent::Errors::Conflict.new('Most likely invalid or missing parameters', request, response) + when 414 then + raise Fog::Compute::Joyent::Errors::RequestEntityTooLarge.new('You sent too much data', request, response) + when 415 then + raise Fog::Compute::Joyent::Errors::UnsupportedMediaType.new('You encoded your request in a format we don\'t understand', request, response) + when 420 then + raise Fog::Compute::Joyent::Errors::PolicyNotForfilled.new('You are sending too many requests', request, response) + when 449 then + raise Fog::Compute::Joyent::Errors::RetryWith.new('Invalid API Version requested; try with a different API Version', request, response) + when 503 then + raise Fog::Compute::Joyent::Errors::ServiceUnavailable.new('Either there\'s no capacity in this datacenter, or we\'re in a maintenance window', request, response) + end + end + + end # Real + end + end +end diff --git a/lib/fog/joyent/models/analytics/joyent_module.rb b/lib/fog/joyent/models/analytics/joyent_module.rb new file mode 100644 index 000000000..2fe6fe549 --- /dev/null +++ b/lib/fog/joyent/models/analytics/joyent_module.rb @@ -0,0 +1,13 @@ +require 'fog/core/model' + + +# named 'JoyentModule' to avoid name conflicts with ruby's 'Module' +module Fog + module Joyent + class Analytics + class JoyentModule < Fog::Model + attribute :label + end + end + end +end diff --git a/lib/fog/joyent/models/analytics/joyent_modules.rb b/lib/fog/joyent/models/analytics/joyent_modules.rb new file mode 100644 index 000000000..e69de29bb diff --git a/lib/fog/joyent/requests/analytics/describe_analytics.rb b/lib/fog/joyent/requests/analytics/describe_analytics.rb new file mode 100644 index 000000000..e69de29bb