1
0
Fork 0
mirror of https://github.com/fog/fog.git synced 2022-11-09 13:51:43 -05:00
fog--fog/lib/fog/joyent/compute.rb
2012-05-26 11:27:28 -04:00

243 lines
7.7 KiB
Ruby

require 'fog/joyent'
require 'fog/joyent/errors'
require 'fog/compute'
module Fog
module Compute
class Joyent < Fog::Service
requires :joyent_username
recognizes :joyent_password
recognizes :joyent_url
recognizes :joyent_keyname
recognizes :joyent_keyfile
model_path 'fog/joyent/models/compute'
request_path 'fog/joyent/requests/compute'
request :list_datacenters
# request :get_datacenter
# Keys
collection :keys
model :key
request :list_keys
request :get_key
request :create_key
request :delete_key
# Images
collection :images
model :image
request :list_datasets
request :get_dataset
# Flavors
collection :flavors
model :flavor
request :list_packages
request :get_package
# Servers
collection :servers
model :server
request :list_machines
request :get_machine
request :create_machine
request :start_machine
request :stop_machine
request :reboot_machine
request :resize_machine
request :delete_machine
# Snapshots
collection :snapshots
model :snapshot
request :create_machine_snapshot
request :start_machine_from_snapshot
request :list_machine_snapshots
request :get_machine_snapshot
request :delete_machine_snapshot
request :update_machine_metadata
request :get_machine_metadata
request :delete_machine_metadata
request :delete_all_machine_metadata
# MachineTags
request :add_machine_tags
request :list_machine_tags
request :get_machine_tag
request :delete_machine_tag
request :delete_all_machine_tags
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] || '~6.5'
@joyent_username = options[:joyent_username]
unless @joyent_username
raise ArgumentError, "options[:joyent_username] required"
end
if options[:joyent_keyname] && options[:joyent_keyfile]
if File.exists?(options[:joyent_keyfile])
@joyent_keyname = options[:joyent_keyname]
@joyent_key = File.read(options[:joyent_keyfile])
if @joyent_key.lines.first.include?('-----BEGIN DSA PRIVATE KEY-----')
@key = OpenSSL::PKey::DSA.new(@joyent_key)
elsif @joyent_key.lines.first.include?('-----BEGIN RSA PRIVATE KEY-----')
@key = OpenSSL::PKey::RSA.new(@joyent_key)
else
raise ArgumentError, "options[joyent_keyfile] provided must be an RSA or DSA private key"
end
@header_method = method(:header_for_signature_auth)
else
raise ArgumentError, "options[:joyent_keyfile] provided does not exist."
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(request = {})
request[:headers] = {
"X-Api-Version" => @joyent_version,
"Content-Type" => "application/json",
"Accept" => "application/json"
}.merge(request[:headers] || {}).merge(@header_method.call)
if request[:body]
request[:body] = Fog::JSON.encode(request[:body])
end
response = @connection.request(request)
if response.headers["Content-Type"] == "application/json"
response.body = json_decode(response.body)
end
raise_if_error!(request, response)
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
begin
signature = Base64.encode64(@key.sign("sha256", date)).delete("\r\n")
rescue OpenSSL::PKey::PKeyError => e
if e.message == 'wrong public key type'
puts 'ERROR: Your version of ruby/openssl does not suport DSA key signing'
puts 'see: http://bugs.ruby-lang.org/issues/4734'
puts 'workaround: Please use an RSA key instead'
end
raise
end
key_id = "/#{@joyent_username}/keys/#{@joyent_keyname}"
{
"Date" => date,
"Authorization" => "Signature keyId=\"#{key_id}\",algorithm=\"rsa-sha256\" #{signature}"
}
end
def decode_time_attrs(obj)
if obj.kind_of?(Hash)
obj["created"] = Time.parse(obj["created"]) if obj["created"]
obj["updated"] = Time.parse(obj["updated"]) if 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 Errors::Unauthorized.new('Invalid credentials were used', request, response)
when 403 then
raise Errors::Forbidden.new('No permissions to the specified resource', request, response)
when 404 then
raise Errors::NotFound.new('Requested resource was not found', request, response)
when 405 then
raise Errors::MethodNotAllowed.new('Method not supported for the given resource', request, response)
when 406 then
raise Errors::NotAcceptable.new('Try sending a different Accept header', request, response)
when 409 then
raise Errors::Conflict.new('Most likely invalid or missing parameters', request, response)
when 414 then
raise Errors::RequestEntityTooLarge.new('You sent too much data', request, response)
when 415 then
raise Errors::UnsupportedMediaType.new('You encoded your request in a format we don\'t understand', request, response)
when 420 then
raise Errors::PolicyNotForfilled.new('You are sending too many requests', request, response)
when 449 then
raise Errors::RetryWith.new('Invalid API Version requested; try with a different API Version', request, response)
when 503 then
raise 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