GH-690 Joyent Cloud Provider #739

Closed
wants to merge 9 commits into
from
Jump to file
+1,497 −1
Split
View
@@ -64,6 +64,7 @@ def collections
require 'fog/bin/glesys'
require 'fog/bin/go_grid'
require 'fog/bin/google'
+require 'fog/bin/joyent'
require 'fog/bin/libvirt'
require 'fog/bin/linode'
require 'fog/bin/local'
View
@@ -0,0 +1,31 @@
+class Joyent < Fog::Bin
+ class << self
+
+ def class_for(key)
+ case key
+ when :compute
+ Fog::Compute::Joyent
+ else
+ raise ArgumentError, "Unrecognized service: #{key}"
+ end
+ end
+
+ def [](service)
+ @@connections ||= Hash.new do |hash, key|
+ hash[key] = case key
+ when :compute
+ Fog::Logger.warning("Joyent[:compute] is not recommended, use Compute[:joyent] for portability")
+ Fog::Compute.new(:provider => 'Joyent')
+ else
+ raise ArgumentError, "Unrecognized service: #{key.inspect}"
+ end
+ end
+ @@connections[service]
+ end
+
+ def services
+ Fog::Joyent.services
+ end
+
+ end
+end
View
@@ -32,6 +32,9 @@ def self.new(attributes)
when :gogrid
require 'fog/go_grid/compute'
Fog::Compute::GoGrid.new(attributes)
+ when :joyent
+ require 'fog/joyent/compute'
+ Fog::Compute::Joyent.new(attributes)
when :libvirt
require 'fog/libvirt/compute'
Fog::Compute::Libvirt.new(attributes)
View
@@ -0,0 +1,8 @@
+module Fog
+ module Joyent
+ extend Fog::Provider
+
+ service(:compute, 'joyent/compute', 'Compute')
+
+ end
+end
@@ -0,0 +1,194 @@
+require File.expand_path(File.join(File.dirname(__FILE__), '..', 'brightbox'))
+require 'fog/compute'
+require 'multi_json'
+
+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])
+
+ @rsa = OpenSSL::PKey::RSA.new(@joyent_key)
+
+ @header_method = method(:header_for_signature)
+ 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)
+ 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_options = {})
+ (request_options[:headers] ||= {}).merge!({
+ "X-Api-Version" => @joyent_version,
+ "Content-Type" => "application/json",
+ "Accept" => "application/json"
+ }).merge!(@header_method.call)
+
+ if request_options[:body]
+ request_options[:body] = MultiJson.encode(request_options[:body])
+ end
+
+ response = @connection.request(request_options)
+
+ if response.headers["Content-Type"] == "application/json"
+ response.body = MultiJson.decode(response.body)
+ response.body = decode_time_props(response.body)
+ end
+
+ response
+ end
+
+ private
+
+ def header_for_basic
+ {
+ "Authorization" => "Basic #{Base64.encode64("#{@joyent_username}:#{@joyent_password}").delete("\r\n")}"
+ }
+ end
+
+ def header_for_signature
+ date = Time.now.utc.httpdate
+ signature = Base64.encode64(@rsa.sign("sha256", date)).delete("\r\n")
+ key_id = "/#{@joyent_username}/keys/#{@joyent_keyname}"
+
+ {
+ "Date" => date,
+ "Authorization" => "Signature keyId=\"#{key_id}\",algorithm=\"rsa-sha256\" #{signature}"
+ }
+ end
+
+ def decode_time_props(obj)
+ if obj.kind_of?(Hash)
+ if obj["created"]
+ obj["created"] = Time.parse(obj["created"])
+ end
+
+ if obj["updated"]
+ obj["updated"] = Time.parse(obj["updated"])
+ end
+ elsif obj.kind_of?(Array)
+ obj.map do |o|
+ decode_time_props(o)
+ end
+ end
+ obj
+ end
+ end # Real
+ end
+ end
+end
@@ -0,0 +1,17 @@
+module Fog
+ module Compute
+ class Joyent
+ class Flavor < Fog::Model
+
+ identity :name
+
+ attribute :name
+ attribute :memory
+ attribute :swap
+ attribute :disk
+ attribute :default
+
+ end
+ end
+ end
+end
@@ -0,0 +1,25 @@
+require 'fog/core/collection'
+require 'fog/joyent/models/compute/flavor'
+
+module Fog
+ module Compute
+
+ class Joyent
+ class Flavors < Fog::Collection
+
+ model Fog::Compute::Joyent::Flavor
+
+ def all
+ load(connection.list_packages().body)
+ end
+
+ def get(id)
+ data = connection.get_package(id).body
+ new(data)
+ end
+
+ end
+ end # Joyent
+
+ end # Compute
+end # Fog
@@ -0,0 +1,18 @@
+module Fog
+ module Compute
+ class Joyent
+ class Image < Fog::Model
+
+ identity :id
+
+ attribute :name
+ attribute :os
+ attribute :type
+ attribute :version
+ attribute :created, :type => :time
+ attribute :default
+
+ end
+ end
+ end
+end
@@ -0,0 +1,25 @@
+require 'fog/core/collection'
+require 'fog/joyent/models/compute/image'
+
+module Fog
+ module Compute
+
+ class Joyent
+ class Images < Fog::Collection
+
+ model Fog::Compute::Joyent::Image
+
+ def all
+ load(connection.list_datasets().body)
+ end
+
+ def get(id)
+ data = connection.get_dataset(id).body
+ new(data)
+ end
+
+ end # Images
+ end # Joyent
+
+ end # Compute
+end # Fog
@@ -0,0 +1,19 @@
+module Fog
+ module Compute
+ class Joyent
+ class Key < Fog::Model
+ identity :name
+
+ attribute :name
+ attribute :key
+
+ attribute :created, :type => :time
+
+ def destroy
+ requires :name
+ self.connection.delete_key(name)
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,34 @@
+require 'fog/joyent/models/compute/key'
+
+module Fog
+ module Compute
+ class Joyent
+ class Keys < Fog::Collection
+
+ model Fog::Compute::Joyent::Key
+
+ def all
+ data = connection.list_keys.body
+ load(data)
+ end
+
+ def get(keyname)
+ data = connection.get_key(keyname).body
+ if data
+ new(data)
+ else
+ nil
+ end
+ end
+
+ def create(params = {})
+ raise ArgumentError, "Key name required" unless params.key?(:name)
+ raise ArgumentError, "Key body required" unless params.key?(:body)
+
+ self.connection.create_key(params)
+ end
+
+ end
+ end
+ end
+end
Oops, something went wrong. Retry.