1
0
Fork 0
mirror of https://github.com/fog/fog.git synced 2022-11-09 13:51:43 -05:00

[digitalocean|compute] initial release

DigitalOcean Compute Service Provider.
See https://www.digitalocean.com/api
This commit is contained in:
Sergio Rubio 2013-01-29 21:25:06 +01:00
parent ed34ead1e7
commit f11926a5a2
30 changed files with 783 additions and 0 deletions

View file

@ -6,6 +6,7 @@ require 'fog/bluebox'
require 'fog/brightbox' require 'fog/brightbox'
require 'fog/cloudstack' require 'fog/cloudstack'
require 'fog/clodo' require 'fog/clodo'
require 'fog/digitalocean'
require 'fog/dnsimple' require 'fog/dnsimple'
require 'fog/dnsmadeeasy' require 'fog/dnsmadeeasy'
require 'fog/dreamhost' require 'fog/dreamhost'

View file

@ -63,6 +63,7 @@ require 'fog/bin/bluebox'
require 'fog/bin/brightbox' require 'fog/bin/brightbox'
require 'fog/bin/cloudstack' require 'fog/bin/cloudstack'
require 'fog/bin/clodo' require 'fog/bin/clodo'
require 'fog/bin/digitalocean'
require 'fog/bin/dnsimple' require 'fog/bin/dnsimple'
require 'fog/bin/dnsmadeeasy' require 'fog/bin/dnsmadeeasy'
require 'fog/bin/dreamhost' require 'fog/bin/dreamhost'

View file

@ -0,0 +1,31 @@
class DigitalOcean < Fog::Bin
class << self
def class_for(key)
case key
when :compute
Fog::Compute::DigitalOcean
else
raise ArgumentError, "Unsupported #{self} service: #{key}"
end
end
def [](service)
@@connections ||= Hash.new do |hash, key|
hash[key] = case key
when :compute
Fog::Logger.warning("DigitalOcean[:compute] is not recommended, use Compute[:digitalocean] for portability")
Fog::Compute.new(:provider => 'DigitalOcean')
else
raise ArgumentError, "Unrecognized service: #{key.inspect}"
end
end
@@connections[service]
end
def services
Fog::DigitalOcean.services
end
end
end

9
lib/fog/digitalocean.rb Normal file
View file

@ -0,0 +1,9 @@
require 'fog/core'
module Fog
module DigitalOcean
extend Fog::Provider
service(:compute, 'digitalocean/compute', 'Compute')
end
end

View file

@ -0,0 +1,93 @@
require 'fog/digitalocean'
require 'fog/compute'
module Fog
module Compute
class DigitalOcean < Fog::Service
requires :digitalocean_api_key
requires :digitalocean_client_id
recognizes :digitalocean_api_url
model_path 'fog/digitalocean/models/compute'
model :server
collection :servers
model :flavor
collection :flavors
model :image
collection :images
model :region
collection :regions
request_path 'fog/digitalocean/requests/compute'
request :list_servers
request :list_images
request :list_regions
request :list_flavors
request :get_server_details
request :create_server
request :destroy_server
# request :digitalocean_resize
class Mock
def self.data
@data ||= Hash.new do |hash, key|
hash[key] = {}
end
end
def self.reset
@data = nil
end
def initialize(options={})
@digitalocean_api_key = options[:digitalocean_api_key]
end
def data
self.class.data[@digitalocean_api_key]
end
def reset_data
self.class.data.delete(@digitalocean_api_key)
end
end
class Real
def initialize(options={})
@digitalocean_api_key = options[:digitalocean_api_key]
@digitalocean_client_id = options[:digitalocean_client_id]
@digitalocean_api_url = options[:digitalocean_api_url] || \
"https://api.digitalocean.com"
@connection = Fog::Connection.new(@digitalocean_api_url)
end
def reload
@connection.reset
end
def request(params)
params[:query] ||= {}
params[:query].merge!(:api_key => @digitalocean_api_key)
params[:query].merge!(:client_id => @digitalocean_client_id)
response = @connection.request(params)
unless response.body.empty?
response.body = Fog::JSON.decode(response.body)
if response.body['status'] != 'OK'
raise Fog::Errors::Error.new
end
end
response
end
end
end
end
end

View file

@ -0,0 +1,14 @@
require 'fog/core/model'
module Fog
module Compute
class DigitalOcean
class Flavor < Fog::Model
identity :id
attribute :name
end
end
end
end

View file

@ -0,0 +1,25 @@
require 'fog/core/collection'
require 'fog/digitalocean/models/compute/flavor'
module Fog
module Compute
class DigitalOcean
class Flavors < Fog::Collection
model Fog::Compute::DigitalOcean::Flavor
def all
load service.list_flavors.body['sizes']
end
def get(id)
all.find { |f| f.id == id }
rescue Fog::Errors::NotFound
nil
end
end
end
end
end

View file

@ -0,0 +1,15 @@
require 'fog/core/model'
module Fog
module Compute
class DigitalOcean
class Image < Fog::Model
identity :id
attribute :name
attribute :distribution
end
end
end
end

View file

@ -0,0 +1,25 @@
require 'fog/core/collection'
require 'fog/digitalocean/models/compute/image'
module Fog
module Compute
class DigitalOcean
class Images < Fog::Collection
model Fog::Compute::DigitalOcean::Image
def all
load service.list_images.body['images']
end
def get(id)
all.find { |f| f.id == id }
rescue Fog::Errors::NotFound
nil
end
end
end
end
end

View file

@ -0,0 +1,14 @@
require 'fog/core/model'
module Fog
module Compute
class DigitalOcean
class Region < Fog::Model
identity :id
attribute :name
end
end
end
end

View file

@ -0,0 +1,25 @@
require 'fog/core/collection'
require 'fog/digitalocean/models/compute/region'
module Fog
module Compute
class DigitalOcean
class Regions < Fog::Collection
model Fog::Compute::DigitalOcean::Region
def all
load service.list_regions.body['regions']
end
def get(id)
all.find { |f| f.id == id }
rescue Fog::Errors::NotFound
nil
end
end
end
end
end

View file

@ -0,0 +1,50 @@
require 'fog/compute/models/server'
module Fog
module Compute
class DigitalOcean
#
# A DigitalOcean Droplet
#
class Server < Fog::Compute::Server
identity :id
attribute :name
attribute :status
attribute :image_id
attribute :region_id
attribute :flavor_id, :aliases => :size_id
attribute :backups_active
def reboot
end
def shutdown
end
def save
raise Fog::Errors::Error.new('Resaving an existing object may create a duplicate') if persisted?
requires :name, :flavor_id, :image_id, :region_id
meta_hash = {}
options = {
'name' => name,
'size_id' => flavor_id,
'image_id' => image_id,
'region_id' => region_id,
}
data = service.create_server name, flavor_id, image_id, region_id
merge_attributes(data.body['droplet'])
true
end
def destroy
service.destroy_server id
end
def ready?
status == 'active'
end
end
end
end
end

View file

@ -0,0 +1,27 @@
require 'fog/core/collection'
require 'fog/digitalocean/models/compute/server'
module Fog
module Compute
class DigitalOcean
class Servers < Fog::Collection
model Fog::Compute::DigitalOcean::Server
def all
load service.list_servers.body['droplets']
end
def get(id)
if server = service.get_server_details(id).body['droplet']
new server
end
rescue Fog::Errors::NotFound
nil
end
end
end
end
end

View file

@ -0,0 +1,47 @@
module Fog
module Compute
class DigitalOcean
class Real
#
# FIXME: missing ssh keys support
#
def create_server( name,
size_id,
image_id,
region_id,
options = {} )
query_args = []
query_hash = {
:name => name,
:size_id => size_id,
:image_id => image_id,
:region_id => region_id
}.each { |k, v| query_args << "#{k}=#{v}" }
query_hash.each { |k, v| query_args << "#{k}=#{v}" }
request(
:expects => [200],
:method => 'GET',
:path => 'droplets/new',
:query => query_hash
)
end
end
class Mock
def create_server( name,
size_id,
image_id,
region_id,
options = {} )
Fog::Mock.not_implemented
end
end
end
end
end

View file

@ -0,0 +1,28 @@
module Fog
module Compute
class DigitalOcean
class Real
#
# FIXME: missing ssh keys support
#
def destroy_server( id )
request(
:expects => [200],
:method => 'GET',
:path => "droplets/#{id}/destroy"
)
end
end
class Mock
def destroy_server( id )
Fog::Mock.not_implemented
end
end
end
end
end

View file

@ -0,0 +1,25 @@
module Fog
module Compute
class DigitalOcean
class Real
def get_server_details(server_id)
request(
:expects => [200],
:method => 'GET',
:path => "droplets/#{server_id}"
)
end
end
class Mock
def get_server_details(server_id)
Fog::Mock.not_implemented
end
end
end
end
end

View file

@ -0,0 +1,25 @@
module Fog
module Compute
class DigitalOcean
class Real
def list_flavors(options = {})
request(
:expects => [200],
:method => 'GET',
:path => 'sizes',
)
end
end
class Mock
def list_flavors
Fog::Mock.not_implemented
end
end
end
end
end

View file

@ -0,0 +1,25 @@
module Fog
module Compute
class DigitalOcean
class Real
def list_images(options = {})
request(
:expects => [200],
:method => 'GET',
:path => 'images',
)
end
end
class Mock
def list_images
Fog::Mock.not_implemented
end
end
end
end
end

View file

@ -0,0 +1,25 @@
module Fog
module Compute
class DigitalOcean
class Real
def list_regions(options = {})
request(
:expects => [200],
:method => 'GET',
:path => 'regions',
)
end
end
class Mock
def list_regions
Fog::Mock.not_implemented
end
end
end
end
end

View file

@ -0,0 +1,25 @@
module Fog
module Compute
class DigitalOcean
class Real
def list_servers(options = {})
request(
:expects => [200],
:method => 'GET',
:path => 'droplets',
)
end
end
class Mock
def list_servers
Fog::Mock.not_implemented
end
end
end
end
end

View file

@ -0,0 +1,30 @@
Shindo.tests("Fog::Compute[:digitalocean] | flavor model", ['digitalocean', 'compute']) do
service = Fog::Compute[:digitalocean]
flavor = service.flavors.first
tests('The flavor model should') do
tests('have the action') do
test('reload') { flavor.respond_to? 'reload' }
end
tests('have attributes') do
model_attribute_hash = flavor.attributes
attributes = [
:id,
:name,
]
tests("The flavor model should respond to") do
attributes.each do |attribute|
test("#{attribute}") { flavor.respond_to? attribute }
end
end
tests("The attributes hash should have key") do
attributes.each do |attribute|
test("#{attribute}") { model_attribute_hash.has_key? attribute }
end
end
end
end
end

View file

@ -0,0 +1,31 @@
Shindo.tests("Fog::Compute[:digitalocean] | image model", ['digitalocean', 'compute']) do
service = Fog::Compute[:digitalocean]
image = service.images.first
tests('The image model should') do
tests('have the action') do
test('reload') { image.respond_to? 'reload' }
end
tests('have attributes') do
model_attribute_hash = image.attributes
attributes = [
:id,
:name,
:distribution
]
tests("The image model should respond to") do
attributes.each do |attribute|
test("#{attribute}") { image.respond_to? attribute }
end
end
tests("The attributes hash should have key") do
attributes.each do |attribute|
test("#{attribute}") { model_attribute_hash.has_key? attribute }
end
end
end
end
end

View file

@ -0,0 +1,30 @@
Shindo.tests("Fog::Compute[:digitalocean] | region model", ['digitalocean', 'compute']) do
service = Fog::Compute[:digitalocean]
region = service.regions.first
tests('The region model should') do
tests('have the action') do
test('reload') { region.respond_to? 'reload' }
end
tests('have attributes') do
model_attribute_hash = region.attributes
attributes = [
:id,
:name,
]
tests("The region model should respond to") do
attributes.each do |attribute|
test("#{attribute}") { region.respond_to? attribute }
end
end
tests("The attributes hash should have key") do
attributes.each do |attribute|
test("#{attribute}") { model_attribute_hash.has_key? attribute }
end
end
end
end
end

View file

@ -0,0 +1,48 @@
Shindo.tests("Fog::Compute[:digitalocean] | server model", ['digitalocean', 'compute']) do
service = Fog::Compute[:digitalocean]
server = service.servers.create :name => 'fog-test',
:image_id => service.images.first.id,
:region_id => service.regions.first.id,
:flavor_id => service.flavors.first.id
tests('The server model should') do
# Wait for the server to come up
begin
server.wait_for(120) { server.reload rescue nil; server.ready? }
rescue Fog::Errors::TimeoutError
# Server bootstrap took more than 120 secs!
end
tests('have the action') do
test('reload') { server.respond_to? 'reload' }
%w{
shutdown
reboot
}.each do |action|
test(action) { server.respond_to? action }
end
end
tests('have attributes') do
model_attribute_hash = server.attributes
attributes = [
:id,
:name,
:status,
:backups_active,
:flavor_id,
:region_id,
:image_id
]
tests("The server model should respond to") do
attributes.each do |attribute|
test("#{attribute}") { server.respond_to? attribute }
end
end
end
end
server.destroy
end

View file

@ -0,0 +1,22 @@
Shindo.tests('Fog::Compute[:digitalocean] | create_server request', ['digitalocean', 'compute']) do
service = Fog::Compute[:digitalocean]
tests('success') do
test('#create_server') do
data = Fog::Compute[:digitalocean].create_server 'fog-test',
service.flavors.first.id,
service.images.first.id,
service.regions.first.id
# wait some time before destroying the server
# otherwise the request could be ignored, YMMV
sleep 120
data.body['status'] == 'OK' and \
(service.destroy_server(data.body['droplet']['id']).body['status'] == 'OK')
end
end
end

View file

@ -0,0 +1,11 @@
Shindo.tests('Fog::Compute[:digitalocean] | get_server_details request', ['digitalocean', 'compute']) do
tests('success') do
test('#get_server_details') do
Fog::Compute[:digitalocean].get_server_details(nil).body.is_a? Hash
end
end
end

View file

@ -0,0 +1,23 @@
Shindo.tests('Fog::Compute[:digitalocean] | list_flavors request', ['digitalocean', 'compute']) do
# {"id":2,"name":"Amsterdam 1"}
@flavor_format = {
'id' => Integer,
'name' => String,
}
tests('success') do
tests('#list_flavor') do
flavors = Fog::Compute[:digitalocean].list_flavors.body
test 'returns a Hash' do
flavors.is_a? Hash
end
tests('flavor').formats(@flavor_format, false) do
flavors['sizes'].first
end
end
end
end

View file

@ -0,0 +1,24 @@
Shindo.tests('Fog::Compute[:digitalocean] | list_images request', ['digitalocean', 'compute']) do
# {"id"=>1601, "name"=>"CentOS 5.8 x64", "distribution"=>"CentOS"}
@image_format = {
'id' => Integer,
'name' => String,
'distribution' => String
}
tests('success') do
tests('#list_images') do
images = Fog::Compute[:digitalocean].list_images.body
test 'returns a Hash' do
images.is_a? Hash
end
tests('image').formats(@image_format, false) do
images['images'].first
end
end
end
end

View file

@ -0,0 +1,23 @@
Shindo.tests('Fog::Compute[:digitalocean] | list_regions request', ['digitalocean', 'compute']) do
# {"id":2,"name":"Amsterdam 1"}
@region_format = {
'id' => Integer,
'name' => String,
}
tests('success') do
tests('#list_regions') do
regions = Fog::Compute[:digitalocean].list_regions.body
test 'returns a Hash' do
regions.is_a? Hash
end
tests('region').formats(@region_format, false) do
regions['regions'].first
end
end
end
end

View file

@ -0,0 +1,11 @@
Shindo.tests('Fog::Compute[:digitalocean] | list_servers request', ['digitalocean', 'compute']) do
tests('success') do
test('#list_servers') do
Fog::Compute[:digitalocean].list_servers.body.is_a? Hash
end
end
end