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
Paul Thornthwaite 0e1daf3ddd [GH-2711] Replace Fog::Connection with XML shim
Unlike last attempt this replaces Fog::Connection with
Fog::XML::Connection which should be directly compatible.

Fog::Connection is there for old PRs but should be removed real soon.

Providers using JSON should be able to replace "XML" with "Core" within
their code to cut down on the dependency.

If I get the time I may attempt to clean up some but testing with Mock
will mean that is mostly educated guesswork.
2014-02-27 00:54:17 +00:00

281 lines
9 KiB
Ruby

require 'fog/joyent/core'
require 'fog/joyent/errors'
require 'net/ssh'
module Fog
module Compute
class Joyent < 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
secrets :joyent_password, :joyent_keydata, :joyent_keyphrase
model_path 'fog/joyent/models/compute'
request_path 'fog/joyent/requests/compute'
request :list_datacenters
# request :get_datacenter
# Datacenters
collection :datacenters
model :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
request :list_images
request :get_image
# 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
# Networks
collection :networks
model :network
request :list_networks
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
attr_accessor :joyent_version
attr_accessor :joyent_url
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]
@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::XML::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
if e.response.headers["Content-Type"] == "application/json"
e.response.body = json_decode(e.response.body)
end
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 Joyent::Errors::Unauthorized.new('Invalid credentials were used', request, response)
when 403 then
raise Joyent::Errors::Forbidden.new('No permissions to the specified resource', request, response)
when 404 then
raise Joyent::Errors::NotFound.new('Requested resource was not found', request, response)
when 405 then
raise Joyent::Errors::MethodNotAllowed.new('Method not supported for the given resource', request, response)
when 406 then
raise Joyent::Errors::NotAcceptable.new('Try sending a different Accept header', request, response)
when 409 then
raise Joyent::Errors::Conflict.new('Most likely invalid or missing parameters', request, response)
when 414 then
raise Joyent::Errors::RequestEntityTooLarge.new('You sent too much data', request, response)
when 415 then
raise Joyent::Errors::UnsupportedMediaType.new('You encoded your request in a format we don\'t understand', request, response)
when 420 then
raise Joyent::Errors::PolicyNotForfilled.new('You are sending too many requests', request, response)
when 449 then
raise Joyent::Errors::RetryWith.new('Invalid API Version requested; try with a different API Version', request, response)
when 503 then
raise 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