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

Merge pull request #772 from rubiojr/dreamhost

[dreamhost|dns] initial import
This commit is contained in:
Sergio Rubio 2013-01-28 02:45:33 -08:00
commit a67198e885
25 changed files with 885 additions and 1 deletions

View file

@ -65,6 +65,7 @@ require 'fog/bin/cloudstack'
require 'fog/bin/clodo'
require 'fog/bin/dnsimple'
require 'fog/bin/dnsmadeeasy'
require 'fog/bin/dreamhost'
require 'fog/bin/dynect'
require 'fog/bin/ecloud'
require 'fog/bin/glesys'

31
lib/fog/bin/dreamhost.rb Normal file
View file

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

View file

@ -81,6 +81,7 @@ An alternate file may be used by placing its path in the FOG_RC environment vari
:dnsimple_password:
:dnsmadeeasy_api_key:
:dnsmadeeasy_secret_key:
:dreamhost_api_key:
:cloudstack_host:
:cloudstack_api_key:
:cloudstack_secret_access_key:

View file

@ -20,6 +20,9 @@ module Fog
when :dnsmadeeasy
require 'fog/dnsmadeeasy/dns'
Fog::DNS::DNSMadeEasy.new(attributes)
when :dreamhost
require 'fog/dreamhost/dns'
Fog::DNS::Dreamhost.new(attributes)
when :dynect
require 'fog/dynect/dns'
Fog::DNS::Dynect.new(attributes)

11
lib/fog/dreamhost.rb Normal file
View file

@ -0,0 +1,11 @@
require(File.expand_path(File.join(File.dirname(__FILE__), 'core')))
module Fog
module Dreamhost
extend Fog::Provider
service(:dns, 'dreamhost/dns', 'DNS')
end
end

86
lib/fog/dreamhost/dns.rb Normal file
View file

@ -0,0 +1,86 @@
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'dreamhost'))
require 'fog/dns'
module Fog
module DNS
class Dreamhost < Fog::Service
requires :dreamhost_api_key
model_path 'fog/dreamhost/models/dns'
model :record
model :zone
collection :records
collection :zones
request_path 'fog/dreamhost/requests/dns'
request :create_record
request :list_records
request :delete_record
class Mock
def self.data
@data ||= Hash.new do |hash, key|
hash[key] = {}
end
end
def self.reset
@data = nil
end
def initialize(options={})
@dreamhost_api_key = options[:dreamhost_api_key]
end
def data
self.class.data
end
def reset_data
self.class.data.delete
end
end
class Real
def initialize(options={})
require 'multi_json'
@dreamhost_api_key = options[:dreamhost_api_key]
if options[:dreamhost_url]
uri = URI.parse(options[:dreamhost_url])
options[:host] = uri.host
options[:port] = uri.port
options[:scheme] = uri.scheme
end
@host = options[:host] || "api.dreamhost.com"
@persistent = options[:persistent] || false
@port = options[:port] || 443
@scheme = options[:scheme] || 'https'
@connection = Fog::Connection.new("#{@scheme}://#{@host}:#{@port}", @persistent)
end
def reload
@connection.reset
end
def request(params)
params[:query].merge!( { :key => @dreamhost_api_key,
:format => 'json' } )
response = @connection.request(params)
unless response.body.empty?
response.body = MultiJson.decode(response.body)
end
if response.body['result'] != 'success'
raise response.body['data']
end
response
end
end
end
end
end

View file

@ -0,0 +1,105 @@
# Getting started with Fog::DNS and Dreamhost (2013/01/21)
You'll need a [Dreamhost](http://www.dreamhost.com) account and API key
to use this.
See http://wiki.dreamhost.com/API.
Create an API key selecting **'All dns functions'** to be able to add/remove/list
records.
## Create the service
We need to create the service first, using the API key from our account:
```ruby
require 'fog'
require 'pp'
dh = Fog::DNS.new( :provider => "Dreamhost",
:dreamhost_api_key => '6SHU5P2HLDAYECUM' )
```
## List all the DNS zones
This will list all the DNS zones avaialble in your account:
```ruby
dh.zones.each do |zone|
puts zone.domain
end
```
## Retrieve all the records
List all the records available in your Dreamhost account, accross all the zones:
```ruby
dh.records.each do |r|
puts r.name
end
```
If you want to fetch all the records in a single zone:
```ruby
zone = dh.zones.get 'fog-dream.com'
zone.records.each do |r|
# do something with the record
end
```
See http://wiki.dreamhost.com/API/Dns_commands#dns-list_records
## Retrieve a single record
Get a single record and do something with the attributes:
```ruby
rec = dh.records.get 'msn.jabber.groo.com'
rec.type # A, CNAME, TXT, etc
rec.zone # zone the record belongs to
rec.account_id # Dreamhost account ID
rec.comment # Record text comment
rec.value # record value
```
## Create a new A record
Let's create a new A record:
```
zone = dh.zones.get 'rbel.co'
zone.records.create :name => 'stuff.rbel.co',
:type => 'TXT',
:value => 'foobar bar bar'
```
Since Dreamhost API does not support the concept of zone,
you can also use this code to accomplish the same thing:
```ruby
dh.records.create(
:name => 'stuff.rbel.co',
:type => 'A',
:value => '8.8.8.8'
)
```
## Destroy all the records in a zone
```ruby
(dh.zones.get 'rbel.co').records.each do |rec|
rec.destroy
end
```
## Resources
The Dreamhost API:
http://wiki.dreamhost.com/Application_programming_interface
DNS API commands:
http://wiki.dreamhost.com/API/Dns_commands

View file

@ -0,0 +1,35 @@
require 'fog/core/model'
module Fog
module DNS
class Dreamhost
class Record < Fog::Model
identity :name, :aliases => 'record'
attribute :value
attribute :zone
attribute :type
attribute :editable
attribute :account_id
attribute :comment
def destroy
service.delete_record(name, type, value)
true
end
def save
requires :name, :type, :value
data = service.create_record(name, type, value, comment).body
merge_attributes(data)
true
end
end
end
end
end

View file

@ -0,0 +1,38 @@
require 'fog/core/collection'
require 'fog/dreamhost/models/dns/record'
module Fog
module DNS
class Dreamhost
class Records < Fog::Collection
model Fog::DNS::Dreamhost::Record
def all(filter = {})
clear
if filter[:zone]
data = service.list_records.body['data'].find_all { |r| r['zone'] == filter[:zone] }
else
data = service.list_records.body['data']
end
load(data)
end
def get(record_name)
data = service.list_records.body['data'].find { |r| r['record'] == record_name }
new(data)
rescue Excon::Errors::NotFound
nil
end
end
def new(attributes = {})
requires :zone
super({ :zone => zone }.merge!(attributes))
end
end
end
end

View file

@ -0,0 +1,59 @@
require 'fog/core/model'
require 'fog/dreamhost/models/dns/records'
module Fog
module DNS
class Dreamhost
#
# Dreamhost API has no concept of 'Zone', but we
# can emulate it.
#
# http://wiki.dreamhost.com/API/Dns_commands
#
class Zone < Fog::Model
identity :id
attribute :domain, :aliases => 'name'
#
# There's no destroy API call
#
def destroy
raise NotImplementedError.new
end
#
# Return a list of records for this zone
#
# Since Dreamhost does not support zones, this is
# emulated. Iterates over all the records and discards
# the ones where Record.zone != domain (the current zone domain)
#
def records
service.records.all :zone => domain
end
#
# Return the Dreamhost nameserver list
#
def nameservers
[
"ns1.dreamhost.com",
"ns2.dreamhost.com",
"ns3.dreamhost.com",
]
end
#
# There's no zone create API call
#
def save
raise NotImplementedError.new
end
end
end
end
end

View file

@ -0,0 +1,41 @@
require 'fog/core/collection'
require 'fog/dreamhost/models/dns/zone'
module Fog
module DNS
class Dreamhost
#
# Dreamhost API has no concept of 'Zone', but we
# can emulate it.
#
# http://wiki.dreamhost.com/API/Dns_commands
#
class Zones < Fog::Collection
model Fog::DNS::Dreamhost::Zone
def all
clear
zones = []
zones_added = []
service.records.each do |r|
unless zones_added.include?(r.zone)
zones << { :id => r.zone, :domain => r.zone }
zones_added << r.zone
end
end
load(zones)
end
def get(zone_id)
service.zones.find { |z| z.domain == zone_id }
rescue Excon::Errors::NotFound
nil
end
end
end
end
end

View file

@ -0,0 +1,32 @@
module Fog
module DNS
class Dreamhost
class Mock
def create_record(record, type, value, comment = "")
Fog::Mock.not_implemented
end
end
class Real
def create_record(record, type, value, comment = "")
request( :expects => 200,
:method => 'GET',
:path => "/",
:query => {
:record => record,
:type => type,
:value => value,
:cmd => 'dns-add_record',
:comment => comment
}
)
end
end
end
end
end

View file

@ -0,0 +1,31 @@
module Fog
module DNS
class Dreamhost
class Mock
def delete_record(name, type, value)
raise Fog::Mock.not_implemented
end
end
class Real
def delete_record(name, type, value)
request( :expects => 200,
:method => "GET",
:path => "/",
:query => {
:cmd => 'dns-remove_record',
:type => type,
:record => name,
:value => value,
}
)
end
end
end
end
end

View file

@ -0,0 +1,25 @@
module Fog
module DNS
class Dreamhost
class Mock
def request(*args)
Fog::Mock.not_implemented
end
end
class Real
def list_records
request( :expects => 200,
:method => "GET",
:path => "/",
:query => { :cmd => 'dns-list_records' } )
end
end
end
end
end

View file

@ -6,6 +6,7 @@ require 'fog/cloudstack'
require 'fog/clodo'
require 'fog/dnsimple'
require 'fog/dnsmadeeasy'
require 'fog/dreamhost'
require 'fog/dynect'
require 'fog/ecloud'
require 'fog/glesys'
@ -30,4 +31,4 @@ require 'fog/vmfusion'
require 'fog/vsphere'
require 'fog/voxel'
require 'fog/xenserver'
require 'fog/zerigo'
require 'fog/zerigo'

56
tests/dreamhost/README.md Normal file
View file

@ -0,0 +1,56 @@
# Testing the Dreamhost DNS API
Dreamhost API sandbox only permits read-only commands, so you'll need a Dreamhost
PS account for the testing and a dedicated domain.
See http://wiki.dreamhost.com/Application_programming_interface#Test_Account
## Create an API key
You'll need a Dreamhost (PS I think) account and a dedicated domain for testing.
1. Go to the Dreamhost web panel and create an API key to manage DNS records
https://panel.dreamhost.com/index.cgi?tree=home.api
Select 'All dns functions' for the new API key to be able to add/remove/list
records.
2. Create a .fog file in the tests/ directory with the following contents:
```yaml
:default:
:dreamhost_api_key: SDFASDFWQWASDFASDFAS
```
Where dreamhost_api_key is the key you created in the previous step.
3. Update the test_domain helper in tests/dreamhost/helper.rb to use your own
domain for testing. You will also need at least a record created via
the Dreamhost Webpanel (you'll get a **no_such_zone** error otherwise).
I usually create a do-not-delete.my-domain.com record. The tests skip that
record when cleaning up (see the do_not_delete_record helper).
4. Run the tests
```
shindo tests/dreamhost
```
## Notes
The API is rate limited, so do not smash the DH servers too often. Two
consecutive test runs will trigger the rate limit.
You'll see a **slow_down_bucko** error if the frequency is too high.
http://wiki.dreamhost.com/Application_programming_interface#Rate_Limit
## Resources
Dreamhost API:
http://wiki.dreamhost.com/Application_programming_interface
Dreamhost DNS API:
http://wiki.dreamhost.com/API/Dns_commands

View file

@ -0,0 +1,20 @@
Shindo.tests('Fog::DNS[:dreamhost]', ['dreamhost', 'dns']) do
service = Fog::DNS[:dreamhost]
tests("collections") do
%w{ records zones }.each do |collection|
test("it should respond to #{collection}") { service.respond_to? collection }
test("it should respond to #{collection}.all") { eval("service.#{collection}").respond_to? 'all' }
test("it should respond to #{collection}.get") { eval("service.#{collection}").respond_to? 'get' }
end
end
tests("requests") do
%w{ list_records create_record delete_record }.each do |request|
test("it should respond to #{request}") { service.respond_to? request }
end
end
end

19
tests/dreamhost/helper.rb Normal file
View file

@ -0,0 +1,19 @@
def test_domain
'fog-dream.com'
end
def do_not_delete_record
"do-not-delete.#{test_domain}"
end
## Cleanup
# We need to have at least one record defined for the Dreamhost DNS api to work
# or you will get a no_such_zone runtime error
# The first record needs to be created using the Dreamhost Web panel AFAIK
#
def cleanup_records
Fog::DNS[:dreamhost].records.each do |r|
# Do not delete the 'do-not-delete' record, we need it for the tests
r.destroy if r.name =~ /#{test_domain}/ and r.name != do_not_delete_record
end
end

View file

@ -0,0 +1,73 @@
Shindo.tests("Fog::DNS[:dreamhost] | record", ['dreamhost', 'dns']) do
service = Fog::DNS[:dreamhost]
record = service.records.first
tests('#attributes') do
tests('should have') do
model_attribute_hash = record.attributes
attributes = [
:name,
:value,
:zone,
:type,
:editable,
:account_id,
:comment,
]
attributes.each do |attribute|
test("#{attribute} method") { record.respond_to? attribute }
end
attributes.each do |attribute|
test("#{attribute} key") { model_attribute_hash.has_key? attribute }
end
end
test('be a kind of Fog::DNS::Dreamhost::Record') do
record.kind_of? Fog::DNS::Dreamhost::Record
end
tests('Write operations') do
name = "test.#{test_domain}"
r = service.records.create :name => name,
:type => 'A',
:value => "8.8.8.8"
sleep 10
tests('#save') do
test('returns Fog::DNS::Dreamhost::Record') do
r.is_a? Fog::DNS::Dreamhost::Record
end
test('value is 8.8.8.8') do
r.value == '8.8.8.8'
end
test("name is #{name}") do
r.name == name
end
test("listed") do
!(service.records.find { |r| r.name == name }).nil?
end
end
tests('#destroy') do
test('returns true') { r.destroy == true }
test('destroyed record not listed') do
(service.records.find { |r| r.name == name }).nil?
end
end
tests('#save from zone') do
name = "zone-create.#{test_domain}"
r = service.zones.first.records.create :name => name,
:type => 'A',
:value => "8.8.8.8"
sleep 10
test("listed") do
!(service.records.find { |r| r.name == name }).nil?
end
end
end
end
# cleanup
cleanup_records
end

View file

@ -0,0 +1,29 @@
Shindo.tests("Fog::DNS[:dreamhost] | records", ['dreamhost', 'dns']) do
service = Fog::DNS[:dreamhost]
tests('#all') do
records = service.records
test('should be an array') { records.is_a? Array }
test('should not be empty') { !records.empty? }
tests('should list Fog::DNS::Dreamhost::Record') do
records.each do |r|
test("as records") { r.is_a?(Fog::DNS::Dreamhost::Record) }
end
end
end
tests('#get') do
tests('should fetch a record') do
record = service.records.get do_not_delete_record
test('should be a Fog::DNS::Dreamhost::Record') do
record.is_a? Fog::DNS::Dreamhost::Record
end
end
end
end

View file

@ -0,0 +1,62 @@
Shindo.tests("Fog::DNS[:dreamhost] | zone", ['dreamhost', 'dns']) do
service = Fog::DNS[:dreamhost]
zone = service.zones.first
tests('#attributes') do
tests('should have') do
model_attribute_hash = zone.attributes
attributes = [
:domain,
:id,
]
attributes.each do |attribute|
test("#{attribute} method") { zone.respond_to? attribute }
end
attributes.each do |attribute|
test("#{attribute} key") { model_attribute_hash.has_key? attribute }
end
end
test('be a kind of Fog::DNS::Dreamhost::Zone') do
zone.kind_of? Fog::DNS::Dreamhost::Zone
end
tests('Write operations') do
name = "#{test_domain}"
tests('#save') do
# Does not capture the exception for some reason
#raises(NotImplementedError, 'raises NotImplementedError') do
# service.zones.create :domain => name
#end
test 'raises NotImplementedError' do
begin
service.zones.create :domain => name
false
rescue NotImplementedError => e
true
end
end
end
tests('#destroy') do
test 'raises NotImplementedError' do
begin
zone.destroy
false
rescue NotImplementedError => e
true
end
end
end
tests('#records') do
zone.records.each do |r|
test('list records') { r.is_a? Fog::DNS::Dreamhost::Record }
test('zone matches') { r.zone == test_domain }
end
end
end
end
end

View file

@ -0,0 +1,29 @@
Shindo.tests("Fog::DNS[:dreamhost] | Zones Collection", ['dreamhost', 'dns']) do
service = Fog::DNS[:dreamhost]
tests('#all') do
zones = service.zones
test('should be an array') { zones.is_a? Array }
test('should not be empty') { !zones.empty? }
tests('should list Fog::DNS::Dreamhost::Zone') do
zones.each do |r|
test("as zone") { r.is_a?(Fog::DNS::Dreamhost::Zone) }
end
end
end
tests('#get') do
tests('should fetch a zone') do
zone = service.zones.get test_domain
test('should be a Fog::DNS::Dreamhost::Zone') do
zone.is_a? Fog::DNS::Dreamhost::Zone
end
end
end
end

View file

@ -0,0 +1,39 @@
Shindo.tests('Fog::DNS[:dreamhost] | create_record request', ['dreamhost', 'dns']) do
tests("success") do
test("create an A resource record without comment") do
name = "foo.testing.#{test_domain}"
type = "A"
value = "1.2.3.4"
response = Fog::DNS[:dreamhost].create_record(name, type, value)
response.body['result'] == 'success'
end
test("create an A resource record with comment") do
name = "foo2.testing.#{test_domain}"
type = "A"
value = "1.2.3.4"
comment = "test"
response = Fog::DNS[:dreamhost].create_record(name, type, value, comment)
response.body['result'] == 'success'
end
test("create TXT record") do
name = "txt.testing.#{test_domain}"
type = "txt"
value = "foobar"
comment = "test"
response = Fog::DNS[:dreamhost].create_record(name, type, value, comment)
response.body['result'] == 'success'
end
end
# helper
cleanup_records
end

View file

@ -0,0 +1,26 @@
Shindo.tests('Fog::DNS[:dreamhost] | delete_record request', ['dreamhost', 'dns']) do
tests("success") do
test("delete testing records") do
name = "delete-test.#{test_domain}"
type = "A"
value = "1.2.3.4"
comment = "test"
Fog::DNS[:dreamhost].create_record(name, type, value, comment)
response = Fog::DNS[:dreamhost].delete_record(name, type, value)
response.body['result'] == 'success'
end
end
tests( 'failure') do
raises(RuntimeError, 'deleting non-existent record') do
Fog::DNS[:dreamhost].delete_record('foo.bar.bar', 'A', '1.2.3.4')
end
end
# helper
cleanup_records
end

View file

@ -0,0 +1,31 @@
Shindo.tests('Fog::DNS[:dreamhost] | list_records request', ['dreamhost', 'dns']) do
tests("success") do
response = Fog::DNS[:dreamhost].list_records
test("should return 200") do
if response.status == 200
@records = response.body["data"]
end
(response.status == 200) and (response.body.size == 2)
end
test("data should be an Array") do
@records.is_a? Array
end
tests("should return records") do
%w{type zone value comment record}.each do |elem|
test("with #{elem}") do
@records.first[elem].is_a? String
end
end
end
end
# helper
cleanup_records
end